ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于YOLOv5的胸片数据集解析与迁移学习训练实践

基于YOLOv5的胸片数据集解析与迁移学习训练实践 简介面向医学影像识别与目标检测任务的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, encodingutf-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这种相对温和的权重再对少数类做马赛克增强。mosaic1.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 还是 nano800张胸片类别间差异集中在纹理和形状没有复杂的背景语义YOLOv5s是折中方案。nano快但查全率低结核这种小病灶容易漏medium效果好一点但800张图足够让yolov5s过拟合到不错的状态medium反而需要更强的正则。用yolov5s起步训到第30个epoch看曲线如果验证集mAP还在明显上升再换yolov5m不迟。4.2 写 data.yaml 和运行训练需要创建chest_xray_data.yamltrain: 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_pneumonia4对应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看每个类别的APpython 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格式的检测结果方便后面用其他指标分析。输出会打印每个类别的AP0.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, 5nc)需要自己在后处理里做非极大抑制。这一点和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训练mAP0.5从0.62提升到0.71代价是训练时间翻倍。验证时也用--img 960推理时不要混用尺寸否则框坐标会偏移。6.3 打乱类别顺序做二次标注校验YOLOv5训练完发现某个类mAP特别低不要急着调超参先回到标注本身。把模型的预测框画出来和原图一起看如果预测框总是比标注框大一圈说明标注本身就画得松。我会把预测结果写回成一个新的label然后用视觉工具对第二遍标注结果进行人工修正。800张图不多人工核对一遍只需要两小时但带来的提升比任何参数调优都明显。医学图像的定位精度直接决定后续诊断分析宁可框紧凑一点也不要框住大片背景。使用这份数据集时每个zip解压出来的文件名都保留了Roboflow痕迹认真对待这些痕迹比直接跑通训练更有价值。本文还有配套的精品资源点击获取
返回列表