免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

YOLOv8乳腺X光检测:900张标注数据集与训练避坑指南

YOLOv8乳腺X光检测:900张标注数据集与训练避坑指南 简介YOLOv8标记乳房X光检查数据集是一份面向医学影像目标检测任务的专业标注资源适合需要训练YOLOv8检测模型的开发者与研究人员。数据集整合自RSNA、MINI-DDSM-PNG-16、MIAS、INBREAST四个公开来源共900张图像已调整为416×416像素并完成YOLOv8格式标记可直接用于模型训练与验证。压缩包内共1908个文件其中包含1000张png图像、901个txt标签文件、4个pt预训练模型、2个cache缓存文件及1个yaml配置文件整体大小约496.76MByaml文件方便配置训练参数预训练权重可加速收敛。配套目录区分训练与测试数据结构清晰便于快速上手迁移学习。目前已有764人浏览学习适合医学影像AI入门及乳腺X光检测落地实践可省去数据收集、清洗与标注的大量时间。1. 900 张乳腺 X 光标记数据能直接喂给 YOLOv8 吗这份资源先替你踩平了格式坑做乳房 X 光乳腺钼靶病灶检测的人第一道坎往往不是 YOLOv8 参数调优而是没有一份能直接喂给 YOLOv8 的标注数据。RSNA、MIAS、INBREAST、MINI-DDSM-PNG-16 都是公开数据集但一个给 DICOM 加 CSV 坐标框一个给 PGM 加圆心半径标签体系互不通用光转换和清洗就能耗掉一两个晚上。这份资源把四个来源筛成 900 张 416x416 的 PNG配好 YOLO 格式标注和训练时生成的 labels.cache还带 yolov8n / yolov8m / yolov8l / yolov8x 四档预训练权重下载后可以直接进入训练流程。适合两类人想把 YOLOv8 用于乳腺 X 光检测、但没有现成 YOLO 格式数据的从业者和研究生以及想先跑通基线、再扩充数据的预研团队。2. 四个数据源合并的细节看起来都是乳腺片标签体系并不相通2.1 来源差异DICOM、PGM、16 位 PNG 的转换与筛选痕迹这份资源号称「YOLOv8 标记乳房 X 光检查数据集」但不是从某一个源搬过来的而是由四个公开数据集合并而成。RSNA 出自 Kaggle 2018 乳腺癌检测挑战赛原始图像是 DICOM标注框在配套 CSV 里字段是 xmin/ymin/xmax/ymax 像素坐标MIAS 是英国曼彻斯特的经典小数据集原始约 102 张 PGM 灰度图ground truth 是文本行里的病灶圆心与半径MINI-DDSM-PNG-16 是 DDSM 的 PNG 重制版16 位位深保留更细灰度层次但标注是链码轮廓转成 YOLO 的 xywh 框需要自己算外接矩形INBREAST 原数据有 115 例、约 410 张钼靶图带 BI-RADS 分级这里只挑了 62 张。提原始格式不是考古是因为 YOLO 训练时只认每个 PNG 同名的 txttxt 里第一列是类别号、后四列是归一化 xywh。所以拿到任何「下载后直接训练」的数据集第一反应都该是怀疑三件事作者合并时有没有把坐标系统一到归一化坐标有没有在筛选子集时把类别标签改坏有没有同一病例的多视角图像混进不同数据划分。下面这张表把四个源的差异和最容易翻车的点列出来照着查就行。数据源资源中数量原始格式原始标注形态合并时最容易翻车的点RSNA538 张DICOMCSV 像素坐标框坐标未归一化、同一病例多张图MINI-DDSM-PNG-16200 张16bit PNG链码轮廓轮廓转矩形框丢失方向信息MIAS100 张PGM圆心 半径半径换算矩形后需归一化INBREAST62 张DICOM / 导出 PNGBI-RADS 人工框类别号与其余源不一致合并逻辑里能看出筛选痕迹MIAS 全数据集 102 张这里给 100 张INBREAST 原数据集 115 例约 410 张图这里只选 62 张。这基本可以推断作者把「没有病灶框」「标注无法对齐」的图像移除了剩下 900 张都是带正样本框的图。这个动作本身很关键YOLOv8 的监督信号完全来自阳性框若训练集混进大量无框负样本而不做特殊处理正负比失衡会让 loss 前期抖动更明显。这份资源帮你把负样本事先筛掉了所以跑起来 loss 会比「原图直接转格式」干净很多。2.2 文件命名与 labels.cache训练之前先读懂目录从资源列表看目录主力是两类文件一类是 RSNA__39816__996130280.png 这种图像文件另一类是训练时生成的 labels.cache。YOLO 格式下每张 PNG 应该有一个同名 txttxt 每行对应一个目标框格式是class x_center y_center width height全部是 0~1 归一化值。目录里没把每个 txt 单独列出来不表示不存在ultralytics 训练时按同名文件在 images 和 labels 两个目录间查找。下载后第一步我会先写一个小脚本把文件名的规律拆出来顺便确认病例数量import re from pathlib import Path root Path(datasets/breast-yolo) imgs sorted(root.rglob(*.png)) pat re.compile(r^(RSNA|MINI-DDSM|MIAS|INBREAST)__(.?)__(\d)\.png$) stats {} for p in imgs: m pat.match(p.name) if not m: print(命名不满足约定训练时要留意:, p.name) continue # 分组数据源、疑似病例编号、流水号 stats.setdefault(m.group(1), set()).add(m.group(2)) for src, pids in stats.items(): print(f{src}: 共 {len(pids)} 个不同病例编号)这段代码的逻辑是文件名前两段分别是数据源和疑似病例编号无论这个编号在原始数据集里是不是严格的患者 ID把它当分组键已经比随机划分安全。打印出来的病例数如果和图像数相等说明每个病例只有一张图如果病例数明显小于图像数说明同一病例存在双视角CC / MLO图像划分 train/val 时就要小心串组。图像统一放 images/train 和 images/val对应 labels 目录保持一致ultralytics 会根据 data.yaml 自动找 sibling 目录不要自己乱改层级。labels.cache 是 ultralytics 首次加载数据集时用 torch.save 序列化出来的二进制缓存内容大致是图像路径到标签 tensor 的映射、每张图的长宽、整个数据集的 hash。第二次训练直接读取缓存省掉反复解析 txt 的 IO。坏处是一旦图像或标注被改动旧 cache 里的 hash 对不上会出现两种诡异表现——要么报 stale cache 相关错误要么训练时静默使用旧标签。所以我拿到任何别人的数据集第一个动作都是先清理 cache 再开训find /path/to/datasets/breast-yolo -name labels.cache -delete如果后续改了类别名、换了 data.yaml也要回到这一步重新删。这不是可选项是必做项第 4 章我会再展开讲它怎么坑人。2.3 416x416 的分辨率先跑通基线再谈病灶分辨率乳腺钼靶原图通常 2000x3000 像素级别资源作者统一 resize 到 416x416。好处是显存占用小yolov8n 在 GTX 1660 Ti 级别显卡也能 batch16 跑起来迭代速度快坏处是超过 5 倍的降采样。YOLOv8 的最小检测特征图尺寸是 imgsz/32416 输入对应 13x13意味着小于 32 像素的结构在下采样链上基本丢失。对 mass肿块、asymmetry局灶不对称这类直径 50~200 像素的大目标416 够用对微钙化簇这种只有 10~30 像素的目标416 下基本糊掉。按病灶类型选分辨率的建议如下病灶类型原图典型尺寸416x416 下表现建议输入mass 肿块50~200 px可检出但边缘不锐416 或 640asymmetry / 结构扭曲30~150 px勉强可检640 起microcalcification 簇10~30 px基本丢失896 且需数据增强我的建议是把这份 416 数据集当低成本 baseline 用。先用它把数据管线跑通、把类别和划分策略调好然后再把原图按同样标注重导出成 640 甚至 896 训一轮对比 mAP 提升幅度。如果你冲着边缘部署去416 反而是优势训练时用 416部署到 rk3588 这类板子做 int8 量化后延迟很低检测小病灶时再把输入切回 640相当于在推理资源和精度之间做一个折中方案。3. 训练 YOLOv8从标签校验到看 loss 曲线的完整流程3.1 环境准备与数据自检环境配置是老生常谈但版本要锁住。我用 python 3.10 建独立环境ultralytics 锁 8.2.x 而不是最新版因为 8.3 之后部分回调接口有调整照着老教程抄作业容易踩版本差异conda create -n yolo8 python3.10 -y conda activate yolo8 pip install ultralytics8.2.* opencv-python pandas装完环境先别急着训数据自检比环境更重要。YOLO 训练最常见的问题不是模型跑不起来而是标签和图像对不上、坐标越界、空 txt 一堆训练日志还不直接报错。我习惯先把 images 和 labels 全部扫一遍import numpy as np from pathlib import Path root Path(datasets/breast-yolo) for split in [train, val]: img_dir root / images / split lbl_dir root / labels / split problems 0 for img_path in sorted(img_dir.glob(*.png)): txt_path lbl_dir / (img_path.stem .txt) if not txt_path.exists(): print(f缺失标注: {img_path} - {txt_path}) problems 1 continue lines np.loadtxt(txt_path, ndmin2) if lines.size 0: print(f空标注: {txt_path}) problems 1 continue if lines[:, 1:].max() 1.0 or lines[:, 1:].min() 0.0: print(f坐标越界: {txt_path} 最大值 {lines[:, 1:].max():.3f}) problems 1 print(f{split}: 检查完成共 {problems} 个问题)三类最常见问题一是缺同名 txt训练时图像会被静默跳过二是空 txtYOLOv8 会跳过但大量存在就意味着「看着在训、实际没学到」三是归一化坐标越界多半是 RSNA 像素坐标转过来时忘了除以原图宽高。这个脚本很小但建议别跳过因为 labels.cache 的存在让损坏的标注有时不会立刻暴露等训完才发现就晚了。3.2 写 data.yaml 并核对类别号自检通过后写数据配置。path 建议直接写绝对路径相对路径在 ultralytics 8.2 里会因为工作目录不同而解析失败这种报错最容易让人误以为是数据问题path: /data/breast-yolo train: images/train val: images/val names: 0: mass 1: calcificationnames 的类别号必须和 txt 第一列严格对应。有些作者只标了一类这时 names 要缩成0: lesion如果你想复现作者的完整实验先看 labels 目录里 txt 第一列到底有几个值一条命令就能统计find labels/train -name *.txt -exec awk {print $1} {} | sort | uniq -c输出如果同时有 0 和 1说明原始标注包含两类如果只有 0data.yaml 就要改成单类。names 写错不会让训练崩掉但后面 mAP 曲线、混淆矩阵、热力图的类别名全是错的排查起来非常绕。3.3 训练命令、参数速查与 loss 曲线判断数据集确认没问题就可以正式开训。我一般用 yolov8m.pt 起步而不是最小的 n因为乳腺病灶相对小且背景复杂度高n 级模型容易欠拟合m 级是性价比最稳的一档yolo detect train \ databreast.yaml \ modelyolov8m.pt \ epochs60 \ imgsz416 \ batch8 \ device0 \ patience15 \ projectruns/breast \ namemass_416几个参数随时会改先记清楚参数值说明modelyolov8m.pt从预训练权重起步比随机初始化收敛快首次调试可以先用 yolov8nimgsz416沿用资源作者口径显存够就改 640batch8按显存调整416 下 8 约占用 6~8GBpatience15连续 15 个 epoch 无提升就停防过拟合烧时间device0无 GPU 改 devicecpu但 60 epoch 会非常慢训练过程中看两样东西终端打印的 loss 和 val 指标。更直观的是直接画 results.csv 里的损失曲线import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/breast/mass_416/results.csv) fig, axes plt.subplots(1, 2, figsize(12, 4)) axes[0].plot(df[epoch], df[train/box_loss], labeltrain box) axes[0].plot(df[epoch], df[val/box_loss], labelval box) axes[0].set_title(box loss) axes[0].legend() axes[1].plot(df[epoch], df[metrics/precision(B)], labelprecision) axes[1].plot(df[epoch], df[metrics/recall(B)], labelrecall) axes[1].legend() plt.show()判断标准不复杂val/box_loss 前 10 个 epoch 快速下降、之后缓慢下降属于正常train loss 一直降而 val loss 中途反弹就是过拟合把 patience 调小取 best.pt如果前 5 个 epoch 的 val mAP 一直是 0先别急着调参回头查类别号有没有对、目标是不是太小。补充一句YOLOv8 默认在最后 10 个 epoch 关闭 mosaic 增强loss 曲线最后一两轮会有个小跳变那是正常现象不是训崩了。4. 避坑乳腺 X 光训练集最常见的五个翻车点4.1 类别不平衡肿块框把钙化框吞了现象训练时 loss 曲线很漂亮但测试时钙化类别的 recall 极低混淆矩阵里某一类预测几乎为空。原因RSNA 和 DDSM 这两个源都以肿块为主微钙化样本占比通常不到 10%。YOLOv8 按图像采样大头类别主导 loss小类别即便有框也很难被优化起来。900 张图里如果钙化只有几十张模型基本学不到稳定的钙化特征。解决先统计类别分布awk {print $1} labels/train/*.txt | sort | uniq -c看一眼比例。如果超过 5:1常见做法是把少数类的图像在同目录复制几份相当于过采样。900 张的小数据集里复制图像会带来一定过拟合但这是性价比最高的手段。等数据扩充到几千张再考虑更精细的采样策略。4.2 RSNA 坐标未归一化框直接画出图外现象训练时出现坐标大于 1 的报错或者 val 预测框全部堆在图像边缘。原因RSNA 原 CSV 给的是像素坐标如果合并脚本直接把这些值写进 txt没有除以原图宽高坐标范围完全错掉。RSNA 每张 DICOM 原始尺寸还不完全一样不能拿一个固定分辨率去归一化。解决写脚本把越界坐标先夹回 0~1 兜底import numpy as np from pathlib import Path for txt in Path(labels/train).glob(*.txt): lines np.loadtxt(txt, ndmin2) if lines.size 0: continue lines[:, 1:] np.clip(lines[:, 1:], 0.0, 1.0) np.savetxt(txt, lines, fmt%.6f)这个 clip 只能防训练崩掉不能修正原本就歪的框。正确做法是在源头转换时用每张 DICOM 的 columns 和 rows 做归一化。所以第 3 章的自检脚本要放在 clip 之前跑先知道哪些文件有问题再决定是修脚本还是手动改标注。4.3 labels.cache 过期训练在偷偷用旧标签现象修改标注后重新训练mAP 几乎不变或者明明删了某些图像文件训练依然不报错正常跑完。原因labels.cache 把标签 tensor 和数据集 hash 一起缓存了。ultralytics 会校验 hash 决定是否重建缓存但如果改动发生在「同一路径下的文件内容更新」这种场景或者工作目录里有多个 cache 副本就可能复用旧结果。更隐蔽的情况是你改了 txt但 cache 里存的是改之前的 tensor训练时根本不会重新解析 txt。解决开训前强制清一次 cache命令第 2 章给过把路径换成你的数据集根目录即可。我现在的习惯是训练脚本开头固定加三步删 cache、跑自检、再启动训练。同一个目录反复实验时这一步能省掉大量「为什么改了没用」的排查时间。4.4 按图像划分 train/val同一病人的两张图串组现象val mAP 高达 0.85看起来效果极好换一批新病人测试时漏检严重模型泛化能力明显不如指标。原因同一个 patient 的 CC 和 MLO 两个视角图像高度相似。如果按文件随机划分同一个病人的两张图可能一张进 train 一张进 val模型等于提前见过答案。乳腺 X 光这种小数据集上这种数据泄漏特别致命mAP 虚高但实际不可用。解决按病例分组划分用第 2 章从文件名解析出的疑似病例编号作为 groupfrom sklearn.model_selection import GroupShuffleSplit imgs [...] # 图像路径列表 patient_ids [...] # 与 imgs 一一对应的病例编号 gss GroupShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, val_idx next(gss.split(imgs, groupspatient_ids))GroupShuffleSplit 保证同一个病人整体进 train 或整体进 val。这是四个数据源里最容易忽略的坑。RSNA 一个病例通常有双视角图像文件名里 pid 相同按 image 划分会让同一人的两张图分到两边训练指标全面失真。改成分组划分后 mAP 数字通常会掉 0.05~0.1但那个「变差」的数值才是真实水平。4.5 模型尺寸不是越大越好900 张图喂不动 yolov8x现象换上 yolov8x 后显存直接撞墙训练速度骤降val loss 降一段就开始反弹最终 mAP 反而不如 m 级模型。原因900 张图对 68M 参数量的 x 级模型来说太小了。乳腺 X 光图像同质化高背景结构相似大模型更容易把训练集细节背下来过拟合来得比自然图像数据集快得多。GTX 1660 Ti 这种 6GB 显存跑 m 级 batch8 imgsz416 没问题x 级大概率 OOM。解决先用 yolov8n 把数据管线和训练流程跑通再用 yolov8m 做正式 baseline。等数据扩充到 3000 张以上再考虑 l 或 x。资源里带的四档预训练权重不是让你一上来就全试一遍n、m、l、x 的关系是递进的用 m 训出第一个可信 baseline 后把它的 best.pt 当作后续更大模型的初始化权重比每次从头烤 GPU 更省时间。5. 进阶冻结 backbone 与 Grad-CAM 热力图验证5.1 用冻结层把 416 权重迁移到 640第 3 章训出的 best.pt416 输入不是终点。把它当「预训练权重」接着训 640 输入比从头训 640 收敛快得多yolo detect train \ databreast.yaml \ modelruns/breast/mass_416/weights/best.pt \ imgsz640 \ epochs40 \ batch4 \ freeze10freeze10 表示冻结 model.model 前 10 个模块的权重。YOLOv8 的前 10 层基本覆盖 backbone 的卷积和 C2f 结构提取的是通用的乳腺组织纹理特征冻结它可以保留 416 阶段学到的特征只让检测头和后半部分适应新分辨率。跑完这 40 个 epoch再把 freeze 去掉全量微调 10~20 个 epoch效果通常比直接训 640 更稳。5.2 Grad-CAM 判断模型到底在看什么乳腺 X 光背景高度相似模型很容易学到「看胸大肌边缘」这种伪特征。mAP 高不保证泛化好所以训练结束后我习惯做热力图而不是只看指标。YOLOv8 没有分类模型那种 classifier 输出常见做法是 hook backbone 最后一个 C2f 的输出做 Grad-CAM。import cv2 import numpy as np import torch from ultralytics import YOLO from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import show_cam_on_image model YOLO(runs/breast/mass_416/weights/best.pt).model model.eval() # 打印层清单确认 backbone 最后一个 C2f 的索引 for i, layer in enumerate(model.model): print(i, type(layer).__name__) target_layers [model.model[8]] # 以打印结果为准n/s/m/l/x 有微小差异 img cv2.imread(val_sample.png) rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) input_tensor torch.from_numpy(rgb.transpose(2, 0, 1)).unsqueeze(0).float() / 255.0 cam GradCAM(modelmodel, target_layerstarget_layers) grayscale_cam cam(input_tensor)[0] vis show_cam_on_image(rgb.astype(np.float32) / 255.0, grayscale_cam, use_rgbTrue) cv2.imwrite(heatmap_416.png, vis)hook 层选得太浅热力图只反映低层边缘纹理选得太深缺失空间信息高亮区域糊成一片。backbone 最后一个 C2f 通常就在 model.model[8] 或 [9] 附近以打印结果为准。热力图实验的意义很直接高亮集中在肿块周围说明模型学到的是病灶高亮在乳房边缘或胸肌说明它抄了近路需要回数据里加难样本或调整归一化。把同一张图在 416 和 640 下各出一张热力图对比还能直观看到小病灶在高分辨率下唤醒区域是否更聚焦。我拿到任何别人整理好的数据集固定步骤都是先删 labels.cache再按病例分组划分跑完训练最后做一次热力图抽查。第一次用这份资源时我花了两天才定位到 val mAP 虚高是患者串组导致的换成 GroupShuffleSplit 重新训练后mAP 从 0.83 掉到 0.74但换新病人测试反而更稳。那个「变差」的数字才是可信的。从那以后我每次完成乳腺 X 光数据管线都强制走一遍「删 cache → 查类别比例 → 按病例划分 → 热力图抽查」四连这类数据结构太相似不这样做很难分清模型到底学会了什么。希望帮到你。本文还有配套的精品资源点击获取
返回列表