ARTICLE DETAIL

资讯详情

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

YOLOv5实战CT肾脏结石检测:数据集处理与避坑指南

YOLOv5实战CT肾脏结石检测:数据集处理与避坑指南 简介面向需要构建CT图像肾脏、结石检测模型的目标检测初学者与医疗影像开发者提供已按YOLOv5目录结构整理的监督学习数据集包含肾脏、结石两个类别可直接用于训练和验证无需额外转换格式。压缩包为RAR格式共923个文件包括460张640×640分辨率的RGB图像、461个对应txt标注文件内含边界框坐标与类别信息、1个可直接运行的Python可视化脚本及1张预览图整体仅16.9MB轻量易用。训练集含325张图片与325个标签验证集含135张图片与135个标签图像均来自CT扫描边界框标注清晰适合教学演示、科研实验与模型调优。已有132人学习/下载是医疗影像目标检测领域简洁实用的入门级数据。除标注数据外还附带可视化脚本可随机读取一张图像并绘制边界框保存至当前目录便于检查标注质量减少数据清洗与格式适配工作量。1. 拿到一个CT肾脏结石检测数据集先搞清楚它在解决什么问题做医学影像目标检测的人大多会遇到同一个坎算法好抄数据难造。普通场景随便拍几千张照片就能标可CT图像从采集到脱敏到找医生标注每一环都卡人。这个标题给的是一个已经整理好的yolov5数据集CT图像肾脏、结石检测2类别带训练集和验证集意味着你不需要从DICOM序列里一层层切窗、转格式、写标注拿到手就能开始跑yolov5训练自己的数据集。它解决的核心问题是“从0到1”那段最脏最累的数据准备工作适合刚入门医学影像检测的工程师和研究生也适合需要快速验证检测方案可行性的团队。但数据集能用不代表能出好结果CT图像和自然图像在灰度分布、目标尺度、标注边界上都有本质差异下面拆开讲。2. CT断层图像为什么不能照搬通用目标检测套路先看懂数据再动手2.1 灰度图像、窗宽窗位和检测模型的输入习惯矛盾CT图像本质上是单通道灰度图存储的是人体组织对X射线的衰减系数医学上习惯用HU值Hounsfield Unit来表示。但yolov5的通用预训练模型是基于三通道RGB照片训出来的处理灰度图时有几种常见做法直接复制成三通道、保持单通道输入、或者用伪彩色映射。直接复制三通道跑起来最简单但这意味着模型看到的“三个通道”完全一样等于没有额外信息只是满足了网络的输入格式。更麻烦的是窗宽窗位。同一张CT切片用不同的窗宽窗位显示视觉效果截然不同看骨骼是一种亮度看软组织是另一种亮度。如果数据集导出时没有做统一的窗宽窗位处理那么训练集里“肾脏区域的亮度特征”和你看片软件里看到的可能不是一回事。低剂量CT图像因为噪声更大这种差异会更明显。所以拿到数据集第一件事不是急着训练而是先抽查几十张图像确认图像的灰度范围、对比度是否一致肾脏和结石的视觉特征是否清晰可辨。另一个容易忽视的点是CT图像的背景复杂度。CT切片里除了目标器官还有床板、空气、身体轮廓、血管截面等大量背景。自然图像检测里背景通常是“天空、草地、墙壁”语义相对简单而CT背景里有很多和肾脏灰度接近的组织。这就是为什么通用检测模型直接用在CT图像上常常出现高误检光看loss曲线根本察觉不到问题val出来的mAP凑合一上真实数据就翻车。2.2 标注格式的文件级检查类别ID、归一化坐标和空标签标题里写了“2类别”对应到yolov5标注格式就是两个类别ID。通常是0和1具体哪个是肾脏、哪个是结石不同数据集定义不同训练前必须确认。标注文件是txt每行五个数字类别ID、归一化后的中心点x、中心点y、宽度w、高度h全部是0到1之间的小数。不要直接打开txt看坐标觉得“不对”因为这个坐标是相对于图像宽高的比例不是像素值。拿到数据集先做一轮文件级体检用Python脚本扫一遍所有标签的类别ID有没有越界比如出现2或负数、有没有空文件目标没标上、坐标有没有小于0或大于1的异常值。这类问题在公开数据集里并不少见尤其是人工标注转格式时容易漏行。脚本本身很简单但是跑一遍能省掉后面训练时大量莫名其妙的报错和指标异常。import os label_dir datasets/CTKidneyStone/labels/train valid_ids {0, 1} issues [] for name in os.listdir(label_dir): if not name.endswith(.txt): continue path os.path.join(label_dir, name) with open(path, r) as f: lines f.read().strip().splitlines() if len(lines) 0: issues.append((name, empty_label)) continue for idx, line in enumerate(lines): parts line.split() if len(parts) ! 5: issues.append((name, fline_{idx}_not_5_fields)) continue cls int(parts[0]) vals [float(v) for v in parts[1:]] if cls not in valid_ids: issues.append((name, finvalid_class_{cls})) if any(v 0.0 or v 1.0 for v in vals): issues.append((name, fcoordinate_out_of_range_line_{idx})) for issue in issues: print(issue)这段脚本的逻辑是逐行解析每个txt标签检查类别ID是否落在{0,1}范围内坐标值是否都在0到1之间以及文件本身是否为空。任何一条违规都会被记录到issues列表里最后统一打印。这里要说明的是空标签文件在yolov5训练时不会直接报错但它意味着这张图完全没有目标参与训练会让模型在对应区域的学习变得不稳定坐标越界则可能导致训练时损失值异常跳变。发现这些问题后处理方式是直接删除空标签对应的图片和标签坐标越界的行则要先看是整体偏移还是个别异常点再决定修整还是删除。2.3 训练集和验证集的划分逻辑只看文件夹名不够标题明确写了“包含训练集、验证集”但划分质量直接影响你对模型效果的判断。常见做法是train和val按病例patient划分而不是按切片划分。因为同一病人的相邻CT切片高度相似如果同一个人的切片一部分进了训练集、一部分进了验证集验证集指标会虚高推理时一遇到新病人就暴露真实水平。这种“数据泄漏”在医学影像里很常见表现就是训练时loss正常、验证mAP也不错但实际部署到新扫描数据上效果大跌。判断方式简单直接看文件名有没有病人ID比如“patient001_frame012.png”这类命名用脚本提取前缀统计train和val里有没有重叠的病人ID。如果数据集没有按病人划分我的建议是自己重新划分一次。把全部数据合并按病人ID排序后用random.seed固定随机种子以病人为单位按比例切分7:3或8:2都可以再把对应图片和标签复制到新的train、val目录。虽然麻烦但这一步能避免你后面拿着虚高指标到处踩坑。3. 用yolov5跑通这个数据集环境、目录结构与第一次训练3.1 本地环境配置conda创建独立环境避免依赖冲突yolov5的依赖不算复杂但它对PyTorch、OpenCV、numpy的版本组合比较敏感。我一般用conda建独立环境先装PyTorch再装requirements顺序不要反。直接用pip装yolov5的requirements.txt时如果当前环境里已有旧版numpy或opencv经常会出现“版本被强制升级导致其他库崩掉”的情况所以独立环境是最稳妥的做法。conda create -n yolo5 python3.9 -y conda activate yolo5 conda install pytorch torchvision torchaudio pytorch-cuda11.8 -c pytorch -c nvidia git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt这段命令的逻辑是先创建一个干净环境Python版本用3.9是兼容性比较好的选择3.10以上在某些旧版本依赖上会报错然后通过conda安装与CUDA 11.8匹配的PyTorch如果你的显卡驱动支持更高CUDA版本可以换成12.1最后把yolov5仓库克隆到本地并安装依赖。安装时如果遇到“ERROR: Could not build wheels”这类提示通常是某些包需要编译而本机缺编译器优先去对应包官网找预编译版本手动安装不要硬扛着装源码版。3.2 整理数据集目录与编写yaml配置路径写错是最常见的启动失败原因yolov5训练时通过data参数指定一个yaml文件里面写训练集和验证集路径、类别数和类别名。路径写错是最常见的启动失败原因尤其是Windows上反斜杠和Linux斜杠混用或者相对路径从不同目录启动导致找不到数据集。我一般建议把数据集放在yolov5项目外的独立目录yaml里写相对于项目根目录的相对路径这样换机器后只要保持相对结构不变就能直接跑。# dataset.yaml path: ../datasets/CTKidneyStone train: images/train val: images/val nc: 2 names: 0: kidney 1: stone这个yaml配置里path是基准目录train和val是相对于path的子目录。yolov5会自动去path/train/images下找图片并通过同名机制在path/train/labels下找对应标签。注意这里有个隐含约定图片文件叫img_001.jpg标签文件就必须叫img_001.txt扩展名不同但主名必须一致。很多人在这一步翻车——图片是png标签是jpg同名文件拷过来的或者文件名多了空格训练时能启动但丢样本。校验方法很简单训练前先用脚本统计images和labels目录下的主文件名集合是否完全相等。3.3 跑通第一次训练用最小可运行的参数组合第一次训练不要追求指标目标是“完整跑完一个epoch流程走通”。用yolov5s模型、640输入尺寸、训练30个epoch左右验证脚本、数据流、标注解析全部没问题之后再考虑调参。python train.py \ --data dataset.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 30 \ --cache ram \ --name ct_kidney_stone_v1每个参数的作用要说清楚--data指定刚才写的yaml文件--weights用yolov5s.pt预训练权重做迁移学习初始点不要从scratch训练否则收敛慢且更容易陷入震荡--img 640是输入分辨率CT图像本身是512x512或1024x1024居多640意味着会被缩放或填充后面会细说这个问题--batch 16受显存限制如果你的显卡只有8G显存可以降到8--cache ram表示把图像预加载到内存里能明显加快数据读取但如果数据集图像特别大且内存不足反而会导致OOM此时应去掉这个参数。训练启动后重点关注两个现象一是loss有没有从高位快速下降二是每个epoch结束后有没有打印出mAP相关的验证指标。如果第一个epoch就卡在“WARNINGNo labels found”说明路径或标签名字有问题立即停止排查。4. 训练参数怎么设医学图像检测里的batch、imgsz与自适应锚框4.1 输入尺寸不是越大越好CT原始分辨率和显存成本的平衡CT图像原始分辨率通常是512x512或1024x1024很多人在训练时习惯性用--img 640或更高觉得分辨率高细节多、小目标更容易检测。这个思路在自然图像里成立但在CT肾脏结石检测里要打折扣。结石目标在CT切片上往往只有几个像素到几十个像素大小如果原始图像是512x512缩放到640反而被放大模型学到的“结石纹理”实际上是被插值平滑后的版本如果原始图像是1024x1024缩放到640意味着信息丢失。常见做法是先统计数据集里目标框的像素尺寸分布再看原始图像分辨率两者差距过大时优先保目标尺寸而不是整图尺寸。另一个实际约束是显存。batch 16、img 640下yolov5s大约需要10G左右显存如果你的卡只有6G要么降batch到8要么把img降到512。显存不够时千万别尝试混合精度强行硬跑训练过程虽然能启动但会频繁出现CUDA out of memory中断纯属浪费生命。我给一个参考推荐表具体以你的显存为准显存推荐img推荐batch6G51288G640812G6401624G768或原始尺寸16训练过程中观察loss曲线比死盯mAP有用。正常情况是box_loss和cls_loss稳步下降val曲线有波动但总体跟随train。如果train loss一路降、val loss降不下去典型过拟合信号常见做法是增加数据增强、减小模型从s换成n或者提前终止如果train和val从一开始就不降多数是学习率不对yolov5默认lr是0.01batch改小后可以适当降到0.005。4.2 自适应锚框小结石检测里最容易被忽略的免费收益yolov5的anchor默认是为COCO数据集设计的COCO里面大目标占比高anchor尺寸整体偏大。肾脏检测中目标框通常是中大型矩形问题不大但结石是典型的极小目标默认anchor在小目标上匹配率低表现就是“大肾能框出来小结石各种漏”。yolov5提供了自动锚框机制训练时加上--noautoanchor表示关闭不加则自动按你的数据集重新聚类anchor。我见过不少人因为AutoAnchor在训练前多跑一段时间就把它关掉这在CT小目标检测上等于主动放弃了免费收益。python train.py \ --data dataset.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --cache ram \ --name ct_kidney_stone_v2训练启动后你会看到类似“AutoAnchor: 7.2 anchors/target, 0.95 Best Possible Recall”这样的日志。Best Possible Recall如果低于0.98说明当前anchor和你的目标框匹配得不好模型再怎么训练都很难把目标全部检出来。处理方式是直接跑autoanchor的独立脚本重新聚类再回来训练。还有一种情况是数据集中结石目标框长宽比极端比如细长形结石AutoAnchor聚类后会出现几个长条anchor这没问题但如果某个anchor的宽或高聚类结果小于3个像素说明目标框标注本身可能有误或目标太小需要回到数据检查。import numpy as np labels [] with open(labels.txt) as f: for line in f: parts line.strip().split() if len(parts) 5: w, h float(parts[3]), float(parts[4]) labels.append((w, h)) labels np.array(labels) img_size 640 abs_w labels[:, 0] * img_size abs_h labels[:, 1] * img_size print(目标框宽中位数(像素):, np.median(abs_w)) print(目标框高中位数(像素):, np.median(abs_h)) print(小于5像素的目标占比:, np.mean((abs_w 5) | (abs_h 5)))上面这段脚本用于快速了解数据集中目标框的实际尺寸分布。它把归一化宽高乘上你打算训练用的img尺寸得到目标在输入图像中的像素尺度然后计算中位数和极小目标占比。如果“小于5像素的目标占比”超过5%说明这个数据集里天然存在大量超小目标此时需要优先考虑切图策略或者更高分辨率输入anchors优化只能解决部分问题。4.3 预训练权重的运气成分灰度图迁移到COCO权重的真实效果yolov5s.pt在COCO上训得很好但COCO是彩色自然图像CT是灰度医学图像特征分布差异巨大迁移学习的效果没有想象中好。实际训练时你会观察到前几个epoch loss下降很快这是模型在快速适应新数据分布但到后面可能会卡在某个平台期怎么调学习率都上不去。这是因为骨干网络的前几层卷积核是为彩色自然图像的边缘、纹理设计的在灰度CT上这些特征相关性低。常见做法有两种。一是冻结骨干网络前几层训练等头部和检测层收敛后再解冻微调优点是不会破坏预训练权重里相对通用的边缘提取能力二是直接用yolov5训练中的“从零开始”模式适合追求极致精度且数据量较大的情况。我实践下来的建议是如果训练集图片数量少于2000张用预训练权重加冻结训练更稳如果超过5000张直接从头训完全可行最终mAP往往还更高因为不受预训练分布约束。这个选择没有绝对对错但你可以两边各跑一次对比毕竟一次训练也就几十分钟。5. CT肾脏结石检测的避坑手册五个常见问题排查实录5.1 现象训练正常启动loss也能降但验证集mAP一直在0.1以下徘徊原因多数是类别ID和标签内容不匹配。比如数据集标注文件里0代表结石、1代表肾脏而你在yaml里写的是0代表肾脏、1代表结石yolov5依然能正常训练因为类别只是ID映射关系它不会自动判断语义正确性。但mAP这类指标是按类别计算的语义反了之后检测框的位置虽然对类别却全错指标自然上不去。解决方法是随机抽几张训练图用yolov5自带的标注可视化脚本画框检查人工确认每个框对应的类别标签和实际目标是否一致。5.2 现象肾脏检测效果还行结石几乎全部漏检跟没训一样原因很直接结石目标太小且原图分辨率下降导致信息丢失。低剂量CT图像中结石和周围组织对比度进一步降低模型根本分不清噪点和结石。解决方法是先用脚本统计目标框像素尺寸分布如果大量结石目标在640输入下小于10像素优先改大输入尺寸到1024或1280同时减小batch或者对原图做滑窗切块后训练。切块会让每张训练图里的目标相对变大但对“类别不平衡”没有帮助因为结石和肾脏的样本数量仍然悬殊。5.3 现象训练时偶发loss暴涨到几十甚至NaN然后一直不恢复原因是batch内有异常的标注框比如坐标越界、宽度或高度为0模型在反向传播时计算了无意义的损失。最常见的来源是标注文件里出现“0 0 0 0 0”这种全零行或者某行坐标在归一化后超出图像边界。解决方法是回到第2节的标签体检脚本把所有异常行打出来逐条确认是删除整图还是修坐标。注意yolov5虽然会打警告但不一定会中断训练你如果不看日志根本不知道有样本在持续污染loss。5.4 现象同一个数据集换一台电脑训练后mAP明显下降训练配置完全一样原因是环境版本不一致尤其是PyTorch版本、CUDA版本和opencv版本。pyTorch版本影响算子实现的细节和随机数生成opencv版本影响图像读取时的插值方式和颜色通道顺序。CT图像虽然是灰度图但如果读图时用了不同的通道处理逻辑输入网络的张量分布也会变化。解决方法是固定一套环境版本用conda export把环境导出为yaml文件换机器时用conda env create -f完全复现不要凭感觉“装个新版应该没问题”。5.5 现象验证集mAP很高但把模型接到新的CT扫描序列上漏检和误检一起爆发原因最可能是数据集按切片而不是按病人划分导致的泄漏其次是过拟合到了训练集里固定的扫描设备噪声模式上。验证集里如果有同一病人相邻切片模型相当于提前“见过答案”新扫描则完全是陌生分布。解决方法是按病人重新划分数据集另外观察训练集是否来自单一设备或单一参数设置——如果是需要补充不同设备的数据做域泛化否则模型离开这个数据集范围就失效。这个问题在医学影像里非常普遍是实际部署时最容易被忽略的一环。6. 验证和进阶用小目标切图把小结石召回率再拉高一截前面提到过输入尺寸和anchors这两者能解决一部分小目标问题但对大量只有几个像素的结石来说最有效的还是滑窗切图。做法是在训练和推理时把原始CT切片切成若干有重叠的patchpatch尺寸和训练时的输入尺寸保持一致这样结石在patch里的相对大小会被放大模型学习起来容易得多。推理时把每个patch的检测结果按坐标映射回原图然后做NMS合并重叠框。这个方案在医学影像检测里非常常见最适合目标小而分布稀疏的任务比如肺结节、微小结石。验证模型效果时除了看整体mAP我建议单独算一下stone类别的recall。mAP是精度和召回的综合而医学检测场景里漏检的代价远高于误检——宁可多框一个非目标也不要漏掉一个真结石。如果你的数据集中结石类别样本占总标注框比例不足20%考虑用yolov5的class weight参数或简单的过采样策略平衡一下否则训练过程会被肾脏大目标主导。我第一次在这个数据集上翻车就是盯着mAP调参调了一整天后来按类别分开看指标才发现结石的recall只有0.3根源在于目标尺度不平衡和类别不平衡叠加。后来靠切图加单独调augmentation把stone的recall从0.3拉到了0.68虽然还是有提升空间但至少模型在真实场景里“能用”了。最后提一个习惯每次训练完把跑得最好的权重连同dataset.yaml、训练参数、日志文件一起归档。医学影像数据集标注成本高模型效果很大程度取决于数据质量一份完整的实验记录比调参玄学更值钱。还是那句话配置文件随手存一份后面复现和出报告都省事。希望帮到你。本文还有配套的精品资源点击获取
返回列表