苹果目标检测数据集制作:VOC标注转YOLO格式全流程解析

📅 发布时间:2026/9/1 7:39:14
苹果目标检测数据集制作:VOC标注转YOLO格式全流程解析 简介面向YOLO目标检测任务的苹果缺陷数据集共收录700张真实场景下的高清苹果图片覆盖不同光照、角度与背景并已划分好训练集与验证集可直接用于模型训练与效果评估。所有图片均使用LabelImg人工标注生成与图片一一对应的VOC格式XML文件标注框质量高涵盖苹果缺陷检测所需的目标类别适合苹果分拣、农产品外观质检等场景也可作为目标检测入门练手数据。资源压缩包内共1426个文件其中713个JPG图片与713个XML标注文件成对存放结构清晰包体仅5.66MB轻量易下载。图片与标注文件同名对应便于脚本批量读取与增广训练、验证子集目录明确无需额外划分搭配现有YOLO训练流程即可快速启动。已有1330人学习下载适合希望节省标注成本、集中精力调优模型的目标检测开发者和AI初学者。 做目标检测的朋友应该都体会过最花时间的往往不是模型结构而是数据准备。我自己最开始接触YOLO时光是搞明白标注格式就折腾了整整两天网上教程一搜一大把但要么只讲VOC或者YOLO其中一种要么就是拿几个现成文件糊弄一下真到自己标注一批苹果数据时各种问题全冒出来了。这篇博文就把我积累的经验完整写出来——怎么把苹果目标检测数据集标成VOC格式再转成YOLO训练能直接用的标签文件附带完整的代码和避坑清单新手照着操作就能跑通有基础的也可以看看我在细节上的处理思路。1. 整体设计思路为什么选用VOC格式作为中间格式1.1 先定方案再动手标注苹果目标检测说白了就是教会模型在图片里找到苹果的位置和大小这和检测猫狗、行人、车辆本质上是同一类任务都属于目标检测范畴。做这种任务绕不开数据标注格式的选择市面上主流格式无非三种VOC、YOLO、COCO。VOC格式是XML文件存储标注信息YOLO格式是TXT文件存储归一化坐标COCO格式则是JSON文件。很多初学朋友直接一上来就用LabelImg标注成YOLO格式这个思路不能说错但有一个比较麻烦的问题YOLO格式的TXT文件一旦保存信息和原始图像的对应关系就变得非常隐晦中间如果出了错很难排查。而VOC格式的XML文件非常直观打开就能看到图像尺寸、目标类别、边界框坐标这些信息后期检查、修改、清洗数据都方便得多。所以我的习惯是标注阶段一律使用VOC格式等数据清洗完毕、准备送入模型之前再统一转成YOLO格式。这条思路既保留了排查的便利性也兼顾了YOLO训练的高效性。1.2 苹果检测数据集的特殊性苹果这类目标有一个特点——很多是圆形或椭圆形物体互相之间容易重叠遮挡。这和车辆检测、行人检测类似但又更麻烦苹果长在树上时枝叶遮挡严重苹果放在货架上时前后堆叠导致边界不清晰甚至一张照片里可能有几十个苹果密密麻麻的。这要求标注时对边界框的划定有明确规范否则不同标注员或者你自己分几天标注的标准不一致模型训练出来的效果就会很飘。另外苹果目标大小差异极大。近距离拍摄的特写可能撑满整张图远景的果园照片里苹果可能只有二三十个像素大小。这种情况下如果统一用一种标注策略小目标很容易被漏标或标偏直接影响模型的召回率。1.3 任务规划先整理再标注动手之前建议把整个流程分成五个阶段每一步都有明确产出物心里才踏实图像采集与筛选收集苹果相关图片统一格式、重命名、剔除模糊或重复的图片。标注规范制定确定类别名称、边界框标注标准、难度样本的处理方式。使用标注工具制作VOC格式XML这一步是核心工作在标注软件里手动或辅助完成。数据校验与增强检查标签是否有遗漏、错标、错位适当做数据增强。转换为YOLO格式并划分数据集编写脚本把VOC格式换成YOLO训练所需的TXT格式按比例划分训练集、验证集和测试集。2. VOC格式标注实操一步步把苹果框出来2.1 图像采集与预处理标注之前必须先把原始图像处理利索。我用过不少现成的苹果数据集也自己爬过图、拍过照整理下来的统一标准是图片统一用JPG格式分辨率最好不低于640x640因为YOLO训练时通常会把输入resize到这个尺寸。如果原图太小标注框的分辨率信息就会丢失很多模型学到的特征质量会明显下降。图片命名要规范我的习惯是apple_0001.jpg这种格式序号从0001开始递增不要用中文名、不要有空格。这里还有个容易踩的坑——图片命名不要以数字开头否则很多脚本处理的时候会产生莫名其妙的排序问题。另外采集时要尽量覆盖不同场景室内白底、室外自然光、清晨逆光、傍晚暗光、树上挂果、果篮堆放、单果特写、多果远景这些场景越丰富模型的泛化能力越好。处理完图片之后把它们统一放到一个名为JPEGImages的文件夹里。这个命名是VOC数据集的惯例虽然不是强制要求但沿用惯例总比自创一套要省心。2.2 标注工具选型与配置VOC格式标注的首选工具我推荐LabelImg这是目前使用面最广的开源图形化标注工具。它支持Windows、Linux、macOS安装简单导出格式直接就是VOC XML。对比其他工具比如labelme主打多边形分割、CVAT需要部署服务器对于只做矩形框目标检测的场景LabelImg是最轻量高效的方案。LabelImg的安装有几种方式最简单的直接用pip安装pip install labelImg如果pip安装遇到问题也可以从GitHub上clone源码运行这里不展开。启动之后界面里需要设置的最关键一项是默认标注保存目录我把这个目录直接设置为VOC数据集的Annotations文件夹这样标注出来的XML文件就直接落到了正确位置省去后期移动的麻烦。2.3 标注规范与边界框划定标准开始标注之前先定义标注规范这是很多新手最容易忽略的环节。我再强调一遍规范不提前定好标了一半再发现标准不一致返工成本非常高。我自己标注苹果时用的是这样几条标准类别名称统一用apple全部小写。YOLO训练时类别名是大小写敏感的Apple和apple会被当作两个不同类别这种错误相当隐蔽排查起来很头疼。边界框要贴合苹果轮廓但又不能太紧。苹果的轮廓虽然接近圆形但标注框是矩形所以框的四边应该和苹果的外切矩形基本重合允许有1到2个像素的容差。框太紧会把苹果边缘的暗部切掉一部分框太松又会把背景也框进去这两种情况都会影响模型的学习效果。相互遮挡的苹果可见部分只要超过苹果整体大小的30%就应该单独标注出来。完全被遮挡的苹果不标。这里有个小技巧如果两个苹果紧挨着边界框可以适当重叠不要因为强迫症硬把框切得刚好不重叠那样反而会让模型学不到“两个物体相邻”这种真实情况。对于模糊的苹果例如运动模糊或景深虚化导致边缘不清晰的只要还能判断出轮廓就标注如果已经无法判断边界位置直接不标。宁可不标也不要标一个边界错误的框因为错误标注对模型训练的干扰远比漏标大。在LabelImg里快捷键可以大幅提高标注效率W键画框、A键切换上一张、D键切换下一张、CtrlS保存。熟练之后一天标几百张不是问题。2.4 检查XML标签文件标完一批图之后随手打开一个XML文件检查是很有必要的。一个标准的VOC格式XML文件长这样annotation folderJPEGImages/folder filenameapple_0001.jpg/filename size width1920/width height1080/height depth3/depth /size object nameapple/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin326/xmin ymin189/ymin xmax612/xmax ymax431/ymax /bndbox /object /annotation其中size里的宽高必须和原始图像完全一致否则后续坐标归一化时计算会出错而且这类错误在训练阶段非常难发现。bndbox里的xmin、ymin、xmax、ymax就是苹果的边界框像素坐标注意这个坐标体系是以图像左上角为原点的向右为x轴正方向向下为y轴正方向。有的教程里会提到difficult这个参数如果某张图里的目标特别难可以标记为1但在YOLO训练中这个参数通常不参与计算所以统一设0就好。3. 从VOC到YOLO格式转换的核心原理与代码实现3.1 理解两种格式的本质区别VOC用像素坐标表示边界框xmin是框左边的x坐标ymin是框上边的y坐标xmax是框右边的x坐标ymax是框下边的y坐标。YOLO格式则完全不同它只用一行五个数字表示一个目标类别编号 中心点x 中心点y 框宽 框高其中后四个数字全部是归一化到0到1之间的比例值也就是除以了图像的宽和高。这就是为什么转换不是简单改个扩展名而是要做一次坐标系的换算。换算公式如下center_x (xmin xmax) / 2.0 / image_width center_y (ymin ymax) / 2.0 / image_height width (xmax - xmin) / image_width height (ymax - ymin) / image_height这里有一个新手特别容易搞混的地方YOLO归一化是除以整张图像的宽和高而不是某些人以为的除以边界框自身的宽高。每张图片的尺寸可能不同所以每张图片对应的TXT里的比值是基于这张图自己的宽高来计算的不同图之间没有可比性。3.2 Python实现VOC转YOLO现在直接上代码。我用的是xml.etree.ElementTree标准库来解析XML不需要额外安装第三方依赖。完整代码如下import os import xml.etree.ElementTree as ET # 类别映射表只需维护这一个列表顺序就是YOLO类别的编号 class_names [apple] def convert_voc_to_yolo(xml_file, output_dir): tree ET.parse(xml_file) root tree.getroot() # 获取图像尺寸 size root.find(size) img_width int(size.find(width).text) img_height int(size.find(height).text) # 生成输出文件名保持和图像同名扩展名为txt base_name os.path.splitext(os.path.basename(xml_file))[0] output_file os.path.join(output_dir, base_name .txt) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: # 遇到未知类别时跳过并给出提示避免静默出错 print(f警告: 未知类别 {name} 在 {xml_file} 中已跳过) continue class_id class_names.index(name) bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # 严格校验坐标合法性 if xmin xmax or ymin ymax: print(f错误: {xml_file} 中 {name} 的边界框坐标非法已跳过) continue if xmin 0 or ymin 0 or xmax img_width or ymax img_height: print(f警告: {xml_file} 中 {name} 的边界框超出图像范围) # 核心转换公式 center_x (xmin xmax) / 2.0 / img_width center_y (ymin ymax) / 2.0 / img_height width (xmax - xmin) / img_width height (ymax - ymin) / img_height lines.append(f{class_id} {center_x:.6f} {center_y:.6f} {width:.6f} {height:.6f}) with open(output_file, w) as f: f.write(\n.join(lines) \n if lines else ) def batch_convert(annotation_dir, output_dir): os.makedirs(output_dir, exist_okTrue) for xml_file in os.listdir(annotation_dir): if xml_file.endswith(.xml): convert_voc_to_yolo(os.path.join(annotation_dir, xml_file), output_dir) print(f已转换: {xml_file}) if __name__ __main__: batch_convert(Annotations, YOLOLabels)运行这个脚本需要在命令行执行python convert.py前提是把脚本文件放到VOC数据集根目录下并确保Annotations和YOLOLabels文件夹都存在。执行完毕后YOLOLabels文件夹下就会生成和图片一一对应的TXT文件。3.3 数据集的划分与目录结构整理转换完成后还有最后一步关键操作划分数据集。YOLO训练时需要一个存放标签路径的文件列表通常是train.txt和val.txt每行是图片的绝对路径或相对路径训练时框架会根据图片路径自动找对应的TXT标签文件。目录结构上我长期使用的标准布局是这样的dataset/ ├── images/ │ ├── train/ # 训练集图片 │ └── val/ # 验证集图片 ├── labels/ │ ├── train/ # 训练集标签TXT │ └── val/ # 验证集标签 ├── train.txt # 训练集图片路径列表 ├── val.txt # 验证集图片路径列表 └── apple.yaml # YOLO数据配置文件这里需要注意一个匹配原则图片和标签的文件名必须完全一致不含扩展名且对应放置。如果图片在images/train/apple_0001.jpg标签就必须在labels/train/apple_0001.txt。转换脚本和划分脚本里我把图片和标签放在不同目录因为YOLO训练时默认的目录规则就是images和labels两个兄弟目录。划分数据还有一个小建议如果采集的图片有多张来自同一个视频片段或者同一批拍摄环境建议把同场景的图片尽量划到同一个集合里避免训练集和验证集之间出现数据泄露导致验证指标虚高。4. 常见问题与排查技巧实录4.1 中文路径和特殊字符导致读取失败很多新手标完数据后运行训练脚本报错排查到最后发现是数据集路径带中文。YOLO的很多依赖库对中文路径兼容性并不好尤其是Windows环境下各种编码问题层出不穷。解决方案非常简单粗暴数据集的整个路径从根目录开始就全部用英文字母、数字和下划线不要有任何中文和空格。4.2 类别文件缺失或名称不一致训练YOLO时通常需要在apple.yaml里配置类别列表而很多朋友是照着别人的配置文件改的结果自己数据集里的类别名称、数量和顺序跟配置对不上。这里最容易出问题的是标注时用了Apple配置里写的是apple或者标注时只标了一种苹果配置却写了三种类别。这些错误不会直接报错但训练出来的模型表现会非常诡异。4.3 坐标越界与归一化结果异常标注时手抖把框拖到图像边缘之外是再常见不过的事。这种越界框在VOC格式里看不太出来但转成YOLO格式后边界框的中心坐标就可能出现大于1或者小于0的情况YOLO训练时遇到这种异常标签会直接导致loss变成无穷大。所以代码里写了坐标校验逻辑同时在项目里运行一个检查脚本是值得的逐行扫描所有TXT标签看是否有坐标落在0到1之外一旦发现立即返回XML检查。4.4 转换时对象名与类别不匹配有一种比较隐蔽的错误XML里类别名在类别列表里不存在。比如你定义了apple但某个XML里写的是Apples或apple_tree。转换脚本会打印警告然后跳过但如果你粗心没看警告就会漏掉这个目标导致模型学不到这种样本。4.5 个人习惯的一键检查和可视化辅助为了省事我一直在用的一个方法是把转换后的TXT标签重新画回原图上直观地检查标注是否准确。代码逻辑很简单读取原图遍历TXT的每行数据把归一化坐标换算回像素坐标使用OpenCV的rectangle函数画出边界框把生成的图片保存下来人工过一遍就知道标注效果如何。import cv2 def draw_yolo_labels(image_path, label_path, class_names): img cv2.imread(image_path) height, width img.shape[:2] with open(label_path, r) as f: for line in f.readlines(): parts line.strip().split() class_id int(parts[0]) center_x float(parts[1]) * width center_y float(parts[2]) * height box_width float(parts[3]) * width box_height float(parts[4]) * height xmin int(center_x - box_width / 2) ymin int(center_y - box_height / 2) xmax int(center_x box_width / 2) ymax int(center_y box_height / 2) cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 255, 0), 2) cv2.putText(img, class_names[class_id], (xmin, ymin - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow(check, img) cv2.waitKey(0) cv2.destroyAllWindows()这个方法对排查坐标系统错乱尤其管用如果画出来框的位置和苹果对应不上说明转换环节的坐标换算有问题直接可以定位到是哪一步算错了。5. 训练前再叮嘱几句数据准备工作做到这里基本已经拼齐了从原始图片到YOLO训练所需数据的完整链路。转换脚本、校验脚本和可视化脚本都跑通后整个流程已经沉淀成一套固定的自动化工具链后续再做其他目标检测项目比如检测橘子或者车辆只需要修改类别列表和重新标注即可复用。根据我自己的实操经验一个小型的苹果检测数据集比如几千张图、几万个标注框用LabelImg手工标注大概需要两到三天转格式和可视化检查只需要几十分钟。如果想要提升标注效率可以先用一个预训练的YOLO模型对图片做预标注然后在LabelImg里手动修正速度能提升不少。市面上像X-AnyLabeling这类标注工具也已经集成了模型辅助标注的能力感兴趣的可以试试。最后再分享一个我踩过好几次的坑训练过程中发现验证集loss一直正常下降但实际检测的时候大量漏检尤其是小苹果。排查了很久才发现问题出在标注阶段——远景图片里的小苹果很多被我自己漏标了。模型根本没见过这种样本当然检测不出来。所以在标注阶段宁可花时间多检查几遍也好过训练完之后再回头补数据。每一张小目标的正确标注都可能直接影响模型在实际场景中能不能用。本文还有配套的精品资源点击获取