☰
昆虫识别与数目统计实战:YOLOv8目标检测与计数去重全流程解析
2026/10/5 3:58:10 网站建设 项目流程

简介:一份围绕大四毕业设计展开的昆虫识别与数目统计完整项目方案,面向计算机视觉、深度学习方向的高年级本科生,可直接用于课程设计或大作业参考。压缩包共164个文件,大小14.59MB,内容涵盖97张昆虫jpg图像、23个Python脚本、13个npy数据文件、10个xml标注,并附带csv数据表、训练好的模型文件、UI界面和PDF说明等,既有原始数据也有处理脚本与成果文件,目录结构清晰,可按照数据处理、模型训练到界面展示的流程逐模块学习与复用。已有140人学习下载。借助该资源,读者能够理解昆虫检测与数目统计的核心思路,掌握图像标注数据组织、npy特征读取、模型调用与界面交互等具体实现,并可直接运行自带模型观察效果。对于准备毕业设计或课程答辩的同学,它提供了一个可对照练习和二次开发的基础框架,便于在此基础上扩展算法或界面功能。

1. 昆虫识别和数目统计:这个毕设题目真正在磨什么

大四毕设拿到“昆虫识别和数目统计”这个 .zip 的时候,大部分人的第一反应是:“不就是训练一个目标检测模型吗?”真做起来会发现,识别只是地基,数目统计才是磨人的那部分——叶片背面挤成一团的小虫、视频里同一只虫每帧都被数一次、网上扒的数据集解压出来路径全是乱码。这个题本质是一条完整的工程链路:数据标注、模型训练、推理部署、计数去重、误差评估,每一环都能消耗掉你比预期多一倍的时间。这篇笔记按实际做过的方案,把选型、参数、踩坑和验收技巧一次讲透,适合正在写代码、准备答辩、或者想拿这套做法做农林业虫情监测的人照着走。

2. 方案选型:检测框架与计数策略怎么配对才不返工

题目里的“识别”和“数目统计”是两个技术环节,很多人一开始把题目理解成“训练一个分类模型”,这是返工的第一大原因。分类模型只能告诉你图像里有没有某种虫,不能告诉你在哪、有几只。数目统计必须建立在检测结果上,先有边界框,再把框的数量聚合成语义上的“只数”。所以动手前先想清楚:你的题目是数单张图片里的虫,还是数一段视频里出现的虫,这直接决定后续选型。

2.1 先分清识别、检测和计数三件事

识别(classification)输出的是类别标签,比如“这是蚜虫”;检测(detection)输出的是类别加位置框,比如“这里有 3 只蚜虫,各自在图里的坐标和大小”;计数(counting)则是对检测框集合做统计或跨帧去重。毕设题目里“昆虫识别”四个字容易让人直接联想分类网络,但后半段“数目统计”已经锁定了方案——必须选目标检测。

有人会问:用密度图估计(heatmap regression)行不行?比如人头计数的 CSRNet 思路。对于昆虫场景,我一般不建议:昆虫个头小、颜色和背景接近、聚集严重,密度图方案在小目标场景误差比检测框方案更大,而且没法指出每一只的位置。检测框方案对答辩最友好,因为可以可视化,错了也能查到是哪一帧哪一帧出了偏差。所以主线就定为目标检测,计数作为检测结果的后处理。

2.2 为什么 YOLO 系是毕设性价比最高的检测框架

目标检测框架常见的有 YOLO 系、Faster R-CNN、SSD、DETR 等。毕设场景的时间预算、算力预算和答辩可视化需求决定了不能只挑 mAP 最高的,要挑“综合折腾成本最低”的。下面这张表是我做选型判断时的常用口径:

框架推理速度单卡可训性小目标表现部署难度资料与社区
YOLOv8快很好,n/s/m 都跑得动中上,需配合高分辨率输入低,自带 export 到 ONNX很丰富
YOLOv5快很好中上,版本老但稳定低资料最全
Faster R-CNN慢可训,但显存占用高中上中,需手工转推理脚本中
SSD中等可训偏差,小目标容易丢中偏少

结论很清楚:主推 YOLOv8,备选 YOLOv5。理由很现实——Ultralytics 的生态开箱即用,pip 装完就能训,自带预训练权重,验证集可视化、模型导出都顺手。导师也认这套。Faster R-CNN 适合做对比实验,不建议主训,因为同样的数据量下它训练更慢,部署时还要写额外的预处理和后处理代码。

一个常犯的错是同时训 YOLOv8、YOLOv5、Faster R-CNN 三个模型做对比,最后发现时间根本不够。除非导师明确要求“检测框架对比”作为论文创新点,否则我建议只训一个模型,把省下来的时间全部投入到数据质量和计数去重上——那才是本题目真正的区分度。硬件方面,如果只有 CPU 笔记本,优先选 YOLOv8n 或 yolov8s,训练用小分辨率,推理导出 ONNX 后还能跑得动。

2.3 计数策略:静态图、抽帧、跟踪去重三种方案怎么选

检测框有了,计数逻辑怎么设计?三种主流做法,对应不同的题目口径:

计数方案粒度精度工作量主要翻车点
A:每帧独立计数帧静态图准极低视频中同一只虫被重复计数
B:抽帧间隔计数片段中等低抽帧间隔难定,虫快时漏计,慢时重计
C:跟踪去重计数片段最高较高ID Switch 导致计数漂移,依赖检测质量

如果你的题目是“给一张诱虫板照片,数出上面有几只”,方案 A 就够。这是最简单也最稳的口径,直接对检测结果做 NMS 统计。如果你拿到的是摄像头拍摄的林地视频,方案 A 一定会翻车——同一只虫在连续 30 帧里出现,逐帧求和会把数量放大 20 倍以上。方案 B 抽帧,比如每 30 帧取一帧统计,思路简单,但虫子爬动速度不均匀,误差不稳定。方案 C 用 ByteTrack 或 DeepSORT 对检测框做跨帧 ID 关联,把同一只虫的轨迹合并,最接近真实数量,但要接受 ID Switch 带来的误差。

选型可以按这个顺序走:先看题目要求的是“单张图像内数量”还是“一段视频内数量”;然后定义一个明确的计数口径,写进论文;再决定要不要引入跟踪器;最后设计独立的验证集来测计数误差,而不是拿 mAP 当计数精度。区域限定和网格计数也值得留一手:只统计指定区域(比如黄板范围内)能减少边缘误检;把大图切成小网格分别计数,对密集场景比整体 NMS 稳定。

3. 数据准备:从公开数据集到能直接训的 YOLO 格式

数据是昆虫识别项目里最吃时间的部分。很多同学拿到的 .zip 里自带一份数据集,但这份数据是什么格式、有没有边界框、类别和你题目是否匹配,都要先验证。更常见的情况是数据集至少存在以下问题之一:只有分类标注、图片和标签文件名对不上、类别体系过于宽泛、样本不均衡到训练直接崩。

3.1 公开数据集能用但没那么好用:分类标注和检测框的差距

公开数据集里,IP102 是常被提到的昆虫/害虫分类数据集,类别覆盖面广,但注意它的标注是分类标签,没有边界框。拿它来做预训练可以参考,直接喂给 YOLO 不行,除非自己补标。AI Challenger 有农作物病虫害检测数据集,带框,但类别集中在作物病害,和“昆虫识别”不完全重合。Open Images 里也有昆虫相关类别,标签是框,但类别很粗,筛选工作量不小。

我见过最稳的做法是:从图库平台(比如 iNaturalist 类目页)按昆虫类别筛选,筛出 8 到 12 个常见类,每类收集 300 到 500 张图,自己用标注工具打框。体量控制在“毕业设计可完成”范围内——2000 到 5000 张图,每张一到几十个框,标注两到三周能做完。如果题目允许,优先选形态差异大的类,比如蚜虫、蝴蝶、甲虫、蝗虫、天牛,这样识别难度适中,答辩时可视化也直观。标注工具用 LabelImg 或 X-AnyLabeling,后者支持半自动分割辅助,密集场景能省不少力。

3.2 整理目录与标签:VOC XML 转 YOLO txt 的转换脚本

YOLO 训练要求的数据结构是 images 和 labels 两个目录,每张图片对应一个 .txt,每行格式为class cx cy w h,坐标都是相对值。拿到手的数据往往是 VOC 格式(.xml)或 COCO 格式(.json),需要转换。下面这个脚本是常用的 VOC XML 转 YOLO txt 版本,标准库实现,不用装额外包。

import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(voc_dir, target_dir, class_map): voc_dir, target_dir = Path(voc_dir), Path(target_dir) (target_dir / 'labels').mkdir(parents=True, exist_ok=True) for xml_file in voc_dir.glob('*.xml'): tree = ET.parse(xml_file) root = tree.getroot() width = int(root.findtext('size/width')) height = int(root.findtext('size/height')) lines = [] for obj in root.findall('object'): name = obj.findtext('name') if name not in class_map: continue # 跳过未注册类别,避免类别名不一致 box = obj.find('bndbox') xmin = float(box.findtext('xmin')); ymin = float(box.findtext('ymin')) xmax = float(box.findtext('xmax')); ymax = float(box.findtext('ymax')) if xmax <= xmin or ymax <= ymin: continue # 过滤标注软件导出的无效框 dw, dh = 1.0 / width, 1.0 / height cx = (xmin + xmax) / 2.0 * dw cy = (ymin + ymax) / 2.0 * dh w = (xmax - xmin) * dw h = (ymax - ymin) * dh lines.append(f"{class_map[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") if lines: out_name = xml_file.with_suffix('.txt').name (target_dir / 'labels' / out_name).write_text('\n'.join(lines), encoding='utf-8')

逻辑说明:脚本遍历 voc_dir 下所有 XML,解析图片宽高和每个 object 的 bndbox,把绝对坐标归一化到 0~1 后写入 txt。这里有两个容易被忽略的处理:一是坐标倒置检查,二是未注册类别的跳过。标注软件导出的框偶尔出现 xmax < xmin,不处理的话训练时会越界。顺便说一句,如果遇到 XML 里嵌套标签结构不一致,先打印一下根标签结构再改字段路径,不要盲改。

参数说明:voc_dir 是存放 XML 的目录,target_dir 是输出目录,class_map 是类别名到索引的字典,例如{'aphid': 0, 'butterfly': 1},必须和后面 data.yaml 里的 names 顺序一致。转换完建议立刻做一次统计检查:数一下每个类别的框数量,确认没有 class_id 为 0 的样本被漏掉或错置。这一步花五分钟,能避免训练时“看起来跑了,但 loss 不降”的玄学问题。

3.3 小虫子的增强策略:多尺度、切图与类别不均衡

昆虫目标小、密集、背景复杂,增强策略和普通目标检测不一样。第一,Mosaic 增强默认开启,对常规目标效果好,但小虫场景要谨慎——Mosaic 把四张图缩在一起,小虫进一步缩小,特征几乎消失。我一般会在训练的后 10 轮关闭 Mosaic,让模型在真实尺度下微调。Ultralytics 训练参数里可配close_mosaic=10,就是这个用途。

第二,翻转增强可利用昆虫姿态的对称性:上下翻转基本不影响语义,可以开;但 90 度旋转对某些细长昆虫(比如螳螂)会产生不自然形态,建议关掉或只做 180 度翻转。颜色扰动 HSV 参数可以设得稍激进一些,农林业虫情监测常遇到的强光、阴影、黄板反光都需要模型扛。

增强项推荐参数适用场景注意点
Mosaic默认开,最后 10 轮关闭通用小目标缩严重
HSV 扰动hsv_h 0.015, hsv_s 0.4, hsv_v 0.4应对光照变化过大会改变真实颜色
水平/垂直翻转50% 概率昆虫姿态对称90 度旋转谨慎
复制粘贴增强对少数类目标粘贴到其他图类别不均衡不要遮挡原有目标
Tile 切图原图 1080 以上时切成 640/1280小目标明显需要推理时同步切图

类别不均衡是昆虫数据集的老问题。常见虫种数量可能差一个数量级。与其调损失函数权重,不如先把少数类做数据层面补齐:对少数类样本做复制粘贴增强,或者干脆去图库多找一些该类的图片。损失函数权重是最后手段,而且超参数不好调。在实践里,把少数类补齐到类的 30% 以上,比任何 fancy loss 都管用。做完增强后,把增强结果可视化一版,确认没有把标签框截半或越界,再进训练。

4. 模型训练:关键参数与评估指标怎么盯才不白跑

数据准备好了,训练反而是可复制性最高的一步。最常见的翻车不是网络结构选错,而是配置参数没对齐:data.yaml 路径写错、batch 设置过大导致显存溢出、imgsz 和数据集实际分辨率不匹配、没有人告诉你怎么看训练曲线。这些都可以在训练启动前检查清楚,避免浪费几十个小时。

4.1 训练前的三样东西:data.yaml、预训练权重和显存预算

训练前先写 data.yaml,这是 YOLO 训练的数据配置入口。一个典型的配置长这样:

path: /home/user/insect_project # 数据集根目录 train: images/train # 训练图片相对路径 val: images/val # 验证图片相对路径 names: 0: aphid 1: butterfly 2: beetle 3: locust

path 字段写绝对路径,这是排在第一位需要确认的事。很多 loss 不降的案例最后都出在这——YOLO 在训练启动时不会报路径错误,而是加载到了空数据集。names 里的类别顺序必须和转换脚本里的 class_map 一致,否则标签全错位还很难排查。train 和 val 的目录划分不要用随机文件打乱,要按采集来源或拍摄时间段划分,否则同一处场景的相似图片同时出现在训练集和验证集,验证指标虚高,答辩时一换真实数据就露馅。

预训练权重选择上,先用 yolov8s.pt 起步。s 级别对小目标检测的性价比最高,n 偏轻量,m 开始显存占用上升明显。l 和 x 不建议在昆虫数据集上直接用——除非你有 24G 显存且数据量足够大,否则大模型在几百张图的数据集上很容易过拟合。训练命令如下:

yolo detect train \ data=data.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=1280 \ batch=8 \ device=0 \ patience=15 \ close_mosaic=10

参数说明:imgsz 设为 1280 对小目标友好,但显存占用接近翻倍,如果显卡只有 8G,先用 imgsz=640 跑基线,再逐步调高。batch 按显存上限设,8G 显存配 imgsz=640 时可以设 16,配 1280 时降到 4 或 8。patience=15 表示验证指标连续 15 轮不上升就早停,不要硬拉满 100 轮。close_mosaic=10 是最后 10 轮关闭 Mosaic,让模型在真实图像尺度上收尾。如果你用的 YOLO 版本较老,先确认 close_mosaic 参数存在,不存在的话把 10 改成 0。

4.2 训练曲线怎么盯:loss、mAP 和早停的取舍

训练启动后,不要只看 loss 曲线。YOLO 训练过程有三条 loss:box_loss(框回归)、cls_loss(分类)、dfl_loss(分布焦点损失)。三条都应该逐步下降。如果训练 loss 下降但验证 loss 回升,说明过拟合,早停会帮你自动止损。另一个关键点是区分 train 和 val 的指标:如果 val 的 box_loss 一直抖,但 mAP 还在上升,可以继续跑;如果 mAP50-95 连续 15 轮不动,早停触发,不要觉得“没收敛”就反复重启。

评估指标里,mAP50 是答辩时最常用的口径,但小目标场景更要看 mAP50-95,后者对框的 IoU 要求更严格,能反映出定位精度。Precision 和 Recall 是两条此消彼长的曲线,训练日志里会同时打印。不要只报“mAP 90%”,要能回答导师“这个指标是在哪个 IoU 下算的、验证集有多少张图、和训练集是如何划分的”。训练结束后看 Results 目录下的 confusion_matrix.png 和 val_batch_pred.jpg,这些可视化比数字更能帮你判断模型到底学会了什么。

讲一个实操习惯:训练日志里 val 指标是黑匣子吗?不是,但很多同学从不打开 val 图片看预测结果。我会把 val 预测图逐张翻一遍,尤其关注“把叶片纹理误检为虫”“两只虫叠在一起只画了一个框”这两类错误。它们不会显著拉低 mAP,但会直接摧毁计数精度。

4.3 推理阈值和计数精度的联动:conf 与 iou 怎么调

训练完只是第一步。同一模型在推理时,conf 和 iou 两个参数会直接影响计数结果,这是训练阶段最容易忽略的联动关系。默认 conf=0.25 是通用目标检测的预设值,昆虫小目标置信度普遍偏低,0.25 会漏掉大量真目标。我用昆虫模型时通常把 conf 调低到 0.15 左右;反之,如果背景复杂导致误检多,则要往上升。

yolo predict model=best.pt source=test_video.mp4 conf=0.15 iou=0.5

iou 控制 NMS 合并重叠框的阈值。默认 0.5 意味着两个框的重叠度超过 50% 就会被合并成一个。密集场景下,同一只虫有时被拆成两个框,这时适当降低 iou 到 0.45 有助于合并;而两只虫贴得很近时,iou 过高又会错误地把它们合并成一个框。这套参数每个数据集都不一样,不能照搬别人的配置文件。血泪经验是:mAP 高不代表数得准,计数阶段一定要单独做阈值验证,用代表性子集调 conf 和 iou,调到人工复核误差最小为止。

提示:调阈值前先做 50 到 100 张图的预测可视化,统计漏检和误检分别主要是哪种场景。如果是密集贴一起的虫漏检,优先调 iou;如果是远处小虫漏检,优先降 conf,配合更高的 imgsz。

5. 避坑记录:从标注到计数的五个翻车现场

这一章是把这个题目从头到尾过一遍时最常踩的五类坑,每一条都是真实场景里会反复出现的。数据、训练、计数、部署、压缩包,每条线都有自己的坑。

5.1 标注翻车:框把同一条虫一分为二

现象:训练出的模型在密集虫簇上要么把一个框横跨两只虫,要么把一只虫拆成两个框,计数忽高忽低,复现不出稳定结果。
原因:标注时框贴合度不一致。有人标得紧,有人标得松,密集区域两个框高度重叠,NMS 合并逻辑在不同置信度下时好时坏。真正深层原因是标签边界太碎,模型不知道该按哪个粒度回归。
解决:标注时把图像放大到 200% 再打框,框必须完整包住虫体,不把触角或腿当主体。密集簇中每只虫的框要贴合轮廓,宁可小一点不要跨虫。训练后逐张看 val 预测图,发现系统性拆分或合并,回查对应标注样本。用半自动分割标注工具辅助,比手工框更稳。

5.2 训练翻车:loss 不降的玄学,先查数据再调参

现象:训练跑了 20 轮,box_loss 纹丝不动,mAP 全零。
原因:八成不是网络结构问题,而是数据问题。常见三种:data.yaml 的 path 路径错误导致加载空数据集;label 文件名与图片名不对齐,模型读到的全是空标签;类别 id 映射错位,增强后标签框全部越界被过滤。去调学习率是浪费时间。
解决:先跑单 batch 训练并可视化,确认图像上真的画出了标签框。检查 labels 目录下 txt 文件数量和 images 目录对应图片数量是否一致,用脚本找出没有标注的图片。打印 data.yaml 的路径确认存在。以上排完再看学习率,默认 lr0=0.01 对大多数情况够用,需要调时从 0.005 起步即可。

5.3 计数翻车:视频里同一条虫被数了三次

现象:视频推理忠实输出每帧的框,一帧 8 只,10 秒视频共 300 帧,输出“总数 2400 只”。导师问“这段视频里到底有几只”,答不上来。
原因:把“帧内计数”当成了“片段计数”,没有做跨帧 ID 关联。同一只虫在每帧里都被计了一次。
解决:明确题目的计数口径。如果是视频计数,引入 ByteTrack 或 DeepSORT 进行跨帧关联,按 track ID 去重后统计。如果只是静态图计数,在论文和演示文稿里写明“计数对象为单张图像内昆虫”,并用单张图像验证集评估。抽帧间隔方案可以缓解重复计数,但治标不治本。

提示:视频中同一只虫静止不动时,跟踪器也可能因检测短暂丢失而 ID Switch,计数会偏大。建议对视频先做检测再平滑轨迹,或按轨迹长度过滤——只出现一帧的“目标”大概率是误检。

5.4 部署翻车:CPU 推理慢到演示现场卡住

现象:答辩演示时,用 yolo 源码跑 1080P 视频,一帧要两秒,现场等得尴尬。
原因:选了 YOLOv8x 大模型 且直接用 PyTorch 推理,没有任何部署优化。CPU 上大模型的卷积计算量完全跑不动。
解决:导出 ONNX 并量化。yolo export model=best.pt format=onnx int8=True可以显著压缩体积;推理端用 onnxruntime 或 OpenVINO,速度提升近一个数量级。模型尺寸降级到 yolov8s 甚至 yolov8n 是更根本的解法。视频分辨率先降到 720P,推理 imgsz 保持 1280 的话,切块推理会比整图推理稳定。这个操作放到答辩前一周做,留时间重新验证精度。

5.5 数据翻车:zip 解压报错与路径乱码

现象:从网上下载的昆虫数据集压缩包解压时报 invalid zip archive: could not find eocd,或者解压后图片显示正常但标签文件全是乱码、标注对不上。
原因:EOCD 报错说明压缩包没下载完或文件本身损坏;乱码是因为压缩包内文件名编码不是 UTF-8,中文 Windows 下解压软件用 GBK 解码失败。
解决:先校验压缩包大小和下载页声明是否一致,不完整就重新下载。换用 7-Zip 打开,解压时强制 UTF-8 编码,然后在仓库本地立即把目录重命名为纯英文路径。旧的 zip 如果有密码保护,不要用来路不明的“移除密码”工具,直接把内容复制到新目录后用 7-Zip 重新打包成无密码的新包。后续所有代码里路径统一用相对路径或 pathlib,避免在字符串里拼中文路径。

6. 计数结果验证:抽帧复核算误差率的演示脚本

训练和计数逻辑都完成后,最后一个技巧是“计数精度的独立验证”。只给 mAP 数字,导师没法直观感受到“数得准”;给一段视频、抽帧抽样表和一个误差率,五分钟就能说明白。

6.1 抽帧复核与误差率脚本

常见的做法是把一段测试视频按固定间隔抽帧,自动模型数一遍,人工数一遍,然后对比误差率:

import cv2 from ultralytics import YOLO model = YOLO("best.pt") cap = cv2.VideoCapture("test_video.mp4") fps = cap.get(cv2.CAP_PROP_FPS) interval = max(1, int(fps * 2)) # 每 2 秒抽一帧 records, frame_id = [], 0 while True: ret, frame = cap.read() if not ret: break if frame_id % interval == 0: r = model(frame, conf=0.15, iou=0.5, verbose=False)[0] auto_count = int(r.boxes.shape[0]) review_image = r.plot() # 带预测框可视化的帧 cv2.imwrite(f"review/frame_{frame_id}.jpg", review_image) manual_count = int(input(f"第{frame_id}帧自动{auto_count}只,人工复核数量:")) records.append((frame_id, auto_count, manual_count)) frame_id += 1 cap.release() errors = [abs(a - m) / m for _, a, m in records if m > 0] print(f"平均误差率: {sum(errors) / len(errors):.2%}")

逻辑说明:脚本按 2 秒间隔抽帧,对每一帧做推理并保存可视化图。人工复核时可以对着图片数,也可以快速跳过模糊帧。误差率是abs(自动数 - 人工数) / 人工数,把所有抽样帧平均后得到模型在计数口径上的精度。这个数字才是答辩时该展示的指标——mAP 再高,计数误差率也能暴露真实问题。

参数说明:interval按视频帧率动态生成,fps 为 30 时每 60 帧抽一帧;fps 未知或无头视频,建议写死 30 左右。conf=0.15 和 iou=0.5 沿用推理时调好的参数。抽样帧数控制在 30 到 60 帧即可,覆盖视频开头、中间、结尾,人工数一小时以内完成。

我做完这个题目后最深的教训是:早一半时间把计数验证脚本写到项目里,就不会在答辩前一周发现模型在真实视频上误差率高达 40%。mAP 高和数得准是两回事,这个道理放在工作里也成立。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询