医疗影像碎片检测数据集构建全流程:从DICOM清洗到YOLOv8分割
简介这是一份面向医疗影像目标检测与结构分析任务的数据集为医学AI开发者、算法工程师及临床研究人员提供标准化数据支撑基于YOLO格式标注可直接适配yolov12等主流检测框架。数据集划分清晰训练集894张、验证集255张、测试集128张共1277张医学影像标注包含Fragment碎片、Ignore忽略区域、Set结构集合三类目标兼顾碎片检测、干扰过滤与整体结构关联分析。包内共2000个文件以1277个txt标注文件、721个jpg图像为主另含1个yaml配置和1个docx说明文档压缩包约15.62MB目录规整便于直接接入现有训练流程。目前已有66人学习浏览。借助碎片—集合关联标注可支持空间关系建模忽略区域标注则能显著提升模型对噪声和伪影的鲁棒性辅助医疗器械定位、病理特征提取与临床研究也为医学AI算法教学提供了具有明确临床意义的检测范例。1. 医疗影像碎片检测与结构分析数据集先回答三件事医疗影像里的碎片检测难点不是大块异物而是几个像素宽的骨皮质碎屑和金属微颗粒。这类任务通常还要接结构分析碎片数量、最大径、离心率、在骨缺损区里的分布密度这些指标全依赖分割掩膜够不够干净。拿到一份“碎片检测与结构分析数据集.zip”大多数人第一步就是解压丢进 YOLO但真正决定上限的是标签口径、目录结构和几何基准真值。骨科钢钉内固定术后的 X 光复查里细碎骨片和小金属屑混在软组织与骨纹理间算法误检漏检都常见放疗摆位影像里补片边缘碎粒也是一样的难题。这个数据集要回答三件事碎片在哪、什么材质、几何参数是多少。下面按一条可复现的落地路径把从清洗、标注到训练与质检的步骤讲清楚。2. 医疗影像碎片数据集的采集窗口、标注规范与清洗掉坑2.1 成像段与格式选择DICOM 先看窗宽窗位落盘用 PNG 加映射表碎片检测的数据集通常不是单一模态。X 光平片对高密度骨碎片敏感CT 薄层对金属碎粒有星芒伪影MRI 序列里低信号的铁屑反而好认。做数据集的第一步是把这些来源分开不要混在一个目录里——模型学的是“图像域加任务域”的联合分布混在一起轻则误检重则训练不收敛。我一般用 pydicom 读取 DICOM 文件先按窗宽窗位做一次归一化再转成 8bit PNG。股骨颈骨折的碎片在 X 光下对比度低窗宽拉宽会把皮质骨纹理压平碎片轮廓跟着糊掉这时要按组织局部的直方图峰谷做二次拉伸。转换代码写进脚本import pydicom import numpy as np from PIL import Image ds pydicom.dcmread(dicom/0001.dcm) arr ds.pixel_array.astype(np.float32) # 影像设备会把灰度映射到 12bit 或 16bit先看 WindowCenter / WindowWidth wc, ww ds.WindowCenter, ds.WindowWidth if isinstance(wc, pydicom.multival.MultiValue): wc wc[0] if isinstance(ww, pydicom.multival.MultiValue): ww ww[0] low, high wc - ww / 2.0, wc ww / 2.0 arr np.clip((arr - low) / (high - low), 0, 1) * 255.0 img Image.fromarray(arr.astype(np.uint8)) img.save(images/0001.png) np.save(mapping/0001_wl.npy, np.array([wc, ww]))这段代码先把 DICOM 的像素值按窗宽窗位做线性映射再落成 PNG。注意我把窗宽窗位也存了一份 npy原因是推理阶段的预处理必须跟训练时一致否则模型在训练图上是“看得见的对比度”上线后变成另一幅图掉点会非常难看。mapping 目录在发布时应该跟着权重一起交付。提示DICOM 转 PNG 时一定保留 PixelSpacing结构分析里所有像素距离都要靠它换算成毫米。2.2 碎片类别判据边界模糊时标签约定比模型结构更影响性能碎片检测的第一个坑是“碎片”本身缺乏严格定义。做标注的人会问骨皮质撕裂的锐角算不算碎片金属钻头磨出的 0.2mm 微粒在影像上就一个像素点标还是不标这些问题不解决F1 永远提不上去。我的习惯是先在标注规范里定三层规则最小闭合面积、与母体组织的分离度、可检测性阈值。以 X 光骨碎片为例规则可以这样写碎片必须形成完整闭合边缘与周围骨组织的灰度差大于 80~255 灰度域面积小于 30 像素的忽略不标因为标签噪声会被模型放大。金属异物单独一类“高亮伴放射状伪影”是它的判据不接受只标亮核不标伪影的做法。钙化灶和骨碎片灰度接近但钙化灶多出现在血管壁或软组织碎片贴着骨质缺损区这类语义差异要求标注平台能同时显示 DICOM 原图和定位标记。类别的粒度直接决定模型效果。我现在更倾向用 5 个互斥类别bone_fragment、metal_fragment、calcification、foreign_body、artifact。很多人觉得 artifact 不是检测目标但它是误检的主要来源单独成一类让模型学会“这不是碎片”比在 NMS 后加规则过滤要稳得多。2.3 清洗清单DICOM 去重、运动模糊、脱敏检查原始数据里总有重复扫描、重复导出的文件。两幅图内容一样但压缩级别不同文件名不同肉眼很难分辨。清洗阶段用感知哈希去重是最省事的方法find ./dicom_export -name *.dcm -print0 | xargs -0 -I{} python -c import pydicom, hashlib, sys ds pydicom.dcmread({}) raw ds.PixelData[:1024] h hashlib.blake2b(raw).hexdigest() print(h, {}) | sort | awk {if($1last){print $2} last$1}用 blake2b 对每个 DICOM 的 PixelData 前 1024 字节做哈希排序后扫出重复项再人工复核是否真的同源。不要对全文件做哈希DICOM 头里的时间戳、患者注释只要变化就失去去重意义。接着用 Laplacian 方差筛运动模糊严重的帧阈值通常取 40低于这个值说明图像边缘基本都是平滑的出现伪影的概率高。最后一步是脱敏检查DICOM 的 PatientName、PatientID、InstitutionName 必须清零转 PNG 前还要检查像素里是否烙有住院号水印。清洗完成后应该有下面这么一张 check 表清洗项手段通过阈值重复图像blake2b 像素哈希无重复运动模糊Laplacian 方差大于等于 40 保留对比度过低灰度直方图 2%/98% 分位差差值大于等于 30脱敏字段pydicom 检查 tag 0x0010,0x0008均为空清洗之外还要注意病例级别的划分。同一个患者的术前术后多幅图必须放进同一个子集防止同一病人的图像同时出现在训练集和验证集把评估指标虚高好几个点。3. 数据集目录设计与结构分析用元数据3.1 目录三层结构原始、标注、划分三件套打开数据集.zip 之前先看目录能猜出做的人有没有经验。比较稳的医疗影像数据集目录是这样的data/ ├── raw_dicom/ # 脱敏后的原始 DICOM ├── images/ # 模型实际消费的 PNG ├── annotations/ │ ├── coco/ # COCO 格式标注检测和分割共用 │ └── yolo/ # YOLO 格式 txt ├── metadata/ │ ├── structure_analysis.json # 碎片几何参数基准真值 │ └── split.json # train/val/test 划分 └── splits/ ├── train.txt ├── val.txt └── test.txt不要把标注文件和图像混在一个平铺目录也不要直接在 images 下建 train/val/test 三个物理目录理由有二医疗影像经常要按“病例”而不是按“图像”来划分子集物理目录复制难度大后续想换 5 折交叉验证时soft link 或 split.txt 比移动文件高效得多。structure_analysis.json是标题里“结构分析”落地的载体。我一般会在推理阶段从分割掩膜里算碎片的面积、周长、等效圆直径、离心率同时把人工标注算好的这些值存进 JSON 做基准真值{ image_001.png: { fragments: [ {label: 0, area_px: 512, perimeter_px: 87, max_diameter_mm: 4.2, bbox: [120, 88, 23, 31]} ] } }注意长度单位要统一成毫米这要求记录每张图的空间分辨率DICOM PixelSpacing tag转 PNG 时不能把这个信息丢掉。3.2 用 Python 把标注转成 COCO 语义结构COCO 是标题对应的常见数据结构也是后面接多数开源检测框架时损耗最小的一种。把标注平台 JSON 转成 COCO 的代码不复杂但有一次性的三个坑类别 ID 要从 0 开始且不能跳号每张图的 id 在 images 和 annotations 里必须一致多边形坐标保留小数点后两位就够。下面这个函数处理单张图import json import numpy as np from PIL import Image def to_coco(entry, ann_id, categories): img_path entry[image_path] w, h Image.open(img_path).size img_entry { id: entry[image_id], file_name: img_path, width: w, height: h } anns [] for poly, label in zip(entry[polygons], entry[labels]): seg np.array(poly).reshape(-1).tolist() x seg[0::2] y seg[1::2] bbox [min(x), min(y), max(x) - min(x), max(y) - min(y)] anns.append({ id: ann_id, image_id: entry[image_id], category_id: categories[label], segmentation: [seg], area: abs(np.sum(x[:-1] * y[1:] - np.array(x[1:]) * y[:-1]) / 2.0), bbox: bbox, iscrowd: 0 }) ann_id 1 return img_entry, anns, ann_id这里的 segmentation 是闭合多边形的坐标扁平列表area 用鞋带公式算而不是用 bbox 面积因为碎片往往不规则bbox 会高估真实面积进而干扰后续密度统计。category_id 由类别名映射成整数。一次性跑完所有图像后建议顺手统计每张图的标注数量分布和每个类别的实例总数这两个数字能提前暴露出“某个类别只有几十个实例”这类会让训练直接崩掉的隐患。3.3 空样本与类不平衡留出 10% 的负样本图医疗影像里阴性和阳性样本的比例天然失衡。正常 X 光平片没有碎片如果开发者把所有阴性图都删掉模型误检率必然高因为模型没见过“骨骼自然纹理下的非碎片”。我通常会在验证集里保留 10%~15% 的负样本图这些图不提供任何标注但 COCO 的 images 表里要保留它们评估时会把这些负样本计入假阳性。类不平衡的处理只换损失函数往往不够。先统计类别实例数的对数分布若某类少于 50 实例优先用仿射变换加对比度扰动做复制粘贴增强把碎片多边形从一幅图贴到另一幅图的骨纹理背景上贴合边缘做 2~3 像素的羽化。这类增强在 COCO 格式下很好实现因为 segmentation 是多边形变换后的坐标仍然闭合。4. 用 YOLOv8 把数据集训成碎片检测模型4.1 写一份 data.yaml别让路径出错YOLOv8 训练自己的数据集入口是 data.yaml。用相对路径配合项目根目录是最稳的写法path: ./medical_splinter train: splits/train.txt val: splits/val.txt nc: 5 names: 1: bone_fragment 2: metal_fragment 3: calcification 4: foreign_body 5: artifacttrain 和 val 可以填 txt 文件每一行是一张 PNG 的路径。官方文档里常见train: images/train指向目录的写法但如果用 COCO 转 YOLO 的脚本生成标注别忘记把 images 前缀对齐。这一行小配置错了会直接报AssertionError看着吓人其实只是路径对不上。YOLO 要求每张图对应的 txt 和图像同名默认把 txt 放在 labels 目录里。我习惯在转换脚本里显式指定输出python tools/coco2yolo.py --coco annotations/coco \ --img-dir images --label-dir labels --classes bone_fragment metal_fragment calcification foreign_body artifact转换脚本跑完我总会随便打开一张图和它的 txt 人工看一眼。YOLO 的行格式是class x_center y_center width height坐标全部归一化到 0~1当出现某个值大于 1.05 或小于 -0.05说明多边形的坐标空间和图像像素空间没对齐问题多半出在导出环节。4.2 训练命令里的 5 个关键参数当前 YOLOv8 的命令足够简单yolo detect segment train \ datamedical_splinter.yaml \ modelyolov8s-seg.pt \ epochs150 \ imgsz640 \ batch16 \ device0,1 \ projectruns/segment \ namefragment_v2用segment而不是detect原因回到标题里的“结构分析”要从分割掩膜里计算碎片的面积、最大径和离心率bbox 给不出这些信息。模型权重选yolov8s-seg.pt在碎片这种小目标且类别数少的场景下比更大的模型收敛快推理稳定性更好。接下来是 5 个容易踩坑的参数参数建议值说明imgsz640 起调到 960 观察碎片可能只有 20×20 像素640 下过小但 960 会明显增加显存占用mosaic1.0配close_mosaic20前 130 epoch 开 mosaic最后 20 epoch 关闭恢复真实分布让框稳定lr00.0015~0.003预训练权重下 0.003 安全从零训练时用 0.01 配合 warmupoverlap_maskFalse碎片之间可能重叠掩膜重叠会引入噪声保持非重叠掩膜patience40医疗数据标注量一般不大早停放到 40避免前 20 epoch 的小震荡被误判训练日志如果显示 train 的 box_loss 一直下降而 val 的 box_loss 在某个 epoch 后反复弹跳那不是过拟合更像是验证集里的病例和训练集来自同一患者序列。回第 2 章把 split.json 的划分规则重新做一遍按 PatientID 分桶。4.3 从预测掩膜接回碎片的结构分析标题里的“结构分析”在推理阶段体现为后处理脚本。模型训完我跑一个独立脚本加载 best.pt 处理验证集并统计from ultralytics import YOLO import numpy as np model YOLO(runs/segment/fragment_v2/weights/best.pt) res model.predict(images/0117.png, conf0.35, iou0.5) for r in res: if r.masks is None: continue mask r.masks.data.cpu().numpy().astype(np.uint8) # N, H, W for i in range(mask.shape[0]): area mask[i].sum() ys, xs np.where(mask[i] 0) max_d np.sqrt((xs.max() - xs.min())**2 (ys.max() - ys.min())**2) print(ffragment_{i}: area{area}, max_diameter_px{max_d:.1f})打印出来的 area 和 max_diameter_px 是像素单位要换算物理单位必须乘以 PixelSpacing。这里记录的是轴向最大径不是三维空间的最大径单平面影像能算的极限就到这。5. 用一致性指标给数据集做“质检终检”5.1 kappa 一致性检查和空图复查验证集跑完一轮模型 mAP50 到 0.7 以上后不要急着做评估汇报。我会拿两份标注对比数据集里同一张图如果有多个标注人版本计算类别层面的交集和差异用 Cohen’s kappa 度量一致性。碎片检测的 kappa 达到 0.75 以上说明标注争议可控低于 0.6 就说明规范里“最小面积”和“闭合边界”的定义有歧义需要回到 2.2 重写规则。没有多人标注时可以用机器反证把训练好的模型对验证集做预测把高置信但不在标注里的框挑出来逐一人工复核。这个“模型建议”列表通常能发现漏标的碎片记录后重新补标签。我在骨科影像数据上这样补了一轮训练集实例数量增加了 8%最后验证集 mAP50 提升了 3~4 个点效果比换骨干网络都明显。复查负样本时可以用一小段脚本检查空图是否还在import json from collections import Counter with open(annotations/coco/instances_val.json) as f: coco json.load(f) img_ids {img[id]: img[file_name] for img in coco[images]} counts Counter() for ann in coco[annotations]: counts[img_ids[ann[image_id]]] 1 empty [name for name in img_ids.values() if counts[name] 0] print(空标注图像:, len(empty), 张, empty[:5])如果空图数量明显少于划分时的负样本配置说明漏了图或标注阶段误删需要回去对账。5.2 增广底线与发布前的 JSON 复查增广策略要留一条底线。医疗影像不能像自然图像那样随意做颜色抖动骨灰度里混了伪彩图翻转还要注意左右对称是否影响解剖语义。碎片检测这类几何敏感任务我只推荐旋转、缩放、平移三种增广旋转范围限制在 ±30°超过 90° 翻转会丢失“上方偏头侧”的解剖位置先验。数据集发布前的最后一步是用统一脚本复查标注 JSON 结构类别数是否符合 data.yaml、每张图的 bbox 是否在图像内、多边形的点数是否少于 4、是否有重复的 ann id。这步用几十行 Python 就能扫完连通性和字段完整性都包括。只有当 structure_analysis.json 里的几何参数分布和人工复核结果对得上时这个数据集才真正具备“碎片检测”和“结构分析”双用途。本文还有配套的精品资源点击获取