ARTICLE DETAIL

资讯详情

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

VOC2007苹果数据集处理攻略:从格式转换到YOLO训练准备

VOC2007苹果数据集处理攻略:从格式转换到YOLO训练准备 简介面向YOLOv3目标检测的苹果数据集与配套Python处理代码针对目标识别与检测方向的初学者及研究者提供了从原始图像到标注数据的完整准备流程数据集规模适中便于进行小样本训练与参数调优。压缩包共2000个文件大小约90.81MB包含1648张jpg图片、820个xml标注文件、4个txt说明及3个Python脚本其中原始苹果图片414张经数据增强、resize和填充预处理后生成828张图片xml文件由LabelImg标注工具以预选框形式生成。已有1073人学习该资源。读者可获得可直接用于YOLO系列训练的标注数据集和数据处理代码省去自行采集图片与手工标注的时间代码中自带的预处理逻辑还能灵活适配其他目标检测任务既适合算法学习也可作为实验对比的基准数据。1. 一份VOC2007格式的苹果照片数据集到底能帮你省下多少事做目标检测的人大多经历过这种状态模型选好了、训练脚本调通了结果卡在数据上——网上下的数据集要么类别对不上要么标注不是残缺就是越界最后自己拿LabelImg一张张框框到凌晨两点连验证集都没凑齐。这份“苹果照片数据集和python处理代码-VOC2007.zip”解决的就是这个痛点它不是一堆零散图片而是按PASCAL VOC2007组织规范打包好的苹果检测数据图片归图片、标注归标注、划分名单归划分名单再配上一份能直接改改就跑的Python处理脚本。常见做法是把VOC格式转成YOLO能读的txt、按比例切出训练验证集、再画框检查一遍标注质量这套流程走完你的数据集就算立住了。适合正在入门目标检测、想跳过“自己造标注”这一步的人也适合做果蔬检测项目时需要一份干净数据起步的从业者。这里的“苹果照片”指的是水果苹果不是手机拍的照片这一点先分清后面所有操作才不会跑偏。2. 先拆开VOC2007的目录结构三个文件夹各管什么数据里藏着哪些规则2.1 解压后先看这三个目录JPEGImages、Annotations、ImageSetsVOC2007这个格式最早来自PASCAL VOC挑战赛后来成了目标检测领域的事实标准很多开源检测框架原生支持它。解压数据集后先别急着写代码把目录结构摸清楚后面每一步都会顺畅。典型的VOC2007布局是JPEGImages里放所有原图Annoations里放与图片同名的XML标注文件ImageSets/Main里放train.txt、val.txt这类划分名单。这套规则的核心是“同名关联”图片叫apple_001.jpg标注就必须是apple_001.xml训练名单里写apple_001程序才知道去哪个目录找对应文件。先确认你的解压结果是不是符合这个预期顺便检查里面是否有多余的隐藏文件或损坏图片。在这个数据集里图片一般就是苹果在不同背景、不同光照下的照片有的近景有的远景有的一个果框里挤了好几个苹果。这里的“图片数量、类别数量、每张图标注框密度”属于数据集的实物特征我没法替作者报数但按VOC2007的惯例打开某一两张图对应的XML就能搞清楚标注风格。建议先建一个干净的workspace把解压后的目录层级固定住后续所有脚本都在这个结构上跑能省掉很多路径报错。# 解压后先看一眼结构 mkdir -p workspace cd workspace unzip ../苹果照片数据集和python处理代码-VOC2007.zip # 查看目录骨架只显示到两层避免刷屏 find . -maxdepth 2 -type d | sort # 随便抽一张图、一个标注、一个名单确认内容真实存在 ls JPEGImages | head -5 ls Annotations | head -5 cat ImageSets/Main/train.txt | head -10unzip路径记得按实际压缩包所在位置调整find命令是为了确认目录名到底叫JPEGImages还是JPEGImages_很多人踩过大小写或下划线的坑head只取前几条是避免一口气打几百个文件名刷屏。这套动作不是走过场因为你后面写转换脚本时目录名和文件名规则会被硬编码进代码里一开始看错了后面全盘报错。2.2 读懂Annotations里的XML标注一个苹果框要写清哪些字段VOC2007的XML标注不是随便写的它有一套固定的字段结构你不需要记住全部标签但必须认得出核心那几组。打开一个XML文件能看到folder、filename、source、size、object这几大块。size里记录图片的宽、高、深度通道数object里是每个目标的标注包含name即类别名、pose、truncated、difficult以及关键的bndbox——xmin、ymin、xmax、ymax这四个整数框出苹果在图像中的位置单位是像素。这些字段里真正影响训练结果的是name和bbox数值truncated表示目标是否被图片边缘截断difficult表示这个目标是否因太小或太模糊而难以识别。如果标了difficult1很多训练代码会默认忽略这个框这会影响你的有效标注数量。我的惯用做法是先用脚本把所有XML扫一遍统计一下类别名是否统一、bbox是否越界、每个对象是否都完整包含边框坐标这一步跑完数据集的底子好不好基本心里有数。别等到训练的时候发现类别名一个写成apple一个写成Apple模型就默认它们是两类了。2.3 用Python把VOC2007组织规则验证一遍坐标、类别、数量一个不漏前面手动确认只是粗筛接下来要写一段Python脚本做自动化体检。这一步能一次性发现文件夹缺失、图片打不开、XML解析不了、类别名不一致四种问题。具体做法是遍历Annotations目录用xml.etree.ElementTree解析每个XML提取filename和object信息再检查对应的JPEG图片是否存在、尺寸是否和XML里记录的size一致。这里用到的就是Python标准库和OpenCV如果你还没装好Python环境建议先补齐python基础语法和第三方库安装Pillow和cv2是本阶段的主力。import os import xml.etree.ElementTree as ET annotations_dir Annotations images_dir JPEGImages error_log [] class_count {} for xml_name in sorted(os.listdir(annotations_dir)): if not xml_name.endswith(.xml): continue xml_path os.path.join(annotations_dir, xml_name) tree ET.parse(xml_path) root tree.getroot() filename root.findtext(filename) img_path os.path.join(images_dir, filename) if not os.path.exists(img_path): error_log.append(f{xml_name}: 图片不存在 {img_path}) continue size root.find(size) width int(size.findtext(width)) height int(size.findtext(height)) for obj in root.findall(object): name obj.findtext(name) class_count[name] class_count.get(name, 0) 1 bndbox obj.find(bndbox) xmin int(bndbox.findtext(xmin)) ymin int(bndbox.findtext(ymin)) xmax int(bndbox.findtext(xmax)) ymax int(bndbox.findtext(ymax)) if xmin 0 or ymin 0 or xmax width or ymax height: error_log.append(f{xml_name}: 越界框 xmin{xmin}, ymin{ymin}, xmax{xmax}, ymax{ymax}, 图片尺寸{width}x{height}) if error_log: print(发现以下问题) for e in error_log: print(e) else: print(所有标注都通过基础检查) print(类别统计, class_count)先用findtext拿到filename字段再去解析图片尺寸等于用“XML说的”去核对“目录里有的”任何一边对不上都会记入error_log。最后打印类别统计确认所有对象都标成了apple而不是混入apple_green、apple_red这类不统一的名字。这个脚本不是一次性的后面扩数据、改标注、加新类别都能复用。参数层面如果你手里的图片还带多类别可以把class_count做成字典套字典但单类别苹果检测用这个程度就够了。运行完这一步如果报错是0数据集才算真正接管进你的工作流。3. 用Python把VOC2007转成YOLO格式坐标归一化与数据集划分一次搞定3.1 为什么转YOLO是当前最常用的第一步VOC2007的XML标注是“像素绝对坐标”也就是框的位置直接以图片像素值记录图片多大坐标就写到多大。这种格式肉眼友好但对训练并不方便尤其是数据增强阶段图片会被随机缩放、裁剪绝对坐标无法跟着变换。YOLO系模型使用的是归一化的相对坐标cx、cy表示目标中心的相对位置w、h表示目标框相对于图片宽高的比例四个值全在0到1之间。这种格式不依赖图片原始尺寸模型在训练时对输入做拉伸缩放不会破坏标注的正确性。所以社区里最普遍的处理方式就是先把VOC的XML标注转成txt格式的YOLO坐标文件。实践中转格式的脚本雷同度很高但细节决定成败归一化时是除以图片宽还是除以注释里的size宽类别编号从0还是1开始一个类别一个txt还是所有类别共用这些问题一旦搞错训练出来的模型mAP直接为零。这里不涉及某个特定框架而是整个YOLO生态的共同约定每个txt文件对应一张图片txt里的每一行对应图片里的一个目标框格式是 class_id cx cy w h全部是空格分隔的浮点数。3.2 转换脚本XML解析、归一化、类别映射一次完成下面这段脚本是完整的VOC转YOLO工具输入Annotations目录和JPEGImages目录输出每张图片对应的txt文件并按顺序写到一个labels目录里。脚本执行完毕后还会生成一份classes.txt记录类别名顺序后续配YOLO训练配置时会用到。import os import xml.etree.ElementTree as ET # 类别白名单顺序就是最终训练时的类别编号 # 如果数据集只有apple一种编号始终为0 class_name_to_id {apple: 0} annotations_dir Annotations images_dir JPEGImages labels_dir labels os.makedirs(labels_dir, exist_okTrue) for xml_name in sorted(os.listdir(annotations_dir)): if not xml_name.endswith(.xml): continue xml_path os.path.join(annotations_dir, xml_name) tree ET.parse(xml_path) root tree.getroot() filename root.findtext(filename) img_path os.path.join(images_dir, filename) # 读取真实图片宽高而不是依赖XML里的size字段 # 因为部分数据集size字段和实际图片不一致会造成归一化偏移 import cv2 img cv2.imread(img_path) if img is None: print(f警告无法读取 {img_path}已跳过) continue img_height, img_width img.shape[:2] txt_out os.path.join(labels_dir, os.path.splitext(xml_name)[0] .txt) lines [] for obj in root.findall(object): name obj.findtext(name) if name not in class_name_to_id: print(f跳过未知类别 {name}) continue bndbox obj.find(bndbox) xmin float(bndbox.findtext(xmin)) ymin float(bndbox.findtext(ymin)) xmax float(bndbox.findtext(xmax)) ymax float(bndbox.findtext(ymax)) # YOLO格式要求的中心点坐标 cx (xmin xmax) / 2.0 / img_width cy (ymin ymax) / 2.0 / img_height w (xmax - xmin) / img_width h (ymax - ymin) / img_height lines.append(f{class_name_to_id[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) if lines: with open(txt_out, w) as f: f.write(\n.join(lines)) print(转换完成)不是直接取XML里的size字段除一下而是重新用cv2读图片拿真实宽高这条是血泪经验。因为有些数据集制作时先裁剪了图片但忘了同步更新XML里的size或者XML里写的是原始分辨率图片却已经被压过一遍这种情况直接除会得到偏到离谱的标注。cx、cy、w、h统一保留6位小数太短会损失精度太长txt文件无意义地变大。类别白名单用dict维护未来如果数据集中新增了类别只需在class_name_to_id里加一行。3.3 生成train.txt和val.txt划分文件按比例随机切分并固定随机种子YOLO训练时还需要两份文件train.txt和val.txt里面每一行是图片的绝对路径或相对路径。划分比例没有硬性规定apple这类单类别数据通常8:2或9:1就够如果目标场景里图片变化不大甚至7:3也能用。这里的关键是要保证划分的随机性可复现也就是每次运行划分脚本进训练集和验证集的图片名单必须一致否则两次训练的验证结果没法对比。固定随机种子是基本操作random.seed(42)在Python社区里算是个约定俗成的惯例。import os import random random.seed(42) images_dir JPEGImages image_files [f for f in sorted(os.listdir(images_dir)) if f.endswith((.jpg, .jpeg, .png))] random.shuffle(image_files) val_ratio 0.2 val_count int(len(image_files) * val_ratio) with open(train.txt, w) as f: for img in image_files[val_count:]: f.write(os.path.join(os.path.abspath(images_dir), img) \n) with open(val.txt, w) as f: for img in image_files[:val_count]: f.write(os.path.join(os.path.abspath(images_dir), img) \n) print(f训练集 {len(image_files) - val_count} 张验证集 {val_count} 张)注意train.txt里写的是绝对路径这是很多YOLO框架的默认要求。如果你后续要把项目换到别的机器上绝对路径就得全部重写所以一个更稳妥的做法是写相对workspace的路径比如JPEGImages/apple_001.jpg。为了避免路径错乱可以把路径写法接到上面代码的os.path.abspath处按需改成相对路径。建议保存一份相对路径版本和一份绝对路径版本训练时用哪个都行。验证集不需要太多苹果这类背景差异不大的目标200张验证图已经能稳定评估。4. 可视化校验与故障排查标注框是否越界、图片是否损坏、路径是否对得上4.1 用OpenCV在原图上画框先“看”再训练坐标归一化之后一个常见误区就是不检查就直接开训练。标注框的真实位置只有画在图上才知道是正还是歪尤其是从网上下载的数据集标注质量参差不齐。我一般会写一个可视化脚本随机抽十几张图用cv2.rectangle把原图上的框画出来把类别和置信度概念用在这个阶段就是“人工目测回归”。这一步能发现坐标偏移、框没有贴住苹果边缘、框太大把背景兜进去一堆等肉眼可见的问题。import os import random import cv2 import xml.etree.ElementTree as ET random.seed(2024) annotations_dir Annotations images_dir JPEGImages xml_files [f for f in os.listdir(annotations_dir) if f.endswith(.xml)] sample random.sample(xml_files, min(16, len(xml_files))) for xml_name in sample: tree ET.parse(os.path.join(annotations_dir, xml_name)) root tree.getroot() filename root.findtext(filename) img_path os.path.join(images_dir, filename) img cv2.imread(img_path) if img is None: print(f跳过无法读取的图片: {img_path}) continue for obj in root.findall(object): name obj.findtext(name) bndbox obj.find(bndbox) xmin int(bndbox.findtext(xmin)) ymin int(bndbox.findtext(ymin)) xmax int(bndbox.findtext(xmax)) ymax int(bndbox.findtext(ymax)) cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 255, 0), 2) cv2.putText(img, name, (xmin, max(0, ymin - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) out_path os.path.join(check_vis, os.path.splitext(xml_name)[0] _vis.jpg) os.makedirs(check_vis, exist_okTrue) cv2.imwrite(out_path, img) print(f已保存 {out_path})画框是在原始像素坐标上进行的所以这里读的是XML里的bndbox原始值不是归一化后的YOLO坐标。cv2.rectangle的第四个参数是颜色第五个是线宽线宽2在1920宽的大图上可能会显得细可以改成3。cv2.putText里fontScale取值0.6适配800到1500像素宽的图太小的图字会糊成一片可以调成0.4。跑完这批图逐张打开check_vis目录下的结果重点看有没有框跑出图片边界、框是不是包住了整个苹果但没包住太多背景。4.2 用Python做一致性体检文件名、标注内容、图片尺寸三重核对画完框肉眼看一轮之后还要再跑一遍硬性的数据一致性检查。这里的核心思路是保证三个目录里的数据能够互相咬合JPEGImages里每张图都有对应的XML标注和对应的标签txt反过来标注和标签文件也都能在图片目录里找到源图。训练框架一般不会帮你检查这些一旦出现只有图片没有标注的情况训练过程会直接报错而图片和标签文件数量对不上是最容易犯的错误。import os sets [train, val] for s in sets: with open(f{s}.txt) as f: lines [line.strip() for line in f if line.strip()] missing_txt 0 missing_xml 0 for line in lines: img_relpath line.replace(os.path.abspath(JPEGImages) os.sep, ) stem os.path.splitext(img_relpath)[0] label_path os.path.join(labels, stem .txt) xml_path os.path.join(Annotations, stem .xml) if not os.path.exists(label_path): missing_txt 1 print(f{s} 集缺少标签: {stem}) if not os.path.exists(xml_path): missing_xml 1 print(f{s} 集缺少XML: {stem}) print(f[] {s}集检查完成: 共{len(lines)}项, 缺标签{missing_txt}, 缺XML{missing_xml})这个脚本的价值在于把图片集合和标签集合交叉验证了一遍。train.txt里写的是图片路径将其转成stem文件名后到labels目录和Annotations目录里找对应文件找不到就是漏了。如果某张图没有对应XML那么它也不会有标签在YOLO训练中会因负样本没有被隐式管理而报错。跑通这一步后基本可以确认数据管线没有断裂点。遇到缺标签的情况别手动补一个假框直接回到标注工具里重新标注嘴里保留原始画面省得补一个歪框反而污染模型。5. 苹果照片数据集使用避坑指南常见问题与处理办法5.1 现象某些图片坐标算出来是负数或大于1转换脚本运行时偶尔会出现 yolo_cy 或 yolo_w 算出来小于0或大于1的情况。这类异常坐标不会让程序报错但会让模型在训练时认为目标中心跑到图片外面去了。原因有两个层面一是XML里bndbox的坐标本身就越界比如xmax写成了比width还大的值二是图片实际尺寸比XML记录的size字段大正常目标框在缩小的原图里反而看起来越界。解决这类问题最干净的办法是在转换脚本里加一个过滤逻辑凡是归一化后cx、cy、w、h超出(-0.05, 1.05)这个容差范围的先把原始XML数据打印出来人工判断。如果坐标只是轻微越界可以做一个裁剪把框限制在图片范围内如果越界很多说明标注本身有误直接丢掉这个目标框不要整个图片报废。从数据角度讲丢掉几个脏框比带上疯狂越界框更划算。5.2 现象同一个苹果被标成两个框打开check_vis生成的可视化图有时会发现同一个苹果身上叠着两个差不多大小的框一个偏左一个偏右或者一个大框里面套着一个更小框。这是因为制作数据集时不同标注员对“苹果边界”的理解不一致有人把苹果梗算进去了有人不算。在VOC2007里这类重复标注很常见特别是密集目标场景。解决方式有两种。一种是用IoU合并计算两个同类框的交并比如果大于0.8就合并成一个框取两个框的外接矩形或加权平均位置。另一种是设置置信度优先级来源图里哪个人工标注更贴近苹果主体的边缘用哪个。实际项目中我用IoU0.7作为合并阈值合并完之后再人工抽检一轮防止误合并掉相邻但不同的苹果。合并功能可以写在转换脚本之前作为独立预处理步骤改完XML后重新执行转换。5.3 现象YOLO训练时提示图片路径找不到训练报错最常见的不是模型问题而是路径问题。报错信息一般是FileNotFoundError或类似提示打开train.txt一看里面的路径是当时做转换那台机器的绝对路径比如C:\Users\xxx\data\JPEGImages\apple_001.jpg换到服务器或者别人电脑上就找不到了。解决方法是不要用绝对路径改成相对路径train.txt里只写JPEGImages/apple_001.jpg并且训练前把工作目录切换到包含JPEGImages的根目录。另外很多人容易忽略的是Windows和Linux的路径分隔符问题如果txt文件在Windows上生成转到Linux训练会带着反斜杠。要么在生成脚本里统一用os.path.join和正斜杠要么在训练前做一个文本替换。一个通用的做法是在生成train.txt时不要调用os.path.abspath直接用相对路径写入兼容性最好。5.4 现象训练集和验证集里出现同一张图数据集划分如果不严格控制会出现同一张图片既在train.txt又在val.txt这会导致验证指标虚高模型作弊了你还不知道。很多下载来的数据集压缩包已经自带了train.txt和val.txt但你不清楚它是按什么比例切的。如果想要随机重切一份却不固定随机种子每次运行结果都不同两次训练对比的基准就乱了。我的做法是重切之前先检查现有划分是否重叠用Python直接set交集判断有没有同一个filename出现在两份名单中。如果有直接放弃原划分用固定种子重切一遍。推荐在训练脚本里把划分代码独立成一个文件固定random.seed并注释说明用途这样同事或未来的你跑同一份划分结果完全一致不会出现“你的验证集和我的验证集不一样指标没法比”的争论。5.5 现象用cv2.imread读返回None转换脚本跑着跑着突然报“无法读取图片”大概率是文件路径里带中文或者图片后缀是.JPG但实际内容是PNG格式又或者图片在传输过程中损坏了。OpenCV的imread对中文路径是出了名的支持不好哪怕在Windows下文件存在也会返回None这是个经典玄学问题。解决方式有两个方向。第一种是文件处理前用os.path.exists检查存在性再加一个后缀白名单只允许.jpg、.jpeg、.png三种其他后缀直接跳过并打日志。第二种是从源头清理把所有文件名统一重命名为英文数字加下划线去掉空格和中文用一条rename脚本全量替换。顺手修复损坏图片需谨慎如果文件头损坏导致数据真丢了宁可重新下载也不要硬补。最省事的做法是运行前批量用PIL打开一遍图能打开的正常保留打不开的单独列出来人工决定去留。6. 把数据集用好一个直接能跑的HSV与Mosaic增强配置代码跑到这个阶段你的苹果数据集已经被清洗干净并转成了YOLO格式下一步就是投入训练。这里给一个不依赖具体框架的增强配置思路它能把数据集里的苹果泛化能力往上拉一截尤其是光照变化场景里苹果红色在不同角度下差异极大。HSV增强对颜色类目标效果明显色相H调整偏移幅度在±5以内避免绿色叶子被改成蓝色饱和度S和明度V可以放得更开±20附近的值能模拟不同天气和光线。Mosaic增强是YOLOv4之后被广泛验证有效的在线增强方式把四张图拼成一张训练等于一次迭代看到四个不同场景等于变相放大了batch size。import random import cv2 import numpy as np def hsv_augment(img, h_shift5, s_shift25, v_shift25): hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV).astype(np.float32) h_shift random.randint(-h_shift, h_shift) s_shift random.randint(-s_shift, s_shift) v_shift random.randint(-v_shift, v_shift) hsv[:, :, 0] (hsv[:, :, 0] h_shift) % 180 hsv[:, :, 1] np.clip(hsv[:, :, 1] s_shift, 0, 255) hsv[:, :, 2] np.clip(hsv[:, :, 2] v_shift, 0, 255) return cv2.cvtColor(hsv.astype(np.uint8), cv2.COLOR_HSV2BGR) img cv2.imread(JPEGImages/apple_001.jpg) aug hsv_augment(img) cv2.imwrite(apple_001_aug.jpg, aug)hsv_augment函数里mod 180是针对OpenCV的H通道取值范围0到179很多人忘了这一点直接加了再写回会溢出。S和V通道用clip而不是取模和真实拍摄场景更接近。这套增强不会改变标注坐标因为整张图做的是色调偏转没有几何变形不需要同步修改txt文件里的框。除HSV之外还可以调亮度和对比度来扩大数据泛化空间但同一份数据里叠加太多种增强方式容易让模型学到噪声循环里一张图最多做两种变换就够了。验证方式很简单把你的增强代码挂到训练数据管线里跑一个短周期训练对比无增强版本在val集上的mAP如果提升超过2个点说明增强方向是对的否则调回保守参数。这是我几年跑检测项目的一个习惯——数据清洗和增强的功夫永远花在训练前面数据集的底子决定了模型天花板参数只是逼近天花板的手段。希望帮到你。本文还有配套的精品资源点击获取
返回列表