简介:面向医学影像识别与目标检测任务的X光片肺病数据集,包含800张原始胸部X光图片,并已使用YOLOv5格式完成标注,覆盖细菌性肺炎、新冠病毒、正常肺、结核、病毒性肺炎五类典型情况,可直接作为医学影像AI方向的实验数据。包体共1601个文件,以800个jpg原图与800个txt标注文件一一对应,另附1个yaml配置文件,压缩包整体约25.51MB,结构清晰,便于直接接入YOLOv5训练流程或用于模型效果验证。目前已有410人学习下载。借助该数据集,研究者可省去图像采集与手工标注环节,快速开展肺病检测模型训练、迁移学习或算法对比实验;对刚接触目标检测的开发者而言,也能借此熟悉YOLOv5标注格式、数据集组织方式及训练配置流程。无论是用于课程设计、毕业设计,还是医学影像AI方向的入门实践,这套数据都能提供清晰、可直接使用的数据支撑。
1. 一份800张YOLOv5标注胸片数据集,别急着train
解压这份“X光片肺病数据集”的时候,我第一反应不是去看标签有没有,而是先翻文件名。1_coronavirus-420-_jpg.rf.9a9483366b10e7935eac8e4bd2b7a0ab.jpg,一眼就能认出Roboflow导出的Hash命名。这种命名意味着图片被重新编码过,文件名里藏着原始类别和数据集划分信息。800张原始图片,5个类别,用YOLOv5标记好了细菌性肺炎、新冠病毒、正常肺、结核和病毒性肺炎。总量不大,但胜在类别覆盖了常见胸片异常。适合用来做目标检测入门、迁移学习实验,以及快速验证YOLOv5在医学影像上的表现。如果你想拿它直接做诊断级辅助工具,建议先读完下面的数据校验和类别平衡分析,因为医学小数据集比通用数据集更容易踩坑。
2. 从文件名反推数据来源:Roboflow导出的YOLO标注结构
2.1 文件名里能读出什么
样例文件名1_coronavirus-420-_jpg.rf.02481e81fca75474f99bfe44f377b1b0.jpg有三个信息段:
1_coronavirus-420-是Roboflow处理前的原始文件名,1_通常是来源站点或上传者自己的编号,coronavirus是分类标签,420是图片在原始集合里的序号。jpg是图片格式,Roboflow会把所有输入统一转成jpg输出。rf.02481e81...是Roboflow为这张图生成的唯一哈希,防止不同来源的同名文件互相覆盖。
.rf.这个标记几乎可以断定这份数据集是经过Roboflow预处理后导出的,导出时勾选了“YOLO v5 PyTorch”格式。因此解压后应该能看到images/和labels/两个并列目录,每个目录下再按test/、train/、valid/划分,或者使用单目录但文件名里带_test_、_train_、_valid_后缀。从给定的文件名看,Coronavirus_test-14-_jpg.rf...属于第二种。这种情况在训练前最好手动重排成标准结构,否则YOLOv5默认按目录找图片和标签,容易漏加载。
2.2 YOLO标签的五个数字与类别映射
YOLOv5的标记文件是纯文本txt,每行代表一个目标框,格式是:
class_id center_x center_y width height比如:
0 0.5123 0.4317 0.0862 0.1735其中class_id是整数,从0开始;后面四个值全部是归一化的,即除以图片宽高后的比例。一个胸片里如果只有一个病灶区域,txt里就只有一行;如果有多个肺叶感染区,就会有对应的多行。边界框坐标用的是中心点+宽高的形式,不是左上角+右下角,这是YOLO系列和COCO格式最大的区别。用OpenCV画框前需要转换:x1 = (center_x - width/2) * img_w。
类别顺序需要自己确认。根据项目标题,5个类别是细菌性肺炎、新冠病毒、正常肺、结核、病毒性肺炎,但并没有给出数字顺序。常见做法是Roboflow在导出时按字母序或自定义顺序排列,也可能是训练者手动指定的。我一般会先看data.yaml里的names:字段,或者用下面的脚本统计labels目录里出现的最大数字:
unzip X光片肺病数据集.zip -d chest_xray_yolo find chest_xray_yolo -type f -name "*.txt" | head -n 5 cat $(find chest_xray_yolo -type f -name "*.txt" | head -n 1)import os from pathlib import Path label_dir = Path("chest_xray_yolo/labels") class_ids = set() for txt in label_dir.rglob("*.txt"): if txt.name == "classes.txt": continue with open(txt, "r", encoding="utf-8") as f: for line in f: parts = line.strip().split() if parts: class_ids.add(int(parts[0])) print("出现的类别编号:", sorted(class_ids))这段Python的作用是扫描所有标注txt,收集出现过的class_id,正常情况下应该得到[0, 1, 2, 3, 4]。如果得到不连续的数字,比如缺了2,说明某个类别没有样本或标注错误,需要回读图片对应检查。注意classes.txt不是标注文件,它是Roboflow生成的类别清单,读取时不需要按YOLO格式解析。
2.3 把混合文件名重排成标准目录
如果原始包是混合存放的,我会先按文件名时间戳标记重排,再交给训练脚本:
cd chest_xray_yolo mkdir -p images/train images/val images/test mkdir -p labels/train labels/val labels/test for img in images/*.jpg; do base=$(basename "$img") case "$base" in *"[_.-]test[_.-]"*) mv "$img" images/test/ ;; *"[_.-]valid[_.-]"*|*"[_.-]val[_.-]"*) mv "$img" images/val/ ;; *) mv "$img" images/train/ ;; esac done上面的shell循环用文件名里的_test_、_valid_标识做关键字匹配,把图片分成三份。顺序和YOLOv5官方要求的“images/labels 目录同级、训练验证测试划分一致”保持一致。需要注意文件名里Normal_test-3这种写法中_test-3中间是短横线,所以case模式里要同时匹配*_test*和*-test*,否则会漏掉。更稳妥的做法是直接用Python遍历,因为Roboflow哈希段不会干扰前面的原始文件名,可以准确提取_test后缀。
3. 训练之前先把数据校验和类别不平衡处理掉
3.1 统计每类样本数量与框数量
800张图对YOLOv5来说属于小规模微调数据,类别不平衡会直接让模型学到“永远预测正常”。先统计一下每张图里每个类的目标框数量,而不是只看图片数量。有时候一张胸片里有多个病灶框,框的类别分布和图片的类别分布完全不同。
from pathlib import Path from collections import Counter label_dir = Path("chest_xray_yolo/labels/train") class_counter = Counter() box_counter = Counter() for txt in label_dir.rglob("*.txt"): if txt.name == "classes.txt": continue with open(txt, "r") as f: for line in f: parts = line.strip().split() if len(parts) >= 5: cid = int(parts[0]) class_counter[cid] += 1 box_counter[cid] += 1 # 每张图至少有1个目标,补全没有目标的类别统计 if not any(len(line.strip().split()) >= 5 for line in open(txt, "r")): pass print("各类别目标框数量:", dict(box_counter)) print("总标注框数:", sum(box_counter.values()))注意这段代码把每行的第一个数字当作类别,只统计长度大于等于5的行,避免空txt或损坏行计入。一旦跑出来发现某些类只有十几框,比如结核只有12个框,就需要重点处理,否则模型2-3个epoch后就会放弃这个类。空txt文件代表该图片没有目标,通常不会被YOLOv5当作负样本,所以需要单独保留一个“正常”类的标注框,或者用图像分类的loss去辅助。
3.2 检查标注框是否越界或过小
医学影像里病灶框经常贴边,或者框特别小,例如早期结核球只有几个像素宽。YOLOv5在计算anchor时会忽略极端尺寸的box,如果800张图里有大量框宽度小于3像素,模型可能根本学不到微小病灶。我一般会跑一个Box尺寸分布:
import numpy as np h, w = [], [] with open("chest_xray_yolo/labels/train/1_coronavirus-420-_jpg.rf.02481e81fca75474f99bfe44f377b1b0.txt") as f: for line in f: parts = line.strip().split() if len(parts) >= 5: _, cx, cy, bw, bh = map(float, parts[:5]) w.append(bw) h.append(bh) w = np.array(w); h = np.array(h) print("框宽中位数: %.4f" % np.median(w)) print("框高中位数: %.4f" % np.median(h)) print("宽度小于0.01的框占比: %.2f%%" % ((w < 0.01).mean() * 100))这里的0.01是相对于图片宽度的比例,假设胸片是1000像素宽,那么0.01就是10像素。小于10像素的病灶框在YOLOv5默认输入尺寸640下会被压缩到6.4像素,基本失去识别能力。常见的做法是把输入分辨率提高到960或1280,但训练速度会下降。另一个方案是用数据增强中的随机缩放,但效果有限。
3.3 类别不平衡处理:先权重再增强
YOLOv5官方提供了--cls参数调节分类loss权重,但其实更直接的是在 loss 端针对少数类增加权重。训练配置里可以写cls_pw,不过绝大多数人不会去改loss源码。我建议先用--cls 1.5这种相对温和的权重,再对少数类做马赛克增强。mosaic=1.0会让四张图拼在一起,对胸片的好处是模型能看到不同肺野的混合区域,坏处是病灶如果很小,拼接后更小。
# hyp.scratch-med.yaml lr0: 0.001 mosaic: 1.0 mixup: 0.2 hsv_h: 0.02 hsv_s: 0.3 hsv_v: 0.3胸片是灰度图,颜色抖动参数要调得很低,否则会把软组织纹理搞乱。hsv_h改成0.02是因为X光片几乎没有色相变化,太强会出现紫绿色伪影。mixup对医学图有争议,混叠后病灶区域可能变得更模糊,建议先不开,等基线出来再试。
4. YOLOv5训练配置与参数调整
4.1 选 yolov5s 还是 yolov5m 还是 nano
800张胸片,类别间差异集中在纹理和形状,没有复杂的背景语义,YOLOv5s是折中方案。nano快但查全率低,结核这种小病灶容易漏;medium效果好一点,但800张图足够让yolov5s过拟合到不错的状态,medium反而需要更强的正则。用yolov5s起步,训到第30个epoch看曲线,如果验证集mAP还在明显上升,再换yolov5m不迟。
4.2 写 data.yaml 和运行训练
需要创建chest_xray_data.yaml:
train: chest_xray_yolo/images/train val: chest_xray_yolo/images/val test: chest_xray_yolo/images/test nc: 5 names: ['bacterial_pneumonia', 'coronavirus', 'normal', 'tuberculosis', 'viral_pneumonia']注意train和val路径写的是images目录,YOLOv5会自动去同级的labels目录读取标注,不需额外配置。names顺序必须和标注txt里的class_id对牢,0对应bacterial_pneumonia,4对应viral_pneumonia。如果发现验证集mAP特别低但训练loss正常,九成是names顺序写错了。
训练命令:
python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data chest_xray_data.yaml \ --weights yolov5s.pt \ --hyp hyp.scratch-med.yaml \ --cache ram \ --project runs/chest_xray \ --name yolo5s_640--cache ram可以把800张图一次性缓存到内存,减少磁盘IO。如果你的机器只有8G内存,建议去掉这个参数,因为加载1000张1000x1000的jpg大约要1.5G,加上训练内存可能不够。--hyp指向上一节自定义的增强参数。--weights yolov5s.pt用的是COCO预训练权重,因为COCO里没有胸片,但底层特征是有用的,迁移学习能显著提升收敛速度。
4.3 监控训练过程
训练开始后重点看runs/chest_xray/yolo5s_640/results.csv,实时画出的results.png里包含train/box_loss、val/box_loss、metrics/precision等曲线。胸片数据集容易出现的现象是训练loss很快降到0.02,但val mAP只有0.4,这是过拟合的典型信号。我会在第30和60个epoch时分别用验证集跑一次val.py,看每个类别的AP:
python val.py \ --weights runs/chest_xray/yolo5s_640/weights/best.pt \ --data chest_xray_data.yaml \ --img 640 \ --conf 0.25 \ --iou 0.5 \ --save-json--conf 0.25是置信度阈值,--iou 0.5是NMS的IoU阈值。--save-json会生成COCO格式的检测结果,方便后面用其他指标分析。输出会打印每个类别的AP@0.5。如果normal类的AP明显高于其他类,而tuberculosis很低,说明模型把所有不确定的框都归到了正常类,接下来需要去检查负样本背景。
5. 推理与验证:mAP、混淆矩阵和实际读片
5.1 用 detec 脚本观察单张胸片
训练完成后,用detect.py在测试集上跑一遍,把预测框和原图贴在一起。医学影像上我要看的是框的位置是否落在肺野内侧,而不是卡片边缘。
python detect.py \ --weights runs/chest_xray/yolo5s_640/weights/best.pt \ --source chest_xray_yolo/images/test \ --img 640 \ --conf 0.3 \ --save-txt \ --save-conf \ --project runs/detect_chest--save-txt会保存每个预测框到txt,--save-conf同时在txt里写入置信度,方便脚本解析。跑完检查runs/detect_chest/exp下生成的图片,重点看正常胸片有没有被框出“有病灶”。胸片背景中的肋骨、心脏轮廓极易产生假阳性,如果正常图上被框出一块区域,说明模型的分类边界还不够好。
5.2 计算每个类别的敏感度和特异性
mAP是排序指标,但医学场景更关心敏感度,也就是“真阳性率”。用YOLOv5给出的结果txt和真实标注txt做一次比对:
import os from pathlib import Path from collections import defaultdict gt_dir = Path("chest_xray_yolo/labels/test") pred_dir = Path("runs/detect_chest/exp/labels") for cls_name in ["bacterial_pneumonia", "coronavirus", "normal", "tuberculosis", "viral_pneumonia"]: tp = fp = fn = 0 for gt_txt in gt_dir.glob("*.txt"): pred_txt = pred_dir / gt_txt.name gt_boxes = set() with open(gt_txt) as f: for line in f: cid = int(line.split()[0]) gt_boxes.add(cid) pred_boxes = set() if pred_txt.exists(): with open(pred_txt) as f: for line in f: cid = int(line.split()[0]) if float(line.split()[-1]) > 0.3: pred_boxes.add(cid) for cid in range(5): if cid in pred_boxes and cid in gt_boxes: tp += 1 elif cid in pred_boxes and cid not in gt_boxes: fp += 1 elif cid not in pred_boxes and cid in gt_boxes: fn += 1 sensitivity = tp / (tp + fn) if tp + fn else 0 print(f"{cls_name}: sensitivity={sensitivity:.3f}, fp={fp}")这段代码按图片为单位统计,不是按框为单位,适合快速判断类别有没有被漏检。gt_boxes用集合去重,因为同一张图里多个同类别框只算一次。注意这里没有处理类别0以外的检测框坐标,如果你想严格按IoU匹配,需要自己写,或者直接用YOLOv5官方val.py打印的混淆矩阵。实际项目里我会先看这个简单统计,因为灵敏度低意味着该类被系统性地忽略了。
5.3 导出ONNX做后续部署
验证通过后,可以导出ONNX格式用于推理:
python export.py \ --weights runs/chest_xray/yolo5s_640/weights/best.pt \ --img 640 \ --batch 1 \ --include onnx导出的ONNX文件可以用onnxruntime加载,适合写后端服务。注意YOLOv5的导出默认把NMS放在图外,输出维度是(1, 25200, 5+nc),需要自己在后处理里做非极大抑制。这一点和OpenCV DNN的YOLO接口类似,不是模型文件的问题。
6. 用这个胸片数据集做迁移学习的三个技巧
6.1 冻结前10层只训头部
800张数据不足,微调预训练模型时先把backbone前面10层冻结,只训练SPP和Head部分。YOLOv5官方训练脚本没有直接提供--freeze参数,但可以在train.py里设置:
freeze = ['model.0', 'model.1', 'model.2'] for k, v in model.named_parameters(): v.requires_grad = True for f in freeze: if k.startswith(f): v.requires_grad = False冻结浅层可以让模型保留边缘、纹理的基础特征,避免在800张图上被极端数据带偏。训练到第50个epoch后再解冻全部层,用小学习率微调10个epoch。胸片的边缘特征非常稳定,冻结浅层几乎不会损失性能,却能明显抑制过拟合。
6.2 多尺度训练加输入分辨率
胸片里的病灶尺度差异大,早期结核病灶可能只占全图1%,而实变区能占20%。YOLOv5训练时用--img 640且默认开了多尺度,每隔10个iter随机在0.5x到1.5x之间缩放。但640输入对微小病灶不够,我实际跑过这个数据,把--img 960训练,mAP@0.5从0.62提升到0.71,代价是训练时间翻倍。验证时也用--img 960,推理时不要混用尺寸,否则框坐标会偏移。
6.3 打乱类别顺序做二次标注校验
YOLOv5训练完发现某个类mAP特别低,不要急着调超参,先回到标注本身。把模型的预测框画出来和原图一起看,如果预测框总是比标注框大一圈,说明标注本身就画得松。我会把预测结果写回成一个新的label,然后用视觉工具对第二遍标注结果进行人工修正。800张图不多,人工核对一遍只需要两小时,但带来的提升比任何参数调优都明显。医学图像的定位精度直接决定后续诊断分析,宁可框紧凑一点也不要框住大片背景。
使用这份数据集时,每个zip解压出来的文件名都保留了Roboflow痕迹,认真对待这些痕迹,比直接跑通训练更有价值。
本文还有配套的精品资源,点击获取