基于YOLO的昆虫识别与数目统计毕设实战指南
简介计算机视觉方向的高年级本科生常为毕业设计发愁压缩包内提供了从昆虫图片数据集、标注信息到识别与数量统计模型、Python脚本及简易界面在内的完整实现方案适合课设、大作业或毕设场景直接参考与二次开发。压缩包共164个文件以jpg样本图片、py识别脚本、npy特征数据、xml标注及ui界面为主配套model模型文件与csv统计结果整体仅14.59MB轻量易用。npy与xml保留了训练与标注的中间数据便于深入分析识别原理训练好的模型和推理脚本可直接运行快速得到统计结果。已有140人浏览学习资源包含多类昆虫图片、可运行的界面既能帮助理解目标检测思路也可快速复现昆虫数量统计流程。从数据清洗到结果导出都有脚本支撑目录结构清晰便于逐模块学习或按需摘取。1. 昆虫识别和数目统计为什么这个毕设方向值得做每年大四下学期总有一批人被昆虫识别和数目统计这个课题卡住代码跑不通、数据不齐、模型精度上不去、统计结果没法解释。这个方向听起来像农业遥感或者生物多样性监测实际落到毕设里本质就两件事——用目标检测模型把昆虫从背景里找出来再按画面或视频统计个数。技术栈是标准的 YOLO 系检测 计数逻辑拼接适合计算机视觉、物联网、软件工程甚至农学背景的学生。它的优势在于公开数据集能拿到模型生态成熟复现路径清晰答辩时既能讲算法又能讲业务场景且工作量可以按需裁剪成一个学期能完成的范围。后面你看到的每一章都是我按这个过程完整趟过一遍之后的方案和踩坑记录。2. 先把数据喂到位昆虫数据集的获取、清洗与标注策略2.1 数据集从哪来公开集与自采集的取舍昆虫识别的公开数据集大多来自农业植保领域主流选择是 IP102包含 102 类常见农业害虫。它有将近 7.5 万张图片类别覆盖广但原始标注存在明显的类别不均衡和少量错标直接拿来训练模型会对样本多的类别过拟合对样本少的类别直接视而不见。常见做法是拿 IP102 做预训练基底再叠加自己拍摄或网上下载的补充图片形成一个小规模定制集。如果你有学校农田、实验站或城市绿地资源用手机拍摄到的真实场景图质量远高于网络图因为在答辩演示时评委更看重模型在自己采集的数据上能不能跑通。另外要注意公开数据集里的昆虫图大多是白色背景的标本图或特写图和实际田间地头的复杂背景差距很大。所以拿到 IP102 后不要直接开训先做一轮人工清洗把背景单一、光照异常、画面中有多类昆虫重叠的图片挑掉。表昆虫识别常用公开数据源对比数据源类别数大概规模主要问题IP102102 类7 万类别不均衡、部分错标AI Challenger 病虫害27 类5 万作物病害偏多昆虫识别可作为延伸自采集自定几百到几千张工作量大但背景贴合真实场景2.2 类别怎么定合并还是保留细粒度IP102 的 102 个类别里大量昆虫属于同科不同种视觉差异极小比如多种蛾类在画面里人眼都分不清强行让模型分 102 类训练时间和难度会成倍增加最终精度反而不如合并后的粗分类。我一般建议把类别合并到 815 类按目或科组织例如蚜虫、叶蝉、飞虱、螟蛾、夜蛾、蝶类、甲虫、蝗虫、蜂类、蝇类等。合并后类别之间视觉差异明显检测难度下降统计结果也更稳。合并方式上直接改标注文件的类别 ID 即可。YOLO 格式标注是class_id x_center y_center width height只需要把原 IP102 类别映射写成一个字典批量替换首个字段。注意替换前备份原始标注文件避免改错后无法回溯。2.3 标注工具与数据划分脚本定制数据集的标注我常用 X-AnyLabeling它是 LabelImg 的强化版内置自动标注模型。先用公开集模型粗标再人工微调标注速度能快一倍以上。标注时统一遵守一个原则昆虫只占画面几个像素的直接放弃被遮挡超过一半的也放弃否则会把残缺目标本身当成特征学进去。标注完成后数据要按训练集、验证集、测试集划分常见比例是 8:1:1。下面的 Python 划分脚本不依赖重型框架按文件做软链接不会复制图片浪费磁盘空间import os import random from pathlib import Path def split_dataset(img_dir, label_dir, output_dir, train_ratio0.8): images list(Path(img_dir).glob(*.jpg)) list(Path(img_dir).glob(*.png)) random.shuffle(images) total len(images) train_cut int(total * train_ratio) val_cut int(total * (train_ratio 0.1)) for split, subset in [(train, images[:train_cut]), (val, images[train_cut:val_cut]), (test, images[val_cut:])]: img_out Path(output_dir) / images / split label_out Path(output_dir) / labels / split img_out.mkdir(parentsTrue, exist_okTrue) label_out.mkdir(parentsTrue, exist_okTrue) for img_path in subset: label_path Path(label_dir) / (img_path.stem .txt) if not label_path.exists(): continue (img_out / img_path.name).symlink_to(img_path.resolve()) (label_out / label_path.name).symlink_to(label_path.resolve()) split_dataset(raw_images, raw_labels, insect_dataset)逻辑说明先把图片路径集合打乱按 8:1:1 切出三分每一份在目标目录下建立 images 和 labels 子目录然后做软链接既不占额外空间也让 YOLO 能直接按目录读取。参数说明train_ratio控制训练占比默认 0.8如果你后期要加大数据量可以改成 0.85文件后缀只处理了 jpg 和 png遇到 bmp 或 tiff 格式记得加进glob。3. 训练一个能用的昆虫检测模型YOLO 选型与参数调优3.1 模型选型为什么以 YOLOv8n 为基线昆虫检测的难点不是网络结构而是小目标和密集场景。YOLOv8n 是 nano 版参数量最小推理最快在 CPU 上也能勉强跑视频对毕设来说容错率高。如果你的 GPU 显存 6G 以上可以用 YOLOv8m精度有明显提升但训练速度也会拉长。YOLO11 是更新一版的模型精度在公开 COCO 集上略好但生态和资料没有 YOLOv8 那么成熟。毕设追求的是能稳定复现、代码能讲清、踩坑有人回答YOLOv8n 是性价比最高的选择。把 YOLOv11 作为消融实验的对照组写进论文倒是可以主线不建议换。3.2 训练配置数据 YAML 与训练命令先把数据集组织成 YOLO 标准结构写一个数据描述文件insects.yamlpath: ./insect_dataset train: images/train val: images/val names: 0: aphid 1: leafhopper 2: moth 3: butterfly 4: beetle 5: locust 6: bee 7: fly字段说明names的 ID 顺序必须和标注文件里的 class_id 完全一致否则模型会把类别学串。建议在开始训练前写一行检查代码统计每一个类别在训练集里出现的次数发现某些类只有个位数样本时先做过采样或合并不要贸然开训。训练命令如下yolo detect train datainsects.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/insect_experiment \ namebaseline_v1参数说明modelyolov8n.pt是加载 COCO 预训练权重做迁移学习比从头训练收敛快得多imgsz640是兼容识别速度和精度的常用值如果你的昆虫在画面中占比小可以调到 800但显存占用会上升patience20表示连续 20 个 epoch 验证集指标不提升就早停防止过拟合的同时节省时间。训练完成后在runs/insect_experiment/baseline_v1/下会产出best.pt和last.pt。best.pt是验证集精度最高的权重后续推理和导出都用它。3.3 训练异常怎么判断先看损失曲线再看指标训练中要关注终端输出的box_loss、cls_loss和dfl_loss。如果 loss 在下降但验证集 mAP 原地不动多半是过拟合如果 loss 根本没降先检查数据 YAML 路径是否正确再检查标注文件有没有空文件或坐标越界的情况。一个非常实用的小技巧在训练启动后跑到第 10 个 epoch手动停掉用当前权重对一张最复杂的验证图做一次预测可视化输出。这一步能提前发现模型把背景里的泥土、叶子阴影当成昆虫之类的问题不用等 100 个 epoch 跑完才发现白训。这类问题在你的模型里会以高置信度的错误框出现和标注覆盖不全面、样本难以区分都有关系趁早发现趁早调整。如果想提升 AP50 之外的表现可以调整 YOLO 自带的增强参数。在数据集小时开启马赛克增强能扩展样本多样性但训练后期马赛克反而会影响小目标学习最好在最后 10 个 epoch 关闭它。Ultralytics 允许通过mosaic0在训练命令中直接关闭这也算一个冷门但有用的调参手段。4. 数目统计不是数个数从检测框到可靠计数的三种接法4.1 静态图片统计直接数框但有前提最简单直接的计数方式是统计检测结果里boxes的数量适用于单张图片、昆虫分散、无重叠的场景from ultralytics import YOLO model YOLO(runs/insect_experiment/baseline_v1/weights/best.pt) results model.predict(field_01.jpg, conf0.35, iou0.45) boxes results[0].boxes insect_count len(boxes) print(f检测到 {insect_count} 只昆虫) # 按类别统计 if boxes is not None: cls boxes.cls.int().tolist() class_counts {int(c): cls.count(c) for c in set(cls)} print(f各类数量分布: {class_counts})逻辑说明conf0.35是置信度阈值低于这个值的框会被丢弃。阈值不是越高越好了在密集昆虫场景下低置信度框往往代表被遮挡的真实个体。iou0.45控制非极大值抑制的严格程度值越低抑制越强重叠框越容易被合并。参数调整建议当你的验证集 mAP50 高于 0.85 时可以把 conf 降到 0.25 来召回更多小目标再人工核对误差。4.2 视频流计数接跟踪器避免重复计算静态计数在视频流上会出大问题——同一只昆虫在连续多帧中出现每一帧都会被数一次。常见的解决方法是在检测后接一个跟踪器对每个检测框分配一个 ID同一 ID 只计一次数。Ultralytics 集成了 ByteTrack 和 BoT-SORT用起来很简单from ultralytics import YOLO model YOLO(runs/insect_experiment/baseline_v1/weights/best.pt) tracks model.track( field_video.mp4, trackerbytetrack.yaml, conf0.35, iou0.45, persistTrue, verboseFalse ) ids_seen set() for frame in tracks: if frame.boxes is not None and frame.boxes.id is not None: ids frame.boxes.id.int().tolist() for i in ids: ids_seen.add(i) print(f视频中总共出现 {len(ids_seen)} 只昆虫)逻辑说明trackerbytetrack.yaml指定跟踪算法BYTETrack 的策略是保留低分框参与后续数据关联对遮挡场景更友好。persistTrue表示跨帧保持同一 ID是去重的关键开关漏掉这个参数每个帧的 ID 会重置计数会变成每帧累加。注意这个方案的一个已知限制两条昆虫交叉运动时跟踪器可能互换 ID导致新 ID 出现而重复计数这是所有单摄像头方案的边界。4.3 带 ROI 区域的计数让数字更有业务含义很多时候总数并不能说明问题农业场景更关注检测区域内的虫口密度。常见的做法是画一个多边形 ROI只统计框中心落在区域内的目标这在防治决策时比全图计数更实用。from ultralytics import YOLO from shapely.geometry import Point, Polygon roi_polygon Polygon([(100, 100), (500, 100), (500, 400), (100, 400)]) model YOLO(best.pt) results model.predict(field_02.jpg, conf0.35) count_inside 0 if results[0].boxes is not None: for box in results[0].boxes.xyxy.tolist(): x_center (box[0] box[2]) / 2 y_center (box[1] box[3]) / 2 if roi_polygon.contains(Point(x_center, y_center)): count_inside 1 print(f黄色粘虫板区域内昆虫数量: {count_inside})逻辑说明用检测框的中心点作为落点判断依据而不是框本身能避免跨区域边界的框被两侧重复计算。上方的例子中prediction 返回的xyxy是像素坐标所以 ROI 也要用像素坐标定义。如果你需要批量处理几十张图注意不同图片的尺寸可能不同ROI 坐标最好记录为相对坐标而非绝对坐标。5. 昆虫识别的 4 个常见翻车点现象、原因与排查路径5.1 小目标漏检严重模型对大目标过拟合现象验证集 mAP 不错但实际测试图片上画面远处或体积较小的昆虫完全检测不到只有大特写能出框。原因数据集中昆虫占比多为大目标模型没有学到小目标的特征。另外imgsz640时小目标的特征图尺寸只有 20x20 左右信息损失严重。解决把输入尺寸提到 800 或 960同时开启 YOLOv8 的切片推理通过给图片做 tiles 切块让每个小目标在子图里变成大目标。我有一次把尺寸从 640 调到 800、并做 50% 重叠切块后小类别召回率从 0.31 提升到 0.57。代价是把推理时间变长了约 60%但毕设演示是离线处理视频完全能接受。提示如果使用了切块推理重叠区域的目标会被重复计算要在计数端做去重——比如剔除中心点距离小于 10 个像素的重复框。5.2 类别不均衡少数类别被全部吃掉现象训练的损失曲线正常但查看按类别的 PR 曲线时某几个稀有类别的 AP 接近 0。原因数据集里蚜虫类有一万张蜂类只有三百张模型倾向于把所有细长带翅的都判成蚜虫因为这样整体 loss 降得最快。解决第一优先是做数据过采样把少样本类的图片复制 3~5 份进训练列表不改变标注文件只让模型在每轮 epoch 里看到它们的次数变多。第二是在命令里给少样本类加权Ultralytics 支持按类别权重修改 loss。最容易被忽略的其实是数据层面的问题——少样本类的标注本身可能就不一致比如有的标了整体有的只标了头部模型无法统一学习。先去抽看 50 张该类别标注再决定是重标还是合并。5.3 训练到一半显存不足中断现象用batch16训练时前 10 个 epoch 正常后续出现CUDA out of memory但 batch 已经调小过一轮。原因训练中马赛克增强会随机拼接图片实际 batch 的显存波动很大。尤其是当增强逻辑在某个 epoch 生成了特别大的组合图时瞬间显存占用飙升。解决batch8是 6G 显存下的安全值同时开启cacheram将图片预加载到内存中以减少显存碎片。如果训练进程保存的中断权重可用直接在命令里指定resumeTrue从断点继续不要从零开始。这里最玄学的部分是同样的参数有时能跑有时爆显存建议按峰值显存占用预留 15% 余量。5.4 计数结果重复或异常偏大现象一张明显只有 5 只昆虫的图统计出 12 只或者视频里一只昆虫爬过计数到了 7 次。原因置信度阈值太低导致背景误检或是 NMS 的 IoU 阈值设太高重叠框没有被合并再或者视频流未接跟踪器。解决先调conf到 0.4 看误检是否消失再调iou到 0.3 让重叠框合并更激进。视频场景务必用跟踪器。如果静态图使用了切片推理还要处理切片边界处被截断的目标它在两个切片里各被检测了一次。有一个直观诊断技巧把检测结果可视化画框保存成图片数一下图里的框数和代码数是否一致——如果一致但和真实数量不一致问题出在模型如果不一致问题出在计数逻辑。6. 把模型落到毕设演示半精度推理与批量推理脚本6.1 半精度推理与 ONNX 加速Python 端的 YOLO 推理在 CPU 上每秒只能跑几帧毕设演示现场如果有性能要求用半精度浮点推理可以明显提速。PyTorch 处理的原理是让模型权重和输入图像都加载成 FP16在支持 FP16 的 GPU 上计算速度提升约两倍model YOLO(best.pt) results model.predict(field_03.jpg, halfTrue, conf0.35)halfTrue只适用于支持 FP16 的 GPU。若要在 CPU 上提速把模型导出为 ONNX 格式yolo export modelbest.pt formatonnx imgsz640导出成功后使用model.predict(..., devicecpu)时Ultralytics 会自动用官方的 ONNX 运行时做推理。注意导出的 ONNX 精度是 FP32CPU 上速度提升依赖运行时优化大约能快 30%不会有质变。如果你追求更大提升就用开放式神经网络交换后再转成相应平台的引擎格式但那一步配置复杂对毕设来说不是必须的。6.2 批量推理脚本给统计加上明细输出做完识别和统计最终要给毕设报告配数字。写一个批处理脚本把每个文件的计数结果按类别汇总到 CSV 中import csv from pathlib import Path from ultralytics import YOLO model YOLO(best.pt) image_dir Path(test_images) results_log [] for img_path in sorted(image_dir.glob(*.jpg)): res model.predict(str(img_path), conf0.35)[0] box_count len(res.boxes) if res.boxes is not None else 0 cls_names res.names class_dist {} if res.boxes is not None: for c in res.boxes.cls.int().tolist(): class_dist[cls_names[int(c)]] class_dist.get(cls_names[int(c)], 0) 1 results_log.append([img_path.name, box_count, class_dist]) with open(insect_count_result.csv, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([图片名, 总数量, 类别分布]) writer.writerows(results_log)逻辑说明res.names是数据集配置里的类别名映射把类别 ID 转成可读名称写入 CSV方便答辩报告直接引用。这里也处理了无目标的空图——boxes为 None 时计数为 0而不是抛异常中断整个批次。做完整套流程下来我最深的一个体会是检测模型只是系统的一半另一半是数据清洗和计数方案。提前规划好类别合并策略、接好跟踪器和 ROI 逻辑远比你多调 10 个 epoch 有用。答辩时能拍着胸脯说我跑过 30 张图上每个数字验证过比我把模型调到了最好更有说服力。希望帮到你。本文还有配套的精品资源点击获取