简介面向大四毕业设计、课程设计及作业场景这是一套昆虫识别与数目统计的完整项目包适用于计算机视觉、人工智能等相关方向的本科生参考或二次开发。压缩包共164个文件整体大小14.59MB核心内容包含97张昆虫图片构成的图像样本集、23个Python源码文件、13个npy数据文件、10个xml标注文件、3个已训练模型、3个csv统计表以及一个ui交互界面。这些材料覆盖了从图像采集、数据标注、模型训练到数量统计的完整技术链路同时附带说明文档、许可证文件等便于理解工程结构并快速运行验证。读者可以从零复现昆虫识别与计数流程复用模型和脚本完成自己的实验或毕设任务。目前已经有一百四十人学习下载整体上是课设、大作业、毕设场景中较为实用的参考资源。1. 昆虫识别与数目统计毕设这份压缩包能帮你省多少事“昆虫识别”和“数目统计”是图像处理方向课设、大作业和毕业设计里高频出现的一道题给一批昆虫图片训练出能分辨种类并统计数量的程序。这个压缩包里装的是 data.csv、datatest.csv、ques.csv 三个数据集外加一组 fly*.jpg 测试图和 IDEA 工程文件一看就是能直接跑的完整骨架不是网上那种论文配残缺代码的缝合怪。它适合两类人一类是大四学生想拿它快速跑通整套流程再换成自己的数据集做改造另一类是写过一点 Java 图像程序、需要一份带干净数据集的参考工程。下面按我拆项目的顺序写先看数据再做识别再做计数最后是高频翻车点。2. 三个 CSV 数据集先确认训练集、测试集与问题集的分工2.1 表头与前五行拿到压缩包后第一步不是找主函数很多同学拿到毕设压缩包习惯先去找 xxx.java 然后点运行跑起来报错再回头看数据。我的习惯反一下先看 CSV。因为这类项目里识别效果好不好七成由数据决定代码只是把数据里的规律挖出来。压缩包里出现三张表命名本身就是线索data.csv 是训练主体datatest.csv 是拿来做验证的测试集ques.csv 字面意思是“题目集”也就是待预测样本。至于是不是这样需要打印表头确认。import java.nio.file.*; import java.nio.charset.*; import java.util.*; public class CsvProbe { public static void main(String[] args) throws Exception { String[] files {data.csv, datatest.csv, ques.csv}; for (String file : files) { ListString lines Files.readAllLines( Paths.get(file), StandardCharsets.UTF_8); if (lines.isEmpty()) continue; String header lines.get(0).replace(\uFEFF, ); String[] cols header.split(,, -1); System.out.println(file 列数 cols.length); System.out.println(表头: String.join( | , cols)); if (lines.size() 1) { String firstRow lines.get(1).trim(); System.out.println(首行: firstRow); } System.out.println(---); } } }去掉 BOM 是跟着第一个坑走的Excel 导出的 CSV 常见带\uFEFF不处理的话第一列列名会粘上一个隐形字符后面按列名取值全部取不到。我一般用StandardCharsets.UTF_8先读如果首行还是乱码就把编码换成Charset.forName(GBK)再试Windows 老工程十有八九是 GBK。读完先别急着写算法把“列数、表头、首行”打印出来贴到文本编辑器里看三张表字段是否对齐。常见字段形态有两类一类是纯特征表列是 id、feature1…featureN、label另一类是带文件名的表列是 id、filename、label特征要自己从图片里提。这两种工程结构完全不同判断错方向后面全是无用功。2.2 三张表的关系怎么验证行数、样本 ID 与图片名对齐仅仅看表头还不够还需要验证三张表的样本是否来自同一分布。我的做法是数行数、看第一列 ID 或文件名做一个简单的对照。如果 data.csv 有一千行而 datatest.csv 只有两百行通常说明后者是从前者按比例抽出来的留出集如果两表行数非常接近前面若干列是特征、最后一列是种类编号那这个资源的定位就是“特征分类 数目统计”不是目标检测框回归。判断依据是 label 列的值域种类用 0/1/2 这种整数是分类任务如果是 x、y、w、h 四列连续值才是检测任务那么 fly*.jpg 就变成定位验证图后面的思路全要调整。我处理这类毕设包的固定动作是做一个 15 分钟的“冒烟测试”统计每类样本数、检查是否存在全零特征列、看看 label 分布是否极度倾斜。这三个检查能提前暴露两个问题数据不均衡导致准确率虚高或者个别特征列全是同一个值导致归一化除零。用 Java 写一个简单统计循环就行不用引入 pandas。如果你的机器上装了 Python用 pandas 的df.describe()看数值分布会更直观但项目主体是 Java 工程我建议保留在 Java 环境里做答辩现场演示能用同一个工程展示不用在两种语言之间横跳。提示三张表字段一致时优先把 datatest.csv 当作留出集对待不要动它所有调参都只基于 data.csv。2.3 标签与图片对应关系识别对象是几类、图片够不够标签列是整个项目的 Ground Truth它直接决定识别算法选型。压缩包里 fly2.jpg、fly11.jpg、fly15.jpg、fly16.jpg、fly17.jpg 这一组测试图说明图片是按拍摄样本来的命名不连续是因为采集时会剔除模糊帧。一个常见误用是把所有图片全部塞进训练数据留出集就失去意义了。毕设和课设答辩最常问的就是“你的测试集从哪来”所以必须保留 datatest.csv 对应的图片不参与训练其他图片才进训练集。文件命名是否和 CSV 里的文件名列一致这一步也要核对清楚否则后面图像读取阶段全在抛 FileNotFound。第 3 章会从图像侧讲怎么把图片和标签接上。另外如果 label 只有两类比如“有虫/无虫”识别部分就是一个二分类问题KNN、逻辑回归都够用如果分类数到五类以上就要重点关注特征的可分性必要时先做一个 PCA 降维可视化看看类簇有没有重叠。3. 识别流程搭建特征提取与分类不硬上深度模型3.1 为什么这类毕设资源通常走传统特征路线看到图片 CSV 的组合很多刚入行的同学第一反应是“上 CNN”这个判断在真实压缩包里经常是错的。原因很简单压缩包里没有模型权重文件数据体量也撑不起端到端训练。data.csv 通常只有几百到几千行特征fly*.jpg 也只有几十张这个体量根本不满足深度模型的最低要求硬上只能得到过拟合。毕设场景下颜色直方图、灰度和几何矩这类传统特征配合 KNN 或朴素贝叶斯是成本最低、答辩最好解释的方案。它跑得快CPU 就能出结果而且每一类特征的物理意义都能说清楚对“识别 数目统计”这个题目正好呼应。3.2 灰度化与二值化先让目标从背景里浮出来识别之前要做图像预处理。这里的核心问题是虫子颜色和背景叶片的对比关系。常见做法是把彩色图转灰度再二值化阈值可以用固定值或 Otsu。如果拍图环境是白色背景板固定阈值 127 就够用如果是在田间叶片上拍的就需要 Otsu 自动阈值或者 HSV 通道分离。我给一个带注释的 Java 实现用了可变阈值参数方便后续微调。public static BufferedImage toBinary(BufferedImage src, int threshold) { int w src.getWidth(), h src.getHeight(); BufferedImage dst new BufferedImage(w, h, BufferedImage.TYPE_BYTE_BINARY); for (int y 0; y h; y) { for (int x 0; x w; x) { int rgb src.getRGB(x, y); int red (rgb 16) 0xFF; int green (rgb 8) 0xFF; int blue rgb 0xFF; int gray (red green blue) / 3; dst.setRGB(x, y, gray threshold ? 0xFFFFFF : 0x000000); } } return dst; }平均灰度实现简单但有个弱点绿色背景和褐色虫子在某些光照下灰度几乎一样阈值怎么选都分不开。我遇到这种图会改用加权灰度gray 0.299 * red 0.587 * green 0.114 * blue人眼对绿色敏感这个系数能拉开类间差距。threshold 参数不需要一次到位第 6 章的参数网格搜索会告诉你怎么选。二值化的目标只是把“可能是虫子的像素”标出来不必保证像素级精度欠分割可以在连通域阶段处理过分割的问题留到第 4 章。3.3 特征提取面积、长宽比、颜色矩与直方图拼接二值化图出来后目标已经从背景分离下一步是统计特征。统计连通域的面积和周长是计数的基础而颜色特征要从原图的彩色通道里提取。我对这类项目的固定特征组是面积、外接矩形长宽比、RGB 三通道颜色均值与方差、灰度直方图。直方图 bin 数设成 16 起步太小类间差距不够太大会让特征维度爆炸影响 KNN 的速度。这些特征拼成一个向量后要归一化否则面积动辄几千、颜色方差只有几十距离计算会被面积一维主导。做 Min-Max 归一化比 Z-score 更适合这种场景因为特征分布未知且可能有零方差列Z-score 会在零方差列上除零。public static double[] extractFeature(BufferedImage colorImg, BufferedImage binImg) { double[] res new double[24]; // 3均值 3方差 16直方图 2几何 double area countForegroundPixels(binImg); Rectangle box boundingBox(binImg); double aspect box.getWidth() * 1.0 / (box.getHeight() 1e-6); double[] mean colorMean(colorImg); double[] var colorVar(colorImg, mean); double[] hist grayHistogram(colorImg, 16); System.arraycopy(mean, 0, res, 0, 3); System.arraycopy(var, 0, res, 3, 3); System.arraycopy(hist, 0, res, 6, 16); res[22] area; res[23] aspect; return minMaxNormalize(res); }代码里几个辅助方法都是常规实现countForegroundPixels 统计二值图前景像素总数boundingBox 返回最小外接矩形colorMean 和 colorVar 分别算 RGB 三通道均值与方差grayHistogram 输出 bin 数为 16 的灰度直方图。minMaxNormalize 的 min 和 max 必须提前从训练集计算并缓存测试集复用同一套参数绝不能单独对测试集再算一次。很多项目准确率虚高根源就在归一化参数不统一。3.4 分类器KNN 为什么是这类题目的默认选择特征齐了分类器就简单了。KNN 的 K 取 3 或 5距离度量用欧氏距离。为什么不用 SVM 或随机森林因为毕设要求解释模型KNN 就是“看离它最近的 k 个样本属于哪一类”一句话讲完而且在特征维度几十维、样本量几千的情况下KNN 的准确率和随机森林差不了多少。如果答辩老师追问你再解释为什么选 KNN数据量小、维度低、便于可视化、调参只有 k 一个。k 的候选值 1、3、5、7用 datatest.csv 跑一遍就能定。如果某个类别样本少于 5 个k 设 5 会导致多数类霸榜这时 k 改 3 或者加类别权重。4. 数目统计实现连通域标记与去重计数4.1 识别正确不等于数得准识别是给每个样本打标签计数的难点至少在三个地方同一只虫子横跨多个连通域、连续帧重复统计同一只虫子、树叶间隙造成的干扰噪声。翅脉细的虫子在二值化后翅膀位置可能断开一只飞蛾就裂成两个连通域计数直接翻倍浅色虫子在浅背景上只框住半个身体又容易漏。数目统计这一层要解决的是把“像素连通域”合并成“目标实例”。4.2 两遍扫描连通域标记实现// binary[y][x]1 视为前景像素返回连通域个数 public static int countComponents(int[][] binary, int w, int h) { int[] parent new int[w * h]; for (int i 0; i parent.length; i) parent[i] i; int[][] labels new int[h][w]; int nextLabel 1; int[][] dirs {{-1, 0}, {0, -1}}; // 只检查左邻和上邻 for (int y 0; y h; y) { for (int x 0; x w; x) { if (binary[y][x] 0) continue; int left x 0 ? labels[y][x - 1] : 0; int top y 0 ? labels[y - 1][x] : 0; if (left 0 top 0) { labels[y][x] nextLabel; } else if (left ! 0 top 0) { labels[y][x] left; } else if (left 0 top ! 0) { labels[y][x] top; } else { int a find(parent, left); int b find(parent, top); labels[y][x] Math.min(a, b); parent[a] labels[y][x]; parent[b] labels[y][x]; } } } SetInteger roots new HashSet(); for (int y 0; y h; y) for (int x 0; x w; x) if (labels[y][x] ! 0) roots.add(find(parent, labels[y][x])); return roots.size(); } private static int find(int[] p, int x) { while (p[x] ! x) { p[x] p[p[x]]; x p[x]; } return x; }这是两遍扫描加并查集的经典实现。当当前像素的左上两个邻居属于不同连通域时说明当前位置把两块区域连起来了要做等价合并否则计数时同一块区域会出现两个编号。最后通过find找出所有根标签roots.size()就是前景目标个数。如果你更习惯用 OpenCVconnectedComponentsWithStats一行就能拿到标签和面积但毕设代码里手写一遍标记算法反而加分因为教材里刚好有这一章答辩可以直接对着推导过程讲。4.3 面积阈值与形态学修正把噪声从计数结果里剔除连通域标记完噪声一般以“面积很小”和“长宽比极端”两种形态出现。灰尘噪声面积通常少于 20 像素而真实虫子在图像里一般超过 100 像素。参数表如下参数含义常见取值范围调小了会怎样调大了会怎样minArea低于此面积的连通域视为噪声50200按分辨率调整灰尘被计入数量小虫被漏掉maxAspect外接矩形长宽比上限35细长噪声漏进计数真实细长昆虫被过滤closeRadius形态学闭运算结构元半径25 像素断裂翅膀无法合并两只邻近虫子连成一体这三个参数里 minArea 最关键。如果一张图分辨率是 640×480虫子约占 200 像素minArea 取 60 左右比较稳放大到 1920×1080 后面积整体放大五倍以上minArea 就要抬到 300。固定阈值不调参的结果就是小图上噪声一堆大图上目标全丢。闭运算的 closeRadius 影响更隐蔽它会先膨胀再腐蚀把小于两个像素的缝隙补上对翅膀断裂问题很有效但若半径大于两个相邻个体间距的一半就会把两个虫子融成一个 blob这一步务必在第 6 章验证集调参时一并检查。提示调 minArea 时先连通域面积分布把直方图的低谷作为阈值起点别拍脑袋写 50 或 100。4.4 计数的时间维度多帧去重避免连续帧重复计数如果这个毕设做的是“视频帧序列里的昆虫计数”单帧连通域数量还不够因为同一只虫子会连续出现在十几帧里。能扛得住答辩的简单方案是按轨迹 IoU 做跟踪当前帧的目标框与上一帧某个目标框的重叠面积超过 50%就认为是同一只。每一帧在同一个轨迹 ID 上只累加一次总数等于轨迹数而不是检测框数。没有时间维度的静态图项目用前面 4.2 和 4.3 两节就够了不必为了炫技硬加跟踪模块。5. 避坑与排查IDEA 导入、编码与路径五个高频翻车点5.1 解压后 Module 满屏红叉现象用 IDEA 打开 Insect_Identification.iml 所在目录后源码全部标红JDK 提示找不到整个目录没有 src 标记。原因.iml 文件里保存了分享者本机的 JDK 版本和输出路径换机器后绝对路径失效这个工程又没有 pom.xml 或 build.gradle依赖不会自动恢复。解决把根目录里的*.iml和.idea文件夹先删掉再通过 File → Project Structure → Modules → Add → Import Module 选中项目根目录让 IDEA 以当前机器的 JDK 重新生成模块描述然后在 Project Structure → SDK 里选本机 JDK。一般用 JDK 8 或 11 最稳高版本 JDK 跑老式图像代码容易遇到 AWT 模块问题。之后右键源码目录Mark as Sources Root。5.2 CSV 读出来全是乱码现象表头第一列前面有“锘”字符或者整张表的中文列名变成乱码。原因CSV 的编码不是 UTF-8。Windows 的 Excel 默认导出 ANSI也就是 GBK代码里写 UTF-8 读取自然乱码。带 BOM 的 UTF-8 读出来不会乱码但第一列会带上隐藏字符\uFEFF按列名匹配时会失败。解决先确认压缩包里有没有转码说明文件没有的话把读取编码从 UTF_8 换成 GBK 再试同时保留代码里的去 BOM 逻辑。用第 2 章的 CsvProbe 打印前五行能正常输出中文说明编码对了继续乱码就换编码继续试。即使后面要做特征归一化也要先保证列名能正确匹配否则代码会在读写字段时直接抛异常。5.3 图片 FileNotFound路径与工作目录不一致现象程序一运行就在 fly11.jpg 的读取处抛 FileNotFoundException但文件明明在工程目录里。原因代码里写的是相对路径new File(fly11.jpg)而 IDEA 运行时工作目录默认指向打开工程的顶层如果工程结构里还有一层 src 或其他子目录相对路径就失效了。Windows 路径分隔符用反斜杠也容易在跨平台时出问题。解决在 Run Configuration 里把 Working directory 改成$MODULE_DIR$或者在代码里用全局常量拼路径new File(images/fly11.jpg).getCanonicalFile()。更稳的做法是启动时接受一个-Dimg.dir...参数数据路径和代码路径分离换数据集时不用改代码直接改启动参数。我的习惯是永远不在代码里写死图片文件名至少用一个配置常量。5.4 分类准确率约等于随机猜测现象datatest.csv 上测试准确率只有 0.5 左右和丢硬币一样。原因多数情况不是算法错了是特征没归一化。面积量纲是几千颜色均值只有几十到两百欧氏距离计算时面积一维贡献了 90% 的权重其他维度形同虚设另一种可能是训练集和测试集的归一化参数各自独立计算导致特征空间不对齐。解决训练集计算 min 和 max 并保存成归一化参数测试集复用同一套变换检查每个特征列的方差方差极小的列直接丢弃。改完这两个点准确率通常会有肉眼可见的提升。如果还不行再回头怀疑标签对齐或特征选择不要急着换分类器。5.5 计数把同一只虫子算成两个连通域现象肉眼看到图里只有 3 只虫程序输出 5或者统计数比真实数翻倍。原因最常见是二值化阈值不佳导致虫身内部出现小的暗区连通域断裂其次是 4.3 里的 minArea 太小翅膀旁边的碎屑也被算成独立目标。解决先看分割输出图对二值图做闭运算再标记如果阈值效果不稳定改成 Otsu 自动阈值而不是写死 127最后用面积阈值过滤连通域。计数结果要对齐第 4 章的参数表不能只看最终数字要把中间二值图和标记结果可视化出来对比不然永远不知道是漏了还是多了。6. 验证技巧用 datatest.csv 做留出验证与参数微调6.1 参数网格minArea、归一化与 K 值一起调调参不是一个参数一个参数试那样参数之间会互相干扰。我的做法是把 minArea、灰度直方图 bin 数、K 值三个核心参数做成小网格用 datatest.csv 评估所有组合选准确率最高且计数误差最小的一组。minArea 影响单图计数误差bin 数影响特征可分性K 值影响分类稳定性三者调一次就够了。参数调小会怎样调大会怎样建议起始值minArea灰尘混入计数偏高小目标漏检按面积直方图低谷选bin直方图过细类内抖动大过粗类间差异被抹平16K过拟合单样本噪声边界样本被多数类带偏36.2 固定随机种子与留出集顺序最后把 datatest.csv 当留出集数据切分要固定随机种子Java 里用Random random new Random(42)保证每次跑结果一致。如果 data.csv 和 datatest.csv 字段分布一致可以在 data 上随机抽 20% 与 datatest 合并为验证集如果 datatest 只有特征没有标签就只用 data.csv 内部切分验证。整个流程从表头探查到参数网格一次走完不超过 20 分钟。从那以后我每次拿毕设压缩包都会先去打印表头、数类别数、核对图片和 ID 对应关系再跑这套留出验证。这个顺序至少帮我在三个项目里避免了“答辩现场才发现归一化参数不一致”的尴尬。数据、代码骨架、测试图一次给齐你只需要把参数和特征调成符合自己场景的样子希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?