MATLAB读取NIfTI医学图像:从load_nii到可视化与重采样
2026/9/16 21:02:48 网站建设 项目流程

简介:对于从事神经影像与生物医学数据分析的MATLAB用户,这是一套专门用来打开、读取和处理NIfTI格式图像的工具包。NIfTI是脑成像研究的通用格式,但MATLAB原生支持有限,该工具集正好补齐了读取头信息、导入图像矩阵、交互式显示、坐标变换、重切片等常用功能,帮助科研人员在MATLAB环境中顺利完成MRI等影像数据的预处理与分析。压缩包共50个文件、4.24MB,以43个m源码文件为主体,覆盖load_nii、save_nii、view_nii等核心函数的原始代码,便于理解与二次开发;附带3份PDF帮助文档和2个txt说明,对文件格式与使用细节做了补充;另有2个nii示例数据,用户可立即验证工具效果。资源已有超过700人浏览学习,说明其在医学影像处理场景中有一定参考价值。借助这套工具,既可快速读懂NIfTI文件结构,也能直接调用或修改函数完成批量处理、格式转换与可视化分析,是一条高效处理脑成像数据的实用路径。

1. 拿到 nii 图像的第一步,先别急着 imread

医学影像里最常见的格式之一就是 NIfTI,扩展名通常是.nii.nii.gz。很多从公开数据集(比如 BraTS、ADNI)下载的样本,解压后是一堆体积不小的.nii文件,于是很自然有人想在 MATLAB 里直接imread('image.nii'),然后报错。原因很简单:nii 不是图片,是一个带有 348 字节头部的多维数组,里面除了像素值,还有体素大小、图像方向、坐标变换矩阵、时间点数量等信息。用普通图像工具箱去读,MATLAB 根本不知道这个文件内部到底是怎么组织的。

NIfTI_20140122.zip 是多年前流传很广的一个 NIfTI 工具箱压缩包,里面包含load_nii.msave_nii.mmake_nii.m等函数,配合nii_viewer.m可以直接可视化三维体数据。虽然官方对它的维护更新很慢,但在处理标准 nii 文件时,它依然是最容易上手的方案之一。这篇文章就围绕这个 zip 包展开,讲清楚 nii 文件的数据结构、如何在 MATLAB 里正确加载、可视化、做基本处理,以及加载过程中最容易踩的坑。适合刚接触医学图像处理的算法工程师、研究生,也适合需要把 nii 数据转成矩阵做深度学习的同学——读完你至少能彻底搞明白,为什么load_nii读出来的结构里全是.img.hdr

2. NIfTI 格式到底存了什么:hdr 与 img 的对应关系

2.1 348 字节头部里最关键的 8 个字段

NIfTI-1 标准的头部固定占 348 字节,剩下的数据区按类型顺序排列。很多人在 MATLAB 里直接fopenfread读坏文件,就是因为没有先把头部解析出来。理解头部的关键字段,后续用load_nii时你才知道返回的结构体里每个字段在说什么。

常用字段及含义如下:

字段名字节偏移常见值作用
sizeof_hdr0348校验是否为 NIfTI-1
datatype702/4/8/16/64体素数据类型,2=uint8,4=int16,8=int32,16=float32,64=float64
bitpix728/16/32/64每个体素占用的比特数
dim40[3 256 256 150]维数及各维度大小
pixdim76[1 0.5 0.5 0.5]每个维度的物理体素尺寸
srow_x280[1 0 0 -128]从体素坐标到世界坐标的行向量
qform_code2521/2坐标变换类型
scl_slope2001斜率,用于实际值 = 存储值 * slope + inter

load_nii返回的结构体里,nii.hdr.dime对应这些头部字段,nii.img是真正的体素数据。很多人不理解为什么读出来的矩阵尺寸是[X Y Z]而不是[行 列]——因为 NIfTI 的维度顺序是[x y z t],x 方向是第一个维度,y 是第二个,z 是第三个。和 MATLAB 里matrix(row, col, slice)的习惯刚好反着,显示的时候需要permuterot90调整。

2.2 为什么用 MAT 文件里的矩阵直接保存不行

有人会问,nii 文件里的img和 MATLAB 的工作区变量不都是一个三维矩阵吗,为什么不直接.mat保存?区别在于 nii 保留了一整套物理空间信息。比如一个 256×256×150 的 MRI 数据,如果只知道体素值,你不知道每一层在真实人体里对应哪个位置,也无法对齐另一个受试者的数据。srow_xsrow_ysrow_z共同构成一个仿射矩阵,能把体素坐标映射到毫米级的脑模板空间。如果你只存矩阵,训练模型前做配准时就得重新手工编码坐标信息,而 NIfTI 天然把数据与坐标绑定在一起。

NIfTI_20140122.zip这种工具箱做的事情,本质就是把这 348 字节解析出来,再根据datatypedimfread读入对应大小的数组。它没有用任何魔法,所以当文件头损坏或者dim写错时,load_nii会报出非常怪异的错误——接下来章节里我们逐个处理。

2.3 压缩过的.nii.gz不是简单解压就能读

.nii.gz是 gzip 压缩后的 nii 文件,很多官方的数据都使用这种形式。直接在 MATLAB 里用gunzip解压也能拿到.nii,但NIfTI_20140122工具箱自带一个load_nii的变体,会在读取时临时解压到内存,不会在磁盘上留下额外的中间文件。不过需要注意的是,这个版本的工具箱内部调用的是gzip命令,Windows 下如果没有安装相应工具,通常会报"系统找不到指定的路径"。更稳妥的做法是用 MATLAB 自带的gunzip先解压到临时目录,再用load_nii读取。

3. MATLAB 里加载 nii 的最小实现:从 addpath 到 load_nii

3.1 准备工具包:把下载的 NIfTI_20140122.zip 变成可执行文件夹

假设你手头已经有一个NIfTI_20140122.zip,这不是某个私有项目的专属包,而是经过广泛传播的公开工具箱。在 MATLAB 中使用它不需要安装,只需要解压后添加路径即可:

unzip('NIfTI_20140122.zip', 'NIfTI_folder'); addpath(genpath('NIfTI_folder'));

这段代码先解压 zip 到当前工作目录下的NIfTI_folder,然后用genpath递归添加所有子目录到 MATLAB 路径。如果不加genpath,而工具箱内部又引用了子目录里的view_niirri_*辅助函数,运行时会报Undefined function。另外,解压之后建议先确认目录里是否存在load_nii.m,因为有些历史版本会缺少部分文件,被精简过的压缩包可能只含一个单文件版,功能不全。

3.2 读取一个标准 nii 文件的基本命令

nii = load_nii('subject_T1.nii'); img = nii.img; hdr = nii.hdr;

运行之后如果你在命令窗口输入whos img,会看到img是一个 uint16 类型的三维数组。为什么是 uint16?因为 MRI 数据常用 16 位灰度。保留原始类型是明智的——很多人在这一步直接double(img),然后再做归一化,这本身没错,但要注意如果原始数据是灰度值而非物理量,转换前应该先查看scl_slope字段是否非 1。scl_slope表示存储值需要经过线性变换才能得到真实信号强度:

if nii.hdr.dime.scl_slope ~= 0 img = double(nii.img) * nii.hdr.dime.scl_slope + nii.hdr.dime.scl_inter; else img = double(nii.img); end

3.3 处理.nii.gz压缩格式

如果文件名是T1_3D.nii.gzload_nii的直接调用可能因为找不到对应 gzip 程序而失败。常见做法是先用 MATLAB 的gunzip解压到内存或临时目录。下面这段代码可以自动识别并处理:

inputFile = 'T1_3D.nii.gz'; if endsWith(inputFile, '.gz') gunzip(inputFile); % 解压到当前目录,生成 .nii inputFile = erase(inputFile, '.gz'); end nii = load_nii(inputFile);

注意gunzip之后的文件名是不带.gz的原名。如果原先的文件名里包含_3D等标识,解压后仍保留。若担心覆盖原文件,可以先tempdir建一个临时目录再解压,处理完删除。

3.4 批量读取多个 nii 文件时的路径组织

实际操作中经常有几十个.nii文件分散在子目录里,推荐用dir配合模式匹配循环读取。下面这个方法比手动拼路径稳健得多:

files = dir(fullfile('data', '**', '*.nii')); for i = 1:length(files) filePath = fullfile(files(i).folder, files(i).name); nii = load_nii(filePath); img = nii.img; % 这里把每个文件的体素尺寸等记录到表里 end

dir('data', '**', '*.nii')是 MATLAB R2016b 之后才支持递归搜索的写法,老版本需要自己用genpath构造目录列表。另一个容易忽略的点是:如果数据是 4D nii(例如功能磁共振 fMR,维度是[x y z t]),nii.img的大小是[x y z t],后续当你要把时间序列提取出来做频谱分析或 FFT 时,通常要squeeze(permute(img, [4 1 2 3]))才能得到[t x y z]的顺序,方便按时间切片。

4. nii 图像的可视化与常用操作:切片、体素坐标与重采样

4.1 在 MATLAB 中显示横断面、矢状面和冠状面

NIfTI_20140122自带一个图形界面工具view_nii.mnii_viewer.m(不同版本名称略有差异),直接调用:

nii = load_nii('T1.nii'); view_nii(nii);

这个界面里可以拖动十字线定位三维坐标,同时切换三个正交面的视图。它内部实现是通过slice函数依次提取第 z 层、x 方向和 y 方向的数据。如果不习惯界面交互,自己动手画三个方向的切片也只需要几十行代码。下面是一个经典的横断面绘制:

img = nii.img; sliceIdx = round(size(img, 3) / 2); % 取中间层 imgSlice = squeeze(img(:, :, sliceIdx)); if size(imgSlice, 1) < size(imgSlice, 2) imgSlice = permute(imgSlice, [2 1]); % 调整行列方向,使其不旋转 end imagesc(imgSlice); axis image; colormap(gray);

这里axis image保证体素在屏幕上按 1:1 显示时不会因坐标轴缩放拉伸。但要注意,如果pixdim不是 1,例如 x 方向体素间隔为 0.5 mm,y 方向为 0.8 mm,直接axis image会显示为正方形,视觉上会失真。正确的显示方式应该按物理比例进行拉伸:

pixdim = nii.hdr.dime.pixdim(2:4); aspect = pixdim(2) / pixdim(1); % y / x set(gca, 'DataAspectRatio', [1 aspect 1]);

这个细节很容易被忽略,但在论文里贴图时,体素非各向同性数据的显示比例错误会直接影响审稿人对病灶形状的判断。

4.2 体素坐标与世界坐标的互相转换

在医学图像处理中,你不会只关心数组下标,特别是在做病灶中心点定位、或者与标准脑模板比对时,必须知道某个体素位置 (x, y, z) 对应的毫米坐标。NIfTI 头部里srow_xsrow_ysrow_z这三个四维行向量定义了仿射变换:

% 假设 vxl 是体素坐标 [x; y; z; 1] vxl = [55; 120; 80; 1]; worldX = nii.hdr.hist.srow_x * vxl; worldY = nii.hdr.hist.srow_y * vxl; worldZ = nii.hdr.hist.srow_z * vxl;

如果要反过来从世界坐标映射回体素坐标,则先构建 3×4 仿射矩阵,再求逆。NIfTI_20140122没有直接提供这个逆变换函数,但可以手动实现。更简单的做法是直接用nii_viewer界面里的鼠标位置读出世界坐标。手写代码时要注意 NIfTI 中世界坐标的 x 方向是从右到左(R-L),y 方向是后前(P-A),z 方向是下上(I-S),具体方向要看qformsform的类型,不要想当然地当成物理轴。

4.3 提取感兴趣区域(ROI)并生成 mask

处理 nii 的另一个高频需求是把某一阈值的区域提取出来,或者用已有 mask 裁剪图像。例如分割结果中标签为 1 的区域是病灶,需要把它周围的图像裁下来:

nii = load_nii('T1.nii'); seg = load_nii('seg.nii'); mask = (seg.img == 1); [row, col, sli] = ind2sub(size(mask), find(mask)); minRow = min(row); maxRow = max(row); minCol = min(col); maxCol = max(col); minSli = min(sli); maxSli = max(sli); roiImg = nii.img(minRow:maxRow, minCol:maxCol, minSli:maxSli);

裁剪之后,如果你要把roiImg保存回 NIfTI 文件,需要单独重建make_nii,并且要注意原点的变化,否则保存出来的数据在空间上会和原图对不上。NIfTI_20140122make_nii默认把原点设在体素中心,若裁剪后不修改srow,用view_nii打开时会看到图像偏到奇怪的位置。推荐的做法是保留原nii.hdr,修改dimsrow后通过save_nii写入,但这样做容易因头部细节不一致导致报错,所以我通常建议在做 ROI 裁剪时直接用矩阵运算,仅在最终需要落盘时再构建新 nii。

4.4 重采样到同尺寸:多模态数据配准的第一步

深度学习预处理中,常常要把 T1、T2、FLAIR 三种模态的 nii 统一到同一尺寸。最简单粗暴的方法是imresize对三维矩阵逐层缩放,但这样会破坏体素坐标信息。更稳妥的算法是用interp3结合仿射坐标进行物理空间重采样。这里给出一个适用于 NIfTI 工具箱的最小实现思路:

function resliced = reslice_nii_general(nii, newDim, method) ox = nii.hdr.dime.pixdim(2); oy = nii.hdr.dime.pixdim(3); oz = nii.hdr.dime.pixdim(4); [x, y, z] = ndgrid(0:newDim(1)-1, 0:newDim(2)-1, 0:newDim(3)-1); newX = x * (ox * size(nii.img,1)) / newDim(1); newY = y * (oy * size(nii.img,2)) / newDim(2); newZ = z * (oz * size(nii.img,3)) / newDim(3); resliced = interp3(double(nii.img), newX, newY, newZ, method); end

这段代码按物理尺寸等比例缩放,而不是简单均匀插值。如果原始图像的pixdim不是各向同性,这个重采样方式会保留空间比例关系。method可以传入'nearest''linear',对于标注 mask 用'nearest',对于灰度图用'linear''cubic'。要注意interp3的坐标是三元网格,且坐标范围必须落在原始数据范围内,否则out of range会返回 NaN,导致后续网络训练报错。

5. NIfTI 加载中的 4 个高频报错与检查顺序

5.1Size of header (348) not correct说明文件头不对

出现这个报错,多数情况下并不是文件损坏,而是你把 ANALYZE 格式用 NIfTI 加载器去读了。ANALYZE 7.5 的头文件也是 348 字节,但某些字段的偏移和 NIfTI 不同,导致sizeof_hdr被读错。遇到这种情况,先检查扩展名是不是.hdr/.img成对出现的老式写法。处理办法是打开 RAW 流,直接读头部的前 40 字节看魔数:如果第 344-347 字节不是n+1\0,那大概率是 ANALYZE 格式。可以修改加载逻辑,用load_untouch_nii.m(工具箱里通常附带)尝试兼容。

5.2Error using fopenPermission denied常见于路径与缓存问题

Windows 下如果文件正被其他程序(如 ITK-SNAP、MRIcron)占用,fopen会拿不到句柄,导致工具箱内部报fopen失败。检查对象不只是那个.nii文件,还有你解压后的.hdr,因为 NIfTI 有时会以分离头文件的方式存在。另外,如果 MATLAB 当前目录是中文路径或者带空格,工具箱内部某些老代码里可能有硬编码路径问题。建议统一把数据放在纯英文、无空格的目录下。这里有一个排查顺序,先跑一遍最小实验:

fid = fopen('T1.nii', 'r'); if fid == -1 disp('文件无法打开'); else fclose(fid); end nii = load_nii('T1.nii'); % 再跑一次

如果 fopen 成功但 load_nii 失败,则问题出在解析逻辑上,继续看下一步。

5.3 读出来全 0 或矩阵尺寸不符:检查dimdatatype

有朋友共享的 nii 文件,在 MATLAB 里读出来img全是 0,但在其他软件里显示正常。最常见的原因是文件中数据真实类型是 float32,但datatype字段被误设成 uint16,导致每个体素被错误解释为 2 个字节。遇到这种问题,先用以下命令查看实际 datatype:

nii.hdr.dime.datatype nii.hdr.dime.bitpix

如果看到bitpix是 32 但datatype是 4(int16),可以手动强制转换后重新保存。另一个可能是文件里存在缩放斜率,原数据存储值在 int16 范围内,真实值是img * slope + inter,而你直接用原始img做可视化,由于斜率是 0.003 之类的小数,所有值都被 imshow 当作接近 0 的暗色显示。解决办法是先按缩放系数计算,见 3.2 节。

5.4 4D 数据读出来维度是 5 层还是 4 层

fMRI 的 nii 文件维度是[x y z t],但有时dim(1)表示维数,dim(2)dim(5)才是各维度大小。工具箱中nii.hdr.dime.dim的第一个元素是 4(表示 4D),后面四个数依次为 x、y、z、t。当你squeeze(nii.img)后发现变成了二维矩阵,不要惊讶——因为 z 方向可能只有 1 层。检查维度大小是否和dim一致,必要时补上reshape,不要直接依赖squeeze来调整顺序。

5.5 验证加载是否正确的黄金法则

确定文件读取无误的最快方式,是把原始 nii 和保存后的 nii 做一个像素级差分:

nii1 = load_nii('T1_original.nii'); nii2 = load_nii('T1_retest.nii'); diffImg = double(nii1.img) - double(nii2.img); fprintf('Max diff: %g, Num non-zero: %d\n', max(abs(diffImg(:))), nnz(diffImg));

如果max diff为 0,说明加载和保存过程没有数据损坏。如果有差值,优先怀疑浮点类型与整型的转换误差,再检查scl_slope有没有被重新写入。通常处理 nii 数据的管线里这步验证值得加进去,尤其是数据流水线里有多个人在改代码时。毕竟 nii 文件本身是标准格式,但每一版工具的解析细节仍有些历史遗留差异,用基线文件做回归检查,比肉眼扫切片靠谱得多。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询