26类蔬菜目标检测数据集:VOC+YOLO双格式7155张图片实战指南 简介这份资源是面向计算机视觉学习者与目标检测开发者的蔬菜图像数据集覆盖26类常见蔬菜适合训练YOLO或VOC格式的检测模型也可用于课程设计、算法验证与竞赛练手。压缩包共约2000个文件以1999个xml标注文件和1个说明txt为主整体约544MB图片与标注一一对应方便直接接入主流检测框架。数据集包含7155张jpg图片每张均配有对应的VOC格式xml与YOLO格式txt标注类别涵盖牛油果、豆类、甜菜、甜椒、西兰花、卷心菜、胡萝卜、花椰菜、芹菜、玉米、黄瓜、茄子、大蒜、辣椒、洋葱、豌豆、土豆、南瓜、萝卜、沙拉菜、番茄、西葫芦等其中牛油果、甜菜等类别框数较多可支撑多类别检测任务。目前已有402人学习下载适合需要现成标注数据快速开展实验的读者。1. 26 类蔬菜目标检测数据集7155 张 VOCYOLO 双格式到底能跑出什么如果你正在做智慧农业、菜品识别、无人零售称重或者单纯想找一个类别够多、标注够干净的目标检测练手数据这份 26 类蔬菜数据集值得先看一眼。它包含 7155 张 jpg 图片每张图都配了同名的 Pascal VOC 格式 xml 和 YOLO 格式 txt标注框总数按类别分布从 avocado 的 1163 框到 beans 的 307 框不等类别覆盖根茎、叶菜、果菜、瓜类等常见蔬菜。适合谁适合已经跑通过 YOLOv5/v8/v11 官方 demo、想换一套真实场景数据做迁移训练的人也适合需要 VOC 与 YOLO 双格式互转练手的标注质检岗位。不适合谁不适合想直接拿来做分割任务的人——这份数据只有检测框没有 mask也没有分割路径的 txt。下面按「拿到手怎么用、参数怎么设、坑在哪」的顺序拆开讲。2. 从压缩包到训练集VOC 与 YOLO 双格式的目录结构与转换逻辑2.1 先看清压缩包里到底有什么解压后你会看到一堆同名不同后缀的文件比如xyxr_images_1642.xml、xyxr_images_1642.txt、xyxr_images_1642.jpg外加一个说明.txt。这种命名方式说明图片和标注是一一对应的没有按 train/val 分文件夹。VOC 的 xml 里存的是绝对像素坐标xmin,ymin,xmax,ymaxYOLO 的 txt 里存的是归一化后的class_id x_center y_center width height。两者描述的是同一批框只是坐标系和存储方式不同。常见做法是先把所有 jpg 归到images/xml 归到annotations/txt 归到labels/再写脚本按比例切分。注意说明.txt里通常写了类别顺序26 个类别的 id 映射必须和训练时的data.yaml完全一致否则会出现「框对了但类别全错」的玄学现象。2.2 用 Python 做一次格式自检与划分在训练之前我一般会先跑一段自检脚本确认 xml 和 txt 的框能对上同时生成 train/val 列表。下面这段代码不依赖 pycocotools只用标准库和 Pillow适合快速排雷。import os, random, xml.etree.ElementTree as ET from PIL import Image IMG_DIR images XML_DIR annotations TXT_DIR labels CLASSES [avocado,beans,beet,bell pepper,broccoli,brus capusta, cabbage,carrot,cayliflower,celery,corn,cucumber, eggplant,fasol,garlic,hot pepper,onion,peas, potato,pumpkin,rediska,redka,salad,squash-patisson, tomato,vegetable marrow] def voc_to_yolo(xml_path, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() boxes [] for obj in root.findall(object): cls_name obj.find(name).text.strip() if cls_name not in CLASSES: continue cls_id CLASSES.index(cls_name) 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) # 归一化并转中心点格式 xc (xmin xmax) / 2.0 / img_w yc (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h boxes.append(f{cls_id} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}) return boxes def check_and_split(ratio0.8): names [f[:-4] for f in os.listdir(IMG_DIR) if f.endswith(.jpg)] random.shuffle(names) split int(len(names) * ratio) train, val names[:split], names[split:] bad [] for name in names: img_path os.path.join(IMG_DIR, name .jpg) xml_path os.path.join(XML_DIR, name .xml) txt_path os.path.join(TXT_DIR, name .txt) if not (os.path.exists(xml_path) and os.path.exists(txt_path)): bad.append(name); continue w, h Image.open(img_path).size yolo_lines voc_to_yolo(xml_path, w, h) with open(txt_path) as f: exist [l.strip() for l in f if l.strip()] if len(yolo_lines) ! len(exist): bad.append(name) print(异常样本数:, len(bad), bad[:5]) with open(train.txt,w) as f: f.write(\n.join(train)) with open(val.txt,w) as f: f.write(\n.join(val)) check_and_split()逻辑说明voc_to_yolo把 VOC 的左上右下坐标转成 YOLO 的中心点加宽高并除以图片宽高做归一化。check_and_split遍历所有 jpg检查 xml 和 txt 是否同时存在再对比转换后的框数和已有 txt 行数是否一致不一致就记入bad。参数上ratio0.8是训练验证比蔬菜类别里样本少的类比如 beans 只有 307 框建议不要低于 0.75否则验证集里可能一个框都没有。跑完看bad列表如果数量超过 5%说明压缩包里可能有损坏文件需要重新解压或找原始来源核对。2.3 生成 data.yaml 与类别权重YOLO 训练只认data.yaml路径和类别名必须写对。26 个类别名里有几个拼写容易翻车brus capusta、cayliflower、rediska、redka、squash-patisson这些不是标准英文但数据集里就是这么标的训练时不要自作主张改成cauliflower或radish否则类别 id 会错位。常见做法是直接复制说明.txt里的顺序生成 yaml。path: ./vegetable_dataset train: train.txt val: val.txt nc: 26 names: 0: avocado 1: beans 2: beet 3: bell pepper 4: broccoli 5: brus capusta 6: cabbage 7: carrot 8: cayliflower 9: celery 10: corn 11: cucumber 12: eggplant 13: fasol 14: garlic 15: hot pepper 16: onion 17: peas 18: potato 19: pumpkin 20: rediska 21: redka 22: salad 23: squash-patisson 24: tomato 25: vegetable marrownc: 26必须和 names 行数一致。如果训练时日志里出现nc26但类别名显示乱码检查 yaml 缩进YOLO 对空格敏感不要用 Tab。另外样本少的类别可以在训练命令里加--cls_pw 0.5之类的权重参数但更稳的做法是先跑一轮 baseline看混淆矩阵里哪些类被吞了再决定要不要过采样。3. 用 YOLOv8/v11 跑通第一轮训练参数、显存与类别不均衡处理3.1 环境与命令从 ultralytics 到第一次 forward假设你已经装好 PyTorch 和 ultralytics训练命令本身不复杂但参数怎么设直接决定能不能收敛。下面这条命令是我在单卡 8G 显存上跑 640 分辨率常用的配置。yolo detect train \ datavegetable_dataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ workers4 \ device0 \ optimizerSGD \ lr00.01 \ lrf0.01 \ momentum0.937 \ weight_decay0.0005 \ warmup_epochs3 \ cos_lrTrue \ close_mosaic10 \ patience30 \ projectruns/vegetable \ nameexp1逻辑说明modelyolov8n.pt是轻量 backbone适合先验证数据管线通不通如果 mAP 卡在 0.3 以下再换yolov8s.pt或yolov11s.pt。batch16在 8G 显存上跑 640 分辨率比较稳显存不够就降到 8 并开ampTrue默认开。close_mosaic10表示最后 10 个 epoch 关闭 mosaic 增强让模型在真实分布上收尾这个参数对蔬菜这种背景相对固定的场景很有用。patience30是早停26 类里有些类样本少容易过拟合早停能省时间。跑起来后看runs/vegetable/exp1/results.csv重点看metrics/mAP50-95和val/box_loss如果 box_loss 震荡不降先把lr0降到 0.005 再试。3.2 类别不均衡beans 只有 307 框怎么办26 个类别的框数差距很大avocado 有 1163 框beans 只有 307 框差了近 4 倍。这种不均衡在训练时表现为模型对 beans 的召回率明显低于 avocado混淆矩阵里 beans 经常被误判成 beet 或 fasol。常见做法有三种一是过采样把 beans 的图片在 train.txt 里重复写几次二是用copy_paste增强但蔬菜数据里豆子经常成簇出现copy_paste 容易造出不自然的重叠三是在 loss 里给稀有类加权。我一般先用第一种改 train.txt 时注意不要重复到验证集里。# 对稀有类别做简单过采样写回 train.txt import collections with open(train.txt) as f: train_names [l.strip() for l in f if l.strip()] # 统计每个类别在训练集中出现的图片数 cls_img_count collections.Counter() for name in train_names: with open(flabels/{name}.txt) as f: for line in f: cid int(line.split()[0]) cls_img_count[cid] 1 rare_ids [cid for cid, cnt in cls_img_count.items() if cnt 500] print(稀有类别 id:, rare_ids) # 对包含稀有类别的图片重复 2 次 oversampled [] for name in train_names: oversampled.append(name) with open(flabels/{name}.txt) as f: ids [int(l.split()[0]) for l in f if l.strip()] if any(i in rare_ids for i in ids): oversampled.append(name) with open(train_oversample.txt, w) as f: f.write(\n.join(oversampled)) print(原始:, len(train_names), 过采样后:, len(oversampled))逻辑说明先统计每个类别在训练集里出现在多少张图中把少于 500 张的类标记为稀有类然后对包含稀有类的图片重复一次。参数500是经验阈值你可以根据说明.txt里的框数分布调整。过采样后把data.yaml的train指向train_oversample.txt重新训练。注意不要重复太多次否则模型会对这几张图过拟合验证集 mAP 反而掉。如果过采样后 beans 的 AP 还是低于 0.2考虑把 beans 和 fasol 合并成一个「豆类」大类但这会改变类别数需要同步改nc和 names。3.3 验证与推理怎么看结果是不是「假高」训练完看results.png和confusion_matrix.png。如果 mAP50 很高但 mAP50-95 很低说明框的位置不够准常见于标注框贴边或长宽比极端的情况。蔬菜数据里 carrot 和 cucumber 是细长条YOLO 的 anchor 如果没适配宽高回归会吃亏。可以在data.yaml同级加一个anchors.yaml自定义 anchor或者直接用 YOLOv8 的 anchor-free 头省去这步。推理时用yolo detect predict modelruns/vegetable/exp1/weights/best.pt sourcetest_images saveTrue conf0.25conf0.25是默认阈值蔬菜类别多建议先调到 0.3 减少误检再根据业务需求微调。如果发现某类蔬菜总是漏检先看训练集里这类图片是不是背景太暗或遮挡严重而不是急着改模型结构。4. 避坑与排查26 类蔬菜数据训练时最容易翻车的 5 个点4.1 现象训练日志里 nc26 但类别名全是乱码原因data.yaml里 names 用了中文冒号或 Tab 缩进YOLO 解析时把整行当成了一个类别名。解决用英文冒号加空格缩进统一用两个空格保存为 UTF-8 无 BOM。改完用python -c import yaml; print(yaml.safe_load(open(data.yaml)))验证一遍。4.2 现象xml 和 txt 框数对不上自检脚本报 bad原因压缩包里部分 xml 的object节点有重复或者 txt 里有多余空行。解决在voc_to_yolo里加去重逻辑按(cls_id, xc, yc, w, h)四舍五入到 4 位小数后去重txt 读取时用if l.strip()过滤空行。如果 bad 列表里某个文件反复出现直接把它从 train/val 里剔除不要硬修。4.3 现象训练到 20 epoch 后 loss 突然变 NaN原因学习率太大或某张图里有宽高为 0 的框。解决先检查标注用脚本扫一遍所有 txt把w0或h0的行删掉然后把lr0从 0.01 降到 0.005加warmup_epochs5。如果还 NaN换optimizerAdamW并设lr00.001。4.4 现象验证集 mAP 很高但实际推理时同一张图重复检测原因验证集和训练集有重叠图片或者过采样时把验证集图片也写进了 train。解决切分数据时先固定随机种子确保 train 和 val 没有交集过采样只在 train 上做。用set(train) set(val)检查交集非空就重新切。4.5 现象beans 和 fasol 总是互相误判原因这两个类别在视觉上确实接近且样本都少。解决先看混淆矩阵确认误判方向如果 beans→fasol 多就在训练时对 beans 过采样如果 fasol→beans 多反过来。实在分不开就合并类别但要在说明.txt里备注避免后续标注混乱。5. 进阶技巧用 26 类蔬菜数据做开放词汇与 TensorRT 加速的验证如果你已经跑通 baseline想进一步压榨这份数据的价值可以试两个方向。第一个是开放词汇检测的零样本验证拿 YOLO-World 或 Grounding DINO用tomato、carrot这类文本提示直接推理看模型在没见过的蔬菜图片上能不能框出来。这份数据的 26 个类别名正好可以当 prompt 列表但注意brus capusta、rediska这些非标准拼写开放词汇模型可能不认识需要换成brussels sprouts、radish再测。第二个是 TensorRT 加速把训练好的best.pt导出成 engine在 T4 上测 640 分辨率的吞吐。下面这条命令是导出 FP16 engine 的常用写法。yolo export modelruns/vegetable/exp1/weights/best.pt formatengine halfTrue imgsz640 device0导出后加载 engine 推理对比 PyTorch 版本的延迟。蔬菜检测如果部署在边缘设备上yolov8n加 TensorRT FP16 通常能到 100 FPS 以上但类别多的时候后处理 NMS 会占时间可以适当调大iou阈值到 0.5 减少框数。验证时不要只看 mAP还要看每类的 AP 分布把 AP 低于 0.3 的类挑出来回到训练集看是不是标注框太小或太密。我自己的习惯是每次换数据集都先跑一遍自检脚本再跑 10 epoch 的快速 baseline确认 loss 正常下降才开长训练。从那以后我每次拿到新压缩包都强制走一遍「解压→自检→切分→yaml 校验」的流程省掉了很多事后返工。希望帮到你。本文还有配套的精品资源点击获取