
简介提供一套含800张X光胸片原图的肺病检测数据集已使用YOLOv5完成标注覆盖细菌性肺炎、新冠病毒、正常肺、结核与病毒性肺炎五类。数据集共1601个文件包括800张jpg胸片、800个对应的txt标签文件及1个yaml配置文件压缩包约25.51MB。txt文件采用YOLOv5标准格式记录每个目标实例的类别与边界框坐标yaml文件定义了五种类别名称、路径与训练验证划分方式可直接用于模型训练、验证与推理。对于医学影像分析入门者或目标检测开发者省去了人工标注、格式转换与目录整理等繁琐步骤可快速开展肺炎筛查、新冠识别等实验也可用于复现论文结果或对比不同模型的检测效果。目前已有410人学习下载适合计算机视觉课程设计、毕业设计、竞赛训练或相关课题研究参考。1. 用800张X光片跑YOLOv5肺病检测这份标记数据集能不能直接开工拿到一份X光片肺病数据集第一反应不是打开图片看肺而是先想清楚一个问题这800张原始图片带着YOLOv5标记能不能直接丢进train.py开跑答案是不一定。文件名里带着Roboflow导出的痕迹.rf.后缀类别覆盖coronavirus、normal等常见肺病场景摘要里还提到细菌性肺炎、结核、病毒性肺炎但原始图片的标签格式、类别分布、train/val划分是否干净都要先做一轮标记体检。这篇笔记记录我拆这份数据集的完整过程从标签格式校验、目录整理、YOLOv5训练到避坑排查适合手里有类似医疗影像数据、想快速验证检测效果的从业者。2. 拆解数据集结构从文件名识别导出痕迹用脚本做标签体检2.1 文件名里的信息量rf后缀、类目前缀与Roboflow导出特征这份数据集的图片文件名很有规律比如1_coronavirus-420-_jpg.rf.9a9483366b10e7935eac8e4bd2b7a0ab.jpg。拆开看coronavirus是原图里带的类目前缀_jpg表示原始文件格式是 jpg后面的.rf.加一串哈希是 Roboflow 导出时给每张图重新生成的唯一ID目的是避免重名也便于关联同名的标签文件。jpeg结尾的图同理前缀可能是normal或coronavirus说明这份数据的源头是把不同来源的X光片统一收口到一套标注体系里。这个细节决定了后续操作方式图片和对应的YOLO标签txt文件一定是同名的除扩展名外比如1_coronavirus-420-_jpg.rf.9a948336….jpg对应labels/1_coronavirus-420-_jpg.rf.9a948336….txt。如果你在整理目录时把图片重命名了标签就全断了。我一般会先把原始文件完整拷一份留底再动目录结构这样后面训练翻车时还有后悔药。需要提醒的是从文件名只能看到coronavirus和normal两类前缀但摘要明确说这套数据集能识别细菌性肺炎、新冠病毒、正常肺、结核、病毒性肺炎五类。实际下载解压后要先用脚本把真实存在的类别和标注框数量清点一遍别靠文件名猜。Roboflow导出的数据通常自带data.yaml会写明nc和names字段先看这个文件最省事。2.2 用Python统计类别分布清点标签文件是否完整我拿到任何目标检测数据集第一步永远是统计类别分布和标签完整性而不是急着训练。YOLOv5对标签格式非常敏感一张图缺标签文件训练时会被当成无目标样本参与loss计算直接拉低召回类别分布严重倾斜模型会倾向把所有框都预测成多数类。下面是基础体检脚本。import os from collections import Counter label_dir labels cls_counter Counter() empty_files [] for f in os.listdir(label_dir): if not f.endswith(.txt): continue with open(os.path.join(label_dir, f), r) as fp: lines fp.read().strip().splitlines() if len(lines) 0: empty_files.append(f) continue for line in lines: cls_id line.split()[0] cls_counter[cls_id] 1 print(类别ID分布:, dict(cls_counter)) print(空标签文件数:, len(empty_files)) print(empty_files[:10])这段脚本的逻辑是遍历labels目录下所有txt文件逐行读取把每行第一个字段当作类别ID统计频次同时挑出没有任何标注内容的空文件。空文件通常有两种来源Roboflow导出时把没有目标的图也生成了空txt或者标注时漏了框。训练前把这些空标签对应的图片移出数据集否则YOLOv5会把它们当作纯背景样本影响负样本比例。分布结果出来后重点看两个指标一是类别ID是否只有0到4对应五类肺病如果出现类别ID为5或更大的数字说明标签里有脏数据二是最少类和最多类的数量差距比如normal正常肺如果是300张、tuberculosis结核只有50张那就要做第6章的类别均衡处理。这里不要只关心总数800要关心的是有效标注框总数和每类框数。2.3 标签格式校验归一化坐标、类别ID与图像尺寸对不上会怎样统计完分布还得逐条校验标签坐标。YOLOv5要求的格式是class_id x_center y_center width height四个坐标值全部除以图片宽高做归一化范围在0到1之间。常见翻车点是有人把VOC格式的绝对坐标或COCO格式的像素坐标直接塞进YOLO训练loss变成NaN模型根本学不出来。import os image_dir images label_dir labels bad_list [] for img in os.listdir(image_dir): if not img.endswith((.jpg, .jpeg, .png)): continue stem os.path.splitext(img)[0] txt os.path.join(label_dir, stem .txt) if not os.path.exists(txt): bad_list.append((img, 标签文件缺失)) continue with open(txt, r) as fp: for line in fp: parts line.strip().split() if len(parts) ! 5: bad_list.append((img, 字段数不为5)) continue try: cls_id, cx, cy, w, h map(float, parts) except ValueError: bad_list.append((img, 存在非数字字段)) continue if cx 0 or cy 0 or w 0 or h 0 or cx 1 or cy 1: bad_list.append((img, 归一化坐标越界)) # 检查w和h是否大于1意味着没归一化 if w 1 or h 1: bad_list.append((img, w/h疑似绝对像素值)) print(异常样本数:, len(bad_list)) for item in bad_list[:20]: print(item)这段脚本对每张图片找同名txt再逐行解析五个字段看字段个数、坐标范围和归一化情况。我特别解释下这里两个判断条件cx 1说明中心点坐标超出了归一化范围很可能是没有除以图片宽高w 1说明框宽是绝对像素值同样没归一化。这两种情况在训练时轻则梯度震荡重则直接NaN。类目ID映射也要在这个阶段核对清楚。这份数据集摘要提到五类常见映射如下类别ID名称对应中文0bacterial_pneumonia细菌性肺炎1coronavirus新冠病毒2normal正常肺3tuberculosis结核4viral_pneumonia病毒性肺炎这个ID顺序必须和后面章节里lung.yaml的names列表保持一字不差。顺序错了模型训练完推理出来的是张冠李戴临床上是没法用的。3. 训练YOLOv5肺病检测模型数据拆分、数据集配置与超参数实录3.1 目录怎么摆train/val/test拆分与固定随机种子标签体检通过后先按YOLOv5的约定目录结构重新组织数据。项目根目录下建datasets/lung/里面分成images和labels两大块每块下面再拆分train、val、test三个子目录。Roboflow导出通常是images和labels平级、且不带拆分需要自己做划分。拆分的比例我习惯用8:1:1800张图的话就是640训练、80验证、80测试验证集数量在目标检测里80张偏少但数据总量就这么多只能接受。mkdir -p datasets/lung/images/{train,val,test} mkdir -p datasets/lung/labels/{train,val,test}目录建好后写个拆分脚本一次跑完避免手工拖文件出错。import os import random import shutil random.seed(42) # 固定随机种子保证每次拆分结果一致 image_dir images_all # 全体原图 label_dir labels_all # 全体标签 imgs [f for f in os.listdir(image_dir) if f.endswith((.jpg, .jpeg, .png))] random.shuffle(imgs) n len(imgs) for i, img in enumerate(imgs): if i int(n * 0.8): part train elif i int(n * 0.9): part val else: part test stem os.path.splitext(img)[0] # 图片拷入对应目录 shutil.copy(os.path.join(image_dir, img), fdatasets/lung/images/{part}/{img}) # 同名标签同步过去如果标签缺失则跳过该图 txt os.path.join(label_dir, stem .txt) if os.path.exists(txt): shutil.copy(txt, fdatasets/lung/labels/{part}/{stem}.txt) print(拆分完成:, n)这段脚本先固定random.seed(42)确保重复执行时划分结果完全一样方便后面对比实验。然后按比例把图片和同名txt一起拷进对应子目录。如果某张图没有标签txt这里直接跳过图片本身避免训练时报错。这里有个我踩过的坑按文件随机拆分不等于按患者拆分。X光片经常一个患者有多张片子同一个患者可能既进了train又进了val模型在val上的指标会虚高等到上线时换一批真实患者表现大跌。看这份数据集的文件名1_coronavirus-…、Normal_val-…、Normal_test-…这类前缀暗示原始数据可能已经按用途分过但也不排除同一个病号多张图。拆完数据后我建议检查一遍重名患者前缀是否存在跨目录现象有的话要么手动调要么认了这个风险在第5章还会展开讲。3.2 写lung.yaml类别ID映射与路径陷阱YOLOv5训练前需要一份数据集配置文件告诉框架图片路径和类别信息。创建datasets/lung/lung.yaml内容如下。train: /absolute/path/to/datasets/lung/images/train val: /absolute/path/to/datasets/lung/images/val test: /absolute/path/to/datasets/lung/images/test nc: 5 names: 0: bacterial_pneumonia 1: coronavirus 2: normal 3: tuberculosis 4: viral_pneumonianc必须是5对应五类肺病names的索引顺序要和标签txt里的class_id严格一致。路径这里有个容易踩的坑YOLOv5在解析yaml时相对路径是相对于当前工作目录的不是相对于yaml文件所在位置。如果train.py在yolov5/目录下执行写相对路径datasets/lung/images/train必须保证yolov5/是你的当前目录。我一般直接写绝对路径省去来回纠结。用Roboflow导出时自带的data.yaml也要检查它的train字段可能是网络图片地址本地训练时一定要改成本地路径。3.3 训练命令与超参数从yolov5s起步遇到loss不降怎么调YOLOv5训练命令不长但参数含义值得逐条说清楚。cd yolov5 python train.py \ --data datasets/lung/lung.yaml \ --weights yolov5s.pt \ --img 640 \ --epochs 100 \ --batch 16 \ --workers 4 \ --device 0 \ --name lung_exp--weights yolov5s.pt是拿COCO预训练权重做迁移学习虽然COCO没有X光片类别但底层特征提取器已经学会通用边缘纹理比从头训练收敛快很多如果你显存紧张可以换成yolov5n.pt。--img 640是输入分辨率X光片原图通常不止640训练时会被缩放到640这个小节第5章会讲它对小病灶的影响。--batch 16在8G显存下比较稳显存不够就减到8同时把--workers 2降下来避免CPU瓶颈。训练起来后重点观察终端打印的box_loss、obj_loss、cls_loss三个值。我见过不少人一上来就盯着mAP看其实训练刚开始几十个epoch指标意义不大loss稳定下降才是模型在学的信号。如果loss波动剧烈、迟迟不降先做两件事一是把学习率调低默认lr00.01换成--hyp data/hyps/hyp.scratch-low.yaml里更小的初始学习率二是关闭数据增强里的水平翻转。X光片有个特殊性左右肺在影像上并非完全对称水平翻转这种在自然图像里常用的增强手段放到医疗影像里可能把病灶位置关系搞乱临床上左肺和右肺的解剖位置是固定的。我通常会把hyp.scratch-low.yaml里的fliplr: 0.5改成fliplr: 0.0严重类别失衡时还会把mosaic增强调低。这套基础配置跑100轮在800张图上大约需要几十分钟看GPU性能而定。4. 验证模型别只信mAP混淆矩阵、PR曲线与误检图像怎么读4.1 用val.py评估模型Precision、Recall与mAP指标怎么读训练跑完YOLOv5会在runs/train/lung_exp/下生成训练曲线、混淆矩阵和最佳权重best.pt。先用官方评估脚本在验证集上跑一遍指标。cd yolov5 python val.py \ --data datasets/lung/lung.yaml \ --weights runs/train/lung_exp/weights/best.pt \ --img 640 \ --conf-thres 0.25 \ --iou-thres 0.45输出会包含每个类别的Precision、Recall、mAP0.5和mAP0.5:0.95。我解释下这几个值的实际含义Precision是模型框出来的目标里有多少框对了Recall是所有真实目标里模型找到了多少mAP0.5是IoU阈值0.5时的平均精度mAP0.5:0.95是IoU从0.5到0.95取多个阈值的平均值后者更严格。肺病检测这个场景我更看重Recall而不是Precision。漏检一个病灶框在临床上可能直接错过一个病例代价远高于多框几个误检框。如果某个类别Recall很低比如结核类别只有0.4基本可以判断是这个类样本太少或者特征和正常肺太接近。这时候别急着调训练参数先翻验证集里这个类的预测结果看是没框出来还是框的位置不对。4.2 混淆矩阵和labels.jpg哪些类别在互相打架val.py跑完会在runs/train/lung_exp/下生成confusion_matrix.png和训练集标签可视化labels.jpg。混淆矩阵的横轴是真实类别纵轴是预测类别对角线越亮越好。我第一次跑这个数据集时发现细菌性肺炎和病毒性肺炎之间有一块不小的非对角响应后来仔细看图才明白这两种病的X光片都表现为肺部斑片状阴影形态学上本来就很接近连有经验的医生都可能误判。labels.jpg这个图很多人不看但它非常有用它把训练集的标注框按位置和尺寸分布画出来。如果看到框的中心点大量聚集在图像中心、框的尺寸集中在很小范围说明原始标注可能有系统性偏差比如只框了病灶中心没框全边缘。这个图能帮你判断标注质量而标注质量直接决定模型上限。4.3 跑一批真实推理把误检框可视化出来指标是数值但数值好看不等于实际效果能看。我会用测试集跑一轮推理把结果保存下来逐张翻。python detect.py \ --weights runs/train/lung_exp/weights/best.pt \ --source datasets/lung/images/test \ --conf-thres 0.25 \ --save-txt \ --save-conf \ --project runs/detect \ --name lung_test--conf-thres 0.25是置信度阈值低于这个值的框会被丢弃--save-txt会输出每张图的预测坐标和类别ID--save-conf会附带置信度。生成的标注图存到runs/detect/lung_test/。翻这些图时我按两类问题看一类是漏检真实病灶没有框出来几乎都是小病灶或者病灶和正常纹理太接近另一类是误检正常解剖结构比如肋骨、心脏边缘被框成病灶。后一种情况在医疗影像里尤其危险模型可能学的是纹理特征而不是病理特征需要回到数据层面想办法。5. YOLOv5肺病检测避坑指南五个最容易翻车的问题与排查方法5.1 数据泄漏同一个病号的片子被拆进train和val现象是训练时mAP很高一到真实临床场景检测效果明显缩水。原因在于拆分时只按文件名随机切分同一个病号的多张X光片同时被分到了训练集和验证集验证指标虚高。解决方式是检查文件名前缀看有没有同一前缀病号标识跨目录手动把同前缀的图归拢到同一个集合以后做这类医疗数据集第一步就要按病号分组再拆分。我习惯直接在拆分脚本里加上按前缀分组的逻辑而不是拆完再补救。5.2 标签坐标越界或者字段错误训练loss直接NaN现象是train.py启动后没跑几十个batchloss就变成nan。原因多数是标签txt里存在没归一化的绝对坐标、负数坐标或者字段数不是5。排查方法就是第2章的校验脚本跑一遍把异常文件全挑出来。解决方式是删除错误标签或修正坐标对于这份Roboflow导出的数据还要检查是否混入了未标记图片的占位txt有就一并处理。5.3 加载预训练权重报错类别数不匹配现象是用--weights yolov5s.pt启动时报权重尺寸不匹配或者某些层加载失败。原因是COCO预训练模型的输出层是80类本地数据集是5类检测头shape对不上。新版YOLOv5一般会自动跳过不匹配的层但不一定每个版本都处理得很干净。解决方式有两个要么直接删掉预训练权重里和类别数相关的层权重让这部分随机初始化要么不用预训练改成随机初始化训练。对于小样本肺病检测我推荐保留预训练的前面部分特征提取层只丢弃最后的检测头权重。5.4 mAP0.5偏高但mAP0.5:0.95偏低anchor和框回归的问题现象是验证时mAP0.5有0.9但mAP0.5:0.95只有0.4左右两个指标差距过大。原因是模型能框出目标但是框的位置、边界不够精细IoU稍微提高到0.75就匹配不上。这时候优先检查anchor是否适配当前数据集的框尺寸分布。YOLOv5训练时会默认用k-means自动重新计算anchor但如果你用了--noautoanchor或者改动过anchors配置就要手动跑一次k-means。我一般直接删掉yaml里的anchor配置让模型自动算比手工调靠谱。5.5 小病灶漏检率偏高输入分辨率与mosaic增强的选择现象是验证集上正常肺和新冠检测效果还行但结核这类小病灶阴影频繁漏检。原因一是输入分辨率640把原始X光片里的小阴影进一步缩小模型特征图里几乎找不到二是mosaic增强把四张图拼接缩放小目标更容易被挤压到难以学习。解决方式是先把输入分辨率提到960显存不足就提到768同时调小batch再把训练的最后10到20个epoch关闭mosaic让模型在接近真实分布的数据上收敛。这一点在医疗小目标场景里几乎是必做的mnist那种大目标数据集完全不会有这个困扰。6. 让模型在真实X光片上站得住类别不均衡处理、盲测与术语对齐6.1 类别不均衡的两种处理重采样少数类与损失加权这类肺病数据集的类别分布通常不会均衡normal正常肺的样本量往往远高于结核或病毒性肺炎。YOLOv5本身没有直接的类别加权接口常见做法是重采样把少数类样本复制补充到接近多数类水平。注意是复制图片和标签不是同一张图硬塞进多个epoch而是每次epoch采样时让少数类有更高的出现概率。另一个思路是在loss层面给少数类更大的惩罚权重这个需要改YOLOv5的损失函数代码新手不熟悉的话先从重采样开始。6.2 用外部CXR数据做盲测验证泛化而不是自嗨测试集指标再漂亮也只能说明模型在你这份数据分布内有效。医疗影像场景模型可能在公开数据集上表现不错换一台X光机的成像风格就崩。我做完训练后总会额外找一批来源不同的CXR数据做盲测跑一遍推理统计误检率和漏检率再决定要不要上线。没有外部数据时把测试集和验证集的成像条件差异找出来分析也有帮助。从那以后我每次拿到带标注的医疗影像数据集第一件事不再是急着训练而是先跑标签体检、按病号拆分、检查类别分布这三个步骤已经变成了标准流程。这套习惯帮我少走了很多弯路希望帮到你。本文还有配套的精品资源点击获取