YOLOv10实战:大豆检测系统从数据标注到界面封装

发布时间:2026/10/21 12:47:57
YOLOv10实战:大豆检测系统从数据标注到界面封装 前阵子帮某农业团队做大豆考种预研需求说起来不复杂摄像头架在输送带正上方实时统计豆粒数量顺带把破瓣、杂质的位置标出来。真正动手才发现同一颗豆子换个角度、换个光线分类模型得分就来回飘光靠“整张图属于什么类别”根本没法用。把方案改成目标检测之后我选了YOLOv10从采集数据、标注、训练到用PyQt5封装界面前后折腾了两周多最后系统能在600毫秒左右处理一帧1920×1080的图。这篇把整个链路完整记录下来包括哪些环节容易翻车给后来人一个参考。这篇内容适合三类人准备拿目标检测做毕设或课程项目的学生想把训练好的模型快速包成桌面工具的开发以及关注农业视觉落地场景、想了解真实项目里哪里有坑的算法同学。我不会只贴命令会把每个选择背后的理由也讲清楚因为很多坑不在代码本身而在你“为什么这么选”。1. 为什么用目标检测做大豆计数与品质分类先想清楚任务边界1.1 大豆检测的三个典型场景大豆相关的视觉任务我接触到的落地场景大致分成三类。田间测产和考种是最常见的科研场景。农学团队需要统计豆荚数量、豆粒数量和粒径分布过去靠人工数费眼睛还容易错。这类场景光照变化大豆粒和叶片、泥土背景混在一起对检测器的泛化能力要求比较高。输送带在线分拣是工厂场景。机位固定光照相对可控但豆粒是连续运动的对推理速度有硬指标。检测结果要能联动剔除阀漏检比误检更麻烦因为漏掉一颗就直接混进下一道工序里了。还有实验室仪器分析场景通常把大豆平铺在标准托盘上拍照环境最干净但客户对精度要求很苛刻豆粒重叠边缘要尽量分开为后续的粒径测量提供准确的框坐标。1.2 检测、分类、分割的任务拆解很多第一次做这类项目的人第一步就在模型选型上纠结到底是分类、检测还是分割我第一版也试过纯分类加滑窗把图像切成小块挨个判断“有没有大豆”。效果很差因为滑窗尺寸很难定豆子大小不统一窗口里可能只有半颗豆。而且分类输出只有“有”或者“没有”做不了计数更给不出位置。语义分割能把每个像素归类但标注成本极高要把豆粒边缘一点一点描出来推理对硬件要求也更高。对“数清楚”“框出来”这个需求来说属于杀鸡用牛刀。目标检测是刚好匹配的方案。每个豆粒输出一个边界框框中心近似豆粒位置框数量就是计数结果同一个类别还能继续做大小统计。我最终只用一个类别“soybean”把破瓣、杂质都先归到背景里。第一版就强行区分多类别会把检测器训乱——它连豆子都找不全更别说区分好坏。先让模型把所有豆子找全再谈品质分类这是很关键的路线决策。1.3 系统的组成模块整个系统不是一个训练脚本而是五个模块串起来缺一环都不行。采集模块负责把相机画面变成训练样本我用的是工业USB相机实际用手机拍也能跑通标注模块生成YOLO格式的txt标签训练模块在GPU上迭代权重推理模块把best.pt加载进内存输出框、类别和置信度界面模块负责显示图像、绘制检测框、汇总计数、导出结果。从软件工程角度看推理模块和界面模块最容易写烂。很多人训练完模型就只写个预测脚本把图片路径写死界面无法选择文件摄像头流更是不管。后面我会专门说界面封装因为这是“系统”和“脚本”的本质区别。2. YOLOv10相比v8改了哪些东西端到端机制、网络结构、选型依据2.1 去掉NMS意味着什么YOLO系列一直有个绕不开的步骤叫NMS非极大值抑制。算法会对同一个目标输出一堆重叠框NMS按置信度排序保留得分最高的框把重叠度大的邻框删掉。NMS看着简单实际很烦。阈值调高了密集目标容易被合并调低了一堆假框还在。它还是不可微的只存在于推理阶段导致训练和推理的行为不完全一致。更关键的是大豆在画面里经常一颗挨着一颗NMS稍微激进一点相邻两颗豆就可能被压成一个框计数直接少一。YOLOv10引入了一致性匹配损失网络在训练时用“一对多”分支给每个目标分配多个正样本学得更充分同时并行一个“一对一”分支让每个目标只匹配一个输出框。推理时只用一对一分支直接从模型输出里拿最终框整个NMS阶段就没了。这个改动对密集小目标场景收益很明显。我实测同一堆紧密排列的大豆v8推理后还要手动调NMS的iou阈值v10直接给框数量和位置都更干净。2.2 网络结构里新增的模块拆解YOLOv10骨干网络保留了v8那套C2f结构强调梯度流和特征复用同时加入了几个新设计。第一个是PSA金字塔尺度注意力它在特征图上做自注意力计算让网络更关注豆粒区域而忽略背景。大豆很小背景占大头注意力机制确实有帮助。第二个是SCDown下采样模块用卷积加池化组合替代普通步进卷积在降低分辨率时减少信息损耗和计算量。对部署来说省一点是一点尤其要上嵌入式设备时。第三是轻量化分类头设计在保证精度的前提下去掉冗余计算这也是v10能在参数量不大的情况下跑出不错mAP的原因。我在项目里不需要改网络结构直接用官方结构训练但这些知识点对调参有用。比如发现豆粒漏检集中在较小尺寸就该往注意力机制和输入分辨率方向想而不是盲目加大模型。2.3 针对大豆场景选n还是s还是mYOLOv10有n、s、m、l、x五个尺寸我参考了官方公开数据结合自己的机器情况做了对比。型号参数量约精度表现形态适用场景n2.3M基础够用极轻量嵌入式、实时性要求极高的场景s7.2M均衡体积适中普通GPU、精度和速度兼顾m15.4M更优中等精度优先、显存充足l / x24M最高偏重离线分析、服务器我当时机器是8GB显存的桌面GPU直接选了s。理由很直接n的精度在大豆密集场景下不够稳m训练时间和显存压力更大s刚好卡在甜点上。如果让我重新选对只有4GB显存的环境我会用n加更大的输入分辨率如果做离线高精度分析直接上m。模型尺寸不是越大越好还要看数据量和目标大小。小数据集硬套大模型极大可能过拟合训练集loss很漂亮验证集一塌糊涂。数据只有几百张的话s都偏大n反而更稳。3. 把现实画面变成标准训练集大豆数据的采集、标注与增强3.1 数据来源与拍摄规范数据质量决定模型上限很多项目死在标注混乱上。采集阶段的第一个原则是场景多样性。我让样本来至少覆盖三种背景白色托盘、黑色绒布、输送带本色。一开始只拍白背景训练出来模型到了输送带场景漏检严重后来补了一部分深色背景数据才好起来。第二个原则是分辨率统一。我统一拍1920×1080训练输入用640或960。相机离豆子高度固定保证豆粒在画面里的大小相对一致。不要一会儿贴着拍一会儿离远了拍会导致模型对尺度无所适从。第三个原则是数量。单类别检测起步建议800到1500张图每张包含几十到上百颗豆。数量太少mosaic增强也只能无中生有地拼贴真实光照变化还是学不到。划分比例我按8:1:1切训练集、验证集、测试集。其中测试集一定要留出完全没见过的现场照片不能从同一段视频的相邻帧里抽否则泄题严重。3.2 标注工具与YOLO格式的对应关系我用labelImg做单机标注界面简单学习成本低。如果需要多人协作可以上CVAT支持网页端标注。YOLO格式的标签是一个txt文件每行代表一个目标格式是类别id x中心(归一化) y中心(归一化) 宽度(归一化) 高度(归一化)归一化是指相对图像的宽和高。假设图像是1920×1080某颗豆的边界框左上角在(650, 430)右下角在(710, 485)那么计算方式如下x1, y1, x2, y2 650, 430, 710, 485 img_w, img_h 1920, 1080 w (x2 - x1) / img_w h (y2 - y1) / img_h cx ((x1 x2) / 2) / img_w cy ((y1 y2) / 2) / img_h # 对应的label行0 0.3541 0.4236 0.0312 0.0509我见过不少人在这一步出错把中心点x写成了左上角x或者忘了除以图像宽高。标注工具导出时一般不会错但自己写脚本处理时就要格外小心。另存为txt时记得把所有标注放一个目录图片放另一个目录别混着放。3.3 增强策略里最容易被忽略的文件联动问题训练时可以在线增强官方代码会自动做mosaic、翻转、色彩抖动等操作。但如果自己做了离线增强比如把图旋转90度或者水平翻转那么同一张图的标注框坐标也必须同步变换。很多人只增强图片label文件还是旧的训练时边框就和豆子对不上。这个错误很隐蔽loss看起来在降但mAP上不去因为网络一直在学习“图片长这样框在别的地方”。水平翻转时中心点x坐标要变成1减原x垂直翻转同理处理y坐标。旋转时要先把四个角点算出来旋转后再重新生成外接矩形框。def flip_horizontal(img, box_cxcywh): cx, cy, bw, bh box_cxcywh new_cx 1.0 - cx return img.transpose(0), (new_cx, cy, bw, bh) # 伪代码示意做增强前最好写个小脚本把增强后的图片和框一起可视化出来肉眼检查一遍。这一步只花十分钟但能避免后面浪费半天训练时间。4. 训练阶段的关键参数和损失曲线解读4.1 环境准备与目录约定我用的Python 3.10安装官方实现的确切版本最好固定版本号避免API变动导致网上找到的教程对不上。# 创建虚拟环境并激活 conda create -n soybean python3.10 -y conda activate soybean # 安装PyTorch根据你的CUDA版本选择对应命令 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装训练框架 pip install ultralytics opencv-python pillow数据集目录我按固定约定组织soybean/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── soybean.yaml图片文件名和标签文件名要严格同名比如001.jpg对应001.txt。4.2 数据集配置文件配置文件指定数据路径和类别信息path: ./soybean train: images/train val: images/val test: images/test nc: 1 names: 0: soybean注意path写相对路径时是相对运行命令的当前目录不是相对yaml文件目录。我第一次就因为这个路径解析到错误位置模型训练了半天都在空数据上转圈。4.3 训练命令与参数选择我用的是s模型训练命令长这样yolo detect train \ datasoybean.yaml \ modelyolov10s.pt \ epochs150 \ imgsz640 \ batch16 \ device0 \ patience20 \ projectrun_soybean \ nameexp1参数里面有几个值得展开说。imgsz640是输入分辨率。豆粒目标偏小640够用。显存有余量我会上960大分辨率对小目标的召回率提升明显。代价是训练和推理都更慢看机器下菜。batch16受显存限制。8GB显存跑yolov10s640输入16附近比较安全如果OOM就降到8。别为了追求大batch把机器搞崩小batch多跑几个epoch效果未必差。patience20是早停耐心值验证集指标连续20轮没提升就停止。这个参数很重要能帮你节省时间。我有次设了150个epoch实际到90轮就停了后面全是靠早停兜底。modelyolov10s.pt是预训练权重。在COCO上预训练过的模型迁移到大豆任务收敛速度和最终精度都比随机初始化好很多。别一上来就yolov10s.yaml从头训练数据量不够时效果差不少。4.4 从曲线判断模型质量训练过程中日志会输出分类损失、边框损失和各项指标。我最关心的有三样训练集loss、验证集loss、mAP50。正常情况是训练loss和验证loss同步下降mAP50稳步上升。如果出现验证loss先降后升、训练loss还在降说明过拟合了解决办法是提前停或者加大增强。如果训练loss一开始就很大、怎么都不降优先检查标注文件是否对应上了图片其次检查学习率。训练结束时best.pt和last.pt两个权重会保存在输出目录。best是验证集指标最好的权重last是最后一轮的。部署时永远用best别为了省事直接用last。我见过有人把last当最终模型明明验证集指标已经提示过拟合了还在自我感觉良好。输出指标里除了mAP50还要关注mAP50-95。mAP50只看预测框和真值框IoU超过0.5就算命中比较宽容mAP50-95看更严格的定位质量。大豆要做粒径测量的话mAP50-95至少要过0.8这说明框的位置足够准。5. 从训练权重到能用的软件PyQt5界面与推理封装5.1 为什么选PyQt5而不是Flask或Gradio模型训练完只提供一个python推理脚本是不行的需求方要的是“能点按钮、能看图、能导出结果”的工具。界面方案我当时在Flask、Gradio、PyQt5之间选。Gradio开发最快几行代码就能出一个网页Demo但它的问题是并发能力弱、摄像头拉流体验差、样式定制受限。用作内部演示可以作为产线工具不太合适。Flask适合做成服务端前后端分离但意味着要写一整套前端页面周期长。对单机单摄像头场景来说有点重。我最后选了PyQt5原因是它天然适合桌面端工具窗口即程序不需要浏览器QThread解决界面卡顿样式可以按客户要求微调。缺点是需要学一点Qt的信号槽机制不过入门很快。5.2 封装一个稳定的Detector类界面归界面模型推理逻辑必须单独封装不要在槽函数里写一堆OpenCV和模型代码。我建了一个SoybeanDetector类负责加载权重、预处理、推理、后处理、绘制框界面只调用它的方法。import cv2 import numpy as np class SoybeanDetector: def __init__(self, weights_path, conf_thres0.25): from ultralytics import YOLO self.model YOLO(weights_path) self.conf_thres conf_thres def detect(self, frame_bgr): # 训练时默认是RGB输入OpenCV读出来是BGR必须先转换 frame_rgb cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGB) results self.model.predict( sourceframe_rgb, confself.conf_thres, imgsz640, verboseFalse ) boxes results[0].boxes if boxes is None: return frame_bgr, {count: 0, detail: []} xyxy boxes.xyxy.cpu().numpy() confs boxes.conf.cpu().numpy() clses boxes.cls.cpu().numpy() stats [] for box, conf, cls in zip(xyxy, confs, clses): x1, y1, x2, y2 [int(v) for v in box] cv2.rectangle(frame_bgr, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame_bgr, f{conf:.2f}, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) stats.append({box: [x1, y1, x2, y2], conf: float(conf)}) result {count: len(stats), detail: stats} return frame_bgr, result这里有个非常容易踩的坑cv2.imread读出来是BGR通道顺序而模型训练时的图像预处理通常是RGB。如果你不转换直接喂给模型就会看到模型把黄色大豆的响应搞反检测效果莫名变差。检测结果画框时又要注意cv2.rectangle要画在BGR图上所以推理完成后不要再转回RGB。5.3 多线程、摄像头输入与结果导出界面最忌讳的就是推理期间卡死。大图推理几百毫秒如果直接放在界面线程里跑窗口会变成“未响应”客户体验极差。解决方式是用QThread把推理放到后台线程。from PyQt5.QtCore import QThread, pyqtSignal import numpy as np class DetectThread(QThread): result_ready pyqtSignal(np.ndarray, dict) def __init__(self, detector, frame): super().__init__() self.detector detector self.frame frame def run(self): result_img, stats self.detector.detect(self.frame) self.result_ready.emit(result_img, stats)界面里点击“检测”按钮先把当前帧复制一份传给线程立即返回。线程完成后通过信号把结果图像和统计数据传回主界面刷新。这样无论推理多慢界面都能正常缩放、拖动、点击。摄像头输入我用cv2.VideoCapture拉流放到另一个QThread里循环读帧主界面用信号更新预览。注意摄像头退出时要释放资源否则下次打开会报设备占用。导出功能很简单把统计结果写成csvimport csv def export_csv(stats, path): with open(path, w, newline) as f: writer csv.writer(f) writer.writerow([index, x1, y1, x2, y2, confidence]) for i, item in enumerate(stats, 1): x1, y1, x2, y2 item[box] writer.writerow([i, x1, y1, x2, y2, item[conf]])有一个生产细节如果图像里大豆数量超过几百颗一帧的推理时间会明显上涨。我在Detector里加了max_det参数限制单帧最多输出目标数防止极端情况推理时间失控。这个参数在密集场景下很实用。6. 实测效果与排障记录6.1 不同光照下的表现差异模型在标准光照下跑得很好但搬进现场后第一个坑就是背光。输送带上方有灯豆粒在灯下反光边缘和背景分离度反而差。检测结果表现为漏检尤其豆粒堆叠区域的暗部豆子直接消失。我试过在推理前加CLAHE对比度受限的自适应直方图均衡化把局部对比度拉上来漏检率降低了一些。def preprocess_clahe(img): lab cv2.cvtColor(img, cv2.COLOR_BGR2LAB) l, a, b cv2.split(lab) clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) l clahe.apply(l) lab cv2.merge([l, a, b]) return cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)但这不是万能药最好的方案还是改善现场光源让豆粒受光均匀。算法能修补一部分问题别指望它完全替代设备环境。6.2 文件路径与图像通道的隐蔽陷阱做过Windows桌面工具的人应该都有体会中文路径是个大坑。OpenCV的老版本cv2.imread对中文路径支持不好会读到空图。界面里用户选择的文件夹可能叫“大豆0921”一旦路径带中文图片就读不出来了。解决办法是先用np.fromfile读字节再用cv2.imdecode解码import numpy as np import cv2 def imread_unicode(path): data np.fromfile(path, dtypenp.uint8) return cv2.imdecode(data, cv2.IMREAD_COLOR)另外如果你在界面里用PyQt的QLabel显示图像QPixmap需要的是RGB格式不是BGR。先把cv2.cvtColor转回RGB再显示否则大豆会变成蓝绿色调看起来非常诡异。6.3 模型明明收敛却检测不到目标的排查链路这个坑我印象最深也是很多人会卡住的问题训练输出mAP很高模型在验证集上效果不错但部署到自己的UI代码里就是什么也检测不到。排查是有固定链路的不要瞎猜。第一步检查预处理是否一致。训练时官方代码做了letterbox缩放推理时如果你直接cv2.resize把图片硬拉到640×640目标形状被拉伸框位置全乱。解决办法是推理也使用letterbox填充。第二步检查通道顺序也就是我前面反复强调的BGR转RGB。验证集测试脚本和推理脚本可能用的输入格式不同训练时自动走RGB你手工推理时用了BGR表现就是一团糟。第三步检查置信度阈值。我用0.25做默认但现场光照差时模型置信度整体偏低0.25可能把所有框都滤没了。可以先降到0.05看原始输出里到底有没有低置信度的框再决定阈值怎么设。第四步检查加载的是哪个权重。之前遇到一个诡异情况训练没问题但加载的路径指向了last.pt而last.pt正好是过拟合到训练集的那个阶段在真实场景当然泛化差。换成best.pt立刻正常。那次排查让我养成了习惯每次部署前先用一张测试图在命令行脚本里走一遍完整流程确认框能出来、置信度合理再接到界面上。别偷懒这一步能省下大量翻代码的时间。6.4 密集遮挡场景的进阶处理大豆大量堆叠时模型输出的框会重叠计数依然会偏少。这是所有检测模型都面临的遮挡问题不是YOLOv10独有。我的处理思路从易到难排一下。最简单的是把输入分辨率提到960或1280让小目标在特征图里占据更多像素。其次是只检测托盘外露的豆粒通过目标大小和分布密度估算总数做一层统计修正。再进阶是换用分割模型输出豆粒轮廓再用轮廓分离来计数。如果要做粒径测量建议在检测框基础上加一个边缘拟合的后处理步骤因为检测框是矩形不能精确代表圆形豆粒直径。把框中心的图像区域裁出来做二值化再拟合椭圆计算长短轴这才是可用的粒径信息。密集场景永远是农业视觉里的硬骨头不要指望单模型把所有问题解决。工程上允许校准和启发式修正商业项目里只要计数误差在客户容忍范围内就是合格交付。我在实际个项目里最深的感受是基于YOLOv10的大豆检测系统真正难的不是跑通官方训练命令而是把数据、训练、推理、界面这一整条链路理顺。从模型选型、数据标注到界面封装每一步都会冒出来意想不到的小问题。最后再分享一个经验做这类项目一定要保留一份版本记录包括数据集划分方式、训练参数、best权重位置、推理脚本版本。客户哪天说“你之前那个模型效果不是挺好”你得能复现当时的全部环境不然就只能重新训练时间和信任都耗不起。整个项目做完之后我对目标检测落地的认识变化很大算法模型只是底座真正稳定的系统是靠对数据细节和工程问题的持续打磨堆出来的。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询