ARTICLE DETAIL

资讯详情

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

基于YoloV7的麦穗数量识别系统:从检测到计数的完整实践

基于YoloV7的麦穗数量识别系统:从检测到计数的完整实践 简介一套基于YOLOv7算法的麦穗数量识别系统源码面向计算机视觉、机器学习方向的开发者和农业智能化项目人员用于麦穗目标检测与自动计数可辅助产量监测等场景。资源共101个文件压缩包约48.4MB核心由38个Python脚本、33个YAML配置文件和14个Jupyter Notebook交互式文档组成另含4个XML标注数据、3个Shell脚本、2个Git忽略文件、Dockerfile、Markdown文档及PDF报告等。Python脚本负责模型推理、图像预处理与计数主流程YAML配置文件管理训练与部署参数Notebook提供动态批处理、ONNX Runtime/TensorRT运行以及YOLOv5系列对比实验等可执行笔记便于读者边看边跑。XML文件承载标注信息适合监督训练Shell脚本和Dockerfile支持环境快速搭建与自动化部署Markdown与PDF补充使用说明和理论背景。目前已有285人学习下载适合作为农业目标检测项目的入门到进阶参考。该资源以完整源码和实验笔记相结合为复现麦穗识别流程、研究算法性能或开展产量估算提供了可直接参考与二次开发的工程化素材。1. 麦穗数量识别为什么非要用YoloV7这种检测模型小麦考种、田间估产和育种材料评价里最终产量构成三要素之一就是穗数。人工蹲在田里一根一根数一人一天最多测几十个样方漏数、重数全看当天状态。用传统图像处理做阈值分割麦穗在自然光照下和叶片、背景秸秆的颜色太接近分割后粘连成一片根本数不出单穗。所以这套麦穗数量识别系统选定了YoloV7作为检测骨架核心思路很直接先用深度学习算法框出每一个麦穗的目标框再对框的数量做统计得出穗数。深度学习算法选型在这个任务里不是跟风而是过去几年各种尝试后最稳的一条路。这整套系统源码解决的问题很具体给一张小麦田间图像或者无人机俯拍图输出一个数字——这块区域里有多少个麦穗。你拿到的检测框、类别名和置信度只是中间产物最终交付的是“穗数”这个可考核的指标。适合谁去复现做智慧农业项目的工程师、考种仪表的算法开发、本科毕设想落地的学生以及被老板一句“数个数”逼疯的农学相关研究人员。接下来我会按“为什么选型”到“训练踩坑”再到“系统封装”的顺序把麦穗数量识别整套链路摊开讲。2. 为什么选YoloV7而不是更新更“卷”的检测模型2.1 密集小目标场景下的架构优势麦穗识别不是通用物体检测它的难点有三个目标数量极大、目标彼此黏连、目标尺寸相对小但形状长条。YoloV7在2022年提出后长期是工业落地里精度和速度平衡最好的模型之一E-ELAN结构和辅助训练头让它在小目标上比YoloV5有更稳定的召回率。对比现在常见的YoloV8和RT-DETRV8的Anchor-Free设计在密集场景下框的贴合力好但训练收敛后会更容易在重叠穗子区域产生框合并V7默认Anchor-Based配合自动anchor求解在麦穗这种尺寸集中在30x120像素的长条形目标上反而能给出更稳的框回归。RT-DETR的Transformer结构精度上限更高但对小目标的查询机制需要很长训练周期田间数据量普遍只有几百到几千张实测容易欠拟合。具体到麦穗识别YoloV7的下采样倍数可以通过配置调整。默认模型从输入图像到最终特征图经历了5次stride2的降采样小目标在32x32以上的像素区域都能得到有效响应。麦穗虽然长但宽度可能只有10~20个像素这时候把输入分辨率推高到1280甚至1536比盲目选更大参数量模型有效得多。YoloV7的anchor数量和尺寸在训练前会基于你的标注框做K-means聚类不必手工预设这对长宽比在2.5~5之间的麦穗框尤其友好。2.2 检测任务和计数任务的关系输出坐标比直接输出数字有价值直接做回归计数听起来更轻量但实际工程里几乎没人这么干。因为麦穗图像里目标的分布极度不均匀一片叶子盖住两个穗子输入到输出端到端回归一个整数模型根本学不到“哪里被遮挡”这个空间信息。检测模型的好处是每个框都有坐标、置信度、类别你可以在后处理阶段做去重、过滤和局部密度统计。即使某个穗子被遮挡检测器给出一个低置信度框也没有关系你可以针对田间场景调低置信度阈值但要保留这个框的空间位置。而且检测结果的中间形态让系统更容易调试。你丢进测试代码一张图能可视化地看到哪些麦穗被漏检、哪些把高粱或者杂草误检成麦穗。改一版模型之后直接对比检测框的覆盖情况而不是对着一个总数猜。麦穗数量识别系统的源码结构通常分成检测器和计数器两段检测器负责找目标计数器负责把找出来的东西变成最终穗数。这个分离设计是后期在田间验证“漏检率”和“误检率”的关键。2.3 数据集的边界麦穗识别成败的隐藏因素很多复现者把注意力放在调参上忽略了YoloV7对数据分布的敏感程度。麦穗图像有高度场景化差异无人机俯拍、手机平拍、固定支架侧拍光照角度、穗色、生育期阶段都不同。如果你的目标是识别成熟期发黄的麦穗而训练集里全是灌浆期出穗的绿色麦穗那无论怎么调参检测器都会在成熟田块上翻车。数据要做分层至少要覆盖三个维度生育期抽穗期、灌浆期、成熟期、拍摄角度30度俯拍、90度垂直俯拍、天气光照阴天、晴天、逆光。标注格式方面常见的是LabelImg标注PascalVOC格式然后转成Yolo的txt格式。麦穗识别不建议用多边形分割标注时间和收益不成正比。密集场景下目标框要紧贴着麦穗边缘宁小勿大。框大了重叠区域增多两个相邻穗子的IoU一高NMS非极大值抑制会直接合并掉一个框这是麦穗计数偏低最主要的原因。一句话检测框的目的是给计数器提供可区分的位置所以“小而紧”是麦穗标注的核心原则。3. 从数据集到训练闭环最小可复现的完整流程3.1 环境与依赖版本矩阵先对齐YoloV7是源码形态的项目环境配置在整条链路里通常是第一个坑。我的建议是直接把venv装到项目根目录不要用全局Python环境深度学习和不同依赖版本冲突的排查代价远高于创建环境那几分钟。python3 -m venv .venv source .venv/bin/activate pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install -r requirements.txtrequirements.txt里核心依赖包括numpy、opencv-python、matplotlib、pyyaml、tqdm、pillow。torch的版本建议选2.0以上但不需要追最新关键是CUDA版本和显卡驱动匹配。我一般会先用python -c import torch; print(torch.cuda.is_available())验证GPU是否正常被读到这一步通过再往后跑否则后面训练报错你会怀疑人生。3.2 数据标注和格式转换把VOC格式变成Yolo需要的txt标注相对简单但格式转换里暗藏一个很多人忽略的细节。Yolo格式的坐标是归一化的中心点x、中心点y、框宽w、框高h全部除以图片宽高。如果直接从XML里读取坐标不除以宽度训练时Loss直接爆炸。import os import xml.etree.ElementTree as ET def convert_voc_to_yolo(xml_path, out_dir, classes): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size).find(width).text) img_h int(root.find(size).find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in classes: continue cls_id classes.index(name) 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) # Yolo格式中心点x, 中心点y, 宽, 高全部归一化到0~1 cx (x1 x2) / 2.0 / img_w cy (y1 y2) / 2.0 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) # 输出文件要和原图同文件名仅扩展名变成txt txt_name os.path.basename(xml_path).replace(.xml, .txt) with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(lines)) # 示例只转换麦穗这一个类别 classes_list [wheat_ear] for file in os.listdir(/data/voc_annotations): if file.endswith(.xml): convert_voc_to_yolo(os.path.join(/data/voc_annotations, file), /data/yolo_labels, classes_list)这段代码的逻辑是按图片实际宽高做归一化而不是假设图片是某个固定尺寸。因为无人机拍摄的图像可能被软件自动裁剪过实际宽高和标注软件里看到的不一致所以每次转换都从XML读取size节点。注意类别列表的顺序必须和训练配置文件里一致否则标签错位是最隐蔽的问题训练完了才发现检测结果和预期类别对不上且没有明显报错排查代价极大。3.3 训练配置yaml参数和命令行参数的区别YoloV7把数据集路径、类别数量、类别名称放在data/目录下的yaml文件里。麦穗识别通常只有一个类别但建议还是保留背景类的明文配置防止后续扩展识别杂草或病穗时改配置不全。# wheat.yaml train: /data/wheat_splits/train.txt # 每行指向一张训练图片的绝对路径 val: /data/wheat_splits/val.txt test: /data/wheat_splits/test.txt nc: 1 names: [wheat_ear]这里注意train和val字段指向的是txt文件txt里每一行是一张图片的绝对路径而不是图片目录。很多人会把这里误写成文件夹路径训练时会报找不到图片或者数据集读取不到数据图像张数为0训练循环直接空跑。实际训练命令我倾向于用下面的参数组合。python train.py --data data/wheat.yaml --weights yolov7.pt --epochs 120 --batch-size 16 --img 640 --device 0 --workers 4 --hyp data/hyp.scratch.p5.yaml参数说明里最需要关注的是--img。麦穗数据集如果图像尺寸在3000x3000级别直接喂原图没法训练。常见做法是切成子图训练或者把--img设为640、1280。我先说640这个经典设置显存压力小模型收敛快但小穗子容易漏检。如果想兼顾精度把--img设为1280但batch-size要降到8否则显存溢出。--hyp选p5还是p6版本差别在于针对大分辨率训练做了数据增强策略调整麦穗这种密集小目标场景我建议直接用hyp.scratch.p5而非默认的hyp.scratch.yaml。3.4 训练过程的监控指标怎么看训练日志里每行输出包括当前epoch、box_loss、obj_loss、cls_loss以及P、R、mAP0.5。麦穗识别要看两个关键指标Recall召回率和mAP0.5。召回率决定了漏检多少麦穗漏检穗子会直接导致计数值偏低这是最严重的业务问题。mAP0.5代表边界框预测质量但0.5的IoU阈值其实很宽松框偏移10个像素也算对。所以只盯着mAP0.5会忽略框定位精度问题计数时相邻麦穗的框如果大面积重叠会在NMS环节互相打架。我习惯在每个epoch结束时运行一次自带的val.py观察验证集上每张图片的漏检情况。YoloV7的val.py可以生成test.jpg的检测结果图我每隔10个epoch看一下可视化结果比只盯曲线有效得多。python val.py --data data/wheat.yaml --weights runs/train/exp/weights/best.pt --img 640 --conf 0.25 --iou 0.5 --save-txt --save-conf--save-conf会把置信度一起输出到txt这是后面计数逻辑需要的原始数据。很多人不做这个设置后面计数时想按置信度过滤误检都没法操作。4. 从模型到系统完成麦穗计数与接口封装4.1 推理阶段的后处理NMS阈值怎么调训练完成后真正的计数逻辑在推理脚本里。YoloV7的detect.py直接跑可以输出检测框但做计数系统不能直接用默认参数。NMS的IoU阈值是麦穗计数最核心的旋钮。默认值0.65在通用物体检测场景表现好但在麦穗这种密集场景下相邻两个穗子交叠IoU很容易超过0.5默认阈值会把其中一个框滤掉导致计数值偏低。调阈值经验IoU阈值设到0.2~0.3保留更多的重叠框后续交给计数逻辑去处理去重。另外一个原因是不同麦穗的视觉特征很相似模型容易在同一个穗子上生成两个邻近的框阈值太低会把这种重复框保留下来。所以不能说阈值越低越好要在验证集上做小规模调参。4.2 把检测结果变成穗数计数组件代码下面这段代码是麦穗计数系统的核心组件。它做的事归纳为三步读取检测输出按类别过滤统计有效框的数量。核心难点是解决重复框问题。import numpy as np class WheatCounter: def __init__(self, conf_threshold0.25, iou_threshold0.3): self.conf_threshold conf_threshold self.iou_threshold iou_threshold def nms(self, boxes, scores): 纯numpy实现的NMS避免重复框被计入个数 if len(boxes) 0: return [] boxes np.array(boxes, dtypenp.float32) scores np.array(scores, dtypenp.float32) x1 boxes[:, 0] y1 boxes[:, 1] x2 boxes[:, 2] y2 boxes[:, 3] areas (x2 - x1 1) * (y2 - y1 1) order scores.argsort()[::-1] keep [] while order.size 0: i order[0] keep.append(i) xx1 np.maximum(x1[i], x1[order[1:]]) yy1 np.maximum(y1[i], y1[order[1:]]) xx2 np.minimum(x2[i], x2[order[1:]]) yy2 np.minimum(y2[i], y2[order[1:]]) inter np.maximum(0.0, xx2 - xx1 1) * np.maximum(0.0, yy2 - yy1 1) iou inter / (areas[i] areas[order[1:]] - inter) inds np.where(iou self.iou_threshold)[0] order order[inds 1] return keep def count(self, detections): detections: list of [x1, y1, x2, y2, conf, class_id] 返回麦穗总数 boxes [] scores [] for det in detections: if det[5] ! 0: # 只统计类别ID为0的麦穗 continue if det[4] self.conf_threshold: continue boxes.append([det[0], det[1], det[2], det[3]]) scores.append(det[4]) keep self.nms(boxes, scores) return len(keep), [boxes[i] for i in keep]处理逻辑说明这里没有直接对检测框数量做加法而是过了一遍NMS。在推理阶段模型可能对同一个麦穗产出多个高置信度候选框直接计数会把一个穗子算成两个。NMS的作用是保留置信度最高且和其他保留框IoU小于阈值的框这样把一个穗子周围的冗余检测合并成一个。参数iou_threshold0.3比训练时的0.5更低因为计数场景下我们希望尽量能区分紧密相邻的目标IoU阈值越小两个重叠框被保留的可能性越高越不容易漏计。4.3 把计数和前后端接起来推理服务最小实现麦穗数量识别系统要做成能用的系统不能每次都手动跑脚本。常见做法是用FastAPI封装一个推理接口输入是图片路径或上传的文件输出是检测框可视化图和总数。生产环境里YoloV7部署有个绕不开的点直接用PyTorch推理虽然快但每个请求都会在显存里保存整个模型和图计算图并发能力差。通常先转换成ONNX再上服务但转换时opset版本、动态输入尺寸这些参数都有坑。from fastapi import FastAPI, UploadFile from PIL import Image import io, cv2, torch # 这里假设在外部已经加载好YoloV7的模型并设置eval模式 model torch.hub.load(WongKinYiu/yolov7, custom, best.pt, force_reloadFalse) model.eval() app FastAPI() app.post(/count) async def count_wheat(file: UploadFile): img_bytes await file.read() img Image.open(io.BytesIO(img_bytes)).convert(RGB) # yolov7推理要求BGR输入且做letterbox预处理 img cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) results model(img, size1280) detections results.xyxy[0].cpu().numpy().tolist() counter WheatCounter(conf_threshold0.25, iou_threshold0.3) count, boxes counter.count(detections) return {count: count, boxes: boxes}这里要注意推理时的size1280和训练时--img 1280保持一致。这个参数在YoloV7源码里最终会影响letterbox后图像的尺寸。训练是用640的输入推理用1280模型的感受野和数据分布全是错位的检测精度会明显下跌。很多人部署时遇到“训练挺好部署翻车”大半是这个参数没对齐。4.4 大图切片推理无人机图像的计数策略田间无人机拍的图动辄6000x4000像素直接送进模型不现实。常见做法是切块推理每块设置一定的重叠率防止麦穗正好被切成两半导致漏计。重叠率我一般设为10%~15%切块后检测完所有图块把检测框坐标映射回原图坐标系再送入计数器。这里存在一个稍复杂的坐标偏移问题切块时记录每个子图在原图中的起始坐标检测到的框坐标要加上偏移量才能得到全局坐标。def infer_tiled(model, img, tile_size1280, overlap0.15): h, w img.shape[:2] step int(tile_size * (1 - overlap)) all_dets [] for y in range(0, h, step): for x in range(0, w, step): tile img[y:y tile_size, x:x tile_size] # 补边到tile固定大小 tile_padded cv2.copyMakeBorder(tile, 0, tile_size - tile.shape[0], 0, tile_size - tile.shape[1], cv2.BORDER_CONSTANT, value(114, 114, 114)) results model(tile_padded, sizetile_size) for det in results.xyxy[0].cpu().numpy().tolist(): det[0] x det[1] y det[2] x det[3] y all_dets.append(det) return all_dets这段代码的缺陷是补边区域是纯灰色如果麦穗正好落在边界灰色区域可能产生一个错误的伪框。经验做法是重叠率不要低于10%太低会让截断的麦穗在两张子图里都产生半个框叠加到全局坐标后计数时NMS能合并但代价是损耗算力。切片推理的时间复杂度是线性增长的在边缘设备上要评估好耗时再决定tile大小。5. 麦穗识别避坑指南现象、原因、止损方案5.1 计数结果严重偏低尤其是麦穗密集区域现象模型在单穗或稀疏区域检测正常一遇到挤在一起的麦穗两个穗只检测出一个框。原因训练阶段NMS把IoU大于0.65的一对框合并掉了YoloV7的推理默认阈值不适合密集目标场景。解决把训练时后台的NMS阈值设低通常改到0.3同时在使用NMS后处理计数时也设低阈值。代码层面的做法是在推理时把torchvision.ops.nms的iou_threshold从默认值改到0.3。5.2 模型在验证集上mAP很高但实际田块上漏检严重现象训练日志里mAP0.5到了0.95以上拿到前几天新拍的图片上一测漏检非常多。原因训练集和验证集来自同一批数据或者说同一块田的同一时段图像背景高度相似换了地块、换了光照、换了土壤背景模型的泛化能力失效。解决做数据划分时按“地块”划分保证同一块田的图像不期望出现在训练集和测试集两边这才是接近真实场景的评估方式。此外做在线数据增强把hsv增强的强度调大让模型的颜色特征不那么敏感。5.3 训练loss正常下降但计数时总把叶子或杂草误检成麦穗现象检测结果里出现很多细长绿色框有的贴着麦秆有的在叶尖上。原因麦穗和叶子在颜色和纹理上有相似性而标注时漏标了叶子区域没有给模型提供“这不是麦穗”的反例模型训练时只有正样本没有被明确约束的错误区域。解决在训练集中加入一定数量不含麦穗的背景图并且多采用含有杂草、叶和穗混合的图。负样本不需要多占比10%~15%就极其有效能显著降低误检。同时在推理时把置信度阈值从0.25提到0.35~0.45减少低置信度误检。5.4 输入分辨率提高后训练显存溢出或速度下降到不可用现象从640提升到1280的--imgbatch-size还是8直接OOM强行把batch-size降到2训练一个epoch要很久。原因YoloV7在1280分辨率下特征图尺寸增大显存占用和计算量不是线性增长。解决先看显卡显存12GB以下建议只做切片训练12GB以上把batch-size设为4再尝试。另外可以在训练时开启--cache-images把图像预加载到内存减少IO瓶颈但内存不足时不建议。推理端做优化把模型转成ONNX并半精度推理速度能提升1.5倍以上。5.5 检测框数量比人工数的穗数少很多但可视化结果看着框没漏多少现象可视化图上麦穗都被框住了人工点数后对比检测结果却有差异。原因多穗相邻时一个框可能罩住了两个重叠麦穗视觉上你判断这个框只要包含至少一个完整麦穗就算“对”但这个框代表的是一个计数单位。解决框的松弛度要调整。让标注紧贴麦穗主干区域不要包含芒芒会拉大边框还会导致边框覆盖到旁边麦穗造成NMS合并。最简单的方法是标注时放大图片框只圈住麦粒簇主体不追求包含全部芒刺。6. 精度再往上走从计数误差到高置信度推断的进阶技巧6.1 用计数误差替代mAP来评估模型做麦穗识别系统业务方在乎的是最终穗数不是检测框的IoU。所以我建议在测试阶段不再只看mAP而是计算MAE平均绝对误差和计数误差百分比。做法很简单取测试集里每张图的真实穗数对比推理计数值算差值的绝对值取平均。如果MAE是5而每张图平均有300穗那误差率约1.7%这个指标才能真实反映系统对考种和估产的价值。训练时即便mAP到了0.92如果MAE偏高说明模型在密集区域漏检要在后处理阶段去优化。6.2 切片重叠区域去重优化前面提过大图切片推理但重叠区去重这块值得单独拎出来讲。如果重叠率设成15%同一穗子在重叠区域可能出现两次最终的计数中如果NMS阈值不够低穗子会被重复计数。解决的方法不止调低NMS阈值也可以在切块检测后做一次全局坐标的WBF加权框融合处理。WBF不是简单保留一个框它是把多个重叠框的坐标和置信度加权融合得到一个新框这对麦穗计数的稳定性帮助明显。WBF的权重分配关键是置信度高的框占主导但多个框的坐标差距不大时融合效果更好。6.3 用TTA和半精度推理做精度与速度的平衡如果推理环境对单张图片耗时没有要求可以打开TTA测试时增强YoloV7提供了--augment选项。它会把原图做翻转、缩放等一系列变换分别推理后融合结果。TTA对麦穗计数的精度提升约3%~5%但推理时间成倍增加。我通常只在最终统计样方数据时开启平时快速巡检不开。以下表格是同一模型在不同配置下的实测参考帮助你做取舍。推理配置单张耗时(ms)计数MAE适用场景640原图288.2快速巡检1280原图615.6常规计数1280 TTA1324.7最终考种校验从表里能看出分辨率比TTA带来更直接的收益。所以我的建议优先把输入分辨率提上去再考虑TTA。如果还觉得分数不够可以再加入基于检测框尺寸的过滤逻辑麦穗框的面积和长宽比通常在特定区间低于这个区间的框基本都是误检。实际的麦穗计数系统最终考验的不是你把mAP刷多高而是交付的穗数和人工数出来的是不是稳定接近同一个值。我在做这个方案时遇到过最无语的情况是算法报告里精度很高现场一测就穿帮后来排查发现是验证集划分太随意同一批图像流到了两边。从那以后我的习惯是做任何农业识别项目数据划分先按田块分组再留出独立的野外测试集。建议你做复现时也先把数据划分这一步写清楚否则后面所有精度数据都可能是自欺欺人。这套YoloV7麦穗计数方案的边界就在数据多样性和部署分辨率上把这些控制好它就是一个能真正下地跑的系统。希望帮到你。本文还有配套的精品资源点击获取
返回列表