
简介数据集包含800张原始胸部X光片及按YOLOv5规范生成的标注文件面向深度学习初学者、医学影像分析人员与目标检测算法开发者可用于训练肺炎、结核、新冠肺炎等常见肺部疾病的检测模型也可用于迁移学习、模型微调与算法效果对比。包内共1601个文件包括800张jpg原始影像、800个txt标签文件及1个yaml类别配置文件整体压缩包仅25.51MB便于快速下载与本地实验。每个txt文件对应一张图片记录目标框的类别编号与归一化坐标yaml文件定义了细菌性肺炎、新冠病毒、正常肺、结核、病毒性肺炎五类名称可直接接入YOLOv5数据配置。图片文件名保留了数据来源与训练/验证/测试划分信息方便用户按目录组织实验减少前期数据整理时间。当前已有410人学习/下载特别适合需要带标注医学影像数据来训练YOLO系列模型、评估不同肺部疾病识别性能的研究者与学生。1. 800张X光片跑通yolov5五类肺病检测先别急着怀疑数据量第一次拿到这个X光片肺病数据集时我的第一反应是800张原始图片要喂给yolov5还要识别细菌性肺炎、新冠病毒、正常肺、结核、病毒性肺炎五个类别平均每类只有一百来张这在目标检测里算不上充裕。真跑起来才发现这个规模反而适合验证“小数据预训练权重”的落地路径医学影像背景相对单一五类病变位置集中yolov5s在迁移学习下能在两三个小时内给出一版可用模型。这篇笔记把拆标签、配环境、训练调参和踩坑的完整过程写出来适合正在做医疗影像毕设、算法预研或只想快速验证目标检测流程的工程师照着重现。2. 拆解yolov5标记的X光片数据标签格式、类别索引与三个校验步骤拿到任何标注好的目标检测数据集第一步不是急着配环境而是把标注格式确认清楚。以这类X光片肺病数据集为例最典型的结构是images目录放原始片子labels目录放同名txt标注文件。YOLOv5要求txt文件名和图片名完全一致只是后缀不同里面每一行对应一个目标框格式固定为“class x_center y_center width height”。这五个字段的含义要记牢class是类别索引从0开始后四个是归一化后的框中心坐标和宽高取值在0到1之间。标题里按“细菌性肺炎、新冠病毒、正常肺、结核、病毒性肺炎”的顺序列出五类常见做法就是按这个顺序绑定索引0到4。但数据集作者换顺序也很常见所以解压后第一件事是用classes.txt或dataset.yaml里的names核对不要用眼睛猜。类别索引一旦错位训练出来的模型会把所有框识别成错误类别而且loss还正常下降这是最容易翻车的黑匣子。2.1 校验脚本逐行解析标签txt把越界坐标和错类别一次扫出下面这段脚本建议在任何数据集上先跑一遍。它不做训练只回答三个问题标签行数是否正常、类别索引是否越界、归一化坐标是否在合法范围。from pathlib import Path data_dir Path(chest_xray_yolo) label_dir data_dir / labels cls_names [bacterial_pneumonia, covid, normal, tuberculosis, viral_pneumonia] cls_count {name: 0 for name in cls_names} box_count 0 invalid_lines [] for label_file in sorted(label_dir.glob(*.txt)): with open(label_file, r) as f: for line_no, line in enumerate(f, 1): parts line.strip().split() if len(parts) ! 5: invalid_lines.append((label_file.name, line_no, 字段数不为5, line)) continue cls_idx int(parts[0]) if cls_idx 0 or cls_idx len(cls_names): invalid_lines.append((label_file.name, line_no, 类别索引越界, line)) continue x_c, y_c, w, h map(float, parts[1:]) if not (0 x_c 1 and 0 y_c 1 and w 0 and h 0): invalid_lines.append((label_file.name, line_no, 坐标异常, line)) continue cls_count[cls_names[cls_idx]] 1 box_count 1 print(有效框总数:, box_count) for name in cls_names: print(f{name}: {cls_count[name]}) print(异常行数:, len(invalid_lines)) for item in invalid_lines[:10]: print(item)这段脚本的逻辑很直白按行读取txtsplit之后必须得到5个字段类别索引必须在0到4之间中心坐标与宽高必须落在合法区间。跑完以后如果异常行数不为0先把数据修好再进训练流程而不是指望训练器忽略坏行。YOLOv5对越界坐标的容错方式在不同版本里不一样有的会裁剪有的会直接报cuda错误与其赌版本行为不如在数据入口把所有非法行清掉。参数说明只有一处需要留意cls_names列表的顺序必须和你最终写的dataset.yaml保持一致。这里按标题顺序写如果数据集根目录另有classes.txt就优先以那个文件为准再回头改这个列表。2.2 图片校验PIL打开一遍X光片转存过程最容易出坏文件标注txt再规范图片本身损坏也会让训练中途崩掉。尤其医学影像数据集经常来自医院导出或扫码转存JPG在压缩、改名、网络传输过程中出现截断文件并不少见。建议在建索引前用PIL把每张图过一遍既检查文件头也顺手确认通道数和尺寸。from PIL import Image from pathlib import Path img_dir Path(chest_xray_yolo/images) bad_images [] for img_file in sorted(img_dir.glob(*.jpg)): try: with Image.open(img_file) as im: im.verify() except Exception as e: bad_images.append((img_file.name, str(e))) print(图片总数:, len(list(img_dir.glob(*.jpg)))) print(损坏图片数:, len(bad_images)) for name, err in bad_images[:10]: print(name, err)逻辑说明im.verify()只校验文件完整性和图像头部不解码全部像素速度很快。它不能发现所有花屏问题但足以把解压或传输导致的截断文件抓出来。如果发现坏图直接删除对应图片和同名txt不要保留空列表让训练时找不到图。这里glob写的是*.jpg实际数据集里可能是png或jpeg按解压后的扩展名调整。2.3 先看框数再看张数类别不均衡在X光片里比想象中严重脚本输出的cls_count经常会呈现一个规律细菌性肺炎和结核的框数明显多于正常肺因为正常肺没有病灶标注者通常不画框或只给肺野大框病毒性肺炎与新冠病毒在影像学上又高度相似框数量接近但位置分布完全不同。这提醒我们800张图平均到每类一百多张只是纸面数字真正有效正样本可能只有几十张训练时要用类别权重或调整置信度阈值来补偿。建议顺手统计一下每个框的面积占整张图的比例。X光片里的实变影、结节、磨玻璃影通常只占图像很小一部分如果某个类别出现过宽高接近0.5的框多半是标注把整片肺野框了进去。这类框不一定要删但会让模型更偏向学“整肺纹理”而不是“病灶位置”后面训练时如果发现PR曲线异常可以回头查这一步。import numpy as np from pathlib import Path label_dir Path(chest_xray_yolo/labels) areas [] for label_file in label_dir.glob(*.txt): with open(label_file, r) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue w float(parts[3]) h float(parts[4]) areas.append(w * h) areas np.array(areas) print(框面积占比分位数:, np.percentile(areas, [50, 90, 99]))逻辑说明把每个txt里所有框的面积占比收集起来看中位数和尾部。中位数在0.01左右说明框都小99分位超过0.3说明存在整肺大框需要回去人工抽查。参数说明percentile里的[50, 90, 99]是分位点可以按自己的容忍度改。跑脚本时还会看到另一种情况有些图片的txt文件不存在。YOLOv5允许一张图没有标注文件这表示图中没有目标对训练来说就是负样本。正常肺这类图片如果留空txt模型会把它当纯背景处理如果将它标成normal类别并给一个整肺框则相当于多了一类正样本。这两种做法都有人用关键是要在dataset.yaml里说清楚别混着来。我更倾向于给正常肺保留框这样训练时能约束模型在正常肺区域不产生高置信度输出推理时也更容易做分类统计。3. 跑通yolov5训练闭环环境配置、数据划分与dataset.yaml编写训练自己的数据集最稳的路线是拿YOLOv5官方仓库的源码直接跑。不要自己写训练器也不要用第三方魔改版后者在医疗影像这类小众数据上出了问题很难搜到答案。创建环境时Python版本建议3.9torch版本按官方requirements安装即可GPU机器务必先装好与驱动匹配的CUDACPU机器也能训练800张X光片只是时间会长三到五倍。3.1 先把yolov5环境配置好Python版本与依赖的坑conda create -n yolov5 python3.9 -y conda activate yolov5 git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt逻辑说明conda隔离环境的理由是避免和本机其他框架冲突requirements里已经锁定了torch等核心依赖版本不要手动升级。参数说明requirements.txt里部分组件体积较大国内网络环境下建议加镜像源参数指定pip下载源否则下载时间会非常折磨人。yolov5环境配置的常见报错集中在torch版本与CUDA不匹配装完先跑python -c import torch; print(torch.cuda.is_available())确认GPU可用。首次运行训练命令时代码会自动下载yolov5s.pt预训练权重。如果网络受限提前把权重文件放到yolov5根目录即可训练时--weights参数会优先读本地文件。3.2 划分train/val/test按患者划分优于随机划分在目标检测常规任务里随机划分够了。但医学影像数据集经常出现同一个患者多张片子文件名带患者编号或检查号。如果随机划分同一患者的片子可能同时出现在训练集和验证集模型记住的是患者个体特征验证指标虚高。拿到手先看文件名能提取患者ID就按ID分组否则随机划分时也要在报告里注明风险。import random import shutil from pathlib import Path random.seed(42) img_dir Path(chest_xray_yolo/images) label_dir Path(chest_xray_yolo/labels) output_root Path(chest_xray_split) img_files sorted(img_dir.glob(*.jpg)) random.shuffle(img_files) n len(img_files) n_train int(n * 0.8) n_val int(n * 0.1) for split_name, split_files in [ (train, img_files[:n_train]), (val, img_files[n_train:n_train n_val]), (test, img_files[n_train n_val:]) ]: for img_path in split_files: img_out output_root / split_name / images / img_path.name label_out output_root / split_name / labels / (img_path.stem .txt) img_out.parent.mkdir(parentsTrue, exist_okTrue) label_out.parent.mkdir(parentsTrue, exist_okTrue) shutil.copy(img_path, img_out) label_path label_dir / (img_path.stem .txt) if label_path.exists(): shutil.copy(label_path, label_out) print(split_name, len(split_files))逻辑说明脚本把所有图随机打乱固定随机种子保证每次执行结果一致方便复现训练。80%训练、10%验证、10%测试是对小数据集最常用的切法测试集只在最后评估时用训练和调参阶段不要碰它。YOLOv5训练只用train和val两段test不是必需但医疗场景建议单独留出来做最终盲测。参数说明random.seed(42)改成别的数可以得到另一组划分如果文件名里疑似带患者ID先对文件名去重看看重复前缀否则随机划分造成的患者重叠会让最后测试表现严重失真。这一步做不好后面所有指标都是陷阱。3.3 dataset.yaml类别数、names顺序与路径是训练的唯一契约YOLOv5不读zip里的任何配置它只认一个yaml文件。把上一步生成的目录路径写进去训练脚本才知道去哪里找图和标签。train: /home/user/chest_xray_split/train/images val: /home/user/chest_xray_split/val/images test: /home/user/chest_xray_split/test/images nc: 5 names: 0: bacterial_pneumonia 1: covid 2: normal 3: tuberculosis 4: viral_pneumonia逻辑说明train和val必须指向图片目录不用写labels路径YOLOv5会按同名txt规则自动在相邻的labels目录里找标签。nc是类别数5类就写5。names的索引顺序必须和标签txt里的数字一致如果数据集的classes.txt把tuberculosis排在第0位这里就不能按标题顺序写。这个文件是所有训练命令的入口命名随意但字段不能错检查过的yaml建议复制一份备份防止调参时改坏。注意yaml里的路径建议写绝对路径。YOLOv5会把相对路径拼到当前工作目录下从其他目录启动训练时容易找不到数据这是新手最常碰到的路径问题。3.4 数据集结构自检训练前再花一分钟确认find chest_xray_split -maxdepth 3 -type f | sed s/.*\.// | sort | uniq -c逻辑说明查看生成目录下各扩展名的文件数量确认jpg与txt数量基本一致、train/val/test下都有图有标签。如果txt比jpg少说明有空标签图片这会作为负样本参与训练不算错误如果少得离谱回到3.2检查复制逻辑。参数说明maxdepth 3是因为目录层级是root/split_name/images或labels两层再加文件本身一层目录结构变了就调整这个数字。X光片的原始尺寸可能差异较大有的5000×4000有的1024×1024。YOLOv5在训练时会自动做letterbox缩放理论上不必手动resize但强烈建议先统计所有图片尺寸把过大的图统一压到2048以内再进训练否则PIL解码和缓存会吃掉大量内存800张图也能把16G内存占满。4. 启动yolov5训练预训练权重、超参数与日志解读800张X光片从零训练一个检测器基本不可能收敛更不要提五分类。选择从COCO预训练的yolov5s.pt开始是这类小数据集任务最稳的做法。s是参数量与速度的平衡点医学影像对实时性要求不高可以换yolov5m但对800张数据来说s已经足够m反而更容易过拟合。4.1 预训练权重怎么选yolov5s是性价比最高的起点yolov5s.pt在COCO上见过大量物体、纹理和局部结构迁移到X光片上不需要重新学边缘、对比度这些底层特征只需要在顶层把病灶特征接上。这也是为什么小数据量下一定不要关掉预训练权重从零开始那等于把所有底层特征全部推翻。首次训练会自动下载权重文件或手动放到yolov5根目录路径里不要出现中文和空格否则torch加载权重时容易报文件找不到。4.2 四个必调超参数img、batch、epochs、hyp所谓yolov5超参数不等于只在hyp配置文件里调那些学习率项。训练命令里这四项同样属于必调参数img输入图片尺寸。X光片原始分辨率经常在2000像素以上直接输入太大显存受不了直接用640对病灶检测足够。想保留更多细节可试1280但batch必须降下来。batch显存占用和梯度稳定性的折中。16在常见24G显卡上配640是安全的8G显卡就把batch降到8或4不要硬撑。epochs小数据集建议100起步。跑到50轮时如果val指标还在缓步上升就继续加如果早就震荡不动再改其他参数。hyp使用默认hyp.scratch-low.yaml即可。X光片不是自动驾驶场景大量增强反而把肺纹理搞乱建议只调一个mosaic0.0因为单张X光片被切碎后会生成大量不存在的病灶边界对医学任务弊大于利。python train.py \ --data chest_xray_dataset.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --cache ram \ --project runs/chest_xray \ --name exp1 \ --hyp hyp.scratch-low.yaml逻辑说明--data指向上一章写好的yaml--weights使用官方预训练权重--cache ram把所有图片缓存进内存800张JPG总量不大能明显减少训练时的磁盘IO但如果内存不足就去掉这个参数改为--cache disk或不用缓存。--project和--name决定训练日志输出到runs/chest_xray/exp1目录方便多组实验对比。参数说明hyp.scratch-low.yaml是YOLOv5自带的数据增强配置比hyp.scratch.yaml更轻。第一轮训练先用默认值不要一上来就改学习率学习率动量这些参数对800张数据的影响远没有batch size和输入尺寸大。4.3 训练日志怎么看P/R/mAP才是决策依据训练时终端会滚动显示每一轮的loss、精度、召回、mAP等指标。很多人只看loss下降就以为模型在变好这是亏。目标检测里loss低只说明预测框收敛类别是否对、框是否准要看后面几列Pprecision预测出的框里真正命中的比例。Rrecall实际病灶被找出来的比例。mAP0.5IoU阈值0.5下的平均精度小数据集的黄金指标。mAP0.5:0.95更严格800张图数据上数值普遍偏低不必强求。如果训练中段R明显高于P说明模型倾向于多画框典型的小数据过检反过来P高R低说明漏检多。根据场景决定取舍医疗辅助宁可多框让医生过滤也别漏掉病灶。这时可在val.py或detect.py里把置信度阈值调低到0.1~0.15。训练结束后验证python val.py \ --data chest_xray_dataset.yaml \ --weights runs/chest_xray/exp1/weights/best.pt \ --conf 0.25 \ --iou 0.5逻辑说明val.py输出模型在验证集上的P/R/mAP并生成混淆矩阵和PR曲线图。best.pt是训练过程中验证指标最优的权重优先用这个不要用last.pt。参数说明--conf是置信度阈值低于该值的框被丢弃--iou是NMS的IoU阈值默认0.45验证时用0.5更贴近实际评测口径。4.4 跑一次detect推理确认框落在肺野而不是整图乱飘训练完成后用自己的图片跑一遍推理python detect.py \ --weights runs/chest_xray/exp1/weights/best.pt \ --source ./test_images \ --conf 0.25 \ --save-txt逻辑说明--source可以指向单张图、目录或视频。--save-txt会同时输出YOLO格式的txt结果便于写脚本统计类别如果只要看图去掉这一项即可。推理后打开标注框检查若框大都落在肺野外或框住整张片子问题多半出在整肺框标注或者mosaic增强没有关掉。5. 常见问题与避坑小数据量医学影像训练yolov5的五个翻车点5.1 验证集mAP很高测试集却一塌糊涂患者泄漏现象训练日志里mAP0.5一路涨到0.85自己留出的test图片也全是高置信度命中拿到医院新导出的几张X光片立刻漏检。原因3.2里随机划分时同一患者的多张片子被同时分进train和val模型记住了患者ID对应的纹理特征而不是病灶本身。解决检查文件名里的患者标识按患者分组后再划分没有标识就在报告里注明风险。这一步在小数据集里比任何调参都重要。5.2 loss降了P和R却来回跳过拟合来得比想象中早现象训练到第40轮cls_loss还在缓慢下降val的mAP却不再上升甚至掉头向下。原因800张图片相对五类目标而言太少模型开始背诵训练集里的噪声纹理。解决epochs放到100后盯住val指标而不是train lossbest.pt自带早停效果训练完直接用它。如果50轮就出现过拟合把img从640降到512去掉mosaic增强再不行就换yolov5n更小的模型。5.3 正常肺类别总是被误报成细菌性肺炎负样本太少现象测试集里的正常肺X光片几乎每张都被画出几个框框内类别多为bacterial_pneumonia。原因正常肺没有病灶标注框数量天然少于其他类别模型也没见过足够正常肺的负样本。解决遍历labels目录统计normal对应txt里框数是否严重偏少把无框图片保留为负样本并保证验证和测试集里都有足够正常肺图片。另一招是训练时数据增强里的flipud不要开X光片上下翻转会让肺尖和膈肌位置颠倒加重误检。5.4 txt里类别写成5训练报IndexError现象训练开始几秒直接报index out of range in self或者cuda assert错误。原因标注工具导出时类别编号从1开始写成了1,2,3,4,5而YOLOv5要求从0开始。解决在2.1的校验脚本里把cls_idx 0 or cls_idx 5作为硬条件发现错误后统一减一不要手工改文件写个循环批量处理。5.5 显存够反而训练慢且花屏cache与成像格式问题现象开了--cache ram后每隔一段时间训练卡顿loss正常但visualize出的训练图出现黑白棋盘格。原因内存不足触发swap或者X光片原始格式是16bit DICOM转换来的PNG普通JPG解码路径处理异常。解决--cache ram换成--cache disk或者去掉cache图片统一转成8bit JPG压缩质量90以上不要保留透明通道。这类数据入口问题往往比调超参数更见效。6. 进阶验证五折交叉验证和混淆矩阵判断模型是不是真的可用一个隐藏的验证技巧800张图规模下单次划分的mAP受随机性影响很大按不同随机种子训练五次取平均比一次训练结果可信得多。更实用的是五折交叉验证——每一折用640张训练、160张验证轮流五次最后把五个验证子集的预测结果汇总成一张混淆矩阵。for fold in 0 1 2 3 4; do python train.py \ --data chest_xray_fold$fold.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 60 \ --project runs/chest_xray_cv \ --name fold$fold done逻辑说明这个循环不是严格意义的k折但对小数据集完全够用。每一折单独生成dataset.yaml把对应折的val指向验证图片目录训练60轮后记录mAP。五个mAP的平均值和中位数比任何单独一次的日志都有说服力。参数说明fold数不要太多800张图五折已经让训练集只有640张再增加折数会导致每折训练样本太少。混淆矩阵的读取也有技巧。val.py运行后在runs/chest_xray_cv/fold*/目录下会生成confusion_matrix.png。对角线越亮越好但更该看两个讨厌的格子covid被识别成viral_pneumonia的比率以及normal被识别成任何肺炎的比率。这两个错在临床上最难接受也是这个数据集的天然难点。最后一步是把检测输出降级成图片级分类来算准确率。医学场景里医生关心的往往是“这张片子里有没有病灶、是哪类”而非精确的框。写一个简单脚本一张图只要出现某类置信度超过0.25的框就认为这张图属于该类。用这个逻辑在test集上统计每个类别的真实准确率和召回率如果normal类误报率超过10%直接把detect阶段的置信度阈值上调到0.4通常能把误报压下来。我自己的习惯是小数据集上永远把“验证集构成”和“误报类型”列在实验记录最前面指标只排第二。这五类肺病中covid和viral_pneumonia的影像相似度极高模型给的框往往落在同一片磨玻璃影上如果只盯mAP会忽视这种系统性错分而它在临床上是致命的。先把混淆矩阵做成项目验收的第一张图再谈阈值和部署。这个数据集的真正价值不在于800张图能训练出多强的模型而在于让整个yolov5训练与验证流程在一个干净的医学任务上完整跑通希望帮到你。本文还有配套的精品资源点击获取