YOLOv8医学影像小样本实战:800张X光片肺病检测全流程
简介这是一份面向医学影像分类与目标检测任务的数据集包含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 的五行数值与类别idYOLOv8标记数据集里的每个.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/valYOLOv8会报Dataset not found。所以运行训练之前过一遍data.yaml里的路径确保train和val都真实存在。3.2 imgsz与batchX光片大图与显存的取舍YOLOv8默认的imgsz是640但很多X光片原始分辨率在2000×2000以上里面病灶直径可能只有几十像素。直接用640训练小病灶可能直接缩没了。我的经验是先统计一下标签框的像素宽度分布再看原始图分辨率imgsz设为1200或以上会明显提升小目标召回率。代价是显存。800张图片不算多如果你用的是8GB显存的GTX 1660 Tiimgsz1280时batch只能开到4到8。batch太小会导致BN层统计不稳定但YOLOv8在训练时会自适应调整所以更稳妥的做法是调低imgsz到960同时保证batch不小于8。下面是我在单卡16GB设备上常用的组合yolo detect train \ dataxray.yaml \ modelyolov8m.pt \ epochs100 \ imgsz960 \ batch12 \ device0 \ fliplr0.0 \ cacheTrue参数说明modelyolov8m.pt是中等规模的预训练权重比yolov8n精度更高比yolov8l好训练。epochs100是小数据集的基本设置如果100轮后mAP还在明显上涨可以加到150。cacheTrue把图片全部加载进内存800张图完全够用省去每轮从磁盘读图的时间。fliplr0.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(fclass {cid}: {cnt} boxes, weight {total / (len(counter) * cnt):.2f})输出的权重可以折算进训练。如果某类占比低于10%光靠权重还不够建议再过采样少数类图片比如把结核样本复制到ROS文件夹里再按原始目录结构合并。注意不要太猛复制否则模型对少数类过拟合验证集mAP看起来高换一批医院的数据就崩。3.4 数据增强取舍医学影像慎用水平翻转YOLOv8默认开启随机水平翻转、HSV色域增强、仿射变换等。在自然图像检测里这些都是好用的技巧但用在X光片上需要逐项反思。水平翻转会让心脏轮廓和主动脉弓左右对调骨骼结构也换边病灶相对位置发生左右镜像。虽然左右肺在解剖上对称但病灶在右肺中叶和左肺上叶的分布含义不同翻转会让模型学到错误的解剖位置依赖。因此训练参数里我一般会加上fliplr0.0关闭水平翻转。HSV增强对医学灰度图没有意义X光片是单通道伪彩色或灰度转成RGB后通道间高度相关hsv_h、hsv_s、hsv_v保持默认影响不大。值得保留的是轻微缩放和旋转degrees5左右可以容忍X光拍摄时病人的轻微姿态变化。用命令行传参的话正式训练时常写成这样yolo detect train ... degrees5 translate0.05 scale0.3 fliplr0.0scale0.3表示缩放比例范围在0.7到1.3之间让模型对胸片距离变化更鲁棒。translate0.05做轻微平移模拟不同机器和摆位导致的视野偏移。下面是一个针对X光小数据集的参数建议表参数本地默认值X光建议值理由imgsz640960或1280保留小病灶像素batch自动816平衡BN稳定性与显存fliplr0.50.0避免左右解剖位置语义反转degrees0.005容忍摆位偏差scale0.50.3不过度缩放影响病灶尺度hsv_h / hsv_s0.015 / 0.70X光灰度通道不宜调色cacheFalseTrue800张图可全量缓存3.5 训练命令与损失函数曲线查看训练启动之后YOLOv8默认在终端打印每个epoch的box_loss、cls_loss、dfl_loss和mAP指标。很多新人只关心最后mAP但小数据集上真正要盯的是loss曲线的收敛形态。box_loss在20轮以内快速下降后面缓慢波动是正常的如果100轮后cls_loss还持续走高说明数据标签有大量误标或者类别id对应错误。要看YOLOv8训练过程曲线训练时加一个projectresults参数跑完后会在results目录下生成results.png和results.csv。YOLOv8会自动画损失函数曲线图包括train/box_loss、val/cls_loss等。你也可以在训练中打开results下的train_batch0.jpg检查送入训练的图片和标签框是否正确显示这一步能发现很多标签错位问题。训练命令里我习惯用plotsTrue它会在验证阶段额外保存混淆矩阵和预测样本图。运行到第100轮时打开confusion_matrix.png基本就能判断这个800张的数据集最终能不能用。4. 验证“可识别”这件事用mAP50和混淆矩阵判断五类真实水平4.1 在验证集上跑val命令并读指标训练完的模型不能只靠训练集loss说好。对800张图片的小数据集验证集至少留出20%也就是160张。模型训练完成后跑验证集的命令非常简单yolo detect val \ dataxray.yaml \ modelresults/train/weights/best.pt \ imgsz960 \ conf0.25 \ iou0.45参数说明model指定训练时保存的最佳权重best.ptYOLOv8还会保存一个last.pt验证时永远用best.pt。conf0.25是置信度阈值低于这个值的预测框会被丢弃iou0.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( sourcesample_covid.jpg, conf0.3, imgsz960, saveTrue, save_txtTrue, save_confTrue ) 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(fclass {cls_id}, conf {conf:.2f}, box {xyxy})source可以是图片文件或目录。save_txtTrue会把预测结果保存成YOLO格式的txtsave_confTrue让txt里多出一列置信度。这段代码在推理时重新指定了conf0.3如果验证集里误检主要来自正常肺的纹理可以把这个阈值调到0.4代价是漏检会变多。下表是医学X光检测场景里常用的验证阈值及其含义参数推荐值含义场景取舍conf0.250.4置信度过滤调高减少误检增加漏检iou0.45NMS去重阈值病灶密集时调低到0.4imgsz与训练一致图像缩放尺寸必须与训练imgsz相同max_det60单图最大检测数一张肺片多病灶时调高4.4 验证集划分的另一个坑同病人多张图泄漏X光片数据集往往来自公开医学数据库同一个病人可能有多张不同时间拍摄的X光片。如果这些图片被分到训练集和验证集两个地方验证指标会虚高因为模型其实记住了病人特征而不是病变特征。这个问题在800张的小数据集上尤其严重。解决办法是在划分数据集之前先检查文件名里是否包含病人ID。常见命名格式如patient_001_left.jpg和patient_001_right.jpg左侧和右侧是同一病人的两张片子。划分时按病人ID分而不是按图片分。如果文件名里没有病人信息只能退而求其次依靠第5章要去重后的哈希方式至少避免完全重复的图片跨集出现。5. 小数据集落地的3个技巧去重、五折交叉验证、导出ONNX部署到RK35885.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光片单张几MB800张完全没问题。如果你有几千张大图就改用分块读文件的方式。去重后对应.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 \ dataxray_fold$i.yaml \ modelyolov8m.pt \ epochs80 \ imgsz960 \ batch12 \ fliplr0.0 \ projectfold_$i done每个fold保存独立权重。做最终模型时可以选择在全部800张图上用交叉验证得到的最佳超参数重新训练一个模型也可以保留5个模型做集成推理。后者在推理时会多花时间但医学小数据集上集成通常能提升1到3个百分点的mAP50。5.3 导出ONNX并在RK3588上部署如果你的目标是把模型跑在边缘设备上比如瑞芯微RK3588YOLOv8训练好的best.pt要先导出成ONNX格式yolo export modelfold_1/best.pt formatonnx imgsz960 opset12导出后得到一个best.onnx。后端推理时用onnxruntime加载模型输入是1×3×960×960的RGB张量输出有三个层分别是边界框回归、分类概率和分布焦点损失。为RK3588做部署时通常还需要把ONNX转成RKNN格式转换前确保ONNX里的算子都是RKNN Toolkit支持的版本。X光片在部署阶段要注意输入预处理训练时YOLOv8自动把图片缩放到imgsz并且归一化到0到1导出后的ONNX模型也会要求同样的预处理参数。不要在后端代码里再额外减均值除方差否则推理效果会明显变差。到了这一步一个用小样本X光数据集训练出的YOLOv8模型才算真正具备“可识别”的能力。本文还有配套的精品资源点击获取