☰
糖尿病肾病检测数据集:4122张VOC/YOLO双格式标注与YOLOv8实战
2026/10/1 22:24:13 网站建设 项目流程

简介:本资源为糖尿病肾病视网膜病变检测数据集,面向医学影像分析、深度学习目标检测方向的开发者与研究人员,可用于训练和评估糖尿病视网膜病变分级模型。数据集采用Pascal VOC与YOLO双格式标注,包含4122张jpg图片,每张图片均配有对应的VOC格式xml文件与YOLO格式txt文件,标注类别共5类,分别为mild-DR、moderate-DR、normal、proliferation-DR、severe-DR,覆盖轻度、中度、重度、增殖型及正常五种眼底状态。压缩包为7z格式,内含2000个文件,以1999个xml标注文件和1个说明用txt为主,整体约44.31MB,目录结构清晰,便于直接接入主流检测框架进行训练。目前已有139人学习下载,适合需要快速获取标注完备、类别均衡的医学影像数据集,用于模型对比实验、算法验证或课程实践。

1. 4122 张糖尿病肾病切片,为什么值得你花一个下午跑通

糖尿病肾病(Diabetic Nephropathy, DN)的病理诊断,长期依赖肾活检后的光镜、免疫荧光和电镜三套结果交叉印证。问题在于,能同时拿到这三种模态且标注规范的公开数据极少,大部分团队要么自己攒几十张,要么在私有数据上反复调参,模型换个人就复现不出来。这个标题里的数据集,把 4122 张图像统一整理成 VOC 和 YOLO 两套标注格式,覆盖 5 个类别,等于把「数据准备」这一步从几周压缩到几小时。它适合三类人:想入门医学图像检测但缺数据的算法工程师、需要快速验证检测方案可行性的肾内科研究方向人员、以及做 YOLO 系列改进但苦于没有领域数据做消融的开发者。VOC 和 YOLO 双格式并存,意味着你既可以用现成的检测框架直接吃 YOLO 格式,也能用 VOC 格式做格式转换、数据增强和跨框架迁移。数据集本身不解决诊断问题,但它把最耗时的标注和格式统一做完了,剩下的就是你的模型和调参。

2. 从 VOC 到 YOLO:4122 张图的两套标注到底差在哪

2.1 VOC 的 XML 结构与 YOLO 的 TXT 结构对比

VOC 格式每张图对应一个 XML 文件,里面记录图像尺寸、目标类别和边界框的左上角与右下角坐标。YOLO 格式每张图对应一个 TXT 文件,每行是一个目标,格式为类别索引 中心x 中心y 宽 高,所有坐标都归一化到 0 到 1 之间。这两种格式的差异不只是文件后缀,而是坐标体系和类别表达方式的根本不同。VOC 用绝对像素坐标,YOLO 用相对比例坐标;VOC 在 XML 里直接写类别名,YOLO 用从 0 开始的整数索引,索引到类别名的映射需要额外维护一个classes.txt或data.yaml。如果你拿到的是 VOC 格式,想直接喂给 YOLOv8,必须做一次转换,否则框架读不懂 XML。

转换的核心逻辑是:读 XML 里的size拿到宽高,读每个object的bndbox拿到xmin, ymin, xmax, ymax,然后计算中心点和宽高并除以图像宽高做归一化。类别名要按固定顺序映射成整数,这个顺序一旦确定就不能改,否则训练时类别会错位。下面是一个可直接运行的转换脚本,处理 4122 张图大约需要十几秒。

import os import xml.etree.ElementTree as ET # 类别顺序必须与训练配置一致,一旦确定不要随意调整 CLASSES = ['class0', 'class1', 'class2', 'class3', 'class4'] CLASS_TO_ID = {name: i for i, name in enumerate(CLASSES)} def voc_to_yolo(xml_path, txt_path): tree = ET.parse(xml_path) root = tree.getroot() size = root.find('size') img_w = int(size.find('width').text) img_h = int(size.find('height').text) lines = [] for obj in root.iter('object'): cls_name = obj.find('name').text if cls_name not in CLASS_TO_ID: continue # 跳过未定义类别,避免索引越界 cls_id = CLASS_TO_ID[cls_name] bbox = obj.find('bndbox') xmin = float(bbox.find('xmin').text) ymin = float(bbox.find('ymin').text) xmax = float(bbox.find('xmax').text) ymax = float(bbox.find('ymax').text) # 归一化并计算中心点 cx = (xmin + xmax) / 2.0 / img_w cy = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") with open(txt_path, 'w') as f: f.write('\n'.join(lines)) # 批量转换 xml_dir = 'annotations_xml' txt_dir = 'labels_yolo' os.makedirs(txt_dir, exist_ok=True) for fname in os.listdir(xml_dir): if fname.endswith('.xml'): voc_to_yolo(os.path.join(xml_dir, fname), os.path.join(txt_dir, fname.replace('.xml', '.txt')))

这段脚本的关键参数是CLASSES列表,它决定了类别名到整数索引的映射。如果你拿到的数据集类别名不是class0到class4,需要先打开几个 XML 文件确认实际名称再改。另一个容易忽略的点是img_w和img_h的读取,有些 VOC 标注里size字段可能缺失或写错,转换前最好抽查几张,确认宽高与实际图像一致。归一化后的坐标如果出现负数或大于 1,说明边界框超出了图像范围,这类样本在训练时会导致损失异常,建议在转换阶段就过滤掉或裁剪到边界内。

2.2 5 个类别在检测任务里的实际含义与分布检查

标题里说 5 个类别,但没有展开具体是哪 5 类。从糖尿病肾病的病理检测常见任务推断,这 5 类大概率对应肾小球、肾小管、间质、血管和炎症细胞浸润区域,或者是正常与不同病变程度的肾小球分类。不管具体名称是什么,拿到数据后第一件事是统计每个类别的实例数量。如果某一类只有几十个实例,而另一类有几千个,直接训练会导致模型严重偏向多数类,少数类的召回率会低到没法用。

统计类别分布的代码很简单,遍历所有 TXT 文件,读每行第一个数字,用字典计数即可。如果发现长尾分布,常见的处理方式有三种:对少数类做过采样、在损失函数里给少数类更高权重、或者用数据增强专门扩充少数类。YOLOv8 的data.yaml里没有直接的类别权重参数,但可以在训练时通过cls损失的自定义或采样策略来缓解。更实际的做法是先用原始分布跑一版,看混淆矩阵里少数类是否被完全忽略,再决定要不要干预。

from collections import Counter counter = Counter() for fname in os.listdir(txt_dir): if fname.endswith('.txt'): with open(os.path.join(txt_dir, fname)) as f: for line in f: if line.strip(): counter[int(line.split()[0])] += 1 for cls_id in sorted(counter): print(f"类别 {cls_id}: {counter[cls_id]} 个实例")

跑完这段,你会得到每个类别的实例总数。如果某个类别实例数低于总实例数的 5%,就要警惕了。糖尿病肾病数据里,肾小球和肾小管的实例通常最多,炎症细胞浸润区域可能最少。这不是数据集的问题,而是病理图像本身的分布特点,但训练时必须意识到这一点,否则模型在少数类上的表现会让你误以为方案不行,其实是数据不平衡导致的。

3. 用 YOLOv8 跑通第一版糖尿病肾病检测:环境、配置与训练命令

3.1 环境搭建与 data.yaml 的五个必填字段

YOLOv8 的环境搭建已经非常成熟,用 conda 或 venv 建一个 Python 3.9 以上的环境,然后pip install ultralytics即可。不需要单独装 PyTorch,ultralytics 会自动拉取匹配的版本。如果你有 GPU,确认 CUDA 版本和 PyTorch 版本匹配,否则训练会回退到 CPU,4122 张图在 CPU 上跑一版可能要几个小时,在 GPU 上通常十几分钟就能跑完一个 epoch。

data.yaml是 YOLOv8 读取数据的入口,必须包含五个字段:path是数据集根目录,train和val是训练集和验证集的图像目录,nc是类别数,names是类别名列表。注意train和val写的是图像目录路径,YOLOv8 会自动在同级目录下找同名的labels文件夹。如果你的目录结构是images/train和labels/train,那train就写images/train。下面是一个可直接用的data.yaml示例。

path: /data/diabetic_nephropathy train: images/train val: images/val nc: 5 names: - class0 - class1 - class2 - class3 - class4

nc必须和names的长度一致,且names的顺序必须和转换脚本里的CLASSES完全一致。如果顺序错了,训练不会报错,但模型学到的类别会全部错位,验证时的指标会莫名其妙地低。这是血泪经验里最常见的一类翻车:数据没问题,配置也没报错,但结果就是不对,最后发现是类别顺序对不上。

3.2 训练命令与三个影响收敛的关键参数

YOLOv8 的训练命令很简洁,一行就能启动。但默认参数不一定适合医学图像检测,尤其是糖尿病肾病这种目标密集、边界模糊的场景。下面这条命令是我一般会用的起点。

yolo detect train \ data=/data/diabetic_nephropathy/data.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ device=0 \ project=runs/dn_det \ name=exp1

model选yolov8s.pt而不是yolov8n.pt,是因为医学图像里小目标和模糊边界多,n 版本的感受野和特征提取能力偏弱,s 版本在速度和精度之间更平衡。imgsz=640是默认值,但如果你的图像分辨率远高于 640,比如 2000 像素以上,直接缩到 640 会丢失大量细节,可以考虑用imgsz=1024,代价是显存占用增加,batch 要相应调小。lr0=0.01是初始学习率,如果训练 loss 在前几个 epoch 就剧烈震荡,说明学习率偏大,可以降到 0.001。patience=20表示 20 个 epoch 验证指标不提升就早停,避免过拟合。

训练过程中要盯住三个指标:box_loss、cls_loss和mAP50。box_loss下降但cls_loss不降,说明定位在改善但分类没学好,可能是类别不平衡或类别特征太相似。mAP50在某个 epoch 后突然掉下去,通常是学习率过大导致模型跳出最优区域,可以加学习率衰减或降低初始学习率。如果训练集 loss 持续下降但验证集 loss 开始上升,就是过拟合的典型信号,需要加数据增强或减少模型复杂度。

4. 避坑与排查:4122 张图训练时最容易翻车的五个地方

4.1 图像与标签文件名不匹配导致训练时静默跳过

现象:训练日志里显示的图片数量远少于 4122,比如只有 3000 多张,但没有任何报错。原因:YOLOv8 按文件名匹配图像和标签,如果某张图叫img_001.jpg,标签叫img_001.txt,但实际标签文件是img_001.xml或img_001.JPG.txt,框架找不到对应标签就会跳过这张图,且不报错。解决:训练前用脚本检查图像目录和标签目录的文件名是否一一对应,去掉扩展名后做集合比较,把差集打印出来手动修复。

4.2 归一化坐标越界导致 loss 变成 NaN

现象:训练几个 epoch 后 loss 突然变成 NaN,或者从第一个 epoch 开始 loss 就异常大。原因:VOC 转 YOLO 时,某些边界框的坐标超出了图像宽高,归一化后出现负数或大于 1 的值。YOLOv8 对越界坐标不会直接报错,但计算损失时会产生异常梯度。解决:在转换脚本里加一行判断,如果cx、cy、w、h任意一个不在 0 到 1 之间,就裁剪到边界或直接丢弃该目标。更稳妥的做法是转换后统一扫描所有 TXT 文件,把越界行找出来。

4.3 类别顺序不一致导致 mAP 虚低

现象:训练正常收敛,loss 也降得不错,但mAP50始终在 0.1 到 0.2 之间,混淆矩阵显示所有类别都混在一起。原因:data.yaml里的names顺序和转换脚本里的CLASSES顺序不一致,模型学到的类别索引和验证时的类别名对不上。解决:把data.yaml的names和转换脚本的CLASSES放在同一个文件里维护,或者写一个校验脚本,读几个 TXT 文件确认类别索引对应的名称是否和data.yaml一致。

4.4 验证集里出现训练集图像导致指标虚高

现象:验证集mAP50高得离谱,比如 0.95 以上,但拿新图测试时效果很差。原因:划分训练集和验证集时没有去重,同一张图或高度相似的切片同时出现在两边。糖尿病肾病图像如果来自同一张活检切片的连续视野,相邻图像可能高度相似。解决:按病例或切片 ID 划分,而不是按图像随机划分。如果数据里没有病例 ID,至少用图像哈希做一次去重,把重复图像只保留在训练集。

4.5 显存不足导致 batch 被迫调小后训练不稳定

现象:用batch=16时显存溢出,改成batch=4后 loss 震荡严重,收敛很慢。原因:batch 太小导致梯度估计噪声大,尤其是医学图像里目标密集时,小 batch 的梯度方向波动明显。解决:不要直接降 batch,先用batch=8配合梯度累积,或者把imgsz从 640 降到 512 来省显存。如果必须用很小的 batch,把lr0同比调小,比如 batch 从 16 降到 4,学习率从 0.01 降到 0.0025 左右,否则震荡会更严重。

5. 把 4122 张图用出复利:从单次训练到可迭代的数据闭环

数据集的价值不在于跑一次训练,而在于能不能形成「训练-验证-补标-再训练」的闭环。4122 张图、5 个类别,如果只是跑一版 YOLOv8 看个 mAP,那这个数据集的利用率不到三成。我一般会做三件事:第一,用训练好的模型对未标注或标注不全的图像做推理,把高置信度的预测结果导出成 YOLO 格式的伪标签,人工抽检后加入训练集;第二,把验证集中模型预测错误但人工确认正确的样本单独拿出来,分析是标注问题还是模型问题,如果是标注漏标,补标后重新训练;第三,每轮训练后保存混淆矩阵和 PR 曲线,对比不同轮次的类别级指标变化,找出持续表现差的类别,针对性做数据增强。

伪标签的导出可以用 ultralytics 的predict模式,加上save_txt=True和save_conf=True,输出格式就是 YOLO 的 TXT,每行末尾多一个置信度。置信度阈值建议设 0.5 以上,低于这个值的预测不要直接当标签用,否则会引入噪声。下面这条命令可以把一个目录下的所有图像推理一遍并保存 TXT。

yolo detect predict \ model=runs/dn_det/exp1/weights/best.pt \ source=/data/unlabeled_images \ save_txt=True \ save_conf=True \ conf=0.5 \ imgsz=640 \ project=runs/pseudo_labels \ name=round1

conf=0.5是置信度阈值,低于这个值的预测不会写入 TXT。save_conf=True会在每行末尾追加置信度,方便后续按置信度筛选。导出的 TXT 默认在labels子目录下,格式和训练用的 YOLO 标签完全一致,可以直接合并到训练集里。但要注意,伪标签的类别索引必须和data.yaml的names一致,否则合并后会类别错位。

验证闭环是否有效,不能只看 mAP 涨了多少,还要看少数类的召回率有没有提升。如果加了伪标签后整体 mAP 涨了但少数类召回没变,说明伪标签主要来自多数类,对长尾问题没有帮助。这时候要调整推理时的类别置信度阈值,对少数类单独设更低的阈值,或者对少数类区域做专门的裁剪和增强。我自己的习惯是每轮训练后把混淆矩阵和 PR 曲线截图存档,文件名带上日期和轮次,过一个月回头看,能清楚知道哪个类别一直没学好,而不是凭感觉调参。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询