基于YOLOv8的昆虫识别与数目统计毕业设计全流程实战
2026/9/14 18:19:55 网站建设 项目流程

简介:这是一套面向高校大四学生及课程设计、毕业设计场景的昆虫识别与数目统计项目资源。项目以计算机视觉技术为核心,从数据准备、模型训练到界面展示形成完整闭环,适合需要快速搭建毕设原型或进行相关课题研究的开发者参考。压缩包共164个文件,约14.59MB,其中包含97张昆虫图像样本、23个Python脚本、13个npy数据文件、10个xml标注文件、9张png图片、3个训练好的model模型、3个csv表格数据、1个ui界面文件及说明文档等,目录结构较为完整,可支撑从数据读取、模型推理到结果统计的整套流程。该资源已有140人学习下载,具有一定的参考热度。读者可从中获得可运行的代码框架、已标注的数据集与模型文件,以及界面设计思路,能够帮助理解目标检测与计数任务的实际实现方式,减少重复开发成本。

1. 昆虫识别与数目统计的毕业设计题,实质是两件事

把“昆虫识别和数目统计”丢给做过目标检测的人,第一反应是“这不就是YOLO出框、数框嘛”。真做起来会发现,识别是识别,统计是统计,中间隔着一条把检测框变成业务数字的鸿沟。识别要解决昆虫类间相似、类内变态期差异、小目标占比高这些视觉问题;数目统计要解决的则是同一只虫在视频里出现多帧、群聚重叠、目标遮挡之后的去重与计数策略。这个题目作为大四毕业设计,常见做法是“检测模型 + 计数后处理”两条线并行:用目标检测框架训一个能区分若干昆虫类别的模型,再在输出层之外单独设计数目统计算法。本文按这个思路拆开讲,覆盖选型、数据、训练、计数实现和答辩部署,给出一套能直接落地的技术方案。

2. 技术选型:昆虫识别用哪类检测算法,YOLOv8 为什么是底线

2.1 检测模型对比:两阶段、单阶段与端到端方案的取舍

昆虫识别属于典型的目标检测任务,候选方案无非三类:以 Faster R-CNN 为代表的两阶段检测器,以 YOLO 系列为代表的单阶段检测器,以及 RT-DETR、DINO 这类基于 Transformer 的端到端检测器。两阶段检测器精度上限高,但推理速度慢,训练时需要单独管理 RPN 和检测头,对课设和毕设的周期来说性价比低。RT-DETR 省去了锚框和 NMS,在公开数据集上表现亮眼,但收敛依赖大数据量,如果昆虫数据集只有几千张,训练不稳定是常态。YOLO 系列在精度和速度之间最均衡,v8 版本把 anchor-free 检测头、C2f 结构和多种数据增强策略集成在 Ultralytics 框架里,一个配置文件加几行命令就能完成训练、验证和导出。对于需要现场演示和答辩跑 demo 的场景,训练快、推理快、生态成熟这三点比极致精度更重要。

我一般会建议在 YOLOv8n 或 YOLOv8s 之间选。昆虫数据集通常不大,几百到两三千张,v8n 参数量小,不容易过拟合,CPU 上也能勉强推理;v8s 精度更好,适合有一张普通显卡的环境。如果后续想做性能对比实验,可以再跑一版 v8l 或 RT-DETR 作为对照组,写论文时多一张消融表格。但底线是 YOLOv8 及其配置管理方式,不要从零实现检测网络,毕业设计的时间经不起这样消耗。

2.2 环境搭建与预训练权重的作用

环境搭建用 Ultralytics 官方包即可,Python 3.9 以上版本都兼容。核心安装命令如下:

pip install ultralytics onnxruntime

安装完成后先做一次最小验证,用预训练权重跑通推理链路:

yolo predict model=yolov8n.pt source=https://ultralytics.com/images/bus.jpg

这一步的用途不是训练,而是确认 GPU 或 CPU 推理环境可用,同时验证 CUDA 版本与 PyTorch 是否匹配。如果predict能正常输出标注图片,说明环境没问题,可以进入数据阶段。这里的yolov8n.pt是 COCO 预训练权重,迁移学习的意义在于 COCO 上的卷积特征对昆虫的纹理、边缘、颜色有泛化迁移能力,尤其是背景类和昆虫形态差异较大的情况下,预训练权重能显著缩短收敛时间。需要注意,COCO 中没有太多细粒度昆虫类别,不能指望预训练直接给出好的昆虫特征,它只是提供一个初始化起点,最终结果仍然依赖专门的数据。

2.3 分类粒度怎么定:物种级还是形态级

昆虫识别的类别定义是数据阶段的决策,但必须在选型时同步想清楚。常见做法有两种:按物种分类,例如“蝶、蛾、蜂、蝇”,每组内部仍存在大量近似外观;按生态功能或拍摄场景分类,例如“传粉昆虫、害虫、中性昆虫”。从模型角度,类别越细,类间距离越小,误检率越高;从统计角度,类别定义直接影响后续数目统计的维度——是统计总虫数还是分种类统计。毕设题目里“昆虫识别”没有限定粒度,我会把类别数量控制在 5 到 15 类,每类样本 150 张以上。类太少体现不出识别难度,类太多数据采集工作量会压垮项目进度。这个决策要在论文里给出理由,答辩时能解释“为什么选这 8 类而不是按科属全部展开”本身就是加分项。

3. 昆虫数据集的采集、清洗与标注:别急着跑训练

3.1 样本来源与类别不均衡处理

昆虫没有现成的大规模公开数据集可以直接对齐到自定义类别,常见做法是自己拍摄、从开放图库采集、或借助网络爬虫获取图像。无论来源如何,第一步是盘点类别分布。我一般会先把所有图像文件按类别放到独立文件夹,写一个统计脚本,输出每类图片张数,优先淘汰样本不足的类别。若某类样本特别少,可以用水平翻转、旋转、亮度扰动做离线增强,但不会单纯复制粘贴图片充数——那种做法只会增加过拟合风险。

类别不均衡是昆虫数据里的常态。比如“苍蝇”样本容易采集,“寄生蜂”样本可能很难拍满 200 张。处理方法上,训练时开启 Ultralytics 的fraction参数控制每类参与训练的比例,或者在数据配置里对少样本类别提高重复采样次数。更直接的做法是重新定义分类粒度,把难以凑齐样本的类别合并为“其他昆虫”。不要小看这个合并操作,它对最终精度的正面影响往往比调模型参数更大。

3.2 图像清洗脚本:过滤损坏文件和重复样本

采集到的图像里总会有下载失败的空文件、截断的 JPEG、以及大量重复图片。这些脏数据进入训练集后,轻则浪费训练时间,重则导致训练中断。下面这段脚本可以过滤掉无法被 PIL 解码的图像:

from PIL import Image import os import hashlib img_dir = "raw_images" valid_dir = "cleaned_images" os.makedirs(valid_dir, exist_ok=True) seen_hashes = set() for root, _, files in os.walk(img_dir): for fname in files: path = os.path.join(root, fname) try: with Image.open(path) as img: img.verify() # 验证文件完整性 with open(path, "rb") as f: file_hash = hashlib.md5(f.read()).hexdigest() if file_hash in seen_hashes: print(f"丢弃重复图片: {path}") continue seen_hashes.add(file_hash) # 重新打开一次,verify() 之后需要重新加载才能读取尺寸 with Image.open(path) as img: img.load() # 统一转换为 RGB,避免 PNG 带透明通道影响后续标注 rgb_img = img.convert("RGB") rgb_img.save(os.path.join(valid_dir, fname)) except Exception as e: print(f"过滤损坏文件: {path}, 原因: {e}")

这段代码做了三件事:用verify()检查图像文件是否完整且能解析;通过计算 MD5 哈希去掉内容完全相同的重复图片;统一转换为 RGB 格式。参数说明:img.verify()只校验文件结构不加载像素数据,所以后面必须重新Image.open一次才能操作;哈希去重对同一张图的不同分辨率副本无效,如果爬虫采到同一场景不同尺寸的图,还需要加上感知哈希(pHash)进一步去重,普通毕设做完 MD5 去重就够用。

3.3 标注工具选型与 VOC 转 YOLO 格式

标注采用 LabelImg 或 X-AnyLabeling 均可,前者轻量稳定,后者支持半自动辅助标注。昆虫图像的特点是目标多、面积小,一个 1080p 画面里可能有几十只虫,逐只框很费时。常见做法是先标注 100 张图训练一个初版模型,再用模型对剩余图片做预标注,人工修正边框。这个流程能省一半以上时间。

标注格式上,LabelImg 默认输出 VOC XML。YOLO 训练需要的是 txt 格式,每行对应一个目标,内容是类别id x_center y_center width height,坐标值归一化到 0 到 1。写一个转换脚本批量处理:

import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, out_txt_path, class_map): tree = ET.parse(xml_path) 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_name = obj.find("name").text if cls_name not in class_map: continue cls_id = class_map[cls_name] bbox = obj.find("bndbox") xmin = float(bbox.find("xmin").text) ymin = float(bbox.find("ymin").text) xmax = float(bbox.find("xmax").text) ymax = float(bbox.find("ymax").text) x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") with open(out_txt_path, "w") as f: f.write("\n".join(lines)) class_map = {"butterfly": 0, "bee": 1, "fly": 2, "beetle": 3, "moth": 4} voc_to_yolo("annotations/001.xml", "labels/001.txt", class_map)

坐标归一化是关键,因为 YOLO 在训练时会将输入图像缩放到imgsz指定尺寸,归一化坐标保证缩放后目标位置依然准确。class_map字典需要和数据配置文件里的names保持一致,顺序错一个,训练出的模型预测类别就全乱了。

3.4 小目标占比高:切图策略的利弊

昆虫在整张图片里往往只占几十个像素,直接送进网络下采样到 640x640 后,小目标特征会被压缩到几乎没有。解决小目标检测的常见做法有两种:一是提高输入分辨率到 960 或 1280,二是切图推理。切图把大图拆成多个 640x640 的 patch,每个 patch 单独检测,再汇总结果。优势是保留小目标原始尺度,缺点也很明显:跨 patch 的同一只虫会在拼接处被重复检测,需要做 NMS 合并。毕设阶段如果训练图本身是手机拍摄的近景昆虫,目标占比通常不小,先用 640 分辨率训练,观察 mAP 中小目标指标再决定是否切图,不要一开始就上切图,徒增复杂度。

4. 训练与调参:损失曲线、分辨率与置信度的取舍

4.1 数据集配置文件与训练命令

标注完成后,需要写一个 YAML 文件描述数据路径和类别信息,放在项目根目录下。示例如下:

path: /home/user/insect_project train: images/train val: images/val nc: 5 names: ["butterfly", "bee", "fly", "beetle", "moth"]

path是项目根路径,trainval是相对于根路径的训练集与验证集目录,nc是类别数,names顺序必须与标注脚本里的class_map一致。这里有个常见错误:Windows 用户在 YAML 里写反斜杠路径,Ultralytics 解析时会报路径错误,统一用绝对路径的正斜杠或相对路径能省很多事。

启动训练的命令:

yolo detect train data=insect.yaml model=yolov8n.pt epochs=120 imgsz=640 batch=16 optimizer=AdamW lr0=0.01 patience=20

参数说明:model=yolov8n.pt表示加载 COCO 预训练权重做迁移学习;epochs=120对昆虫数据量足够,再多容易过拟合;imgsz=640是输入分辨率,如果类别里包含大量微型昆虫可以改 960,但训练时间会显著增加;batch=16根据显存调整,显存不足时报 CUDA out of memory 就把 batch 减半;patience=20表示 20 个 epoch 内验证集指标没有提升就提前停止。训练完成后,runs/detect/train/weights/best.pt就是验证集上表现最好的权重。

4.2 三个必须盯的指标与常见误读

训练日志里的box_losscls_lossdfl_loss要分开看。box_loss反映边框回归的收敛程度,一直抖动不降通常意味着锚框质量差或目标尺度变化大,调高imgsz会直接改善;cls_loss降不下去说明类别特征没有学出来,优先检查类别定义是否重叠,比如“蛾”和“蝶”在图像上确实难分。mAP50mAP50-95的差别更关键——前者只要求在框和真实框 IoU 超过 0.5 时算命中,对昆虫识别这种任务够用;后者要求更高对齐精度,如果mAP50高但mAP50-95低,说明模型能找到目标但边界框不准,后续数目统计会吃亏,因为边界框不准会直接影响跨帧去重时的 IoU 匹配。

验证集上还要单独看每类别的召回率。总数统计对漏检最敏感:漏了一只就是少计一只,而错检最多是类别标错或误检为背景。recall 低时,优先考虑降低conf_thres的默认值 0.25,推理阶段改成 0.1 看看漏检目标是否被召回,如果召回明显提升但误检爆炸,再训练时就要增加对应负样本。

4.3 训练过程的三个坑:早停误判、类别不均衡、增强过度

Ultralytics 自带早停机制,但它在验证集 mAP 出现平台期时也可能过早停止。我一般把patience调到 20 以上,避免模型还在收敛就被掐断。类别不均衡的坑则体现在训练日志的混淆矩阵里:样本多的类召回率虚高,样本少的类普遍偏低。此时不是继续加训练轮数,而是回数据阶段补样本或合并类别。第三个坑是数据增强过度。YOLOv8 默认开启 mosaic、mixup 等增强,对昆虫这种小目标密集场景,mosaic 会把不同图片切块拼接,小昆虫被拼接缝裁掉一半,反而制造了大量难以学习的目标。若发现训练损失持续震荡,考虑关闭部分增强:

yolo detect train data=insect.yaml model=yolov8n.pt epochs=120 imgsz=640 mosaic=0.0 mixup=0.0

mosaic=0.0不是彻底禁用该增强,而是把概率权重设为 0。关闭后模型收敛更稳定,代价是泛化能力略降,适合标注质量高、背景相对一致的数据。

5. 数目统计的实现:从检测框到可靠计数的三步

5.1 静态图像的计数:直接数框的局限

静态单张图片上计数,最直接的方式是统计置信度超过阈值的检测框数量。这个做法在画面干净、昆虫分散时没问题,一旦出现两个靠得很近的目标,检测框会合并成一个大框或者 NMS 时丢掉一个框,计数就偏少。另一个问题是置信度阈值的选择:阈值设 0.5,模型在低置信度下检出的真目标会被过滤;阈值设 0.1,误检框会虚增计数。正确做法是先绘制“置信度—计数”曲线,找到曲线斜率突变的位置作为阈值,而不是凭经验拍一个数。计算代码如下:

import cv2 from ultralytics import YOLO model = YOLO("best.pt") image = cv2.imread("test.jpg") results = model.predict(image, conf=0.1, iou=0.45, verbose=False) boxes = results[0].boxes for thresh in [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7]: count = int((boxes.conf >= thresh).sum()) print(f"conf>= {thresh:.1f}: {count} 只")

iou=0.45控制 NMS 合并的重叠阈值,值越小,重叠框越容易被合并;昆虫类目标距离近时,这个值调低到 0.4 可以减少重复框,但太小会把同一只虫的两个叠加框误判为不同目标导致漏检。建议静止场景保持 0.5 左右,密集群聚场景调整为 0.4。

5.2 视频跨帧计数:用 IoU 匹配做个体去重

毕设题目里“数目统计”如果面向视频,跨帧去重就是绕不开的问题。一只昆虫在 30fps 视频里停留 5 秒,会产生 150 帧检测结果,直接累加逐帧计数会得到一个天文数字。常见做法是逐帧做检测,然后根据相邻帧检测框的 IoU 判断是否是同一只虫:当前帧某个框与上一帧某个框的 IoU 大于阈值,就认为是同一个体,继续使用上一次分配的 ID;否则视为新个体,ID 加一。简化实现如下:

import numpy as np def compute_iou(box1, box2): x1 = max(box1[0], box2[0]) y1 = max(box1[1], box2[1]) x2 = min(box1[2], box2[2]) y2 = min(box1[3], box2[3]) inter = max(0, x2 - x1) * max(0, y2 - y1) area1 = (box1[2] - box1[0]) * (box1[3] - box1[1]) area2 = (box2[2] - box2[0]) * (box2[3] - box2[1]) union = area1 + area2 - inter return inter / union if union > 0 else 0 prev_boxes = [] tracking_id = 0 seen_ids = set() for frame_boxes in video_detections: # video_detections 是逐帧检测框列表 matched_prev_indices = set() for box in frame_boxes: best_iou = 0.4 best_idx = -1 for idx, prev_box in enumerate(prev_boxes): if idx in matched_prev_indices: continue iou = compute_iou(box, prev_box) if iou > best_iou: best_iou = iou best_idx = idx if best_idx >= 0: matched_prev_indices.add(best_idx) continue # 同一只虫,只延续 ID,不新增计数 else: tracking_id += 1 seen_ids.add(tracking_id) prev_boxes = frame_boxes

逻辑说明:compute_iou计算两个边界框的交并比;matched_prev_indices用来防止一帧内两个检测框匹配到同一个上一帧目标;加入了 ID 分配机制但不实现完整跟踪器。这个简化方案对昆虫移动缓慢、帧率较高的场景效果尚可,但对快速飞行的昆虫,帧间位置变化大,IoU 可能低于阈值导致重复计数。提升方案是结合目标中心点距离做匹配:当 IoU 接近 0 但中心距离小于目标自身尺寸时,也视为同一目标。若毕设要求更高的计数准确率,可以集成 ByteTrack 这类轻量级多目标跟踪库,效果更稳定,但会引入跟踪参数调优的额外工作。

5.3 按区域统计:只数特定区域内的昆虫

很多昆虫统计任务不是数整张图的个体,而是数某个特定区域内出现的数量,例如黄色粘虫板上的蓟马、培养皿里的果蝇。实现方式是对检测框中心点做多边形包含判断:

import cv2 def point_in_polygon(x, y, polygon): return cv2.pointPolygonTest(polygon, (x, y), False) >= 0 region = np.array([[100, 100], [400, 100], [400, 300], [100, 300]], dtype=np.int32) count = 0 for box in boxes: cx = (box[0] + box[2]) / 2 cy = (box[1] + box[3]) / 2 if point_in_polygon(cx, cy, region): count += 1

区域计数需要考虑边界效应:中心点恰好落在区域边缘外的个体会被排除,如果这些个体一半在区域内一半在外,计数就有偏差。常见处理是扩大区域边界一个检测框平均尺寸的范围,把边缘个体容差进来。动态区域则更复杂,比如相机抖动造成的区域偏移,需要引入帧间配准,超出常规毕设范围,选题时量力而行。

6. 部署与答辩:导出模型、验证误差与现场演示技巧

6.1 导出 ONNX 并封装计数 API

答辩现场不能依赖训练环境跑推理,把模型导出为 ONNX 并使用 onnxruntime 做 CPU 推理,是最稳妥的交付方式。导出命令:

yolo export model=best.pt format=onnx opset=12 simplify=True

simplify=True会对计算图做常量折叠和冗余节点删除,减少推理耗时。导出后写一个轻量 API 提供图片上传和计数返回:

from fastapi import FastAPI, UploadFile import cv2 import numpy as np import onnxruntime as ort app = FastAPI() session = ort.InferenceSession("best.onnx", providers=["CPUExecutionProvider"]) @app.post("/count") async def count_insects(file: UploadFile): data = await file.read() img = cv2.imdecode(np.frombuffer(data, np.uint8), cv2.IMREAD_COLOR) # 实际代码需包含 letterbox 预处理与输出后处理, # 此处省略,核心是读取 ONNX 输出 [1, 84, 8400] 的特征图 return {"count": 12, "classes": {"bee": 7, "fly": 5}}

演示时可以把前端做成简单的 HTML 上传页,也可以用 Gradio 写一个拖拽上传的交互界面,后者代码量少、观感更好。注意 ONNX 输出的后处理与 YOLOv8 PyTorch 版本不同,需要手动解码预测框、做置信度过滤和 NMS,这部分建议在答辩前单独编写并测试。

6.2 误差验证方法:人工计数与模型计数的对比

答辩评委最常问的问题之一是本设计的计数准确率是多少。合理做法是准备 30 到 50 张测试图,人工逐张计数作为真值,模型计数作为预测值,计算平均绝对百分比误差 MAPE:

MAPE = (1/n) * sum(|实际数 - 预测数| / 实际数) * 100%

统计一张表,按类别分别列出人工数、模型数、误差率。误差来源要能在答辩时说明白:遮挡导致漏检、背景纹理误检、密集目标框合并。这些问题比精度数值本身更能体现工作量。

6.3 加分演示技巧:把检测框和计数过程可视化

现场演示环节,建议加载模型后用摄像头实时推理,画面右上角绘制实时计数,检测框用不同颜色区分类别。这里有一个小技巧:推理脚本里加上annotator绘制置信度文本,同时记录每秒帧数(FPS),把 FPS 和检测数量同时显示在画面左上角。这样评委能直观看到模型是实时运行的,而不是播放预录视频。对昆虫快速移动导致的帧间重复计数问题,可以现场展示 5.2 节的 IoU 去重逻辑在视频窗口中的 ID 号变化,强调“同一只虫在不同帧的 ID 保持不变,计数不重复增加”这一设计细节,这比任何文字描述都更有说服力。

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

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

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

立即咨询