ARTICLE DETAIL

资讯详情

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

基于YOLOv8的黄瓜好坏检测:1980张VOC+YOLO数据集实战指南

基于YOLOv8的黄瓜好坏检测:1980张VOC+YOLO数据集实战指南 简介这份增强版黄瓜好坏检测数据集面向从事果蔬分拣、农业视觉检测的算法工程师与深度学习学习者用于训练和验证二分类目标检测模型区分新鲜黄瓜与腐烂黄瓜。资源包共2000个文件以1981个VOC格式xml标注文件和19个txt说明文件为主压缩包约88.86MBjpg图片与标注一一对应可直接用于YOLO或Pascal VOC流程。数据集含1981张图片标注类别为fresh cucumber与rotten cucumber对应框数分别为2933和1344总框数4277采用labelImg按矩形框规则标注。需注意该数据集为增强版本图片存在较多重复或角度变换后的标注数据介意者慎购。目前已有180人学习下载适合需要快速搭建黄瓜品质检测基线、验证数据增强策略或做课程实验的读者参考使用。1. 黄瓜好坏检测数据集1980 张 VOCYOLO 双格式到底能干什么拿到「增强黄瓜好坏检测数据集1980张VOCYOLO格式.zip」这个标题第一反应不该是「又一个数据集」而是先问自己三个问题我的检测目标是不是「好瓜 / 坏瓜」这种二分类外观判别我的训练框架吃不吃 VOC 的 XML 和 YOLO 的 TXT 两套标注我手上的算力能不能在 1980 张这个量级上跑出可用模型这三个问题答完你基本就知道这份数据值不值得投入。它解决的是农产品分拣里最典型的一类需求把黄瓜按外观分成合格与不合格用目标检测框把每一根瓜框出来并打上类别。1980 张的规模不算大属于「小样本起步集」适合做原型验证、课程设计、产线预研或者作为更大数据集的自建起点。VOC 格式给的是 XMLYOLO 格式给的是归一化后的 TXT两套标注并存意味着你既可以用 TensorFlow/PyTorch 的 VOC 加载器也能直接喂给 YOLOv5/v8/v11 系列。适合谁做农业视觉、分拣设备、边缘部署的工程师以及需要一份干净二分类检测数据练手的人。不适合谁想要多类别细粒度分级比如按弯曲度、色泽分五级的人这份数据的标签粒度撑不住。2. 拆开压缩包先看什么VOC 与 YOLO 双格式的结构对照2.1 两种标注格式的目录长什么样VOC 格式的标准结构是Annotations/放 XML、JPEGImages/放原图、ImageSets/Main/放划分文件。YOLO 格式则是images/和labels/平行目录标签是每行class_id cx cy w h的归一化数值。这份数据集既然标了双格式通常就是两套目录并列或者用脚本互转。先别急着训练第一步是确认两边的图片文件名能对上、标注数量一致。# 解压后先做一次结构盘点别直接开训 unzip 增强黄瓜好坏检测数据集1980张VOCYOLO格式.zip -d cucumber_dataset cd cucumber_dataset # 看顶层目录确认 VOC 和 YOLO 两套是否都在 find . -maxdepth 2 -type d | sort # 统计图片总数验证是否接近 1980 find . -name *.jpg -o -name *.png | wc -l # 统计 XML 数量 find . -name *.xml | wc -l # 统计 YOLO txt 数量 find . -name *.txt | wc -l这段命令的逻辑是「先数数再动手」。图片数、XML 数、TXT 数三者应该基本相等一张图一个标注文件。如果 XML 比图片少说明有图没标如果 TXT 数量对但内容为空说明有负样本或标注丢失。参数上没什么可调的-maxdepth 2只是防止目录太深刷屏。失败时看什么如果find出来的图片数远小于 1980可能是压缩包里分了多个子目录或者图片是.jpeg后缀把-name改成*.jp*g再试。2.2 类别映射与标签一致性检查二分类检测最怕的是类别 ID 对不上。VOC 的 XML 里类别是字符串比如namegood/name、namebad/nameYOLO 的 TXT 里是数字0 和 1。如果转换脚本把顺序搞反模型会把好瓜学成坏瓜而且 loss 还降得很漂亮这就是典型的「玄学翻车」。所以训练前必须做一次类别分布统计。import os import glob from collections import Counter import xml.etree.ElementTree as ET # 统计 VOC XML 里的类别分布 voc_dir cucumber_dataset/VOC/Annotations counter Counter() for xml_path in glob.glob(os.path.join(voc_dir, *.xml)): tree ET.parse(xml_path) root tree.getroot() for obj in root.findall(object): name obj.find(name).text.strip() counter[name] 1 print(VOC 类别分布:, counter) # 统计 YOLO TXT 里的类别 ID 分布 yolo_dir cucumber_dataset/YOLO/labels id_counter Counter() for txt_path in glob.glob(os.path.join(yolo_dir, *.txt)): with open(txt_path, r) as f: for line in f: line line.strip() if not line: continue cls_id int(line.split()[0]) id_counter[cls_id] 1 print(YOLO 类别 ID 分布:, id_counter)逻辑说明VOC 侧统计的是字符串类别名YOLO 侧统计的是数字 ID。正常情况下两个类别的框数应该和 VOC 侧一一对应。参数上strip()是为了去掉 XML 文本里的换行和空格很多数据集在这里埋雷。如果发现 YOLO 只有 ID 0 没有 ID 1或者某一类数量为 0说明标注或转换出了问题必须先修数据再训练。这一步花十分钟能省掉后面几小时的无效训练。2.3 从 VOC 转 YOLO 的坐标换算要点虽然标题说双格式都有但实际拿到手经常需要自己再转一遍或者验证转换是否正确。VOC 的bndbox是绝对像素坐标xmin, ymin, xmax, ymaxYOLO 要的是归一化的中心点加宽高。换算公式是cx (xminxmax)/2/Wcy (yminymax)/2/Hw (xmax-xmin)/Wh (ymax-ymin)/H。这里最容易错的是把xmax-xmin写成xmax-xmin1差一个像素在 1980 张里不明显但在小目标上会累积偏差。import os import xml.etree.ElementTree as ET from PIL import Image def voc_to_yolo(xml_path, img_path, class_map, out_path): # class_map: {good: 0, bad: 1} img Image.open(img_path) W, H img.size tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in class_map: continue bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 裁剪到图像边界防止标注越界 xmin, ymin max(0, xmin), max(0, ymin) xmax, ymax min(W, xmax), min(H, ymax) cx (xmin xmax) / 2.0 / W cy (ymin ymax) / 2.0 / H w (xmax - xmin) / W h (ymax - ymin) / H lines.append(f{class_map[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(out_path, w) as f: f.write(\n.join(lines))逻辑说明先读图拿宽高再逐个 object 换算。class_map必须和你的data.yaml里names的顺序严格一致这是最容易埋雷的地方。参数上保留 6 位小数足够YOLO 官方也是这个精度。边界裁剪那两行是后悔药很多标注框会超出图片边缘不裁的话归一化后会出现负数或大于 1 的值训练时虽然不报错但会拉低定位精度。3. 用 YOLOv8 在 1980 张上跑通训练配置、参数与验证3.1 data.yaml 怎么写才不出错YOLO 训练的第一步不是敲命令而是把data.yaml写对。这份数据集是二分类nc: 2names的顺序必须和 TXT 里的 ID 对应。路径建议用绝对路径相对路径在切换工作目录时经常翻车。# cucumber.yaml path: /home/user/cucumber_dataset/YOLO train: images/train val: images/val test: images/test nc: 2 names: 0: good 1: bad逻辑说明path是根目录train/val/test是相对根目录的图片路径YOLO 会自动去找同级的labels目录。参数上如果你的数据没有预先划分 train/val需要自己按 8:1:1 切分切分时注意同一根黄瓜的多角度图不要跨集否则验证指标会虚高。这是小数据集最常见的评估陷阱。3.2 训练命令与关键参数怎么设1980 张属于小样本直接上大模型容易过拟合。常见做法是用 YOLOv8n 或 YOLOv8s 这种轻量模型配合强增强。下面是一条我常用的训练命令。yolo detect train \ datacucumber.yaml \ modelyolov8n.pt \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ patience30 \ augmentTrue \ mosaic1.0 \ mixup0.1 \ degrees15.0 \ translate0.1 \ scale0.5 \ fliplr0.5 \ namecucumber_v8n逻辑说明modelyolov8n.pt是 COCO 预训练权重小数据集上迁移学习比从头训快得多。epochs150配合patience30意思是 30 轮没提升就早停避免无效等待。imgsz640是默认值如果你的黄瓜在图中占比很小可以提到 960但显存要够。mosaic1.0和mixup0.1是强增强小样本必备但 mixup 别开太大二分类任务里过度混合会让类别边界模糊。degrees15.0是旋转增强黄瓜在分拣线上角度多变这个参数有用。fliplr0.5水平翻转注意如果好坏特征有方向性比如某侧疤痕翻转可能引入噪声需要看实际数据。3.3 训练过程看什么指标训练启动后重点盯三个东西box_loss、cls_loss和mAP50。box_loss下降说明定位在收敛cls_loss下降说明分类在学。如果cls_loss很快降到接近 0 但mAP50上不去大概率是类别不平衡或标注有问题。1980 张里如果好瓜远多于坏瓜模型会倾向于全预测好瓜这时候要看混淆矩阵而不是只看总 mAP。# 训练完成后在验证集上跑一次评估输出混淆矩阵和 PR 曲线 yolo detect val \ modelruns/detect/cucumber_v8n/weights/best.pt \ datacucumber.yaml \ imgsz640 \ conf0.25 \ iou0.5 \ plotsTrue逻辑说明conf0.25是置信度阈值低于它的框不算。iou0.5是 NMS 的 IoU 阈值。plotsTrue会生成混淆矩阵、PR 曲线等图存在runs/detect/val下。看混淆矩阵时重点看「坏瓜被预测成好瓜」的数量这在分拣场景里是致命错误比「好瓜被误判成坏瓜」严重得多。如果这个数字高要么加坏瓜样本要么在推理时对坏瓜类别降低阈值。4. 小样本检测的避坑清单从标注到部署的五个翻车点4.1 标注框越界导致训练 loss 异常现象训练前几轮 loss 直接飙到几百甚至 NaN。原因VOC 转 YOLO 时没有做边界裁剪归一化后出现负数或大于 1 的坐标。解决在转换脚本里加max(0, xmin)和min(W, xmax)转换完再写一个校验脚本扫描所有 TXT发现越界值就打印文件名。4.2 类别 ID 顺序与 names 不一致现象训练指标看着不错但推理时好瓜坏瓜完全反了。原因data.yaml里names写的是0: bad, 1: good但 TXT 里 0 是好瓜。解决转换前先固定一份class_mapVOC 和 YOLO 两侧都用同一份训练前用第 2.2 节的统计脚本核对数量。4.3 验证集与训练集图片重复现象验证 mAP 高达 0.95上线后一塌糊涂。原因同一根黄瓜的多张连拍图被随机分到了 train 和 val模型记住了背景而不是特征。解决按「根」划分同一根瓜的所有图只进一个集合。如果数据里没有根 ID就按拍摄批次或文件名前缀分组切分。4.4 强增强把坏瓜特征抹掉现象训练 loss 正常下降但坏瓜召回率始终很低。原因mixup或mosaic把坏瓜的局部疤痕区域和好瓜混合模型学不到稳定特征。解决把mixup降到 0.05 或关掉mosaic保留但close_mosaic设成最后 20 轮关闭让模型在接近真实分布的数据上收尾。4.5 推理阈值照搬默认值现象部署后误检多好瓜被频繁框成坏瓜。原因conf0.25是通用默认值但二分类分拣场景对坏瓜的精度要求更高。解决在验证集上画 PR 曲线找到坏瓜类别 precision 和 recall 的平衡点通常把坏瓜的 conf 提到 0.4 到 0.5好瓜保持 0.25用类别级阈值分别控制。5. 把 1980 张用到极致增量采样与类别级阈值调优1980 张训出一个能用的二分类检测器不难难的是让它稳定。我自己的习惯是训完第一版后不急着部署而是做两件事一是用模型在验证集上跑推理把置信度在 0.3 到 0.6 之间的「模糊样本」挑出来人工复核这些样本往往就是标注边界不清或特征不典型的硬骨头二是针对坏瓜类别单独调阈值因为分拣线上漏掉一个坏瓜的代价远大于误杀一个好瓜。具体做法是写一个脚本遍历验证集图片输出每个预测框的类别和置信度把坏瓜置信度低于 0.5 的图单独存一个目录人工看一遍。如果发现大量坏瓜被漏检说明训练集里坏瓜的形态覆盖不够这时候有两个选择要么补采坏瓜样本要么用现有模型对未标注图片做预标注人工修正后加入训练集这就是增量采样的闭环。1980 张的底子做这个闭环很快一轮下来通常能补出 100 到 200 张高价值样本。类别级阈值的调优可以用下面这段脚本在验证集上扫一遍找到坏瓜的最优工作点。import numpy as np from ultralytics import YOLO model YOLO(runs/detect/cucumber_v8n/weights/best.pt) results model.val(datacucumber.yaml, imgsz640, conf0.01, iou0.5, plotsFalse) # 从验证结果里提取每个类别的 PR 数据 # 这里以坏瓜类别 1为例扫描不同 conf 下的 precision 和 recall for conf in np.arange(0.1, 0.7, 0.05): r model.val(datacucumber.yaml, imgsz640, conffloat(conf), iou0.5, plotsFalse) # r.box.p 和 r.box.r 是各类别的 precision/recall 数组 bad_precision r.box.p[1] bad_recall r.box.r[1] print(fconf{conf:.2f} bad_P{bad_precision:.3f} bad_R{bad_recall:.3f})逻辑说明conf0.01先跑一次拿到完整的 PR 数据然后手动扫阈值。参数上np.arange(0.1, 0.7, 0.05)是扫描范围实际用的时候根据曲线拐点缩小范围。看输出时找bad_R开始明显下降之前的最大conf那个点就是坏瓜的推荐阈值。这套方法比拍脑袋设 0.5 靠谱得多也是我在多个分拣项目里反复用的习惯。最后说一句血泪经验小数据集训练数据质量比模型结构重要十倍。1980 张里如果有 50 张标注错了足够让模型在关键场景翻车。训之前花半天做数据清洗比训之后调三天参有用。希望帮到你。本文还有配套的精品资源点击获取
返回列表