news 2026/10/2 21:23:33

小辣椒小彩椒检测数据集处理与YOLOv8训练部署全攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
小辣椒小彩椒检测数据集处理与YOLOv8训练部署全攻略

简介:小辣椒小彩椒检测数据集共有2292张实地拍摄的辣椒作物图像,聚焦农业目标检测、果实计数和成熟度分布分析,适合计算机视觉研究者、农业智能化开发人员以及需要训练检测模型的学生与工程师。数据采用Pascal VOC与YOLO双格式标注,每张图片配有对应的XML标注文件和TXT标注文件,免去格式转换步骤,可接入主流检测框架;标注类别为果实、树木、背景三类,果实目标框达19293个,背景框6240个,树木框2627个,总计28160个标注框,信息完整,方便划分训练集、验证集以及开展模型性能对比。压缩包采用7z格式,共包含2000个文件,以1999个XML标注文件为主体,并附带1个说明文档,用于辅助理解目录结构和标注统计口径,整体大小约272.99MB,下载后可直接解压使用。目前已有175人学习下载,该数据集尤其适合小辣椒、小彩椒这类小目标密集、枝叶遮挡场景的检测算法调优与评测,也可作为目标检测教学实验的练习数据,帮助快速上手数据准备与模型迭代流程。

1. 小辣椒小彩椒检测数据集2292张3类别:真正值钱的不只是图片数量

小辣椒小彩椒检测数据集,2292张图片、3个类别,这规模放在目标检测里不算大,但对农产品分拣和田间巡检来说,属于“够跑通一个靠谱模型”的典型量级。做这类数据集处理时,真正值钱的不是那张图片总数,而是类别定义是否清晰、标注有没有错漏、以及目标尺寸分布适不适合你手里的部署场景。小辣椒簇生且互相遮挡,小彩椒颜色饱和度高、反光严重,这两类与背景的边界经常比想象中模糊。这篇文章按“先摸格式、再转标注、训练调参、最后上分拣线”的顺序,把一条能直接照做的落地路径写清楚,适合正在做目标检测数据集处理、或准备用 YOLOv8 跑农产品识别的工程师参考。

2. 先摸清2292张数据的底细:目录、标注格式与抽样检查

拿到数据集的第一件事不是开训练,而是搞清楚标注文件到底是什么格式、有没有缺图、类别编号是否连续。很多翻车现场都始于这一步被跳过,等训练跑到一半发现 mAP 是 0,再回头查 label,白白浪费十几个小时。

2.1 打开压缩包先别跑:用10分钟做目录与标注格式摸底

常见的检测数据集标注有三种:VOC XML、YOLO TXT、COCO JSON。这2292张数据包打开后,先别急着写训练脚本,用两三条命令就能把底摸清楚:

find /path/to/chili_dataset -type f | sed 's/.*\.//' | sort | uniq -c find /path/to/chili_dataset -name "*.xml" | wc -l find /path/to/chili_dataset -name "*.txt" | wc -l

第一条命令统计所有文件的扩展名分布,第二条、第三条分别确认 XML 和 TXT 标注的数量。如果 XML 数量接近图片数量,说明大概率是 VOC 格式;如果 TXT 数量接近,说明已经是 YOLO 格式;如果只有 JSON,那就是 COCO。这个判断决定了后面的转换脚本怎么写。

拿到标注格式后,还要确认一件事:有没有 class 列表文件。常见压缩包里的文件结构大致如下:

文件/目录作用
images/ 或 JPEGImages/原始图片
annotations/ 或 labels/标注文件
classes.txt / class_names.txt类别顺序定义
README.txt采集说明、类别含义

classes.txt 非常重要,YOLO 训练时类别编号完全依赖这个顺序。如果压缩包里没有,就从 XML 里提取所有<name>标签看看一共有几种写法。类名大小写不一致(比如Red_Chili和red_chili)也经常出现,后面转换时统一处理。

2.2 标注质量抽检:把3类框画到图上才能看到错误

统计完格式,马上做标注抽检。我的习惯是写一个可视化脚本,把每张图的标注框直接画到图上,随机抽 50 张翻一遍。框错位、类别标反、重复框、漏框,肉眼扫一遍比看任何统计数字都直观。

import xml.etree.ElementTree as ET import cv2 import glob import os def draw_voc_boxes(image_dir, xml_dir, out_dir, color_map=None): os.makedirs(out_dir, exist_ok=True) xml_list = sorted(glob.glob(os.path.join(xml_dir, "*.xml"))) for idx, xml_path in enumerate(xml_list): tree = ET.parse(xml_path) root = tree.getroot() img_name = os.path.basename(root.find("filename").text) img_path = os.path.join(image_dir, img_name) img = cv2.imread(img_path) if img is None: print(f"[skip] {img_name} 找不到对应图片") continue for obj in root.iter("object"): name = obj.find("name").text.strip() box = obj.find("bndbox") x1 = int(float(box.find("xmin").text)) y1 = int(float(box.find("ymin").text)) x2 = int(float(box.find("xmax").text)) y2 = int(float(box.find("ymax").text)) color = color_map.get(name, (0, 255, 0)) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, name, (x1, max(0, y1 - 6)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) out_path = os.path.join(out_dir, f"check_{idx:04d}_{img_name}") cv2.imwrite(out_path, img) if __name__ == "__main__": draw_voc_boxes( image_dir="./images", xml_dir="./annotations", out_dir="./check_output", color_map={ "red_chili": (0, 0, 255), # OpenCV 是 BGR,这里是红色 "green_chili": (0, 255, 0), # 绿色 "colored_pepper": (255, 0, 255) # 紫色 } )

这段脚本的逻辑很直白:遍历所有 XML,解析 filename 找到图片,把每个 object 的 bndbox 画上去,类别名写在框上方。输出到 check_output 目录,翻两遍就能看出大多数问题。两个参数要留意:一是color_map里 OpenCV 用的是 BGR 顺序,红是(0,0,255),反了会颜色错乱;二是 XML 里的 filename 有时带子目录前缀,最好用os.path.basename处理一下,否则图片路径拼接会失败。

如果标注是 YOLO TXT 格式,这段脚本不能直接用,需要先从 TXT 解析出坐标再画框。建议先按 3.1 的脚本把格式统一,再回来做可视化。

2.3 类别比例与目标尺寸分布:决定你后面要不要重采样

可视化抽查之后,做一次量化统计。小彩椒和小辣椒在视觉上相似度不低,如果某一类标注框数量明显少于另外两类,训练时模型很容易偏向多数类。目标尺寸分布也直接决定训练分辨率怎么设。

import xml.etree.ElementTree as ET import glob import os def analyze_voc_boxes(xml_dir, image_dir): class_counter = {} size_counter = {"small": 0, "mid": 0, "large": 0} total_boxes = 0 for xml_path in sorted(glob.glob(os.path.join(xml_dir, "*.xml"))): tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) for obj in root.iter("object"): name = obj.find("name").text.strip() box = obj.find("bndbox") x1 = float(box.find("xmin").text) y1 = float(box.find("ymin").text) x2 = float(box.find("xmax").text) y2 = float(box.find("ymax").text) w = x2 - x1 h = y2 - y1 class_counter[name] = class_counter.get(name, 0) + 1 total_boxes += 1 if w < 32 and h < 32: size_counter["small"] += 1 elif w < 96 and h < 96: size_counter["mid"] += 1 else: size_counter["large"] += 1 print("类别统计:", class_counter, "总框数:", total_boxes) print("尺寸分布:", size_counter) if __name__ == "__main__": analyze_voc_boxes("./annotations", "./images")

这段脚本统计两个东西:每个类别的标注框数量,以及小、中、大目标的占比(以 32x32 和 96x96 为分界,这是目标检测里比较常用的划分口径)。小目标占比高,训练时 imgsz 就要往上提;某类框数量过少,后面就要考虑重采样或 copy-paste 增强。2292 张图按 3 类均分,每类约 700 多张,但每张图里可能有多个辣椒实例,所以总框数会远大于 2292。如果统计出来某类只有 300 个框,那这类大概率会拖后腿。

3. 把标注转成YOLO能吃的格式:VOC转YOLO脚本与训练集划分

YOLO 训练需要的是 TXT 格式的标注,每行一个目标:类别编号、归一化后的中心点 x、中心点 y、宽、高。如果拿到的数据包是 VOC XML,就必须先转换。这一步不复杂,但边界情况很多,转换前先想清楚类别顺序,转换后一定要抽检。

3.1 从VOC XML到YOLO TXT:一个能直接跑的转换脚本

转换的核心是把像素坐标归一化到 [0,1] 区间。这个脚本可以处理多类、未闭合边框、图片尺寸读取三种常见情况。

import glob import os import xml.etree.ElementTree as ET # 这个顺序必须和后面的 dataset.yaml 保持一致 CLASSES = ["red_chili", "green_chili", "colored_pepper"] def voc_to_yolo(xml_dir, image_dir, out_label_dir): os.makedirs(out_label_dir, exist_ok=True) for xml_path in sorted(glob.glob(os.path.join(xml_dir, "*.xml"))): tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) if img_w <= 0 or img_h <= 0: print(f"[skip] {xml_path} 图片尺寸无效") continue out_txt = os.path.join( out_label_dir, os.path.splitext(os.path.basename(xml_path))[0] + ".txt" ) lines = [] for obj in root.iter("object"): name = obj.find("name").text.strip() if name not in CLASSES: print(f"[skip] 未知类别 {name} in {xml_path}") continue box = obj.find("bndbox") x1 = float(box.find("xmin").text) y1 = float(box.find("ymin").text) x2 = float(box.find("xmax").text) y2 = float(box.find("ymax").text) if x2 <= x1 or y2 <= y1: continue x_center = ((x1 + x2) / 2.0) / img_w y_center = ((y1 + y2) / 2.0) / img_h w = (x2 - x1) / img_w h = (y2 - y1) / img_h x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) w = min(max(w, 0.0), 1.0) h = min(max(h, 0.0), 1.0) lines.append(f"{CLASSES.index(name)} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") with open(out_txt, "w", encoding="utf-8") as f: f.write("\n".join(lines)) print(f"[ok] {os.path.basename(xml_path)} -> {len(lines)} boxes") if __name__ == "__main__": voc_to_yolo("./annotations", "./images", "./labels")

几个关键点:尺寸用 XML 里的 size 节点而不是cv2.imread去读图,因为读大图会浪费时间,而且 XML 里记录的尺寸通常就是标注时的原图尺寸;类别名 strip 掉首尾空格,避免red_chili和red_chili被当成两个类;if x2 <= x1 or y2 <= y1直接过滤非法框,这种框在人工标注里不少见。所有坐标统一做 clip 到 [0,1],是因为少数标注会把框的中心或宽高写到图像外,不 clip 会让 YOLO 训练报 NaN。

转换完成后,随便打开一个生成的 TXT 看一眼,格式应该类似:

0 0.523412 0.441203 0.152301 0.233442 1 0.612345 0.521334 0.101234 0.181234

每行五个数字,类别编号必须在[0, nc-1]之间。注意编号从 0 开始,不是从 1 开始。这一步出错,后面训练 mAP 永远为 0。

3.2 按图片划分训练/验证集:脚本与三种划分策略

划分数据集的原则只有一条:同一张图片不能同时出现在训练集和验证集里。听起来是废话,但很多人直接按标注文件划分,一张图有两个标注文件,前面一半进训练,后面一半进验证,结果就是数据泄露,验证指标虚高到不敢信。

import os import random import shutil IMG_DIR = "./images" LBL_DIR = "./labels" OUT_DIR = "./split" RATIO = (0.85, 0.10, 0.05) SEED = 2024 def split_dataset(img_dir, lbl_dir, out_dir, ratio, seed): names = [ os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.lower().endswith((".jpg", ".jpeg", ".png")) ] random.Random(seed).shuffle(names) n = len(names) n_train = int(n * ratio[0]) n_val = int(n * ratio[1]) n_test = n - n_train - n_val groups = { "train": names[:n_train], "val": names[n_train:n_train + n_val], "test": names[n_train + n_val:], } for split_name, name_list in groups.items(): img_out = os.path.join(out_dir, split_name, "images") lbl_out = os.path.join(out_dir, split_name, "labels") os.makedirs(img_out, exist_ok=True) os.makedirs(lbl_out, exist_ok=True) for name in name_list: img_src = os.path.join(img_dir, name + ".jpg") lbl_src = os.path.join(lbl_dir, name + ".txt") if os.path.exists(img_src): shutil.copy2(img_src, img_out) if os.path.exists(lbl_src): shutil.copy2(lbl_src, lbl_out) if __name__ == "__main__": split_dataset(IMG_DIR, LBL_DIR, OUT_DIR, RATIO, SEED)

这里用的是复制而不是移动,保留原始数据作为备份。数据集的划分比例,2292 张图我的常用配置是 85% 训练、10% 验证、5% 测试,也就是约 1948 / 229 / 115 张。对这个小数据集,测试集留 5% 只做最终评估,平时调参只看验证集。SEED固定成 2024,保证每次复现结果一样,后面想对比增强策略时,这个固定种子就是唯一的控制变量。

三种划分策略里,随机划分最简单,但如果图片是按地块或按天采集的,同一株辣椒的多个角度可能同时出现在训练和验证里,指标会虚高。更严谨的做法是按采集批次分组划分:把相同时间、相同地块的图片归到一个包里再分。不过这需要数据包里带场景信息,拿到数据后先看 README 有没有这类字段,有就按组划分,没有就用随机划分加固定种子。

3.3 写dataset.yaml与数据增强参数:2292张的增强尺度怎么控

划分完目录,写 YOLO 训练用的 dataset.yaml。这个文件是所有训练命令的入口,路径写错或类别顺序不对,训练直接崩。

# chili.yaml train: ./split/train/images val: ./split/val/images test: ./split/test/images nc: 3 names: 0: red_chili 1: green_chili 2: colored_pepper

两个坑提醒一下:路径建议写绝对路径,相对路径依赖你执行 yolo 命令时的工作目录,换个终端就找不到数据;Windows 下不要直接写C:\chili\images这种带反斜杠和冒号的路径,YAML 会把C:解析成奇怪的键值对,最好换成 Linux 容器或转成正斜杠写法。

数据增强这块,2292 张属于中小型数据集,增强要做但不能做过头。我自己在这个数据集上的常用参数如下:

参数建议值说明
hsv_h0.005 ~ 0.01红色系对色相偏移极为敏感,默认 0.015 会把红辣椒变成黄辣椒
hsv_s0.5 ~ 0.7饱和度增强对辣椒这种高饱和目标有效,但别拉满
hsv_v0.3 ~ 0.4亮度扰动模拟田间光照变化,比色相安全
mosaic1.0前中期开,最后 10 个 epoch 关掉,让小目标稳定收敛
copy_paste0.1 ~ 0.3框多时很有用,能缓解遮挡和实例不平衡
flipud0.0 ~ 0.1辣椒不会倒着长,垂直翻转少开
degrees0 ~ 10小角度旋转模拟摄像头安装偏差,别超过 15 度

这里的逻辑是:农业场景下相机角度变化有限,旋转增强过了会让模型学到错误的姿态先验。色相增强尤其要克制,因为红色小辣椒、黄色彩椒、绿色辣椒在 HSV 空间里距离很近,色相扰动过强会直接把类间边界搅乱。如果你用的是 Ultralytics YOLOv8,这些参数可以直接写进训练命令,也可以写到 yaml 里统一管理。

4. 训练参数怎么设:YOLOv8上的模型选型、超参与评估指标

数据准备好了,接下来是训练。很多人拿到模型就默认 YOLOv8n 起步,但 2292 张图、3 个类、目标多为中等大小,n 往往欠拟合,s 才是性价比较高的起步点。

4.1 为什么我从YOLOv8s起步,而不是n、m或l

在 2292 张的数据规模下,模型体量选择的本质是平衡拟合能力和过拟合风险。YOLOv8n 参数量最少,训练快,但对辣椒这种形状多变、相互遮挡的目标容易欠拟合;YOLOv8m 和 l 表达能力更强,可数据量不够,训练后期验证 mAP 会原地踏步甚至回退。

模型相对计算量显存占用(batch=16, imgsz=640)适用性判断
YOLOv8n最轻约 4GB边缘设备优先,但小辣椒这种密集小目标容易表现不足
YOLOv8s适中约 6~8GB这个数据规模最稳的起步点,推荐
YOLOv8m较重约 10~12GB迁移学习基础好、数据量大再考虑,否则容易过拟合

我的习惯是从预训练权重yolov8s.pt开始练,而不是从空的yolov8s.yaml开始。预训练权重在 COCO 上学到的通用特征,尤其边缘、纹理和颜色表征,对辣椒这种视觉目标帮助很大,能明显加快收敛,也能缓解数据量不足的问题。

4.2 训练命令与关键参数:epochs、batch、imgsz、mosaic怎么选

拿 Ultralytics YOLOv8 举例,训练命令我一般长这样:

yolo detect train \ data=./chili.yaml \ model=yolov8s.pt \ epochs=200 \ imgsz=640 \ batch=16 \ device=0 \ workers=8 \ patience=30 \ close_mosaic=10 \ project=./runs/chili \ name=exp1

逐个说参数:epochs=200对 2292 张属于够用且不过量的值,配合早期停止patience=30,验证集指标连续 30 个 epoch 不涨就自动停;imgsz=640是 3 类中等目标的标准配置,如果你的尺寸分布统计显示小目标占比很高,再提到 768;batch=16取决于显存,12GB 显卡建议从 16 起步;workers=8是数据加载线程,Linux 下开满没问题,Windows 下容易报错,降到 2~4;close_mosaic=10表示最后 10 个 epoch 关闭 mosaic 增强,这是稳定收敛的小技巧,因为 mosaic 拼图里的目标尺度变化太剧烈,后期需要回归到真实尺度分布。

训练中断了不要慌,续训命令:

yolo detect train resume=True project=./runs/chili name=exp1

这个resume是后悔药,前提是项目目录里还有上次的 weights 和超参数记录。训练过程中如果发现 loss 是 NaN,大概率是数据里有空标注或坐标异常,回到 2.2 的抽检脚本重新查,不要继续调参数。

4.3 数值指标怎么读:mAP50和PR曲线能告诉你什么

训练完看 results.csv,里面每一行对应一个 epoch 的指标。关注三列:metrics/mAP50(B)、metrics/mAP50-95(B)、metrics/precision(B)和metrics/recall(B)。

mAP50 是 IoU 阈值 0.5 下的平均精度,mAP50-95 是 0.5 到 0.95 多个阈值下的平均,后者对预测框的位置精度更敏感。对分拣线场景,我的判断习惯是先用 mAP50 确认“能不能找到辣椒”,再用 PR 曲线确认“误检能不能接受”。PR 曲线可以从验证集预测结果里画出来,固定一个置信度阈值,比如 0.25,看 precision 和 recall 的平衡点。

在runs/chili/exp1/下会生成confusion_matrix.png,这个图直接展示每个真实类别被预测成了什么。小辣椒和小彩椒如果互相误检,图谱上这两格的数字会很扎眼,这比单纯看 mAP 数字更直观。如果某一类 recall 明显低,说明该类漏检严重,回到第 2.3 节检查类别框数是不是太少,而不是盲目增加训练轮数。

5. 辣椒检测的避坑清单:从标签编号到HSV增强的五个翻车点

这块内容全部来自我在类似农产品检测数据集上的血泪经验。每一条都是“现象 → 原因 → 解决”的结构,遇到类似问题可以直接对照排查。

5.1 训练loss一直掉,mAP却是0

现象:训练日志里 loss 稳步下降,但验证集 mAP 一直是 0,模型预测输出里全是空框。

原因:最常见的是标签类别编号从 1 开始写,比如1 0.52 0.44 0.15 0.23,但nc: 3的模型只认[0, 1, 2]。编号 3 超出类别范围,YOLO 在计算损失时把这些框全部忽略。另一个常见原因是数据增强后的某一类标签全为空,验证集里所有标注都没被正确读取。

解决:先打印任意一张验证图片对应的标签文件,确认编号范围在[0, nc-1];然后跑一次验证集预测:

yolo detect predict model=runs/chili/exp1/weights/best.pt source=./split/val/images save_txt=True

如果生成的预测 txt 全部为空,基本可以确定是标签编号错位。回看 3.1 的转换脚本,确认CLASSES.index(name)得到的确实是 0 开头。

5.2 红色小辣椒和红色彩椒互相误检

现象:验证集上红色小辣椒的预测框经常套在红色彩椒身上,反之亦然,单看 AP 值两个类都一般。

原因:本质上这两类在颜色特征上几乎重合。小辣椒和彩椒都有红色品种,如果采集时类别定义只按颜色区分,模型学不到可分的形状特征。小辣椒细长、彩椒接近圆球形,但训练数据里如果类内形状差异大,模型就容易只抓颜色这个最强特征。

解决:先看 confusion_matrix.png,确认是这两个类互混。最简单的方案是训练后处理:在推理端加一个长宽比过滤规则,细长框判定为小辣椒,接近正方形的框判定为彩椒,这比修改模型便宜得多。更根治的做法是重新定义类别,比如按“红色细长椒/红色圆椒”而不是“小辣椒/小彩椒”,或者干脆合并成一类,再用第二个分类器处理等级划分。

5.3 显存爆掉,你以为只是batch太大

现象:batch=16、imgsz=640 在 12GB 显卡上训练到一半 OOM,甚至刚启动就报错。

原因:除了模型和图片本身,mosaic 增强会把 4 张图拼成一张大图参与前向传播,显存消耗远高于单张 640x640 的推理。另外 Ultralytics 默认开启 AMP,但某些显卡驱动组合下 AMP 不一定生效,OOM 就会来得更早。验证集的前向推理也会占用显存,如果验证集图片分辨率很高,同样会爆。

解决:先把 batch 减半到 8,如果还爆就把 imgsz 降到 608;再不行就加cache=False显式关闭缓存,因为缓存的是内存不是显存,但会间接影响加载效率。最后还可以把训练和验证分开:训练完单独跑一次yolo detect val,避免训练过程中的验证阶段抢显存。OOM 的排查顺序是 batch -> imgsz -> AMP -> cache。

5.4 训练图片近景多,部署后远处小辣椒全漏

现象:测试集 mAP50 不低,但把模型放到分拣线或田间巡检摄像头上,距离稍远的小目标几乎全部漏检。

原因:训练集里近景大目标占比过高,损失函数被大目标主导,模型在小目标特征上的能力没有被充分优化。尤其辣椒这类数据,采集时离果实半米内拍,标注框可能占整张图的一半;部署相机装在 2 米高处,一个辣椒只有 30x30 像素,尺度差异太大。

解决:先用第 2.3 节的统计脚本看目标尺寸分布。如果小目标占比低于 20%,两个办法:一是把 imgsz 从 640 提到 768 甚至 896,小目标在特征图上的像素点变多,模型有机会学到细节;二是推理时用切图检测,把大图切成 640x640 的小块分别推理再合并结果,这是做检测数据集处理时解决小目标漏检最稳的手段。当然最根本的还是补一批远距离拍摄的数据,标注几十张都比增强有效。

5.5 HSV增强把红辣椒变成了黄辣椒

现象:训练日志里的训练集预览图里,红色小辣椒有的是橙色,有的接近黄色,和彩椒类混在一起。训练结束后,红辣椒类的 precision 明显低于其他类。

原因:Ultralytics 默认hsv_h=0.015,这个值对大多数目标没问题,但红色在 HSV 色相空间里紧挨着橙色和黄色,0.015 的随机偏移已经足够让红的变橙、变黄。辣椒不是交通标志,颜色是它的核心辨识特征,色相一旦被增强破坏,模型就会丢失最重要的线索。

解决:把hsv_h降到0.005甚至0.0,用hsv_s和hsv_v的增强来替代色相扰动。饱和度增强可以模拟不同光照下的颜色浓郁程度,亮度增强可以模拟顺光和逆光,这两个维度对辣椒识别是安全的。如果你用 Albumentations 做外部增强,优先选随机亮度和对比度,少动色相。这个参数配置我在 3.3 的增强表格里已经给了具体值,直接抄即可。

6. 把模型送上分拣线:导出、量化与产线验证

训练告一段落,模型如果只在测试集上指标好看,那离上线还差一半。分拣线部署要过两关:推理速度够不够、真实场景里有没有误检漏检。

6.1 导出ONNX,把dynamic batch打开

yolo export model=runs/chili/exp1/weights/best.pt format=onnx dynamic=True opset=12 simplify=True

dynamic=True让 batch 维度可变,现场推理时一次处理几张图都灵活;opset=12是边缘设备兼容性比较好的版本,opset 太高,老 GPU 或端侧 NPU 可能不认;simplify=True会去掉一些冗余计算节点,体积更小、推理略快。导出后在 Python 里确认输入输出:

import onnxruntime as ort sess = ort.InferenceSession("best.onnx") print(sess.get_inputs()[0].shape) print(sess.get_inputs()[0].type)

打印出来输入 shape 应该带None的 batch 维度。这里要留意,导出后最好用同一张图分别跑 PyTorch 模型和 ONNX 模型,对比输出框的置信度差异。两边输出如果差超过 0.01,大概率是导出过程出了问题,最常见的是预处理(比如归一化参数)没有对齐。

6.2 用10分钟产线视频抽帧回测,别只信mAP

我见不少人拿测试集 mAP 交代模型效果,结果一上产线就翻车。测试集再怎么分,都是离线数据,没有现场的光照变化、黑色塑料薄膜背景和运动模糊。我的习惯是拉一段 10 分钟的产线视频,按帧抽块,先跑一遍模型看看真实表现。

import cv2 cap = cv2.VideoCapture("field_day.mp4") frame_id = 0 while True: ret, frame = cap.read() if not ret: break if frame_id % 10 == 0: cv2.imwrite(f"frames/{frame_id:06d}.jpg", frame) frame_id += 1 cap.release()

抽帧保存后,用固定置信度阈值对抽出的每一帧推理,统计两个数字:漏检率(标注过的辣椒没被框出来)和误检率(框出来的不是辣椒)。分拣线这种场景,漏检率比误检率更致命,漏掉一个辣椒意味着分拣错误,误检一次可能只是多一次复核。我自己的验收标准是:单帧推理延迟低于部署设备预算的一半,漏检率低于 1%,误检率低于 5%,才敢说模型真的能上线。

这是我第一次做农业检测时踩过的坑。当时只看测试集 mAP50 有 0.93,觉得稳了,结果现场一跑,远处小辣椒漏了一半,FPS 也达不到分拣节拍。后来把验收指标改成产线视频抽帧回测加单帧耗时统计,才真正让模型在分拣线上跑起来。希望这些弯路能帮你省几个通宵。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 21:20:39

从零搭建PMSM FOC仿真模型:原理、步骤与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 21:20:09

K230如何用MicroPython重构边缘AI开发范式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 21:20:03

一行命令解锁Amicro:shadcn/ui Registry命名空间集成简单指南

一行命令解锁Amicro&#xff1a;shadcn/ui Registry命名空间集成简单指南 【免费下载链接】Amicro--Micro-transitions- 项目地址: https://gitcode.com/gh_mirrors/am/Amicro--Micro-transitions- Amicro&#xff08;Amicro--Micro-transitions&#xff09;是一个基于…

作者头像 李华
网站建设 2026/10/2 21:19:27

航天仿真坐标转换:SOFA库实现GCRS到ITRS全流程与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 21:18:07

AI智能体训练、多AI协作与内容生产工业化实践解析

1. 今日三条主线&#xff1a;Agent训练方法论、多AI协作与内容生产工业化今天的AI圈信息密度相当高&#xff0c;翻了一圈热搜和项目流&#xff0c;真正值得从业者关注的其实集中在三条主线上。第一条是DeepSeek公开的智能体训练新方法&#xff0c;这属于底层技术层面的进展&…

作者头像 李华
网站建设 2026/10/2 21:15:35

Cesium 3DTiles分层分户抽屉实现:从节点遍历到动画完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华