
简介这是一份基于马萨诸塞道路遥感数据集整理而成的nnUNet训练与测试数据资源面向从事遥感图像分析、道路提取以及深度学习语义分割的研究者和开发者。原始数据包含高分辨率航空影像与精确道路标注并已按nnUNet标准格式进行划分和命名可直接用于模型训练、验证与测试。资源共59个文件含58张PNG格式图像与1个dataset.json配置压缩包整体大小142.02MB。文件夹内imagesTr、labelsTr、imagesTs、labelsTs分别对应训练图像、训练标签、测试图像和测试标签图像文件统一采用四位数编号及通道后缀便于框架自动识别与加载。目前已有1772人学习使用。这份数据集省去了从影像预处理、标签格式转换到目录结构配置的大量工作能帮助快速启动道路提取、遥感分割等实验也适合作为学术论文中的公开基准数据集或算法效果对比数据。1. nnunet训练测试数据集为什么推荐用一套命令跑完医学图像分割里nnUNet 几乎成了“开箱即用”的代名词。拿到的数据既不是分类任务的 CSV也不是检测任务的框标注而是一堆 nii.gz 三维图像这时候最容易踩的坑不是网络结构而是“数据集怎么摆、预处理怎么跑、训练完怎么测”。这篇笔记就围绕 nnunet 训练测试数据集这件事把从原始图像到分割结果的完整路径拆开讲目录结构、预处理、五折训练、推理、评估以及我实际跑项目时翻过车的几个地方。适合手里已经有一批标注数据、想用 nnUNet 快速出基线结果的读者也适合被“找不到数据”“预测全黑”这类问题卡住的人。2. 拆解数据集布局与预处理从乱糟糟的原始图像到可训练的 nnUNetnnUNet 对数据布局的要求极度严格严格到第一次用的人会觉得“这也太矫情了”。但正是这种严格换来了后面的全自动配置。这一章先把目录、命名、标签和 dataset.json 讲透再给出预处理命令。2.1 目录结构与命名规则nnUNet 的第一道门槛nnUNetv2 的数据目录逻辑上分为三块原始数据、预处理结果、训练结果。原始数据目录必须长成下面这样nnUNet_raw/ └── Dataset001_MyData ├── imagesTr │ ├── case_0000_0000.nii.gz │ └── case_0001_0000.nii.gz ├── imagesTs │ ├── test_0000_0000.nii.gz │ └── test_0001_0000.nii.gz ├── labelsTr │ ├── case_0000.nii.gz │ └── case_0001.nii.gz └── dataset.json这里最容易忽略的是命名里的“_0000”。imagesTr 里每个文件必须带_0000后缀表示第 0 个模态labelsTr 里的文件不能带这个后缀但文件名主干必须和 imagesTr 一一对应。比如case_0000_0000.nii.gz对应case_0000.nii.gz。如果你的数据是多模态的比如 T1 和 T2 两个序列那就要写成case_0000_0000.nii.gz和case_0000_0001.nii.gz最后一个数字表示模态序号。Dataset001_MyData这个目录名也要注意Dataset后必须跟三位数字 ID从 001 开始后面接任意名称。后面所有命令里用到的-d 1指的就是这里的 001。imagesTs 不是必须的如果只是训练和交叉验证可以暂时不建但如果你要测试就必须把测试图像按同样的_0000规则放进去。我自己的习惯是写一个检查脚本先确认 imagesTr 和 labelsTr 一一对应再开始预处理省得后面报“数据不匹配”才回头找问题import os images_dir nnUNet_raw/Dataset001_MyData/imagesTr labels_dir nnUNet_raw/Dataset001_MyData/labelsTr image_names sorted(os.listdir(images_dir)) label_names sorted(os.listdir(labels_dir)) # 去掉 imagesTr 里的 _{模态号} 后缀再和 labelsTr 对比 image_stems [f.replace(_0000.nii.gz, .nii.gz) for f in image_names if f.endswith(.nii.gz)] assert image_stems label_names, ( f数量不一致: imagesTr {len(image_stems)} 个, labelsTr {len(label_names)} 个 ) print(imagesTr 和 labelsTr 配对正确)这段代码的核心逻辑是把 imagesTr 里的_0000尾巴去掉然后和 labelsTr 做严格比对。实际项目里经常出现多了一张图、少了一个标注的情况这种问题在预处理阶段才会炸出来提前检查能省掉一次莫名其妙的报错。2.2 dataset.json 和标签类别最容易写错的两处dataset.json 是整个数据集的身份证明。最小可用的内容如下{ channel_names: { 0: CT }, labels: { background: 0, liver: 1, tumor: 2 }, numTraining: 100, file_ending: .nii.gz }逐个字段解释。channel_names里的 key 是模态序号value 是你对这个模态的描述比如 CT、MRI_T1、MRI_T2value 本身不影响训练但建议写清楚方便查 plans.json 时能看懂。labels是最关键的部分0 一定是 background前景类别从 1 开始递增。这里的 key 是类别名value 是标签图像里对应的像素值。numTraining是训练样本数量可以写也可以不写nnUNet 会自动统计。file_ending必须是.nii.gz如果你的数据是.nii或者别的格式官网建议你转成.nii.gz不要试图改这个字段去迁就数据。标签的像素值处理和 YOLO 这类把标注转成 txt 的思路完全不同nnUNet 使用的是原始像素级 mask。最常见的问题是标签图里除了 0、1、2 之外还有别的像素值比如 255或者把前景标成了 10、20。nnUNet 在训练时只会把 dataset.json 里声明过的 label 当作有效类别遇到没声明过的像素值会直接忽略或导致 loss 计算异常。我的做法是在预处理前加一次标签重映射import SimpleITK as sitk import numpy as np import os input_dir raw_labels output_dir labelsTr os.makedirs(output_dir, exist_okTrue) for fname in os.listdir(input_dir): if not fname.endswith(.nii.gz): continue img sitk.ReadImage(os.path.join(input_dir, fname)) arr sitk.GetArrayFromImage(img).astype(np.int16) # 重映射把 255 和 200 这类杂散值统一到 1前景0 保持为背景 arr[arr 255] 1 arr[arr 200] 1 out_img sitk.GetImageFromArray(arr) out_img.CopyInformation(img) # 保留 spacing/origin/direction sitk.WriteImage(out_img, os.path.join(output_dir, fname))注意最后那行CopyInformation这是医学图像处理里最容易丢信息的操作。直接用sitk.GetImageFromArray(arr)写出来的文件会丢掉 spacing 和 direction后面 nnUNet 做重采样时会出现错位。这种错位不会报错但结果就是训练正常、预测结果空间位置不对。2.3 跑 plan_and_preprocessnnUNet 的自动配置到底做了什么数据目录和 dataset.json 准备好之后先设三个环境变量再跑预处理export nnUNet_raw/data/nnUNet/nnUNet_raw export nnUNet_preprocessed/data/nnUNet/nnUNet_preprocessed export nnUNet_results/data/nnUNet/nnUNet_results nnUNetv2_plan_and_preprocess -d 1 -c 3d_fullres --verify_dataset_integrity三个环境变量分别指向原始数据、预处理输出、训练输出目录。这一步不做后面所有命令都会报“nnUNet_raw is not set”之类的错。-d 1对应Dataset001_MyData-c 3d_fullres表示只生成 3d_fullres 配置的预处理结果--verify_dataset_integrity会检查图像和标签是否对齐、数据能否正常读取我建议第一次跑必须带上。预处理完成以后在nnUNet_preprocessed/Dataset001_MyData/下会生成dataset_fingerprint.json和plans.json。你可以打开plans.json看几眼里面的关键信息包括每个类别的像素占比、中位数的 spacing、中位数的图像尺寸、以及 nnUNet 定的 target spacing 和 batch_size。这就是 nnUNet“自配置”的核心——它不靠人去调参而是靠数据分布统计出来的一套方案。比如它发现你的数据 spacing 中位数是[0.5, 0.5, 3.0]层间距特别大它可能会建议你用 2d 配置或者在 3d 配置里做特殊处理。有几个参数值得调整。-np 4可以开 4 个进程并行预处理能显著加速但因为预处理要读入整幅三维图像内存占用也会跟着翻倍小内存机器上反而会卡死。--verify_dataset_integrity在数据量大时比较慢第二次跑可以去掉不过我自己的习惯是每次换了新数据都留着毕竟出错成本比等待成本高得多。3. 训练五折交叉验证与三个 U-Net 配置预处理跑完接下来进入真正的训练阶段。nnUNetv2 的训练命令和配置选择有固定的套路这一章把命令参数、配置选型和资源估算讲清楚。3.1 从 3d_fullres 开始训练命令与显存控制训练命令看起来非常简单export nnUNet_results/data/nnUNet/nnUNet_results CUDA_VISIBLE_DEVICES0 nnUNetv2_train 1 3d_fullres 0这个命令的意思是训练 Dataset001-d 1隐含在命令参数的位置里使用 3d_fullres 配置跑第 0 折。这里nnUNetv2_train后面依次是数据集 ID、配置名、fold 编号。fold 0 表示把训练集切成五折当前用其中 80% 训练、20% 验证fold 可以取 0 到 4也可以用all表示不切分、用全部数据训练。训练过程中会看到类似Epoch 10/1000的输出nnUNet 默认训练 1000 个 epoch但实际远不需要跑满它会根据验证集 loss 自动选最佳 checkpoint。我一般不会去改动这个默认值因为 nnUNet 的 early stopping 策略已经足够可靠手动调小反而容易欠拟合。如果你是 16G 显存的卡跑 3d_fullres 大概率会遇到 OOM。nnUNet 会自动根据显存推算 batch_size但推算结果不一定保守。这时候有两条路第一改plans.json里的batch_size字段从默认值往下调一半重新训练第二在训练命令里限制数据加载线程数因为有时 OOM 不是显存炸了而是 CPU 预处理线程把内存吃满了。可以用export nnUNet_def_n_proc4 CUDA_VISIBLE_DEVICES0 nnUNetv2_train 1 3d_fullres 0nnUNet_def_n_proc控制数据加载进程数默认会按 CPU 核心数开很多线程在小数据集上线程开太多反而拖慢训练。3.2 三个配置怎么选3d_fullres、3d_lowres、2d 和 cascadennUNetv2 针对三维数据提供了几种配置训练前必须想清楚用哪个。3d_fullres是全分辨率三维 U-Net是绝大多数情况下的默认选择3d_lowres是低分辨率版本先把图像降采样再训练适合图像尺寸特别大、目标器官也大的场景2d是纯切片级训练适合层间距特别大、层间信息不可靠的数据3d_cascade_fullres是级联的第二阶段先由 3d_lowres 产生粗分割再把这个粗分割作为额外输入做精修。我的选型经验是这样的如果你的数据是常见的 CT 或 MRI且目标器官在图像中占比不是极端小直接用3d_fullres就跑出不错的结果了。只有当数据量极大、或者你的 GPU 实在跑不动全分辨率时才考虑3d_lowres加3d_cascade_fullres的组合。2d 配置最容易被忽略但遇到层间距 5mm 以上的数据时它往往比 3d 更稳因为 3d 卷积在这种数据上会学到很多层间插值的假信息。关于 fold 的理解可以和目标检测里“训练集/验证集划分”对照着看。每条命令只能跑一个 fold所以完整跑五折需要五条命令CUDA_VISIBLE_DEVICES0 nnUNetv2_train 1 3d_fullres 0 CUDA_VISIBLE_DEVICES0 nnUNetv2_train 1 3d_fullres 1 CUDA_VISIBLE_DEVICES0 nnUNetv2_train 1 3d_fullres 2 CUDA_VISIBLE_DEVICES0 nnUNetv2_train 1 3d_fullres 3 CUDA_VISIBLE_DEVICES0 nnUNetv2_train 1 3d_fullres 4多卡机器可以同时开五条命令每张卡跑一个 fold。3.3 训练资源估算别让训练时间变成翻车现场训练资源是 nnunet 训练测试数据集这个方向里最容易被低估的部分。以常见的腹部 CT 分割任务为例单折 3d_fullres 在 24G 显存、CPU 给足的情况下通常要跑 12 到 24 小时五折全跑完就是 3 到 5 天。如果你只有一张 8G 显卡这个时间还要翻倍这时候就得认真考虑是不是要放弃 3d_fullres。几个实用控制手段第一前面说的nnUNet_def_n_proc限制 CPU 线程能减少内存争抢。第二在plans.json里调低batch_sizevolume 不变的情况下显存占用会线性下降但训练稳定性会略微变差。第三只跑 3d_fullres 配置不要默认把 3d_lowres 也一起训。很多新手以为 nnUNet 会自动训所有配置实际上预处理时用-c 3d_fullres限制过了训练时也只会跑你指定的那个配置不会主动训 cascade。这里还要提醒一个数据规模的问题。网上经常有人说 nnUNet “只用 50 例就能跑”这个说法方向没错但你要知道结果的含义。50 例的数据跑五折每折只有 40 例训练、10 例验证DSC 的波动会很大。我见过有人拿 30 例数据跑完五折平均 DSC 0.85但每一折的标准差接近 0.1这个结果只能说明“方法可行”还不能说明“模型稳定”。数据量越小越要关注 5 折之间的一致性而不是只看平均指标。4. 测试与推理把模型权重变成分割结果训练完的权重是用来做推理的。nnUNet 的推理流程和训练一样高度命令行化但有几个参数直接影响输出质量值得单独展开。4.1 单模型预测命令与输出说明推理命令nnUNetv2_predict \ -i /data/nnUNet/nnUNet_raw/Dataset001_MyData/imagesTs \ -o /data/nnUNet/results/pred \ -d 1 \ -c 3d_fullres \ -f 0 1 2 3 4参数含义-i是测试图像目录里面的文件命名必须和训练时一致带_0000后缀-o是输出目录不存在会自动创建-d 1指定数据集-c 3d_fullres指定配置-f 0 1 2 3 4表示把五个 fold 的模型都加载并做预测最后把五个 softmax 概率平均再取 argmax这个操作本质是五折模型集成比单折预测更稳。输出目录里每个输入文件会对应一个同名的.nii.gz分割结果像素值和 dataset.json 里定义的类别编号一致。注意 nnUNet 推理时会把输入图像自动重采样到训练时的 spacing预测完再重采样回原始 spacing所以输出的几何信息和输入图像是严格对齐的不需要你自己做任何空间变换。-step_size是另一个值得关注的参数默认 0.5表示滑窗推理时窗口之间有 50% 重叠。重叠越多预测越平滑但速度越慢。如果测试集比较大可以调成 0.6 或 0.7 平衡一下如果追求极致精度可以调成 0.9代价是推理时间可能翻倍。4.2 五折平均与跨配置集成的区别上面命令里的-f 0 1 2 3 4已经做了五折模型集成这是 nnUNet 最推荐的推理方式。但如果你还想更进一步把不同配置的模型结果也集成起来比如把 3d_fullres 和 2d 的结果做融合那就需要先保全概率再做概率平均。做法是先在预测时加--save_probabilities会额外输出.npz文件然后使用 nnUNet 自带的概率平均命令做像素级平均再转成最终标签。具体命令名不同小版本略有差别建议先跑nnUNetv2 --help确认。跨配置集成能带来稳定的收益但收益通常在 1 到 3 个百分点的 DSC 之间不是质变。我自己的习惯是先只跑 3d_fullres 五折集成如果发现某个类别特别难分再针对性加 2d 或 cascade 配置的集成而不是一上来就把所有配置都训一遍。4.3 评估DSC、NSD 以及怎么看预测结果如果测试集有对应的金标准标签用 nnUNet 自带的评估命令可以一次性算出多个指标nnUNetv2_evaluate_folder \ /data/nnUNet/results/pred \ /data/nnUNet/gt_labels \ -l 1 2-l 1 2表示只评估类别 1 和类别 2。输出会包含每个类别的 DSC、NSD表面距离一致性等指标。DSC 是日常最常看的但 DSC 对大目标友好对小目标或细长结构会“虚高”。比如一个器官占图像体积 20%就算预测漏掉一半DSC 也可能还有 0.7 以上。所以细长结构、小病灶一定要额外看 NSD或者干脆把预测结果叠加在原图上做目检。目检这一步别省。我一般会把预测 mask 和原图用 SimpleITK 读出来用 matplotlib 叠一张切片图扫一眼再去看指标。指标只能告诉你“好不好”目检能告诉你“错在哪”。5. 避坑nnunet 训练测试数据集最常见的五个翻车现场这一章写的是我实际跑 nnunet 训练测试数据集时踩过的坑每条都是“现象→原因→解决”的完整记录。5.1 预处理报错明明有数据却提示找不到文件现象执行nnUNetv2_plan_and_preprocess时日志里出现No cases were found或imagesTr is empty但用文件管理器看目录里明明有数据。原因最常见的是文件后缀不统一。nnUNet 只认.nii.gz如果你的数据是从某些标注工具导出的.nii文件它不会识别还有一种情况是文件名里混了大写比如Case_0000_0000.nii.gznnUNet 对大小写敏感目录里明明有文件但匹配时找不到。解决写一个小脚本统一改名。把.nii转成.nii.gz把所有文件名转成小写确认_0000后缀存在。改完以后重跑--verify_dataset_integrity过了再继续。5.2 训练正常但验证集 DSC 一直为 0现象训练 loss 在下降但验证集每个类别的 DSC 始终是 0或者某个类别的预测结果完全不存在。原因标签图像里出现 dataset.json 没有声明的像素值。最常见的是把前景标成了 255但 dataset.json 里类别值是 1、2这样 nnUNet 在训练时根本看不到这个类别的监督信号模型学会的只有背景。解决预处理前做标签重映射把 255 这类杂散像素值统一到合法类别里。重映射以后重新跑预处理不要直接接着训练因为plans.json里的类别统计已经错了。5.3 OOM显存不够训练中途崩掉现象训练跑到第几个 epoch 时突然报CUDA out of memory或者训练一开始加载模型就崩了。原因3d_fullres 全分辨率训练对显存的需求和数据体素量成正比。nnUNet 虽然会自动估计 batch_size但估计逻辑偏向保守选大遇到 spacing 特别不规则的数据时估计结果会失准。解决打开nnUNet_preprocessed/Dataset001_MyData/plans.json找到batch_size字段改成原来的一半保存后重新训练。如果还是崩就换 2d 配置或者 3d_lowres。同时检查是不是 CPU 线程开太多把系统内存吃满了用nnUNet_def_n_proc4限制一下。5.4 训练时间过长一周没跑完一折现象训练日志显示 epoch 推进极慢一个 epoch 要好几分钟照这个速度一折要跑十天。原因很可能你同时训了多个配置也可能预处理时把-np开得过大导致预处理阶段花了好几天还有一种情况是数据量本身很大比如几百例 512×512×300 的高分辨率 CT全分辨率训练本来就慢。解决先只训 3d_fullres 一个配置预处理阶段用-np 2或-np 4控制并行数如果数据实在太大把plans.json里的 target spacing 调大一点比如从[0.5, 0.5, 0.5]改成[1.0, 1.0, 1.0]体素数量直接降到原来的八分之一训练速度会快很多代价是细节丢失一点。5.5 预测结果全黑或全是背景现象推理完成输出的 mask 里只有一个类别或者整张 mask 全黑DSC 惨不忍睹。原因这个坑至少有三种来源。一是测试图像的像素值范围不对比如训练时是 CT 值范围测试时输入的是归一化到 0 到 255 的 PNG 转的 nii.gz分布变了模型直接失效二是标签映射反了预测时把背景当成前景三是推理时忘了加-f 0 1 2 3 4单折模型本身效果就差。解决先用--save_probabilities保存 npz检查每个类别的概率分布是否正常如果某个类别的概率始终接近 0说明模型没学到这个类别的特征问题大概率在训练数据或预处理阶段而不是推理命令。然后检查测试图像和训练图像的 intensity 分布是否一致最简单的方式是读一组训练图像和一组测试图像对比灰度的 min、max、mean。6. 把 nnunet 训练测试数据集用得更稳增量训练、自定义配置和结果核对训练一轮拿到基线结果以后通常还有两个诉求一是数据多了怎么办二是结果怎么进一步提升和验证。增量训练是最常见的需求。场景是这样的你先用 50 例数据训了一版模型两个月后又标了 30 例新数据这时候不需要从零重新训练。常见做法是把新数据合并进同一数据集目录重新跑预处理然后直接加载旧 checkpoint 继续训练。nnUNetv2 的训练命令里提供了继续训练的参数具体名称建议先看nnUNetv2_train --help确认。要注意继续训练之前先备份旧模型因为权重会被覆盖没有后悔药。我的习惯是把nnUNet_results/Dataset001_MyData目录整个复制一份再动。自定义配置是另一个实用方向。plans.json里的batch_size、target_spacing、num_processes都可以手动改。改完以后要重新跑预处理因为预处理产物依赖于 plans 里的 spacing 设置。我自己改得最多的是batch_size因为显存大小直接决定能不能跑起来target_spacing我一般只在数据尺寸极端时才动比如原始图像是 1024×1024 的大图不改 spacing 的话全分辨率训练根本跑不动。验证环节除了看 DSC我每次都会加一步“切片目检”。用 SimpleITK 读预测结果和原图挑几个代表性的切片叠在一起看重点看边界是否平滑、小目标是否漏检、有没有出现训练集里没见过的奇怪伪影。指标是给人看的但最终模型是给机器用的边界上的系统性偏差只靠 DSC 是看不出来的。比如模型总把器官边界往外扩一圈DSC 可能只掉两三个点但下游手术规划根本不敢用。最后说一个我这几年跑分割项目的习惯任何新数据集第一次一定是只跑 3d_fullres 五折不做任何花哨操作拿到评估指标后先看 5 折标准差再看每个类别的 NSD最后目检十个切片。这三关过了才考虑跨配置集成、增量训练这些进阶操作。这个习惯帮我挡掉了至少一半的无效实验。希望帮到你。本文还有配套的精品资源点击获取