简介:一套可直接用于YOLOv3模型训练的苹果照片数据集,包含Python预处理代码与标注文件,面向从事目标识别与检测的开发者、研究者,也适合刚接触目标检测的入门学习者,解决数据集整理、尺寸不统一和格式适配等常见痛点。资源包整体约2000个文件,以JPG图片、XML标注文件为主,另有少量txt说明和py脚本;包内收录原始苹果图片414张,并提供828张经数据增强、resize和填充处理后的标准图片,同时附有labelImg预选框形式标注的XML结果,压缩包约90.81MB。目前已有1074人学习浏览,热度适中,适合正在进行YOLO系列实验或苹果实例检测项目的人员参考复用。配套Python代码可帮助使用者快速完成批量图片缩放、填充与增强,逐步生成符合YOLOv3输入格式的样本集;借助已标注文件,也能直接用于模型训练前的数据划分与验证,省去手动整理标签的重复劳动。
1. 苹果数据集与 YOLOv3 目标识别:一份带 Python 处理代码的 VOC2007 资源
做苹果检测的人,最头疼的往往不是模型选型,而是缺一份标注干净、格式规整的数据集。公开数据集里车牌有 CCPD、遥感舰船有 HRSC2016,但苹果这种农作业单类目标,反而很难找到直接能喂给 YOLOv3 的资源。这份苹果数据集包含 414 张原始照片、经数据增强和 resize/填充处理后的 828 张图片,以及配套的 .xml 标注文件和 Python 处理代码,格式按 Pascal VOC2007 组织,标注工具是 labelimg 的预选框方式。对正在跑 YOLOv3、或者打算用同一套流程迁移到 YOLOv8 的从业者来说,它最大的价值不只是图片本身,而是那份「增强 + 标注同步 + 格式转换」的代码,能帮你把 414 张底图扩成可用的训练集,同时避免我自己踩过的那些数据坑。这篇笔记就把数据集结构、处理逻辑、训练配置和排错记录完整拆开讲。
2. 数据集拆解与 VOC2007 格式:预处理流程和它的设计逻辑
2.1 目录结构与文件清单
这份资源按 VOC2007 的标准骨架组织,拿到手先别急着跑代码,把目录结构认一遍比什么都重要。我一般会先把压缩包解压后,用tree或直接看文件管理器确认下面这几类内容。
| 路径/文件 | 内容 | 说明 |
|---|---|---|
| JPEGImages/ | Apple (编号).jpg,414 张原始图 | 原始照片,未做任何缩放,尺寸不一 |
| 预处理后图片目录 | 828 张增强+resize+填充后的图 | 每张原图对应一张增强副本,数量翻倍 |
| Annotations/ | 同名 .xml 文件 | labelimg 标注结果,包含 bndbox 坐标 |
| Python 脚本 | 数据增强、格式转换、划分脚本 | 本资源的核心复用资产 |
一个容易被忽略的点:资源和很多公开检测数据集不一样,它把「原始图」和「预处理图」分开了。这个设计很实用——原始图留给你做自定义增强、换输入尺寸;预处理图则保证你拿到手就能直接进训练流程,不用自己写 resize 逻辑。做目标检测的都知道,后续训练改动输入尺寸、调整增强策略是常态,两份数据并存能省很多反复。
2.2 labelimg 标注的 .xml 里到底存了什么
VOC2007 的标注文件是 XML 格式,labelimg 保存的就是这个。打开任意一个 .xml,内容结构长这样:
<annotation> <folder>JPEGImages</folder> <filename>Apple (472).jpg</filename> <source> <database>Unknown</database> </source> <size> <width>640</width> <height>480</height> <depth>3</depth> </size> <object> <name>apple</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>120</xmin> <ymin>80</ymin> <xmax>260</xmax> <ymax>200</ymax> </bndbox> </object> </annotation>这里<size>里的 width/height 是原图的像素尺寸,所有 bndbox 坐标都是基于这个尺寸的绝对像素值。转换 YOLO 格式时必须拿这些坐标除以原图宽高,而不是除以预处理后的尺寸。<name>是类别名,这份资源里就是 apple。<truncated>和<difficult>是 VOC 协议里两个容易忽略的字段:truncated 表示目标是否被边缘截断,difficult 表示是否因过小或遮挡而难以识别,训练时 YOLO 一般直接读 bndbox 和 name,这两个字段对训练影响不大,但转换脚本里要做好兼容,否则遇到缺失字段会直接抛异常。
另外注意文件名是Apple (472).jpg这种带括号和空格的格式。Linux 系的 shell 处理这种名字容易踩坑,后续脚本里建议统一用 Python 的glob或os.listdir去读,别在 bash 里裸跑通配符。
2.3 为什么 414 张变成了 828 张:数据增强和 fill 的取舍逻辑
从数量看,414 → 828 就是翻了一倍,最常见的做法是每张原图生成一个水平翻转副本。单类目标检测数据量本来就少,苹果这种目标形状相对规则、场景变化大的对象,翻一倍能显著提升泛化。如果资源里实际用的是旋转或亮度扰动,逻辑也是同一条:增强带来的多样性必须大于它引入的标注误差。
再说 resize 和填充。YOLOv3 默认输入是 416×416,如果你的图是 640×480 之类的不等尺寸,直接cv2.resize拉成方形会造成目标变形。苹果是圆形的,横向拉伸成椭圆后,模型学到的框宽高比就全乱了。正确做法是等比例缩放 + 灰色填充(letterbox):先按比例把长边缩放到 416,短边补灰边。这份资源里明确写了「resize 和填充」,说明走的正是 letterbox 路线,这是处理非正方形输入的标准操作,YOLOv8 的数据加载器内部也是这么做的。
关于增强策略,我的建议是:复制一份做水平翻转是最安全的选择,因为翻转时只有 x 坐标做w - x变换,y 坐标不变,不容易出错。而任意角度旋转、裁剪这类强增强,虽然能提升模型对遮挡和姿态的鲁棒性,但 bndbox 需要做完整的仿射变换甚至改写成多边形,脚本复杂度高一个量级,标注稍有偏差模型直接学歪。真要上强增强,用 imgaug 的 BoundingBoxes 同步变换,别手写坐标换算。
3. 处理代码实战:VOC 转 YOLO、数据集划分与增强脚本的落地写法
3.1 VOC 标注转 YOLO txt:脚本与换算公式
YOLOv3 原生训练用的是 txt 格式标注,每行代表一个目标,格式是类别id x_center y_center width height,四个坐标值都是归一化到[0,1]的浮点数。VOC 的 xml 转成这个格式,核心就是坐标换算。下面是我常用的转换脚本,直接放在资源目录下跑:
import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_dir, class_map): tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") w = int(size.find("width").text) h = int(size.find("height").text) lines = [] for obj in root.iter("object"): name = obj.find("name").text if name not in class_map: continue cls_id = class_map[name] 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) x_center = (xmin + xmax) / 2.0 / w y_center = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}") stem = os.path.splitext(os.path.basename(xml_path))[0] with open(os.path.join(out_dir, stem + ".txt"), "w") as f: f.write("\n".join(lines))坐标换算逻辑:YOLO 的 x_center/y_center 是归一化中心点坐标,所以先取 bndbox 的中间点,再除以图像宽高;width/height 同理。除的是原始宽高 w/h,这一点必须和 xml 里<size>块保持一致。class_map 用来做类别名到 id 的映射,单类数据集就是{"apple": 0},用 id 而不是名字,YOLO 的 obj.names 文件按行索引对应。跑完后抽查几个 txt,坐标值都应该在 0 到 1 之间,如果出现大于 1 的数,基本可以断定是 xml 里 xmax 或 ymax 写错了。
3.2 数据集划分:训练集、验证集和文件清单生成
darknet 训练时通过 train.txt 和 valid.txt 拿到图片路径列表,每行一个绝对路径。划分的原则是:原始图和它的增强副本必须进同一个集合,不能一张在训练集、一张在验证集,否则就是数据泄漏,验证指标会虚高。所以划分操作要在增强流程之后做,而不是先划分再增强。
import glob import random import os images = sorted(glob.glob("JPEGImages_aug/*.jpg")) random.seed(42) random.shuffle(images) split_idx = int(len(images) * 0.8) train_files = images[:split_idx] val_files = images[split_idx:] def write_path_list(path_list, out_file): with open(out_file, "w") as f: for p in path_list: f.write(os.path.abspath(p) + "\n") write_path_list(train_files, "train.txt") write_path_list(val_files, "val.txt")这里random.seed(42)固定随机种子,保证每次划分结果一致,方便复现。0.8/0.2 的训练验证比是检测任务的常用默认值。如果你是拿这份数据做比赛或调参对比,建议把划分好的两个 txt 文件备份一份,因为重新跑脚本会打乱顺序,调参前后对比就看不出是数据变化还是参数变化了。
特别注意:glob.glob的路径如果包含空格和括号,返回的字符串本身没问题,但写入 txt 后,darknet 在 C 层读文件按换行符分割,空格和括号不影响解析,这个实测没问题。真正要防的是在 bash 里写for f in JPEGImages_aug/*.jpg这种循环,带括号的文件名会被 shell 解释出问题,所以能用 Python 就别用 shell。
3.3 数据增强脚本:水平翻转、亮度扰动与标注同步
这是整个资源里最容易出错的部分。很多人增强图片后直接把 xml 复制一份,坐标完全没变,训练出来的框全部偏移。下面是安全的水平翻转增强,带完整的标注同步:
import cv2 import os import glob import xml.etree.ElementTree as ET xml_dir = "Annotations" img_dir = "JPEGImages" out_img_dir = "JPEGImages_aug" out_xml_dir = "Annotations_aug" os.makedirs(out_img_dir, exist_ok=True) os.makedirs(out_xml_dir, exist_ok=True) for xml_path in glob.glob(os.path.join(xml_dir, "*.xml")): stem = os.path.splitext(os.path.basename(xml_path))[0] img = cv2.imread(os.path.join(img_dir, stem + ".jpg")) h, w = img.shape[:2] flipped = cv2.flip(img, 1) tree = ET.parse(xml_path) root = tree.getroot() for obj in root.iter("object"): box = obj.find("bndbox") xmin = int(box.find("xmin").text) xmax = int(box.find("xmax").text) # 水平翻转后,xmin 变成 w - 原xmax,xmax 变成 w - 原xmin box.find("xmin").text = str(w - xmax) box.find("xmax").text = str(w - xmin) cv2.imwrite(os.path.join(out_img_dir, stem + "_flip.jpg"), flipped) tree.write(os.path.join(out_xml_dir, stem + "_flip.xml"))核心是翻转后的坐标变换:新 xmin =w - 原xmax,新 xmax =w - 原xmin。这个w必须是原图的宽,不能是 resize 后的宽。y 坐标完全不动,所以不需要改 ymin/ymax。如果还要做亮度扰动,建议叠加在翻转副本上,比如cv2.convertScaleAbs(flipped, alpha=1.2, beta=20),alpha 控制对比度倍数,beta 控制亮度偏移量,这类扰动不改变坐标,安全无副作用。
关于增强脚本,我有一条血泪经验:跑完增强后,一定要随机抽 10 对(原图、增强图)把标注框画回图上肉眼检查。脚本逻辑写错是一回事,labelimg 原始标注本身就可能有漏标和错标,增强只会把错误同步放大。你可以用cv2.rectangle画框检查,这一步花不了三分钟,但能救回一整天的训练时间。
4. 用这份数据跑通 YOLOv3:目录组织、cfg 修改与训练参数设置
4.1 常见做法:先按 darknet 的目录约定重排数据
darknet 对数据目录没有硬性要求,但约定俗成的结构是单独建一个数据目录,把图片、txt 标注、names 文件和 data 配置都归拢到一起。按 3.3 的增强脚本,输出是 JPEGImages_aug 和 Annotations_aug 两个目录,先转换成 txt 再统一移动:
mkdir -p yolov3_data/backup mv JPEGImages_aug yolov3_data/images # 把 3.1 转换出的 txt 文件也归入 data 目录 mv txt_labels yolov3_data/labels这样做的目的很直接:后续训练命令里的路径不会乱七八糟,backup 目录专门存训练过程中的权重备份,默认每迭代 10000 次存一次。darknet 在 Windows 和 Linux 下的路径分隔符处理不一致,建议全部用相对路径或在 obj.data 里写绝对路径,避免跨平台时报文件找不到。
4.2 obj.names 与 obj.data 的写法
这是 YOLOv3 训练绕不开的两个配置文件。obj.names 只有一行,因为这是单类数据集:
appleobj.data 指向训练集、验证集和配置文件:
classes = 1 train = /绝对路径/yolov3_data/train.txt valid = /绝对路径/yolov3_data/val.txt names = /绝对路径/yolov3_data/obj.names backup = /绝对路径/yolov3_data/backupclasses = 1必须和 obj.names 行数一致,和 cfg 文件里的 classes 也要一致。三个地方对不上,训练要么直接报错,要么 loss 异常。train 和 valid 指向 3.2 生成的 txt,文件名是 train.txt 和 val.txt 都没问题,但 obj.data 里字段名是 train/valid,不要写成训练集字段名。
4.3 cfg 文件必改参数:filters、classes 和 random
训练前要基于 yolov3.cfg 复制一份自己的配置,照着这份数据改三个位置。先看 filters:每个 yolo 层之前的卷积层 filters 数等于3 × (classes + 5)。单类数据集就是3 × (1 + 5) = 18。YOLOv3 有三个 yolo 层,每个 yolo 层前面都有一个[convolutional]层需要改,搜索 filters=255 的地方全部改成 18。
| 参数 | 原值 | 改为 | 说明 |
|---|---|---|---|
| filters(3 处) | 255 | 18 | 3 × (classes + 5),单类=18 |
| classes(3 处) | 80 | 1 | 对应 obj.data 的 classes |
| random | 1 | 1 或 0 | 1=多尺度训练开,显存不够改 0 |
random 这个参数很多人忽略。random=1 时每个 batch 会动态改变输入尺寸(320~608 之间随机),能显著提升模型对不同尺度目标的适应能力,代价是显存占用暴涨。如果你的显卡只有 8GB,建议改成 0,固定 416×416 训练,否则很容易 OOM。
4.4 训练命令与超参数取值参考
用 darknet 的官方训练命令,加载 darknet53.conv.74 预训练权重做迁移学习:
./darknet detector train yolov3_data/obj.data yolov3_data/yolov3-custom.cfg darknet53.conv.74 -mapdarknet53.conv.74是 darknet 官方提供的分类预训练权重,用它初始化骨干网络,只训练检测头,收敛速度能快好几倍。-map参数会在验证集上每 4 个 epoch 计算一次 mAP,用来观察模型是否真的在变好。
单类小数据集的超参数,我一般这样设:
| 参数 | 取值 | 说明 |
|---|---|---|
| batch | 64 | 每批次样本数,显存不够降到 32 并调大 subdivisions |
| subdivisions | 16 | 把大 batch 拆成小块喂给显卡,16 适合 8GB 卡 |
| learning_rate | 0.001 | 迁移学习用这个起步,比默认 0.01 稳妥 |
| max_batches | 8000 | 单类 4000~8000 足够,看 loss 是否进入平台期 |
| momentum / decay | 0.9 / 0.0005 | darknet 默认值,改动机率小 |
这套数据和配置文件同样能平滑迁移到 YOLOv8:把 txt 标注按 ultralytics 的目录结构整理成 images/ 和 labels/,然后yolo detect train data=voc.yaml model=yolov8n.pt就能跑。很多做 YOLOv8 训练自己数据集的人,卡在标注格式转换这一步,这份资源的 VOC 转 YOLO 脚本正好补上这个环节。
5. 避坑排查:这套流程里最容易翻车的五个细节
5.1 现象:训练 Loss 一直不降
训练日志里 loss 从 20 掉到 2 之后就卡住不动,甚至偶尔反弹。
原因:单类目标数据集太小,模型很快拟合了训练集,进入过拟合状态;另一个常见原因是 learning_rate 太大,loss 在最优值附近震荡。苹果这种目标外观变化有限的场景,loss 卡在 1.x 是正常的,不需要追求降到 0.1 以下。
解决:先把 learning_rate 从 0.001 降到 0.0001,看 500 次迭代内有没有下降趋势;其次确认数据增强是否只有翻转和亮度扰动,如果增强种类过于单一,加上随机裁剪和 HSV 扰动再训。最后看验证集 mAP,如果 mAP 还在升而 loss 不降,模型没坏,是 loss 曲线进入平台期,耐心等就好。
5.2 现象:检测框整体偏到图的角落或缩成一团
训练没崩,但预测时所有框都挤在某个区域,或者框的尺寸明显不对。
原因:90% 的情况是标注转换时归一化坐标算错了。最常见是把 x_center 直接除以了预处理后的宽(比如 416),而标注本身是原始像素坐标,归一化到[0,1]必须除原图宽高。如果原图是 640×480,预处理缩放到 416,两张图的宽不一样,除错就直接把坐标算飞了。另一个曾让我翻车的原因是增强后的 xml 里 width/height 没改成增强图尺寸,导致坐标错位。
解决:转换后抽一个 txt 看数值是否在[0,1]区间;更直观的方法是写几行代码把 bndbox 画回图上,看框是不是盖住苹果。这一步能做绝不跳过,我已经因为偷懒吃过一次大亏。
5.3 现象:train.txt 路径报错或图片加载不出来
darknet 训练刚开始就报Can't open image,或读了 0 张图。
原因:train.txt 里写的是相对路径,而 darknet 的工作目录和相对路径基准不一致。另外多数用户在 Windows 上生成的路径分隔符是\,Linux 下训练时解析会出问题。还有文件名里的空格和括号在某些环境下会被误解析。
解决:统一在 obj.data 里写绝对路径,txt 文件里也用绝对路径;路径分隔符统一用手工替换replace("\\", "/")。如果用的命令是./darknet detector train,注意从项目根目录执行,darknet 会把相对路径拼到当前工作目录上,所以别边 cd 边换路径。
5.4 现象:增强后 XML 和图片数量对不上
增强脚本跑完,发现图片 828 张但 xml 只有 700 多,或者有 xml 找不到同名图片。
原因:原始数据里可能有个别图片本身没有标注(比如所有苹果都被遮挡或太远),labelimg 保存时就没生成 xml;或者增强脚本对每张图生成了副本,但读取 xml 时遇到空文件抛异常中断,导致后半程没跑完。labelimg 这类工具偶尔也会写出文件名和实际图片名大小写不一致的 xml。
解决:处理前先做一轮一致性检查——遍历所有 jpg,检查同名 xml 是否存在;遍历所有 xml,反过来检查图片。缺失的直接删掉或单独挑出来人工补标。增强脚本里加个容错,遇到解析失败的 xml 打印文件名并continue,不要中断整个流程。
5.5 现象:resize 后苹果明显被拉伸变形
预处理图里苹果从圆形变成椭圆,训练出来的模型预测框宽高比也跟着变形。
原因:直接用cv2.resize(img, (416, 416))把不规则尺寸硬拉到正方形,没有保持原始宽高比。YOLOv3 对变形图像不会报错,但模型学到的目标形状特征是错的,迁移到真实场景时框就不准了。
解决:用 letterbox 方案:等比例缩放到 416×416 的短边填充。缩放比例取min(416/w, 416/h),填充后图里苹果还是圆的。注意数据增强脚本里如果用了翻转和 resize,先翻转再 letterbox,顺序反了坐标又得重算。做完之后随机看几张预处理图,确认没有可见的拉伸。
6. 进阶验证:把训练好的模型拿到真实苹果照片上,先过这三道关
模型训完,先在资源自带图片上跑一遍推理验证流程通顺,只是第一步。我做目标检测的习惯是把手里的苹果照片分成三组:一组拿来训练、一组做验证、一组专门「假装没看见」。最后一组图片不参与任何训练步骤,只用来做最终测试,这样得出的精度才有参考价值。如果手头还能拍到傍晚逆光、枝条遮挡、苹果紧挨着的照片,混进测试组里,比看的 mAP 更能反映真实场景的体感精度。
第一关是尺度适配。苹果目标在照片里的大小跨度很大:近景特写可能占图的一半,果园远处一整棵树上的苹果可能只有十几个像素。YOLOv3 对小于 32×32 的目标基本无感,所以测试时遇到小目标集中图,把输入尺寸从 416 提到 608 再训一轮,通常比调 NMS 阈值见效更快。第二关是负样本。只给模型看「有苹果的图」,它会倾向于把一切红绿色块都当成苹果。建议额外收集几十张没有苹果的果园、天空、树干照片放进验证集,观察误检率,这个操作基本不增加训练成本,但对落地体验提升很大。第三关是后处理阈值。darknet 默认置信度阈值 0.25,对单类检测来说太松,我会调到 0.5 以上。如果目标有遮挡导致检出率下降,可以同时调低 NMS 的 IoU 阈值到 0.4,两个框叠在一起时只保留得分高的那个,框会干净很多。
我曾经有一版模型在验证集 mAP 0.92,拿进果园一测全是偏移框,后来发现是增强副本泄漏进验证集导致指标虚高。从那以后我每次改数据流程或增强脚本,强制抽 10 张训练图和 10 张验证图,把标注框画回去肉眼检查同步性,确认无误才开训练,成了雷打不动的习惯。希望帮到你。
本文还有配套的精品资源,点击获取