资讯动态

基于YOLO的诱虫板害虫检测与计数:从数据标注到报表输出

发布时间:2026/10/6 8:22:49 来源:尧图企业网站定制
简介这是一份面向农业植保场景的机器视觉毕业设计项目针对害虫远程识别与数量统计需求给出从图像采集、虫体计数到种类分类的完整实现方案适合计算机视觉、数据分析方向的高校学生和农业信息化开发者参考。压缩包共165个文件约14.6MB涵盖97张jpg昆虫样本图像、23个py源码、13个npy模型数据、xml标注及训练生成的csv、model等文件代码与数据集配套便于直接运行和二次开发。已有241人学习下载。内容包含昆虫矩形度、延长度、圆形度、球状性、叶状性等5类形态特征提取并对比逻辑斯蒂回归、线性SVM和K邻近分类器效果同时实现了野外图像采集装置的远程上传、粘板图像轮廓计数与虫类鉴别服务器识别流程可完成虫害爆发趋势预测的前期数据积累。对需要完整项目骨架、可复现代码和分类实验对比的毕业设计者而言是一份清晰可用的参考资料。1. 这个毕设题到底在做什么一张诱虫板照片换一份虫情报表农业植保站每年夏天最头疼的事是让技术人员蹲在田里数害虫。一块黄色诱虫板上密密麻麻粘着几十上百头小虫子要分清楚是哪几种、各有多少头两个人用一个放大镜数一小时眼睛就花得不行。这个毕设标题就是在解决这件事用机器视觉替代人工点数输入一张田间照片输出每种害虫的类别和数量。它本质上是目标检测加计数任务的组合对新手友好的点在于不用自己造轮子公开数据集和开源检测框架都能直接用。适合想做图像方向毕设、又希望结果能落地汇报的学生也适合农业信息化方向的从业者拿来做原型验证。2. 技术选型检测范式、模型骨架和评估指标的取舍2.1 先别急着选模型想清楚你要的是“检测”还是“分类”很多人拿到这个标题的第一反应是“用卷积神经网络做分类”。但这种直觉是错的分类模型只能告诉你图里有没有某种害虫回答不了“每种有几头”。你要的是每个虫子的位置和类别然后按位置去数数量这是目标检测任务的定义。图像分类、目标检测、语义分割这三条路我见过不少毕业生在这上面反复横跳最后拖到答辩前才换方向。如果选分类把整张诱虫板照片扔给 ResNet模型只会输出“这张图里有稻飞虱、二化螟、蚜虫”但数量完全拿不到。如果选分割确实能把虫和背景像素分开但逐像素标注成本高到可怕而且密集成虫在分割时互相粘连后处理做分离的难度比检测还大。检测框反而是最合适的表达一个框对应一个独立目标框的数量就是头数逻辑直接。所以这个题目的技术路线基本锁定在“目标检测 计数后处理”。先让模型把每只虫用矩形框标出来再统计每个类别的框数量最后结合诱虫板面积换算成虫口密度。这套流程也是植保行业做虫情监测的通用做法不是毕设特有的花架子。2.2 YOLO 系还是 Faster R-CNN机器视觉学习路线上的常见选择选检测模型时大家讨论最多的是 YOLO 系和 Faster R-CNN。机器视觉v社区里吵得最凶的话题常年是这两个框架谁更强但落到这个毕设场景答案其实很明显优先选 YOLO 系。Faster R-CNN 在两阶段模型里精度确实稳尤其在小目标上表现比早期的 YOLO 好但它的训练和推理速度慢部署时还要处理 Region Proposal 这一套依赖对只有一张显卡、还要赶论文的本科生不太友好。YOLO 系从 v5 到 v8 再到 v11已经把训练、验证、导出、部署串成了一条命令而且 nano 版本模型文件只有几 MB后期不管是做 Web 演示还是放到 Jetson 上跑都留了退路。按机器视觉学习路线看先跑通 YOLO 的检测流程再回头理解 Faster R-CNN 的两阶段思想比一上来啃 ROI 和 Anchor 机制要顺得多。如果你担心小目标漏检YOLO 系也可以通过调高输入分辨率、切片推理来补不一定非要换两阶段模型。我一般会给学生一个判断标准如果你的害虫尺度普遍大于 32×32 像素YOLO 完全够用如果很多虫子只有十几个像素优先考虑 SAHI 切片推理而不是换模型。2.3 评估指标别让 mAP 掩盖数量统计的硬伤毕设答辩时老师最常问的不是“mAP 是多少”而是“你说能数虫子那数得准不准”。这是评估指标选择的第一个坑。mAP 衡量的是检测框和真值框的匹配程度它把分类和定位混在一起算对数量任务并不敏感。你可能得到 0.85 的 mAP但计数偏差仍然可能超过 30%。问题出在匹配策略上。标准 mAP 计算要求预测框和真值框的 IoU 超过某个阈值才算命中比如 0.5。对密集的小目标一个预测框可能同时压住两个真值框另一个被当成忽略点数也就是在匹配阶段直接被丢弃。你说模型“漏检了”机器视觉忽略点数规则却告诉你看不到漏检因为那个目标从未被匹配过。所以评估时不能只看 mAP还要单独统计每个类别的召回率、精确率和计数误差率。计数误差率的计算公式很简单绝对值偏差除以人工点数。比如人工数出 50 头稻飞虱模型数出 43 头误差率是 14%。我建议你在实验记录里同时放 mAP 和计数误差率两张表后者更能回应“你这个系统到底能不能用”的质疑。2.4 整体流程从数据到报表的四段式结构这套系统的完整链路可以拆成四段数据采集与标注、模型训练、检测计数、报表输出。数据段决定上限模型段决定逼近程度计数后处理决定最终数字可信度报表段决定答辩时能不能讲清楚。四段工作量大致是 3:3:3:1不要因为模型训练看起来最“技术”就把时间全砸进去数据不干净后面全白搭。3. 数据集与标注从“看得见虫”到“认得出虫”3.1 公开数据集不够用时按这个思路补采集公开数据集方面IP102 是一个常见的大规模昆虫数据集类别覆盖很广但它多为自然场景下的高清特写和诱虫板上的密集、粘连、反光场景差异很大。用它做预训练可以直接拿来微调会水土不服。更靠谱的做法是拍一批自己学校周边或合作植保站的真实诱虫板照片数量不用太多每个类别至少 200 个标注实例就能把模型拉回正轨。采集时注意三点一是固定拍摄角度和距离否则后期推理时目标尺度漂移模型会突然失效二是同一块板分早晨、中午、傍晚拍三张让模型见过不同光照下的虫子反光三是不要只挑虫子稀疏的板拍必须包含一块板上几十头虫的密集样本否则计数任务会在密集场景翻车。标注工具选 LabelImg 或 X-AnyLabeling 都行导出格式建议直接选 YOLO 格式一个 txt 对应一张图每行是“类别id x_center y_center w h”。但如果多人协作标注大家习惯用的工具导出的可能是 VOC 格式的 XML这时就需要一个转换脚本兜底。3.2 从 VOC XML 到 YOLO TXT转换脚本与四个边界坑VOC 格式把标注信息存在 XML 里坐标是像素绝对值YOLO 需要的是归一化后的中心点坐标和宽高。转换本身不复杂但我在帮学生处理数据时见过太多转换后训练直接报错的案例。下面这个脚本可以处理单个 XMLimport xml.etree.ElementTree as ET def voc_to_yolo(xml_path, txt_path, class_list): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_list: continue # 跳过不在类别清单里的目标 cls_id class_list.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) # 边界裁剪防止标注出图范围导致归一化值小于 0 或大于 1 x1 max(0, min(x1, img_w)) x2 max(0, min(x2, img_w)) y1 max(0, min(y1, img_h)) y2 max(0, min(y2, img_h)) # 过滤掉退化框宽或高小于 1 像素就直接丢 if x2 - x1 1 or y2 - y1 1: continue x_center ((x1 x2) / 2) / img_w y_center ((y1 y2) / 2) / img_h box_w (x2 - x1) / img_w box_h (y2 - y1) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) with open(txt_path, w, encodingutf-8) as f: f.write(\n.join(lines))逻辑说明脚本先把 XML 里的像素坐标读出来除以图片宽高得到归一化中心点和宽高。这里的关键是做了两次保护坐标越界裁剪和宽高小于 1 像素的过滤。YOLO 训练时如果读到负数坐标或零宽高的框轻则报警告重则 loss 直接变成 nan血泪经验。实际跑批处理的时候还有四个边界坑。第一XML 里的 object 标签可能嵌套不同标注工具会写成 object 或 item用 root.iter(object) 能兼容大部分情况但你要先检查实际标签名。第二类别顺序必须固定class_list 一旦定下来就不能中途改否则所有 txt 都要重生成。第三图片尺寸在标注前确认过不代表训练时一致转换脚本里的 img_w 和 img_h 必须取 XML 里 size 节点的值不能从文件名读否则训练和标注对不上。第四名称里有中文时txt 文件用 utf-8 写没问题但后续读取时统一用 utf-8不然 Windows 上训练会乱码。3.3 数据增强让模型见过“看不清”的害虫害虫检测的数据增强和其他视觉任务不太一样。常规的随机裁剪可能把一只小虫裁掉一半反而制造错误标签旋转角度太大也会让诱虫板上的虫子分布变得不像真实场景。我用得比较稳的是 YOLO 内建的轻量增强训练时通过参数控制强度一组常见配置如下hsv_h: 0.015 hsv_s: 0.7 hsv_v: 0.4 degrees: 10.0 fliplr: 0.5 mosaic: 1.0参数说明hsv_h、hsv_s、hsv_v 是色调、饱和度和亮度的扰动幅度害虫体色是识别的重要特征饱和度扰动可以模拟不同光照但色调扰动不能太大否则绿色虫被偏成蓝色反而把特征学歪了。degrees 是旋转范围10 度够模拟摆放角度差异。fliplr 是水平翻转概率对对称的昆虫形态没问题。mosaic 是拼图增强能在一张图里塞多个场景但对微小目标不一定有利如果你发现小目标漏检严重把 mosaic 调到 0.3 甚至 0 试试。除了内建增强我还会做离线补充用高斯模糊模拟失焦用随机亮度模拟早晚光线再保留一部分完全不增强的原图。增强的比例控制在 1:2即一份原图配两份增强图太多增强会让模型过拟合增强噪声反而在真实照片上退化。4. 训练与数量统计从检测框到“一亩地多少头”4.1 最小可复现的训练命令数据准备好之后用一个开源检测框架把基线跑起来是最快建立信心的方法。这里以 ultralytics 库为例训练命令可以收敛成一条yolo detect train \ data./dataset/pests.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ project./runs \ namepest_exp逻辑说明data 指向数据集配置文件model 用 yolov8n.pt 作为预训练权重epochs 设 100 轮imgsz 是输入分辨率batch 按显存调整。project 和 name 控制训练日志输出目录方便同时跑多组对照实验。data 对应的 pests.yaml 也要自己写格式如下path: ./dataset train: train/images val: val/images nc: 3 names: 0: rice_planthopper 1: striped_stem_borer 2: aphid说明path 是数据集根目录train 和 val 是相对路径。nc 是类别数names 是类别名顺序必须和标注 txt 里的类别 id 一致。这里的类别名建议用英文小写避免后续导出部署时某些工具链对中文路径支持不好。4.2 四个必调参数imgsz、epochs、batch 和置信度训练参数里有四个直接决定计数效果值得单独列出来逐个调。第一个是 imgsz。默认 640 对大多数目标够用但诱虫板照片里的害虫普遍偏小把 imgsz 提到 960 或 1280 往往能提升小目标召回。代价是训练时间成倍上涨显存也吃紧。我的经验是先从 640 跑一版基线如果 val 集小目标召回率低于 60%再考虑提分辨率。第二个是 epochs。100 轮是起步不是终点。观察训练日志里的验证集 loss如果到第 80 轮还在下降果断把 epochs 加到 200。提前用早停反而容易错过后半程的收敛点。第三个是 batch。batch 越大训练越稳定但显存爆了只能往回降。batch 从 16 降到 8 通常不会让精度掉太多但梯度噪声会变大学习率也要相应调低。第四个是推理置信度。默认 conf0.25 会输出大量低分框直接用于计数会让数量虚高。做计数时建议把 conf 提到 0.4再用 NMS 的 iou 阈值 0.7 排除重叠框。这个参数不是训练参数但它是计数后处理里最该调的一个。4.3 数量统计脚本按类别计数并生成 CSV模型推理结果里包含每个框的类别 id 和置信度。统计数量时不能简单数所有框要先过滤低置信度框再按类别累加。下面这段代码可以直接接在预测结果后面from collections import Counter def count_by_class(detections, class_names, min_conf0.4): detections: 模型返回的检测结果列表 每个元素是单张图片的 Boxes 对象 counter Counter() for boxes in detections: cls_ids boxes.cls.cpu().numpy() confs boxes.conf.cpu().numpy() for cls_id, conf in zip(cls_ids, confs): if float(conf) min_conf: counter[int(cls_id)] 1 return {class_names[i]: counter[i] for i in sorted(counter)}逻辑说明遍历每张图的检测框读取类别 id 和置信度过滤掉低于 min_conf 的框剩下的按类别计数。Counter 累加每个类别的数量最后用字典输出键是类别名值是对应头数。拿到头数之后如果还需要虫口密度要结合拍摄面积换算。常见做法是在数据集配置里记录每张诱虫板的物理尺寸然后用“害虫数量 / 板面积”得到头/平方米。密度指标对植保报告更有说服力单纯给“300 头”缺乏对照价值。4.4 为什么不要直接相信模型输出的“数量”代码跑出来的数字再漂亮也只能算“模型计数”不是“真实数量”。诱虫板上的虫子存在互相遮挡、肢体残缺、同色背景融合等问题模型计数和人工计数一定存在偏差。所以在做结论之前务必留出至少 50 张未参与训练的照片人工点数后和模型数做对比算误差率。这一步既是验证也是答辩时的底气。5. 避坑排查五个让虫情检测翻车的真实细节5.1 小目标漏检诱虫板上的成虫只有十几个像素现象模型对单独一只大虫识别得很准但遇到半块板都是密密麻麻的小虫时输出框少得可怜计数直接腰斩。原因网络下采样后小目标特征几乎丢失加上标注框太小正样本在训练时贡献微薄。解决优先把 imgsz 提到 1280 再训如果显存撑不住就用 SAHI 把原图切块每块独立推理再合并。我见过不少项目靠切片这一个操作就把小目标召回率拉高了 20 个百分点。5.2 同一只虫被数成两三头现象人工数是 40 头模型数出 60 头偏差巨大。原因同一只虫被多个重叠框命中NMS 没有完全抑制掉或者推理置信度设得太低把背景误检也当成虫。解决先看置信度直方图把预测时的 conf 从 0.25 逐步提到 0.4如果还有重复框把 NMS 的 iou 阈值从 0.7 调到 0.5强制压缩重叠框。这组参数可以在 val 集上多试几个值别凭感觉定。5.3 同一类害虫的不同发育阶段被拆成两类现象幼虫、蛹、成虫形态差异大模型把同一物种分成两个类导致计数报表里数据很怪。原因这类问题大多不是模型问题而是标注阶段没有统一标准。解决在标注规范里明确“一个物种不同发育阶段合并为一类”并给标注员看对照图如果训练时已经分错了把两类合并回一个类别重新训练一次。这个坑在答辩时很容易被老师点出来提前处理好会加分。5.4 验证集和训练集“串场”指标虚高现象验证集 mAP 高达 0.92模型一到新拍的照片上就崩溃。原因很多同学按文件随机划分数据集同一个诱虫板的连续帧或同一张照片的裁剪块被同时分到训练集和验证集相当于模型提前见过答案。解决按拍摄时间或诱虫板编号划分保证同一个板的照片全部进训练集或全部进验证集不允许跨集合出现。这是机器视觉方向最容易忽略的数据泄露场景。5.5 训练没问题一部署就变慢现象PC 上推理 30 帧每秒换到树莓派或 Jetson 上变成 2 秒一张图。原因没做模型精简输入分辨率又高边缘设备的算力扛不住。解决先换 YOLOv8n 这类轻量模型再尝试导出成 TensorRT 引擎前提是你部署设备是 NVIDIA 芯片。如果设备连 TensorRT 都不支持只能降输入分辨率到 640 或更小同时接受一定的精度损失。调参有时候是玄学但部署性能基本是算术题算清楚算力就够不用硬撑。6. 进阶把模型输出变成一份能写入植保报告的虫情报表模型训练好只是第一步毕设的最终产出应该是一份能直接放进植保报告里的虫情报表。我的习惯是写一个批量推理脚本对一批诱虫板照片执行检测、计数、密度换算最终生成 CSV再让前端或 Excel 模板渲染成图表。这一步工作量不大但能显著提升项目的完整度。import csv from collections import Counter def generate_report(image_paths, results, class_names, density_factor): rows [] for img_path, detections in zip(image_paths, results): counts count_by_class(detections, class_names, min_conf0.4) row {image: img_path} for name in class_names: row[name] counts.get(name, 0) row[total_pests] sum(counts.values()) row[density_per_m2] row[total_pests] * density_factor rows.append(row) with open(pest_report.csv, w, newline, encodingutf-8) as f: writer csv.DictWriter(f, fieldnamesrows[0].keys()) writer.writeheader() writer.writerows(rows)逻辑说明这段脚本把每张图片的计数结果和密度换算结果整合成一行最终写进 CSV。density_factor 是每张图对应的面积换算系数由实际拍摄面积决定。如果后续要做趋势分析把照片按日期排序后再汇总周平均虫量就能看出虫情变化曲线。验证时不要只看整体误差率要分类别看。有些类别的误差可能稳定在 5% 以内另一些经常上下 30%说明后者的小目标或遮挡问题还没解决。把误差率最高的那个类别单独拎出来重新检查标注和增强策略比整体调参更有效。这也回应了机器视觉方向常说的“数据决定上限”这句话。最后一件事是留存测试过程。我吃过亏训练了三天只记住最终模型没记下每组超参数对应的日志老师问“为什么从 640 升到 960 之后指标变了”答不上来。后来每次实验都用独立目录保存配置文件、YAML 和 val 日志复盘时直接对比。这个习惯让我在后续项目里少走了很多弯路也希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑