简介本资源为面向医学图像目标检测的乳腺癌检测数据集适用于深度学习入门者、医学影像算法研究者及需要开展癌症病灶检测实验的学生与工程师。数据集采用YOLOv5标准文件夹结构保存无需额外预处理即可直接投入训练图像统一为640×640分辨率RGB格式边界框标注清晰完整前景丰富每张图像均含多个目标。包内共1845个文件以922个txt标签文件、921张jpg图像为主另附1个可视化py脚本与1个png预览图压缩包约12.87MB。数据划分为训练集778张图像及对应778个标签、验证集143张图像及对应143个标签类别为breast cancer单类。可视化脚本无需修改即可运行随机传入一张图片便能绘制边界框并保存至当前目录便于快速核验标注质量。目前已有1074人学习下载适合作为乳腺癌检测模型训练与验证的实用数据基础。1. 乳腺癌检测数据集为什么让一批 YOLO 模型集体翻车拿到「YOLO 医学图像数据集乳腺癌检测1类别包含训练集、验证集」这个标题很多人第一反应是单类别检测任务够简单拿现成 YOLO 权重微调几轮就能出结果。真跑起来才发现mAP 卡在 0.5 上下不动验证集 loss 震荡模型把病灶和正常腺体组织混成一团。问题不在 YOLO 本身而在这类数据集的结构和医学图像的成像特性——单类别意味着没有类别间竞争来帮模型收敛病灶区域小、对比度低、边界模糊训练集和验证集的分布差异往往比自然图像数据集大得多。这篇笔记面向的是手里已经有一份乳腺癌检测数据集、想用 YOLO 跑通检测链路的从业者。我会按「数据集结构怎么读 → 标注格式怎么转 → 训练参数怎么设 → 验证指标怎么看 → 坑在哪」的顺序把单类别医学检测从数据到推理的完整路径拆开讲。不涉及任何具体医疗机构或真实病例所有示例均为模拟数据。读完你应该能判断这份数据集值不值得投入训练以及第一轮 baseline 该怎么搭。2. 单类别乳腺癌检测数据集的结构与标注格式拆解2.1 训练集/验证集目录长什么样先做一次数据体检医学图像数据集常见的交付形态是一个压缩包解压后得到 images 和 labels 两个平行目录各自再分 train 和 val。但实际拿到的数据往往更乱有的把标注写成 CSV有的用 COCO JSON有的直接给 XML。第一步不是急着转格式而是先做数据体检——统计图像数量、分辨率分布、标注框数量和面积分布。import os import cv2 import numpy as np from pathlib import Path from collections import Counter def dataset_health_check(root_dir): 对 YOLO 格式数据集做基础体检 stats {train: [], val: []} for split in [train, val]: img_dir Path(root_dir) / images / split lbl_dir Path(root_dir) / labels / split if not img_dir.exists(): continue for img_path in img_dir.glob(*.png): img cv2.imread(str(img_path)) if img is None: continue h, w img.shape[:2] lbl_path lbl_dir / (img_path.stem .txt) n_boxes 0 areas [] if lbl_path.exists(): with open(lbl_path) as f: for line in f: parts line.strip().split() if len(parts) 5: _, _, bw, bh map(float, parts[1:]) n_boxes 1 areas.append(bw * bh) # 归一化面积 stats[split].append({ file: img_path.name, w: w, h: h, boxes: n_boxes, mean_area: np.mean(areas) if areas else 0 }) for split in [train, val]: data stats[split] if not data: print(f{split}: 无数据) continue res Counter([d[w] for d in data]) print(f{split}: {len(data)} 张图, f分辨率分布 top3{res.most_common(3)}, f平均框数{np.mean([d[boxes] for d in data]):.2f}, f平均归一化面积{np.mean([d[mean_area] for d in data]):.4f}) return stats dataset_health_check(./breast_dataset)这段代码做三件事遍历 train/val 两个 split读取每张图的宽高解析对应的 YOLO 标注文件统计每张图的标注框数量和归一化面积。输出里最需要关注的是「平均归一化面积」——如果这个值低于 0.01说明病灶在整张图中占比极小YOLO 默认的 640 输入尺寸下目标可能只有几十个像素这是后续 mAP 上不去的核心原因之一。分辨率分布则决定了你该不该统一 resize还是用 letterbox 保持长宽比。2.2 标注格式转换从 COCO/XML 到 YOLO txt 的四个边界坑如果数据集原始标注不是 YOLO txt就需要转换。COCO JSON 转 YOLO 是最常见的一种转换逻辑本身不复杂但边界处理容易翻车。import json from pathlib import Path from PIL import Image def coco_to_yolo(coco_json, img_dir, out_dir): COCO JSON - YOLO txt单类别 class_id 固定为 0 with open(coco_json) as f: data json.load(f) # 建立 image_id - 文件信息 的映射 img_map {img[id]: img for img in data[images]} # 按 image_id 聚合标注 ann_by_img {} for ann in data[annotations]: ann_by_img.setdefault(ann[image_id], []).append(ann) out_dir Path(out_dir) out_dir.mkdir(parentsTrue, exist_okTrue) for img_id, anns in ann_by_img.items(): info img_map[img_id] w, h info[width], info[height] lines [] for ann in anns: x, y, bw, bh ann[bbox] # COCO: 左上角 x,y 宽高 # 转 YOLO: 中心点 宽高全部归一化 cx (x bw / 2) / w cy (y bh / 2) / h nw bw / w nh bh / h # 边界裁剪防止浮点误差导致越界 cx min(max(cx, 0.0), 1.0) cy min(max(cy, 0.0), 1.0) nw min(max(nw, 0.0), 1.0) nh min(max(nh, 0.0), 1.0) # 过滤掉宽高为 0 的无效框 if nw 0 or nh 0: continue lines.append(f0 {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) if lines: stem Path(info[file_name]).stem with open(out_dir / f{stem}.txt, w) as f: f.write(\n.join(lines)) coco_to_yolo(./annotations.json, ./images, ./labels_all)四个边界坑分别是第一COCO 的 bbox 是左上角坐标加宽高YOLO 是中心点坐标加宽高转换时中心点要加半个宽高这一步写错会导致框整体偏移第二浮点运算后归一化坐标可能略微超出 [0,1]必须裁剪否则训练时数据增强会报错第三宽高为 0 的退化框要过滤医学标注里偶尔出现第四图像文件名和标注文件名的 stem 必须严格对应大小写敏感Windows 和 Linux 之间迁移时最容易出问题。2.3 单类别数据集的划分策略验证集不能随便切训练集和验证集的划分不是随机切就完事。医学图像有个特点同一批数据可能来自同一台设备、同一个扫描参数随机切分会导致训练集和验证集高度同分布验证指标虚高换一批数据就崩。常见做法是按来源分组切分——如果数据集里隐含了设备批次或扫描序列信息尽量让验证集覆盖不同的来源。另一个问题是单类别数据集的正负样本比例。如果验证集里有一部分图像完全没有标注框纯负样本这些图对 mAP 没有贡献但对误检率评估很重要。建议在划分时保证验证集中至少包含 10% 到 20% 的纯负样本否则你无法判断模型在正常组织上的误报情况。# 按 8:2 划分同时保证验证集包含负样本 python split_dataset.py \ --images ./images_all \ --labels ./labels_all \ --out ./breast_dataset \ --val-ratio 0.2 \ --neg-ratio 0.15 \ --seed 42--neg-ratio控制验证集中纯负样本的最低比例--seed固定随机种子保证可复现。划分完成后训练集和验证集的图像文件分别复制到images/train和images/val标注文件同理。这一步做完数据集才算真正可用。3. 用 YOLO 在本地跑通乳腺癌检测的最小训练链路3.1 环境准备与数据配置文件YOLO 训练需要一个 YAML 配置文件描述数据集路径和类别信息。单类别数据集的配置非常简洁但路径写错是最常见的翻车点。# breast_data.yaml path: /home/user/breast_dataset # 数据集根目录绝对路径最稳 train: images/train # 相对 path 的训练集图像目录 val: images/val # 相对 path 的验证集图像目录 nc: 1 # 类别数乳腺癌检测只有 1 类 names: 0: lesion # 类别名用 lesion 比用 breast_cancer 更中性path建议写绝对路径相对路径在不同工作目录下启动训练时容易找不到文件。nc必须和标注文件里的 class_id 最大值一致单类别就是 1class_id 从 0 开始。names里的名称只影响可视化标签显示不影响训练但建议用简洁的英文名。环境方面YOLO 系列通常依赖 PyTorch、OpenCV、NumPy 等。如果用的是较新的 YOLO 实现一条 pip 安装命令即可。显存方面单类别检测在 640 输入尺寸下8GB 显存足够跑 batch size 16。pip install ultralytics opencv-python numpy pyyaml # 验证环境 python -c import torch; print(torch.__version__, torch.cuda.is_available())3.2 训练命令与关键参数怎么设最小训练链路就是一条命令但参数设不对结果差很多。yolo detect train \ databreast_data.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.001 \ lrf0.01 \ patience20 \ augmentTrue \ mosaic0.5 \ scale0.3 \ fliplr0.5 \ flipud0.0 \ cacheTrue \ device0 \ projectruns/breast \ nameexp01逐参数说明model选 s 级别而不是 n 级别是因为医学图像特征弱n 级别容量不够容易欠拟合lr00.001比默认的 0.01 小一个量级医学数据量通常不大学习率太大会震荡patience20表示 20 轮验证指标不提升就早停避免过拟合mosaic0.5把默认的 1.0 降下来因为医学图像拼接后解剖结构不合理太强的 mosaic 会引入噪声flipud0.0关闭上下翻转乳腺图像有明确的方位语义上下翻转会破坏这一先验cacheTrue把图像缓存到内存小数据集能显著加速。注意如果显存不够优先降 batch 而不是降 imgsz。医学图像里小目标多imgsz 从 640 降到 320 会让病灶区域缩到十几个像素mAP 直接掉一截。3.3 训练过程监控看哪些曲线什么时候该停训练启动后runs/breast/exp01 目录下会生成 results.csv 和若干可视化文件。重点看三条曲线box_loss、cls_loss、mAP50。单类别检测没有 cls_loss 的类别竞争cls_loss 通常下降很快如果它一直不降说明标注文件里的 class_id 有问题。import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/breast/exp01/results.csv) df.columns df.columns.str.strip() fig, axes plt.subplots(1, 3, figsize(15, 4)) axes[0].plot(df[epoch], df[train/box_loss], labeltrain) axes[0].plot(df[epoch], df[val/box_loss], labelval) axes[0].set_title(box_loss); axes[0].legend() axes[1].plot(df[epoch], df[metrics/mAP50(B)], labelmAP50) axes[1].plot(df[epoch], df[metrics/mAP50-95(B)], labelmAP50-95) axes[1].set_title(mAP); axes[1].legend() axes[2].plot(df[epoch], df[metrics/precision(B)], labelprecision) axes[2].plot(df[epoch], df[metrics/recall(B)], labelrecall) axes[2].set_title(P/R); axes[2].legend() plt.tight_layout() plt.savefig(training_curves.png, dpi150)判断标准如果 train/box_loss 持续下降但 val/box_loss 在某个 epoch 后开始上升就是过拟合早停该触发如果 mAP50 在 0.3 以下就平了大概率是学习率或数据增强参数不对如果 precision 高但 recall 很低说明模型太保守可以适当降低置信度阈值或增加正样本增强。4. 验证集评估与推理mAP 之外还要看什么4.1 验证集评估命令与指标解读训练结束后用验证集做一次完整评估。yolo detect val \ modelruns/breast/exp01/weights/best.pt \ databreast_data.yaml \ imgsz640 \ batch16 \ conf0.001 \ iou0.6 \ save_jsonTrue \ projectruns/breast_val \ nameeval01conf0.001是评估时的最低置信度设低是为了计算完整的 PR 曲线不影响最终 mAP 计算。iou0.6是 NMS 的 IoU 阈值医学图像病灶密集时这个值可以适当调低到 0.5避免相邻病灶被合并。save_jsonTrue会输出 COCO 格式的预测结果方便后续做逐图分析。评估输出里mAP50 和 mAP50-95 是核心指标但单类别检测还要看 precision-recall 曲线的形状。如果曲线在 recall 0.6 之后 precision 断崖式下跌说明模型在高召回区间误检严重临床上不可接受。4.2 单张图像推理与结果可视化验证指标是全局的实际使用时要看单张图的推理效果。from ultralytics import YOLO import cv2 model YOLO(runs/breast/exp01/weights/best.pt) results model.predict( source./test_images, imgsz640, conf0.25, # 推理置信度阈值比评估时高 iou0.5, # NMS IoU saveTrue, save_txtTrue, projectruns/predict, nametest01 ) # 逐图统计检出框数量 for r in results: n len(r.boxes) if r.boxes is not None else 0 print(f{r.path}: {n} boxes, fmax_conf{r.boxes.conf.max().item() if n else 0:.3f})推理时conf0.25是常用起点比评估时的 0.001 高目的是过滤掉低置信度噪声。如果发现漏检多降到 0.15 试试如果误检多升到 0.4。save_txtTrue会输出每张图的检测结果 txt格式和训练标注一致方便做错误分析。4.3 错误分析把漏检和误检分开看拿到推理结果后不要只看总数要把漏检和误检分开统计。漏检是验证集里有标注但模型没检出的框误检是模型检出但标注里没有的框。这两类错误的成因完全不同漏检通常是目标太小或对比度太低误检通常是正常组织被误判。import numpy as np from pathlib import Path def error_analysis(pred_dir, gt_dir, iou_thresh0.5): 简单统计漏检和误检数量 tp, fp, fn 0, 0, 0 for pred_file in Path(pred_dir).glob(*.txt): gt_file Path(gt_dir) / pred_file.name preds np.loadtxt(pred_file, ndmin2) if pred_file.stat().st_size else np.empty((0, 5)) gts np.loadtxt(gt_file, ndmin2) if gt_file.exists() and gt_file.stat().st_size else np.empty((0, 5)) # 简化匹配按中心点距离粗匹配实际项目建议用 pycocotools matched_gt set() for p in preds: best_iou, best_idx 0, -1 for i, g in enumerate(gts): if i in matched_gt: continue # 计算 IoU此处省略具体实现按标准公式 iou compute_iou(p[1:], g[1:]) if iou best_iou: best_iou, best_idx iou, i if best_iou iou_thresh: tp 1 matched_gt.add(best_idx) else: fp 1 fn len(gts) - len(matched_gt) print(fTP{tp}, FP{fp}, FN{fn}, fprecision{tp/(tpfp1e-6):.3f}, recall{tp/(tpfn1e-6):.3f}) error_analysis(runs/predict/test01/labels, ./breast_dataset/labels/val)这段代码是简化版匹配逻辑实际项目建议直接用 pycocotools 的 COCOeval它处理了多框匹配、面积分组等细节。但简化版足够让你快速判断如果 FN 远大于 FP优先解决漏检如果 FP 远大于 FN优先解决误检。5. 乳腺癌检测训练避坑5 个血泪踩坑记录5.1 坑一验证集 mAP 很高换一批数据就崩现象训练时验证集 mAP50 到 0.85信心满满拿去推理新数据结果漏检一大片。原因训练集和验证集来自同一批数据分布高度一致模型记住了这批数据的成像特征而不是病灶本身的特征。解决划分验证集时按来源分组如果数据里隐含设备批次信息确保验证集覆盖不同批次同时用更强的数据增强亮度、对比度、高斯噪声提升泛化。5.2 坑二标注框归一化坐标越界导致训练中断现象训练启动几轮后报错提示坐标超出 [0,1] 范围。原因原始标注转换时浮点运算产生微小越界或者标注本身有误。解决转换脚本里加裁剪逻辑见 2.2 节代码同时在训练前跑一次标注校验把所有越界框修正或剔除。5.3 坑三mosaic 增强让病灶位置变得不合理现象训练 loss 正常下降但验证 mAP 始终上不去。原因mosaic 把四张图拼成一张医学图像的解剖结构被破坏模型学到了拼接边缘的伪特征。解决把 mosaic 从默认 1.0 降到 0.3 到 0.5或者直接关闭改用 mixup 和随机仿射变换。5.4 坑四图像尺寸不统一letterbox 后目标太小现象数据集里图像分辨率从 512 到 2048 不等统一 resize 到 640 后小图目标尚可大图目标缩得太小。原因直接 resize 改变了目标像素占比。解决用 letterbox 保持长宽比或者对高分辨率图像做切块训练tiling把大图切成 640 的子图分别标注和训练。5.5 坑五推理置信度阈值照搬评估值现象评估时 mAP 不错实际推理时要么漏检多要么误检多。原因评估时 conf0.001 是为了算完整 PR 曲线实际推理需要根据场景调。解决在验证集上扫一遍置信度阈值画出 precision 和 recall 随阈值变化的曲线选一个业务可接受的平衡点。临床上通常优先保 recall阈值可以设低一些。6. 把单类别检测推到可用阈值扫描与模型集成的小技巧单类别乳腺癌检测的最后一公里往往不是模型结构问题而是决策阈值和结果后处理。我一般会做两件事阈值扫描和轻量集成。阈值扫描的做法是在验证集上跑一次推理保存所有预测框的置信度和匹配结果然后从 0.05 到 0.9 以 0.05 为步长遍历统计每个阈值下的 precision、recall 和 F1。import numpy as np def threshold_sweep(preds, gts, iou_thresh0.5): preds/gts: list of (conf, x1, y1, x2, y2) thresholds np.arange(0.05, 0.95, 0.05) results [] for t in thresholds: tp fp fn 0 for pred, gt in zip(preds, gts): p [x for x in pred if x[0] t] matched set() for box in p: best_iou, best_j 0, -1 for j, g in enumerate(gt): if j in matched: continue iou compute_iou(box[1:], g[1:]) if iou best_iou: best_iou, best_j iou, j if best_iou iou_thresh: tp 1; matched.add(best_j) else: fp 1 fn len(gt) - len(matched) prec tp / (tp fp 1e-6) rec tp / (tp fn 1e-6) f1 2 * prec * rec / (prec rec 1e-6) results.append((t, prec, rec, f1)) # 打印 F1 最高的三个阈值 results.sort(keylambda x: -x[3]) for r in results[:3]: print(fconf{r[0]:.2f}, P{r[1]:.3f}, R{r[2]:.3f}, F1{r[3]:.3f}) return results这个扫描能直接告诉你在保 recall 的前提下置信度最低能放到多少。临床上如果要求 recall 不低于 0.9就选满足这个条件里 precision 最高的阈值。轻量集成的做法更简单用同一份数据训练两个不同规模的模型比如 s 和 m推理时把两个模型的预测框合并再做一次 NMS。单类别检测的集成收益通常比多类别小但在小目标上往往能提升几个点的 recall。我自己的习惯是如果单模型 recall 卡在 0.8 上不去就加一个 m 级别模型做集成通常能推到 0.85 以上代价是推理时间翻倍。最后说一个我踩过的坑不要用测试集去调阈值。阈值扫描必须在验证集上做调完直接用到测试集。我见过有人反复在测试集上试阈值最后指标好看但模型实际不可用。验证集和测试集的边界在医学检测里比自然图像更敏感因为数据量小过拟合验证集的风险更高。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?