尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

糖尿病肾病检测数据集VOC/YOLO格式转换与YOLOv8训练实战

糖尿病肾病检测数据集VOC/YOLO格式转换与YOLOv8训练实战 简介糖尿病肾病检测数据集包含4122张眼底图像覆盖正常、轻度、中度、增殖性和重度五个糖尿病视网膜病变等级每张图像均配有Pascal VOC格式的xml目标框标注与YOLO格式的txt标注文件可直接用于目标检测模型的训练、验证与迁移学习。资源包约44.31MB压缩包内文件总数2000个文件类型以xml标注为主并附带txt格式的使用说明方便用户快速了解目录结构与标注规范数据集类别名称与糖尿病视网膜病变临床分级一致便于医学与算法理解。数据集类别划分清晰格式标准统一既适合从事医学图像分析的研究人员与研究生进行算法论证也适合本科生或竞赛选手作为糖尿病肾病自动筛查的入门数据可用于对比YOLO、SSD等主流检测框架的性能或作为模型调参、迁移学习的基准数据集。目前已有138人学习下载值得作为该领域实验与实践的基础资源。1. 糖尿病肾病检测数据集VOC与YOLO双格式值钱的其实是格式一致性在病理AI项目里真正让人头疼的不是模型选型而是数据格式。糖尿病肾病检测数据集这类VOCYOLO双格式压缩包听起来只是把标注存了两份实际上XML里是像素坐标TXT里是归一化坐标5个类别命名还不一定和你的训练脚本一致。任何一个环节没对齐训练时loss不降跑出来的框在图上乱跳。所以这篇按一条完整路径走先把VOC的XML和YOLO的TXT换算关系讲清楚再处理7z解压和目录整理接着用一个校验脚本把漏标、坐标越界、类别错位挡在训练之前最后落到YOLOv8训练配置与验证技巧上。适合准备用自有数据跑检测模型的工程师也适合刚把病理图像接进深度学习流程的研究生。2. 先拆格式VOC的XML与YOLO的TXT以及5个类别怎么映射2.1 VOC格式一个XML对应一张图坐标是像素绝对值VOC格式来自Pascal VOC竞赛早年做检测的人几乎都碰过它后来成了标注工具导出的常见格式之一。这类数据集如果保留VOC目录通常会看到一张图配一个同名XML目录可能是JPEGImages与Annotations其中Annotations里装的就是标注文件。XML的根节点是annotation里面有filename、size、object几类信息每个object是一个待检测目标name写类别名bndbox给出xmin、ymin、xmax、ymax四个像素坐标值。以一张1600x1200的病理图为例一个标注区域长这样annotation filenamekidney_0001.jpg/filename size width1600/width height1200/height depth3/depth /size object namegsc/name bndbox xmin214/xmin ymin302/ymin xmax487/xmax ymax576/ymax /bndbox /object /annotation这段XML里值得关注的不是某个数字而是“width和height与bndbox四个值处于同一套坐标系”。很多脏数据是怎么产生的图片被resize过但XML里width还停在旧尺寸bndbox也停在原图坐标。所以后续转YOLO时一定以XML里记录的size为准不要用程序读图片尺寸去顶替。VOC格式的好处是人对坐标有直觉坏处是信息密度低一个目标十几行4000多张片子叠加起来解析效率并不高。因此训练阶段普遍会转成YOLO的TXT。2.2 YOLO TXT一行一个目标坐标全部归一化YOLO格式每个图片对应一个同名TXT文件文件里有多少行就有多少个目标。每行的结构固定为class_id x_center y_center width height五个值其中class_id是整数从0开始排后四个是浮点数全部除以图片宽高做了归一化取值范围应该在0到1之间。注意第三个、第四个参数是“目标框中心点的x和y”不是框的左上角这一点经常与COCO的x,y,w,h格式混在一起。同一张图对应的TXT内容大致如下0 0.219 0.366 0.171 0.228 1 0.720 0.442 0.105 0.133 2 0.453 0.601 0.084 0.117这里第一行表示类别0的目标中心点在图片水平21.9%、垂直36.6%的位置宽度占17.1%高度占22.8%。如果把这四个浮点数乘回原图宽高就能还原成和VOC等价的像素框。真正跑训练的时候yolo读取的只是这些浮点数不再关心原图尺寸所以一旦换算出错框架是不报错的只会出现大量越界框被丢弃或者画在奇怪的位置。这也是为什么最好在转换阶段就做一次边界保护而不是把希望寄托在训练框架的容错上。2.3 VOC转YOLO脚本边界保护、类别映射与一个容易漏的字段拿到双格式数据集时如果压缩包只给了VOC的XML需要自己转YOLO哪怕已经给了TXT也建议用下面脚本重新生成一遍做交叉验证。思路是从XML读取size和每个object的bndbox四个像素值先算成中心点与宽高再统一除以图片宽高。脚本如下# voc2yolo.py 放在 Annotations 同级目录下执行 import xml.etree.ElementTree as ET import os # 类别表按 XML 里实际出现的 name 顺序排顺序决定数字编号 CLASSES [gsc, emt, tat, ifb, inf] def convert(xml_path, out_path): tree ET.parse(xml_path) root tree.getroot() # 用 XML 里记录的尺寸做归一化不要用 PIL 重读图片尺寸 size root.find(size) width float(size.find(width).text) height float(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in CLASSES: continue # 跳过未注册类别常见于漏标或拼写不一致 box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 边界保护越界的框先裁剪到图像范围内 xmin max(0.0, min(xmin, width)) xmax max(0.0, min(xmax, width)) ymin max(0.0, min(ymin, height)) ymax max(0.0, min(ymax, height)) x_center ((xmin xmax) / 2.0) / width y_center ((ymin ymax) / 2.0) / height w (xmax - xmin) / width h (ymax - ymin) / height if w 0 or h 0: continue # 过滤退化框 lines.append(f{CLASSES.index(name)} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(out_path, w) as f: f.write(\n.join(lines)) if __name__ __main__: os.makedirs(labels, exist_okTrue) for xml in os.listdir(Annotations): if xml.endswith(.xml): convert(os.path.join(Annotations, xml), os.path.join(labels, xml.replace(.xml, .txt)))这段代码里有几个位置值得按自己的数据改。CLASSES的顺序一旦定下来训练侧的data.yaml和标签文件里的数字必须与它一致中途加类别会改变数字编号历史标签全部作废。边界保护那三行不是可有可无病理切片裁切后常见少量框越界几个像素不做裁剪会生成大于1的归一化坐标。脚本的输出精度保留6位小数对1600宽的图换算误差不到千分之一像素完全够用。同样的转换思路也适用于KITTI标注转YOLO只是KITTI的字段来自csv而非XML节点。转换完成后会得到与图片同名的labels目录。此时先别急着训练目录结构、压缩包残留和标签合法性都要再过一遍这就是下一章要处理的事。3. Linux下解压7z数据集先装p7zip再谈目录整理3.1 linux命令解压7z装包、常用参数与常见报错服务器上最常见的坑是没有装p7zip直接用unzip去解.7z会报“unknown compression method”。.7z是7-Zip自己的压缩算法Linux发行版默认不装所以第一步是装包sudo apt update sudo apt install p7zip-full p7zip-rarp7zip-full提供7z和7za命令p7zip-rar额外支持rar解压医学数据集偶尔会混放几个rar格式的病例表一起装上省事。装完之后用7z而不是7za来解压7za功能更精简部分新压缩算法不支持。执行解压7z x diabetes_detection_voc_yolo.7z -o/workspace/datasets/medicine_kidney -y这里x表示完整解压且保留压缩包内的目录层级-o指定输出目录注意7z的老规矩是-o后直接跟路径不加空格写成-o /path反而会被识别错-y表示遇到同名文件自动覆盖适合反复解压同一批数据。如果只想先看压缩包内部结构用7z l列出条目而不解压。遇到.7z.001这样的分卷压缩包直接对第一个文件执行相同的x命令7z会按序号找齐其余分卷。下表列出解压、验证和选择性提取的常用场景实际用的时候可以按需组合参数场景命令说明查看压缩包内容7z l data.7z只列条目不解压完整解压7z x data.7z -o/tmp/data -y保留内部目录结构只释放Annotations目录7z x data.7z -o/tmp/data Annotations/*目录名带通配符测试压缩包完整性7z t data.7z训练前跑一遍避免中途IO错误合并分卷解压7z x data.7z.001自动搜索后续分卷解压时如果文件名带中文个别服务器终端会显示乱码或解不出来常见做法是先ls确认当前LANG再在命令前加LANGzh_CN.UTF-8 7z x ...。压缩包如果设置了密码命令会交互式询问脚本化运行可以加-p密码但密码明文出现在shell历史里并不安全训练服务器上传数据时尽量先解压到本地。3.2 解压后的目录结构VOC式、YOLO式和images/labels划分解压后第一件事先别急着看图片直接看目录树确认拿到的是哪套布局。常见有两种组织方式。VOC风格一般长这样medicine_kidney/ ├── JPEGImages/ # 全部图片jpg/png混放 ├── Annotations/ # 与图片同名的XML标注 └── ImageSets/ └── Main/ # 可选的官方train/val划分YOLO风格一般长这样medicine_kidney/ ├── images/ │ ├── train/ # 训练图片 │ └── val/ # 验证图片 ├── labels/ │ ├── train/ # 训练TXT │ └── val/ # 验证TXT └── data.yaml # 有的压缩包自带有的需要自己写两种布局的核心差异不在文件夹名字而在“有没有提前把训练集和验证集分开”。下载包如果直接给了images/labels结构训练起来最省事但要警惕图片编号和标签编号是否真的对得上。如果只给了VOC式的平铺目录常见做法是自己划分并整理成YOLO布局而不是直接拿平铺目录去喂yolo因为yolo默认从images/train和labels/train两个路径读数据路径不配对会在训练阶段暴露排查起来比解压时多花几倍时间。整理目录时可以用下面的命令先建骨架mkdir -p images/train images/val labels/train labels/val cd JPEGImages for f in *.jpg; do [ -f ../Annotations/${f%.jpg}.xml ] || echo missing xml $f done这段循环把JPEGImages下每张jpg和同名的xml做对照xml缺失的记录会打印出来。病理数据集常见的问题是图片文件齐全但部分XML是后续补的文件名里多了一个副本后缀循环一跑就现形。3.3 同名文件与资源占用训练目录不能混入多余的txt目录结构理完再做一次数量核对。理想情况下图片总数与TXT/XML总数相等差的往往是空标签、重复文件或截断解压的残留echo images: $(find images -name *.jpg -o -name *.png | wc -l) echo labels: $(find labels -name *.txt | wc -l)两个数字对不上时优先排查源文件而不是直接删。常见三种情况某张图漏了标注某张图有两个名字不同的TXT以及解压中途磁盘写满导致文件只有几KB。前两种用脚本批量查最后一种可以看文件大小分布正常的TXT至少十几字节为空或小于10字节的大概率是空标签。空标签在yolo里并不非法但会让该图在训练时频繁被采样而没有任何正样本信息浪费训练时间。数据量达到4000多张时每次训练前跑一遍这个统计成本只有几秒钟换来的是一次稳定的启动。4. 训练前数据校验把5类数据的漏标、越界和失衡挡在yolo面前4.1 标签合法性检查一个脚本处理字段数、类id和坐标范围解压、建目录、数量对上了不等于标签合法。yolo训练对标签的容错很低标签文件里出现6列、或类id超出nc、或坐标归一化后大于1都会导致训练进程崩掉或默默丢弃目标。下面的脚本对全部txt做一轮硬校验# check_labels.py 遍历labels目录 from pathlib import Path label_dir Path(labels/train) num_classes 5 # 与data.yaml里nc一致 bad 0 for txt in sorted(label_dir.glob(*.txt)): text txt.read_text().strip() if not text: print(f{txt.name}: 空标签) bad 1 continue for line_no, line in enumerate(text.splitlines(), 1): parts line.split() if len(parts) ! 5: print(f{txt.name}:{line_no} 字段数{len(parts)}) bad 1 continue cid, x, y, w, h parts cid int(cid) x, y, w, h map(float, (x, y, w, h)) if not (0 cid num_classes): print(f{txt.name}:{line_no} 类id{cid} 越界) bad 1 if not (0.0 x 1.0 and 0.0 y 1.0): print(f{txt.name}:{line_no} 中心点越界 x{x} y{y}) bad 1 if not (0.0 w 1.0 and 0.0 h 1.0): print(f{txt.name}:{line_no} 宽高异常 w{w} h{h}) bad 1 print(fbad lines: {bad})这里字段数必须等于5多一个空格拆出来的空字符都不行类id从0开始所以num_classes5时合法范围是0到4中心点和宽高的校验用大于等于0、小于等于1的闭区间但宽高还要额外排除等于0的退化框。跑完这个脚本输出的坏样本绝大多数不需要人工修而是回到标注源头重新导出一次。这个脚本本身不修改文件只做报告修数据要在生成TXT的环节解决改这里没有意义。4.2 类别平衡与目标尺寸这两个统计决定data.yaml和imgsz合法不等于均衡。5类病变在病理数据里的分布通常很不平均正常肾小球的样本可能比某个罕见病变多出十几倍。训练前统计每个类别的目标数能直接预判loss曲线会不会被头部类带偏from collections import Counter from pathlib import Path counter Counter() area_ratio [] # 目标框面积占整图面积的比例 for txt in Path(labels/train).glob(*.txt): for line in txt.read_text().strip().splitlines(): parts line.split() if len(parts) ! 5: continue cid, _, _, w, h parts counter[int(cid)] 1 # 归一化坐标下整图面积1w*h 就是目标占整图面积比例 area_ratio.append(float(w) * float(h)) p sorted(area_ratio) print(类别目标数:, counter) print(框面积占比 P50%.4f P90%.4f % ( p[len(p) // 2], p[int(len(p) * 0.9)]))输出里两个指标分别回答两个问题。类别目标数决定要不要做类别加权采样框面积占比决定训练输入分辨率如果P50小于0.5%说明半数以上目标都只是图上一个很小的区域直接以640分辨率训练小目标可能在多次下采样后被压缩成几个像素。此时要么把imgsz提到1024要么在推理阶段做切块slice检测。5类数据里如果某一类长期只有几十个目标训练到中后期val loss会震荡需要特别关注验证集里这类目标的召回率而不只是整体mAP。4.3 按图片划分train/val固定随机种子别按目标划分目录整理好、标签校验通过下一步正式划分数据集。划分要按图片切不能按目标切同一张图里的5个框属于同一个采样单位把一张图的不同目标分到训练和验证会形成信息泄漏mAP虚高得离谱。下面脚本按8:2划分并生成目录import random from pathlib import Path import shutil random.seed(42) src_img Path(images/all) # 平铺图片目录 src_lab Path(labels/all) # 平铺标签目录 train_img Path(images/train) val_img Path(images/val) train_lab Path(labels/train) val_lab Path(labels/val) for p in [train_img, val_img, train_lab, val_lab]: p.mkdir(parentsTrue, exist_okTrue) imgs sorted(src_img.glob(*.jpg)) random.shuffle(imgs) val_count int(len(imgs) * 0.2) for i, img in enumerate(imgs): lab src_lab / (img.stem .txt) if not lab.exists(): continue # 上一轮校验应该已经暴露这类问题 if i val_count: shutil.move(str(img), str(val_img / img.name)) shutil.move(str(lab), str(val_lab / lab.name)) else: shutil.move(str(img), str(train_img / img.name)) shutil.move(str(lab), str(train_lab / lab.name))固定随机种子之后同一份数据每次划分结果一致不同算法比较才有公平基础如果直接用time当种子换一次机器结果就漂移难复现。上面脚本会跳过缺失标签的图这个跳过逻辑在完整数据里应该一次都不触发一旦触发就说明校验步骤没有真正闭环。如果数据集自带ImageSets/Main下的train.txt和val.txt优先沿用官方的划分而不是自己重新分因为VOC式数据集里官方划分往往考虑了类别分布。自己划分时最稳妥的做法是从4.2的类别统计出发保证两个集合里各类目标占比接近简单场景下8:2随机基本够用。5. YOLOv8训练配置与参数dataset.yaml怎么填损失函数先看哪三项5.1 dataset.yaml的三个关键字段path、train/val路径与names顺序数据校验通过后进入YOLOv8训练。第一步不是敲命令行而是写data.yaml。很多从VOC转过来的人习惯把xml里的类别名做成txt文件yolov8并不认这种间接方式它读的只有yaml。一个最小可用的yaml长这样# medicine_kidney.yaml path: /workspace/datasets/medicine_kidney train: images/train val: images/val nc: 5 names: 0: gsc 1: emt 2: tat 3: ifb 4: inf这里path是训练时的工作根目录最好写成绝对路径train和val是相对于path的文件夹路径不要写成../images/train这种相对上层目录的写法yolov8虽然支持相对路径解析但改了启动位置就失效。nc和names必须与第2.3节CLASSES列表的顺序完全一致只要错一位所有标签的数字含义就整体平移训练不报错但mAP会低到离谱。判断yaml写没写对的最快方式是在训练命令前加一条yolo detect val modelyolov8n.pt datamedicine_kidney.yaml模型里自带的coco权重和这个5类yaml不匹配会直接报category数不一致报错信息比训练中途出现好排查得多。5.2 训练命令与损失函数关键字loss三项权重和close_mosaic数据规模只有4122张且标注目标通常是小区域病理结构模型优先考虑yolov8n或yolov8s不要一上来就yolov8l。基础训练命令yolo detect train datamedicine_kidney.yaml \ modelyolov8n.pt \ epochs120 imgsz640 batch16 \ patience30 close_mosaic10单独拆参数epochs120对这种规模够用配合patience30让训练在验证指标连续30轮不涨时提前停imgsz640是默认值如果4.2统计发现目标占比很小改成1024要同时关注显存占用batch16在12GB显存附近比较稳显存紧张降到8close_mosaic10表示最后10个epoch关闭mosaic增强。YOLOv8的损失函数由三块组成box_loss用于回归框位置cls_loss用于类别分类dfl_loss用于优化框边界。训练输出的results.csv里Box、Cls、DFL三列就是这三项的数值如果Cls在后期一直降而Box不动说明类别层还在过拟合解决方向是增强或正则而不是继续加epoch。默认权重里box权重最大病理图中小目标多、box回归难收敛时可以尝试放大box部分的权重但ultralytics没有直接在命令行暴露这项配置通常的做法是在模型配置或损失回调里改日常训练先用默认值跑通基线再考虑损失重构。AMD显卡跑yolo也是同一套命令前提是PyTorch装的是ROCm版本训练阶段大多数算子能复用CUDA生态的写法但个别自定义算子需要验证第一次跑建议先用yolov8n小模型跑通流程再上完整数据。5.3 训完看什么best.pt、last.pt和results.png训练结束输出在runs/detect/trainN目录里面有weights/best.pt、weights/last.pt和results.png。best.pt是验证集指标最优的权重last.pt是最后一轮的权重两者差距大说明训练后期过拟合明显。results.png里的val/box_loss和val/cls_loss两条曲线如果降到谷底又反弹基本上就是过拟合信号此时要回退到best.pt而不是last.pt。还要顺手看一眼每类的P和R第4.2统计里数量最少的类别这一项通常垫底如果它的recall在0.5以下说明模型压根没学到该类特征单靠调损失权重救不回来得回数据侧补样本。6. 验证不只盯mAP把错误案例转回VOC XML交给病理复核6.1 用best.pt跑val同时看mAP50和mAP50-95训练完先用验证集跑一次正式评测yolo detect val modelruns/detect/trainN/weights/best.pt \ datamedicine_kidney.yaml \ imgsz640 splitval输出里的mAP50和mAP50-95要分开解读。病理目标本身边界模糊mAP50只看交并比0.5以上的命中数值容易虚高mAP50-95把IoU阈值拉高更接近临床场景对边界的要求。如果mAP50有0.8但mAP50-95只有0.3说明框的位置和大小普遍不够准优先调输入分辨率和dfl相关参数而不是继续堆epoch。6.2 从YOLO预测结果生成VOC XML让医生在熟悉的环境复核yolo原生输出是TXT和一叠可视化图病理科复核时往往不习惯看图上的框更愿意在标注工具里逐张核对。常见做法是把预测结果逆转换成VOC XML这样能和原始标注XML放到同一个工具里对照。实现不复杂# yolo2xml.py 把单个预测TXT还原成XML骨架 # names 与训练时 data.yaml 里的 names 保持一致 def yolo_to_xml(img_name, img_w, img_h, lines, names): return fannotationfilename{img_name}/filename sizewidth{img_w}/widthheight{img_h}/heightdepth3/depth/size {.join( fobjectname{names[int(parts[0])]}/name fbndboxxmin{(float(parts[1]) - float(parts[3])/2) * img_w:.0f}/xmin fymin{(float(parts[2]) - float(parts[4])/2) * img_h:.0f}/ymin fxmax{(float(parts[1]) float(parts[3])/2) * img_w:.0f}/xmax fymax{(float(parts[2]) float(parts[4])/2) * img_h:.0f}/ymax f/bndbox/object for parts in lines)}/annotation调用时为每张验证图准备一个预测TXT传入原始图片宽高即可得到对应的VOC XML。这套逻辑与第2.3节脚本是同一条换算链路的反向操作两条链路都跑通才算真正把双格式数据集的价值用上了。出现争议病例时把原始标注XML和预测XML一起交给病理医生比发一张可视化图的沟通成本低很多。6.3 错误分类统计把6.1预测结果按真实类别和预测类别做混淆矩阵yolo val本身会输出混淆矩阵图但多数人没有保存。如果复检发现两个形态相近的病变类别被反复混淆优先怀疑的不是模型而是训练标签里存在系统性标注漂移常见做法是抽20个混淆样本回标注工具里目检。漏检集中在某一类时看它的目标数占总量比例确认尾部类是否需要做一次过采样增强。每个数据集在训练脚本跑通之后最好把预测XML和原始XML放在同一目录里用diff工具比对逐条核对差异直到混淆样本清空。本文还有配套的精品资源点击获取
返回列表