RSNA肺炎检测数据集:VOC+YOLO双格式目标检测实战指南
简介该数据集围绕RSNA肺炎检测场景构建提供VOC与YOLO两种流行的目标检测标注格式适配主流训练框架方便算法工程师、科研人员以及医学影像智能分析方向的学习者使用也可迁移至其他单类别目标检测任务。数据规模包含六千零一十二张图像对应的标注信息每张图像均配有XML和TXT标注文件类别名称为meat共标注出九千五百五十五个矩形框标注工具为LabelImg绘制规则仅针对目标画框标注结果准确合理但作者特别声明不对训练后的模型精度提供任何保证因此实际使用时应独立验证模型效果。压缩包内文件总数为两千个其中一千九百九十九个为XML标注文件另外一个是使用说明文档整体体积约八百八十六点七八兆字节且不包含分割路径信息。目前已有五百九十四人学习下载。获取之后可直接用于YOLO、Faster R-CNN等常见检测框架的训练与验证免除自行转换格式的步骤还可按照VOC或YOLO目录规范配置路径快速展开肺炎检测实验结合数据规模与标注类型进行数据增强、预训练和模型评测等后续研究。1. RSNA肺炎检测数据集VOCYOLO双格式到底解决了什么问题做过目标检测的人最怕的不是模型难调而是数据格式难缠。RSNA肺炎检测数据集来自北美放射学会RSNA的公开挑战赛原始素材是DICOM胸片标注埋在CSV里坐标、灰度、尺寸都不规整直接拿来喂给YOLO会当场劝退。这个7z包已经把数据整理成VOCXML和YOLOtxt双份格式共6012张、1个类别解压之后可以直接进训练流水线。适合正在做肺结节或肺炎检测的从业者也适合想用医学影像入门YOLO目标检测的人你不需要再写一轮格式转换脚本省下的时间足够你把训练参数多调几轮。2. 把7z压缩包安全落盘Linux与Windows两条解压路径2.1 为什么用7z压缩率、固实包与磁盘预判这个包把VOC和YOLO两份数据放进了同一个7z文件而不是分开两个zip是有现实考虑的。JPG、PNG这类图片已经做过一次有损或无损压缩zip对它们的再压缩能力很弱但7z的LZMA使用固实压缩把整个包当成一个连续数据块处理能利用图片之间的横向相关性再挤出10%~20%的体积更重要的是只有一个文件分发、校验、归档都省事。拿到包的第一步不要急着解压先在命令行下用7z l看包内清单。这样你能摸清几件事包内是不是VOC和YOLO并列的目录结构、JPEGImages里是jpg还是png、有没有单独放ImageSets。这些信息直接决定你后面的数据加载代码怎么写。另外先看下磁盘剩余空间够不够——解压后的体积通常是压缩包体积的2~3倍RSNA这类胸片数据集尤其明显。2.2 Linux下解压7zp7zip-full与7z x的精确用法常见做法是装p7zip-fullDebian/Ubuntu一行搞定。如果你在CentOS/RHEL系对应包名是p7zip用yum install p7zip。装完确认7z命令存在再操作。# 查看包内目录结构避免盲解压 7z l RSNA肺炎检测数据集VOCYOLO格式6012张1类别.7z # 完整解压到指定目录-o后面不要带空格 7z x RSNA肺炎检测数据集VOCYOLO格式6012张1类别.7z -o/data/rsna/只要7z l能看到VOC/和YOLO/两个顶层目录解压出来就不会乱。注意7z x会保留包内目录结构7z e则会把所有文件平铺到同一个目录下——很多人在这步翻车解压完6000多张图全堆在一层XML和JPG混在一起没法用。这个包务必用7z x。如果只需要YOLO格式完全可以用通配符只解压一部分省一半磁盘7z x RSNA肺炎检测数据集VOCYOLO格式6012张1类别.7z -o/data/rsna_yolo/ YOLO/*通配符参数跟在包名后面路径要从包内根目录写起。我在生产环境还会加-bsp0把进度信息关掉配合nohup扔后台避免终端被刷屏。2.3 Windows下解压7z7-Zip与侧边栏文件树的取舍Windows下最省事的方案是装7-Zip然后右键解压。但对于只想确认包内结构的场景直接双击7z文件7-Zip会在界面上把包内目录展开成文件树不用解压就能预览VOC和YOLO各目录里的文件。这个习惯能帮你提前发现是不是只有图片没有标注、ImageSets里train和val是否齐全等于一个免费的后悔药。真正要解压时选中需要的目录再点“解压”按钮7-Zip只会解出选中的部分。需要全部解压就右键整个压缩包选择“7-Zip - 解压到 当前文件夹”。解压过程中不要直接拔电源或强杀进程7z写文件是顺序进行的被中断后虽然能重新解压但之前的半截文件会造成空间浪费。有一点容易忽略如果文件名里有中文Windows打包后到Linux下可能出现编码错乱。这个包里顶层目录是VOC和YOLO这类ASCII名称一般不受影响但如果发现解压出来的文件名带乱码用convmv或7z的编码参数重命名/重新解压一次就能解决。2.4 解压后的三件事数图片、数标注、看尺寸分布解压完先校验不要直接开训练。第一步数文件数量图片数和标注数必须一致。第二步看图片模式和尺寸这决定后面数据加载写不写额外处理。第三步是抽样目检用脚本随机挑几张图把XML里的框画出来确认框不是乱框。find /data/rsna/VOC/JPEGImages -type f | wc -l find /data/rsna/VOC/Annotations -type f | wc -l两条命令的结果应该一致。如果不一致优先怀疑是解压时跳过同名文件回压缩包里核对一下。接着用Python看部分图片的尺寸和模式from PIL import Image import glob for img in sorted(glob.glob(/data/rsna/VOC/JPEGImages/*.jpg))[:10]: with Image.open(img) as im: print(img.split(/)[-1], im.size, im.mode, im.format)输出里看到L模式的灰度图先记下来第5章会讲它怎么坑你。看到尺寸参差不齐也不用慌ultralytics在训练时会自动letterbox但如果你要自己写Dataset类这一步就躲不掉了。3. VOC和YOLO两种标注格式转换脚本与四个边界坑3.1 Pascal VOC的目录约定与XML里的坐标系VOC格式有个显著特点它不靠配置文件而是靠目录结构说话。JPEGImages里放图Annotations里放同名XMLImageSets/Main里放train.txt、val.txt这些划分文件。拿到别人整理的VOC包第一件事就是确认这三个目录都在缺Annotations就白搭缺ImageSets其实还能补救自己按比例划分就行。XML里每个目标对应一个object节点name是类别名bndbox里是xmin、ymin、xmax、ymax的绝对像素坐标。这个直观适合人看但YOLO训练不认这种坐标所以双格式包的意义就是把麻烦提前解决掉。这里有个细节XML里的size字段记载的是图片宽高但它只是一份“当时的记录”。图片被resize过、被格式转换过程丢掉过原始信息的情况在RSNA数据里不少所以转换时永远以JPEGImages里真实图片的宽高为准不要信XML里的size。3.2 归一化坐标的计算法与class id的处理YOLO格式的核心是“归一化到0~1的相对坐标”。每一行txt对应一个目标class_id x_center y_center width height。x_center和y_center是框中心的相对坐标width和height是框宽高相对图片的比例。而class id是整数映射关系由data.yaml里的names定义。这个包只有1个类别pneumoniaclass_id固定为0。很多人拿到1类数据集会忽略类别字典一旦哪天往包里加了第二个类别所有训练代码都要返工。我一般会从一开始就维护一个name-id字典哪怕只有1个类别也保持同样的代码路径。3.3 一份可以直接跑的VOC转YOLO脚本import glob import os import xml.etree.ElementTree as ET from PIL import Image xml_dir /data/rsna/VOC/Annotations img_dir /data/rsna/VOC/JPEGImages out_dir /data/rsna/YOLO/labels class_map {pneumonia: 0} os.makedirs(out_dir, exist_okTrue) for xml_path in sorted(glob.glob(os.path.join(xml_dir, *.xml))): tree ET.parse(xml_path) root tree.getroot() img_name root.find(filename).text img_path os.path.join(img_dir, img_name) with Image.open(img_path) as im: img_w, img_h im.size lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in class_map: continue box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h if w 0 or h 0: continue lines.append(f{class_map[cls_name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) txt_path os.path.join(out_dir, os.path.splitext(img_name)[0] .txt) with open(txt_path, w) as f: f.write(\n.join(lines))这段脚本里有两个地方特意写成这样一是用Image.open读真实宽高而不是用XML里的size二是float()而不是int()去解析坐标。RSNA里有些XML坐标带小数用int会截断标注框会偏移一点点累积起来就是mAP下跌的元凶。归一化坐标保留6位小数足够再多不会带来精度提升。脚本跑完后用下面这段代码做单样本正确性校验把txt里的归一化坐标换算回绝对值画在原图上直接目测框位。from PIL import Image, ImageDraw img_path /data/rsna/YOLO/images/0001.jpg txt_path /data/rsna/YOLO/labels/0001.txt with Image.open(img_path) as im: w, h im.size draw ImageDraw.Draw(im) for line in open(txt_path): cls_id, xc, yc, bw, bh map(float, line.split()) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) draw.rectangle([x1, y1, x2, y2], outlinered, width4) im.save(/tmp/check_0001.jpg)这步花不了1分钟能拦下至少一半的“训练效果不好”的玄学问题。3.4 转换过程中最常踩的四个边界坑坑一说的是多目标只取第一个。RSNA胸片上肺炎病灶可能不止一块XML里会有多个object节点。如果转换脚本只取了obj.find(object)返回第一个节点而不是findall后面几个框就凭空消失了。现象是训练时正样本数急剧减少、模型recall低原因是多数入门教程只演示单目标XML解决是统一用findall遍历。坑二是坐标小数被int截断。现象是标注框整体往左上偏移原因是白用了int()解决见3.3的脚本用float。坑三是文件夹命名与txt文件不同名。RSNA整理包有时图片叫0001.jpg、标注叫0001.txt但labels目录里可能混有来自其他数据集的同名文件。现象是训练警告image not found或者样本数对不上解决是解压后先数文件数量一致性。坑四是ImageSets的train/val划分没转成YOLO路径清单。YOLO训练需要的是train.txt和val.txt两个文件每个路径一行。而VOC的ImageSets里写的是不带扩展名的名字。不转的话data.yaml配train会找不到文件训练直接报错。这条对用ultralytics跑的朋友来说几乎必踩。生成这两份清单的常见做法是# 把VOC的ImageSets/Main/train.txt转成YOLO需要的train.txt图片绝对路径清单 main_dir /data/rsna/VOC/ImageSets/Main with open(f{main_dir}/train.txt) as f: names [line.strip() for line in f if line.strip()] with open(/data/rsna/YOLO/train.txt, w) as out: for name in names: out.write(f/data/rsna/YOLO/images/{name}.jpg\n)这段代码逻辑不复杂但要注意拼接的是images目录不是labels目录data.yaml里train指定的就是这个txt。4. 用YOLO在PyCharm里跑通肺炎检测yaml、损失函数与训练参数4.1 在PyCharm里从零装好YOLO环境用PyCharm跑因为新手调试友好断点、监视变量都直观。常见做法是新建项目时选好虚拟环境然后在底部Terminal里装ultralytics。注意不要直接在全局Python里装避免把系统环境搞坏。pip install ultralytics装完先不要急着训练跑两条验证命令确认torch能看到GPU确认yolo能加载权重。import torch from ultralytics import YOLO print(torch.__version__, torch.cuda.is_available()) model YOLO(yolov8n.pt) # 首次运行会下载预训练权重torch.cuda.is_available()返回False说明装的torch是CPU版需要重装CUDA版pip uninstall torch torchvision -y # 根据本机CUDA版本选择cu118/cu121/cu124后缀 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121参数说明cu121表示匹配CUDA 12.1的预编译包如果本地是CUDA 11.8把后缀改成cu118或cu124。这步踩的人最多报错里带CUDA unavailable十有八九是这里的问题。4.2 data.yaml的两个字段绝对路径与单类别的namespath: /data/rsna/YOLO train: train.txt val: val.txt names: 0: pneumonia这份yaml最关键的一点path写绝对路径。PyCharm里运行训练脚本时当前工作目录往往是项目根目录而不是数据目录写相对路径会导致ultralytics找不到train.txt。names里0: pneumonia说明这个检测任务只有1个类别。还有一个容易忽略的细节train.txt里的图片路径必须和path字段拼接后恰好构成真实路径。如果train.txt里写的是绝对路径ultralytics会让train.txt里的路径优先于path所以两种写法不要混用要么全是绝对路径要么全是相对路径相对data.yaml里的path。4.3 三个优先调的参数epochs、batch、imgszfrom ultralytics import YOLO model YOLO(yolov8n.pt) results model.train( data/data/rsna/YOLO/data.yaml, epochs100, batch16, imgsz512, device0, patience20, )参数说明epochs在这个数据规模下100轮足够再多容易过拟合batch按显存来定12GB卡用16比较稳24GB卡可以试32imgsz512是因为胸片分辨率高但目标肺炎区域相对大512和640之间的差别不大512能省显存、提速。patience20是早停耐心值val的mAP连续20轮不涨就停。医生标注的边界框主观性较强val指标波动本来就大patience设大一点没坏处。用yolov8n而不是yolov8s起步是因为这个数据集只有6000多张且只有1类n的容量够用。想追更好效果再跑s或m但要在同等参数下对比避免换模型的同时换了imgsz导致结论失真。4.4 训练日志里的损失函数box_loss、cls_loss与波动现象的观察训练时终端会同步打印box_loss、cls_loss、dfl_loss三个数值。box_loss对应边界框回归损失包含CIoU与DFL组合cls_loss是分类损失dfl_loss是分布聚焦损失。三者之和是总loss但ultralytics默认分别打印。对RSNA这类医学影像前10个epoch里cls_loss下降最快因为模型先学会区分有炎症和没有box_loss的下降会慢很多因为边界框精度依赖特征图分辨率。常见误区是只看总loss——它在3个分量互相抵消时可能显示平稳掩盖了某一个分量发散的问题。我一般会把每个epoch的三个分量都记录到CSV里单独画曲线。如果box_loss在后面出现反弹先查坐标转换如果cls_loss持续不降先查类别配置。另外医学影像里病变区域往往只占整图很小比例anchor在特征图网格上大多数位置是负样本这种背景主导会让loss曲线呈现波动——看着在降其实模型在输出“没有目标”。这不是代码bug属于数据本身特性处理方法放在第5章的样本不均衡条目里。如果做的是肺结节定位而不是肺炎检测建议把cls_loss的权重调高一点ultralytics的train参数里有cls权重。肺结节更小、更稀疏分类错误的代价比定位错误更高。4.5 数据划分与跑训练时的注意事项训练前先检查train.txt与val.txt是否按病人ID隔离尤其RSNA一类的医学数据。“同一病人的两张胸片一张在train、一张在val”是个隐性数据泄露会让val指标虚高。这个包在整理时基本会按VOC约定的划分走但换到自己的数据集时要按病人维度做分组划分。如果是在AGX Orin这类边缘设备上跑同样的训练建议后续把权重导出为TensorRT推理速度比FP32翻倍不止训练阶段这些参数不用动直接把device指到CUDA核心即可。5. RSNA数据集专踩的5个坑灰度图、坐标转换与样本不均衡医学影像数据集和自然图像数据集有一批不一样的坑。自然图像翻车大多出在网络结构或超参RSNA这类胸片数据的坑集中在数据本身的物理性质上——灰度、尺寸、标注质量。下面5条是跑这个数据集跑出来的血泪经验按现象、原因、解决三段写。5.1 灰度图被当成单通道训练时直接报错现象训练进行到某个epoch时日志突然抛出类似“Expected input channels to be 3, but got 1”的报错或者图片在DataLoader加载阶段直接失败整个训练中断。原因RSNA原始胸片是DICOM灰度图整理成VOC格式时如果只做了格式转换没有做通道复制JPG里就是单通道灰度。PIL读取为L模式而YOLO的backbone接受的是RGB三通道输入。解决解压后统一检查并转RGB再开训练。from PIL import Image import glob for img_path in glob.glob(/data/rsna/VOC/JPEGImages/*.jpg): with Image.open(img_path) as im: if im.mode ! RGB: im.convert(RGB).save(img_path)这段代码跑一遍后再随机抽几张确认im.mode都是RGB。如果XML里的sizedepth写3但实际是灰度图转换脚本也要同步修正——这就是第3章强调“以实际图片为准”的由来。5.2 图片尺寸悬殊batch训练OOM现象训练时一个batch正常下一个batch直接OOM换小batch又慢得离谱。原因RSNA整理出来的胸片尺寸不统一有1024x1024的也有256x256的。YOLO在训练时会按imgsz做letterbox缩放大图的内存占用明显更高导致显存波动。解决不要在自己代码里统一resize会破坏bbox坐标而把缩放交给模型的letterbox。ultralytics在送入batch前会统一缩放显存峰值基本稳定。如果单卡实在不够优先降imgsz而不是降batch因为batch太小时BN层统计不稳训练效果会明显变差。5.3 坐标绝对值写死换图就错位现象训练出来的模型在单张测试图上效果不错换一批图后框整体偏移。原因VOC的XML坐标是绝对像素值图片resize后如果没有同步缩放坐标框就错位。常见做法是有人为了统一尺寸直接用PIL的resize保存图片却忘了改XML。解决统一用归一化坐标喂给YOLO如果要resize先算缩放比例再更新坐标不要手动改XML。5.4 阳性样本占比低recall虚低现象训练完后日志里mAP50看着还行但实际推理时很多真实病灶没检出recall只有0.4~0.6。原因RSNA肺炎检测数据里很多胸片根本没有病灶阴性样本占大头。YOLO默认的置信度阈值是0.25在负样本主导下模型倾向保守输出mAP和实际漏检率对不上这是个典型的黑匣子问题。解决推理时把conf降到0.05~0.1再配合NMS。评估还是用val的mAP为准。如果阈值降下来recall有明显提升说明模型本身没有大问题。另一种手段是训练时开启更强的增强mosaic、mixup或者给cls_loss加权重。5.5 病灶框太小被YOLO直接过滤现象训练后发现val集上一个小病灶都没检出而大的实变区都能检出。原因YOLO有默认的anchor和最小框限制框宽或高小于某个像素阈值时会被当成噪声滤掉。RSNA里早期肺炎的病灶可能是几毫米的小斑片影转成512x512训练图后可能只占十几个像素。解决在转换阶段把小目标按原图比例保留训练时imgsz不要开太小同时用ultralytics的scale增强参数扩大目标尺寸多样性。如果要正经做小目标检测后续应该切patch训练或者换成支持小目标的模型结构。6. 训练完成后的验证mAP曲线、漏检分析与ONNX导出6.1 val跑一遍不要只看日志里的mAP训练结束后ultralytics在runs/detect/train/下已经存了验证集的结果图像和metrics。但自己再显式跑一次val更放心from ultralytics import YOLO model YOLO(/data/rsna/runs/detect/train/weights/best.pt) metrics model.val(data/data/rsna/YOLO/data.yaml, conf0.05) print(metrics.box.map50, metrics.box.map)conf0.05是故意调低的。医学影像负样本多按默认0.25评估会把一堆低置信度真阳性框过滤掉map50会偏低。如果压低conf后map50从0.55跳到0.65说明模型其实学到了特征只是置信度标定偏保守。6.2 漏检分析哪些图翻车val输出里有每张图的预测结果把漏检的图挑出来逐张看。我会把漏检图分成三类处理病灶太小、病灶被肋骨遮挡、病灶对比度极低。前两类靠增强或切patch缓解第三类多是标注本身的问题——原始标注框画得极松模型学出来的框反而比标注准。这个习惯帮我避开了一个坑第一次跑通时mAP50有0.6我觉得可以收工了结果漏检的全是小病灶。后来训练时开了mosaic加小目标增强才把recall拉上去。6.3 导出ONNX脱离PyTorch也能部署导出这一步很常见也简单model.export(formatonnx, imgsz512, halfTrue)参数说明imgsz要和训练时的输入尺寸一致halfTrue导出FP16模型体积减半但部署端需要支持FP16推理。导出的onnx可以用onnxruntime在CPU上直接跑遇到显存紧张的情况转TensorRT部署的收益会更明显AGX Orin这类边缘设备上差距尤其大。我现在拿到任何新的医学影像数据集第一件事一定是先看图模式和坐标分布而不是急着写训练代码。这个习惯帮我省下一个又一个深夜排查的时间也让我少走了很多弯路。希望帮到你。本文还有配套的精品资源点击获取