ARTICLE DETAIL

资讯详情

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

骨骼癌YOLO数据集:908张临床CT/MRI+小目标优化训练指南

骨骼癌YOLO数据集:908张临床CT/MRI+小目标优化训练指南 简介本资源是面向AI医疗影像方向研究者与算法工程师的骨骼癌目标检测专用数据集聚焦骨肿瘤早期筛查与智能辅助诊断场景特别适配YOLO系列模型训练需求。数据集包含908张训练图像与380张验证图像共2000个文件主体为710张标注医学影像JPG、1288个对应YOLO格式标签TXT辅以类别定义yaml文件及详细说明文档DOCX压缩包仅41.14MB轻量易部署。已有86人学习下载反映出临床AI交叉领域对高质量专科数据的迫切需求。用户可直接加载进行骨肿瘤边界框检测模型训练支持CT/MRI多模态影像适配文档明确标注规范与临床校验背景涵盖溶骨性/成骨性病变等典型特征样本为开发放射科辅助定位工具、骨转移癌检测系统或医学影像教学案例提供可靠基准数据。1. 骨骼癌目标检测数据集908张临床CT/MRI影像YOLO原生标注专治骨肿瘤定位不准的“影像盲区”放射科医生看一张全身骨扫描图平均要花47秒盯住可疑溶骨灶——而转移性骨肿瘤早期病灶常小于5mm在灰度平滑的皮质骨边缘极易被漏判。这个数据集不是又一个合成或脱敏后失真的“玩具集”它来自三甲医院核医学科与骨肿瘤中心联合归档的真实临床影像切片908张训练图全部经过两位副主任医师交叉标注、再由主任医师终审校验。所有图像均保留原始DICOM窗宽窗位信息已转为8-bit PNG/JPG供直接加载标注严格遵循YOLOv5/v8/v12通用格式每个.txt文件含class_id center_x center_y width height五元组归一化到0~1区间无坐标越界、无负值、无空标签。它不解决“能不能跑通YOLO”的问题而是直击骨科AI落地最痛的点在低对比度、小目标、边界模糊的骨皮质-髓腔交界区让模型真正学会“看见”肿瘤。适合正在做骨转移筛查系统、教学用AI辅助判读模块、或需要高置信度阳性样本做few-shot微调的工程师与医工团队——别再拿肺结节数据集凑数了骨头和肺根本不是一回事。2. 数据结构解析与YOLO训练前必做的四步清洗2.1 文件组织逻辑为什么不能直接扔进datasets/就开训你解压后看到的不是整齐的images/labels/目录而是混杂的.jpg与.txt文件且文件名含rf.哈希后缀如IMG_1411_JPG_jpg.rf.491d1f54e36d3ad66239c5aa349bbadb.jpg。这是典型临床数据脱敏处理后的产物原始DICOM序列经重建→窗宽窗位调整→JPG压缩→哈希重命名目的就是切断患者ID与影像的可追溯链。但这也带来一个硬伤.txt标注文件名与图像名不完全一致。例如IMG_1411_JPG_jpg.rf.491d1f54e36d3ad66239c5aa349bbadb.jpg ↓ 对应标注文件应为 ↓ IMG_1411_JPG_jpg.rf.491d1f54e36d3ad66239c5aa349bbadb.txt但实际数据集中存在少量错配如.jpg文件存在同名.txt缺失或.txt存在但.jpg已被压缩丢帧。必须先做一致性校验。2.2 校验脚本找出“孤儿文件”与“幽灵标注”# check_consistency.py import os import glob img_dir ./raw_images # 解压后所有.jpg所在目录 label_dir ./raw_labels # 所有.txt所在目录若无此目录需先从zip中提取所有.txt # 获取所有jpg基础名去掉扩展名 img_basenames set() for img_path in glob.glob(os.path.join(img_dir, *.jpg)): basename os.path.splitext(os.path.basename(img_path))[0] img_basenames.add(basename) # 获取所有txt基础名 label_basenames set() for txt_path in glob.glob(os.path.join(label_dir, *.txt)): basename os.path.splitext(os.path.basename(txt_path))[0] label_basenames.add(basename) # 找出只有图没有标、只有标没有图的文件 only_img img_basenames - label_basenames only_label label_basenames - img_basenames print(f[INFO] 总图像数: {len(img_basenames)}) print(f[INFO] 总标注数: {len(label_basenames)}) print(f[WARN] 仅有图像无标注: {len(only_img)} 个 → {list(only_img)[:3]}...) print(f[WARN] 仅有标注无图像: {len(only_label)} 个 → {list(only_label)[:3]}...) # 输出待删除列表供人工复核 with open(orphan_files_to_review.txt, w) as f: f.write( ONLY IMAGE (NO LABEL) \n) for name in sorted(only_img): f.write(f{name}.jpg\n) f.write(\n ONLY LABEL (NO IMAGE) \n) for name in sorted(only_label): f.write(f{name}.txt\n)提示运行后若only_img或only_label非零不要直接删。打开orphan_files_to_review.txt用文本编辑器搜索nodular-fasciitis、187_JPG等关键词确认是否为同一病例不同序列如T1/T2加权导致的合理缺失。临床数据常有“一例多扫”标注可能只落在主序列上。2.3 YOLO标准目录构建train/val拆分必须按病例隔离骨肿瘤数据最忌按文件名随机打乱划分train/val——同一患者的多张影像若被拆到训练集和验证集会严重 inflate 模型指标模型记住了患者特征而非病灶特征。本数据集虽未提供原始病例ID但可通过文件名前缀推断IMG_1411_*、IMG_1409_*→ 同一检查编号1411号检查94_JPG_*、187_JPG_*→ 独立病例编号94号、187号因此train/val划分必须以前缀为单位聚类再按7:3比例分配。脚本如下# split_by_case.py import os import random import shutil from collections import defaultdict # 提取所有文件的“病例前缀”取下划线前第一段如 IMG_1411 → IMG_1411 def extract_case_id(filename): base os.path.splitext(filename)[0] return base.split(_)[0] _ base.split(_)[1] if len(base.split(_)) 2 else base.split(_)[0] img_dir ./raw_images label_dir ./raw_labels output_root ./yolo_dataset # 按case_id分组 case_to_files defaultdict(list) for img_file in os.listdir(img_dir): if not img_file.endswith(.jpg): continue case_id extract_case_id(img_file) case_to_files[case_id].append(img_file) # 划分case_id列表 all_cases list(case_to_files.keys()) random.seed(42) # 固定随机种子保证可复现 random.shuffle(all_cases) split_idx int(0.7 * len(all_cases)) train_cases set(all_cases[:split_idx]) val_cases set(all_cases[split_idx:]) # 创建目录 for split in [train, val]: os.makedirs(os.path.join(output_root, split, images), exist_okTrue) os.makedirs(os.path.join(output_root, split, labels), exist_okTrue) # 复制文件 for case_id, files in case_to_files.items(): split train if case_id in train_cases else val for img_file in files: # 复制图像 src_img os.path.join(img_dir, img_file) dst_img os.path.join(output_root, split, images, img_file) shutil.copy2(src_img, dst_img) # 复制对应txt txt_file os.path.splitext(img_file)[0] .txt src_txt os.path.join(label_dir, txt_file) dst_txt os.path.join(output_root, split, labels, txt_file) if os.path.exists(src_txt): shutil.copy2(src_txt, dst_txt) else: # 若无txt创建空文件避免YOLO dataloader报错 with open(dst_txt, w) as f: pass print(f[SUCCESS] Train cases: {len(train_cases)}, Val cases: {len(val_cases)}) print(f[SUCCESS] Train images: {len(os.listdir(os.path.join(output_root, train, images)))}) print(f[SUCCESS] Val images: {len(os.listdir(os.path.join(output_root, val, images)))})2.4 标注质量二次验证用OpenCV可视化边界框揪出“漂移框”YOLO格式虽简洁但临床标注易出现两类致命错误①坐标归一化错误center_x 1或width 1超出图像范围②边界框漂移因窗宽窗位调整病灶在JPG中位置偏移但标注仍沿用DICOM坐标。用以下脚本批量可视化一眼识别异常# visualize_boxes.py import cv2 import os import numpy as np def draw_boxes_on_image(image_path, label_path, output_path): img cv2.imread(image_path) h, w img.shape[:2] if not os.path.exists(label_path): cv2.imwrite(output_path, img) # 无标注则原图保存 return with open(label_path, r) as f: lines f.readlines() for line in lines: parts line.strip().split() if len(parts) 5: continue try: cls_id, cx_norm, cy_norm, w_norm, h_norm map(float, parts[:5]) # 归一化坐标转像素坐标 cx, cy int(cx_norm * w), int(cy_norm * h) bw, bh int(w_norm * w), int(h_norm * h) # 计算左上角 x1, y1 cx - bw//2, cy - bh//2 # 绘制矩形绿色线宽2 cv2.rectangle(img, (x1, y1), (x1bw, y1bh), (0, 255, 0), 2) # 标类别文字 cv2.putText(img, fbone-cancer, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) except ValueError: print(f[ERROR] Invalid line in {label_path}: {line}) continue cv2.imwrite(output_path, img) # 批量处理 img_dir ./yolo_dataset/train/images label_dir ./yolo_dataset/train/labels vis_dir ./yolo_dataset/train/visualize os.makedirs(vis_dir, exist_okTrue) for img_file in os.listdir(img_dir): if not img_file.endswith(.jpg): continue label_file os.path.splitext(img_file)[0] .txt draw_boxes_on_image( os.path.join(img_dir, img_file), os.path.join(label_dir, label_file), os.path.join(vis_dir, fvis_{img_file}) )运行后打开./yolo_dataset/train/visualize/重点检查所有框是否完整落在骨骼区域内若框飘到软组织或背景说明标注未随图像重采样更新小于10px的框是否清晰可见若模糊成点需确认原始DICOM分辨率本数据集原始CT层厚≤1.25mmJPG缩放后最小病灶应≥15px是否存在大量重叠框同一病灶被多人标注多次——本数据集已做去重若发现立即反馈。3. YOLOv12训练配置详解为什么必须改这5个参数3.1data.yaml路径与类别定义的临床适配YOLOv12即Ultralytics最新版要求data.yaml明确定义路径与类别。注意两点硬约束①路径必须为绝对路径或相对于ultralytics/根目录的相对路径若你在/home/user/yolo/下运行train: ../yolo_dataset/train/images是合法的②类别名必须与标注文件中的class_id严格对应本数据集仅1类class_id0故names只能是[bone-cancer]不可写[tumor]或[bone]。# bones_cancer.yaml train: ../yolo_dataset/train/images val: ../yolo_dataset/val/images test: ../yolo_dataset/val/images # 可选用于最终测试 nc: 1 # number of classes names: [bone-cancer] # class names注意YOLOv12默认使用val集做验证但临床场景必须用test集做最终报告。训练时val用于早停test用于生成ROC曲线与临床报告指标敏感度/特异度。3.2model.yaml针对小目标优化的骨干网络与Neck结构调整骨肿瘤病灶在CT中常表现为20×20像素的低对比度斑点。YOLOv12默认的yolov8n.yaml对小目标召回率不足。必须修改参数默认值骨骼癌适配值原因backboneC2f×3C2f×4增加1个stage提升浅层特征图分辨率保留更多细节neckSPPFC2f×2ASPPC2f×3ASPPAtrous Spatial Pyramid Pooling增强多尺度感受野捕获溶骨灶的弥散边缘headnn.Conv2d×3DWConvnn.Conv2d深度可分离卷积减少小目标特征稀释修改后的bones_cancer.yaml关键片段# backbone backbone: # [from, repeats, module, args] - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2 - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4 - [-1, 3, C2f, [128, True]] # 2 - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8 - [-1, 4, C2f, [256, True]] # 4 ← 新增1个C2f提升P3分辨率 - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16 - [-1, 3, C2f, [512, True]] # 6 - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32 - [-1, 3, C2f, [1024, True]] # 8 # neck neck: - [-1, 1, ASPP, [1024, [3,6,9]]] # 替换原SPPF引入空洞卷积 - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 6], 1, Concat, [1]] # cat backbone P4 - [-1, 3, C2f, [512, False]] # 9 - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 4], 1, Concat, [1]] # cat backbone P3 - [-1, 3, C2f, [256, False]] # 10 (P3/8) # head head: - [-1, 1, DWConv, [256, 3, 1]] # 深度可分离卷积保特征 - [-1, 1, nn.Conv2d, [256, 1, 1, None, 1, 1, 0]] # 1x1降维 - [-1, 1, nn.Conv2d, [3 * (1 4 1), 1, 1, None, 1, 1, 0]] # 最终输出提示ASPP模块需自行实现Ultralytics未内置代码见文末utils/ASPP.py。其核心是并行多个不同dilation_rate的3×3卷积再concat融合对成骨性病灶的毛刺状边缘尤其有效。3.3 训练命令与超参学习率、Batch Size与早停策略临床数据量小908张过大的batch size会导致梯度噪声大收敛不稳定。经实测最优配置为yolo detect train \ data./bones_cancer.yaml \ model./bones_cancer.yaml \ epochs200 \ batch8 \ # 关键GPU显存≥12GB可用12但8更稳 imgsz1280 \ # 骨盆/脊柱CT需大尺寸1280保证病灶不被下采样丢失 lr00.001 \ # 初始学习率比默认0.01低10倍防震荡 lrf0.01 \ # 余弦退火终值保留微调能力 optimizerAdamW \ # 比SGD更适应医疗数据噪声 cos_lr \ # 余弦退火 close_mosaic10 \ # 前10轮禁用mosaic防小目标被裁掉 patience30 \ # 早停耐心值设高临床数据收敛慢 device0 \ namebone_cancer_v12close_mosaic10前10轮禁用Mosaic增强因骨肿瘤病灶小Mosaic易将病灶裁出视野patience30验证集mAP0.5可能在120轮才开始爬升过早停止会丢掉最佳模型imgsz1280必须本数据集CT图像原始尺寸多为1024×1024或1280×1280设1024会导致P3特征图仅160×160小病灶在该层仅占1-2像素。3.4 验证指标解读为什么mAP0.5不够必须看mAP0.5:0.95放射科医生关心的是“模型在IoU≥0.7时还能不能准确定位”——因为IoU0.5的框可能覆盖整个椎体而IoU0.7才真正框住病灶核心。YOLOv12默认输出mAP0.5常用和mAP0.5:0.95COCO标准10个IoU阈值平均。临床部署必须以mAP0.5:0.95为准。训练日志中关注Class Images Instances Box(P R mAP50 mAP50-95): bone-cancer 380 412 0.821 0.793 0.802 0.521此处mAP50-950.521意味着在IoU从0.5到0.95均匀取10个阈值时平均精度仅52.1%。若低于0.5说明模型对精确定位能力不足需检查标注质量或增加ASPP模块权重。4. 避坑骨肿瘤检测项目里踩过的7个血泪坑4.1 现象训练loss下降快但验证mAP始终≈0原因标注文件中存在class_id1的误标本数据集应只有0YOLO将1视为新类别但验证集无此类导致分类头失效。解决用grep -r 1 ./yolo_dataset/labels/全局搜索将所有1替换为0再用awk {print $1} *.txt | sort | uniq -c确认所有标签均为0。4.2 现象推理时大量“幽灵框”空图像上出现置信度0.3的框原因imgsz1280时YOLOv12的anchor尺寸未适配骨肿瘤小目标默认anchor最大为116×116而病灶常30×30导致大anchor主导预测。解决在bones_cancer.yaml中手动设置anchorsanchors: - [10,13, 16,30, 33,23] # P3层小目标 - [30,61, 62,45, 59,119] # P4层 - [116,90, 156,198, 373,326] # P5层本数据集极少用可删重新计算anchor用utils/autoanchor.py并替换。4.3 现象同一张图CPU推理结果与GPU推理结果不一致原因YOLOv12默认启用torch.compile()但某些CUDA版本与FP16混合精度在小目标上存在数值不稳定。解决训练与推理时均添加--no-amp --no-torchscript参数强制FP32运算。4.4 现象visualize_boxes.py显示框完美但模型推理框偏移20像素原因图像预处理时LetterBox填充pad未同步应用到标注坐标。YOLO默认对图像做等比缩放pad但标注坐标未重算。解决在dataset.py中重写__getitem__确保label坐标随letterbox变换实时更新。或改用Albumentations做几何变换推荐。4.5 现象验证集mAP在150轮后突然暴跌从0.79→0.31原因cos_lr退火至末端时学习率过低模型陷入局部极小而patience30未触发早停。解决监控val/box_loss若连续10轮上升手动终止并加载best.pt或改用ReduceLROnPlateau调度器。4.6 现象导出ONNX后推理结果与PyTorch不一致原因ONNX opset版本兼容性问题YOLOv12默认用opset17但部分医疗设备嵌入式GPU仅支持opset11。解决导出时指定--opset 11并在ONNX Runtime中禁用enable_mem_patternFalse。4.7 现象部署到医院PACS终端后CPU占用率100%延迟3s/图原因未启用--half半精度且未关闭--dnnOpenCV DNN后端在医疗设备上性能差。解决推理命令加--half --device cpu并确保OpenCV≥4.8.0修复ARM CPU上的AVX指令bug。5. 临床级推理与报告生成把模型输出变成医生能看懂的PDF5.1 推理脚本增强输出带病灶坐标的DICOM元数据映射医院PACS系统要求结果必须关联原始DICOM的StudyInstanceUID与SeriesInstanceUID。本数据集虽已转JPG但文件名隐含线索IMG_1411_JPG_jpg...中的1411即为检查号。我们用以下脚本生成可溯源的JSON报告# infer_with_dicom_link.py import cv2 import json import numpy as np from ultralytics import YOLO model YOLO(./runs/detect/bone_cancer_v12/weights/best.pt) results model.predict( source./yolo_dataset/val/images, conf0.25, iou0.45, saveFalse, verboseFalse ) report [] for r in results: img_name r.path.split(/)[-1] # 从文件名提取检查号IMG_1411_JPG_jpg... → 1411 study_id img_name.split(_)[1] if len(img_name.split(_)) 1 else UNKNOWN boxes r.boxes.xyxy.cpu().numpy() # [x1,y1,x2,y2] confs r.boxes.conf.cpu().numpy() classes r.boxes.cls.cpu().numpy() # 转为DICOM坐标系左上角为原点y轴向下 detections [] for i, (box, conf, cls) in enumerate(zip(boxes, confs, classes)): x1, y1, x2, y2 box # 计算中心与宽高供PACS系统解析 cx, cy int((x1x2)/2), int((y1y2)/2) w, h int(x2-x1), int(y2-y1) detections.append({ bbox: [int(x1), int(y1), int(x2), int(y2)], center: [cx, cy], size: [w, h], confidence: float(conf), class: bone-cancer }) report.append({ image_filename: img_name, study_instance_uid: f1.2.840.113619.2.55.{study_id}.123456789, # 伪UID实际需对接PACS API detections: detections, total_detections: len(detections) }) # 保存为JSON with open(./inference_report.json, w) as f: json.dump(report, f, indent2)5.2 PDF报告生成用ReportLab绘制带热力图的临床摘要医生不看坐标要看“L3椎体右侧椎弓根见1枚3mm溶骨灶”。我们用ReportLab生成带病灶热力图的PDF# generate_clinic_pdf.py from reportlab.lib.pagesizes import letter from reportlab.pdfgen import canvas from reportlab.lib.utils import ImageReader import json import numpy as np def draw_heatmap_on_pdf(pdf_canvas, img_path, detections, page_width, page_height): # 加载原图 img ImageReader(img_path) pdf_canvas.drawImage(img, 50, page_height-400, width500, height400, maskauto) # 在图上画红框与文字 for det in detections: x1, y1, x2, y2 det[bbox] # 坐标系转换PDF原点在左下图像原点在左上 pdf_y1 page_height - 400 - y2 50 pdf_y2 page_height - 400 - y1 50 pdf_canvas.setStrokeColorRGB(1, 0, 0) pdf_canvas.setLineWidth(2) pdf_canvas.rect(50x1, pdf_y1, x2-x1, pdf_y2-pdf_y1) # 标注文字 pdf_canvas.setFont(Helvetica, 10) pdf_canvas.drawString(50x1, pdf_y1-15, fBone-cancer ({det[confidence]:.2f})) # 主函数 c canvas.Canvas(./bone_cancer_report.pdf, pagesizeletter) width, height letter # 读取JSON报告 with open(./inference_report.json) as f: report json.load(f) for i, item in enumerate(report[:5]): # 仅生成前5例 if i 0: c.showPage() c.setFont(Helvetica-Bold, 16) c.drawString(50, height-50, fClinical Report: Study {item[study_instance_uid]}) c.setFont(Helvetica, 12) c.drawString(50, height-70, fTotal detections: {item[total_detections]}) # 绘制热力图 img_path f./yolo_dataset/val/images/{item[image_filename]} draw_heatmap_on_pdf(c, img_path, item[detections], width, height) c.save() print([SUCCESS] PDF report generated: bone_cancer_report.pdf)生成的PDF每页含✅ 原始JPG影像保持临床观感✅ 红色边界框置信度标签✅ 顶部显示检查号与总病灶数✅ 兼容医院PDF阅读器无JS/动态内容5.3 模型置信度校准用Platt Scaling让0.85真正等于85%概率YOLO输出的conf不是概率而是分类得分。临床决策需真实概率。用验证集做Platt Scalingfrom sklearn.calibration import CalibratedClassifierCV from sklearn.svm import SVC import numpy as np # 提取验证集所有预测置信度与真实标签 all_confs [] all_labels [] for r in val_results: # val_results from model.val() all_confs.extend(r.boxes.conf.cpu().numpy()) all_labels.extend([1]*len(r.boxes.conf)) # 全为正样本但需混入负样本用背景图 # 实际中需采集100张无病灶的正常骨图像作为负样本 # 此处简化用conf0.1的预测作为负样本代理 calibrator CalibratedClassifierCV(SVC(), methodsigmoid) # 训练校准器需正负样本 # calibrator.fit(X_train, y_train) # X_train[[conf1],[conf2],...], y_train[1,0,1,...] # conf_calibrated calibrator.predict_proba([[0.85]])[:,1]血泪经验没做校准的模型当conf0.9时真实阳性预测值PPV仅62%校准后可达89%。这是向医院提交算法备案时监管方必查项。从那以后我每次交付骨肿瘤检测模型都强制走一遍visualize_boxes.pyinference_report.jsonPlatt校准三件套——不是为了炫技是让放射科主任第一次打开PDF时能指着L3椎体那个红框说“就是这儿跟我的判断一样。”希望帮到你。本文还有配套的精品资源点击获取
返回列表