ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

YOLOv8训练X光片肺病数据集:从数据清洗到调参评估全流程

YOLOv8训练X光片肺病数据集:从数据清洗到调参评估全流程 简介面向医学影像识别场景的X光片肺病数据集内置800张原始胸部X光图片并采用YOLOv8格式完成标注可用于细菌性肺炎、新冠病毒、正常肺、结核及病毒性肺炎五类目标检测任务适合作为深度学习目标检测与医学图像分类的练习数据。压缩包共包含1601个文件其中800张jpg原始图片、800个配套txt标注文件及1个yaml配置文件整体大小25.51MBjpg保留原始影像信息txt提供归一化坐标与类别标签yaml定义类别名称及路径便于直接接入YOLO系列训练流程。当前已有417人下载学习是一份结构清晰、体量轻巧的肺部影像数据集。借助现成标注可省去手动标注工作帮助快速搭建肺病检测模型。同时覆盖多种常见肺病类别适合入门者熟悉YOLOv8数据组织方式也可用于验证模型在不同病理特征下的检测效果。1. 拿到X光片肺病数据集之后先别急着开train很多人解压完这个「X光片肺病数据集800张原始图片使用yolov8标记」的压缩包第一件事就是装ultralytics、跑yolo train结果一个epoch都走不完就被报错打断要么是标签类别索引越界要么是图里根本没有对应的txt。这个数据集的含金量不在那800张原图而在五类目标的标注是否干净——细菌性肺炎、新冠病毒、正常肺、结核、病毒性肺炎每一类如果标得不一致后面迁移学习、调参、出指标全是白费功夫。这篇文章就按一条完整路线走一遍先把YOLOv8标注格式和这批数据盘清楚再用脚本做数据清洗和目录重整然后搭环境、跑训练、看损失曲线最后用混淆矩阵和badcase把漏检和误检压下去。适合手里有公共肺病数据集、准备做毕业设计或算法验证的人也适合第一次拿YOLOv8训练自己数据集、想避开“标签格式坑”的工程师。2. 用yolov8格式读懂这800张肺病X光片的标注结构2.1 解压后先核对 images、labels 与 data.yaml 的对应关系YOLOv8的数据集不管是自己标还是别人标好的目录结构都遵循一套约定图片放在images下同名的标注文件放在labels下扩展名从.jpg换成.txt。拿到压缩包先看一眼顶层长什么样常见做法是用tree命令没有就装一个unzip X光片肺病数据集.zip -d ./pneumonia_dataset cd ./pneumonia_dataset tree -L 2正常情况下你会看到类似这样的结构pneumonia_dataset/ ├── images/ │ ├── normal_001.jpg │ ├── covid_014.jpg │ └── ... ├── labels/ │ ├── normal_001.txt │ ├── covid_014.txt │ └── ... └── data.yaml如果压缩包里只有images没有labels或只有原始的XML那就意味着“标记”只是做了数据集整理还没导出成YOLO格式得自己转换或重新标注。一个标注框在txt里占一行格式固定为class_id x_center y_center width height后四个值全部是相对于图片宽高的比例取值在0到1之间。比如2 0.5234 0.6109 0.0877 0.1456表示左上角在第52.34%宽度、61.09%高度处宽占8.77%、高占14.56%。这一步最容易翻车的是data.yaml缺失或内容跟labels对不上。data.yaml里names列表的顺序就是类别IDYOLOv8的txt写的是什么数字就得对应names里的第几个名字顺序错了整个训练就错了。看到缺失的yaml我自己补一份通常长这样path: ./pneumonia_dataset train: images/train val: images/val nc: 5 names: 0: bacterial_pneumonia 1: covid 2: normal 3: tuberculosis 4: viral_pneumonianames这里建议不要用中文。ultralytics对中文类别名的显示支持并不好训练中有可能输出乱码可视化时再映射回“细菌性肺炎”“正常肺”这些中文名更稳妥。2.2 用Python盘一下五类样本分布和标注框质量数据集质量比模型结构更影响上限。写一个统计脚本把五类的框数量、图片数量、空标注文件、越界框全部查一遍这一步能过滤掉80%的后续报错。脚本逻辑并不复杂用pathlib遍历labels目录逐行解析txtimport glob from pathlib import Path labels_dir Path(./pneumonia_dataset/labels) class_names [bacterial_pneumonia, covid, normal, tuberculosis, viral_pneumonia] cls_count {name: 0 for name in class_names} img_with_label 0 empty_labels [] bad_boxes [] for txt_path in sorted(labels_dir.glob(*.txt)): lines [l.strip() for l in txt_path.read_text().splitlines() if l.strip()] if not lines: empty_labels.append(txt_path.name) continue img_with_label 1 for line in lines: parts line.split() if len(parts) ! 5: bad_boxes.append((txt_path.name, len_not_5)) continue cls_id int(parts[0]) if cls_id len(class_names): bad_boxes.append((txt_path.name, fclass_id{cls_id})) continue x, y, w, h map(float, parts[1:]) if not (0 w 1 and 0 h 1) or x 0 or y 0 or x w/2 1 or y h/2 1: bad_boxes.append((txt_path.name, fbox({x},{y},{w},{h}))) cls_count[class_names[cls_id]] 1 print(五类框数统计:, cls_count) print(有标注的图片数:, img_with_label) print(空标注文件:, empty_labels[:10]) print(异常框数量:, len(bad_boxes), bad_boxes[:5])这段代码有三个输出维度值得盯。第一是五类框数统计如果某类只有几十个框而其他类有几百个后面训练就必须考虑类别不平衡不能直接拿着默认参数莽第二是空标注文件这类图片在训练时会自动参与负样本计算如果过多会把模型带偏第三是越界框w或h大于1意味着标注框超出一张图的尺寸这类样本需要直接处理掉。2.3 class id 与 names 顺序不一致的连锁错误标注质量检查里最隐蔽的风险是类别ID错位。有些数据集是从XML转换来的XML里类名排序和YOLO txt的class_id沿用不同顺序还有些是对多个来源做了合并同一张图里0代表疫情、在另一个来源里0代表正常这些都要在训练前理清。验证方法很简单随机抽几张图片把标注框画回去肉眼复核。画框用ultralytics自带的可视化太重直接写个OpenCV脚本看三个文件就够了。先读txt再按(x - w/2) * 图片宽换算成像素坐标画矩形标题写上类别名。这一步能把“顺序错了但统计脚本看不出来”的问题暴露出来因为框会明显画在错误的位置或对应错标签。批量抽20张即可不用全看如果这20张里发现一张错位就回头排查转换脚本的类名映射而不是继续往后走。3. YOLOv8训练前的数据清洗与目录重整3.1 先搭一个能跑ultralytics的conda环境拿到干净标签后下一步才是搭环境。YOLOv8对Python版本不算挑剔3.9到3.11都能跑但建议用conda独立建环境避免把系统Python搞乱。我的常规做法是先建环境再装ultralyticsconda create -n yolov8 python3.10 -y conda activate yolov8 pip install ultralyticsultralytics会自动把torch、torchvision一起带上来CPU版本也能跑但X光片训练的输入尺寸一般不会低于640CPU训练800张图可能要跑到天亮有NVIDIA显卡务必确认CUDA版PyTorch装到位。验证环境是否正常就一句话yolo predict modelyolov8n.pt sourcehttps://ultralytics.com/images/bus.jpg这条命令会下载yolov8n预训练权重并跑一张示例图。如果它能在当前环境正常输出预测结果说明CUDA、模型下载、推理链路都通了。注意这里的权重下载需要在网络通畅的环境下完成之后训练会直接引用这份缓存不用反复下载。3.2 把灰度X光片统一成三通道并做CLAHE增强X光片通常是灰度图或对比度很低的JPG直接喂给在自然图像上预训练好的YOLOv8特征分布差异偏大。虽然PyTorch的DataLoader会把单通道自动广播成三通道但预处理质量会影响收敛速度。我一般会在训练前统一做两步处理第一把所有图片用cv2.imread以三通道方式读入并保存确保通道数是3第二用CLAHE做局部对比度增强让肺部纹理和病灶边缘更清晰这对后续小目标检测有实际帮助。import cv2 from pathlib import Path img_dir Path(./pneumonia_dataset/images) out_dir Path(./pneumonia_dataset/images_enhanced) out_dir.mkdir(exist_okTrue) clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) for img_path in sorted(img_dir.glob(*.jpg)): img cv2.imread(str(img_path), cv2.IMREAD_GRAYSCALE) if img is None: print(跳过无法读取:, img_path.name) continue img_eq clahe.apply(img) img_color cv2.cvtColor(img_eq, cv2.COLOR_GRAY2BGR) cv2.imwrite(str(out_dir / img_path.name), img_color)这段代码先按灰度读取再原地做CLAHE最后转成三通道输出。clipLimit2.0控制对比度限制幅度值越大增强越剧烈过大会把噪声一起放大tileGridSize(8,8)表示将图像分成8x8的块分别做直方图均衡对X光片这种大尺寸整体偏暗的图比较合适。处理完直接替换原目录图片并在步骤中明确标注训练时用的是增强后版本。3.3 按类别分层划分train/val并重写data.yaml800张图对一个五类检测任务来说不算多划分训练集和验证集不能简单随机抽否则某一类可能全部落到验证集里导致训练时见不到这类样本。所以要按类别做分层划分保证每类在train和val中的比例大致相同。import random import shutil from pathlib import Path src_img Path(./pneumonia_dataset/images_enhanced) src_lbl Path(./pneumonia_dataset/labels) train_img Path(./pneumonia_dataset/images/train) val_img Path(./pneumonia_dataset/images/val) train_lbl Path(./pneumonia_dataset/labels/train) val_lbl Path(./pneumonia_dataset/labels/val) for d in [train_img, val_img, train_lbl, val_lbl]: d.mkdir(parentsTrue, exist_okTrue) random.seed(42) val_ratio 0.2 all_images list(src_img.glob(*.jpg)) random.shuffle(all_images) val_count int(len(all_images) * val_ratio) for img_path in all_images: lbl_path src_lbl / (img_path.stem .txt) is_val all_images.index(img_path) val_count target_img val_img if is_val else train_img target_lbl val_lbl if is_val else train_lbl shutil.copy(str(img_path), str(target_img / img_path.name)) if lbl_path.exists(): shutil.copy(str(lbl_path), str(target_lbl / lbl_path.name))这里用random.seed(42)固定随机种子保证每次划分结果一致方便复现。划分比例设为20%验证集对800张照片来说验证集会拿到160张足够看出模型是否过拟合。注意图片拷贝时没有做类别均衡如果第2.2步统计出类别很不均衡还得在此基础上对少数类做过采样这个放在第4章参数部分继续讲。4. 跑通yolov8肺病检测训练命令与参数该怎么调4.1 最小训练命令与每个参数的含义数据和目录都整理好之后训练命令其实很短。在项目根目录下执行yolo detect train \ data./pneumonia_dataset/data.yaml \ modelyolov8s.pt \ epochs150 \ batch16 \ imgsz640 \ device0data指向前面写好的yamlmodelyolov8s.pt是关键选择。800张图的数据量不算大yolov8x这种大模型很容易在该规模的医学数据上过拟合而yolov8n的容量又可能不足yolov8s是常见折中。epochs150配合早停使用不必完全跑满batch16需要根据显存调整6GB显存用16比较稳12GB可以尝试32imgsz640对X光片来说其实偏小如果显存允许如12GB以上可以提到896或1024因为病灶区域往往只占整张图的很小比例更高的分辨率有利于小病灶检出。在训练代码里调用等效接口时路径参数要格外注意。data里的path一般写相对ultralytics运行目录的路径如果在项目的scripts子目录里执行训练用相对路径很容易找不到数据建议data字段直接写绝对路径最省事。4.2 五个必调参数imgsz、batch、epochs、mosaic与lr0训练时真正决定效果差距的参数不超过5个逐个调整比一次性堆一堆高级技巧更有用。下面是我在肺病这类小目标、小样本场景下常用的参数配置参数推荐值调整依据imgsz640显存够用再提到896病灶通常占图面比例小分辨率越低越容易漏检batch166G显存/ 3212G训练时观察显存占用OOM就减半epochs150 早停800图不必固定跑满关注验证集指标是否停止上升mosaic1.0 降到 0.5医学影像目标边缘模糊过多mosaic增强会让病灶变形lr00.01 降到 0.005预训练权重迁移时初始学习率越低越稳定mosaic和lr0这两个参数很多人不调。mosaic默认是1.0对自然图像目标检测很有用但X光片里病灶局部对比度本来就低四张拼图后小病灶可能被裁掉或糊在一起50%的概率保留mosaic是业内处理医学影像的常见做法。lr0从0.01降到0.005迁移学习时能让冻结底层的梯度更新更平缓损失曲线更稳不至于前几个epoch就震荡。类别不平衡的调整窗口也放在训练前。如果第2.2节发现covid类只有三十几个框而normal类有几百个优先考虑对少数类的图像做复制过采样把样本均衡到接近1:2的程度或者对少数类的图片单独执行几轮的额外微调。更精细的类权重方案在ultralytics里没有直接暴露的class_weight参数常见处理就是在数据层面做简单可控。4.3 训练过程怎么盯results.csv、早停与损失曲线训练跑起来后不要干等yolo会自动把训练日志和指标写到runs/detect/train目录下。要实时看训练状态最直接的是盯results.csvtail -f runs/detect/train/results.csv这个文件每行是一个epoch的综合指标包含train/box_loss、train/cls_loss、metrics/precision(B)、metrics/recall(B)、metrics/mAP50(B)等。另一个更直观的路径是看训练结束后生成的results.png里面画了每项指标的曲线结合loss曲线能快速判断是否过拟合。从我的调试经验看肺病数据集上最典型的曲线形态是这样前30个epoch验证集mAP上升很快然后开始缓慢爬升如果在某个epoch之后训练loss还在降但验证mAP不动甚至下降说明模型开始记住训练分布、过拟合了。此时不必等到150个epoch结束直接用早停参数兜底yolo detect train \ data./pneumonia_dataset/data.yaml \ modelyolov8s.pt \ epochs200 \ patience30 \ imgsz640patience30表示30个epoch内验证集mAP没有更好就自动停止模型会把最优权重保存为best.pt。使用预训练权重的情况下不用每次都从随机初始化开始训练这是800张图能做迁移学习的底气所在。int类型参数的意思就是“训练到自己觉得没必要再加epoch为止”检查best.pt的mAP指标即可。5. 用混淆矩阵与badcase回标把肺病识别的漏检压下去5.1 用model.val生成混淆矩阵与每类指标训练结束后第一件不是急着拿best.pt去跑推理而是对验证集做一次完整的val评估看模型的错误具体落在哪些类别上from ultralytics import YOLO model YOLO(./runs/detect/train/weights/best.pt) metrics model.val(data./pneumonia_dataset/data.yaml, plotsTrue)执行完会在runs/detect/val下生成confusion_matrix.png和results.csv。混淆矩阵盯两处一是对角线以外的值是否集中在相邻类别之间比如细菌性肺炎和病毒性肺炎互相误检说明两类病灶形态在X光片上本身相近光靠视觉特征很难区分二是某一行真实类别几乎全部分散到其他列说明这一类存在系统性的漏检。results.csv里每一类都有precision和recall。对检测任务来说recall低比precision低更值得警惕说明模型找到了框但类别分错或者是病灶区域压根没被检测出来。看到某类recall值明显低于其他类就要进入badcase分析不能只调conf_thres阈值来掩盖。5.2 把漏检样本按类导出成HTML回标时直接改txt定位是哪一类出问题后用脚本把验证集里该类别的漏检样本连同预测结果一起导出成HTML页面一张一张看比在终端里翻数字直观得多import cv2 import glob from ultralytics import YOLO model YOLO(./runs/detect/train/weights/best.pt) val_imgs sorted(glob.glob(./pneumonia_dataset/images/val/*.jpg)) cls_name tuberculosis html [htmlbody stylebackground:#222;color:#eee] for img_path in val_imgs: results model.predict(img_path, conf0.25, verboseFalse) if len(results[0].boxes) 0: continue for box in results[0].boxes: if results[0].names[int(box.cls)] cls_name: img cv2.imread(img_path) x1, y1, x2, y2 map(int, box.xyxy[0]) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.imwrite(badcase_tmp.jpg, img) html.append(fp{img_path}brimg srcbadcase_tmp.jpg width400/p) break html.append(/body/html) open(badcase_report.html, w).write(\n.join(html))这个脚本把指定类别的预测框画出来人工打开badcase_report.html逐张对比真实病灶区域。如果发现明明有明显的病灶纹理但模型没给出框基本可以判断是标注遗漏原始标签漏标了一些阳性区域模型在训练时把“该区域不是目标”当成了学习目标。处理方式是用labelImg或X-AnyLabeling打开原图补画漏掉的病灶框更新对应txt后重新训练一轮这类回标带来的提升通常比调参更大。把这张badcase审查表放进每次迭代的流程里比盲目拉长epochs更有性价比。本文还有配套的精品资源点击获取
返回列表