
简介基于Python与YOLOv5实现的手骨骨龄检测项目是一套面向毕业设计、课程设计和项目开发的完整工程资料适合具备深度学习基础、希望快速搭建目标检测与分类全流程的开发者内容完整、开箱可用。资源共189个文件涵盖Python脚本、YAML配置、模型权重、训练脚本与项目文档等压缩包约436MB目录结构清晰便于按模块复用。项目将数据处理拆分为手骨与关节两条流程针对手骨图像雾感问题采用直方图均衡化预处理并通过图像增强将数据集扩充后按比例切分检测模型基于YOLOv5配置分类模型采用ResNet18多个关键关节部位测试准确率均达90%以上文档还说明了环境配置、训练参数调整与测试步骤视频演示直观展示运行效果。目前已有近200人学习下载适合用于工程实践和课程设计答辩前冲刺。1. 手骨骨龄检测为什么值得用 yolo 方案做左手腕 X 光片摆在你面前儿科内分泌大夫要根据骨骺发育程度判断孩子还能长多高、需不需要干预生长激素。传统做法是翻开图谱一张一张对照一个熟练医生看一张片子也要一两分钟碰到边界模糊的骨骺线还会犹豫很久。基于 python 和 yolov5 的手骨骨龄检测项目就是把这套人工阅片流程拆成「先检测骨化区域、再回归骨龄」的两段式自动化方案。yolov5 在这里不是直接输出“几岁几个月”而是负责把桡骨、尺骨、各节指骨的骨化中心准确定位出来再用一个轻量回归网络把检测到的区域换算成骨龄。这个项目非常适合毕业设计或课程设计因为它既有目标检测的完整流程又有医学回归任务的业务深度数据公开可获取训练成本可控做出来还能演示成一套带界面的小系统。2. 骨龄评估为什么不能硬分类yolov5 在任务里的真实分工2.1 业务原理GP 图谱法和 TW3 法给 AI 方案的启发骨龄检测不是拿一张片子扔给模型让它猜年龄那么简单。临床上最常用的 GP 图谱法是把待评估的 X 光片与标准骨龄图谱逐张比对找到最接近的那一档从而给出骨龄。TW3 法则更精细它把腕骨、桡骨、尺骨、指骨分成若干评估区域对每个骨化中心单独打分再加权累加成最终骨龄。你会发现后者天然就是一个“区域检测 局部评估”的流程。做 AI 方案最忌把任务定义成图片分类因为整张 X 光片里真正有用的信息散布在各个骨化中心背景干扰非常多而且骨龄是连续值用离散的年龄类别去做分类边界上一定打架。所以最稳的做法是先让目标检测模型把各个骨化中心框出来再对每个区域做特征提取和回归。yolov5 在这个方案里扮演的就是“找区域”的角色也就是两段式 pipeline 的前半段。2.2 选型理由yolov5 对比 Faster R-CNN、ResNet 直接回归和 ViT很多人会问为什么不直接用 ResNet 把整张片子回归成骨龄也不是不行RSNA 骨龄挑战赛的不少前排方案就是端到端 CNN 回归但那需要很大的数据量和很深的调参功夫。对毕设和课程设计来说时间有限、算力有限端到端方案虐你千百遍还找不到问题在哪因为特征分布完全是个黑匣子。用 yolov5 做检测的好处是网络结构清晰训练过程可视化程度高出框结果能直接画在图上让评审老师一眼看懂模型到底“看”了哪里。相比 Faster R-CNNyolov5 训练速度快一个量级显存占用小得多一台 8G 显存的消费级显卡就能跑相比 ViT 这类 transformer 检测器yolov5 的部署生态成熟不用自己从头写后处理逻辑。所以我的结论很直接除非你的课题叫“基于 transformer 的骨龄检测研究”这种有明确创新点要求的否则 yolov5 是性价比最高的选择。顺便提醒一句yolov5 官方仓库现在已经进入维护状态新项目也可以考虑 yolov8 或 yolov11代码接口和训练流程几乎平移后面我会讲迁移的边界。2.3 整体架构检测、裁剪、回归三件事各管各的整个项目的流程在动手写代码之前就要定下来我建议这么拆第一步用 yolov5 对左手 X 光片做目标检测类别可以是“腕骨-桡骨-尺骨-指骨”这几个大区域也可以是更细的单块骨化中心第二步根据检测框把对应区域从原图上裁剪出来第三步把裁剪图缩放到固定尺寸送进一个轻量 CNN 回归网络输出骨龄单位是月。这三件事各管各的任何一环出了问题都能单独排查不会出现“整体效果差但不知道差在哪”的窘境。有的方案试图改造 yolov5 的检测头直接输出骨龄但那种做法要把回归分支并进检测头涉及 loss 改写和标签重定义训练时收敛很不稳定我身边做过的人都劝退。按检测回归的老实方案走每一阶段的精度都可控项目文档也好写答辩时逻辑链是完整的。3. 数据集与标注格式从 RSNA 骨龄数据到 yolov5 能吃的 txt3.1 数据来源RSNA 骨龄数据集与标注方式骨龄检测最常用的公开数据集是 RSNA Pediatric Bone Age Challenge全称很长你直接搜这几个词就能找到。它包含约 1.2 万张左手 X 光片每张图标注了性别和骨龄以月为单位数据质量比你自己跑医院收集要规范得多而且这些图本身就是左手后前位标准投照非常接近临床真实场景。需要注意的一点是这个数据集只有骨龄和性别的标注没有检测框。yolov5 训练需要每张图对应的框坐标文件所以原始数据不能直接喂进去得先做目标框标注。常见做法是先用 LabelImg 或 X-AnyLabeling 人工标注几百张图训练一个初版检测模型再用这个模型对剩余数据做伪标注最后人工抽检修正。这套半自动标注流程能省下大量时间我在做类似医疗影像项目时一直都在用。3.2 标注格式转换XML 转 yolov5 的 txt 并附带归一化计算标注软件默认导出的是 Pascal VOC 格式的 XML 文件里面记录的是每个目标的左上角坐标和右下角坐标。yolov5 训练需要的是 txt 文件每一行代表一个目标格式是“类别编号 中心点x 中心点y 框宽 框高”注意所有值都要除以图片宽高做归一化。下面这个脚本就是干这件事的遍历标注目录把 XML 里的坐标换算成 yolo 格式并写入同名 txt 文件。import xml.etree.ElementTree as ET import os def convert_xml_to_yolo(xml_path, out_dir, class_map): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) txt_name os.path.splitext(os.path.basename(xml_path))[0] .txt lines [] for obj in root.iter(object): cls obj.find(name).text if cls not in class_map: continue cls_id class_map[cls] box obj.find(bndbox) x_min float(box.find(xmin).text) y_min float(box.find(ymin).text) x_max float(box.find(xmax).text) y_max float(box.find(ymax).text) x_center (x_min x_max) / 2.0 / img_w y_center (y_min y_max) / 2.0 / img_h w (x_max - x_min) / img_w h (y_max - y_min) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(lines)) class_map {radius: 0, ulna: 1, carpal: 2, phalanx: 3} convert_xml_to_yolo(annotations/sample.xml, labels/, class_map)逻辑上这个脚本的核心就是 XML 的解析和坐标归一化两步。要注意几个容易踩坑的点一是图片宽高是从 XML 里的 size 节点读的如果 XML 没写或者写错归一化全错训练时边界框会飞掉二是类别编号从 0 开始yolov5 的 data yaml 文件里类别顺序要和这个编号严格一致三是有些标注软件会把难例标成“difficult”脚本里没有过滤如果你不希望难例参与训练得在遍历 object 时加一个 difficult 节点的判断。3.3 数据集划分与增强策略验证集不能省增强别乱翻数据集划分上我一般按 8:1:1 切训练集、验证集和测试集并且用固定随机种子来切保证每次实验的可复现性。RSNA 数据集本身男女比例均衡但你要检查一下骨龄分布是否均匀如果某个年龄段样本特别少模型在那个区间的误差会明显偏大。增强策略上yolov5 默认会做 mosaic、随机仿射变换、色彩抖动等操作这些对自然图像很好用但医疗 X 光片要小心。最关键的是一条红线不要做水平翻转。左手后前位片翻转后变成右手解剖结构骨龄虽然大致一样但骨骺形态和骨化中心位置的医学语义完全不同会让模型学到错误的位置先验。颜色抖动幅度也要调小X 光片的灰度对比度是诊断信息所在把密度对比破坏掉骨化中心的边界就糊了。我建议在 hyp 超参数文件里把 hsv_h、hsv_s、hsv_v 全调低平移和缩放可以保留旋转角度限制在 ±10 度以内。这些参数看起来很细但它们直接决定了模型在真实场景下的鲁棒性。4. 用 python 训练手骨骨龄检测模型从环境配置到骨龄回归4.1 环境与目录结构python 版本卡住后面能少踩一半坑环境问题永远是第一个拦路虎。yolov5 官方仓库对 python 版本有隐性的兼容边界我踩过的血泪组合是 python 3.11 装旧版 torch 直接编译报错yolov5 里有些算子对新版本 torch 也不兼容。建议用 python 3.8 或 3.10 创建虚拟环境torch 用 1.13 或 2.x 早期版本都比较稳妥。cuda 版本对应好 torch 的 wheel 包用 conda 装最省心。项目目录结构我习惯这么摆datasets/bone 下面分 images 和 labels分别放训练图片和 txt 标注文件images 里再按 train/val/test 三个子目录分好yolov5 仓库代码放在独立目录里不要和数据集混在一起这样训练日志和权重文件输出位置都很清晰答辩时演示目录结构也好看。4.2 训练命令与超参数yolov5 训练自己的数据集就这么调数据集准备好之后先写一个 data yaml 文件指定路径和类别名。然后从官方仓库拉代码、安装依赖一切就绪后执行下面的训练命令。这是整篇最核心的可复现步骤直接抄作业。cd yolov5 pip install -r requirements.txt python train.py \ --data ../datasets/bone/bone.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --hyp hyp.scratch-low.yaml \ --project ../runs/bone_age \ --name exp1 \ --seed 42这里每个参数都有讲究。weights 用 yolov5s.pt 是迁移学习把 COCO 预训练权重当作初始参数它已经学好了通用边缘和纹理特征X 光片虽然和自然图像分布不同但底层特征可以复用训练收敛速度和最终精度都比随机初始化高一个档次。img 640 是输入分辨率如果你的显存只有 8Gbatch 16 已经接近上限了显存不够时优先把 batch 降到 8不要直接降分辨率。hyp 用了 hyp.scratch-low.yaml 这个低增强配置原因我在 3.3 里说过医疗影像的增强保守一点更安全。epochs 100 是给足训练时间实际到 60 轮左右基本稳定但多跑一些方便观察过拟合拐点。训练结束后weights/best.pt 就是验证集表现最好的权重答辩演示就用它。4.3 骨龄回归头把检测框裁出来再回归代码不长但逻辑要对检测模型训练完成后接下来实现骨龄回归网络。这个网络不需要很大输入是检测框裁剪出来的单区域图像输出是一个标量——骨龄月数。我用 ResNet18 做特征提取器把最后一层全连接改成单节点输出。训练时 loss 用 SmoothL1Loss因为它对离群点不像 MSE 那么敏感骨龄标注本身有一定主观性个别偏差大的样本不该把模型带偏。import torch import torch.nn as nn import torchvision.models as models class BoneAgeRegressor(nn.Module): def __init__(self): super().__init__() base models.resnet18(pretrainedTrue) base.fc nn.Linear(512, 1) self.base base def forward(self, x): return self.base(x).squeeze(-1) model BoneAgeRegressor().cuda() criterion nn.SmoothL1Loss() optimizer torch.optim.Adam(model.parameters(), lr1e-4)训练这个回归头的输入数据是“裁剪图 骨龄标签”的配对。训练循环里要注意不要用整张图训练而是用检测框先裁出区域这样才能让回归网络聚焦在骨化中心的纹理细节上。裁剪时建议往外扩 10% 的边界给网络一点上下文信息太贴着框反而丢失形状特征。数据量上RSNA 有一万多张图一张图能裁出 4 到 5 个有效区域回归头的训练集是足够富余的。如果你的检测框类目划分更细比如按掌骨和指骨逐节标注那区域更多回归头的训练数据更充分。4.4 推理与后处理检测、裁剪、回归、可视化一条龙模型训完之后写一个推理脚本把整条链路串起来。流程是读入 X 光片用 yolov5 检测所有骨化区域对每个检出的框做置信度过滤然后裁剪并缩放到 224×224 输入回归网络得到每个区域的骨龄预测值最后把所有区域的预测值取加权平均作为最终骨龄。下面这段代码就是完整的推理逻辑注意 yolov5 加载方式和后处理参数的设置。import torch import cv2 from yolov5.models.experimental import attempt_load from yolov5.utils.general import non_max_suppression det_model attempt_load(weights/best.pt, map_locationcpu) reg_model torch.load(weights/reg_best.pth, map_locationcpu) img cv2.imread(test.jpg) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) results det_model(img_rgb, size640) pred non_max_suppression(results, conf_thres0.25, iou_thres0.45) age_list [] for det in pred[0]: x1, y1, x2, y2 map(int, det[:4]) crop img_rgb[y1:y2, x1:x2] crop cv2.resize(crop, (224, 224)) crop_tensor torch.from_numpy(crop).permute(2,0,1).unsqueeze(0).float() / 255.0 with torch.no_grad(): age_month reg_model(crop_tensor).item() age_list.append(age_month) final_age sum(age_list) / len(age_list) print(fBone age: {final_age/12:.1f} years)推理脚本里有三个细节值得说。第一non_max_suppression 的 conf_thres 对医疗检测任务建议设低到 0.2 左右因为骨化中心边界对比度低置信度普遍不如自然图像的目标那么高宁可多框出来几个再做回归平均也不能漏检。第二回归结果直接取平均不是最优方案更好的做法是按区域加权比如腕骨区域权重高一些远端指骨权重低一些这个权重可以从验证集上用线性回归拟合出来。第三如果一张图里有某个区域没被检测出来最终结果不会崩得太离谱但要是三个以上区域漏检平均的结果就会飘所以检测模型的召回率是这个 pipeline 的命门。5. 手骨骨龄检测项目避坑指南5 条高频踩坑记录5.1 坑点一把骨龄任务做成分类预测值集中在几个整数档位现象训练的模型 loss 降到了挺好看的数字但输出的骨龄永远是几个离散值比如 12.0、13.0、14.0 这样完全不像连续回归。原因标签是月份整数如果用了 CrossEntropyLoss 把它当成多分类任务模型学的就是档位概率分布天然输出离散值。解决严格使用回归头标签保持月份数值loss 用 SmoothL1Loss 或 HuberLoss。这里要特别提醒一句RSNA 原始标签虽然是整数月但回归模型的输出本来就是连续浮点数不需要人为取整评价时再按四舍五入算误差。5.2 坑点二640 分辨率丢失骨纹理细节精度卡在瓶颈上现象检测框很准mAP 到了 0.9 以上但骨龄回归误差始终在 18 个月左右降不下去。原因骨化中心内部的致密骨纹理非常细腻640×640 的输入对整张手骨片来说每个骨化区域只有几十个像素的信息量特征根本不够。解决训练检测时用 640 没有大问题但回归头的训练要用更高分辨率的裁剪图建议裁剪后统一缩放到 224×224 以上能到 320 更好。如果显存吃紧就分块推理先检测出区域再从原图上高分辨率裁剪这是代价最小且效果最明显的提升手段。5.3 坑点三验证集指标虚高换一批真实场景图片立刻翻车现象在 RSNA 测试集上 MAE 只有 10 个月拿自己手机拍的或者医院 PACS 导出的片子一测误差涨到 25 个月以上。原因数据分布不一致。RSNA 的片子全部是标准化左手后前位亮度均匀而真实场景可能有铅衣遮挡、儿童摆位不正、胶片曝光差异。解决增强策略里加入 ±10 度随机旋转和轻微缩放模拟摆位偏差用 CLAHE 对比度增强做预处理降低曝光差异的影响如果条件允许从科室收集几十张真实片子在推理链路末端做人工验证不要只在标准数据集上自嗨。5.4 坑点四检测模型的置信度阈值习惯性设太高现象看检测结果可视化时骨化中心边界模糊的区域时有时无但整图效果挺干净。原因医疗 X 光片骨化中心对比度天然低模型输出置信度普遍只有 0.3 到 0.5默认的 conf_thres 0.5 会滤掉大量真实的框。解决推理时把 conf_thres 降到 0.2IOU 阈值保持 0.45 不变。代价是可能多出几个误检框但回归阶段会对所有框的结果做平均个别误检区域对最终骨龄影响远小于漏检的影响这个 trade-off 在医疗场景里非常划算。5.5 坑点五yolov5 环境兼容性太玄学依赖冲突反复横跳现象pip install -r requirements.txt 装好了跑 train.py 却在某个模块报 ImportError 或者算子不匹配。原因yolov5 的老版本对新版本 torch 和 numpy 的兼容性并不好特别是 python 3.11 之后很多 C 扩展需要重新编译编译失败就直接黑匣子报错。解决这个项目的保险组合是 python 3.8 torch 1.13.1 cuda 11.7一次装通概率最高。如果你已经用了一个装好的环境跑别的项目不想动就把 yolov5 代码放到独立虚拟环境里训练完再把权重文件和推理脚本拷回来千万不要在全局环境里折腾。6. 项目交付与验证从能跑通到让评审信服模型跑通只是第一步毕业设计或课设答辩时老师最关心的是“你凭什么说这个结果是对的”。所以最后一环要把验证做扎实。首要指标不是 mAP而是骨龄预测的 MAE平均绝对误差单位是月。你可以在测试集上统计这个数对每个年龄段分组列出 MAE展示误差分布而不是只给一个总数字。如果误差在 12 个月以内说明模型具备参考价值低于 8 个月就是很亮眼的成绩。把检测结果和回归结果做成对比表左边是原图中间是检测框可视化右边是真实骨龄和预测骨龄的对照一张图就能说清楚整个链路。进阶的验证手段是用 Grad-CAM 可视化回归网络关注的区域。加载回归模型后提取最后一层卷积的特征图计算对骨龄输出的梯度加权生成热力图叠加在裁剪图上。如果热力图主要落在骨化中心内说明网络学到了正确的医学先验如果高亮区域落在背景或软组织上就得回去检查裁剪和预处理逻辑。这套可视化在答辩时的说服力比任何语言描述都强。如果你想把项目包装成完整的系统演示常见做法是写一个 Flask API接收上传的 X 光片调用检测和回归模型返回骨龄结果和可视化图。接口用 POST 请求一行命令就能起服务演示时用浏览器或 POSTMAN 就能操作。前后端不用做得多花哨但流程图和数据流要清晰评审老师通常会问“这个系统能不能接入真实业务流”这时候你要能说出两张网络的输入输出规格和推理耗时。最后说句我在这个项目里最深的教训项目文档不要只写成功路径要把调试过程中的失败实验和参数依据也写进去。比如为什么不用 Flip 增强、为什么置信度阈值设 0.2、为什么回归头不用 MSE 而用 SmoothL1这些记录展示的是你的思考过程比单纯贴训练曲线有用得多。当时我在文档里补了一节“实验记录与失败总结”答辩时好几个问题都直接从那节里找到的答案。希望帮到你。本文还有配套的精品资源点击获取