ARTICLE DETAIL

资讯详情

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

YoloV7麦穗计数系统:从源码到部署的深度学习目标检测实战

YoloV7麦穗计数系统:从源码到部署的深度学习目标检测实战 简介这是一套基于YOLOv7算法的麦穗数量识别系统设计源码面向计算机视觉与智慧农业方向的研究者和开发者用于解决麦穗自动检测与计数难题可服务于农业产量监测等场景。包内共101个文件压缩包约48.4MB核心由38个Python脚本构成算法主体配合33个YAML配置文件管理训练参数与数据路径14个Jupyter Notebook提供交互式实验与验证平台另有XML标注、Shell脚本及Dockerfile等方便部署与自动化流程。目前已有285人浏览学习适合作为目标检测项目从训练到推理的完整参考尤其适合需要快速搭建麦穗识别系统的农业科研团队。通过研究源码结构与配置读者可掌握YOLOv7在细小目标识别中的调参思路、数据组织方式及模型导出部署技巧对开展田间作物识别实验或工程落地具有实用价值。1. 麦穗数量识别不该靠人蹲田里数YoloV7 源码系统把数穗变成“数框”农业科研里最难顶的活数麦穗一定排前三。估产、育种评价、表型统计全指着这一串数字可一根根数下来一个小区两小时中指麻了还容易数错。基于 YoloV7 算法的麦穗数量识别系统设计源码把麦穗计数做成一个深度学习目标检测任务用训练好的 YoloV7 输出每个麦穗的包围框再统计框数量得到穗数。整条链路覆盖数据构建、训练调参、推理部署走完你手里会有一套能复用的 Python 源码流程从标注图片到输出“这张图有 134 个麦穗”的结果。这套方案适合农学课题组、智慧农业方案商、作物表型算法工程师也适合想练手深度学习落地的开发者。前提是你会一点 Python手里有一块显存别太小的显卡。2. 麦穗检测不是通用目标检测的简单平移先想清楚三个问题再开项目很多拿到 YoloV7 源码的人习惯把这套深度学习算法当成通用工具直接套用结果大田照片一测全是漏检。问题不在 YoloV7 本身而在于麦穗任务同时踩中了目标检测里最麻烦的三个属性目标小、互相遮挡、背景杂乱。这章先把任务拆开定好模型选型和标签方案后面训练才不会白费力气。2.1 麦穗的“小、密、杂”三个硬门槛为什么通用配置必然翻车先看小目标。以一张 1920x1080 的大田照片为例一个正常发育的麦穗在图上往往只有 20x60 像素占比千分之几。YoloV7 的骨干网络会把输入图逐步压缩成 32 倍下采样的特征图如果输入是 640x640原本就细小的麦穗在下采样后只剩几个像素特征图上的响应趋近于零。不是模型不行是目标特征在卷积过程中被一层层磨没了。这也解释了为什么很多人用 COCO 预训练权重直接推理时输出几乎为空——麦穗的纹理、轮廓和 COCO 里的常见物体完全不是一个分布。第二个门槛是密集遮挡。抽穗期麦穗一簇一簇挤在一起两个紧挨着的麦穗框IoU 经常超过 0.5。检测后处理里的 NMS 会把重叠度高的框当作重复框合并置信度略低的那个真实麦穗就被顺手删掉了。这种情况不是随机漏检而是专挑密集区域漏对计数的打击是系统性的。第三个门槛是背景干扰。未成熟的小麦叶片和麦穗同为黄绿色杂草、土壤、秸秆在逆光下会产生各种形状的伪目标。通用目标检测模型的先验里没有这些概念光靠预训练权重根本扛不住。结论很清楚必须自建数据集重新训练并且训练策略要围绕这三个门槛调整通用配置直接拿来跑结果就是翻车。2.2 YoloV7 变体选型标准版、Tiny、W6 各自该在什么阶段用YoloV7 有多个变体麦穗项目里常用到的是标准版、Tiny 和 W6。三者用的是同一套网络结构思路但深度、宽度和输入尺寸设计不同对小目标的适应能力差别很大。选型不是越强越好而是匹配当前阶段的任务目标。变体推理速度小目标表现适合阶段我的一般用法标准版中等均衡第一版验证、数据论证默认首选先跑通再谈优化Tiny快偏弱边缘设备、实时视频流压测速度和显存时用W6慢更强高精度计数、大图分块最终定稿模型标准版有最多的社区踩坑记录遇到问题好搜适合第一次跑通流程。Tiny 速度快、显存占用低但密集小目标场景下精度掉得明显。如果一上来就用 Tiny后面翻车了你分不清是模型太小还是标注有问题。W6 对高分辨率输入的支持更好适合把 1280 甚至 1536 的输入喂进去但显存消耗几乎翻倍训练时间也拉长。我的习惯是先用标准版把流程走完确认数据和标注质量没问题再上 W6 做精度冲刺。中间如果只是需要快速验证某个想法Tiny 也能凑合但不要把 Tiny 的结果当成最终能力。2.3 标签策略单类麦穗加背景负样本比多类标注更省心数据集标签怎么定决定了后面训练的天花板。看到过一些论文把麦穗、麦芒、麦粒拆成多个类别分别标注学术上没问题落到工程上标注一致性很难保证。麦芒和麦穗的边界人眼都时常分不清不同标注员框出来的形状天差地别模型训练最怕的就是这种标签噪声损失函数会来回震荡。我一般会先用单类方案class 0 统一叫 wheat_ear框住麦穗主体。这样标注速度快类别间混淆也规避了。真正要花心思的是负样本专门采集一批完全没有麦穗、只有杂草、土壤、秸秆的大田背景图放进训练集和验证集告诉模型“这些不是麦穗”。负样本不参与最终计数但对压制误报的作用立竿见影。至于用点标注做密度图那是另一条技术路线。密度估计的优点是能处理极端遮挡缺点是不可解释。农学老师会追问“为什么这里算的是 134 而不是 130”检测框可以画出来给他看密度图很难沟通。这套源码系统走检测框方案是对的中途换道很容易两头不讨好。3. 数据是这套系统真正的瓶颈采图规范、标注转换与增强参数模型选型半天就能定数据要花两周。很多人一上来就急着跑训练结果数据集小、标注混乱、增强过度最后把账全算在算法头上。实际上麦穗识别这类垂直场景数据质量决定了模型精度的上限训练只是把数据里的信息榨干净而已。3.1 田间采图规范高度、角度、时段直接决定尺度一致性采图不是拿手机随手拍。同一个品种的麦穗在不同生长阶段形态不同但更致命的是拍摄尺度不一致。相机离麦穗 20 厘米拍出来和 50 厘米拍出来同样的麦穗在图上大小差一倍模型会困惑到底该学大麦穗还是小麦穗所以第一条规定是固定拍摄高度和角度。用支架把相机固定在冠层上方 30 到 50 厘米这个常见区间镜头尽量垂直于麦穗层不要斜着拍。斜拍会把麦穗投影拉长框的宽高比分布畸变NMS 的行为也跟着变。第二条规定是覆盖时段。抽穗期早期的麦穗还是绿色灌浆后期变黄特征差距很大。如果只在晴天中午拍模型在早晨逆光、多云阴影下会大量误检。我一般要求采图分早、中、晚三次每种光照下至少 50 张。第三是分辨率底线。原始图片分辨率不能低于 1080p并保留原图不压缩。后面如果要用 1280 输入训练小图放大后的锯齿会让小目标更模糊。大田环境风一吹麦穗就晃拍照时尽量选无风的窗口期画面虚化的图宁可删掉也不要留着标注时很难看出来训练时全是噪声。3.2 数据组织与格式转换LabelImg 导出 VOC 转 YOLO 的脚本数据目录按 YoloV7 的惯例组织images 放图片labels 放同名 txt。每个 txt 的每一行对应一个标注框格式是 class cx cy w h坐标全部归一化到 0 到 1。标注工具我常用 LabelImg导出 PascalVOC 格式的 XML再转成 YOLO 格式。转换脚本需要自己维护因为后面数据回流时还会反复用到。import os import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_dir, out_dir, classes): os.makedirs(out_dir, exist_okTrue) for xml_file in Path(xml_dir).rglob(*.xml): tree ET.parse(xml_file) # 解析单个 VOC XML 文件 root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): cls obj.find(name).text if cls not in classes: 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) cx ((x1 x2) / 2) / img_w # 中心点 x 归一化 cy ((y1 y2) / 2) / img_h # 中心点 y 归一化 w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{classes.index(cls)} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) out_path Path(out_dir) / (xml_file.stem .txt) out_path.write_text(\n.join(lines)) voc_to_yolo(dataset/xmls, dataset/labels/train, [wheat_ear])逻辑说明脚本读取 XML 里 size 字段的图片宽高再把 bndbox 的绝对坐标除以宽高得到归一化数值。输出文件与 XML 同名方便 YoloV7 按图片名自动匹配标签。转换完必须抽查几个 txt确认坐标都是 0 到 1 之间的浮点数行首数字是 0。参数说明第一参数是 XML 目录第二参数是输出目录第三参数是类别表。类别表顺序就是类别 ID必须和后面 data/wheat.yaml 里的 names 完全一致否则类别号会错位。这个脚本不处理中文路径目录和文件名都建议用英文。配套的数据配置文件长这样路径按你自己的目录改# YoloV7 训练时的数据配置 train: ./dataset/images/train val: ./dataset/images/val nc: 1 names: [wheat_ear]train 和 val 指向图片目录YoloV7 会自动到对应的 labels 目录下找同名 txt。路径别加引号别用中文目录名。val 集要和 train 集来自不同地块或不同时段否则验证结果虚高。这一点经常被忽略等真到了新地块上测试才发现模型泛化能力不行。3.3 数据增强参数Mosaic 和 MixUp 该开多少YoloV7 的训练超参文件里可以调整数据增强策略。麦穗场景增强不是越多越好开过头等于给模型强行加噪声。Mosaic 把四张图拼成一张能显著增加密集场景的复杂度建议保留 0.5。但 Mosaic 也会把麦穗切成碎片目标尺寸进一步变小对小目标反而不利。如果训练到后期 mAP 上不去可以降到 0.3 试试对比。MixUp 是两张图叠加混合麦穗这种纹理特征强的目标容易产生伪影不建议开大0.2 以下或者直接关掉。水平翻转 fliplr 可以开 0.5。上下翻转我一般关掉因为麦穗自然生长方向一致翻转后虽然目标检测理论上不敏感但会把天空、田埂这些背景混进训练样本徒增干扰。提示增强参数在 YoloV7 项目的 data/hyp.scratch.p6.yaml 里改。调参时不要只看单个数值要看它在验证集上的表现。增强开过头训练 loss 会降到很低但 val mAP 反而下降这是典型的过拟合信号。4. 训练与落地最小命令、日志判读与 ONNX/TensorRT 部署数据就绪后进入训练环节。这一段把命令、参数、日志和部署串起来。很多人训练只盯着终端里不断跳动的 loss却不知道哪些参数动了会引发连锁反应。先把最小命令跑通再学会看日志最后落到部署这套流程才算闭环。4.1 最小训练命令参数含义与显存不够的降级方案训练命令是整套源码流程里最常被改的地方。下面这条命令是我在麦穗项目里的起点参数值来自实际运行经验不是理论推荐python train.py \ --data data/wheat.yaml \ --cfg cfg/training/yolov7.yaml \ --weights pretrained/yolov7.pt \ --batch-size 8 \ --epochs 150 \ --img-size 1280 1280 \ --device 0--data 指向刚才写的 wheat.yaml--cfg 是网络结构配置--weights 是预训练权重路径。强烈建议使用 yolov7.pt 而不是随机初始化。预训练权重已经在大规模通用数据上学会了边缘、颜色、纹理的底层特征麦穗识别要学的只是“这些特征怎么组合成麦穗”训练会快很多收敛也更稳。--batch-size 8在消费级 12GB 显存显卡上配合 1280 输入比较紧张。如果 OOM优先降到 4而不是立刻把 --img-size 降到 640。降分辨率对小目标的影响是结构性的我在避坑章节会详细说。--epochs 150 对几百张图的起步数据集够用数据量到几千张时150 轮训练时间会明显拉长可以先跑 50 轮看 loss 趋势再决定是否继续。--img-size 1280 是关键参数。麦穗小输入图越大下采样后保留的特征越多。代价是显存和训练时间翻倍。显存不够又不想降分辨率时常见做法有两个一是把大图切块分别推理二是用 half 精度训练。两条路的细节后面都会涉及。4.2 训练日志怎么读mAP 指标在密集计数场景的取舍训练开始后runs/train/exp 目录下会持续输出日志和结果图。我读日志只看四样东西train loss、val loss、mAP0.5、recall。train loss 持续下降、val loss 进入平台甚至回升说明过拟合开始出现训练轮次可以收手。两个 loss 都在降但 mAP0.5 停滞问题多半出在标注噪声或增强太猛。指标选择上密集计数场景把 mAP0.5 当主指标。mAP0.5:0.95 对不同 IoU 阈值求平均麦穗这种天然框不准的目标在这个指标上会非常难看因为 IoU 到 0.75 以上时人的标注误差本身就比阈值要求还大。硬追这个指标只会逼着标注员把框画得越来越紧对最终计数没什么帮助。更值得盯的是 recall 而不是 precision。计数任务里漏检是硬伤一穗漏掉后面统计就少一穗这与分类任务“宁漏勿错”的取向完全相反。如果 precision 和 recall 只能保一个先保 recall再用推理阈值去过滤误检。这个取舍我在实际项目里交过学费属于血泪经验。训练完挑权重时别只看 best.pt 是不是 mAP 最高要结合 recall 曲线一起判断。4.3 从 PyTorch 到现场导出 ONNX、TensorRT 部署与最简推理脚本训练收敛后把训练目录里的 best.pt 导出。YoloV7 项目自带 export.py常见导出命令如下python export.py \ --weights runs/train/exp/weights/best.pt \ --img-size 1280 1280 \ --batch-size 1导出前确认本机的 torch 和 onnx 版本兼容导出完成后用 onnxruntime 跑同一张图对比输出框数量是否和 PyTorch 推理一致。这一步能过滤掉大部分算子兼容问题。如果要做实时视频流计数或者部署到 Jetson 这类边缘设备再把 ONNX 转成 TensorRT engine。TensorRT 对算子版本极其敏感转换报错时先查 CUDA、cuDNN、TensorRT 三个版本是否和源码编译时一致比改代码更管用。调试阶段最省事的推理脚本长这样import torch model torch.hub.load(ultralytics/yolov7, custom, pathruns/train/exp/weights/best.pt) results model(field.jpg, size1280) boxes results.xyxy[0].numpy() # 行结构: x1, y1, x2, y2, conf, class_id count int((boxes[:, 5] 0).sum()) print(f麦穗数量: {count})逻辑说明torch.hub.load 把模型和预处理逻辑封装好适合在开发环境快速验证。推理时传入的 size 必须和训练尺寸保持一致否则框坐标比例会错。统计时只取 class_id 为 0 的框也就是 wheat_ear 类得到的就是单张图的麦穗数量。这段脚本不能直接上生产。torch.hub 加载每次都要走 Python 解释器速度慢还依赖模型文件的存放路径。现场设备推荐换成导出的 ONNX 或 TensorRT推理速度快一个量级部署环境也干净。如果只是离线统计照片CPU 跑 ONNX 完全够用不必为了快而盲目上 TensorRT。5. 麦穗识别避坑指南五个最容易翻车的坑及对症处理做麦穗计数的坑很多和通用目标检测不一样。通用场景里不显眼的问题放到大田密集场景会被放大成灾难。这里列五个我实际踩过的坑每个都按现象、原因、解决三段写照着排查能省下大量试错时间。5.1 大图上一穗都检不到小目标被下采样磨没了现象验证集上 mAP 还看得过去但把一张完整的大田照片丢进模型输出基本为空或者只有零星几框。这是麦穗项目最常见的初版翻车现场。原因推理时把整张 1920x1080 的图直接缩到 640 输入。麦穗原本只有几十像素缩到 640 后只剩几个像素经过骨干网络多次下采样特征图上基本没有响应。模型不是没学到麦穗是输入分辨率根本喂不进去有效特征。解决训练和推理都改用 1280 输入。如果显存不够把大图切成 1280x1280 的 patchpatch 之间保持 10% 到 20% 重叠分别推理后再汇总框重叠区域用 IoU 去重。分块推理是小目标检测里最稳妥的方案代价是推理时间变长但大田计数本来就是离线任务时间换精度很划算。5.2 密集麦穗框叠成一坨数量比人工数少 30%现象单株麦穗检测正常一到簇生区域两个相邻麦穗只出一个框。计数结果系统性偏低不是随机偏差。原因检测后处理 NMS 的 IoU 阈值默认在 0.45 左右。两个麦穗的框重叠超过 0.45 时置信度较低的那个被当作重复框删除。麦穗天然簇生这个重叠非常常见默认阈值在这里明显不合理。解决推理时把 IoU 阈值下调到 0.3让 NMS 更宽容。阈值不能无脑降降到 0.2 会出现同一个麦穗被框两次的情况计数反而偏高。而且这个值在不同地块不一样麦穗密度不同最佳阈值也不同。我的做法是抽三到五张小图分别在 0.25、0.3、0.35 三档跑一遍对比人工计数后定档。5.3 杂草叶子被当成麦穗precision 很难看现象模型把叶片、秸秆、田垄上的杂物都框成麦穗误报数量比真麦穗还多现场结果没法向农学老师交代。原因训练集里正样本多、负样本少。模型只见过“什么是麦穗”没见过足够丰富的“什么不是麦穗”背景里的绿色物体就容易被当成目标。解决分两步处理。第一步数据侧采集一批完全没有麦穗的田间图放进训练集重点覆盖杂草、土壤、枯叶。第二步推理侧把置信度阈值从默认的 0.25 提到 0.4 以上。误报框的置信度通常偏低一次过滤能去掉大半代价是少量真麦穗也被过滤这时要靠上一章说的 recall 思维权衡。5.4 训练中期 loss 突然崩掉出现 NAN现象训练正常跑了一二十轮某个 epoch 开始 loss 变成 NAN后续权重全部报废只能从头再来。原因标注框里有极小的框。有些麦穗被叶子挡住大半标注时只框了一小块宽或高只有 1 到 2 像素。数据增强里的随机缩放和旋转很容易把这种像素级框变成 0 宽 0 高回归损失计算时除零训练就崩了。解决训练前写个脚本扫描所有标签 txt把宽或高小于 5 像素的框过滤掉。注意标签是归一化坐标要先乘以原图分辨率再判断。这个坑典型属于“看起来很努力其实死在标注上”不扫一遍数据就开训练等于把定时炸弹埋在自己脚下。5.5 显存不够来回 OOM训练没法继续现象batch-size 开得很高img-size 又是 1280训练跑一会儿显存溢出进程被杀。反复调了几次还是不稳定。原因显存开销和输入面积、batch 大小近似线性增长。麦穗场景偏偏两个都想要冲突不可避免。如果只有一块 8GB 或 12GB 的显卡不能既要又要。解决按顺序降。先降 batch-size 到 4再开 half 精度训练显存占用能省不少。最后才考虑降 img-size 到 960。如果降到 960 还不够就该用分块推理的思路去解决而不是继续压低训练尺寸。训练尺寸一旦降到 640前面说的小目标漏检问题会全部回来到时候省下的显存全变成返工的时间。提示避坑的核心不是靠记忆是把每个坑变成一条检查命令或一个数据检查脚本每次开新项目先跑一遍。这套流程跑熟了新项目的数据质量检查半天就能完成。6. 从框到最终穗数计数统计、现场验证与数据回流习惯检测框出来最后一步是把框变成农学老师认可的“穗数”。计数统计有两种常见做法一是直接统计单张图的框总数二是对视频流做逐帧计数后靠跟踪算法去重。如果项目只是大田抽样拍照第一种就够将来要做实时监测那需要引入跟踪器是另一套复杂度先不展开。验证阶段建议做一次人机对比抽 20 张有代表性的图让有经验的人逐张数穗再让模型数算误差率。误差在 5% 以内可以交付初版超过 10%回到避坑章节逐条排查。这个对比要保留记录把每一轮结果存成 csv方便追踪模型迭代效果。抽样时注意覆盖不同的光照时段和密度区域只抽好看的照片验证没有意义。import csv from pathlib import Path def count_wheat(model, img_path, conf_thres0.4): results model(img_path) boxes results.xyxy[0].numpy() # 只统计麦穗类class_id 为 0 return int((boxes[:, 5] 0).sum()) def export_counts(model, image_dir, out_csv): rows [] for img_path in sorted(Path(image_dir).glob(*.jpg)): n count_wheat(model, img_path) print(f{img_path.stem}: {n}) rows.append([img_path.stem, n]) with open(out_csv, w, newline) as f: writer csv.writer(f) writer.writerow([sample_id, wheat_count]) writer.writerows(rows)逻辑说明count_wheat 接收模型和图片路径返回麦穗框数量export_counts 遍历目录下所有 jpg把结果写入 csv同时打印到终端供现场核对。每个样本的 filename 就是 sample_id后续要回溯某个地块的数据时靠这个字段关联原始图片。参数说明conf_thres 这里取 0.4 是我在麦穗场景里的经验值不同地块负样本比例不同这个值不能照抄。如果你发现某块地误报多就在 0.3、0.4、0.5 之间各跑一遍取人机误差最小的那档。在大田实测中这个阈值搜索比调训练参数更立竿见影。数据回流是一套源码系统能不能长期用的分水岭。麦穗从抽穗到成熟一直在变模型一旦上线就停更一个月后精度必然下滑。我现在做完一版不会急着收工而是把当轮推理的误检、漏检图整理进一个专门的目录每两周补标一次、增量训练一轮。别小看这个习惯大田数据和实验室数据最大的区别就是它每天都在变模型必须跟着数据一起长。希望帮到你。本文还有配套的精品资源点击获取
返回列表