
简介这是一份面向计算机视觉初学者与目标检测开发者的橘子识别数据集适用于水果分拣、农业采摘机器人、零售结算等场景下的模型训练与算法验证。数据集采用Pascal VOC与YOLO双格式标注包含1699张jpg图片并配套1699个VOC格式xml文件与1699个YOLO格式txt文件标注类别仅orange一类共6302个矩形框全部由labelImg工具人工绘制标注准确合理。压缩包为7z格式共2000个文件其中xml与txt标注文件占主体整体约269.74MB目录结构清晰可直接用于YOLO系列或VOC接口的训练流程。目前已有661人学习下载适合需要快速获取单类别检测数据、验证数据增强与训练管线的读者参考使用。需注意该数据集不对训练所得模型或权重文件的精度作任何保证。1. 橘子数据集1690张VOCYOLO格式从拿到压缩包到跑通第一轮训练你从某个渠道拿到一个名为「橘子数据集1690张VOCYOLO格式」的压缩包解压后大概率会看到两个并列的目录一个装着 JPEG 图片和同名 XML 标注另一个装着 images 和 labels 的 txt 文件。很多人第一反应是「直接丢进 YOLO 训练脚本」结果要么路径对不上要么类别号错位要么验证集里一张橘子都没有。这个数据集真正解决的问题是给目标检测任务提供一个规模适中、双格式对齐的柑橘类果实样本集合1690 张的体量既不会让新手在 CPU 上等到崩溃也足够让熟手验证一个改进模块是否真的涨点。它适合三类人想跑通 YOLO 全流程的入门者、需要做农业视觉检测原型的开发者、以及拿它当小样本基线来对比自己模型的研究者。VOC 和 YOLO 两种格式同时存在意味着你既能用 XML 做数据清洗和可视化也能直接喂给 YOLO 训练器省掉一次格式转换的折腾。2. 拆开压缩包VOC 与 YOLO 两套目录到底怎么对应2.1 VOC 格式的目录结构与字段含义VOC 格式的核心是每张图片配一个同名 XML里面记录了图片尺寸、目标类别和边界框的左上角与右下角坐标。一个标准的 VOC 目录通常长这样橘子数据集/ ├── JPEGImages/ # 所有 jpg 图片 ├── Annotations/ # 与图片同名的 xml 标注 ├── ImageSets/ │ └── Main/ # train.txt / val.txt / trainval.txt └── SegmentationClass/ # 本数据集一般没有可忽略XML 里真正影响训练的是这几个字段size下的width和height决定坐标归一化的分母object下的name是类别字符串bndbox下的xmin/ymin/xmax/ymax是绝对像素坐标。很多翻车现场就出在xmax等于width或ymin大于ymax这种框在 YOLO 里会直接产生负宽高训练时 loss 变 NaN。2.2 YOLO 格式的 txt 行与归一化规则YOLO 格式每张图对应一个 txt每行五个数class_id x_center y_center width height全部是相对于图片宽高的归一化值范围 0 到 1。注意这里的 width 和 height 是框的宽高不是右下角坐标。转换公式是x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h如果数据集里只有橘子一个类别class_id 就是 0如果有青橘、熟橘、烂橘就要先确认类别映射表别想当然按字母序排。2.3 用脚本核对两套格式是否一一对应拿到数据集先别急着训练跑一段核对脚本确认图片数、XML 数、txt 数三者一致且没有空标注文件。import os from pathlib import Path root Path(橘子数据集) img_dir root / JPEGImages xml_dir root / Annotations lbl_dir root / labels imgs {p.stem for p in img_dir.glob(*.jpg)} xmls {p.stem for p in xml_dir.glob(*.xml)} lbls {p.stem for p in lbl_dir.glob(*.txt)} print(图片数:, len(imgs)) print(XML数:, len(xmls)) print(txt数:, len(lbls)) print(图片有但XML缺失:, imgs - xmls) print(XML有但图片缺失:, xmls - imgs) print(图片有但txt缺失:, imgs - lbls) # 检查空标注 empty [p.name for p in lbl_dir.glob(*.txt) if p.stat().st_size 0] print(空标注文件:, empty[:10], 共, len(empty))这段脚本的逻辑很直白用文件名主干做集合运算差集就是缺失项。参数上唯一要注意的是图片扩展名有的数据集混用.jpg和.JPG在 Linux 下大小写敏感glob(*.jpg)会漏掉大写。如果发现空标注要么是负样本要么是标注漏了负样本可以保留但要在训练配置里允许漏标的必须补。3. 从 VOC 转 YOLO转换脚本与四个边界坑3.1 转换脚本的完整实现虽然标题说数据集已经带了 YOLO 格式但实际拿到的包经常只有 VOC 是完整的YOLO 目录可能是空的或者类别号不对。自己写一遍转换脚本比盲目信任压缩包靠谱。import xml.etree.ElementTree as ET from pathlib import Path from PIL import Image classes [orange] # 按你的实际类别顺序改 cls2id {c: i for i, c in enumerate(classes)} def convert(xml_path, img_path, out_path): tree ET.parse(xml_path) root tree.getroot() w int(root.find(size/width).text) h int(root.find(size/height).text) # 用实际图片尺寸兜底防止 XML 里的 size 写错 with Image.open(img_path) as im: real_w, real_h im.size if (w, h) ! (real_w, real_h): w, h real_w, real_h lines [] for obj in root.iter(object): name obj.find(name).text.strip() if name not in cls2id: continue bnd obj.find(bndbox) xmin float(bnd.find(xmin).text) ymin float(bnd.find(ymin).text) xmax float(bnd.find(xmax).text) ymax float(bnd.find(ymax).text) # 裁剪到图片范围内 xmin, xmax max(0, xmin), min(w, xmax) ymin, ymax max(0, ymin), min(h, ymax) if xmax xmin or ymax ymin: continue xc (xmin xmax) / 2 / w yc (ymin ymax) / 2 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{cls2id[name]} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) out_path.write_text(\n.join(lines), encodingutf-8) img_dir Path(橘子数据集/JPEGImages) xml_dir Path(橘子数据集/Annotations) out_dir Path(橘子数据集/labels) out_dir.mkdir(exist_okTrue) for xml_path in xml_dir.glob(*.xml): stem xml_path.stem img_path img_dir / f{stem}.jpg if not img_path.exists(): continue convert(xml_path, img_path, out_dir / f{stem}.txt)逻辑说明先读 XML 的 size再用 PIL 读真实尺寸做校验两者不一致时以真实图片为准这是防止坐标越界的第一道保险。坐标裁剪到[0, w]和[0, h]是第二道保险。宽高小于等于零的框直接跳过避免产生 NaN。归一化保留六位小数足够 YOLO 使用。参数说明classes列表的顺序决定 class_id必须和训练时的data.yaml里names顺序完全一致。:.6f是格式化精度改成.4f也能跑但小目标框会有精度损失。3.2 边界坑一坐标越界与零宽高框现象是训练几个 epoch 后 loss 突然变 NaN。原因通常是某个框的xmax等于xmin或者xmax超过了图片宽度导致归一化后 width 大于 1。YOLO 的损失函数里对宽高取 log零宽高直接产生负无穷。解决办法就是在转换阶段做裁剪和过滤上面脚本已经处理。如果你拿到的是现成 YOLO 格式也要写个校验脚本扫一遍bad [] for p in Path(橘子数据集/labels).glob(*.txt): for i, line in enumerate(p.read_text().splitlines()): parts line.split() if len(parts) ! 5: bad.append((p.name, i, 字段数不对)) continue _, xc, yc, bw, bh map(float, parts) if not (0 xc 1 and 0 yc 1 and 0 bw 1 and 0 bh 1): bad.append((p.name, i, line)) print(异常行数:, len(bad))3.3 边界坑二类别名大小写与空格VOC 的name字段里可能出现Orange、orange带尾空格、orange_ripe等变体。如果不做strip()和统一映射转换后会出现同一类被拆成多个 class_id或者本该保留的类被continue跳过。我一般会在转换前先统计所有出现的 namefrom collections import Counter names Counter() for xml_path in Path(橘子数据集/Annotations).glob(*.xml): root ET.parse(xml_path).getroot() for obj in root.iter(object): names[obj.find(name).text] 1 print(names)看到统计结果再决定classes列表比拍脑袋写靠谱。3.4 边界坑三训练集与验证集划分泄漏1690 张如果按 8:2 随机划分同一棵树上拍的连续帧可能同时进训练和验证导致验证指标虚高。农业数据集常见的问题是同一场景多张连拍划分时要按图片文件名前缀或拍摄批次分组保证同一组只出现在一个集合里。简单做法是按文件名排序后每隔 5 张取 1 张做验证而不是用random.shuffle。import random stems sorted(p.stem for p in Path(橘子数据集/JPEGImages).glob(*.jpg)) val stems[::5] train [s for s in stems if s not in set(val)] Path(train.txt).write_text(\n.join(fimages/{s}.jpg for s in train)) Path(val.txt).write_text(\n.join(fimages/{s}.jpg for s in val))3.5 边界坑四data.yaml 路径与类别数不匹配YOLO 训练报AssertionError: nc ! len(names)或者找不到图片基本都是data.yaml写错。一个可用的配置长这样path: /home/user/橘子数据集 train: train.txt val: val.txt nc: 1 names: [orange]path是数据集根目录train和val是相对于path的列表文件。列表文件里写的是相对path的图片路径。nc必须等于names长度也必须等于转换时classes的长度。三者不一致训练要么直接报错要么静默把类别学乱。4. 用 YOLO 训练橘子检测模型配置、命令与参数怎么调4.1 环境与预训练权重选择训练前先确认环境Python 3.8 以上PyTorch 与 CUDA 版本匹配ultralytics 包能正常 import。预训练权重用官方发布的 YOLO 系列权重即可新手从 yolov8n 或 yolov5s 这种小模型起步1690 张数据量下小模型收敛快、过拟合风险低。如果你要做模型改进对比固定同一个预训练权重和同一组超参只改网络结构否则涨点来源说不清。4.2 最小训练命令与每个参数的含义yolo detect train \ data/home/user/橘子数据集/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/orange \ namebaselinedata指向配置文件model是预训练权重epochs是最大轮数imgsz是输入分辨率batch是批大小lr0是初始学习率patience是早停耐心值project和name决定输出目录。1690 张在单张 12G 显存的卡上batch16和imgsz640通常能跑显存不够就降到 8 或把imgsz降到 512。lr00.01是 YOLO 的常用默认值如果你换了更大的模型或者做了注意力改进可以降到 0.001 观察前 10 个 epoch 的 loss 曲线。4.3 训练中该盯哪些指标训练日志里重点看三列box_loss、cls_loss、mAP50。box_loss持续下降说明框回归正常如果它震荡或者上升检查标注质量。cls_loss在单类别任务里应该很快降到接近零降不下去说明类别号有噪声。mAP50是验证集上的主指标1690 张单类橘子正常收敛后能到 0.85 以上如果卡在 0.3 不动八成是验证集划分泄漏或者标注框大面积偏移。混淆矩阵在单类任务里只有一个格子看的是漏检和误检比例别被「总合不唯一」这种说法绕进去单类就是对角线一个数。4.4 训练完成后的推理验证yolo detect predict \ modelruns/orange/baseline/weights/best.pt \ source/home/user/橘子数据集/JPEGImages \ conf0.25 \ saveTrueconf0.25是置信度阈值低于它的框不显示。验证时挑几张有遮挡、有逆光、有青橘混熟的图看框是否贴合。如果框普遍偏大可能是训练时imgsz和推理时不一致如果漏检小橘子把imgsz提到 960 再训一轮或者检查标注里小目标框是否被过滤掉了。5. 避坑与排查橘子数据集训练中最容易翻车的五件事5.1 现象训练 loss 正常但 mAP 始终为 0原因验证集列表文件里的路径写错YOLO 找不到验证图片或者验证集图片全部没有对应标注。解决打开val.txt逐行核对路径用ls确认文件存在再检查验证集对应的 labels 目录是否有非空 txt。5.2 现象训练到一半报 CUDA out of memory原因batch或imgsz超过显存或者workers开太多导致内存碎片。解决把batch减半imgsz降到 512workers设为 4 或 8。如果还不行用yolo detect train ... ampFalse关闭混合精度显存会省一点但速度变慢。5.3 现象推理时框全图乱飞置信度很低原因类别数不匹配模型输出的通道数和data.yaml的nc不一致或者加载了错误的权重。解决确认nc和names长度一致确认best.pt是本次训练产出的不要拿别的数据集的权重来推理。5.4 现象同一张图重复检测出多个橘子框原因NMS 的iou阈值设得太高重叠框没被抑制。解决推理时加iou0.45训练时检查标注里是否有重复框。VOC 转 YOLO 时如果同一个 object 被写了两次就会产生重复标注。5.5 现象验证集指标很高但实际部署效果差原因训练集和验证集来自同一批连拍分布太接近。解决重新按拍摄批次划分或者留出一个完全独立的场景做测试集。1690 张如果全是同一果园同一角度模型换到另一个果园就会掉点这是数据集的边界不是模型的问题。6. 把 1690 张用透小数据集的增强策略与基线对比技巧1690 张在目标检测里属于小数据集直接训容易过拟合但也不是只能靠堆数据。我一般会做三件事第一开启 YOLO 内置的增强mosaic1.0、mixup0.1、hsv_h0.015、hsv_s0.7、hsv_v0.4这些参数在data.yaml同级传进去或者写在训练命令里。第二把close_mosaic设为最后 10 个 epoch 关闭 mosaic让模型在真实分布上收尾。第三如果要做模型改进对比固定随机种子seed42跑三次取平均单次涨点 0.5 个 mAP 说明不了问题。yolo detect train \ data/home/user/橘子数据集/data.yaml \ modelyolov8n.pt \ epochs150 \ imgsz640 \ batch16 \ mosaic1.0 \ mixup0.1 \ close_mosaic10 \ seed42 \ projectruns/orange \ nameaug增强参数不是越大越好。mixup超过 0.2 在单类任务里会让框变得模糊hsv_v超过 0.5 会让暗光橘子过曝。我习惯先跑一轮默认增强做基线再逐项加每次只改一个参数看验证集 mAP 的变化方向。还有一个容易被忽略的点把 VOC 的 XML 留着别删。当你发现某些验证集误检时可以回到 XML 里查原始标注确认是标注错了还是模型错了。YOLO 格式的 txt 没有图片尺寸信息单独看 txt 无法还原框的绝对位置XML 是唯一的后悔药。最后说个习惯每次训练完把data.yaml、训练命令、results.csv和best.pt的路径记在一个run_notes.md里。1690 张的数据集你可能要跑十几轮对比没有记录两周后根本想不起来哪组参数对应哪个结果。这个习惯帮我省过很多重复劳动希望帮到你。本文还有配套的精品资源点击获取