☰
YOLOv8医学影像实战:X光片肺病五分类数据集训练全流程
2026/9/28 5:31:15 网站建设 项目流程

简介:面向肺部疾病检测的YOLOv8标注格式医学影像数据集,专为计算机视觉初学者与医学影像分析研究者打造,适用于肺炎分类、目标检测模型训练等典型任务。压缩包内共有1601个文件,其中含800张原始X光片JPG图像,以及与之对应的800个Txt标注文件,标注内容采用YOLO格式,记录了每个目标框的坐标与类别序号;另有1个YAML配置文件,用于定义类别名称、训练参数和数据集路径。图片素材覆盖细菌性肺炎、新冠病毒、正常肺、结核与病毒性肺炎五种常见肺部异常类型,文件命名还隐含了训练集、测试集、验证集的划分信息,可减少手工整理数据的工作量。整个压缩包仅25.51MB,轻量便携,方便快速下载与本地实验。该数据集已有419人学习使用,配套的标注格式和目录结构非常适合作为目标检测项目入门练习,也能帮助使用者深刻理解YOLOv8的标签组织方式与配置流程,为后续扩展其他医学影像识别任务奠定基础。

1. X光片肺病数据集为什么值得自己重标一遍:800张图藏着五分类任务的全部难点

一个标注好的X光片肺病数据集,听起来是拿来就能训的现成资源,但实际用yolov8训练自己的数据集时,最先翻车的往往不是网络结构,而是标注质量、类别平衡和文件组织方式。这个标题很有代表性:800张原始图片,五类目标——细菌性肺炎、新冠病毒、正常肺、结核、病毒性肺炎——看起来规模不大,却覆盖了医学影像目标检测里最典型的几个坑:类别相似度高、病灶边界模糊、样本不均衡、小目标占比高。适合正在做医学影像AI落地、想用YOLO系列快速验证可行性,或者刚接触yolov8想拿一份真实数据练手的同学。ZIP包本身不是重点,重点是你拿到之后怎么验证标注、怎么划分数据集、怎么调训练参数,才能真正训出一个能用的检测模型。

2. 先看清数据集的底细:五类标签分布、影像格式与YOLOv8标注文件结构

2.1 解压后先做三件事:查文件数、查类别名、查标注格式

拿到ZIP包后,第一时间不是急着配环境,而是先看清目录结构。常见打包习惯是images/放原图、labels/放标注、classes.txt或者data.yaml定义类别。但不同来源的数据集组织方式差异很大,有的把标注直接嵌在XML里,有的用YOLO格式的TXT,有的连验证集划分都做好了。第一步先统计资源实际内容,别靠猜。

# 列出压缩包内的目录结构(不解压,只看清单) unzip -l xray_lung.zip | head -50 # 统计原图数量 unzip -l xray_lung.zip | grep -E '\.(jpg|jpeg|png)$' | wc -l # 查看标注文件的数量,确认是否和图片一一对应 unzip -l xray_lung.zip | grep -E '\.txt$' | wc -l

第一行命令只列压缩包内容不看正文,避免800张图全部解压出来才发现目录不对。第二、三行分别统计图片和TXT数量,数量对不上就有大问题。正常的YOLO标注设计是一张图对应一个同名TXT文件,比如bacteria_001.jpg对应bacteria_001.txt。如果图片数远大于TXT数,说明有一部分图没有标注对象,这种图在后续训练里容易被当成纯背景,干扰收敛。

2.2 YOLOv8标注文件到底长什么样:归一化坐标和类别ID

YOLOv8沿用了YOLOv5以来的标注格式:每一行代表一个目标,五个字段分别是class_id x_center y_center width height,坐标全部除以图片宽高做了归一化,数值范围是0到1。上面的class_id是整数,从0开始排。假设这个数据集定义的类别顺序是bacterial_pneumonia, covid, normal, tuberculosis, viral_pneumonia,那covid就是1。

# 随机抽一个标注文件出来看内容,判断格式是否标准 unzip -p xray_lung.zip labels/covid_014.txt | head -5 # 如果数据集的图片宽高不一致,先记录一批尺寸,方便后面排查坐标是否越界 unzip -p xray_lung.zip images/covid_014.jpg > /tmp/test_img.jpg python -c "from PIL import Image; im=Image.open('/tmp/test_img.jpg'); print(im.size)"

上面unzip -p是直接把压缩包里的单条文件打到标准输出,不需要解压整个包,排查效率很高。看到标注内容是1 0.5234 0.4456 0.2312 0.3123这种格式,心就可以放下一半。最怕看到的是坐标值大于1的,比如标注框用了绝对像素值,这在YOLO格式里属于致命错误。还有一类标注是空格和逗号混用,YOLOv8的Dataset代码对逗号分隔的处理并不友好,后面训练时直接报标签格式错误。

2.3 五类样本分布:正常肺和病变肺的框数量会是训练成败的关键

X光片肺病数据集中正常肺一般占大头,因为正常样本好收集,而结核和病毒性肺炎的阳性样本相对少。这种天然的不均衡直接决定训练策略。先用脚本统计一下每个类别的目标框数量,再决定要不要做类别重加权。

import os from collections import Counter label_dir = "labels" counter = Counter() invalid_files = [] for txt_name in os.listdir(label_dir): txt_path = os.path.join(label_dir, txt_name) with open(txt_path, "r", encoding="utf-8") as f: for line in f: parts = line.strip().split() if len(parts) != 5: invalid_files.append(txt_name) continue try: cls_id = int(parts[0]) coords = list(map(float, parts[1:])) if not all(0 <= v <= 1 for v in coords): invalid_files.append(txt_name) except ValueError: invalid_files.append(txt_name) counter[cls_id] += 1 print("各类别目标框数量:", dict(counter)) print("异常标注文件数:", len(set(invalid_files))) for fname in set(invalid_files): print("异常示例:", fname)

这段脚本做了两件事:统计每个类别ID出现的框数,以及把所有坐标越界、字段不够5个、非数字内容的标注文件找出来。执行结果如果显示某类只有几十个框,而正常肺有几千个框,那后续要重点考虑要不要对少数类做过采样,或者用更大的输入尺寸来放大病灶特征。医学影像里病灶区域往往只占整图的百分之几,框太小的时候yolov8的高层特征图很可能直接丢掉了这些小目标。

3. 搭建YOLOv8训练环境与数据划分:从ZIP解压到YAML配置的完整流程

3.1 先把ZIP安全解压,别在解压阶段就丢标注文件

yolov8训练自己的数据集第一步是把ZIP完整解压。这里的坑很隐蔽:用Windows自带解压或部分第三方工具解压含中文文件名或特殊字符时会改名、截断,后面ImageNet的读取逻辑就找不着文件。我一般会用Python的zipfile做完整性校验,同时在解压时把权限、时间戳也保留下来。

import zipfile import os zip_path = "xray_lung.zip" extract_dir = "xray_lung_dataset" with zipfile.ZipFile(zip_path, "r") as zf: bad_file = zf.testzip() if bad_file: print(f"压缩包已损坏,第一个坏文件: {bad_file}") else: zf.extractall(extract_dir) print("解压完成,文件数:", len(zf.namelist()))

testzip()会逐文件校验CRC,发现损坏时返回文件名,这时候就不要强行训练了。X光片数据集在网上下载时经常因为传输中断导致部分图片字节缺失,这种图片加载时会报Dataset label error或直接跳过,少一张图可能看不出来,但少几十张带结核标注的图,训练出来的模型对结核的召回率就会很难看。解压完成后建议再跑一次前面的统计脚本,确保解压后文件名和标注文件名逐一对应。

3.2 数据划分必须按患者或按病例去重,不能纯随机打散

800张图的数据集,如果同一患者的多个视图(比如正位和侧位)散落在train和val两个集合里,验证集指标会虚高,部署时一遇到新患者的片子就现原形。这是医学影像数据集最容易出现的数据泄漏。如果ZIP内目录名带患者ID或者图片前缀带病例编号,优先按照前缀划分;没有患者ID的话,至少保证同一来源的连续编号要么全进train要么全进val。

# 先看文件名是不是有规律的前缀,决定按什么维度划分 unzip -l xray_lung.zip | grep -E '\.(jpg|jpeg|png)$' | sed 's/.*images\///' | head -20

输出如果是covid_001.jpg, covid_002.jpg, normal_001.jpg这种命名,说明前缀很可能对应类别,后面的序号对应病例或样本编号。可以按序号的奇偶做划分,保证同一类别的不同样本进不同集合,而不要用完全随机的方式把文件打散。

import os import random import shutil random.seed(42) image_dir = "xray_lung_dataset/images" label_dir = "xray_lung_dataset/labels" train_img_dir = "xray_lung_dataset/images/train" val_img_dir = "xray_lung_dataset/images/val" os.makedirs(train_img_dir, exist_ok=True) os.makedirs(val_img_dir, exist_ok=True) names = [f for f in os.listdir(image_dir) if f.lower().endswith(".jpg") or f.lower().endswith(".png")] # 提取病例前缀,例如 covid_001.jpg -> covid_001 case_ids = sorted(set([n.rsplit(".", 1)[0].rsplit("_", 1)[0] + "_" + n.rsplit(".", 1)[0].rsplit("_", 1)[1] for n in names]))

这一段看起来有点绕,本质是从文件名covid_001.jpg里切出covid_001作为病例ID。先按病例ID分组,再把整个病例ID划分到train或val,而不是把covid_001和covid_002拆到两边。划分比例建议8:2或7:3,800张小数据集用8:2比较合理,验证集留160张以上,指标波动才不至于大得没法看。

3.3 手写data.yaml:类别顺序必须和标注文件对得上

YOLOv8的config文件是模型训练和验证的枢纽。很多刚上手的人直接复制网上的coco.yaml改个路径就开训,结果类别名对不上,训练时不会报错,但验证时mAP全部为零,因为模型预测的class_id和真实框的class_id在语义上错位了。

# xray_lung.yaml path: /absolute/path/to/xray_lung_dataset train: images/train val: images/val nc: 5 names: 0: bacterial_pneumonia 1: covid 2: normal 3: tuberculosis 4: viral_pneumonia

这里的path建议写绝对路径,虽然YOLOv8支持相对路径,但训练脚本经常换目录,绝对路径最省心。train和val字段指向的是图片目录,yolov8会自动去同名labels目录找对应的TXT标注,前提是images下的文件叫covid_001.jpg,labels下也要有covid_001.txt,后缀不同没关系,前缀必须一致。names的索引顺序必须和标注TXT里的第一个数字一一对应,如果标注里0是细菌性肺炎、1是新冠病毒,而YAML里0是正常肺,那你训练出来的模型输出类别全部错位。

3.4 安装ultralytics:CPU版能验证流程,GPU版才谈得上训练效率

环境搭建遵循yolov8的最常见做法,直接用pip安装ultralytics包,它会自动拉取对应的torch版本。如果手里只有CPU电脑,可以先跑通流程,但800张图、5个类别,CPU训练一个epoch可能要接近十分钟,一个完整的训练跑几十个epoch,等不起。建议先用CPU环境做数据加载和单batch前向的验证,再用云GPU或本地显卡训完整模型。

# 创建干净的虚拟环境,避免torch和opencv版本打架 conda create -n yolov8 python=3.10 -y conda activate yolov8 pip install ultralytics

安装完成后跑一个极小的验证,确认yolov8能正常加载权重和做前向推理。

from ultralytics import YOLO model = YOLO("yolov8n.pt") # 用一张训练图片做前向,验证环境没问题 results = model.predict("xray_lung_dataset/images/train/bacterial_pneumonia_001.jpg", imgsz=640, verbose=True) print(results[0].boxes)

yolov8n.pt是轻量版预训练权重,拿来验证环境是最稳的,几十MB下载快,CPU环境下单张图预测也能跑得起来。如果这一行代码报CUDA错误,说明torch版本和显卡驱动不匹配,需要重装对应CUDA版本的torch;如果报opencv的错误,多半是opencv-python和opencv-python-headless装重了,卸载掉一个就行。

4. 五分类训练的参数调优与损失曲线判读:小数据集最怕过拟合,先从这些参数下手

4.1 一次能跑通的训练命令:从bfloat16到早停参数逐个说清楚

环境就绪后,训练命令本身不复杂,复杂的是参数选择。800张图的小数据集,最常见的错误是用coco的默认训练配置:300个epoch、输入640、默认的增强策略,结果训练到一半发现验证集mAP不升反降,典型的过拟合。我用这套配置起步,收敛速度和精度比较平衡。

yolo detect train \ data=xray_lung.yaml \ model=yolov8s.pt \ epochs=120 \ imgsz=640 \ batch=16 \ patience=20 \ optimizer=AdamW \ lr0=0.001 \ weight_decay=0.0005 \ augment=True \ cache=ram \ device=0 \ project=xray_runs \ name=exp_v1

逐个参数拆开看。yolov8s.pt比n版大一档,特征表达能力更强,小数据集上不容易欠拟合;如果机器显存只有8G,batch=16搭配imgsz=640可能刚好卡在极限,显存不足就调低batch到8。epochs=120不是硬性要求,配合patience=20,意思是验证集指标连续20个epoch不提升就自动停止,实际可能跑到60个epoch就停了。cache=ram把800张图全部缓存到内存里,可以极大缩短每个epoch的时间,前提是内存够;16G内存建议不要开。optimizer=AdamW是我在小数据集上的习惯选择,SGD收敛慢、更依赖lr的精细调节,AdamW在样本量小的时候更容易走出稳定的下降曲线。

4.2 输入尺寸的选择:imgsz=640到底够不够看肺结节

X光片里的肺炎病灶区域有大有小,大叶性肺炎可能占据一片肺野,而早期结核球可能只有指甲盖大小。imgsz=640是yolov8的默认值,在大多数场景下能平衡速度和精度。如果想让小目标检测能力更强,把imgsz提到960甚至1280,但显存占用会翻倍。800张图的规模下,我一般先跑640,看验证集的召回率:如果漏检集中在病灶面积很小的样本上,再单独用imgsz=960微调一轮。

from ultralytics import YOLO # 加载已经训练好的模型,用更大的输入尺寸微调 model = YOLO("xray_runs/exp_v1/weights/best.pt") results = model.train( data="xray_lung.yaml", epochs=50, imgsz=960, batch=8, patience=15, lr0=0.0002, )

lr0从0.001降到0.0002,是为了避免大输入尺寸带来的loss震荡。yolov8的迁移机制是会用你传入的权重做预训练,所以第二阶段微调不需要重新从头训。这里要注意:传入的data、epochs这些参数会覆盖之前训练保留的配置,所以每个关键参数都要显式写出来,别漏。

4.3 损失曲线的读法:训练loss和验证mAP不是一回事

小数据集训练时,最容易产生误导的就是训练loss曲线——它一直在下降,看起来一切正常,但验证集mAP已经停滞甚至倒退。要同时打开两个信号:训练loss是模型拟合训练集的反映,验证集mAP才是泛化能力的证据。训练loss降到很低而验证loss开始反弹,这个点就是早停点,patience参数就是为此设计的。

每次训练结束后,项目目录下会自动生成多个图表,其中results.png汇总了box_loss、cls_loss、dfl_loss以及precision、recall、mAP50、mAP50-95的变化曲线。判读的顺序是:第一看mAP50有没有稳步上升,第二看cls_loss有没有持续下降,第三才看box_loss。如果mAP50一直在0.2以下徘徊,而训练loss正常下降,问题基本不在训练参数,而在标注文件或数据划分。

验证集mAP50对五类肺病检测是个关键指标。正常肺和病毒性肺炎在X光片上的差异非常细微,模型输出的置信度普遍偏低,所以mAP50过了0.5就算达到基本可用水平,而mAP50-95因为对框的定位精度要求更高,往往只有mAP50的一半左右,看到0.2到0.3之间是正常的。如果mAP50-95是0.0,那说明模型学到的框定位很差,优先检查标注框有没有偏离病灶区域。

5. 避坑:800张小样本X光片训练的常见问题与排查方法

5.1 训练loss下降但mAP全为0:标注类别索引和data.yaml错位

现象:训练日志里box_loss从2.0降到0.8,看起来一切正常,但验证阶段mAP、precision、recall全部是0,没有任何报错。

原因:第一轮排查下来,绝大多数是标注TXT里的类别数字和data.yaml里names顺序对不上。比如模型预测class_id=2表示normal,但标注里class_id=2是covid,训练时模型会认为prediction和ground truth是不同类别的框,导致所有预测都算错。另一种可能是标注TXT里坐标已经是像素值而非归一化值,此时yolov8会把大量框在预处理阶段过滤掉。

解决:返回2.3节的统计脚本,打印出每个类别ID的框数,和执行一次验证集单图预测对比,手动检查输出框的类别名。在训练脚本里加一行model.names打印,确认模型实际使用的类别顺序。坐标如果是像素值而不是归一化值,写脚本把每个框的四个坐标统一除以图片宽高,再做归一化转换。

5.2 验证集指标很高,部署到新片子上一塌糊涂:数据划分没有按病例去重

现象:训练时mAP50达到0.85,自我感觉良好,但拿一批新的X光片跑推理,漏检和误检都很严重,明显配不上验证集指标。

原因:同一个人物的多张胸片,如果一张在训练集、一张在验证集,验证集指标反映的是模型记住了这个人病灶纹理特征的能力,而不是泛化到其他人身上。更严重的情况是某些数据集本身包含大量同源拷贝,文件名不同但图像几乎一样,随机划分后这些近似重复的图片同时出现在train和val里,等于把val变成了train的记忆题。

解决:严格按患者ID或病例前缀划分,把同一个患者ID的所有图片全部放到同一个集合里。划分完成后做一次相似度抽检,肉眼对比训练集和验证集里的图片,发现明显相似的图要人工再调整。对小样本数据集,宁可验证集只有120张图,也要保证病例级别的隔离。

5.3 损失曲线正常但验证集recall极低:少数类别样本太少导致过拟合

现象:整体mAP50还行,但单独看每个类别的recall,结核和病毒性肺炎的召回率不到0.1,正常肺的召回率却接近0.9。

原因:类别样本量差距几个数量级时,yolov8默认对每个类别的权重是相等的,模型天然倾向于把不确定的框预测为大类,因为这样总loss最小。医学影像中病原体感染在X光片上本来就特征模糊,少数类学不到足够多的pattern。

解决:在3.2节统计结果的基础上做类别重加权。yolov8的cls参数可以调节分类loss的权重,但更直接的办法是数据层面做少数类的过采样——把结核和病毒性肺炎的图片复制一份到训练集,相当于对少数类做了2倍重复学习。需要注意过采样的对象是图片,不是单张框。另一种做法是使用mosaic=0.5增强策略的默认配置,让四个图拼接时少数类的框有更多机会参与训练,但X光片拼接会引入人工切分边界,mosaic比例不要调太高。

5.4 训练中断后重新运行,结果反而更差:ZIP包里有重复文件导致缓存混乱

现象:第一次训练正常,后来清理了缓存文件再次训练,发现loss曲线和第一次完全对不上,甚至出现了很多异常的框。

原因:有些ZIP包在打包时把同一张图放在多个目录下,比如images/covid_001.jpg和images/augmented/covid_001.jpg,yolov8的cache按文件名索引,同名文件被后者覆盖后,标注文件还是旧版,造成图像内容和标注错位。重新解压、换目录后问题依旧,就是因为原始ZIP本身就包含重复。

解决:解压后先按文件名去重,不同目录下同名的文件只保留一份,通过md5sum比较内容,确实不同但同名的文件直接改名。数据集处理阶段多做一步重复文件清理,能为后面节省大量排查时间。这个步骤花不了几分钟,但对训练稳定性影响很大。

5.5 显存OOM或训练速度极慢:800张图不该用大batch硬顶

现象:训练启动后几秒钟就报CUDA out of memory,或者GPU利用率很低,每个epoch要跑十几分钟。

原因:X光片是单通道灰度图,许多安装脚本会默认把它转成三通道RGB加载,显存占用直接翻三倍。再加上yolov8的mosaic增强会拼接四个图,实际显存需求比理论上单张图大一截。

解决:先降batch,从16降到8,再不行降到4。同时关闭cache=ram,改默认磁盘缓存。显存占用和imgsz的平方成正比,640换到512能省接近40%的显存。灰度图可以在训练前用脚本转成RGB的JPG再喂给yolov8,避免运行时反复转换。如果以上都做了还是OOM,检查GPU利用率是不是只有30%以下,很可能卡在数据加载上,把workers从默认2调到8,让CPU提前加载图片,而不是等GPU空转。

6. 让模型能落地:X光片推理验证、模型导出与置信度阈值选择技巧

训练完不等于能用。800张图训练出来的模型,在真实场景里需要单独调置信度阈值,YOLOv8默认的conf=0.25在医学影像上往往偏低,会把肺纹理的阴影误判成病灶。我一般先用验证集跑一遍不同阈值下的精确率和召回率,找到F1最高的点。

from ultralytics import YOLO model = YOLO("xray_runs/exp_v1/weights/best.pt") # 遍历不同置信度阈值,统计验证集指标 for conf in [0.1, 0.2, 0.3, 0.4, 0.5]: metrics = model.val(data="xray_lung.yaml", conf=conf, verbose=False) print(f"conf={conf}, mAP50={metrics.box.map50:.3f}, mAP50-95={metrics.box.map:.3f}")

从0.1到0.5逐个扫一遍,挑选mAP50下降不多但precision明显上升的阈值作为部署值。小样本模型有个特点:阈值低的时候recall高但误检多,阈值高的时候precision高但漏检增加,找到平衡点后把conf写进推理参数。

模型导出方面,我一般会把best.pt转成ONNX格式,方便后续部署到CPU服务或边缘设备。

model.export(format="onnx", imgsz=640, half=True)

half=True在支持FP16的设备上能减半推理耗时,但CPU上不一定有加速,选择时看部署环境。导出后可以用ONNX Runtime单独验证一遍输出,确认和PyTorch结果一致,防止部署时出现闷声不响的精度损失。

最后一个习惯是保存每次训练的预测可视化结果,不只保存指标。yolov8训练结束会自动生成val_batch*.jpg,验证集上的标注和预测框叠在一起。我每次都会翻一遍这些图,专门看两个类别的预测形态:正常肺有没有被误报成结核,结核小病灶有没有漏检。指标会骗人,图像不会。

最终模型落地时,医学场景的误检代价远高于普通物体检测,宁可保守一些把阈值调高,也要保证输出的每个框都有充分的置信度支撑。这是我用小样本医学数据集训练yolov8这段过程最大的教训,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询