简介:本资源是一款面向农业智能化监测场景的麦穗数量自动识别系统,基于YOLOv7目标检测算法实现,适用于农业科研人员、计算机视觉初学者及智慧农业项目开发者,解决田间麦穗计数依赖人工、效率低、误差大的实际问题。压缩包共101个文件,总大小48.4MB,涵盖38个Python核心脚本(含训练、推理与后处理逻辑)、33个YAML配置文件(定义模型结构、数据路径与超参)、14个Jupyter Notebook(含YOLOv7与YOLOv5对比实验、TensorRT/ONNX Runtime部署验证等交互式分析)、4个XML标注文件、3个Shell自动化脚本及PDF技术文档等,目录结构完整、模块职责清晰,支持从数据准备、模型训练到多平台部署的全流程复现。目前已有283人学习下载,读者可直接运行Notebook开展算法对比实验,调用Shell脚本一键执行预处理或评估,结合PDF文档理解技术原理,并参考Dockerfile与IDEA配置快速搭建开发环境。 去年农忙季节接手了一个麦穗计数需求,要在田间环境下对麦穗数量做自动统计,替代人工数穗的重复劳动。我直接选了YOLOv7作为检测主干网络,做了整套麦穗数量识别系统。从数据标注、模型训练、后处理计数到界面部署,前后花了三周左右,最终在自建数据集上mAP@0.5达到0.93左右,单张图片推理耗时约25ms。这篇文章把完整的设计源码思路、训练参数、踩坑记录都整理出来,想动手做农业视觉落地项目的朋友可以直接参考。
这套系统适合谁?一是做农业智能化、田间表型分析的研究者,二是想在边缘设备上做轻量化检测的开发者,三是刚接触YOLO系列想找完整项目练手的同学。你不需要从零懂检测原理,但最好对Python、PyTorch有基本概念。下面我开始拆解整个系统。
1. 内容整体设计与思路拆解
1.1 为什么选YOLOv7而不是其他检测算法
麦穗检测这个任务,核心难点在于目标小、密度高、相互遮挡严重。我评估过Faster R-CNN、SSD、YOLOv5和YOLOv7,最终选了YOLOv7,原因有三。
第一,YOLOv7在COCO数据集上的推理速度和精度平衡非常出色。它的E-ELAN结构通过扩展、洗牌、合并基数来增强网络学习能力,在不显著增加计算量的前提下把特征提取做得更充分,这对小目标检测非常关键。
第二,YOLOv7的receptive field(感受野)设计对密集小目标友好。麦穗在一张图片里可能只有几十个像素宽,背景又是复杂的田间环境,叶片、土壤、阴影都会干扰检测,YOLOv7的SPPCSPC结构能更好地融合多尺度特征,让模型在小目标上不那么容易漏检。
第三,部署生态成熟。PyTorch官方权重、ONNX导出、TensorRT加速都有现成方案,我后续要接摄像头实时视频流和Web界面,选YOLOv7能省掉很多迁移成本。
注意:YOLOv8之后也有新版本,但很多时候项目要的是“稳定可复现”,YOLOv7的源码结构清晰、依赖库兼容性好,在工业落地场景里依然是性价比很高的选择。
1.2 系统架构与数据流设计
整个系统分为三个模块:数据模块、训练模块、推理统计模块。
- 数据模块:负责田间麦穗图像的采集、清洗、标注、数据集划分,输出标准COCO或YOLO格式的标注文件。
- 训练模块:加载YOLOv7模型,配置超参数,在GPU上进行训练,输出最佳权重best.pt和最后一轮权重last.pt,同时记录训练日志和PR曲线。
- 推理统计模块:读取训练好的权重,对单张图片、批量图片或视频流进行检测,输出每个麦穗的边界框坐标、置信度、类别,通过去重逻辑统计麦穗数量,最后在可视化界面中展示。
数据流是单向的:采集原始图像 → 标注 → 生成训练集/验证集 → 模型训练 → 权重评估 → 推理计数。这样的解耦设计让我在调参或换数据集时不用改动全部代码,每个模块可以独立测试。
不过我建议你在做同样的系统时,一开始就把接口定义清楚,比如数据集的目录结构固定为images和labels两个文件夹,标注格式统一为YOLO txt格式,后面可以省去很多格式转换的麻烦。
2. 核心细节解析与实操要点
2.1 麦穗数据集采集与标注要点
数据是农业检测项目里最耗时的一环,也是最决定模型上限的一环。麦穗数据采集要注意几点。
一是多样性。不能只在晴天中午拍,要有阴天、逆光、早晨露水未干、傍晚光线偏暗的照片。我拍了一部分高角度俯视,也拍了一部分45度斜视,因为不同拍摄角度下麦穗形态差异很大,模型能不能泛化就看训练集覆盖是否足够。
二是密集程度。实际麦田里麦穗是挨着的,甚至交叉重叠,所以采集时要刻意覆盖密植场景,而不是只挑稀疏好看的拍。我大概按稀疏、中等、密集三档来控制数据比例,约2:3:5。
三是清晰度。运动模糊、对焦不准的图片直接删除,不要犹豫。模糊样本会让模型学到错误的纹理特征,后期很难清洗。
标注我用的是LabelImg,虽然界面老一点,但胜在轻量、支持YOLO格式和VOC格式。标注麦穗这类密集目标时有一个经验:边界框尽量贴合麦穗主体,不用把芒(麦穗顶端的细丝)完全包进去,因为芒非常细长,强行框进会引入大面积背景噪声,反而降低检测精度。
标注完成后,按8:1:1划分训练集、验证集、测试集。划分时不要随机乱分,最好按地块或拍摄批次划分,避免同一块地的相似图像同时出现在训练集和验证集里,导致评估指标虚高。
重要提醒:麦穗标注的一致性比数量更重要。如果10个人标出来的框大小差异很大,模型会学得很难受。有条件的话让同一个人完成全部标注,或者至少制定一个“框到哪个位置”的规则说明。
2.2 数据增强与格式转换
标注完的原始数据不能直接丢给模型,要先做格式转换和数据增强。YOLOv7原生支持YOLO格式的txt标注,每行是class x_center y_center width height,坐标值都是归一化到0~1的。
如果用的是LabelImg,导出YOLO格式时自动生成对应txt,不需要自己写转换。但如果你从其他平台拿到的是VOC格式xml或COCO格式json,就需要写转换脚本。我在源码里已经包含了voc2yolo.py和coco2yolo.py两个工具脚本,核心逻辑就是读取xml里的bndbox坐标,除以图片宽高得到归一化中心坐标和宽高,然后写入txt。
数据增强方面,YOLOv7训练时自带mosaic增强,它把4张图片随机裁剪拼接成一张新图,能显著提升模型对重叠目标的鲁棒性。我在训练小尺寸麦穗模型时开启mosaic,发现漏检率下降了约6%。但mosaic增强在训练后期不要一直开,最好在最后30个epoch关闭,让模型在接近真实分布的图像上微调收敛,这个技巧在YOLOv7源码里可以通过配置实现。
此外还可以叠加轻度HSV色彩抖动和随机翻转,模拟田间不同光照条件。我不建议做太大的几何增强(比如旋转45度以上),因为麦穗有比较固定的朝向,转太多反而让模型学到错误的方向信息。
2.3 模型选择与预训练权重加载
YOLOv7有标准版(yolov7.pt)、Tiny版(yolov7-tiny.pt)和W6版(yolov7-w6.pt)。麦穗检测属于单类别密集小目标任务,我采用的是标准版yolov7.pt,在COCO上预训练过,迁移学习效果好。
如果你想部署到Jetson Nano这类边缘设备,建议用yolov7-tiny.pt,参数量更小,帧率更高,但精度会下降一些。如果追求极致精度且GPU显存足够(12G以上),可以试yolov7-w6,它的输入分辨率可以设到1280,小目标检测能力会更强。
加载预训练权重时要注意:模型输出类别数是COCO的80类,而我们的任务只有1类(麦穗)。源码里训练时会自动把检测头替换成对应类别数的结构,并保留backbone(主干网络)的权重,只重新训练检测头。这个过程叫迁移学习,能大幅节省训练时间,让模型快速适应新数据集。
我踩过一次坑:直接修改yaml配置文件里的nc参数后,忘了下载预训练权重,从零开始训练,结果训练了50个epoch损失还在高位。后来加载yolov7.pt后,10个epoch内损失就明显下降。除非你的数据集规模足够大(比如10万张以上),否则强烈建议加载预训练权重。
3. 实操过程与核心环节实现
3.1 环境配置与安装步骤
我的开发环境是Ubuntu 20.04 + Python 3.8 + PyTorch 1.10 + CUDA 11.3,显卡是NVIDIA RTX 3060 12G显存。配置方法很简单,先安装PyTorch:
pip install torch==1.10.0+cu113 torchvision==0.11.0+cu113 -f https://download.pytorch.org/whl/torch_stable.html然后克隆YOLOv7源码并安装依赖:
git clone https://github.com/WongKinYiu/yolov7.git cd yolov7 pip install -r requirements.txtrequirements.txt里主要是opencv-python、matplotlib、numpy、pyyaml、tqdm这些基础库。如果你的环境里已经装过,可以跳过。
之后把数据集目录整理如下:
datasets/ ├── wheat/ │ ├── images/ │ │ ├── train/ │ │ └── val/ │ └── labels/ │ ├── train/ │ └── val/然后在yolov7/data/目录下新建wheat.yaml:
train: datasets/wheat/images/train val: datasets/wheat/images/val nc: 1 names: ['wheat']这里train和val路径是相对yolov7根目录的。如果路径写错,训练时会直接报错或者跳过所有图片,我在一开始就遇到过因为路径不对导致训练集为空的问题。
3.2 训练参数配置与计算过程
训练的核心配置在train.py的命令行参数里。我自己使用的训练命令如下:
python train.py --data data/wheat.yaml \ --cfg cfg/training/yolov7.yaml \ --weights yolov7.pt \ --batch-size 8 \ --epochs 200 \ --img-size 640 \ --device 0 \ --workers 4 \ --name wheat_exp参数含义逐个解释:
--batch-size 8:受限于12G显存,我试过batch-size 16时直接OOM(显存溢出)。如果你的显卡是24G显存,可以调到16或32,更大的batch能加速收敛。--epochs 200:麦穗数据集不算复杂,200个epoch足够收敛。如果你的数据量少,可以适当减少到100~150,防止过拟合。--img-size 640:YOLOv7标准输入尺寸。如果麦穗在图像中很小,可以试1280,但训练时间会翻倍,显存需求也更高。--workers 4:数据加载的进程数。Linux下一般4~8都可以,Windows下如果报错建议改成0。
训练过程中,你想快速验证代码是否跑通,可以先只跑5个epoch观察loss是否下降,确认无误后再正式训练。我每次开新实验都会这样做,省去等半天才发现配置错误的尴尬。
训练完成后,权重保存在runs/train/wheat_exp/weights/目录下,best.pt是按验证集mAP指标保存的最优权重,last.pt是最后一个epoch的权重。我通常用best.pt做推理。
3.3 模型评估指标解读
训练结束后,代码会自动输出P(精确率)、R(召回率)、mAP@0.5、mAP@0.5:0.95这些指标。
- P(精确率):预测出的麦穗中有多少是真正的麦穗。值越高,误检越少。
- R(召回率):真实麦穗中有多少被找到了。值越高,漏检越少。
- mAP@0.5:IoU阈值0.5下的平均精度均值。农业场景我主要看这个指标。
- mAP@0.5:0.95:IoU阈值从0.5到0.95(步长0.05)的平均指标,更严格,适合看模型定位精度。
我的麦穗模型最终达到P=0.91、R=0.88、mAP@0.5=0.93。这个结果是可以接受的,因为麦穗相互遮挡严重,召回率想超过0.92非常困难,过分追求高指标会带来大量误检,反而影响最终的计数准确度。
3.4 麦穗数量统计核心逻辑
检测模型输出的是边界框,不是最终数量。因为同一个麦穗在不同帧或不同网络输出中可能被画出多个重叠的框,直接数框数会导致重复计数。我的计数逻辑分三层:
第一层,置信度过滤。检测结果中置信度低于0.25的直接丢弃,减少低质量预测框的干扰。这个阈值在田间场景下需要调整,如果想提高召回就调低到0.15,如果想保证精度就调高到0.4。
第二层,NMS(非极大值抑制)。YOLOv7推理时已经内置了NMS,它会去掉同一目标周围重叠的框,只保留置信度最高的那个。这一步能解决“一个麦穗被画了三个框”的问题。
第三层,跨帧去重。在视频流场景中,同一个麦穗在连续多帧里会反复出现,直接每帧计数再累加会多计。我的方案是记录每个检测框的中心点坐标,如果当前帧的某个框与上一帧某个已统计框的中心距离小于20像素,就视为同一个麦穗,不重复计数。这个阈值可以根据麦穗移动速度调整,静止摄像头下20像素就够了。
单张图片的计数比较简单,直接统计NMS之后的框数量。源码中对应的核心代码是:
import torch from models.experimental import attempt_load from utils.general import non_max_suppression # 加载模型 model = attempt_load('best.pt', map_location='cuda') model.eval() # 图像预处理 img = cv2.imread('field.jpg') img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img_resized = cv2.resize(img_rgb, (640, 640)) img_tensor = torch.from_numpy(img_resized.transpose(2, 0, 1)).float().div(255.0).unsqueeze(0) # 推理 with torch.no_grad(): pred = model(img_tensor)[0] # NMS去重 pred = non_max_suppression(pred, conf_thres=0.25, iou_thres=0.5) # 统计数量 for det in pred: if det is not None: boxes = det[:, :4].cpu().numpy() confs = det[:, 4].cpu().numpy() wheat_count = len(boxes)这段代码的核心逻辑就是:加载权重 → 图像预处理 → 推理 → NMS → 统计框数量。实际项目中你还需要在推理前做letterbox处理,保持原始图像宽高比,周围填充灰色像素。YOLOv7的detect.py里已经内置了这个操作,直接复用即可。
4. 可视化界面与部署方案
4.1 本地可视化界面实现
我做了一个基于PyQt5的简易桌面界面,左边显示原始图像,右边显示检测结果,底部实时刷新麦穗数量。用到的核心库是PyQt5和OpenCV。
界面布局大致是:点击“选择图片”按钮,弹窗选择图片文件,然后调用检测函数,在QLabel里显示绘制了边界框的结果图像,同时在状态栏显示“检测到:XX个麦穗”。
如果要做视频流检测,可以用OpenCV的VideoCapture读取视频帧,每一帧调用一次检测函数,再用QThread把检测放到子线程中,避免界面卡死。这里我给一个简单的子线程示例:
class DetectThread(QThread): frame_signal = pyqtSignal(int) def run(self): cap = cv2.VideoCapture('test_video.mp4') while True: ret, frame = cap.read() if not ret: break count = detect_wheat(frame) # 检测并返回数量 self.frame_signal.emit(count)QThread继承类里把检测结果通过信号发到主界面,主界面更新标签内容。这个方案实测视频流可以达到15~20FPS,满足基本使用需求。
4.2 导出ONNX与边缘部署
如果想把模型部署到没有PyTorch环境的机器上,或者要接入无人机、摄像头盒子,建议导出成ONNX格式。导出命令:
python export.py --weights runs/train/wheat_exp/weights/best.pt --img-size 640 --batch 1导出后得到best.onnx,可以用OpenCV DNN模块或ONNX Runtime加载推理:
import onnxruntime as ort import numpy as np session = ort.InferenceSession('best.onnx', providers=['CUDAExecutionProvider', 'CPUExecutionProvider']) input_name = session.get_inputs()[0].name # 预处理后的图像 outputs = session.run(None, {input_name: img_tensor.numpy()})ONNX Runtime推理速度与PyTorch相当,但依赖轻量很多,非常适合部署在Jetson Nano、树莓派或Windows服务器上。
小技巧:导出ONNX后,可以用Netron工具打开模型结构图,直观查看每一层的输入输出张量形状。排查问题(比如输入尺寸不对、输出层缺失)时非常好用。
4.3 Flask Web服务部署
我还做了一个基于Flask的Web接口,这样前端只需要调HTTP接口就能获得识别结果,方便后续对接小程序或农业管理平台。
from flask import Flask, request, jsonify import base64 app = Flask(__name__) @app.route('/detect', methods=['POST']) def detect(): data = request.json img_base64 = data['image'] # 接收base64编码的图片 img_bytes = base64.b64decode(img_base64) # 解码、推理、计数... return jsonify({'count': wheat_count, 'boxes': boxes})这种部署方式的好处是和业务系统解耦,算法升级时只需要替换模型文件和后端逻辑,前端无感知。不过要注意,如果请求量很大,建议用FastAPI替代Flask,并发性能更好。
5. 常见问题与排查技巧实录
5.1 小目标漏检严重怎么办
症状:图片中远距离的麦穗完全没有检测出来,只有近距离的大麦穗被检出。
排查思路:
- 先检查标注框是否覆盖了小目标。如果训练集里小尺寸标注框占比少,模型就没机会学到小目标特征。
- 检查输入分辨率。用
--img-size 640时,小目标下采样后可能只剩几个像素。可以把分辨率改为960或1280重训,或者用SAHI切片推理方案,把大图切块后分别检测再拼接。 - 降低置信度阈值观察。如果调低阈值后能检测到,说明模型有较弱响应,只是被阈值过滤了,可以适当放宽阈值。
我处理麦穗小目标问题时,最终方案是提高输入分辨率到960,并增加小目标样本的标注数量。效果提升很明显,但显存占用也高了30%,需要权衡。
5.2 训练时显存溢出
症状:运行train.py时中途报错CUDA out of memory。
解决方案按优先级排列:
- 调低batch-size,从16降到8或4。这是最直接的方法。
- 调低img-size,从640降到512。
- 关闭mosaic增强。mosaic在拼接4张图片时耗费额外显存。
- 使用梯度累积。YOLOv7源码没有直接内置,但可以修改train.py每间隔n个batch进行梯度回传,模拟大batch的效果。
我的建议是优先调batch为4并配合梯度累积,这样精度影响最小,显存压力也扛得住。
5.3 麦穗密集遮挡导致计数不准
这是麦穗计数项目里最棘手的问题,即使mAP指标达到0.93,实际计数误差也可能达到8%~12%。
我的处理思路是:
- 增加密集样本比例。我在原有数据集上补充了500张高密度麦穗图像,重新标注训练后,密集场景的召回率提升明显。
- 后处理中使用“密度图”辅助校正。训练一个轻量级密度回归模型(比如CSRNet)输出密度图,通过积分估算总数量,再与检测结果做融合校正。这个方法在严重遮挡场景下比纯检测更稳。
- 接受合理的误差范围。麦穗数量统计本质上允许一定误差,如果允许5%以内的计数误差,单靠检测模型加后处理就已经足够。
5.4 权重文件损坏或训练中断后怎么恢复
训练到一半断点是常事。YOLOv7训练过程会保存last.pt,可以直接用它作为--weights参数继续跑:
python train.py --data data/wheat.yaml --cfg cfg/training/yolov7.yaml --weights runs/train/wheat_exp/weights/last.pt --epochs 200 --resume加--resume会自动读取上一次训练的状态,包括epoch数、优化器状态和最新的学习率。实测恢复后的训练曲线是连续的,不会出现指标断层。如果权重文件在校验时报错,大概率是下载不完整,重新下载即可。
5.5 不同光照/天气下泛化差
症状:模型在晴天测试集上效果好,但一碰上阴天或傍晚就漏检严重。
这是农业视觉项目的通病。解决办法:
- 在标注数据中加入不同光照条件的样本,保证数据集覆盖晴、阴、云、逆光等场景。
- 推理时先做图像增强预处理,比如自动白平衡、对比度受限自适应直方图均衡化(CLAHE)。我可以直接在推理代码里加一行:
gray = cv2.cvtColor(image, cv2.COLOR_BGR2LAB) clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) gray[:, :, 0] = clahe.apply(gray[:, :, 0])CLAH对光照不均的图像有一定校正作用,能提升检测稳定性。
6. 项目经验总结与后续扩展方向
做完这套麦穗识别系统,我自己最大的体会是:农业检测项目真正的难点不在模型结构,而在数据质量和后处理设计。YOLOv7已经是一个非常成熟的工具,真正决定项目成败的是你有没有认真处理标注一致性、有没有针对密集场景做后处理优化。
我建议你在动手之前先问自己三个问题:想让模型检测到什么目标?在什么场景下部署?允许的误差范围是多大?把这三个问题想清楚,后面的技术路线基本就不容易跑偏。如果只是单纯为了练手,直接跑通YOLOv7官方训练流程,然后替换成自己的数据集,就能感受到从数据到模型的完整链路。
最后再分享一个扩展方向:这套系统的核心代码可以迁移到其他农作物目标检测任务,比如稻穗计数、玉米雄穗识别、果实成熟度检测,只需要替换数据集并调整输出类别数即可。甚至可以把检测结果接上无人机GPS定位信息,生成田间密度分布热力图,辅助精准施药和产量预测。前期的检测与计数能力是基础设施,后期的农业决策应用才是真正的增值空间。
本文还有配套的精品资源,点击获取