1. 项目概述与核心目标拆解
1.1 这是一个什么样的项目
我一直在帮某农业智能实验室做一些作物长势监测的视觉算法,前段时间接到一个需求:针对温室大棚里的番茄,不仅要检测出果实,还要把一串一串的果串整体识别出来。相比于单果检测,果串形态的预测难度要高不少,因为番茄果串的特征太不规律了——大小不一、角度各异、密集生长、相互遮挡,再加上温室里复杂的光照和叶片遮挡,很多常规检测模型在实验室数据集上表现不错,一放到真实温室就翻车。
这个项目的核心任务是设计并实现一套基于YOLOv5的温室番茄果串检测系统,能够对输入图像中的番茄果串进行实时定位与分类。毕业设计、农业AI课题、甚至温室智能化改造项目里,这类需求都很常见。如果有人想学习目标检测从数据标注到模型落地的完整流程,这个项目也非常适合,因为它的数据采集和标注环节很考验对目标定义的理解。
系统整体包含数据采集、标注处理、模型训练、后端推理和可视化展示几个模块。我直接把源码整理成了一个完整仓库,训练和预测的命令都已封装好,拿到之后按说明跑起来就行。为了让这篇文章能真正帮到人,我会把整个过程中踩过的坑、调参的心得、数据标注的细节全部写出来。
1.2 为什么温室场景比普通场景更难做
一开始我有过错觉,觉得番茄果串检测不就是多目标检测吗,拿YOLOv5默认配置跑一跑,精度应该差不到哪里去。实际跑完第一轮实验之后才发现,温室场景下的检测难度被低估了。
首先,温室大棚中光线变化剧烈。晴天正午光照强,番茄表面会产生高光反射,果串轮廓变得模糊;阴天或清晨光线弱,暗处的果实和叶子颜色接近,边界难以区分;夜晚补光灯开启时,又会产生偏色。这些光照不确定性直接影响目标特征提取。
其次,果串的目标定义模糊。单果是"一个圆溜溜的红色区域",边界清晰,标注时只要用框框住就行。但果串是由若干果实和茎杆组成的聚合目标,什么时候算"一串"、两个重叠的果串要不要分开标,没有明确规则。如果标注标准不统一,模型训练出来的效果会非常不稳定。
第三个难题是重叠与遮挡。温室番茄通常每穗挂4~6个果实,果串之间挨得近,叶片又是遮挡大户。很多目标只有不到一半的可见面积,YOLOv5这种基于锚框的方法对这种局部可见目标,天然存在漏检问题。
因此,这个项目的难点并不在"用什么模型",而在于如何定义目标、如何规范数据、如何在训练策略上针对性调整。我在后面的章节里会逐个展开。
2. 环境配置与关键技术选型
2.1 YOLOv5版本与依赖环境的确定
项目落地时我对比过YOLOv5的v6.0、v7.0和官方最新主线分支。最终选了v6.0版本,原因很简单:稳定。v7.0之后仓库结构改动比较大,很多第三方教程、工具链还没跟上;而v6.0的文档和社区案例最多,遇到问题基本都能搜到解决方案。在工程化项目中,选型求的不是最新,而是生态成熟度。
依赖环境方面,我整理了一套可复现的配置:
Python 3.8+ PyTorch >= 1.7 torchvision >= 0.8 CUDA 10.2 / 11.2 opencv-python >= 4.1 matplotlib >= 3.2 numpy >= 1.18推理阶段我建议用PyTorch原生的模型结构,配合ONNX导出做加速。训练阶段如果显存不够,可以在配置文件中关闭一些不必要的模块,后面我会详细说。
2.2 硬件配置与性能预期
我训练用的是一张12GB显存的显卡,实测YOLOv5s输入分辨率640x640时,单卡能跑到56左右。番茄果串检测对这种边缘农业场景来说,对实时性要求没那么极端,生产环境中每秒处理3~5帧完全够用。更大的瓶颈在数据侧,而不是模型侧。
这里有一个容易忽略的点:温室场景下摄像头通常安装在固定机位,拍到的图像角度变化很小,光照变化却很大。因此推理模型不需要很强的"多尺度旋转适应性",反而需要很强的"光照鲁棒性"。这也是后续数据增强策略的出发点。
为了给不同预算的使用者提供参考,我把硬件方案分成三档:
| 档位 | 配置 | 训练能力 | 适用场景 |
|---|---|---|---|
| 入门 | CPU训练 + 小数据集 | 仅可微调小模型,速度极慢 | 验证流程、学习调试 |
| 标准 | 8~12GB显存GPU + SSD | YOLOv5s/yolov5m,500张图约3~6小时 | 中小规模温室实验 |
| 进阶 | 24GB显存GPU + 高速存储 | YOLOv5l/x,千张以上数据 | 规模化、多季节覆盖 |
2.3 模型骨架的选择:为什么是YOLOv5s
很多人一上来就选YOLOv5x,觉得模型越大精度越高。但在番茄果串检测这个项目里,目标并不算特别小,通常占画面面积的十分之一以上,所以大模型的提升有限,反而明显拖慢推理速度。我实测过YOLOv5s和YOLOv5l在相同验证集上的mAP差距,只有1~2个百分点,但推理速度差距接近3倍。
综合考虑成本和效果,最终确定YOLOv5s作为主力模型。如果现场算力允许、且对精度有更高要求,可以用YOLOv5m作为备选,只需在训练命令里换一个权重文件,不用改其他代码。
提示:模型规模选择的逻辑,应该根据"目标在画面中的尺度占比"和"实时性要求"来定,而不是一味堆参数。温室固定机位场景下,优先考虑轻量模型。
3. 数据集的构建与标注规范
3.1 温室番茄图像采集策略
很多第一次做农业视觉项目的同学,在数据采集上容易犯两个错误:要么图省事,只在一天的一个时间段去拍一次;要么盲目追求数量,一天拍上千张,但内容高度重复。
我的建议是按照"时间-天气-角度-距离"四维矩阵来拍。具体策略如下:
第一,时间维度。番茄果实的颜色和温室光照在一天中变化很大。我分别在早上7点、中午12点、下午4点、晚上补光灯开启后四个时间段采集。中午高光容易导致红色过饱和,晚上补光下颜色偏黄,这些都是训练集必须覆盖的。
第二,天气维度。晴天、阴天、多云各占一定比例。温室里的散射光和直射光对图像特征影响完全不同,如果只拍晴天,模型会在阴天场景下精度骤降。
第三,角度维度。固定机位虽然视角固定,但检测系统前期测试时,难免要手持设备移动。我拍的时候包含了俯拍、平视、斜角45度左右等角度。
第四,距离维度。果串距离镜头50厘米和150厘米时,成像尺度差别很大。虽然YOLOv5本身具备多尺度检测能力,但训练数据还是要尽量覆盖不同尺度,否则泛化能力会打折扣。
最终采集了约1200张图像,清洗掉模糊、过度曝光、完全被挡住的无效图像后,剩下约1000张可用。对温室场景项目来说,1000张图已经能训练出一个不错的效果。
3.2 类别定义与边界框标注的细节问题
标注是果串检测项目里最容易被忽视、却又最影响最终精度的环节。我刚才提到果串边界模糊,需要在标注前制定一份明确的规范,而且团队所有人都要按同一规范执行。
我定下来的标注规则是这样的:
- 类别一:成熟果串。果串中至少有1个果实达到成熟红色,且以整个果穗的轮廓作为检测目标,框要覆盖所有果实及相连的茎段。
- 类别二:未成熟果串。果实整体呈绿色或青白色,同样以整串边界为准。
- 标注时如果两个果串在图像里重叠但能明确分辨出各自果实归属,就分别标两个框;如果果实互相交错、连人都分不清归属,就直接融合成一个框,不要强行切分。
这个过程有人可能会问:为什么要把成熟和未成熟分开?原因是温室番茄采摘、疏果、花期管理的作业决策完全不同,分开类别能为后续产量预估和分级管理提供基础。如果只做单纯的"有没有果串"检测,数据价值会低很多。
标注工具我用的是LabelImg,它是VOC格式标注的常用工具,导出为YOLO需要的txt格式也很方便。具体标注流程是:先用矩形框框住目标,再选择类别,保存为XML格式,再通过脚本批量转换成YOLO格式的txt文件。
转换脚本的核心逻辑如下:
import xml.etree.ElementTree as ET import os def xml_to_yolo(xml_file, classes, out_dir): tree = ET.parse(xml_file) root = tree.getroot() size = root.find('size') w, h = int(size.find('width').text), int(size.find('height').text) lines = [] for obj in root.iter('object'): cls = obj.find('name').text if cls not in classes: continue cls_id = classes.index(cls) box = obj.find('bndbox') x1, y1 = float(box.find('xmin').text), float(box.find('ymin').text) x2, y2 = float(box.find('xmax').text), float(box.find('ymax').text) dw, dh = 1.0 / w, 1.0 / h cx, cy = (x1 + x2) / 2.0 * dw, (y1 + y2) / 2.0 * dh bw, bh = (x2 - x1) * dw, (y2 - y1) * dh lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") with open(os.path.join(out_dir, os.path.splitext(os.path.basename(xml_file))[0] + ".txt"), "w") as f: f.write("\n".join(lines))3.3 数据增强:不只为了提点,更是为了解决光线过拟合
YOLOv5自带了很丰富的数据增强策略,包括马赛克增强、水平翻转、HSV色域变换、随机缩放、随机剪裁等。刚开始我用默认配置,训练完发现白天高光下的检测效果特别差,仔细分析后发现是训练集里高光样本太少,默认增强没有产生足够多的对抗性样本。
于是我专门针对温室环境做了一套增强方案。在YOLOv5的超参数配置文件hyp.scratch-low.yaml里,重点调整了色域相关参数:
hsv_h: 0.015 # 色调偏移,控制红色到橙色的变化 hsv_s: 0.7 # 饱和度增强,模拟不同天气下的颜色浓淡 hsv_v: 0.5 # 明度增强,模拟高低光照差异同时把马赛克增强的概率提高到1.0,让每张训练图都由4张图拼接而成。这样做的另一个好处是,温室里叶片和果实的分布本身就有很强的重复纹理,马赛克增强能让模型学会特征组合,而不只是记住整体布局。
还有一个很容易被忽略的点:我没有直接使用YOLOv5自带的自动学习增强参数,而是先固定这套手动配置跑了一轮,得到baseline之后再去微调。原因是自动增强在温室这种强光照场景下,可能会把果实颜色增强到严重失真,反而破坏语义信息。
3.4 数据集划分与标注质量控制
数据集按8:1:1划分成训练集、验证集、测试集。这里有一个额外的约束:同一个果串在同一个摄像头视角的不同时间点拍到的图像,不能同时出现在训练集和验证集中。我按照"物理果串编号"来做划分,而不是随机抽图像。如果同一串番茄的图像既在训练集又在验证集,mAP会有虚高,训练看起来效果很好,实际部署验证时立刻现原形。
标注质量控制方面,我会在所有图像标注完成后,再单独做一轮交叉检查。检查的重点是:
- 是否存在漏标。比如果串只露出一小部分,标注员可能漏掉。
- 边界框是否过紧或过松。框过紧会把部分果实切出框外,框过松会框入大量叶片,都会干扰学习。
- 类别是否混淆。青色番茄和成熟番茄在颜色过渡期很难区分,需要在标注规范里增加过渡期归属规则:果实超过三分之一变为橙色就算成熟。
全部检查完毕,再用一个统计脚本检查每个类别的目标数量是否均衡。如果成熟果串数量远多于未成熟果串,就要补充采集未成熟阶段的图像,否则模型会对未成熟果串严重欠拟合。
4. 模型训练与调优全流程
4.1 训练前的准备工作
数据集准备好之后,还需要检查两件事。
第一件事是数据格式。YOLOv5要求图像放在images文件夹下,对应标签放在labels文件夹下,文件名必须一一对应。标签文件的每行格式是"类别ID 中心点x 中心点y 宽度 高度",所有坐标值都是归一化到0~1之间的小数。我写了一个校验脚本,专门检查标签文件中是否有越界坐标、空文件、负值等常见错误。这一步很重要,因为标签文件一旦出错,会在训练过程中引发奇怪的loss波动。
第二件事是生成数据集配置文件。YOLOv5通过yaml文件指定数据路径和类别名,我的配置文件如下:
train: /data/tomato_training/datasets/images/train val: /data/tomato_training/datasets/images/val test: /data/tomato_training/datasets/images/test nc: 2 names: ['ripened_harvest', 'unripened_harvest']names的顺序必须和转换脚本中classes列表的顺序一致,否则类别对不上,训练白跑。
4.2 核心训练参数的选择与解析
我把训练命令写在下面,这套参数是用YOLOv5s在1000张图上的实测优选配置:
python train.py \ --img 640 \ --batch 16 \ --epochs 200 \ --data tomato_data.yaml \ --weights yolov5s.pt \ --cache \ --patience 50 \ --name tomato_yolov5s逐个解释一下关键参数。
--img 640表示输入分辨率640x640。这个值不能随意改,它决定了模型看到的细节程度。如果果串在图像中占比小,可以提高分辨率,但显存占用会明显增加。温室场景下的果串占比比较大,640是性价比不错的选择。
--batch 16在12GB显存下刚好卡在临界值。Batch size影响的是BN层统计量的稳定性。Batch太小,BN均值方差波动大,模型训练不稳定;Batch太大,一次迭代耗时长,收敛变慢。16是一个经过验证的稳妥值。
--epochs 200看起来偏多,但实际训练中有过早停机制。--patience 50表示如果连续50轮验证集mAP没有提升,训练自动停止。我实测最快的一次在第90轮就停住了,说明模型在前期已充分拟合,盲目跑满200轮反而浪费时间。
--cache参数会把图像加载到显存中缓存,显著减少数据读取的IO开销。1000张图完全放得下,训练速度提升明显。如果显存紧张,建议不要开。
还有两个在代码里默认开启但值得单独说明的选项:--multi-scale和--label-smoothing。我建议在基础训练阶段关闭multi-scale,因为多尺度采样会引入额外的输入分布抖动,先固定尺寸让模型稳定收敛。等基础模型精度稳定后,再打开multi-scale做第二轮微调,可以再提升一些泛化能力。
4.3 训练过程观察:loss曲线与mAP变化
训练不是把命令丢进去就完事了,需要持续观察日志。YOLOv5的训练日志里有两个关键指标:box_loss和cls_loss。
box_loss反映边界框回归的误差,刚开始会在0.08到0.1之间波动,如果正确训练,会逐步降到0.03左右。cls_loss反映分类误差,对于成熟/未成熟两个类别,由于类别区分度明显,loss下降速度应该很快,通常在20轮内就会低于0.02。
如果发现box_loss训练过程中出现剧烈震荡、甚至反弹,很可能是学习率设置偏高。YOLOv5默认初始学习率是0.01,配合SGD优化器,训练前期适合大多数场景。如果数据集较小或者标注噪声较大,学习率要降到0.005左右。
除了loss,还要看验证集上的mAP@0.5和mAP@0.5:0.95。mAP@0.5是IoU阈值0.5下的平均精度,反映模型"大致框得准不准"的能力,通常要求能达到0.9以上。mAP@0.5:0.95是IoU从0.5到0.95每隔0.05计算一次的平均值,反映框的精确度,这个指标对果串这种非刚性目标来说,能达到0.55以上就算很好了。
为什么mAP@0.5:0.95对果串检测这么难?因为果串的框并不像人脸、车牌那样有明确的边界。同一个标注框,人眼觉得"标得挺好",但IoU计算出来可能只有0.6。这是目标模糊特性决定的,不是模型能力不行,不用过分焦虑。
4.4 针对温室场景的专项调优
第一轮训练完成后,我拿到了一个baseline模型:mAP@0.5为0.91,mAP@0.5:0.95为0.53。对通用检测任务来说已经不错,但拿到温室测试集上跑了一遍,发现了两个典型问题:重叠密集果串存在漏检,夜间的暗光图像精度下降明显。
针对第一个问题,我把NMS的IoU阈值从默认的0.45调到了0.35。这样做的逻辑是:果串密集重叠时,两个真实目标框的交叠程度本身就高,如果阈值太大,NMS会把相邻的果串当成同一个目标直接抑制掉。降低阈值后,相邻目标被保留的概率变大,漏检减少。
针对第二个问题,我在数据集中增加了暗光图像的占比,并且通过多尺度训练来增强模型对光照和尺度的鲁棒性。同时把置信度阈值从0.25调到0.3,过滤掉夜间低置信度的假阳性框。
这些参数在代码里不是超参,而是推理脚本里的常规参数,修改起来没有风险:
model.conf = 0.3 model.iou = 0.35 model.max_det = 100这里的max_det也要注意。果串最多、混叠最严重的图像里,检测框数量可能超过默认的89个,所以我把上限放宽到100个,避免后处理阶段丢掉真正的目标。
4.5 模型评估与效果验证
训练结束后,我用了三种方式验证模型效果,而不是只看mAP数字。
第一个是精确率-召回率曲线。看曲线之前在哪个置信度阈值下达到平衡。如果曲线在0.9附近还保持较高的召回率,说明模型在保证准确的同时不会漏掉太多目标。番茄果串检测场景中,漏检的代价比误检更大,因为漏检意味着这个果串可能一直没被管理,所以我宁可牺牲一点精确率也要保持召回率。
第二个是PR曲线的可视化和对角线检查。如果PR曲线下面积小,说明模型输出的置信度分布不够合理,需要回到数据标注层面排查,大多数情况下是标注不一致导致的。
第三个是典型场景实测。我专门挑了一组"密集重叠、强逆光、轻度遮挡"的高难度图像做推理可视化。把检测框画出来,跟原始图像对比,看人工能不能接受。这一步靠的不是指标,而是视觉经验。很多时候mAP很高,但框的细节严重抖动,在真实管理场景中并不好用。
5. 检测系统的整体设计与部署实现
5.1 系统架构设计
整个检测系统从上到下分为三层:交互展示层、业务服务层、算法推理层。
交互展示层是我用PyQt5写的一个简洁可视化界面,功能包括选择图片、启动检测、框结果展示、保存标注结果。业务服务层负责图像预处理、调用推理接口、解析检测结果、格式化输出。算法推理层承载YOLOv5模型,提供检测能力。
对于需要部署到生产环境的场景,我建议把算法推理层单独封装成HTTP服务,用Flask或FastAPI实现,业务层通过接口调用。这样做的好处是模型和业务解耦,后续模型更新不需要修改其他代码。
5.2 推理流程的工程化封装
原始YOLOv5的detect.py脚本适合调试,不适合直接集成到系统里,因为它依赖命令行参数且输出格式固定。我在实际项目中单独写了一个推理封装文件,核心逻辑如下:
import torch import cv2 import numpy as np from pathlib import Path class TomatoDetector: def __init__(self, weights_path, device='0', conf=0.3, iou=0.35): self.model = torch.hub.load('ultralytics/yolov5', 'custom', path=weights_path, force_reload=False) self.model.conf = conf self.model.iou = iou self.model.max_det = 100 self.device = device def predict(self, img): if isinstance(img, str): img = cv2.imread(img) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) results = self.model(img) boxes = results.xyxy[0].cpu().numpy() return boxes这个封装返回的boxes数组,每一行包含[x1, y1, x2, y2, conf, cls_id],后续只需要按自己的数据格式转换即可。
部署时还遇到一个问题:PyTorch模型权重在CPU机器上推理速度偏慢。我把模型导出为ONNX格式,利用ONNX Runtime做推理加速。在CPU环境下,单张640x640图像的推理时间从300毫秒降到了100毫秒左右。导出命令如下:
python export.py --weights runs/train/tomato_yolov5s/weights/best.pt --include onnx --opset 12如果部署机器有NVIDIA显卡并且安装了TensorRT,还可以进一步导出为engine格式,推理延迟能压到20毫秒以内。温室实时监测场景一般不需要这么高的帧率,但做大棚视频流巡检时,速度肯定越快越好。
5.3 前端展示界面的实现要点
界面部分我用了PyQt5实现,因为做桌面工具最方便。关键需求有三个:图片加载与显示、检测结果绘制、结果导出。
检测框的绘制要特别注意图层顺序:先画原图,再画检测框和标签,避免遮挡关键信息。颜色方面,我用绿色表示成熟果串,用黄色表示未成熟果串,在温室背景下对比度比较清晰。标签文本包含类别名和置信度,比如"熟果串 0.92"。
导出功能是整个系统使用频率最高的功能之一。检测结果需要保存为标准COCO格式或JSON格式,方方便后续产量统计。我实现的导出逻辑是把检测坐标转换为相对原图的绝对像素坐标,并附上采集时间、图像文件名。
整个界面代码结构按照"界面逻辑与业务逻辑分离"的原则拆分。界面窗口类只负责布局和控件事件响应,业务逻辑放在独立模块里。这样后续如果要把桌面版改成Web服务,只需要替换交互层,算法和业务逻辑不用动。
5.4 源码工程结构一览
这个项目的源码结构整理如下,方便拿到代码后快速定位:
tomato_detection/ │ ├── data/ │ ├── images/ │ │ ├── train/ │ │ ├── val/ │ │ └── test/ │ ├── labels/ │ │ ├── train/ │ │ ├── val/ │ │ └── test/ │ └── tomato_data.yaml │ ├── src/ │ ├── train.py │ ├── detect.py │ ├── export.py │ ├── detector.py │ └── utils/ │ ├── xml_to_yolo.py │ └── validate_labels.py │ ├── ui/ │ └── main_window.py │ ├── weights/ │ ├── yolov5s.pt │ └── best.pt │ └── requirements.txttrain.py和detect.py直接通过命令行运行,detector.py是集成接口,main_window.py是GUI入口。对于只需要跑通流程的读者,可以跳过GUI部分,直接调detector.py。
6. 常见问题与排查技巧实录
6.1 训练loss不下降或者直接爆掉
训练过程中最常遇到的问题就是loss不降。我排查过几次,问题基本出在三个地方。
第一个是标签文件出错。YOLO格式的坐标值必须在0到1之间,如果转换脚本里除以图像宽高的逻辑写错,坐标就会变成几万这种离谱值,模型永远学不会。校验脚本能解决这个隐患。
第二个是数据集类别分布严重不均衡。如果成熟果串占了90%,未成熟果串只有10%,那么模型训练时梯度大部分由成熟样本提供,分类指标会呈现"整体mAP偏高、但某个类别几乎完全检测不到"的假象。处理办法是给稀有类别分配更高的损失权重,或者对稀有类别的样本做复制增强。
第三个是学习率设置过高。YOLOv5默认0.01在通用数据集上没问题,但数据量小时容易出现初始阶段loss就爆炸的情况。把学习率降到0.005甚至0.002,可以让训练过程更稳定。
我遇到过一种更诡异的状况:训练前期loss正常下降,到第80轮左右开始震荡上升。最后定位到原因,是训练过程中关闭了标签平滑导致的优化目标突变。YOLOv5的标签平滑默认在训练集较小时产生正则化效果,改来改去反而好事变坏事,不同阶段修改损失函数相关的配置时要谨慎。
6.2 读数据太慢导致GPU利用率低
1000张图如果用HDD机械硬盘做数据读取,训练时GPU会经常陷入等待状态,显卡利用率可能只有30%。解决办法是加--cache参数,把图片全部缓存进内存或显存,数据读取不再是瓶颈。如果数据集太大无法完全缓存,建议先把所有图像缩放到统一分辨率,再做caching。
另外,Windows环境下OpenCV读取图像、PyTorch的DataLoader开启多进程时,偶尔会报"Too many open files"错误。这是Windows的文件句柄限制导致的,把num_workers从默认值调小到2或1,问题就解决了。
6.3 检测出来的框总是偏大或偏小
这是一个很有意思的现象。模型明明收敛了,mAP也不错,但可视化后发现框普遍比果串实际轮廓大一圈,或者只框住了部分果实。
原因通常出在标注环节:模型学到的是标注的分布均值。如果多人标注时,有人习惯留白,有人习惯紧贴果实,模型会学到一种"平均框法",输出框自然介于两者之间。
解决办法是在标注规范里要求每个目标框紧贴轮廓,再对已有标注做批量偏移修正。我写了一个脚本,计算所有训练框与人工微调框之间的平均偏移量,然后应用到整个标签集。这个脚本虽然简单,但对框精度有明显改善。
6.4 模型在夜间图像上的表现不如白天
温室白天和夜间的图像特征差异太大了,单独增加一点暗光样本起不到决定性作用。我的做法是分两步:先做全局ImageNet风格归一化,再把曝光偏低的图像做直方图均衡化增强后加入训练集。
还有一个自研的小技巧:对夜间图像做CLAHE自适应直方图均衡,增强局部对比度,让果串与叶片边界不那么模糊。这个预处理可以放在数据增强管线里,也可以放在推理前置步骤中。我实测夜间mAP@0.5从0.72提升到了0.83,提升明显。
6.5 推理时内存不断上涨
连续处理视频帧时,PyTorch的显存或内存占用会持续增长,看起来像内存泄漏。实际上大多数情况下是因为在for循环里频繁创建tensor,没有及时释放,或者detector实例被重复创建。我处理的办法是常驻一个全局模型实例,推理时只把图像数据传给同一个实例,不重新加载模型。
如果确实需要在循环里多次调用推理,建议在每轮结束执行torch.cuda.empty_cache()。但频繁调用这个函数也有性能开销,正确使用姿势是每隔固定帧数调用一次,而不是每帧都调用。
7. 从项目源码延伸到场景化应用
7.1 果串计数与产量预估的扩展思路
训练好的果串检测模型不只是个"检测框可视化工具",它还可以为产量预估提供基础数据。在温室管理中,管理者关心的是每个大棚总共有多少串番茄成熟了、多少串还需要多长时间成熟。检测系统输出的每一帧果串坐标,配合多视角标定和一定的跟踪算法,就能完成这一目标。
最简单的做法是固定机位连续拍摄,用帧间检测框的重叠度做目标匹配,统计果串出现次数。如果同一位置的果串连续多帧都出现,就判定为同一个目标,重复计数会被剔除掉。对于移动拍摄的情况,则需要结合位姿估计,复杂度会高很多,但对于产量预估的初级版本,固定机位就够了。
7.2 移动端与边缘设备部署的轻量化
温室场景里很多农业设备是嵌入式设备,算力远不如服务器。我在项目后期做了一轮轻量化尝试,思路有两个:一是用TensorRT对模型做FP16量化,二是用剪枝去掉不重要的通道。前者的精度损失几乎可以忽略,推理加速明显;后者需要重训练补偿,工作量相对大。
还有一个更省事的方法:直接用官方提供的YOLOv5n模型权重作为起点,在温室数据集上继续微调。YOLOv5n比YOLOv5s参数量少了几倍,在边缘设备上运行更流畅,精度只是略低。适合对精度要求不极端、但设备资源有限的场景。
7.3 与温室管理系统的联动
如果整个系统不止要"看见"番茄果串,还要触发后续的浇灌、施肥、采摘提醒等动作,检测系统需要输出标准化的信息格式。我建议把检测结果统一打包成JSON,包含图像ID、目标类别、边界框坐标、置信度和时间戳。
这样后端的管理系统只要解析JSON就能做业务逻辑。比如某果串连续三次检测均为成熟状态,就触发"可采摘提醒";区域内成熟果串数量超过阈值,就在大屏上显示"需优先处理"标记。这种系统对接不需要改模型代码,JSON协议就是接口的全部定义。
8. 实操中的真实心得与避坑记录
写这篇文章时,我又重新回顾了一遍整个项目的开发过程。有一些经验如果当初有人提前告诉我,可以少走很多弯路。
第一点,数据标注的优先级一定高于模型调优。有很多人拿到项目后第一反应是尝试各种模型结构、调优各种超参数,却发现精度一直没有质变。回头检查数据时,才发现标注里到处都是边界框漂移、类别错标、漏标,浪费了大量时间。一个干净的数据集配上一套稳定的默认参数,效果往往超过脏数据配上精心调优的参数。
第二点,温室场景中最有决定性的技术动作是搭建"多轮闭环验证"。第一轮训练出的模型,必须拿它到真实温室里跑一轮,把预测错的案例全收集回来,补齐到训练集里,再训练第二轮。重复两三轮之后,模型的泛化能力会有一个明显跳跃。很多公开的算法比赛不需要这一轮,因为数据固定不变;但落地项目必须做这种迭代,否则测试集之外的场景能力很不够。
第三点,不要迷信单一指标。模型mAP高不一定意味着系统好用,尤其是果串检测这类边界模糊的任务。生产环境里真正起作用的,是系统在特定光照条件下的视觉表现。所以我会在每次模型更新后做一次人工目检,视觉上感觉框的位置合理、类别判断准确,才算验收通过。
最后分享一个关于推理速度的小技巧:如果只是做离线分析,对帧率要求不高,可以批量处理多个文件而不是逐张调用预测函数。YOLOv5支持传入图像列表,内部会自动做一个batch推理,速度提升很明显,而且代码改动很小。
温室番茄果串检测这个方向,本质上是用视觉方法帮助农业生产者把"看果子"这件重复劳动自动化。YOLOv5提供了成熟好用的检测框架,但真正决定系统可用性的,是项目设计中对数据、目标和部署环境的细致考量。这套方法不只是适用于番茄,辣椒、黄瓜、草莓这类挂在藤蔓上的串状作物,同样可以迁移使用。沿着这条思路继续扩展,还可以尝试用分割模型做果串轮廓提取,为自动采摘机器人提供更精细的定位信息。