简介:这是一份面向医学影像分类与目标检测任务的数据集,包含800张X光片原始图像,已使用YOLOv8格式完成标注,覆盖细菌性肺炎、新冠病毒、正常肺、结核和病毒性肺炎五种常见类型,适合用于肺炎/新冠筛查模型的训练与验证。压缩包共1601个文件,除800张jpg原图与800个配套txt标注文件外,还附有1个yaml配置文件;其中jpg为X光片原始图,txt记录每个目标的类别与框坐标,可直接衔接YOLOv8训练流程。压缩包整体约25.51MB,体量轻巧,适合初学者快速上手迁移学习,也便于研究者在本地环境做预实验,目前已有417人学习下载。标注坐标已按YOLO格式归一化,无需二次转换,可直接用于模型训练;yaml文件统一了类别名称与数据路径,降低上手门槛。对正在积累肺病X光片数据集或调试检测管线的开发者来说,是一份省时省力的基础资源。
1. 800张X光片配YOLOv8标记:小样本医学目标检测的真实起点
拿到一个“X光片肺病数据集,800张原始图片使用yolov8标记”的压缩包,第一反应多半是解压后直接跑训练。但做过医疗影像项目的人都知道,真正的麻烦不在训练本身,而在数据集的“干净程度”和标签语义上。这个标题里最关键的词不是“数据集”,也不是“肺病”,而是“标记”——它决定了你能否把YOLOv8跑出一个可信的结果。800张原始图片分五类:细菌性肺炎、新冠病毒、正常肺、结核、病毒性肺炎。听起来任务很直接,实际上类别数量分配不可能均匀,X光片的成像条件、病灶区域大小、标签边框是否贴合病灶,都会直接影响mAP。这篇文章就顺着“拿到标记好的数据集之后该做什么”这条线,说清楚YOLOv8标记数据的结构校验、训练参数调整、验证方法和小样本数据集的落地技巧。适合正在用YOLOv8训练自己的数据集、尤其是医学影像或工业缺陷检测的算法工程师和研究生。
2. YOLOv8标记的X光数据集长什么样:先校验目录和txt再谈训练
2.1 压缩包内的images/ 与 labels/:来自COCO数据集的路径习惯
常见做法是把YOLOv8标记数据制成两个顶层目录,一个放原始图片,一个放标记文件。图片目录通常叫images,标记目录通常叫labels,里面的文件名与图片同名,后缀从.jpg、.png换成.txt。很多从COCO数据集结构迁移过来的工具链都会默认这个约定,ultralytics的YOLO训练接口也支持你直接指定images和labels的根路径,然后在data.yaml里声明。
解压这个“X光片肺病数据集”压缩包后,我一般会先看结构,而不是急着找训练脚本。可能的情况有两种:一种是已经拆好了train/val子目录,另一种是全部图片堆在一起,需要自己划分。无论是哪一种,都要确认图片文件与标签文件一一对应。只有一个图片目录也可以喂给YOLOv8训练,但划分验证集会麻烦一点,建议先用脚本统一成images/train、images/val、labels/train、labels/val的结构。
2.2 labels/xxx.txt 的五行数值与类别id
YOLOv8标记数据集里的每个.txt文件,与同名图片一一对应。文件里每一行代表一个目标框,固定五个值,顺序是:类别id、归一化后的中心点x坐标、中心点y坐标、归一化后的框宽度、归一化后的框高度。归一化是指除以图片原始宽高,所以x、y、w、h全部落在0到1之间。类别id从0开始,也就是说标题里提到的细菌性肺炎、新冠病毒、正常肺、结核、病毒性肺炎,会分别对应0、1、2、3、4中的某一个,具体顺序取决于标注时怎么定义。
X光片里的病灶框一般比自然图像里的目标框更不规则,有的病灶面积很小,比如早期结核的阴影可能只有几十个像素。标注者如果沿用自然图像检测的习惯,把整片肺叶都框进去,训练出来的模型边界会很模糊。
2.3 用Python校验图像标签配对与归一化坐标
拿到数据集后第一件事,我建议先跑一个校验脚本。不要直接看训练指标,先确认每个标签文件是否存在、每个框的坐标是否越界、类别id是否在合法范围内。代码可以这样写:
from pathlib import Path import cv2 root = Path('xray_dataset') img_dir = root / 'images' lbl_dir = root / 'labels' for img_path in sorted(img_dir.rglob('*.jpg')): lbl_path = lbl_dir / img_path.relative_to(img_dir).with_suffix('.txt') if not lbl_path.exists(): print('missing label:', img_path) continue with open(lbl_path) as f: lines = [ln.strip() for ln in f if ln.strip()] for ln in lines: parts = ln.split() if len(parts) != 5: print('bad line:', img_path, ln) continue c, x, y, w, h = int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) if c < 0 or c > 4: print('class id out of range:', img_path, ln) if not (0 <= x <= 1 and 0 <= y <= 1 and 0 < w <= 1 and 0 < h <= 1): print('coordinate out of range:', img_path, ln) print('scan finished')这段脚本做的事情很简单:遍历images目录下所有.jpg文件,把相对路径换成同名的.txt去labels目录里找;找不到就输出缺失文件名。然后逐行检查是否正好5个值、类别id是否在0到4之间、归一化坐标是否合法。img_path.relative_to(img_dir).with_suffix('.txt')是这里的关键操作:它把images/abc.jpg变成abc.txt,再拼接成labels/abc.txt。
跑完这个脚本之后,再统计一下每个类别的目标框数量。如果某一类只有几十个框,而其他类有几百个,后面训练时就要额外做类别不平衡处理。比如我见过一份X光肺病数据集,正常肺样本很多,结核样本很少,直接用默认参数训练,模型会倾向于把所有肺都预测成正常。
2.4 原始图片质量筛选:重复图、损坏图、灰度图
标记是好的,不代表原始图片可以直接拿去训练。医学影像数据集的另一个常见坑是重复图像和损坏图像。有的X光片会以不同文件名重复出现,有的.jpg文件头部损坏,cv2.imread返回空数组,训练时直接中断。
检查图像质量可以用一段很短的动作:
from pathlib import Path from PIL import Image for img_path in Path('images').rglob('*.jpg'): try: with Image.open(img_path) as im: im.verify() except Exception as e: print('corrupt image:', img_path, e)对于重复图,可以算每个文件的SHA-256哈希值,后面第5章会给完整代码。这一步要趁早做,因为一旦开始训练,坏图会导致DataLoader在某个epoch随机报错,而且错误信息往往不带图片路径,排查起来非常费时间。
下面这个表格总结了拿到数据集后第一步要检查的项目和常见结果:
| 检查项 | 方法 | 常见问题 |
|---|---|---|
| 图片与标签配对 | 遍历图片找同名txt | 缺少标签或标签多余 |
| 标签坐标合法性 | 检查5个值是否越界 | 坐标大于1,宽高为0 |
| 类别id范围 | 与data.yaml比对 | id超过类别总数 |
| 图片文件完整性 | PIL verify或cv2.imread | 文件损坏导致训练中断 |
| 图像重复 | 计算文件SHA-256 | 同病人X光片重复出现 |
3. 用YOLOv8训练肺病X光数据集:不能抄默认值的5个参数
3.1 建立xray.yaml时的类别顺序与标签id一一对应
YOLOv8使用data.yaml声明数据集路径和类别名称,训练时模型会严格按照names列表的顺序去解释标签文件里的类别id。这里有最容易踩的坑:压缩包里如果还附带了一个data.yaml,打开后先看names列表的顺序,逐一和标签txt里的数字去对应。很多时候标注工具导出时使用的class列表和训练时写的names顺序不一致,就会导致“细菌性肺炎被当成正常肺”这类错误。
一份典型的X光肺病数据集data.yaml长这样:
path: /home/user/xray_dataset train: images/train val: images/val names: 0: bacterial_pneumonia 1: covid 2: normal 3: tuberculosis 4: viral_pneumonia注意path字段是绝对路径或相对于当前工作目录的路径。如果你把压缩包解压后直接复制成上面的配置,但实际目录没有images/val,YOLOv8会报Dataset not found。所以运行训练之前,过一遍data.yaml里的路径,确保train和val都真实存在。
3.2 imgsz与batch:X光片大图与显存的取舍
YOLOv8默认的imgsz是640,但很多X光片原始分辨率在2000×2000以上,里面病灶直径可能只有几十像素。直接用640训练,小病灶可能直接缩没了。我的经验是先统计一下标签框的像素宽度分布,再看原始图分辨率,imgsz设为1200或以上会明显提升小目标召回率。
代价是显存。800张图片不算多,如果你用的是8GB显存的GTX 1660 Ti,imgsz=1280时batch只能开到4到8。batch太小会导致BN层统计不稳定,但YOLOv8在训练时会自适应调整,所以更稳妥的做法是调低imgsz到960,同时保证batch不小于8。下面是我在单卡16GB设备上常用的组合:
yolo detect train \ data=xray.yaml \ model=yolov8m.pt \ epochs=100 \ imgsz=960 \ batch=12 \ device=0 \ fliplr=0.0 \ cache=True参数说明:model=yolov8m.pt是中等规模的预训练权重,比yolov8n精度更高,比yolov8l好训练。epochs=100是小数据集的基本设置,如果100轮后mAP还在明显上涨,可以加到150。cache=True把图片全部加载进内存,800张图完全够用,省去每轮从磁盘读图的时间。fliplr=0.0是医学影像的一个重要调整,下面专门说。
3.3 类别不平衡:800张五类怎么配权重
肺病数据集的类别数量通常非常不均匀。一个800张的集合里,正常肺可能有400张,结核可能只有80张。YOLOv8有内置的class_weights参数,可以在训练时给少数类更大的损失权重。使用方法是yolo detect train --class_weights True,也可以先统计类别频率后手动计算权重。
我建议先跑下面这段脚本,把每个类别的目标框数量打印出来:
import glob from collections import Counter counter = Counter() for lbl in glob.glob('labels/*.txt'): for line in open(lbl): counter[int(line.split()[0])] += 1 total = sum(counter.values()) for cid, cnt in sorted(counter.items()): print(f'class {cid}: {cnt} boxes, weight {total / (len(counter) * cnt):.2f}')输出的权重可以折算进训练。如果某类占比低于10%,光靠权重还不够,建议再过采样少数类图片,比如把结核样本复制到ROS文件夹里,再按原始目录结构合并。注意不要太猛复制,否则模型对少数类过拟合,验证集mAP看起来高,换一批医院的数据就崩。
3.4 数据增强取舍:医学影像慎用水平翻转
YOLOv8默认开启随机水平翻转、HSV色域增强、仿射变换等。在自然图像检测里这些都是好用的技巧,但用在X光片上需要逐项反思。水平翻转会让心脏轮廓和主动脉弓左右对调,骨骼结构也换边,病灶相对位置发生左右镜像。虽然左右肺在解剖上对称,但病灶在右肺中叶和左肺上叶的分布含义不同,翻转会让模型学到错误的解剖位置依赖。
因此训练参数里我一般会加上fliplr=0.0,关闭水平翻转。HSV增强对医学灰度图没有意义,X光片是单通道伪彩色或灰度,转成RGB后通道间高度相关,hsv_h、hsv_s、hsv_v保持默认影响不大。值得保留的是轻微缩放和旋转,degrees=5左右可以容忍X光拍摄时病人的轻微姿态变化。用命令行传参的话,正式训练时常写成这样:
yolo detect train ... degrees=5 translate=0.05 scale=0.3 fliplr=0.0scale=0.3表示缩放比例范围在0.7到1.3之间,让模型对胸片距离变化更鲁棒。translate=0.05做轻微平移,模拟不同机器和摆位导致的视野偏移。
下面是一个针对X光小数据集的参数建议表:
| 参数 | 本地默认值 | X光建议值 | 理由 |
|---|---|---|---|
| imgsz | 640 | 960或1280 | 保留小病灶像素 |
| batch | 自动 | 8~16 | 平衡BN稳定性与显存 |
| fliplr | 0.5 | 0.0 | 避免左右解剖位置语义反转 |
| degrees | 0.0 | 0~5 | 容忍摆位偏差 |
| scale | 0.5 | 0.3 | 不过度缩放影响病灶尺度 |
| hsv_h / hsv_s | 0.015 / 0.7 | 0 | X光灰度通道不宜调色 |
| cache | False | True | 800张图可全量缓存 |
3.5 训练命令与损失函数曲线查看
训练启动之后,YOLOv8默认在终端打印每个epoch的box_loss、cls_loss、dfl_loss和mAP指标。很多新人只关心最后mAP,但小数据集上真正要盯的是loss曲线的收敛形态。box_loss在20轮以内快速下降,后面缓慢波动是正常的;如果100轮后cls_loss还持续走高,说明数据标签有大量误标,或者类别id对应错误。
要看YOLOv8训练过程曲线,训练时加一个project=results参数,跑完后会在results目录下生成results.png和results.csv。YOLOv8会自动画损失函数曲线图,包括train/box_loss、val/cls_loss等。你也可以在训练中打开results下的train_batch0.jpg,检查送入训练的图片和标签框是否正确显示,这一步能发现很多标签错位问题。
训练命令里我习惯用plots=True,它会在验证阶段额外保存混淆矩阵和预测样本图。运行到第100轮时,打开confusion_matrix.png,基本就能判断这个800张的数据集最终能不能用。
4. 验证“可识别”这件事:用mAP50和混淆矩阵判断五类真实水平
4.1 在验证集上跑val命令并读指标
训练完的模型不能只靠训练集loss说好。对800张图片的小数据集,验证集至少留出20%,也就是160张。模型训练完成后,跑验证集的命令非常简单:
yolo detect val \ data=xray.yaml \ model=results/train/weights/best.pt \ imgsz=960 \ conf=0.25 \ iou=0.45参数说明:model指定训练时保存的最佳权重best.pt,YOLOv8还会保存一个last.pt,验证时永远用best.pt。conf=0.25是置信度阈值,低于这个值的预测框会被丢弃;iou=0.45是NMS用的IoU阈值。这两个值会影响mAP数值,对比不同模型时务必用同一套参数。
验证结束后终端会打印一组指标,重点看mAP50和mAP50-95。对于X光肺病这样的目标检测任务,mAP50达到0.7以上才算勉强可用;mAP50-95一般会比mAP50低很多,因为小病灶框与真实框的IoU很难达到0.75以上。如果mAP50很高但mAP50-95很低,说明存在大量边界不够精的检测框,常见原因是标签框本身画得偏大。
4.2 混淆矩阵:细菌性肺炎与病毒性肺炎的常混淆
YOLOv8的验证结果里会生成confusion_matrix.png,横轴是真实类别,纵轴是预测类别。正常肺和细菌性肺炎通常容易分开,真正难分的是细菌性肺炎与病毒性肺炎,这两种肺炎在X光片上表现为肺部不同区域的斑片状影,但位置和密度高度重叠。如果混淆矩阵里这两类的交叉点数值超过30%,说明模型并没有学到区分它们的稳定特征,可能需要从原始图片里裁剪ROI做二次分类,而不是继续调检测模型。
还有一个常常被忽略的类别是“结核”。结核病灶好发于肺尖和上肺野,与肺炎的分布有明显差异,但早期结核阴影小且淡,很容易被模型漏检。看混淆矩阵时要重点确认最后一列,也就是“肺结核”被预测成背景的比例。
4.3 单张X光片推理并控制置信度阈值
验证集指标是群体结果,上线前还要人工看单张图片的预测效果。使用训练好的模型对单张X光片推理,YOLOv8提供了Python接口:
from ultralytics import YOLO model = YOLO('results/train/weights/best.pt') results = model.predict( source='sample_covid.jpg', conf=0.3, imgsz=960, save=True, save_txt=True, save_conf=True ) for r in results: for box in r.boxes: cls_id = int(box.cls[0]) conf = float(box.conf[0]) xyxy = box.xyxy[0].tolist() print(f'class {cls_id}, conf {conf:.2f}, box {xyxy}')source可以是图片文件或目录。save_txt=True会把预测结果保存成YOLO格式的txt,save_conf=True让txt里多出一列置信度。这段代码在推理时重新指定了conf=0.3,如果验证集里误检主要来自正常肺的纹理,可以把这个阈值调到0.4,代价是漏检会变多。
下表是医学X光检测场景里常用的验证阈值及其含义:
| 参数 | 推荐值 | 含义 | 场景取舍 |
|---|---|---|---|
| conf | 0.25~0.4 | 置信度过滤 | 调高减少误检,增加漏检 |
| iou | 0.45 | NMS去重阈值 | 病灶密集时调低到0.4 |
| imgsz | 与训练一致 | 图像缩放尺寸 | 必须与训练imgsz相同 |
| max_det | 60 | 单图最大检测数 | 一张肺片多病灶时调高 |
4.4 验证集划分的另一个坑:同病人多张图泄漏
X光片数据集往往来自公开医学数据库,同一个病人可能有多张不同时间拍摄的X光片。如果这些图片被分到训练集和验证集两个地方,验证指标会虚高,因为模型其实记住了病人特征,而不是病变特征。这个问题在800张的小数据集上尤其严重。
解决办法是在划分数据集之前,先检查文件名里是否包含病人ID。常见命名格式如patient_001_left.jpg和patient_001_right.jpg,左侧和右侧是同一病人的两张片子。划分时按病人ID分,而不是按图片分。如果文件名里没有病人信息,只能退而求其次,依靠第5章要去重后的哈希方式,至少避免完全重复的图片跨集出现。
5. 小数据集落地的3个技巧:去重、五折交叉验证、导出ONNX部署到RK3588
5.1 用SHA-256给原始图片去重
同一个病人的X光片可能在网络里被重复上传,文件名不同,内容完全一致。800张图片里去掉重复后可能只剩600多张可用,比例可观。去重代码用Python标准库即可:
import hashlib from pathlib import Path seen = {} for img_path in Path('images').rglob('*'): if img_path.suffix.lower() not in ('.jpg', '.jpeg', '.png'): continue digest = hashlib.sha256(img_path.read_bytes()).hexdigest() if digest in seen: print('duplicate:', img_path, 'same as', seen[digest]) else: seen[digest] = img_path注意read_bytes()会把整张图片读进内存,X光片单张几MB,800张完全没问题。如果你有几千张大图,就改用分块读文件的方式。去重后,对应.txt标签也要一起删除或重新关联。
5.2 用五折交叉验证替代一刀切划分
800张图片做8:2划分,验证集只有160张,一次划分的运气成分很大。在数据量有限时,我建议做五折交叉验证,训练5个模型,每个模型用不同的20%数据作为验证集。最终看5个模型的平均mAP,这个数值比单次划分可信得多。
YOLOv8没有内置交叉验证功能。常见做法是先用Python把图片ID分成5折,然后循环5次生成data_fold1.yaml到data_fold5.yaml,依次训练:
for i in 1 2 3 4 5; do yolo detect train \ data=xray_fold$i.yaml \ model=yolov8m.pt \ epochs=80 \ imgsz=960 \ batch=12 \ fliplr=0.0 \ project=fold_$i done每个fold保存独立权重。做最终模型时,可以选择在全部800张图上用交叉验证得到的最佳超参数重新训练一个模型,也可以保留5个模型做集成推理。后者在推理时会多花时间,但医学小数据集上集成通常能提升1到3个百分点的mAP50。
5.3 导出ONNX并在RK3588上部署
如果你的目标是把模型跑在边缘设备上,比如瑞芯微RK3588,YOLOv8训练好的best.pt要先导出成ONNX格式:
yolo export model=fold_1/best.pt format=onnx imgsz=960 opset=12导出后得到一个best.onnx。后端推理时,用onnxruntime加载模型,输入是1×3×960×960的RGB张量,输出有三个层,分别是边界框回归、分类概率和分布焦点损失。为RK3588做部署时,通常还需要把ONNX转成RKNN格式,转换前确保ONNX里的算子都是RKNN Toolkit支持的版本。
X光片在部署阶段要注意输入预处理:训练时YOLOv8自动把图片缩放到imgsz并且归一化到0到1,导出后的ONNX模型也会要求同样的预处理参数。不要在后端代码里再额外减均值除方差,否则推理效果会明显变差。到了这一步,一个用小样本X光数据集训练出的YOLOv8模型,才算真正具备“可识别”的能力。
本文还有配套的精品资源,点击获取