ARTICLE DETAIL

资讯详情

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

1701张猕猴桃目标检测数据集:VOC/YOLO双格式标注与YOLOv8训练实践

1701张猕猴桃目标检测数据集:VOC/YOLO双格式标注与YOLOv8训练实践 简介目标检测猕猴桃数据集包含1701张不同角度摆拍的盘中猕猴桃图片图片与标注均完整提供涵盖Pascal VOC和YOLO两种主流格式专为训练Kiwi类别检测模型设计。压缩包共有2000个文件以1701个XML标注文件与299个TXT标签文件为主体文件总大小74.58MBXML用于VOC格式的矩形框描述TXT用于YOLO格式的归一化坐标使用前无需转换即可直接接入YOLOv5、YOLOv8或Faster R-CNN等常见框架。目前已有106人学习使用这份数据全部图片由labelImg工具人工标注类别统一为Kiwi累计框数达5255个盘中摆放的多样化视角覆盖不同朝向与遮挡情况便于提升检测模型在真实场景中的泛化能力。包内另附说明文档清晰标注数据划分与目录结构适合目标检测初学者做模型练习、农业视觉项目做预训练数据扩充以及算法研究者验证数据增强策略。1. 猕猴桃目标检测数据集1701 张摆拍图、5255 个 Kiwi 框先看它适不适合你目标检测里最让人头疼的往往不是模型而是标注干净的数据集。这个猕猴桃数据集一共 1701 张 JPEG 图每张图都配了一份 Pascal VOC 格式的 XML 和一份 YOLO 格式的 TXT类别只有一个 Kiwi总框数 5255全部用 labelImg 标注工具画的矩形框。图片是典型的摆拍猕猴桃放在盘子里不同角度、不同数量、不同朝向背景基本是桌面和盘子。对做果蔬检测、托盘计数、农产品分拣这类垂直场景的人来说这是一份可以直接拿来训练 YOLOv5/YOLOv8 的实物数据集不用再自己花一两天时间去标数据。它适合谁我觉得有三类人最合适。第一类是刚接触目标检测、想走通「数据预处理 → 训练 → 评估」全流程的新手单类别、标注规范、数量适中翻车成本低一个晚上就能把流程跑完。第二类是做水果分拣、超市货架识别、食堂结算台这类场景的工程师摆拍图比自然场景更接近实际生产中的托盘和传送带画面。第三类是研究「同类别多实例重叠、多角度朝向」对检测精度影响的人这个数据集的框分布天然带有遮挡和堆叠样本。当然它也有边界。所有图片都是室内摆拍背景干净、光照变化有限模型在这上面训练好后直接扔到果园环境大概率会掉点。这一点先想清楚后面用起来才不会觉得数据集「不够好」。下面按文件结构、格式对应、训练接入、踩坑排查到数据体检一条线拆完每一步都给可复现的命令和脚本。2. 数据集结构与格式拆解VOC 与 YOLO 标注如何一一对应2.1 压缩包里有哪些文件图片和双格式标注一一结对打开压缩包除了说明.txt其余文件都遵循同一个命名规则文件名主干是xyxr_iamge_加三位或四位数字同一张摆拍图的 .jpg、.xml、.txt 共用这个主干。也就是说解压后你会看到一千多组这样一一对应的文件三元组xyxr_iamge_130.jpg xyxr_iamge_130.xml xyxr_iamge_130.txt xyxr_iamge_412.jpg xyxr_iamge_412.xml xyxr_iamge_412.txt这种「图片 VOC YOLO」双格式打包方式大概率是数据作者先用 labelImg 的 PascalVOC 模式画框保存 xml再用脚本统一转出一份 YOLO 的 txt。好处是省去你自己转格式的步骤坏处是如果转换脚本里归一化分母取错txt 会整体偏移我在第 4 章会专门讲怎么查这个问题。先把文件构成理清楚后面每一步都建立在它上面。各文件的数量和作用如下表文件类型数量作用.jpg1701摆拍原图猕猴桃放在盘子里、不同角度.xml1701Pascal VOC 格式标注labelImg 输出的绝对像素坐标.txt1701YOLO 格式标注归一化中心点加宽高说明.txt1格式说明、类别名、框数等元信息需要额外提醒的是图片尺寸。简介里没有给统一分辨率摆拍照片大概率来自同一台设备尺寸应该一致但你不能默认它一定一致。拿到手先把所有 jpg 的尺寸扫一遍这一步直接影响后面 imgsz 的设定也影响 xml 坐标的正确性。我见过太多因为某几张图被手动裁剪过、导致训练时标注全部错位的案例。2.2 VOC XML矩形框的绝对坐标藏在哪任选一个 xml 打开结构是标准的 labelImg VOC 输出。以下示例中的数值只是方便讲解实际以你解压出来的文件为准annotation folder./folder filenamexyxr_iamge_130.jpg/filename source databaseUnknown/database /source size width1280/width height960/height depth3/depth /size segmented0/segmented object nameKiwi/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin312/xmin ymin208/ymin xmax641/xmax ymax512/ymax /bndbox /object /annotationxml 里最关键的是三块filename 是图片文件名size 里记录图片宽高object 里是每个目标的类别名和 bndbox 绝对坐标。一张摆拍图里通常有几个到十几个猕猴桃所以 xml 里一般有多个 object 块每个 object 对应一个矩形框。类别名这里是 Kiwi整个数据集只有一个类没有其他类别混入。参数含义需要记牢xmin 和 ymin 是矩形框左上角的像素坐标xmax 和 ymax 是右下角坐标width 和 height 是图片的像素宽高转 YOLO 坐标时全靠它做分母。这三组数字任何一组对不上都会直接导致后续训练数据出错。特别是 size 里的宽高如果和 jpg 实际分辨率不一致说明图片被改过尺寸但标注没有同步更新——这是坐标错位的第一大来源后面排查章节会细讲。2.3 YOLO TXT归一化坐标与类别索引YOLO 的 txt 每一行代表一个目标格式固定为class_id x_center y_center width height后面四个值全部是 0 到 1 之间的浮点数0 0.372 0.375 0.257 0.317 0 0.710 0.402 0.263 0.301每一行的第一列是类别索引因为整个数据集只有 Kiwi 一个类所以索引只可能是 0。如果以后你在别的数据集里看到 1、2那就说明有多个类别索引从 0 开始计数。从 VOC 的绝对坐标转成 YOLO 归一化坐标公式是固定的x_center (xmin xmax) / 2 / width y_center (ymin ymax) / 2 / height box_w (xmax - xmin) / width box_h (ymax - ymin) / height反过来想从 txt 画回像素框做可视化就把四个值分别乘回图片的实际宽高。这个双向换算必须熟练后面排查坐标错位时全靠它。我一般在拿到一个新数据集时会随手写一个脚本把所有 txt 画到图上存成拼图肉眼过一遍比任何统计都直观。提示YOLO 格式要求坐标都落在 [0,1] 区间内。如果 txt 里出现大于 1 的数要么是转换脚本的问题要么是图片在标注后被裁剪过。遇到这种情况先回去核对 xml 里的 size 字段和原图实际尺寸。2.4 labelImg 标注规则与两种输出模式简介里明确写了标注工具是 labelImg、标注规则是「对类别画矩形框」。labelImg 有两种输出模式PascalVOC 模式保存 xmlYOLO 模式直接保存 txt。这个数据集同时给你 xml 和 txt说明数据作者先存了 VOC 再转了一份 YOLO。这个流程本身是合理的cv2 目标检测领域的主流标注工具除了 labelImg还有 CVAT但单机小数据集用 labelImg 最顺手学习成本几乎为零。我个人的习惯是标注时用 PascalVOC 模式因为 xml 里保留了图片尺寸和文件名信息出问题能回溯txt 一旦出错就是裸奔没有任何原始信息可查。你复现这个数据集、或者以后自己标新数据时沿这个习惯能少踩很多坑。标注规则本身没有花头——每个可见的猕猴桃画一个矩形框覆盖果实主体。但摆拍图里猕猴桃互相堆叠、遮挡labelImg 画框时容易出现两类问题一是框把相邻果实也包进去二是被遮挡的果实干脆不标。这两类问题会直接体现在框数统计和 mAP 上第 5 章的体检脚本能帮你把问题量化出来。2.5 VOC 转 YOLO 的常见脚本与容易写错的地方如果你以后自己标数据或者想把别人的 VOC 数据集转成 YOLO 格式下面这个脚本是标准写法值得直接存下来import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_path, class_names): root ET.parse(xml_path).getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text cls_id class_names.index(name) # 类别名映射到索引 b obj.find(bndbox) x1 float(b.find(xmin).text) y1 float(b.find(ymin).text) x2 float(b.find(xmax).text) y2 float(b.find(ymax).text) cx (x1 x2) / 2 / w cy (y1 y2) / 2 / h bw (x2 - x1) / w bh (y2 - y1) / h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(out_path, w) as f: f.write(\n.join(lines))脚本逻辑很简单解析 xml取 size 做分母遍历每个 object 算归一化中心点和宽高按 YOLO 格式写出去。这里class_names是一个列表比如[Kiwi]index方法把类别名转成数字索引。最容易写错的地方有三个。一是忘了把 xmin 转成 float 再运算字符串做除法直接报错二是中心点算成了x1 / w而不是(x1 x2) / 2 / w画出来的框全部偏到左上角三是class_names的顺序和 txt 里类别索引对不上训练时类别错乱。这三个坑我当年都踩过现在写转换脚本都会在末尾加一步校验随机抽几张图把 txt 还原成框画出来对比 xml 的框是否重叠。3. 接入 YOLOv8 训练目录重组、data.yaml 与关键参数3.1 目录重组从散装文件到 images/labels 双目录YOLOv8 默认的 dataset 结构是 images 和 labels 两个根目录各自再分 train/val 子目录。这个数据集原始状态是 jpg、xml、txt 全部平铺在一个目录里直接扔给 yolo 命令它不认训练前必须重组目录。第一步永远是写一个划分脚本把数据切成训练集和验证集import os import random import shutil src xyxr_dataset # 压缩包解压后的目录 dst kiwi_dataset # 重组后的数据集根目录 val_ratio 0.2 # 验证集比例 for sub in [images/train, images/val, labels/train, labels/val]: os.makedirs(os.path.join(dst, sub), exist_okTrue) imgs [f for f in os.listdir(src) if f.endswith(.jpg)] random.seed(42) # 固定随机种子保证可复现 random.shuffle(imgs) val_count int(len(imgs) * val_ratio) val_set set(imgs[:val_count]) for f in imgs: stem os.path.splitext(f)[0] img_src os.path.join(src, f) lbl_src os.path.join(src, stem .txt) if f in val_set: img_dst os.path.join(dst, images/val, f) lbl_dst os.path.join(dst, labels/val, stem .txt) else: img_dst os.path.join(dst, images/train, f) lbl_dst os.path.join(dst, labels/train, stem .txt) shutil.copy(img_src, img_dst) shutil.copy(lbl_src, lbl_dst)脚本逻辑先扫描所有 jpg 作为样本主列表因为每张图必有配套 txt随机打乱后按比例切出验证集用 set 加速归属判断复制而不是移动保留原始数据作为「后悔药」万一划分不满意还能重来。这里我故意没复制 xml因为 YOLOv8 训练只读 txtxml 留作人工核对标注用。参数说明val_ratio0.2 在 1701 张图上对应 340 张验证图约 1361 张训练图比例合理如果你想把验证集加大改成 0.25 即可。random.seed(42) 保证每次运行划分结果一致方便复现别人的实验。注意脚本里lbl_src依赖文件名完全一致如果有图片和 txt 没配对脚本会直接报错这也是一种顺手的完整性检查。3.2 data.yaml 与类别配置目录重组完成后在数据集根目录下建一个 data.yaml这是 YOLOv8 训练时的数据入口path: /home/your_name/datasets/kiwi_dataset # 改成你自己的绝对路径 train: images/train val: images/val nc: 1 names: 0: Kiwipath 必须写绝对路径yolo 命令的工作目录经常和你预期的不一致相对路径容易翻车。train 和 val 是相对 path 的子目录路径yolo 会自动拼接。nc1 是类别数names 的索引从 0 开始与第 2 章 txt 里的类别索引一一对应。类别名大小写不影响训练但保持与标注一致不容易混淆。需要注意的是如果你之后往这个数据集里混入其他水果比如苹果、橙子nc 要改成对应数量names 也要按索引顺序补齐。单类数据集最大的好处就是 yaml 永远只有三行有效信息几乎不可能写错我最怕的是从别人项目里复制 yaml 过来nc 没改、names 没改训练半天发现类别对不上。3.3 训练命令与参数速查目录和 yaml 都就绪后训练命令如下yolo detect train \ modelyolov8n.pt \ datakiwi_dataset/kiwi.yaml \ epochs120 \ imgsz640 \ batch16 \ device0 \ patience20 \ lr00.01逐项说明modelyolov8n.ptn 是 nano速度最快体积最小。1701 张单类小数据集nano 先跑通流程别一上来就上 x训练时间翻几倍收益却不大。这里 yolov8n.pt 是 YOLO 预训练模型下载训练命令会自动拉取权重文件不需要手动去网上找这个机制对新手很友好如果下载超时需要手动把权重文件放到当前目录再跑。imgsz640YOLOv8 默认训练尺寸。如果原图是 1280x960 这种大图可以先扫一遍尺寸再决定要不要调大原图本身是 640 级别就直接默认。batch16看显存来定。12GB 显存跑 nano 很轻松6GB 就降到 8。patience2020 个 epoch 验证集指标没提升就早停。摆拍数据集收敛快一般 50 个 epoch 内就稳定配一个早停能省不少时间。lr00.01默认初始学习率。如果 loss 震荡得厉害降到 0.005 再试。我把不同显存下的参数组合整理成一个速查表方便直接抄显存模型batchimgsz建议 epoch6GByolov8n864010012GByolov8n/s1664012024GByolov8m32640120第一次跑这个数据集时我从 yolov8n 起步跑通后再换 s 或 m 对比精度这是最省时间的节奏。摆拍数据量不大单次训练 10 到 20 分钟就能看到验证曲线趋势没必要一上来就堆大模型。3.4 训练过程的监控与结果判读训练跑起来之后重点看两个地方终端里的 loss 曲线和 runs/detect/ 目录下生成的验证集指标。训练集 loss 下降只能说明模型在拟合训练数据真正要盯的是验证集的 box_loss 和 mAP。单类别、背景干净的摆拍数据集mAP50 做到 0.95 以上是正常水平mAP50-95 会比 mAP50 低一截因为猕猴桃之间有遮挡、有堆叠框和真实框的 IoU 很难拉满。如果 mAP50 高但 mAP50-95 明显偏低说明框的质量不稳定常见修正方向是调低置信度阈值或者换更大模型再训练一轮。混淆矩阵在这种单类别任务里意义有限横竖都只有 Kiwi 一类看它不如看 PR 曲线在 recall 端的衰减速度。衰减慢说明漏检少衰减快说明模型「宁可不错杀也不漏检」需要结合你的落地场景决定阈值往哪边调。这些判断在第 6 章计数应用里还会用到。4. 常见问题与避坑排查五个翻车现场与对应解法4.1 现象训练报错类别数与模型不匹配现象yolo 命令跑起来没几分钟就中断报错类似 nc mismatch 或者 IndexError: class 1 is out of range。原因data.yaml 里 nc 写了 1但某个 txt 文件里出现了类别索引 1 甚至 2或者反过来yaml 里 nc 写了大于 1 的数而实际标注全是 0。前者通常是 VOC 转 YOLO 时类别名映射错位后者多半是复制的别人的 yaml 没改干净。解决先全局扫一遍所有 txt统计每一行的第一个数字awk {print $1} labels/train/*.txt labels/val/*.txt | sort -u如果输出只有 0yaml 的 nc 写 1 就没问题如果出现了 1 或更大值把这几个 txt 找出来对照 xml 看是不是混入了别的类别再决定改成 0 还是直接删除。这一步是训练前最该做的检查30 秒能挡住一小时的无效训练。4.2 现象标注可视化时框的位置完全不对现象把 txt 还原成像素框画到图上框要么整体偏到左上角要么位置和猕猴桃完全错开画出来的框比实际位置小一圈。原因txt 是归一化坐标还原时必须乘回图片实际宽高。如果你用 xml 里 size 的 width/height 去乘但 jpg 实际分辨率是另一个值画出来必然错位。还有一个隐蔽原因手机或相机拍摄的图片带 EXIF 旋转信息OpenCV 读图自动应用旋转而训练管线按原始像素处理两头对不上。解决扫一遍所有 xml 的 size 和 jpg 实际尺寸是否一致from PIL import Image import xml.etree.ElementTree as ET from glob import glob for xml in sorted(glob(xyxr_iamge_*.xml)): root ET.parse(xml).getroot() size root.find(size) xml_w, xml_h int(size.find(width).text), int(size.find(height).text) img_w, img_h Image.open(root.find(filename).text).size if (xml_w, xml_h) ! (img_w, img_h): print(xml, mismatch, (xml_w, xml_h), (img_w, img_h))这里用 PIL 而不是 OpenCV 读图是因为 PIL 默认不应用 EXIF 旋转能读到原始像素尺寸和标注坐标的参照系一致。打印出 mismatch 的文件后要么用原图重新生成标注要么把所有图批量转正再训练。可视化验证这一步千万别跳。4.3 现象训练 loss 正常但验证 mAP 上不去现象训练集 loss 一路下降曲线很健康但验证集 mAP50 卡在 0.6 左右甚至某个 epoch 之后开始掉点。原因摆拍图背景单一、桌面纹理相似模型很容易学到「盘子和桌面特征」这种捷径而不是猕猴桃的形状纹理这叫数据集偏差。另外如果划分时恰好把某个角度的图全放进了验证集mAP 会被人为拉低。解决先看训练集和验证集的图是不是来自同一批场景是的话加数据增强比换模型更有效。我一般打开 hsv_h、hsv_s、rotate 这一类颜色和旋转增强同时把 mosaic 关小或关闭因为摆拍图里目标密集mosaic 会把框裁得特别碎。增强参数改完后重新训练mAP 通常能拉起来一截。4.4 现象图片和 txt 不成对训练时找不到标签现象日志里出现 WARNING: no labels found或者某个 epoch 的实际训练样本数明显少于图片数。原因文件名大小写不一致比如图片叫 xyxr_iamge_130.JPG 而标注叫 xyxr_iamge_130.txt或者解压时某些 txt 被安全软件隔离了。这个数据集的命名比较规整但不代表你解压的机器上不会出幺蛾子尤其是从网盘下载再解压的场景txt 被误判隔离的概率比 jpg 高。解决用脚本做一次配对检查把缺的补上或剔除for f in images/train/*.jpg; do stem$(basename $f .jpg) [ -f labels/train/$stem.txt ] || echo missing: $stem done输出的 missing 清单就是问题文件。如果文件数很多检查解压软件的日志和杀毒软件隔离区如果只有零星几个直接把它们从训练目录移走别让一两个坏文件拖垮整个训练。4.5 现象数据增强把框弄丢或弄变形现象开了 mosaic、旋转、随机裁剪之后训练能跑但可视化训练样本时发现有些框的宽高变成 0或者某些目标明显没框。原因YOLOv8 的增强模块对标签的处理是「框跟着目标走」但旋转超过一定角度、裁剪把目标切出画面时策略是直接丢弃这个框。如果丢弃的框太多等效于训练样本被悄悄减掉。摆拍图里猕猴桃经常贴边这个问题比想象中常见。解决先跑一版关闭所有增强的 baseline再逐项打开增强做对照实验loss 曲线突然变差或 mAP 明显下降时就能定位到具体是哪个增强参数。我一般把 mosaic 概率从默认的 1.0 降到 0.5旋转角度限制在正负 15 度以内既保留了增强的正向作用又减少框被丢弃的比例。5. 数据集质量体检三个脚本把标注问题量化出来5.1 框数核验5255 个框到底分布在哪简介里写的 5255 个框是可信的但拿到手之后要自己核一遍因为下载、解压、复制过程中丢文件不是小概率事件。最朴素的统计方法是数每个 txt 文件的有效行数import os from glob import glob def count_boxes(label_dir): total 0 per_img [] for f in glob(os.path.join(label_dir, *.txt)): with open(f) as fh: n sum(1 for line in fh if line.strip()) per_img.append(n) total n return total, per_img train_total, train_per count_boxes(kiwi_dataset/labels/train) val_total, val_per count_boxes(kiwi_dataset/labels/val) print(train total:, train_total) print(val total:, val_total) print(all total:, train_total val_total) print(train per img: max, max(train_per), min, min(train_per), avg, round(sum(train_per) / len(train_per), 2))逻辑说明txt 里每一行非空文本就是一个目标框逐文件统计行数即可不需要解析坐标。train 和 val 分开统计两者相加应该约等于 5255考虑到划分脚本按图片数切分框数会按比例分散。看 max、min、avg 这三项能快速暴露异常如果某张图有 40 个框多半把盘子边框也画进去了如果 min 是 0 而这张图明明有猕猴桃说明有空标注文件。平均每张图 3 个框左右是比较合理的摆拍密度你可以按这个量级判断自己的统计有没有算错。5.2 标注可视化肉眼是最后的验收手段统计只能发现数字层面的异常框到底画得准不准必须肉眼看图。我每次拿到新数据集都会批量生成画框图一次看几十张import cv2 import xml.etree.ElementTree as ET import os from glob import glob def draw_xml(xml_path, img_path, out_path): img cv2.imread(img_path) if img is None: print(cannot read, img_path) return root ET.parse(xml_path).getroot() for obj in root.findall(object): b obj.find(bndbox) x1 int(b.find(xmin).text) y1 int(b.find(ymin).text) x2 int(b.find(xmax).text) y2 int(b.find(ymax).text) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, Kiwi, (x1, y1 - 6), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) cv2.imwrite(out_path, img) os.makedirs(check, exist_okTrue) for xml in glob(xyxr_iamge_*.xml)[:50]: img xml.replace(.xml, .jpg) draw_xml(xml, img, check/ os.path.basename(img))逻辑说明从 xml 里读出绝对坐标直接画框不用 txt 反向换算因为 xml 保留绝对像素坐标少一步转换就少一个出错环节。这里特意用 xml 而不是 txt也是想核对一下两个格式是否真的对得上。参数说明[:50] 先抽查前 50 张肉眼过一遍如果发现框普遍偏大或偏小再多抽 100 张。绿色框和字体大小按你的图片分辨率调图片大就把字体调大否则看不出框和果实的贴合程度。这一步做的越勤后面训练返工越少。5.3 目标尺度分布决定 imgsz 和 anchor 设置体检的最后一项是算目标尺度分布这直接决定要不要调 imgsz。按 COCO 的约定面积小于 32x32 像素算小目标大于 96x96 算大目标import os from glob import glob small medium large 0 img_w, img_h 1280, 960 # 替换成你扫描出来的图片真实尺寸 for f in glob(kiwi_dataset/labels/train/*.txt): with open(f) as fh: for line in fh: parts line.split() if len(parts) 5: continue w float(parts[3]) * img_w h float(parts[4]) * img_h area w * h if area 32 * 32: small 1 elif area 96 * 96: medium 1 else: large 1 print(small:, small, medium:, medium, large:, large)注意归一化坐标算面积必须乘回原图宽高这里的 1280x960 要替换成你实际扫描出的尺寸不然面积全是 [0,1] 的小数尺度判断完全失真。摆拍场景里猕猴桃镜框一般属于 medium 到 large因为镜头近、果实占比大。如果统计出来 small 占比过高说明拍摄距离远优先考虑把 imgsz 调大而不是依赖模型硬扛小目标。6. 进阶用法用训练好的模型数猕猴桃、迁移到相似场景6.1 从检测到计数重叠目标的处理技巧训练收敛之后最常见的落地需求就是把「框」变成「数」。对摆拍场景直接数框会低估——猕猴桃互相遮挡一个框里可能盖住两个果实。我处理这类需求时会在后处理里做两件事一是把 NMS 的 IoU 阈值从默认的 0.45 降到 0.3让重叠框多保留一些二是统计置信度区间里的框数画一条 confidence vs count 曲线找「陡降点」。具体做法是遍历验证集所有预测结果按置信度 0.1 到 0.9 分桶看每桶框数变化。陡降点往往就是漏检开始的位置对应那批低置信度框要么是被遮挡的果实要么是误检的桌面纹理。跑一组对比实验选一个让计数误差最小的阈值比拍脑袋定 0.25 或 0.5 靠谱得多。如果计数精度要求更高可以考虑在检测输出上接轻量计数头但 1701 张单类数据量撑不起这个方案。我更推荐先把置信度阈值调优做扎实数据量扩到几千张再上计数头步子迈大了容易白费功夫。6.2 迁移到其他水果或相似托盘场景猕猴桃数据集的单类特性决定了它很适合当「预训练底座」。摆拍数据迁移到苹果、橙子这类相似水果时常见做法是冻结 backbone 前几层只训练 neck 和 headyolo detect train \ modelruns/detect/train/weights/best.pt \ dataapple.yaml \ epochs80 \ freeze10freeze10 表示冻结 backbone 的前 10 层让预训练特征尽量保留新类别只在特征层上重新组织检测头。这个策略对摆拍场景特别有效因为猕猴桃和苹果、橙子在「放在盘子里、多角度、多遮挡」这些结构特征上高度一致底层特征可以直接复用相当于用 1701 张图帮你省了一轮从零训练。6.3 导出部署与最后的收尾习惯模型最终要落地的话导出这一步建议早做不要等到演示前才想起来yolo export modelruns/detect/train/weights/best.pt formatonnx导出后用 onnxruntime 跑一遍验证集对比导出前后 mAP 是否一致能挡掉绝大多数算子兼容问题。这个数据集下载下来按第 3 章的流程直接就能跑通作为预训练底座和工作流测试样本值得放进你的数据集库里。回头说句实话——这个数据集我第一次用的时候犯的错全在第 4 章那些坑里先是被安全软件隔离了十几个 txt后来又因为图片 EXIF 旋转导致可视化错位排查了大半天。从那以后我每次拿到任何数据集都强制先跑一遍第 5 章那三个体检脚本再决定要不要训练标注格式、框数、坐标还原这三关不过训练跑得再漂亮也是白搭。这个习惯帮我挡掉了不少后续返工希望帮到你。本文还有配套的精品资源点击获取
返回列表