
简介面向医学影像分析与深度学习语义分割任务该X射线骨骼关节图像数据集提供了训练、验证与测试划分其中训练集约1100张、验证集约100张适合训练像素级分割模型或进行迁移学习。资源共2000个文件以1185个PNG和813个JPG图像为主体另附1个类别相关txt与1个Python工具脚本整体压缩包仅12MB轻量便捷。已有151人学习属于轻量级医学影像数据集可直接用于分割实验。对于从事骨骼影像分析的研究者或初学者可利用该数据快速搭建分割流程验证模型在X光图像上的表现并通过类别文件和脚本了解数据组织方式便于后续扩展自己的训练管道。整套内容完整适合作为课程设计、论文实验或算法对比的基准数据。1. 骨骼X光关节分割一个把灰度图当语义分割做的数据集把这个X射线骨骼关节分割数据集下载下来你会看到一堆472_png.rf.44371cac8276ef31c203b35751e81c71.jpg这种Roboflow导出命名的文件真正的掩码不在PNG里而在同名txt里每行记录一组归一化多边形坐标。这是一套典型的小样本医疗语义分割数据3个骨骼关节相关类别训练图像约1100张、验证集约100张、测试集按要求单独划分具体类别名以json类别文件为准。X光图比自然图像难分割得多——骨骼边缘是骨密度在投影方向上的连续衰减软组织与骨骼灰度高度重叠肉眼都难界定的边界模型更容易在关节间隙处输出碎块。适合它的读者是正在做骨科AI诊断前置模块的工程师也可以是从零训练第一个语义分割模型的深度学习入门者下面所有代码都能直接复现。2. Roboflow导出的txt多边形标签先转成PyTorch能用的语义掩码2.1 从.rf.命名反推数据集结构与标签格式文件名里的.rf.是Roboflow导入导出时留下的版本标记472_png是原始样本编号后半段哈希是本次导出会话标识同名jpg与txt一一对应。常规Roboflow导出结构会把数据按images/train、labels/train、images/test等目录组织训练、验证、测试的划分已在摘要中明确训练约1100张验证约100张测试集另计。解压后的第一件事是把目录完整列出来做一次文件名比对缺标签的样本直接剔除否则训练时DataLoader会读到空列表报错信息又恰好指向不明排查起来非常耗时间。路径内容说明images/train/*.jpgX射线原图灰度图关节区域占画面主体labels/train/*.txt同名标签每行一个实例的多边形data.yaml / json类别定义类别数3ID从0开始递增txt标签是YOLO segmentation格式一行结构是类别ID x1 y1 x2 y2 ...坐标为相对图像宽高的归一化浮点数。多个顶点依次相连构成封闭多边形覆盖一个关节实例。因为坐标做了归一化解析时必须要用原图尺寸还原像素坐标这是新手最容易出错的地方。如果数据里有部分样本是用矩形框标注的多边形只有4个顶点解析逻辑同样适用只是掩码边缘会比人工多边形略微生硬。这正好呼应了语义分割数据集如何制作这条路上最常见的坑标注格式和训练框架不对齐。2.2 解析归一化多边形并绘制真正的掩码import cv2 import numpy as np from pathlib import Path def parse_yolo_seg_txt(txt_path: Path, img_w: int, img_h: int, min_poly_area: float 25.0): masks, class_ids [], [] lines txt_path.read_text().strip().splitlines() for line in lines: parts line.strip().split() if len(parts) 7: # 类别ID 至少3个坐标点 continue cls_id int(parts[0]) xy np.asarray(parts[1:], dtypenp.float32).reshape(-1, 2) xy[:, 0] * img_w # 归一化坐标还原为像素x xy[:, 1] * img_h # 归一化坐标还原为像素y if cv2.contourArea(xy.astype(np.int32)) min_poly_area: continue # 面积过小视为标注噪点 mask np.zeros((img_h, img_w), dtypenp.uint8) cv2.fillPoly(mask, [xy.astype(np.int32)], 1) masks.append(mask) class_ids.append(cls_id) return masks, class_idssplit()按连续空白字符切分能兼容空格或逗号分隔的txtreshape(-1, 2)要求坐标点总数是偶数解析前可以先打印一行原始数据确认有没有尾随空格或空行。cv2.contourArea计算的是多边形面积过滤掉极小噪点这个阈值在X光图上一般设在25~100像素之间具体看原图分辨率。fillPoly在[H, W]的零矩阵上原地绘制掩码值设为1多个实例的合并放到Dataset层处理解析函数保持单实例职责更清晰。2.3 组装BoneJointSegDataset并把同类别实例合并一个关节类别在同一图像里可能出现多个实例比如双侧同名关节而语义分割要求每个像素只有一个类别ID所以同一类别的所有mask要在这里合并import torch from torch.utils.data import Dataset class BoneJointSegDataset(Dataset): def __init__(self, image_dir, label_dir, num_classes3): self.image_paths sorted( p for p in Path(image_dir).glob(*.jpg) if (Path(label_dir) / (p.stem .txt)).exists() ) self.label_dir Path(label_dir) self.num_classes num_classes def __len__(self): return len(self.image_paths) def __getitem__(self, idx): img_path self.image_paths[idx] image cv2.imread(str(img_path), cv2.IMREAD_GRAYSCALE) h, w image.shape[:2] masks, class_ids parse_yolo_seg_txt( self.label_dir / (img_path.stem .txt), w, h ) seg_label np.zeros((h, w), dtypenp.int64) for cls_id, mask in zip(class_ids, masks): seg_label[mask 0] int(cls_id) # 覆盖式合并同类别实例 image torch.from_numpy(image).unsqueeze(0).float() / 255.0 seg_label torch.from_numpy(seg_label) return image, seg_labelseg_label[mask 0]直接覆盖赋值如果同一像素被两个不同类别命中后一个会覆盖前一个这类标注重叠需要打开原图人工确认是绘制错误还是关节自然嵌合。灰度图unsqueeze(0)变成[1, H, W]除以255归一化到[0,1]区间标签保持int64类型供CrossEntropyLoss使用。注意千万不要把txt路径直接传给PyTorch语义分割模型YOLO系模型原生支持txt但大多数语义分割框架只认掩码图必须先完成上面这一步转换。3. 训练路线选择YOLOv8-seg与DeepLabV3的取舍3.1 先回答三个问题再定方案什么样的任务适合用yolov8训练自己的数据集什么样的场景该上deeplabv3语义分割关键看三点。第一如果同一张图里出现两个同类别实例后续流程是否需要区分“左侧关节”和“右侧关节”需要区分就选YOLOv8-seg它输出天然带实例ID。第二如果下游只是拿关节掩码区域做特征统计DeepLabV3直接输出像素级类别概率后处理更少。第三训练成本与预期精度YOLOv8预训练权重来自COCO实例分割与X光场景差异大但胜在框架收敛快、评估链路完整DeepLabV3的ASPP空洞空间金字塔池化对低对比度边缘建模更强关节间隙处的连续性往往更好。这个数据集本身是多实例多边形标注两条路线都能跑通我建议先跑DeepLabV3做基线再用YOLOv8-seg对比实例级效果差距。3.2 路线AYOLOv8-seg直接吃txt标签安装ultralytics后segment子命令原生支持上面解析过的YOLO分割txt格式只需要准备一个data.yaml不需要写Dataset类。# data.yaml path: /data/bone-joint-xray train: images/train val: images/valid test: images/test nc: 3 names: 0: joint_a 1: joint_b 2: joint_cnames的值必须与json类别文件一一对应顺序错位时表现为预测类别和掩码错位但训练loss依然下降最容易漏掉。训练命令如下yolo segment train \ datadata.yaml \ modelyolov8s-seg.pt \ epochs120 \ imgsz640 \ batch8 \ device0yolov8s-seg.pt是s尺寸权重关节这类边缘精细目标不建议用n模型它容易在高噪声X光上把软组织误判成骨骼显存有余量就升级yolov8m-seg.pt。imgsz640适配关节占画面主体的场景原图分辨率特别高时先看一眼缩略图确认关节边缘是否仍然清晰。训练完用yolo segment val datadata.yaml modelruns/segment/train/weights/best.pt跑验证会自动输出每个类别的mAP50与分割掩码IoU。3.3 路线BDeepLabV3用smp库快速复现pip install segmentation-models-pytorch后按照语义分割模型的标配流程组装即可。DeepLabV3的亮点在解码器端的ASPP模块以不同膨胀率的空洞卷积并行抓取多尺度上下文很适合骨密度渐变的X光灰度图像import segmentation_models_pytorch as smp import torch model smp.DeepLabV3Plus( encoder_nameresnet50, encoder_weightsimagenet, classes3, activationsoftmax2d, ) criterion torch.nn.CrossEntropyLoss() optimizer torch.optim.AdamW(model.parameters(), lr1e-4)smp默认输入三通道RGB而X光数据集是单通道常见做法是把灰度通道复制成三份喂给ImageNet预训练编码器。预训练权重通常能让模型前30轮训练稳定掉loss但X光与自然图像特征差异大验证指标一直不涨时就删掉encoder_weights从头训练。训练循环里补上通道复制for images, masks in train_loader: images images.repeat(1, 3, 1, 1).to(device) # [B,1,H,W] - [B,3,H,W] logits model(images) # [B,3,H,W] loss criterion(logits, masks.to(device)) optimizer.zero_grad() loss.backward() optimizer.step()repeat(1, 3, 1, 1)在通道维上复制三次batch维、高宽维保持不变。activationsoftmax2d已经内置Softmax推理时直接用logits.argmax(dim1)取类别ID。两条路线的差异总结如下对比项YOLOv8-segDeepLabV3 (smp)输出粒度实例掩码类别像素级类别概率标签格式原生支持txt需转成掩码图预训练来源COCO实例分割ImageNet分类代码量低yaml即训需自写训练循环边缘平滑性依赖后处理ASPP天然平滑测试集只有理论上独立的一份数据最终评估要以它为准不要反复拿验证集调epoch100张验证图的随机涨跌噪声足以掩盖真实精度差距。4. 灰度投影图的增强策略与关节掩码不均衡处理4.1 增强不是越多越好先把灰度语义想清楚自然图像分割常用的颜色抖动、HSV扰动在X光数据集上要慎用。X光灰度反映组织密度投影骨骼亮、软组织暗是物理量纲而非图像风格。把对比度拉得过强模型会把软组织灰阶误判成骨骼边缘——大量入坑者在关节间隙处得到大片伪掩码根因就是这个。几何类增强安全得多水平翻转对骨骼关节特别有效人体左右解剖结构基本对称旋转控制在±15°内超过这个范围关节相对位置关系失真平移与缩放配合可模拟拍摄距离和摆位差异。垂直翻转在解剖学上没有对应操作建议关闭。4.2 用albumentations组装医疗影像增强管线albumentations会同步变换图像与掩码Compose里的几何增强自动作用于两者这是它比手写torchvision变换省事的地方import albumentations as A import cv2 train_transform A.Compose([ A.HorizontalFlip(p0.5), A.Rotate(limit15, border_modecv2.BORDER_CONSTANT, value0, p0.6), A.ShiftScaleRotate(shift_limit0.05, scale_limit0.12, border_modecv2.BORDER_CONSTANT, value0, p0.5), A.RandomBrightnessContrast(brightness_limit0.15, contrast_limit0.15, p0.5), A.GaussNoise(var_limit(10.0, 30.0), p0.3), A.Resize(512, 512), ], is_check_shapesTrue)BORDER_CONSTANT配合value0让旋转和平移产生的空白区域呈黑色与X光黑底背景一致避免白边被模型学成高密度骨骼特征。GaussNoise的方差按0~255灰度级设置10到30对应中等噪声模拟低剂量X光采集时的量子噪声。Resize放最后保证输出尺寸统一缩放前不要先做其他会改变尺寸的变换否则标签和图像会错位。4.3 掩码占比差异大混合损失函数兜底不同类别在图像上的面积占比差异明显关节掩码整体可能只占全图的3%~20%。纯CrossEntropyLoss被大面积背景主导训练出的模型会把关节边缘往里收缩宁可少预测也不多预测纯DiceLoss在小目标上梯度震荡大验证loss忽高忽低。常见做法是两者按1:0.5加权组合import torch import torch.nn as nn import torch.nn.functional as F class DiceLoss(nn.Module): def __init__(self, smooth: float 1e-5): super().__init__() self.smooth smooth def forward(self, logits, targets): probs torch.softmax(logits, dim1) _, c probs.shape[:2] target_onehot F.one_hot(targets, num_classesc).permute(0, 3, 1, 2).float() intersection (probs * target_onehot).sum(dim(2, 3)) union probs.sum(dim(2, 3)) target_onehot.sum(dim(2, 3)) dice (2.0 * intersection self.smooth) / (union self.smooth) return 1.0 - dice.mean() criterion DiceLoss() 0.5 * nn.CrossEntropyLoss()F.one_hot把[B,H,W]的标签转成[B,H,W,C]permute(0,3,1,2)调整成与logits一致的[B,C,H,W]。smooth参数防除零全背景样本同样适用。训练时留意两类loss的量级DiceLoss长期在0.8以上不降说明类别不均衡严重应提高CE权重CE很低而Dice波动大则是边缘预测与标注存在系统性偏移这类偏移纯CE下看不出来正是混合损失的监控价值。5. 用IoU和Dice校准模型评估、过拟合识别与落地技巧5.1 验证集逐类统计IoU与Dicedef compute_metrics(pred: torch.Tensor, gt: torch.Tensor, num_classes: int 3): ious, dices [], [] for c in range(num_classes): p (pred c) g (gt c) inter (p g).sum().item() union (p | g).sum().item() ious.append(inter / max(union, 1)) dices.append(2.0 * inter / max(p.sum().item() g.sum().item(), 1)) return ious, dices验证循环里逐batch累计全部算完再求平均得到每个类别的IoU与DicemIoU取三类均值。关节类别在IoU上的数值通常会低于Dice因为IoU对漏检更敏感报告模型效果时建议两个指标都写。5.2 过拟合的三种信号训练loss下降而验证mIoU连续5轮不升优先怀疑过拟合回退epoch或加大几何增强强度训练mIoU与验证mIoU差距超过0.15为明显过拟合直接取倒数第二或第三轮权重往往更实用预测图出现细碎孔洞而训练集掩码边缘光滑多为标注噪声被模型放大用连通域过滤即可缓解。5.3 三个低成本提分技巧第一水平翻转TTA。推理时把输入翻转一次同一个模型把softmax概率翻回来与原输出平均关节对称性让这个操作直接提升边缘稳定性flip_logits model(image.flip(-1)).flip(-1) final_logits (logits flip_logits) / 2第二连通域过滤。cv2.connectedComponents去除面积小于100像素的前景块细小伪掩码大多落在关节间隙噪声区num, labels cv2.connectedComponents(pred_mask.astype(np.uint8)) for lbl in range(1, num): if np.sum(labels lbl) 100: pred_mask[labels lbl] 0第三半精度推理。model.half()配合torch.no_grad()在CUDA上提速约1.8倍mIoU下降通常小于0.005批量后处理场景下值得先对比一次精度再决定是否启用。本文还有配套的精品资源点击获取