简介一套完整的昆虫识别与数目统计毕业设计项目资源面向大四学生及需要完成课设、大作业的开发者提供从模型训练、目标检测到数量统计的闭环方案。压缩包共164个文件大小14.59MB内含23个Python脚本、97张JPG图片、13个npy数据文件、10个XML标注文件、3个模型文件、3个CSV表格以及UI界面、PDF说明等便于对照代码、数据与文档理解整体结构。目前已有140人学习下载具备一定参考价值。读者可基于该项目快速搭建昆虫检测与计数实验环境复用已训练模型与预处理数据学习数据集标注格式、模型调用方式与统计逻辑内置的界面文件与说明文档可帮助梳理交互流程适合毕业设计查重、代码复现及功能扩展也可在此基础上替换为其他昆虫类别迁移到类似计数任务中。1. 昆虫识别与数目统计毕设里的两个坑一个在识别精度一个在统计口径“大四毕业设计昆虫识别和数目统计”这个主题听起来不算难无非是分类加计数但真正复现过的人都知道识别模型好训数目统计才是翻车重灾区。这份 zip 资源从文件名看结构是三个 CSVdata.csv、datatest.csv、ques.csv、几张 fly*.jpg 测试图片和一个 .iml 工程文件属于典型的课设/大作业/毕设骨架表管数据、图管验证、工程文件管环境。它能解决的核心问题是——给你一套从数据读到识别再到统计的完整代码基础不用从零搭环境、不需要自己拍图标注改改参数就能往自己的方向迁移。适合 Python 有一定基础、但需要靠这份毕设快速产出可运行结果的在校生下载复现。2. 拆包看结构从 zip 压缩包到可复现工程中间需要的几步拿到资源后第一件事不是看代码而是确认压缩包能不能安全解出来以及解出来以后哪些文件是真的能用的。很多同学在这步就开始了“玄学调试”——文件还没看全就开始改代码。这里把拆包过程完整走一遍。2.1 解压与文件清单核对先用 Linux 命令把 zip 安全打开下载下来是一个 zip 压缩包文件名是「课设大作业毕设-大四毕业设计昆虫识别和数目统计.zip」。这个文件名的坑在解压前就会出现文件名里带了和中文冒号在 Linux bash 里直接unzip会报找不到文件。我的处理方式是先把 zip 改名成纯英文再解压mv 课设大作业毕设-大四毕业设计昆虫识别和数目统计.zip insect.zip unzip -q insect.zip -d insect_project cd insect_project ls -la这里-q是静默模式不会把每个文件名刷屏打出来-d insect_project指定解压目录避免所有文件直接撒在当前目录。解压后你会看到的文件我用表格做一个清单文件作用data.csv训练标注数据通常是 image_path label 的结构datatest.csv测试集标注可能比训练集多一列 count 或 bboxques.csv问题/需求描述或者统计口径说明fly2.jpg、fly11.jpg、fly15.jpg、fly16.jpg、fly17.jpg测试样本图目测是苍蝇类昆虫Insect_Identification.imlIntelliJ 系 IDE 的工程模块文件.gitignoreGit 忽略规则说明这个项目原本带版本管理注意清单里没有model.pth这类权重文件也没有类似train.py的独立脚本所以这份资源更接近「骨架 测试数据」的快照而不是一个解压就能跑出结果的一键项目。你得自己把训练代码补上这对毕设来说反而是好事——能写进论文“本人实现”的部分多了。2.2 CSV 标注结构怎么看data、datatest、ques 三份表的分工看 CSV 这种文件最快的方式是用head看前几行不要直接用 Excel 打开因为一旦文件是 UTF-8 编码且含中文Excel 会把它拆得乱七八糟。在 Linux 下直接执行head -5 data.csv echo ----- head -5 datatest.csv echo ----- head -5 ques.csv常见的列结构是image_path,label或者filename,class_id如果 datatest 里多了count列那这个项目的数量统计就不是靠目标检测而是靠对每个图片样本的计数回归或者多标签分类实现的。ques.csv 一般是题目要求里面可能包含“统计每类昆虫出现次数”这类口径描述。拿到 CSV 以后我建议先做一次快速统计确认样本分布长什么样import pandas as pd train pd.read_csv(data.csv) test pd.read_csv(datatest.csv) # 打印列名确认哪一列是图片路径、哪一列是标签 print(训练集列名, train.columns.tolist()) print(测试集列名, test.columns.tolist()) # 按标签统计样本数量看是否存在类别不平衡 label_col label if label in train.columns else train.columns[1] print(train[label_col].value_counts())这段代码的目的是在动手训练之前就掌握两个信息列名长什么样、每个类别的样本量是否均衡。如果某个类的样本数只有另一个类的十分之一后面训练时就必须做类别加权或者过采样否则模型会把这个类直接忽略掉。2.3 .iml 文件意味着什么用 PyCharm 打开工程省掉配置痛苦.iml是 IntelliJ 家族的模块描述文件PyCharm 打开项目时会靠它识别目录结构和运行配置。如果你用的是 PyCharm直接File - Open选到解压出来的目录IDE 会自动根据.iml恢复项目视图。如果你的环境是 VS Code 或者纯终端忽略这个文件也不影响训练代码运行。这里有个常见误区很多人以为.iml是 Java 项目专属看到它就往 Maven 上想。实际上 PyCharm 的 Python 项目同样会生成.iml它里面记录的只是 module 名称、SDK 以及源码目录标记。真正决定依赖版本的是requirements.txt或environment.yml但这份资源里没有这两个文件所以我一般会先手动建一个虚拟环境把 PyTorch 相关依赖补上python -m venv venv source venv/bin/activate pip install torch torchvision pandas pillow scikit-learn matplotlib为什么装这几样torch 提供模型和训练循环torchvision 提供预训练模型和图像变换pandas 负责解析 CSVpillow 负责读图sklearn 负责生成混淆矩阵和分类报告matplotlib 负责画图。这些都是昆虫识别项目的最小依赖集缺一个后面都会报错。3. 从识别到统计为什么分类模型不能直接回答数量问题很多同学把“昆虫识别和数目统计”理解成“图像分类 数数”实际操作时才发现两个环节之间的衔接有大量细节。这一章把识别、统计的链路拆开讲清楚顺带说明选型时为什么常见毕设会选“迁移学习 分类汇总”而不是从头训练一个检测器。3.1 昆虫识别链路图像分类模型的基座与选择理由昆虫识别本质上是一个图像分类任务输入一张图片输出该昆虫属于哪个类别。毕业设计场景里几乎没有从零训练一个 CNN 的必要因为数据量撑不起来训练时间也不允许。常见做法是拿 torchvision 里在 ImageNet 上预训练过的 ResNet18 或 ResNet50 做迁移学习把最后的全连接层替换成自己的类别数。import torchvision.models as models # 使用预训练 ResNet18只替换最后一层 model models.resnet18(weightsmodels.ResNet18_Weights.DEFAULT) num_classes 10 # 按你的数据类别数修改 model.fc torch.nn.Linear(model.fc.in_features, num_classes)这段代码背后有两个参数值得注意weightsmodels.ResNet18_Weights.DEFAULT表示下载官方预训练权重这个权重在 ImageNet 上学到的底层特征边缘、纹理、形状对昆虫图像同样有效model.fc.in_features读取的是原全连接层的输入维度ResNet18 是 512ResNet50 是 2048直接替换可以避免维度写死导致的兼容性问题。不用更深的网络不代表 ResNet50 不好关键看你的图片尺寸。如果数据集里的照片普遍小于 200×200ResNet18 可能比 ResNet50 更稳因为深网络在小图上容易过拟合如果图片是高清的且昆虫在画面中占比较大ResNet50 的精度优势会体现出来。3.2 数目统计的三种口径按图统计、按目标统计、按类别统计目录统计这个环节决定你用什么样的后处理逻辑。毕业设计里最常见的口径有三种统计口径实现方式对应资源类型按图片统计每张测试图预测一个类别最后统计各类别图片张数datatest.csv 每行是一张图按目标统计单张图里可能有多个昆虫需要检测或密度图回归数据集带 bbox 标注按类别统计统计每个昆虫类别出现的总次数可跨图片数据集带 count 列这份资源里 fly2.jpg、fly11.jpg、fly15.jpg 等多张图片同时存在且没有 bbox 相关文件更接近第一种口径的工程结构。也就是说数量统计的实现方式大概率是先对测试集每张图做分类再用Counter汇总每一类的张数。如果你发现 datatest.csv 里真的带count列那就要把思路切换到回归或者密度估计但那需要目标检测框架不是简单改一个全连接层能解决的。3.3 选型对比ResNet 迁移学习还是 YOLO 检测器我见过不少同学一上来就要上 YOLO理由是觉得它能直接完成“识别 定位 计数”。这个想法本身没问题YOLO 确实适合“一张图里有多个昆虫”的场景但代价是标注成本极高——每个目标都要画框一个图里标几十个框是常有的事。选型标注成本训练难度数量统计精度ResNet 分类 张数汇总低每图一个标签低迁移学习效果好只能统计图片张数Faster R-CNN 检测高每个目标一个框高需要调 anchor能统计每个目标数量YOLO 检测 计数高每个目标一个框中等工程生态成熟能统计每个目标数量但小目标漏检率不低对于“昆虫识别和数目统计”这个题目如果题目本身没有强调“单张图片里有多只昆虫”老老实实用分类模型是最稳的。如果题目要求你必须统计单张图中的个体数那么检测方案逃不掉这时候你需要获取带 bbox 的公开昆虫数据集——这份资源里的数据体量大概率撑不起目标检测训练。4. 动手跑通数据读取、模型训练、数量统计三步走理论说完了进入实操环节。这一章我从数据读取开始完整走一遍训练、推理、统计的流程。所有代码都按“能直接复制运行”的标准写注释里说明每个参数的含义。4.1 数据读取把 CSV 变成 PyTorch Dataset无论 CSV 列名叫什么最终都要把它包装成 PyTorch 能识别的 Dataset。这里的关键是不要假设列名而是动态判断import pandas as pd import torch from torch.utils.data import Dataset from PIL import Image import os class InsectDataset(Dataset): def __init__(self, csv_path, img_root, transformNone): self.df pd.read_csv(csv_path) self.img_root img_root self.transform transform # 动态推断列名优先找 image_path / file否则取第一列 if image_path in self.df.columns: self.img_col image_path elif filename in self.df.columns: self.img_col filename else: self.img_col self.df.columns[0] # 标签列同理优先 label否则取第二列 self.label_col label if label in self.df.columns else self.df.columns[1] def __len__(self): return len(self.df) def __getitem__(self, idx): img_name self.df.iloc[idx][self.img_col] label int(self.df.iloc[idx][self.label_col]) img_path os.path.join(self.img_root, img_name) img Image.open(img_path).convert(RGB) if self.transform: img self.transform(img) return img, label这段代码的逻辑说明__getitem__根据索引读图片并返回图像张量和标签列名推断写在__init__里避免因 CSV 列名不一致导致程序报 KeyError。参数方面img_root是图片所在根目录如果 CSV 里存的是相对路径就必须和根目录拼接label强制转成 int因为 CSV 读进来的分类标签可能是字符串模型输出要求整数索引。transform在训练和测试时通常不一样训练要用随机裁剪和翻转来增强测试只用缩放和归一化。4.2 训练配置epoch、batch、learning rate 的合理区间拿到 Dataset 之后划分训练集和验证集再定义数据加载器和训练超参数。这个环节最容易被忽略的是验证集的划分方式一旦出现数据泄漏数量统计的结果会虚高。from torch.utils.data import DataLoader, random_split import torchvision.transforms as T # 训练时做增强测试时只做缩放归一化 train_transform T.Compose([ T.Resize((256, 256)), T.RandomCrop(224), T.RandomHorizontalFlip(), T.ToTensor(), T.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) # 加载全量数据再按 8:2 划分 full_dataset InsectDataset(data.csv, img_rootimages, transformtrain_transform) train_len int(len(full_dataset) * 0.8) val_len len(full_dataset) - train_len train_ds, val_ds random_split(full_dataset, [train_len, val_len]) train_loader DataLoader(train_ds, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_ds, batch_size32, shuffleFalse, num_workers4)这里的参数含义Resize((256, 256))RandomCrop(224)先放大再随机裁剪相当于做了一定程度的尺度扰动能提升模型对昆虫在画面中大小不一致的鲁棒性。RandomHorizontalFlip()对昆虫图像有效的增强因为昆虫左右翻转仍然是可信样本。Normalize(mean[0.485, ...], std[0.229, ...])ImageNet 统计出的均值和方差使用预训练模型时必须沿用这组数值不能随意改。batch_size32在显存足够的 GPU 上一般不会爆如果你的显卡只有 4GB 显存降到 16 或者 8。这个参数直接影响梯度下降的平滑度batch 太小收敛慢太大容易卡在尖锐的局部最优。训练循环不写了直接跳到一个好的做法验证集准确率卡片如何做这会更实用。以下是训练循环的标准骨架用交叉熵损失和 Adam 优化器import torch.nn as nn device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr1e-4) epochs 20 best_acc 0.0 for epoch in range(epochs): model.train() running_loss 0.0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() # 每个 epoch 后跑一次验证集 model.eval() correct, total 0, 0 with torch.no_grad(): for images, labels in val_loader: images, labels images.to(device), labels.to(device) outputs model(images) _, preds torch.max(outputs, 1) correct (preds labels).sum().item() total labels.size(0) val_acc correct / total print(fEpoch {epoch1}/{epochs}, Loss: {running_loss/len(train_loader):.4f}, Val Acc: {val_acc:.4f}) if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), best_insect_model.pth)提一句torch.save只存state_dict而不存整个模型这是为了换机器部署时更灵活也避免了 PyTorch 版本不一致导致的加载失败。4.3 推理与数量统计写一个从图片到统计结果的完整脚本训练完成拿到权重后要做的事是对测试图片做推理并把结果汇总成数量。这里要注意测试集的图片路径可能没有 CSV或者 CSV 只在 datatest.csv 里所以代码要兼容两种输入方式。import torch import torchvision.transforms as T from collections import Counter from PIL import Image import glob # 测试变换不需要增强 test_transform T.Compose([ T.Resize((224, 224)), T.ToTensor(), T.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) # 加载模型和权重 model models.resnet18(weightsNone) model.fc torch.nn.Linear(512, num_classes) model.load_state_dict(torch.load(best_insect_model.pth, map_locationcpu)) model.eval() # 找所有测试图片 test_images sorted(glob.glob(images/test/*.jpg)) class_names [fly, butterfly, bee, beetle, ant] # 按自己的类别名修改 # 逐张预测并统计 counter Counter() with torch.no_grad(): for img_path in test_images: img Image.open(img_path).convert(RGB) inputs test_transform(img).unsqueeze(0) logits model(inputs) pred_idx logits.argmax(dim1).item() counter[class_names[pred_idx]] 1 print(f{img_path} - {class_names[pred_idx]} (conf{torch.softmax(logits, dim1).max().item():.2f})) print(数量统计结果, dict(counter))这段代码有几个必须按需调整的地方model models.resnet18(weightsNone)后面必须接load_state_dict因为如果你在这里用weightsDEFAULT再加载自己的权重文件会遇到预训练权重被覆盖的警告虽然不影响最终结果但会浪费一次预训练权重的下载时间。class_names的顺序必须和训练时 CSV 里标签的编码顺序一致否则预测结果对不上号。如果你不确定顺序可以打印model.class_to_idx但没有的话就需要从 data.csv 里按sorted(set(labels))生成。unsqueeze(0)把单张图变成 batch 维度为 1 的四维张量这一步漏掉会直接报维度错误。Counter在统计时天然对缺失类别返回 0不会抛 KeyError。我一般会在统计完成后写一个汇总表到本地方便答辩时直接贴到论文附录。4.4 评估模型看混淆矩阵找出模型到底错在哪准确率只告诉你好不好不告诉你怎么改进。量化评估需要用 sklearn 生成混淆矩阵并可视化这是答辩时的加分项。from sklearn.metrics import confusion_matrix, classification_report import numpy as np all_preds, all_labels [], [] model.eval() with torch.no_grad(): for images, labels in val_loader: images images.to(device) outputs model(images) _, preds torch.max(outputs, 1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) cm confusion_matrix(all_labels, all_preds) print(混淆矩阵\n, cm) print(classification_report(all_labels, all_preds, target_namesclass_names))classification_report输出的 precision、recall、f1-score 比准确率更能反映模型在每类上的真实表现。如果某一类的 recall 明显偏低说明模型倾向于把它误判成其他类这时候需要在训练时给这个类更高的权重。把混淆矩阵保存成图片答辩时放在 PPT 上比单纯说“准确率 95%”有说服力得多。5. 避坑清单复现这套毕设最常见的五个翻车点这种毕业设计资源我前后复现过几套翻车点高度集中。这里挑五个现象最典型、原因最隐蔽、解决方式最明确的写下来都是血泪经验。5.1 坑CSV 中文路径乱码现象Image.open()报 FileNotFoundError但手动打开路径明明存在。原因CSV 是 UTF-8 编码Excel 打开再保存后变成了 GBK 编码路径里的中文文件名在程序里读出来是乱码。解决统一你代码的编码格式。读取 CSV 时显式指定编码df pd.read_csv(data.csv, encodingutf-8)如果这样还报错大概率文件已经是 GBK改成encodinggbk再试。最稳的做法是用chardet探测文件编码import chardet with open(data.csv, rb) as f: raw f.read() encoding chardet.detect(raw)[encoding] df pd.read_csv(data.csv, encodingencoding)但终极方案是直接把所有图片名和标签改成英文用脚本重命名一次以后再也不碰编码问题。5.2 坑zip 伪加密导致解压失败现象Windows 下用右键“全部解压缩”能打开部分文件但 Linux 下unzip报 “unsupported compression method” 或者“加密文件”。原因不少从网盘下载的 zip 包被二次打包添加了伪加密标志位。右键解压时 Windows 的 Zipfldr 会自动忽略伪加密但 Linux 的 unzip 会把它当成真加密拒绝解压。解决优先用 7-Zip 处理。7-Zip 能识别伪加密解压时加-p参数传入空密码7z x insect.zip -p如果 7-Zip 也解不开试试 Python 的zipfile模块它有时能绕过伪加密import zipfile with zipfile.ZipFile(insect.zip) as zf: zf.extractall(insect_project)还有一种情况是 zip 压缩包损坏导致某个文件缺失。unzip -t insect.zip可以测试压缩包完整性如果输出里有 “missing entry” 之类的报错就该重新下载资源了。5.3 坑预训练权重下载超时训练卡死在下载阶段现象代码跑到models.resnet18(weightsmodels.ResNet18_Weights.DEFAULT)时长时间卡住最后报 timeout。原因torchvision 的权重托管在 download.pytorch.org国内网络访问不稳定几 MB 的权重文件经常断流。解决手动下载权重文件放到本地目录再通过torch.hub.load_state_dict_from_url加载时指定model_dirtorch.hub.set_dir(./pretrained_weights) model models.resnet18(weightsNone) state_dict torch.hub.load_state_dict_from_url( https://download.pytorch.org/models/resnet18-f37072fd.pth, model_dir./pretrained_weights ) model.load_state_dict(state_dict)或者更干脆让项目支持无预训练训练。对于数据量超过 2 万张的昆虫图集不加载预训练权重也能收敛到不错的效果只是训练时间翻几倍。毕设场景里我不建议等太久预训练是值得的。5.4 坑数量统计时重复统计或漏统计现象测试集总共 50 张图统计完数量之和是 62明显不对。原因glob.glob(images/test/*.jpg)把重复图片路径扫进来了或者 CSV 里的测试图片和 glob 到的图片有大量重叠统计时两个来源都算了一遍。解决统计之前先对图片路径去重并且统一数据来源。用 datatest.csv 做输入就不要再用 glob 扫目录反之亦然test_df pd.read_csv(datatest.csv) unique_paths sorted(set(test_df[image_path])) print(f去重后测试样本数{len(unique_paths)})这个 bug 特别隐蔽因为它不报错只是数字偏大答辩时老师随口问一句“你统计的是个体数还是图片数”你如果没注意过数据来源很容易答错。所以统计口径必须写死在代码注释里别靠记性。5.5 坑类别极度不平衡模型把所有样本判成同一类现象训练 loss 正常下降验证准确率高达 90% 以上但看混淆矩阵发现模型只学会了预测数量最多的那一类其他类全是 0。原因data.csv 里某类样本占比超过 80%模型发现全预测成这一类的损失最小不需要学任何特征。解决给 CrossEntropyLoss 传入类别权重让小样本类有更大的惩罚权重weights torch.tensor([1.0, 3.0, 5.0, 2.0, 4.0]) # 按各类样本量的倒数归一化 criterion nn.CrossEntropyLoss(weightweights.to(device))类别的准确计算方式是num_samples / (num_classes * class_counts)比如某个类只有 300 个样本、总数 2400 个、类别数 5 个那这个类的权重就是2400 / (5 * 300) 1.6。写的时候直接按 sample_count 的倒数算省心一些。6. 答辩前的验证技巧用置信度阈值回放让数量统计可解释模型训练完成、数量统计也跑出来了但查不了准确率时的“黑匣子”感就来了。答辩时老师会问“你判断哪张图是哪一类依据是什么”如果你只能回答“模型是这么预测的”这个答案说服力不足。我的做法是给推理脚本加一个置信度阈值回放机制。思路是这样的模型对每张测试图都会输出一个概率分布如果最高置信度低于某个阈值说明模型自己也不太确定。那么设置两个阈值——高分区间和低分区间高分区间直接采信低分区间把图片原图输出到单独文件夹对着文件夹人工核对一遍。import shutil from pathlib import Path confidence_threshold 0.85 # 置信度低于这个值的图片需要人工复核 low_conf_dir Path(low_conf_review) low_conf_dir.mkdir(exist_okTrue) with torch.no_grad(): for img_path in test_images: img Image.open(img_path).convert(RGB) inputs test_transform(img).unsqueeze(0) logits model(inputs) probs torch.softmax(logits, dim1) conf, pred probs.max(dim1) pred_idx pred.item() if conf.item() confidence_threshold: shutil.copy(img_path, low_conf_dir / Path(img_path).name) print(f低置信样本: {img_path}, 预测{class_names[pred_idx]}, conf{conf.item():.3f})这个脚本的价值在于它把模型的不确定性与统计结果绑定在一起。当你汇报“识别准确率 91%”时可以同时说“这 91% 建立在置信度大于 0.85 的基础上低置信样本已经单独挑出人工复核”。这比拿一个不设阈值的总分去描述更诚实也更有工程味道。有了这些低置信样本之后可以做两件事保证数量统计的可靠一是人工核对并修正 label二是在论文里说明这个阈值的选择依据比如对不同置信度区间画一条准确率曲线选在“准确率开始大幅下降前”的位置。这个操作我一般会顺手画个小图几行 matplotlib 代码搞定import matplotlib.pyplot as plt thresholds [0.5, 0.6, 0.7, 0.8, 0.9, 0.95] accs [] # 每次在验证集上计算只统计置信度大于阈值的样本看准确率 for t in thresholds: acc compute_accuracy_with_threshold(val_loader, t) accs.append(acc) plt.plot(thresholds, accs, markero) plt.xlabel(confidence threshold) plt.ylabel(accuracy) plt.savefig(threshold_curve.png, dpi150)从那次之后我每次做昆虫识别相关的毕设辅助都会在推理脚本里强制走一遍置信度回放不再直接信任模型的原始输出。这个习惯救了我好几次——数据标注错误、类别混淆、模型欠拟合都会通过低置信样本暴露出来。希望这一套流程对你也同样管用祝你顺利跑通。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?