简介:这是一份基于Python机器视觉的害虫种类识别与数量检测完整项目,适用于毕业设计、课程设计或图像识别方向的自学实践,能够直接回应农业场景中的害虫监测需求。项目完整覆盖图像预处理、特征提取、机器学习与深度学习建模、结果评估、数据可视化等关键环节,并包含环境配置说明与数据库相关文件,可帮助系统理解OpenCV、SIFT、SVM、CNN等技术在真实任务中的落地方案。压缩包内共163个文件,约14.58MB;主体包括97张jpg害虫图像样本、23个Python脚本、13个npy特征数据文件及3个训练好的model模型,另有xml标注、csv数据表、pdf说明文档等辅助资料,目录按数据集、脚本、模型等模块划分,查找起来非常方便。目前已有171人学习浏览,尤其适合计算机视觉、农业信息化方向的学生或研究人员作为综合项目参考。通过该项目可拿到可复现的检测脚本、预训练模型权重、图像数据集、评估与可视化代码及环境配置说明,既能快速跑通流程,也能基于现有结构改进算法,是系统巩固机器视觉技能的扎实素材。
1. 害虫种类及数量检测,Python 机器视觉到底能做成什么样
农业植保站和种植基地每年最头疼的活之一,就是数害虫。过去靠人蹲在田里数黄板上的蚜虫、诱虫灯下的蛾子,一个点几张板数完,头晕眼花还不准。这个标题给出的是一个毕业设计、课程设计级别的方案:用 Python 写一套程序,输入一张害虫图像或一段监控视频,自动告诉我图里有哪几种害虫、每种多少只。它的核心是把机器视觉里的目标检测模型应用在农业场景上,能解决计数效率低、人工成本高、数据不标准这三个实际问题。适合计算机、电子、自动化、农信等专业的学生拿来当课题,也适合想做农业物联网小项目的人做原型。你需要具备 Python 基础、会装库,最好用过 OpenCV,但不需要自己从零写神经网络——现在的主流做法是站在 YOLO 这类预训练模型肩膀上做微调。接下来我从选型讲起,再落到可运行的代码和训练参数,最后把常见翻车现场一次说清。
2. 从图像到计数:害虫检测的技术选型与原理
2.1 传统图像处理 vs 深度学习:为什么毕业设计优先选 YOLO
做害虫检测,第一道选择题是用传统机器视觉还是深度学习。传统路线比如 OpenCV 的颜色分割、轮廓提取、SIFT 特征匹配,在实验室摆拍环境里能跑通,但一到田间就露馅:黄板上有灰尘、叶片有阴影、害虫姿态千奇百怪,颜色阈值一调再调,换个光照全完。我见过不少课程设计一开始在 OpenCV 里折腾,最后都转战深度学习。
深度学习目标检测的主流方案有三条路:Faster R-CNN、SSD、YOLO。Faster R-CNN 精度高但速度慢,SSD 速度精度均衡,YOLO 在速度和精度之间最平衡,而且生态好、部署简单。毕业设计选 YOLO 几乎是共识,尤其是 YOLOv5 和 YOLOv8 这两个版本,社区教程最多,遇到报错冷门不冷门都能搜到答案。YOLO 把目标检测当成回归问题,一次前向传播直接输出所有目标的边界框和类别概率,不需要像 Faster R-CNN 那样先提候选区域再分类,所以能做到实时。
这个选择背后还有个现实原因:数据集。害虫检测没有像 COCO 那样大规模的开箱即用数据集,通常需要自己标注几百到几千张图。YOLO 格式的标注最简单,每个目标一行文本,五个数字,用 LabelImg 或 X-AnyLabeling 点几下就导出,这对学生来说是最容易上手的。而传统特征方法需要手动设计特征,泛化能力差,一套特征换一种害虫就失效,等于每个物种从头做一次。
2.2 害虫检测的模型结构:目标检测和分类计数的关系
既然选了 YOLO,就得搞清楚它到底输出什么、我们怎么从输出里得到“种类”和“数量”。YOLO 模型内部由 Backbone、Neck、Head 三段组成。Backbone 负责提取图像特征,比如暗斑、纹理、边缘,常用 CSPDarknet;Neck 是特征金字塔,把不同尺度的特征融合,让模型既能看清大蛾子也能看清小蚜虫;Head 负责最终预测,输出三个维度的信息:边界框坐标(x, y, w, h)、目标置信度、类别概率。
关键点来了:数量就是检测框的个数,种类就是每个框对应的类别标签。你不用自己写复杂的逻辑,模型推理后拿到的列表里,每一行代表一个目标,包含它的框坐标、置信度、类别 ID。你只需要按类别 ID 分组计数,再过滤掉置信度太低的框。但注意,同一只害虫如果被模型输出两个重叠框,直接数就会重复,所以后面要有非极大值抑制(NMS)去重——YOLO 的推理模块默认带了,你在写代码时要是用原生的推理接口,别忘了它已经把 NMS 做了。
从图像处理角度看,输入图像尺寸直接影响效果。YOLO 会把输入 resize 到固定尺寸,比如 640×640。如果原图里害虫很小,直接 resize 会丢失细节,一个常见的补救是把输入尺寸调到 960 或 1280,但这会增大显存占用和推理时间,训练时尤其明显。所以确定技术路线不是把模型跑通就算完,还要针对害虫尺度去调参数,后面第 4 章会细说。
2.3 数据集的构建:标注格式与标注工具
没有了数据集,模型就是黑匣子。害虫检测项目里,数据集构建往往占整个周期 60% 的时间,这也是很多人低估的部分。常见的数据来源是:自己用手机拍黄板或诱虫灯的照片、从农业论文里截取图像、或者用网络爬虫抓公开的昆虫图(注意版权)。整理出来的图片先做清洗,去掉重复、模糊、标注目标过小的图,否则后面训练全是噪声。
标注工具我推荐两个:LabelImg 是经典,单机可用,支持 YOLO 格式导出,缺点是标注框不能旋转;X-AnyLabeling 是后起之秀,支持自动标注辅助,先用一个预训练模型打底,你只修正错框,能节省一半时间。标注格式是 YOLO txt:每个文件对应一张图,每行内容为class_id x_center y_center width height,坐标是归一化到 0~1 的值。举个例子,一张 1280×960 的图上一只棉铃虫,框的左上角在 (320, 240),右下角在 (960, 720),那么 x_center = (320+960)/2/1280 = 0.5,y_center = (240+720)/2/960 = 0.5,width = (960-320)/1280 = 0.5,height = (720-240)/960 = 0.5。这个换算逻辑必须搞懂,不然训练时模型经常“找不到目标”。
类别 ID 的分配不能随意,要和自己的类别列表严格对应。比如你定了两类:0 代表蚜虫,1 代表粉虱。标注时就要看一眼 LabelImg 的 class 文件,别把粉虱标成 1 又写进训练配置里。一个血泪经验是:我见过同学的训练集里类别 ID 和配置文件对不上,模型训练了十个小时,验证时所有预测框全是错误类别,等于白练。
3. 用 Python 跑通最小检测系统:环境搭建与关键代码
3.1 环境准备:Python、OpenCV、PyTorch 与 YOLO 的安装
做这个项目,环境搭错能让你怀疑人生。Python 版本建议 3.8 到 3.11,不要用最新的 3.13,很多依赖还没跟上。YOLOv5 官方要求 Python >=3.7、PyTorch >=1.7;YOLOv8(即 ultralytics 包)要求 Python >=3.8。显卡没有的话,纯 CPU 也能跑推理,但训练会慢到怀疑人生,建议有 N 卡就用 GPU,没有就老老实实用 Google Colab。
安装顺序很重要:先装 PyTorch,再装 ultralytics。很多人一上来就pip install ultralytics,结果它把 torch 装成 CPU 版,后面想用 GPU 又要重装。正确的做法是先到 PyTorch 官网根据你的 CUDA 版本选安装命令。举个例子,CUDA 11.8 的安装命令是:
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118装完用 Python 验证一下 GPU 是否可用:
import torch print(torch.__version__) print(torch.cuda.is_available()) # True 表示 GPU 可用接着安装必要的库:
pip install ultralytics opencv-python matplotlib pandas逻辑说明:ultralytics 是 YOLOv8 的官方包,封装了训练、验证、推理全套;opencv-python 负责图像读取和视频处理;pandas 用来生成计数表格。参数说明:CUDA 版本要和显卡驱动匹配,如果驱动是新的,可以直接用 cu118;实在不确定,在终端输入nvidia-smi看右上角的 CUDA Version,比如显示 12.2,那可以装 cu118 或 cu121。
3.2 加载预训练模型进行害虫检测的核心代码
拿到环境后,先别急着训练,用预训练权重跑通推理更重要。这样你能快速看到整个流程长什么样,再做数据标注时心里有底。下面是我最常用的一段最小推理代码,基于 ultralytics YOLOv8:
from ultralytics import YOLO # 加载预训练权重,yolov8n.pt 是 nano 版本,速度最快,适合验证流程 model = YOLO("yolov8n.pt") # 推理一张害虫图像,conf 是置信度阈值,iou 是 NMS 的 IoU 阈值 results = model.predict( source="pest_image.jpg", conf=0.4, iou=0.5, save=True, show=False, imgsz=640 ) # results 是一个列表,每个元素对应一张图 r = results[0] print(r.boxes.cls) # 类别 ID 列表 print(r.boxes.conf) # 置信度列表 print(r.boxes.xyxy) # 边界框坐标,格式 [x1, y1, x2, y2] # 统计每个类别出现次数(预训练模型有80个COCO类,你需要映射成自己的类别) cls_count = {} for c in r.boxes.cls.tolist(): name = model.names[int(c)] cls_count[name] = cls_count.get(name, 0) + 1 print(cls_count)逻辑说明:这段代码把推理、NMS、保存画框图全部封装在 predict 里了。conf=0.4 表示只保留置信度高于 0.4 的框,太低容易把背景当害虫,太高会漏检;iou=0.5 是 NMS 合并重叠框的 IoU 阈值,两个框交并比大于 0.5 就合并成一个,避免重复计数。imgsz=640 是输入分辨率,如果你的害虫很小,后面要调大。参数说明:这段代码用的预训练模型是 COCO 80 类,里面并没有害虫类,所以你现在看到的结果肯定是“把人认成虫、把叶子认成狗”,这太正常了。它的价值是让你跑通流程,真正要识别害虫,必须用你标注的数据微调模型。
3.3 数量和种类的统计逻辑:过滤置信度、按类别分组
做课程设计时,老师要看到你“统计出了种类和数量”,不能只输出一堆框。统计逻辑要写成一个独立函数,方便嵌入图形界面或报告脚本里。下面是我的一套统计实现,处理的是模型输出的原始结果:
from collections import Counter def count_pests(results, class_names, conf_thresh=0.4): """ 从 YOLO 推理结果中统计各类害虫数量 results: 单张图的推理结果 class_names: 类别名列表,索引对应类别ID conf_thresh: 置信度阈值,低于它的框直接丢弃 """ if results.boxes is None: return {}, [] boxes = results.boxes # 先过滤低置信度框 conf = boxes.conf.cpu().numpy() keep = conf >= conf_thresh if keep.sum() == 0: return {}, [] cls_ids = boxes.cls.cpu().numpy()[keep] xyxy = boxes.xyxy.cpu().numpy()[keep] # 用 Counter 统计类别 ID 出现次数 count_dict = Counter(cls_ids.tolist()) # 把 ID 转为可读名称 readable = {class_names[int(k)]: v for k, v in count_dict.items()} return readable, xyxy逻辑说明:这个函数把置信度过滤从模型推理里单独拿出来,好处是调阈值不用重新跑模型。keep.sum()如果为 0,说明这张图里没有高于阈值的检测框,返回空字典和空坐标列表。参数说明:conf_thresh 在黄板图像上建议 0.4~0.5,背景复杂可以到 0.6,但调太高会丢失小目标;xyxy 坐标后续可用于在图上画框或者裁剪单只害虫做二次验证。
这段代码跑通以后,你就有了一套“输入图像 → 输出种类和数量”的最小闭环。下一步,你需要为你的具体害虫类别训练模型,否则输出永远是 COCO 的 80 类,没法交差。
4. 训练自己的害虫检测模型:数据集划分与训练参数
4.1 自定义数据集的目录结构与 YAML 配置
训练前要把数据集整理成 YOLO 要求的目录结构。我见过最省事也最不容易出错的布局是:
dataset/ ├── images/ │ ├── train/ # 训练图像 │ └── val/ # 验证图像 ├── labels/ │ ├── train/ # 训练图像对应的 txt 标注 │ └── val/ # 验证图像对应的 txt 标注 └── data.yaml有一点必须强调:images 和 labels 下的文件名要一一对应,比如images/train/pest_001.jpg对应labels/train/pest_001.txt,扩展名改成 .txt。大小写、后缀都不能差,否则训练时它会警告“找不到标注”,但不会报错,最后模型学了个寂寞。划分比例按 8:2 或 9:1 都行,验证集至少留 50 张,太少评估指标波动很大。
data.yaml 是训练入口的配置,内容如下:
path: dataset train: images/train val: images/val nc: 3 names: 0: aphid 1: whitefly 2: bollworm逻辑说明:path 是数据集根目录,train 和 val 是相对路径;nc 是类别数量;names 是类别名列表,必须和标注文件里的 class_id 一一对应。参数说明:如果你的类别有 5 种,nc 改成 5,names 下面写 5 行。这里最常见的坑是 names 从 0 开始,但写到 YAML 时漏掉某个 ID,或者列表项数和 nc 不一致,ultralytics 在加载时直接报错,还算好排查。
4.2 训练参数详解:epochs、batch_size、imgsz、学习率
训练命令行不能直接抄一遍就跑,四个参数必须理解:epochs、batch_size、imgsz、学习率。它们的取值范围和矛盾我在下面用表格说清。
| 参数 | 作用 | 建议值 | 说明 |
|---|---|---|---|
| epochs | 模型遍历整个训练集的次数 | 100~300 | 数据集小(几百张)可以 100,大样本 300;观察 val loss 不降就停 |
| batch_size | 每次迭代送入的图片张数 | 8~32 | GPU 显存不够就降;CPU 训练建议 4~8 |
| imgsz | 输入图像的缩放尺寸 | 640~1280 | 害虫小目标多就选大尺寸,训练和推理时间都会增加 |
| lr0 | 初始学习率 | 0.01 | SGD 优化器常用 0.01,Adam 可以调低到 0.001 |
实际训练命令用 ultralytics 的 CLI 或者 Python 接口都可以。我习惯用 Python 脚本,因为参数可以写在配置文件里方便复现:
from ultralytics import YOLO # 加载 YOLOv8 预训练权重,用 COCO 预训练做迁移学习 model = YOLO("yolov8n.pt") model.train( data="dataset/data.yaml", epochs=150, batch_size=16, imgsz=640, lr0=0.01, patience=20, # 验证集指标 20 轮不提升就早停 device=0, # 使用 GPU 0,CPU 训练改成 'cpu' workers=4, # 数据加载线程数 cache=True # 把数据集缓存进内存,加速小数据集训练 )逻辑说明:patience=20是早停机制,验证集 mAP 连续 20 轮不上升,训练自动停止,省时间;cache=True适合小数据集,把图像预加载进内存,减少 CPU 读取瓶颈,但数据集超过几千张时显存和内存压力会变大。参数说明:device=0表示第一块显卡,多卡环境用device=0,1,没有 GPU 就写device='cpu',但训练一个害虫小模型 150 轮可能要跑十几个小时,真不建议用 CPU 硬扛你的课程设计。
4.3 评估指标怎么看:mAP、Precision、Recall 与混淆矩阵
训练结束后,ultralytics 会在runs/detect/train/目录下生成一堆结果文件。老师问你“效果怎么样”时,你要能说出指标含义。首先是 mAP(mean Average Precision),它把 Precision-Recall 曲线下的面积按类别平均。mAP@0.5 表示 IoU 阈值 0.5 时的均值,mAP@0.5:0.95 表示 IoU 从 0.5 到 0.95 间隔 0.05 的平均,后者更严格。害虫检测里,因为目标小、遮挡多,mAP@0.5 达到 0.8 以上就算不错,mAP@0.5:0.95 能到 0.5 就很好了。
Precision 是“模型识别为害虫的框里有多少是对的”,Recall 是“真正的害虫里有多少被找出来了”。这两个指标此消彼长,如果只追求 Precision,你会漏掉大量小害虫;只追求 Recall,误报会很多。一个做农业监测的老师常问的是“漏检率”,对应的就是 Recall。你在调整 conf 阈值时,本质就是在 Precision 和 Recall 之间取舍。混淆矩阵图(confusion_matrix.png)更能直观看出哪两个类别容易混淆——比如蚜虫和粉虱体型相近,模型经常张冠李戴,这时就要检查标注是不是有大量错误,或者考虑增加这两个类别的样本数量。
5. 害虫检测落地避坑:5 个最常见的翻车现场与排查方法
5.1 现象:检测框乱跳、漏检严重,白天晚上效果天差地别
原因:光照变化导致图像亮度不均,机器视觉最怕这个。田间图像有强光直射、阴影、反光,模型在一种光照下训练,到了另一种光照就失灵。这不是模型玄学,而是输入分布偏移。
解决:先做曝光归一化,再进模型。常见做法是训练前统计训练集的平均亮度,推理时对测试图做亮度匹配;更简单的是直接在 OpenCV 里转灰度或做直方图均衡化,但对彩色特征有害。我一般在拍照采集时固定曝光参数,或者把原始图像用 OpenCV 的cv2.normalize调一下对比度。如果条件允许,在数据集里混入不同时间段、不同天气的样本,比任何图像增强都有效。记住:曝光调整不是只靠模型去学,采集端就要控制。
5.2 现象:对视频做检测时,同一只害虫被重复计数几十次
原因:视频是一帧一帧的图片,模型在每一帧都独立检测,同一只害虫停留在画面里,自然会被每帧都数一遍。
解决:引入帧差法或目标跟踪。最简单的方案是每隔 N 帧采样一次,比如每 30 帧取一帧检测,然后用 IOU 判断相邻采样帧里的框是否属于同一目标——如果两个框的中心点距离和 IoU 都很接近,就认为是同一只,合并计数。严格做法是用 ByteTrack 或 DeepSORT 给每个目标分配 ID,按 ID 去重。课程设计做到前一种就够。我写过一个函数,核心逻辑是保存当前帧所有框的中心点,下一帧检测框如果存在中心距小于 20 像素的旧框,就认为是同一个目标,不再加计数。这个“中心距离阈值”按图像分辨率调整,640×640 时 20~30 像素比较合适。
5.3 现象:训练时报错 CUDA out of memory,或者训练到一半进程被杀
原因:batch_size 太大或 imgsz 太大,显存爆了。很多人不调参数直接上batch_size=64,结果显存不够,报一个 PyTorch 的 OOM 错误。
解决:先减 batch_size,后减 imgsz。batch_size 从 16 减到 8,显存占用直接减半;imgsz 从 640 降到 512,显存变化不大但精度会掉。如果显存只有 4G(比如笔记本 1650),推荐batch_size=4, imgsz=640。还有一种办法是开启梯度累积,ultralytics 里没有直接参数,需要用 PyTorch 手改训练循环,对课程设计没必要。更重要的一点:训练前先跑一遍yolo detect train --data data.yaml --batch_size 4 --imgsz 640,确认能跑起来再加大参数。
5.4 现象:小害虫(比如蚜虫)检测不到,框总是偏大或漏检
原因:输入图像下采样后,小目标在特征图里只占几个像素甚至不到一个像素网格。YOLO 的检测头要负责不同尺度目标,但小目标天生难学。
解决:第一步把 imgsz 调到 960 或 1280,让目标在输入里占据更多像素。第二步在训练配置里打开多尺度训练,ultralytics 的 train 参数有个scale=0.5,表示每轮随机缩放到 0.5~1.5 倍,相当于变相增加小目标样本。第三步检查标注是否把小目标漏标了——很多标注工具默认显示小框不明显,容易标丢。如果这三步都做了还不行,就要考虑用 SAHI(切片推理工具)把大图切成小块分别检测再合并结果,这在害虫监测里算是进阶操作,但确实能救小目标的项目。
5.5 现象:训练完成后所有预测框类别都比实际类别错一格
原因:99% 是标注文件里的 class_id 和 data.yaml 里的 names 顺序对不上。比如你标注时定义了0: aphid, 1: whitefly,但 data.yaml 里写成了0: whitefly, 1: aphid,模型学的特征和你认为的类别名错位了。
解决:写一段脚本自动检查标注文件里的最大类别 ID 和 data.yaml 的 nc 是否匹配,再把 label 里每个 ID 的样本数打印出来,看看数量分布是否合理。下面这个排查脚本可以帮你快速发现问题:
import os from collections import Counter label_dir = "dataset/labels/train" cnt = Counter() for file in os.listdir(label_dir): if not file.endswith(".txt"): continue with open(os.path.join(label_dir, file), "r") as f: for line in f: class_id = int(line.strip().split()[0]) cnt[class_id] += 1 print(cnt) # 如果输出 Counter({3: 120, 4: 50}),但你的 names 只有 0,1,2,说明标注里混入了错误 ID # 如果 Counter({2: 500, 1: 300, 0: 200}),顺序越往后数量越少,也值得注意逻辑说明:脚本读取每个 txt 的第一列数字,统计每个类别 ID 出现的次数。参数说明:如果发现最大 ID 大于等于你的 nc 值,说明标注文件有问题,需要回去检查标注导出步骤,别急着训练。这是训练前最该跑的 30 秒检查。
6. 把检测结果做成报告:结果可视化与导出技巧
模型训练完,最后的交付不只是跑通代码,还要让答辩老师看到清晰的结果。我习惯做三件套:可视化画框图片、统计表格、单类别的检测样例。
画框图片可以直接用 ultralytics 自带的results.save(),但默认风格比较朴素。想要标出每只害虫的类别和数量,我一般这样写:
import cv2 # 假设 results 是推理结果,orig_img 是原始图像 for det in results.boxes: x1, y1, x2, y2 = map(int, det.xyxy[0]) cls_id = int(det.cls[0]) conf = float(det.conf[0]) label = f"{class_names[cls_id]} {conf:.2f}" cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)这里class_names就是从 data.yaml 里读出的列表。画框后把同一类的害虫裁剪出来,放在论文的“检测效果”图里,分别展示不同种类的小图,比一整张密集画框图更有说服力。统计表格做成 CSV 导出,直接用 pandas:
import pandas as pd rows = [] for image_name, count_dict in all_results.items(): row = {"image": image_name} row.update(count_dict) rows.append(row) df = pd.DataFrame(rows).fillna(0) df.to_csv("pest_count_report.csv", index=False)CSV 打开后每行是一张图,每列是一个害虫类别,值为数量。老师要看“系统效果”,你就拿 20 张测试图跑一遍,把人工计数结果和模型结果放到同一张表里,算出偏差率。指标上,准确率 = 模型正确识别并计数的个体数 / 人工计数的个体数,这个数字比单纯 mAP 更直观。我习惯在结尾写一段“局限性”:无法区分同一虫态的不同龄期、重叠严重的个体会漏计、夜间红外图像需要单独训练。这些不是减分项,反而证明你真的把系统完整地做下来了。
这套流程我在两个课程设计里带人跑通过:从标注到训练到可视化,两周内可以完成。核心是把每个环节的边界搞清楚——标注决定上限,参数决定能否收敛,光照决定泛化能力。最后记住一点:当你发现某项指标怎么调都上不去,先怀疑数据,而不是怀疑模型和框架。希望帮到你。
本文还有配套的精品资源,点击获取