简介:这是一份面向医学影像目标检测与深度学习入门/进阶人群的糖尿病肾病(DR)检测数据集,图片及标注均为标准的Pascal VOC与YOLO格式,可直接用于YOLO系列、Faster R-CNN等常见检测模型的训练与验证。类别覆盖mild-DR、moderate-DR、normal、proliferation-DR、severe-DR共5类,对研究糖尿病视网膜病变分级检测、类别不均衡处理及医学小目标检测均有较好参考价值。资源包共2000个文件,其中包含1999个xml标注文件和1个txt使用说明,图片与xml/txt标注一一对应,压缩包整体44.31MB,体积适中,适合快速下载并接入现有训练流程。目前已有138人学习浏览,方便在动手训练前快速评估数据分布、标签格式和目录组织。对需要构建医学图像检测基线、理解VOC与YOLO标注差异的学习者来说,是一份可直接上手的数据资源。
1. 糖尿病肾病数据集为什么值得用 VOC+YOLO 双格式
这个资源是一个 7z 压缩包,里面是 4122 张眼底影像和一一对应的 4122 个 Pascal VOC xml、4122 个 YOLO txt 标签文件。类别包含 mild-DR、moderate-DR、normal、proliferation-DR、severe-DR 五个等级,本质上是一份糖尿病视网膜病变分级检测数据集。和常见只给单一标注格式的公开数据相比,它同时给出 VOC 与 YOLO 两套标签,省去了 xml 到 txt 的转换工序,也方便在 detectron2、mmdetection 和 ultralytics 之间来回切换。对想跑 YOLO 训练自己数据集的人来说,直接基于 txt 就能开始,不必重复踩坐标换算的坑。适合目标检测入门、医学影像分类预研,以及需要快速验证模型效果的开发者。
2. Pascal VOC XML 与 YOLO txt 标注字段解析
2.1 两种标注格式的文件对应关系
解压后每个样本都由三个文件组成:jpg 原图、同名 xml、同名 txt。以firc_shenbing_1719为例,三者文件名一致,后缀不同。xml 是 Pascal VOC 标准标注,保存像素级绝对坐标;txt 是由 xml 换算得到的 YOLO 训练标注,存的是归一化后的中心点坐标和宽高。两者描述同一批目标框,但数据结构完全不同。理解这种对应关系,才能在后续做格式转换、标签验证或可视化时不会搞混。
下面用一个简化示例说明 xml 内部结构。真实文件可能包含更多字段,但核心只有size和object两块:
<annotation> <folder>diabetes</folder> <filename>firc_shenbing_1719.jpg</filename> <size> <width>512</width> <height>512</height> <depth>3</depth> </size> <object> <name>mild-DR</name> <bndbox> <xmin>120</xmin> <ymin>80</ymin> <xmax>300</xmax> <ymax>260</ymax> </bndbox> </object> </annotation>这段 XML 定义了一张 512×512 的图片,包含一个类别为 mild-DR 的边界框。bndbox中四个值分别是左上角 (xmin, ymin) 和右下角 (xmax, ymax) 的像素坐标。解析时直接读取/annotation/size/width、/height,再遍历object节点即可。name字段是字符串,需要映射到整数类别 id,映射顺序必须与训练配置一致。
对应的 YOLO txt 每行一个目标,由五个浮点数组成:
0 0.410156 0.332031 0.351562 0.351562第一个数 0 是类别索引,按摘要给出的类别顺序排列:mild-DR=0,moderate-DR=1,normal=2,proliferation-DR=3,severe-DR=4。后四个数按顺序是 x_center、y_center、width、height,全部除以图像宽高完成归一化。这种格式与 YOLOv5/YOLOv8 数据加载器完全匹配,放进labels/train目录即可直接参与训练。两种格式的字段对照关系如下:
| 属性 | VOC XML | YOLO txt |
|---|---|---|
| 坐标基准 | 像素绝对坐标 | 归一化相对坐标 |
| 框表示 | xmin, ymin, xmax, ymax | x_center, y_center, width, height |
| 类别 | 字符串名称 | 整数索引 |
| 图片尺寸 | 每张独立记录 | 不记录,训练时由加载器读取 |
| 文件后缀 | .xml | .txt |
这张表在换框架时很实用。比如把 YOLO 标注转到 mmdetection 的 COCO 格式,仍然需要先从 txt 反推出像素坐标,再按 COCO 的 bbox 字段重组。
2.2 XML 转 txt 的坐标换算逻辑
如果手里只有 VOC 格式,就要自己写转换脚本。核心公式是:
x_center = (xmin + xmax) / 2 / width y_center = (ymin + ymax) / 2 / height box_width = (xmax - xmin) / width box_height = (ymax - ymin) / height四个分母来自每张图片自己的size字段,而不是一个统一固定值。很多初学者用固定宽度高度去归一化,遇到不同分辨率的图片,标注框就会整体偏移。保险做法是在 Python 里逐张读取 xml 的宽高,不假设所有原图尺寸相同。下面是一个可复用的转换脚本:
import xml.etree.ElementTree as ET from pathlib import Path VOC_DIR = Path("./voc") TXT_DIR = Path("./yolo") CLASSES = ["mild-DR", "moderate-DR", "normal", "proliferation-DR", "severe-DR"] TXT_DIR.mkdir(exist_ok=True) for xml_path in VOC_DIR.glob("*.xml"): tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.findtext("./size/width")) img_h = int(root.findtext("./size/height")) lines = [] for obj in root.findall("./object"): name = obj.findtext("name") if name not in CLASSES: continue class_id = CLASSES.index(name) box = obj.find("bndbox") xmin = float(box.findtext("xmin")) ymin = float(box.findtext("ymin")) xmax = float(box.findtext("xmax")) ymax = float(box.findtext("ymax")) x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") out_path = TXT_DIR / f"{xml_path.stem}.txt" out_path.write_text("\n".join(lines), encoding="utf-8")这段脚本遍历 VOC 目录下所有 xml,先读取图片宽高,再遍历每个object节点完成坐标换算和归一化。CLASSES.index(name)将类别字符串转为整数 id,这一步要求CLASSES列表的顺序和最终训练配置保持一致,否则标签错位不会报错,只会让模型收敛到错误映射。输出采用 6 位小数,对 1024 以内的图像足够精确,不会对 mAP 结果产生可见影响。
2.3 常见误区:归一化坐标系与类别 id 偏移
拿到 txt 后,先检查每行第一个数字是否落在 0~4 范围。如果之前处理过其他数据集,把背景也算成一个类,类别 id 可能整体右移,导致 normal 样本变成 mild-DR。另一个易错点是坐标含义,YOLO txt 里永远存储中心点坐标,不是左上角。用 OpenCV 可视化时,需要从中心点反推左上角:
x1 = int((x_center - w / 2) * img_w) y1 = int((y_center - h / 2) * img_h) x2 = int((x_center + w / 2) * img_w) y2 = int((y_center + h / 2) * img_h)这段反推代码用来把 YOLO 标注画回原图,验证转换是否成功。如果画出的框和 xml 原框不一致,多半是归一化计算有误,或者读取 txt 时把宽高顺序颠倒。建议随机抽取 20 张图,分别用 xml 和 txt 绘制边界框并叠加对比,确认后再进入训练环节。
3. 7z 解压与数据集自检:从压缩包到可训练的目录结构
3.1 Linux 解压 7z 文件
数据集以 7z 压缩包发布,Linux 下用 p7zip 解压是最直接的方式。系统里如果还没有 p7zip,先安装:
sudo apt update && sudo apt install -y p7zip-full 7z x 糖尿病肾病检测数据集VOC+YOLO格式4122张5类别.7z -o./dataset-o指定输出目录,注意-o后面不能有空格,这是 p7zip 一个容易踩的小细节。解压后进入./dataset,里面三类文件混放:jpg、xml、txt。Windows 用户可以用 7-Zip 图形界面解压,路径过长时建议放到盘符根目录,避免系统长路径限制导致文件写不完整。
提示:解压后先统计文件数,预期 jpg、xml、txt 各 4122 个,总计 12366 个。数量不匹配就重新下载校验,不要直接开始训练。
这一步虽然简单,却决定了后续所有操作的可靠性。压缩包损坏但部分解压成功,数据加载器通常不会立刻报错,直到某次 epoch 随机采样到缺失文件才会中断,浪费大量等待时间。
3.2 按训练要求重组目录
YOLO 训练默认从images/train读图片,从labels/train读对应 txt。需要把解压后的平铺文件按 train/val/test 拆分,组织成以下结构:
dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/推荐写一个 Python 脚本来划分,下面这段按 7:2:1 的比例切分并移动文件:
import random from pathlib import Path src = Path("./dataset") imgs = sorted(src.glob("*.jpg")) random.seed(42) random.shuffle(imgs) val_ratio = 0.2 test_ratio = 0.1 n_val = int(len(imgs) * val_ratio) n_test = int(len(imgs) * test_ratio) n_train = len(imgs) - n_val - n_test splits = { "train": imgs[:n_train], "val": imgs[n_train:n_train + n_val], "test": imgs[n_train + n_val:], } for split, files in splits.items(): img_out = Path(f"dataset/images/{split}") label_out = Path(f"dataset/labels/{split}") img_out.mkdir(parents=True, exist_ok=True) label_out.mkdir(parents=True, exist_ok=True) for img in files: txt = src / f"{img.stem}.txt" if not txt.exists(): print(f"missing label: {img.name}") continue img.rename(img_out / img.name) txt.rename(label_out / txt.name)这段脚本先按文件名排序,再用固定随机种子打乱,保证每次运行得到同样的划分结果。val_ratio=0.2切出验证集,test_ratio=0.1留出最终测试集,n_train为剩余样本数量。移动文件时检查 txt 是否存在,若缺失则跳过该样本,避免训练中断。划分后的目录结构就是后续数据集 yaml 的标准输入。
3.3 标注完整性校验与坐标边界检查
文件移动后要再跑一次完整性校验,重点确认每个images/train下的 jpg 都有对应 txt。下面这段脚本可以快速扫描:
from pathlib import Path for split in ["train", "val", "test"]: img_dir = Path(f"dataset/images/{split}") label_dir = Path(f"dataset/labels/{split}") for img in img_dir.glob("*.jpg"): txt = label_dir / f"{img.stem}.txt" if not txt.exists(): print("missing txt:", img)检查时也注意空文件。如果某个 txt 文件内容为空,说明该图没有目标,YOLO 会跳过,但保留空文件比删除更安全,因为删除会导致图片没有对应标签,加载器可能告警。边界检查主要防止坐标越界。YOLO 格式的坐标应在 0~1 之间,一旦出现大于 1 或负数,说明原始 xml 标注有误。常见检查项和处理方式如下:
| 检查项 | 判断方法 | 处理方式 |
|---|---|---|
| 文件数匹配 | 统计 jpg/xml/txt 数量 | 不足时重新解压 |
| 空标签 | 检查 txt 文件大小 | 保留空文件,不删除图片 |
| 坐标越界 | awk 检查任意列 >1 | 裁剪到 0~1 或剔除样本 |
| 宽高为 0 | 检查第 4、5 列 <=0 | 删除对应行 |
坐标越界的快速筛查可以直接用单行命令:
awk '$2>1 || $3>1 || $4>1 || $5>1 {print FILENAME, $0}' dataset/labels/train/*.txtawk遍历所有训练标签,只要第 2 到第 5 列任意数值超过 1 就打印文件路径和整行内容。width 或 height 等于 0 的情况不能用这个命令捕获,需要再查$4<=0 || $5<=0。这类脏数据会让损失函数出现 nan,最好在训练前删掉对应行或直接剔除样本。
4. YOLOv8 训练配置:数据集 YAML、损失函数与关键超参数
4.1 编写数据集 yaml 文件
YOLOv8 用 yaml 描述数据集结构。在项目根目录新建dataset.yaml:
path: /abs/path/to/dataset train: images/train val: images/val test: images/test names: 0: mild-DR 1: moderate-DR 2: normal 3: proliferation-DR 4: severe-DRpath建议写绝对路径,避免工作目录切换导致找不到数据。train、val、test是相对path的图片目录,YOLO 会自动到同级labels目录寻找同名 txt。names的索引必须和 txt 第一列的类别 id 完全对应,否则训练时标签错位但不会报错。特别是从 COCO 预训练权重继续训练时,原有类别 id 是 COCO 的 80 类,加载 yaml 后会被覆盖,所以这里的映射顺序是唯一基准。
启动训练的常见命令:
yolo detect train data=dataset.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16yolov8n.pt是 nano 版预训练权重,显存占用小,适合先跑通流程。显存足够可以换yolov8s.pt或yolov8m.pt,检测精度通常会有提升。imgsz=640是常用输入尺寸,但眼底影像的原图分辨率远高于 640,如果病灶区域较小,可以尝试imgsz=1024,不过显存占用会成倍上升。环境配置阶段,AMD 显卡用户要额外确认 PyTorch 是否安装为 ROCm 版本,否则 ultralytics 会找不到 CUDA 设备,从而无法利用 GPU 加速。
4.2 训练损失函数与观察指标
YOLOv8 的损失由三部分构成:box loss 边界框回归损失、cls loss 分类损失、dfl loss 分布焦点损失。训练日志会实时输出这三项指标。正常情况下,三者在训练前 20 个 epoch 都有明显下降,后期趋于平缓。如果 cls loss 反复震荡,大概率是类别不平衡或学习率过高;如果 box loss 长期不降,优先检查标注质量,而不是改模型结构。对眼底病灶这种边界不清晰的检测任务,dfl loss 对框精度的贡献比 box loss 更敏感。
关键超参数调整参考下面这张表:
| 参数 | 建议值 | 调参方向 |
|---|---|---|
| epochs | 100 | 数据量小加大到 200,配合早停 |
| batch | 16 | 显存不足减小,按比例降低学习率 |
| imgsz | 640 | 小目标多升到 1024 |
| lr0 | 0.01 | loss 震荡时降到 0.005 |
| patience | 20 | 验证集 mAP 停止增长就提前结束 |
| augment | True | 医疗影像慎用强旋转和翻转 |
lr0初始学习率默认 0.01,几千张图片的数据集上通常偏大,容易让前几个 epoch 的 loss 剧烈跳动。可以先用lr0=0.005搭配cos_lr=True跑一版,再逐步调高。augment参数需要特别谨慎,眼底影像中的微动脉瘤、出血点有方向性和位置先验,过度翻转可能让模型学到错误的方向特征,建议只开启颜色抖动和缩放,Mosaic 可以保留但概率不要拉满。
4.3 类别不平衡时的损失权重与样本策略
五类样本在医学数据集中极少均匀,normal 通常占大头,proliferation-DR 和 severe-DR 可能只有几百张。直接训练会出现典型的长尾问题,多数类精度高,少数类 recall 低。一个快速验证不平衡影响的方法是统计验证集混淆矩阵,观察少样本类是否被整体预测为相邻级别。处理策略有两个方向:数据层面可以对少样本类图片做复制和光度扰动,损失层面可以在训练配置中调整分类损失权重。
如果要用更精细的类别权重,推荐在数据加载阶段实现类别重采样。常见做法是写一个自定义 Dataset,在__getitem__里根据类别分布随机选择样本,保证每个 batch 内各类别比例相对均匀。实现成本不高,但对 mAP50-95 的提升往往比调整学习率更明显。单纯调高cls损失系数会让模型更注意分类,但对边界框回归没有帮助,少样本类框不准的问题依然存在。因此不平衡严重的数据集,优先做样本级过采样,再考虑损失权重。
4.4 验证模型与导出部署格式
训练结束后,用验证集评估最优权重:
yolo detect val model=runs/detect/train/weights/best.pt data=dataset.yaml命令输出整体 mAP,同时生成混淆矩阵、PR 曲线和类别指标。如果某个高风险类别比如 severe-DR 的 mAP 明显低于平均,需要回到数据层补样本。验证阶段可以调低置信度阈值,把低置信度的预测框可视化叠加到原图,观察模型是在病灶区域内定位不准,还是把背景伪影当成了病灶。部署阶段导成 ONNX:
yolo export model=best.pt format=onnx opset=12导出的模型可以进一步转成 TensorRT,用于 RK3588 之类的边缘设备推理。导出前检查输入尺寸是否和训练时一致,不同尺寸的模型在设备上的延迟差异很大。YOLOv8 的 NMS 过程默认已包含在导出图中,但转 TensorRT 时要注意自定义 NMS 算子的兼容性,必要时在推理端单独实现后处理流程。
5. 类别不平衡下的 mAP 评估与样本筛选技巧
5.1 统计类别分布与不均衡比
训练前先统计各类别标注框数量,明确数据分布。用 Python 读取全部训练标签:
from collections import Counter from pathlib import Path counter = Counter() label_dirs = [Path("dataset/labels/train"), Path("dataset/labels/val"), Path("dataset/labels/test")] for d in label_dirs: for txt in d.glob("*.txt"): for line in txt.read_text().splitlines(): cls = int(line.split()[0]) counter[cls] += 1 for cls_id in range(5): print(cls_id, counter[cls_id])如果发现某类占比低于 5%,模型大概率会把它忽略。一个可行的快速做法是先把 normal 类随机下采样到与中等类接近,再用完整数据训练一版对比。对于医学数据集,下采样可能会丢掉重要样本,所以通常优先做少样本类过采样,而不是直接丢弃 normal。
5.2 用 mAP50 与 mAP50-95 判断模型改进方向
mAP50 是 IoU 阈值 0.5 下的平均精度,mAP50-95 是阈值从 0.5 到 0.95 每步 0.05 的均值。眼底病灶边界模糊,mAP50-95 通常远低于 mAP50。如果 mAP50 上涨但 mAP50-95 停滞,说明定位精度不够,此时提高输入分辨率或增加 dfl loss 权重更有效。如果两者都停在低位,先检查标签框是否贴合病灶边界,再决定是否强化增强策略。
5.3 从测试集中筛选易错样本做结构化复查
把测试集预测结果按错误类型分类,用脚本输出典型错误样本。人工复查时重点看两类:一是标注本身有没有框偏,二是模型是否抓住病灶纹理。对少样本类,可以把它们的预测结果单独抽出来统计 confidence 分布,找出模型不确定的样本,再决定是补充训练还是调整类别权重。实际操作中,对样本极少的类别可以在训练配置里把分类损失权重调高到 1.5~2.0,但不要超过 3。权重过大会让模型对少数类过拟合,严重时反而拉低整体 mAP。先用默认权重跑一版作为 baseline,再逐步增加少数类权重,每次对比验证集 mAP50-95 的变化,而不是只盯训练集 loss。
本文还有配套的精品资源,点击获取