ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

800张X光片训练YOLOv5肺病检测:小样本医学影像实战指南

800张X光片训练YOLOv5肺病检测:小样本医学影像实战指南 简介这套X光片肺病数据集面向医学影像目标检测与AI辅助诊断内含800张原始胸片及配套的YOLOv5格式标注覆盖细菌性肺炎、新冠病毒、正常肺、结核、病毒性肺炎五类关键病灶可直接用于分类或检测模型训练。压缩包共1601个文件其中800个jpg为原始影像800个txt为每个目标的类别与边界框坐标标注1个yaml为数据集配置定义类别序号、名称及训练验证路径整体遵循YOLOv5标准格式无需额外转换即可接入主流目标检测框架。数据包仅25.51MB轻巧便携既适合初学者理解医疗数据标注流程也适合资深研究者作为预训练或迁移学习的补充样本。目前已有410人学习下载对于缺少标注样本的肺病识别项目这套资源能显著节省数据整理时间让开发者更专注于模型调优与性能提升。1. 800张X光片能训出什么这个YOLOv5肺病数据集到底值不值得用做医学影像目标检测的工程师十个里有九个卡在数据上。公开的ChestX-ray14、CheXpert动辄十几万张但那是分类标注想拿来训YOLOv5这种检测器得自己画框、自己核对病灶位置工作量直接劝退。所以当我看到“X光片肺病数据集800张原始图片使用yolov5标记可识别细菌性肺炎新冠病毒正常肺结核病毒性肺炎”这个压缩包时第一反应是这可能是少数拿来就能跑通YOLOv5训练闭环的小规模数据集。800张听起来很少但放在YOLOv5的迁移学习框架下这个量级恰好够用。它解决的核心问题是“标注成本”而不是“数据量”你省去了画框和类别整理的几周时间换来的是一个能跑通完整训练、验证、部署流程的起点。适合三类人做毕设或课题需要快速出结果的学生刚接触YOLOv5想找医学数据练手的算法工程师以及想验证“小样本医学影像检测到底可不可行”的团队。它不适合谁想直接上临床、拿注册证的人800张连门槛都摸不到。理解了这个边界后面每一步才不会跑偏。2. 先看懂数据YOLOv5标记下的五类肺病标注体系YOLOv5训练自己的数据集第一步不是写模型而是把数据格式吃透。这个数据集既然写着“使用yolov5标记”标签文件基本就是标准的TXT格式。但医学影像的目标检测和自然图像有一个显著差异病灶区域形状不规则、边界模糊、大小跨度极大这些都会直接影响你对标签内容的理解和后续训练策略。2.1 数据集目录结构与YAML配置拿到压缩包解压之后常见的目录结构是images和labels两个平级文件夹分别存放原始X光片JPG/PNG和对应的标注TXT文件。每一张图片对应一个同名TXT例如covid_001.jpg对应covid_001.txt。如果压缩包里还带一个data.yaml那更是省事直接指向就可以了。示例data.yaml内容train: ./images/train val: ./images/val nc: 5 names: [bacterial_pneumonia, covid, normal, tuberculosis, viral_pneumonia]这段配置对应标题里的五个类别细菌性肺炎、新冠病毒、正常肺、结核、病毒性肺炎。train和val写的是图片路径YOLOv5训练时会自动到同级的labels目录找同名TXT。如果你的目录结构不是这种规范布局先调整成这个格式再继续否则训练时报错找不到标签文件是家常便饭。2.2 标签TXT的逐行解读与归一化陷阱每个TXT文件里每一行代表一个目标框格式是固定的五列class_id x_center y_center width height。前四列都是归一化数值范围0到1除以图片宽高得到class_id从0开始计数。比如3 0.512 0.533 0.168 0.245含义是结核class_id为3的病灶中心点在图片横向51.2%、纵向53.3%的位置框的宽占16.8%、高占24.5%。用Python脚本快速检查标注是否合法import os label_dir labels violations [] for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue with open(os.path.join(label_dir, fname), r) as f: lines f.readlines() for line in lines: parts line.strip().split() if len(parts) ! 5: violations.append((fname, 列数不对)) continue cid, x, y, w, h parts if not (0 float(x) 1 and 0 float(y) 1 and 0 float(w) 1 and 0 float(h) 1): violations.append((fname, f坐标越界: {line.strip()})) print(f违规标签数: {len(violations)}) for v in violations[:20]: print(v)这段代码遍历所有标签文件检查列数是否为5以及坐标是否越界。X光片里病灶常常贴在胸腔边缘标注时框稍微画出去一点就会导致w或h大于1训练时YOLOv5虽然会自动clip但会带来梯度扰动。这个脚本是最基础的体检工具我拿到任何数据集都会先跑一遍。2.3 五个类别的医学先验与标注难度差异标题里的五个类别在影像学上难度完全不是一个量级。正常肺是最好分的没有局灶性阴影模型只需要学会“什么都没有”的特征。细菌性肺炎和病毒性肺炎都是片状或斑片状磨玻璃影区别在于分布位置细菌性肺炎常累及单侧下叶呈节段性实变病毒性肺炎多为双肺外周带分布间质改变更明显。结核的特征是上叶尖后段的增殖灶或空洞边界相对清晰新冠的典型表现是双肺胸膜下磨玻璃影和病毒性肺炎在影像上高度重叠。这个先验对训练的意义在于类别之间的特征区分度决定了收敛难度。正常肺和结核最好学新冠和病毒性肺炎最难分。你训练完看混淆矩阵大概率就是这两个类别互相串。不要惊讶这是医学图像本身的属性不是模型或数据集的问题。3. 从零训练自己的数据集YOLOv5环境配置到一条龙训练命令数据格式确认无误后接下来就是把YOLOv5跑起来。这一章覆盖从环境搭建到训练出权的完整链路照着敲就能出结果。显卡建议显存不低于6G没有显卡用CPU也能跑只是慢800张图CPU训一轮大概要半小时到一小时后面调参就是个灾难。3.1 YOLOv5环境配置的稳定组合YOLOv5环境配置是新手翻车重灾区多数问题出在torch和CUDA版本不匹配上。最稳妥的做法是先用conda隔离环境再装PyTorch最后装YOLOv5依赖顺序不要乱。conda create -n yolov5 python3.9 -y conda activate yolov5 pip install torch2.0.0 torchvision0.15.0 --index-url https://download.pytorch.org/whl/cu118 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt说明一下这个组合的逻辑Python 3.9兼容性最好太快太慢的版本都可能踩坑torch 2.0.0配cu118是一套久经考验的组合对应CUDA 11.8requirements.txt里的依赖版本和yolov5源码是对齐的装好就能跑。如果你的机器CUDA版本低于11.8别硬装先查nvidia-smi看驱动支持的CUDA版本再选对应的pytorch安装命令否则大概率出现CUDA error: no kernel image is available。3.2 整理数据目录与编写数据配置YOLOv5要求数据集按images和labels分家并且建议在数据集根目录下建train.txt和val.txt指定图片路径也可以直接用YAML里的路径规则。我习惯直接用YAML的目录规则简单清晰。数据集的最终目录结构chest_xray_dataset/ ├── images/ │ ├── train/ │ │ ├── covid_001.jpg │ │ └── ... │ └── val/ │ ├── covid_010.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── covid_001.txt │ │ └── ... │ └── val/ │ ├── covid_010.txt │ └── ... └── data.yaml注意一个关键点images/train里的图片缩略图或旋转过的图必须同步处理对应的标签坐标。如果只是旋转了图片而标签没变训练出来的模型在推理时框的位置会整个偏移。压缩包里的原始图片如果没动过一般没问题但如果你自己做数据增强或预处理这个坑一定要记得。裁切图片后标签坐标同步更新可以参考from PIL import Image import os def crop_image_and_update_label(img_path, label_path, save_img_path, save_label_path, box): img Image.open(img_path) w, h img.size x1, y1, x2, y2 box # 像素坐标 crop img.crop((x1, y1, x2, y2)) crop_w, crop_h crop.size with open(label_path, r) as f: lines f.readlines() new_lines [] for line in lines: cid, cx, cy, bw, bh map(float, line.split()) px_cx, px_cy cx * w, cy * h px_w, px_h bw * w, bh * h # 计算相对裁切区域的坐标 new_cx (px_cx - x1) / crop_w new_cy (px_cy - y1) / crop_h new_w px_w / crop_w new_h px_h / crop_h if new_cx - new_w/2 0 or new_cy - new_h/2 0 or new_cx new_w/2 1 or new_cy new_h/2 1: continue # 目标被裁掉一半过滤 new_lines.append(f{int(cid)} {new_cx:.6f} {new_cy:.6f} {new_w:.6f} {new_h:.6f}\n) crop.save(save_img_path) with open(save_label_path, w) as f: f.writelines(new_lines)这段代码的逻辑是裁图后把原坐标系下归一化的框坐标转成像素坐标再映射到裁切后的新图上。框如果被裁掉一半就丢弃该标注避免给模型一个残缺的目标。医学X光片进行局部裁剪增强时这个脚本可以直接复用。3.3 训练命令与关键启动参数训练入口是YOLOv5自带的train.py最精简的命令如下python train.py \ --data ../chest_xray_dataset/data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --device 0 \ --workers 4 \ --name lung_xray逐个参数说明--data指向我们刚写好的YAML--weights填预训练权重yolov5s.pt是参数量最小的标准版本COCO上训出来的特征提取能力对医学影像的迁移价值很大--img 640是输入分辨率X光片原图一般3000x3000左右直接640会导致病灶细节大量丢失但显存有限的话先640跑通一轮后面再调--batch 16在6G显存下是极限显存小降到8--epochs 100对小数据集来说够跑出趋势--workers 4设成CPU核心数的一半左右少了加载慢多了容易卡死。训练开始后重点看两个东西终端里每个epoch的box_loss和cls_loss是否在下降以及val/object_ness曲线。如果损失在前10个epoch疯狂震荡不降优先怀疑学习率过高或batch过小不要看着训练跑起来就以为万事大吉。4. 800张图怎么调YOLOv5超参数小样本训练的调优路线数据集就800张再怎么调参也弥补不了数据量的绝对劣势但可以在现有框架下把每一张图的价值榨干。这一章讲小样本医学影像训YOLOv5的实际调参经验核心思路是控制自由度、提高增强强度、用验证集兜底。4.1 数据划分按类别比例分层抽样800张图假设五类各约160张如果用随机划分验证集里某一类可能只有十几张导致指标波动极大——这轮mAP 0.85下轮复训变0.7完全是抽样误差。解决办法是分层抽样。import os import random from collections import defaultdict import shutil random.seed(42) img_dir images label_dir labels train_ratio 0.8 class_to_files defaultdict(list) for fname in os.listdir(img_dir): stem os.path.splitext(fname)[0] label_path os.path.join(label_dir, stem .txt) if not os.path.exists(label_path): continue with open(label_path, r) as f: first_line f.readline() if not first_line.strip(): continue class_id first_line.strip().split()[0] class_to_files[class_id].append(fname) train_files, val_files [], [] for cls, files in class_to_files.items(): random.shuffle(files) split_idx int(len(files) * train_ratio) train_files.extend(files[:split_idx]) val_files.extend(files[split_idx:]) os.makedirs(fimages/train, exist_okTrue) os.makedirs(fimages/val, exist_okTrue) os.makedirs(flabels/train, exist_okTrue) os.makedirs(flabels/val, exist_okTrue) for fname in train_files: shutil.move(os.path.join(img_dir, fname), images/train/) shutil.move(os.path.join(label_dir, os.path.splitext(fname)[0] .txt), labels/train/) for fname in val_files: shutil.move(os.path.join(img_dir, fname), images/val/) shutil.move(os.path.join(label_dir, os.path.splitext(fname)[0] .txt), labels/val/)这段脚本的核心是读取每个标签文件的第一行取出类别ID按类别分组后各自按比例划分保证训练集和验证集里五个类别的比例基本一致。注意我只用第一行来判断图片类别因为一张X光片一般标注一个主要病灶多框图片会被归到第一个框的类别里但800张的小数据集这种简化问题不大。划分完打印一下每个集合的类别分布确认没有某类只有个位数再开训。4.2 数据增强参数YOLOv5超参数配置的调整方向YOLOv5内置的数据增强在data/hyps/hyp.scratch-low.yaml文件中默认参数是针对COCO这种多样化自然图像调的直接套用到医学X光片上会水土不服。我的调整思路是增强要强但别强到改变医学语义。关键参数调整lr0: 0.003 lrf: 0.1 momentum: 0.937 weight_decay: 0.0005 warmup_epochs: 3.0 warmup_momentum: 0.8 box: 0.05 cls: 0.5 fliplr: 0.5 mosaic: 0.8 mixup: 0.1 hsv_h: 0.0 hsv_s: 0.0 hsv_v: 0.0 scale: 0.3 translate: 0.1参数说明lr0从默认0.01降到0.003小数据集迁移学习的学习率必须保守否则预训练权重会被快速冲掉hsv_h、hsv_s、hsv_v全部设0X光片是灰度图色调饱和度增强没意义反而会引入伪影mosaic保留0.8但要注意四张X光片拼在一起会破坏肺部结构的整体性模型可能学到“拼接缝”这种虚假特征mixup建议从默认0拉到0.1太强会让病灶区域混叠边界更模糊scale降到0.3因为病灶本来就小过度缩放会让微小结节直接消失。启动训练时加上--hyp参数python train.py \ --data ../chest_xray_dataset/data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 150 \ --device 0 \ --hyp data/hyps/hyp.scratch-low.yaml \ --name lung_xray_v2每轮训练后YOLOv5会保存last.pt和best.pt两个权重best.pt以验证集mAP为准自动选取。小数据集训练的特点是mAP曲线会先快速上升然后进入平台期中间可能出现过拟合导致的回落。盯着results.png里的val/box_loss和val/cls_loss验证损失连续10轮不降反升就该停了。4.3 迁移学习技巧冻结骨干网络还是全量微调YOLOv5支持冻结前几层骨干网络只训练后面的检测头。800张小数据集的常见做法是先冻结训练50轮再解冻全量微调50轮。python train.py \ --data ../chest_xray_dataset/data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 50 \ --device 0 \ --freeze 10 \ --name lung_xray_freeze python train.py \ --data ../chest_xray_dataset/data.yaml \ --weights runs/train/lung_xray_freeze/weights/best.pt \ --img 640 \ --batch 16 \ --epochs 50 \ --device 0 \ --name lung_xray_unfreeze--freeze 10是冻结前10层也就是Backbone的绝大部分。冻结阶段让检测头先适应医学影像的特征分布避免一开始就把预训练权重改得面目全非解冻阶段再精细调整骨干网络。这比直接全量训练150轮效果更稳缺点是训练时间增加约三成。如果你的显卡不支持这么长的训练退而求其次直接全量训练100轮也能出差不多的结果只是方差更大。这步操作比较玄学但小样本下确实能压住过拟合。5. 避坑排查X光片数据集训练YOLOv5的5个真实踩坑点跑通训练只是开始真正劝退人的是那些不会让程序报错、却让结果变成废铁的问题。下面五条全部来自实际训练医学X光片的经验每一条都是“现象→原因→解决”的完整链路。5.1 标签越界导致的AP异常现象训练时box_loss偶发尖峰验证集mAP在0.8附近波动但某个类别的AP只有0.2。检查标签文件发现有几张图片的标注框宽度为1.05或高度为1.1明显是因为病灶紧贴胸廓边缘标注时画出了图像边界。原因标注工具的默认行为允许框超出画布导出归一化TXT时没有做clip操作YOLOv5训练时虽然会自动把越界框裁剪回边界但框的中心点和宽高比例与原始标注不一致导致学习信号被污染。解决用第二章节的检查脚本全量扫描把越界的标签要么删除、要么手动修正。我的做法是设一个阈值任何框的x w/2或y h/2超过1.05的直接把该行从TXT中移除。病灶主体通常还在画布内丢掉一个半个框对训练影响微乎其微但保留一个坏框可能让损失曲线一直抖动。5.2 类别不平衡导致的“正常肺统治一切”现象训练出的模型对细菌性肺炎的召回率有0.75但对结核的召回率只有0.3所有误报都指向了正常肺。原因这个数据集的800张图片里正常肺图片占比可能高达四成约320张而结核只有80张YOLOv5默认的类别损失是等权的样本多的类别梯度占比大模型倾向于把不确定的框分类为正常肺。解决从两个方向入手。第一是训练时给cls损失按类别加权YOLOv5的--cls参数控制的是整体权重做不到逐类加权那就做第二件事数据重采样。对样本少的类别做重复采样让每类参与训练的图片数接近。可以用最简单的办法在划分数据集前把结核和病毒性肺炎的图片在train目录里各复制两份重名会冲突复制时加后缀_copy标签文件同步复制。这不算优雅但在小样本场景极其实用。5.3 验证集图片太少导致的指标幻觉现象训练结束mAP0.5显示0.93你很高心但把模型拿到另一批X光片上一测F1直接掉到0.6。原因800张图按82划分验证集只有160张平均每类32张。mAP是逐类计算再平均的某类验证集里恰好都是清晰典型的病灶AP就会虚高。验证集和训练集来自同一个压缩包、同一个标注人员、同样的设备条件模型学到的很可能包含背景和设备的特定模式。解决如果数据集里还有未参与训练的原图全部拿出来组成一个额外的测试集不要动训练和验证的划分。如果没得选直接从训练集里每类抽10张作为“伪测试集”虽然泄漏严重但至少能评估模型在未见过的具体图片上的稳定性。真正的解法是去公开的CXR数据集如NIH ChestX-ray14里挑一部分图人工标注几十张来测这一步是纯收益别省。5.4 长尾分布导致的mAP虚高与PR曲线崩坏现象mAP0.5有0.85但看PR曲线时置信度高于0.5区间的精度很高置信度0.2到0.4区间出现了大段凹陷也就是“要么很确信要么乱猜”。原因小数据集训练出的模型置信度校准很差。它见过的高质量特征少对模糊病灶的响应不是缓慢下降而是直接跌到低置信度区域可实际部署时你会设一个置信度阈值比如0.25阈值以下全丢弃于是真正困难的病灶无法被检测到。解决不要只看mAP训练完跑一遍val.py保存预测结果导出每张验证图的PR曲线找到曲线拐点对应置信度作为实际部署阈值。例如PR曲线显示阈值0.45处精度和召回率达到均衡那推理时就该设0.45而不是YOLOv5默认的0.25。5.5 大尺寸病灶与小目标共存的检测困境现象正常肺的整叶实变框可能占图片面积的30%而结核的微小空洞框可能只有30x30像素YOLOv5对这两类目标同时检测时大目标主导梯度小目标AP惨不忍睹。原因YOLOv5的损失对anchor的尺寸天然敏感小目标数量少且贡献的损失占比低在800张的小数据集里更明显。解决方案一是把训练输入尺寸从640提高到960或1280让微小病灶在feature map上占据更多像素显存不够就减少batch到8或4方案二是在YAML里增加anchors配置让初始anchor覆盖更小的目标尺寸。YOLOv5会在训练时自动学习anchor但默认9组anchor偏向COCO的中大目标建议显存允许时优先提分辨率效果立竿见影。6. 验证与落地的后手混淆矩阵、热力图和导出部署的一个技巧训练结束不代表模型能用第一步先看混淆矩阵。运行val.py时加上--save-conf --save-txt程序会在runs/val/exp/下生成confusion_matrix.png。重点看新冠和病毒性肺炎这两列如果它们互相串得厉害说明特征在模型眼里确实分不开这时候加数据或调阈值都没用需要用注意力热力图看模型到底盯着哪个区域。可以用YOLOv5社区常见的GradCAM插件生成热力图叠加到原图上如果热力区域大量落在心脏、膈肌或文字水印上而不是肺野说明模型学到的是环境特征而非病理特征这个模型不能直接用于任何真实性场景。导出部署是另一个常见诉求。训练好的best.pt要落地到推理服务常见做法是转成ONNX再用TensorRT或ONNX Runtime加速python export.py \ --weights runs/train/lung_xray_unfreeze/weights/best.pt \ --img 640 \ --include onnx \ --simplify转完的best.onnx在CPU上可以跑到单张50毫秒左右部署时有一个后手要记住ONNX导出的模型严格要求输入尺寸为训练时的640x640但X光片长宽比通常接近11方形胸片或45直接resize会轻微变形。建议在推理代码里使用letterbox缩放保持原图比例剩余区域填充灰色推理完再把框坐标映射回原图。映射公式是原图坐标等于框的坐标减去填充偏移量再除以缩放比例别搞反否则框会整体偏移。我自己训完这类小模型还有个习惯是打印每类的检测数量分布如果某类别的检测数比训练时少一个量级先别怀疑模型回头检查数据划分是否有类别被漏掉。医学影像小数据集的模型调试一半时间在和数据较劲另一半才在调模型。希望帮到你。本文还有配套的精品资源点击获取
返回列表