简介:面向计算机专业毕业设计与课程作业场景,这份源码是一套基于深度学习的目标检测安卓应用完整工程。项目围绕目标检测算法在移动端的落地展开,从数据准备、模型选择与训练,到模型剪枝量化、C++层性能优化,再通过JNI接入Android界面,最终打包成可安装的APK,能帮助开发者理解从算法到产品的完整链路。压缩包内共有1347个文件,整体大小约117.59MB,包括Java源码、XML界面布局、JSON配置文件、Gradle构建脚本、JAR依赖库、DEX字节码、SO原生库以及APK安装包;除源码外还有大量编译后的class与资源文件,适合直接导入工程查看构建产物和运行效果。目前已有113人学习使用,适合准备课程答辩、复现目标检测demo,或希望把YOLO等模型部署到手机上的学生与开发者参考。资源中可以看到完整的目录结构,模型推理、相机预览、结果绘制等模块划分清晰;同时保留了Gradle配置和第三方库,便于快速构建。通过阅读JNI层与SO库的调用方式,还能掌握C++加速推理的关键写法,为后续性能调优提供思路。
1. 基于深度学习的目标检测 app:毕设 zip 拆开之后是一整条流水线
刚把「毕设&课程作业_基于深度学习的目标检测app.zip」拿到手,大多数人期待的是解压、运行、检测框就出来。但我接手过不少这样的包:zip 只是起点,真正要花时间的是把「数据集 → 模型训练 → 权重导出 → app 集成」这条深度学习落地流水线重新跑通。目标检测本身已很成熟,难点不在算法,而在把模型塞进手机或服务端之后还能保持可用。这篇笔记写给两类人:0 基础纯小白,毕设或课程作业选了目标检测 app 这个方向,想用最短路径交差;有检测需求、想快速出一版可演示 demo 的开发者。我会按自己做这类项目的顺序,把选型、数据、训练参数、部署路径和踩坑记录一次讲完。
2. 技术路线先于代码:目标检测选型、数据集准备与标注格式
这类 zip 解压后一般分为三块:训练脚本、数据集(或数据说明)、app 工程。我的建议是别急着跑任何命令,第一步是确认技术路线,再回头碰数据。方向选错,后面每一步都在返工。
2.1 为什么毕设里 YOLO 是默认答案,Transformer 目标检测什么时候才值得上
目标检测的主流路线分两派。两阶段检测器以 Faster R-CNN 为代表,先提候选区域再逐区域分类回归,精度上限高,但速度慢、部署链路长。单阶段检测器以 YOLO 和 SSD 为代表,一次前向同时输出类别和框,速度优势明显。深度学习入门阶段做目标检测项目,YOLO 是事实上的默认答案,骨干网络本质上是 CNN,理由有三条。
第一,训练代码量最小。用 ultralytics 封装好的 yolo 命令,从数据到训练结果只需要一个 data.yaml 加一条命令,不需要从零写网络结构,这对课程作业和毕设来说是决定性的。第二,部署生态最完整。目标检测 app 的难点在移动端和嵌入式端,YOLO 的官方导出链路覆盖 ONNX、TensorFlow Lite、CoreML、NCNN 等主流格式,每个平台都有现成示例可抄。第三,资料密度大。遇到报错,把「yolo + 报错关键字」丢进搜索基本都能找到答案,对赶 deadline 的人来说比什么都重要。
那 Transformer 目标检测(DETR、RT-DETR)什么时候值得上?我的判断是:当你的检测目标里小物体占比高,或者想用「去掉 anchors 的端到端检测」做论文创新点时,才有必要考虑。RT-DETR 的速度已经能对标 YOLO,但它在手机端的 TFLite 算子支持和工程资料远不如 YOLO 成熟,一旦导出环节出问题,排查半径会大很多。答辩时导师问「为什么不用 Transformer」,用「迁移成本高 + 移动端算子支持不完整」回应是站得住脚的。
还有两个常被拿出来说事的方向:三维目标检测和开放词汇目标检测。前者需要点云或多视角输入,app 端几乎无法落地;后者能识别训练集之外的新类别,但要大模型做底座,推理延迟和安装包体积都不适合毕设 demo。这些名词适合放进论文的「后续工作与展望」,不适合放进主线。毕设项目的本质是在受限资源下把闭环跑通,选最成熟的路才能保住核心评分项。
2.2 数据集从哪来:公开数据集、自制标注与目录结构
训练一个能用的检测模型,数据比模型重要。我见过太多项目死在只有 200 张图上——模型怎么调都过拟合,换场景就漏检。数据来源有三条路。
第一条,直接用公开数据集。通用场景用 COCO 或 VOC 的子集;垂直场景找专门的数据集。比如做鸟类目标检测,有整理好的鸟类数据集,包含常见鸟类的框标注;做智慧交通方向的课程作业,也有现成的车辆、行人数据集可用,这种数据集拿来就能切分训练。用公开数据的优势是标注现成,劣势是答辩容易被问「创新点在哪」,所以需要靠后续微调和应用场景来补。
第二条,自己采图自己标。手机拍摄、网络下载、视频抽帧都行,但数量要够。我的经验是每类目标至少 800 到 1500 张才算稳,少于 500 张训练出来的模型换到 app 真实环境基本会漏检。标注工具常用 LabelImg 或 X-AnyLabeling,前者轻量,后者支持半自动标注和模型辅助预标注,对 0 基础纯小白更友好。
第三条,公开数据加自采数据混合。这也是我给毕设学生推荐最多的方案:比如做「教室场景目标检测 app」,先用 COCO 里的人、书、椅子做底座模型,再补 300 张自己学校教室照片做微调。这样既有可说的数据工作量,模型在真实场景的鲁棒性又比纯公开数据好得多。
无论数据来自哪,目录结构必须统一。YOLO 系列的标准布局长这样:
datasets/ classroom/ images/ train/ val/ labels/ train/ val/ data.yamldata.yaml 是 ultralytics 定位数据和类别的关键:
path: D:/projects/datasets/classroom train: images/train val: images/val names: 0: person 1: book 2: chair最容易翻车的地方是 path 写成绝对路径。换机器后路径失效,训练直接报数据集不存在;写相对路径又依赖执行时的工作目录。我的习惯是:写完 data.yaml 后先跑一次yolo checks,确认 torch、ultralytics 版本和 CUDA 可用性;然后正式训练前先看日志里的图片数量统计,如果显示 0 images,马上查 path 和 images/labels 目录名是否匹配,别等训练到一半才报错。
2.3 标注格式转换:VOC/COCO 转 YOLO 的最小脚本与四个边界坑
公开数据集大多给 VOC(xml)或 COCO(json)标注,YOLO 要的是归一化 txt。转换脚本是必写的一环,我提供一个 VOC 转 YOLO 的最小版本:
import xml.etree.ElementTree as ET import os from glob import glob def convert_voc_to_yolo(xml_path, out_dir, class_names): tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") w = int(size.find("width").text) h = int(size.find("height").text) if w == 0 or h == 0: return # 跳过损坏标注 image_name = os.path.splitext(os.path.basename(xml_path))[0] txt_path = os.path.join(out_dir, image_name + ".txt") with open(txt_path, "w") as f: for obj in root.iter("object"): cls = obj.find("name").text if cls not in class_names: continue box = obj.find("bndbox") xmin = float(box.find("xmin").text) ymin = float(box.find("ymin").text) xmax = float(box.find("xmax").text) ymax = float(box.find("ymax").text) x_center = (xmin + xmax) / 2.0 / w y_center = (ymin + ymax) / 2.0 / h box_w = (xmax - xmin) / w box_h = (ymax - ymin) / h f.write(f"{class_names.index(cls)} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}\n") class_names = ["person", "book", "chair"] xml_files = glob("VOC2007/Annotations/*.xml") for xml_file in xml_files: convert_voc_to_yolo(xml_file, "labels_train", class_names)逻辑说明:脚本把每个 VOC xml 解析成目标对象,将 bndbox 的左上右下坐标换算成 YOLO 需要的中心点加宽高,并统一除以图片宽高做归一化。写文件时每行一个目标,格式是「类别序号 中心x 中心y 宽 高」。xml 里 width 或 height 为 0 的异常标注会直接跳过,避免训练过程中读到损坏样本。
参数说明:class_names 的索引就是类别 id,必须和 data.yaml 里 names 列表的顺序完全一致,否则类别错位——这是训练能跑但预测全错的典型隐性 Bug。另一个注意点是 xml 中遮挡严重、目标占比小于 1% 的框,建议转换时筛掉,不干净的标注会让 loss 曲线一直高位抖动。
四个边界坑,对照自查:
- 坑一:归一化必须用 xml 里 size 的 w/h,不能用图片的实际尺寸。公开数据集里 size 字段偶尔和真实图像尺寸不一致,用错会导致所有框整体偏移。
- 坑二:COCO json 转 YOLO 时,COCO 坐标是左上角加宽高,直接套 VOC 的 xmin 加 xmax 公式会得到负宽度,需要先做坐标换算。
- 坑三:类别集合要对齐。从 COCO 里只取 3 类时,其余类别的标注文件不删干净,训练时模型一直在学一个不存在的「第 4 类」。
- 坑四:转换完随机抽 20 张图,把 txt 画回原图检查。坐标算错一位,训练出来 mAP 掉 10 个点以上,这一个动作能救回大量时间。
提示:转换脚本建议固定一个版本存进项目根目录,并写清源数据从哪里下载、类别清单是什么。答辩时老师大概率会问数据怎么来的,这份记录就是你回答的依据。
3. 训练一个能用的检测模型:环境配置、最小训练命令与参数取舍
模型是整个 app 的核心,但训练环节往往是毕设翻车率最高的地方。原因不是算法难,是环境。这一章从环境讲到训练命令,再讲怎么从训练日志判断模型能不能上 app。
3.1 ultralytics 环境配置:给 0 基础纯小白的安装顺序
现在的目标检测训练基本都用 ultralytics,它把 YOLO 系列的训练、验证、导出都封装成了 yolo 命令。安装本身一句话:
pip install ultralytics难的是 CUDA 版本。显卡驱动、CUDA Toolkit、PyTorch 三者不匹配,是出现频率最高的环境问题。我的建议是:先别管系统里 CUDA 装的是多少,直接用 PyTorch 官方预编译包安装,再装 ultralytics:
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 pip install ultralytics逻辑说明:torch 是深度学习的计算框架,ultralytics 依赖它做 GPU 训练;cu121 表示 CUDA 12.1 的预编译版本。显卡驱动支持新版本时向后兼容,驱动太旧才会报 CUDA driver version is insufficient。如果你根本不打算用 GPU,直接pip install torch torchvision装 CPU 版也行,小数据集训练慢一点但能跑通。
装完必须验证:
python -c "import torch; print(torch.cuda.is_available())"输出 True 再继续。输出 False 就先nvidia-smi看驱动版本,再决定升驱动还是降 torch。实践层面深度学习用的编程语言就是 Python,就算你让 codex 这类 AI 工具帮你写训练代码,环境配置这一步也只有自己能确认,AI 救不了驱动不匹配。
3.2 最小训练命令与 5 个必调参数
环境就绪后,最短的训练命令是这样:
yolo detect train data=classroom.yaml model=yolov8n.pt epochs=50 imgsz=640 batch=16参数说明:data 指向 2.2 节的 data.yaml;model 用预训练权重做迁移学习,yolov8n 是 YOLOv8 最小的骨干网络,想提精度就换 yolov8s 或 yolov8m;epochs 是完整跑一遍训练集的轮数;imgsz 是输入分辨率;batch 是每批图片数。加上device=0强制用第一块 GPU,不加则自动选择 CPU 或 GPU。
我实际调参时,优先级最高的是下面几个:
| 参数 | 建议值 | 说明 |
|---|---|---|
| imgsz | 640 | 训练和部署必须一致,训练 640 转手部署 416,mAP 直接打折 |
| batch | 显存允许下尽量大 | 太小会让 BN 统计波动大、loss 抖动;太大容易 OOM |
| epochs | 50~100 | 不是越大越好,以 val mAP 收敛为准 |
| patience | 10~20 | 连续 N 轮 mAP 不涨就早停,能省一半时间 |
| workers | Windows 用 0,Linux 用 4~8 | Windows 下 workers 大于 0 偶发 DataLoader 崩溃 |
Windows 上训练还有一个高频坑:workers设成默认值 8 时,训练可能卡在第一步不动,或者中途报 DataLoader worker 崩溃。Windows 下建议直接workers=0,慢一点但省心;Linux 服务器再调高。训练输出在runs/detect/train目录下,best.pt 和 last.pt 分别是验证集最优和最后一轮的权重。
3.3 训练日志怎么读:result.csv、混淆矩阵与特征图热力图
训练不是启动就没事了。我的习惯是每 10 个 epoch 看一次 mAP,训练结束后再完整看一轮曲线。runs/detect/train/results.csv每一行是一个 epoch 的全部指标,用 pandas 三行代码画图:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("runs/detect/train/results.csv") plt.plot(df["epoch"], df["metrics/mAP50(B)"], label="mAP50") plt.plot(df["epoch"], df["train/box_loss"], label="box_loss") plt.legend() plt.savefig("training_curve.png")逻辑说明:mAP50 是验证集上 IoU 阈值 0.5 的平均精度,它比 loss 更直接地回答「模型能不能用」。train loss 一直降但 mAP50 不动,优先怀疑过拟合或标注污染;loss 断崖式下降后又反弹,可能是有坏样本,也可能是学习率偏大。小数据集做迁移学习时,如果发现一直在震荡,可以把学习率调小一个量级再训。
runs/detect/train/confusion_matrix.png里能看到成对误检:模型最容易把哪两类搞混,比如把书误检成笔记本。答辩时这张图可以直接引出「改进方向」,比空谈效果好得多。
特征图和热力图是另一个加分项。yolo 预测时加 visualize 参数,可以导出网络中间层的特征图:
yolo predict model=runs/detect/train/weights/best.pt source=test.jpg visualize=True project=runs/visualize参数说明:visualize=True 会把各层特征图落盘;要在原图上叠加高亮热力图,通常要配合 Grad-CAM 类工具,把最后一层卷积的梯度反传回来。这一步能向导师证明模型是在「看」目标区域,而不是靠背景纹理蒙答案——现场打开热力图,比任何指标都有说服力。
4. 把模型塞进 app:两条落地路径与模型导出命令
训练告一段落,zip 里那个「app」才算真正开始。目标检测 app 的主流落地路径有两条:端侧部署和云端部署。先选路径再导出模型,因为格式和量化方式完全取决于路径。
4.1 路径一:安卓端 TensorFlow Lite 部署
端侧部署适合「离线、实时」的需求。以 Android 为例,常见做法是把 YOLO 导出成 TFLite,用 CameraX 获取预览帧,送进 TFLite 解释器推理,最后在后处理里做 NMS 去重。模型导出命令:
yolo export model=runs/detect/train/weights/best.pt format=tflite int8导出后得到best_int8.tflite,放进 Android 工程的 assets 目录。核心推理代码(精简版)长这样:
// 加载模型 Interpreter tflite = new Interpreter(loadModelFile(context, "best_int8.tflite")); // 预处理:缩放到 640x640,保持长宽比并归一化 Bitmap resized = letterbox(bitmap, 640, 640); ByteBuffer input = convertBitmapToByteBuffer(resized); // 1x640x640x3 // 推理 float[][] output = new float[1][84 * 8400]; tflite.run(input, output); // 后处理:解析 8400 个候选框,用 NMS 去掉重叠框 List<Detection> detections = postProcess(output, 0.25f, 0.45f);逻辑说明:输出的 84×8400 中,84 等于 80 个 COCO 类别加 4 个坐标(如果模型带 objectness 分支还要加 1);8400 是三种尺度特征图生成的 anchor 候选总数。如果你换成了自建数据集且类别数不是 80,84 必须同步改成 categories 加 4,再加 1(有 objectness 时)。后处理里 0.25f 是置信度阈值,0.45f 是 NMS 的 IoU 阈值。
关键细节:用 CameraX 逐帧推理时,每一帧都做 Bitmap 缩放和 ByteBuffer 拷贝,内存抖动很厉害。我一般会把预览分辨率缩到 640 之内,并显式设置推理线程数:
tflite.setNumThreads(2);如果你是 0 基础纯小白,建议先把官方 demo app 跑通,再往里换自己的模型。摄像头预览、后处理、UI 三个黑匣子同时调,出了问题根本不知道怪谁。一步步来反而最快。
4.2 路径二:Python 后端 + 小程序/Web 前端
第二种路径是把模型放在服务端,app 只负责传图和回显。它对手机硬件没要求,调试直观,毕设现场演示也不容易因为手机性能翻车。常见做法是 FastAPI 起一个 HTTP 服务,前端把图片传上来,服务端返回检测框列表:
from fastapi import FastAPI, UploadFile from PIL import Image import numpy as np from ultralytics import YOLO app = FastAPI() model = YOLO("best.pt") # 进程启动时加载一次,别放到请求里 @app.post("/detect") async def detect(file: UploadFile): image = Image.open(file.file).convert("RGB") results = model(np.array(image), conf=0.25, iou=0.45) detections = [] for r in results: for box in r.boxes: detections.append({ "class": r.names[int(box.cls)], "conf": float(box.conf), "bbox": [float(x) for x in box.xyxy[0]] }) return {"detections": detections}逻辑说明:模型在 import 时加载一次放在全局,避免每个请求重新加载权重——这是服务端部署最容易犯的低级错误,会导致首帧延迟几秒甚至内存暴涨。Pillow 打开上传图后转 numpy 数组,是为了直接喂给 ultralytics 的推理接口。
启动命令:
uvicorn main:app --host 0.0.0.0 --port 8000前端如果赶时间,不必上原生 App。微信小程序里用 wx.request 把图片 base64 放进请求体;更快的方案是写一个 HTML 页面,用<input type="file">选图,JavaScript fetch 调接口,把检测框用 canvas 画出来,十分钟就是一个能演示的 web 版目标检测 app。
这条路径的坑在并发:uvicorn 默认单进程,多人同时传图会排队。毕设演示只有一个人用没问题;如果老师要求多人同时演示,加--workers 2,并确认模型推理线程安全。
4.3 模型导出与量化:pt → ONNX → TFLite 的完整链路
不管走哪条路径,导出都是一道必过关卡。完整命令如下:
# 先转 ONNX,确认计算图能被标准框架接受 yolo export model=runs/detect/train/weights/best.pt format=onnx opset=12 # 再导 TFLite,int8 开启量化,体积可压缩 3~4 倍 yolo export model=runs/detect/train/weights/best.pt format=tflite int8逻辑说明:第一行导 ONNX 是为了让错误暴露在标准框架里;第二行导出 TFLite 时 ultralytics 内部也会走 ONNX 转换,所以前面成功是排错的前置条件。int8 量化用 8 位整数替换浮点权重,模型变小变快,但精度会损失,小目标受影响尤其大。
导出后必做的验证:同一张图分别用 pt 和 tflite 预测,对比检测框。
yolo predict model=runs/detect/train/weights/best.pt source=test.jpg yolo predict model=best_int8.tflite source=test.jpg如果 tflite 的框明显偏,第一嫌疑是预处理不一致:归一化方式、letterbox 填充、输入尺寸,训练和部署任何一项不一致都会让框偏移;第二嫌疑是量化后小目标特征丢失,这时去掉 int8 改 fp16,或者换回更大的输入尺寸。
提示:永远保留一份 best.pt。导出是有损操作,pt 就是后悔药,tflite、onnx 是速效药。导坏了随时能从 pt 重来,别在导出参数上赌一次成功。
5. 目标检测 app 常见问题与排查:5 条可照抄的踩坑记录
这一章按「现象 → 原因 → 解决」写,出现频率从高到低排。这 5 条是我做同类项目时反复遇到的,每一条都对应一次真实返工。
5.1 解压 zip 后环境装不起来,torch 装上也不能用 GPU
现象:按 README 里pip install -r requirements.txt装完,import torch 就报错,或者 torch.cuda.is_available() 一直是 False。
原因:README 大多写于作者自己的机器,requirements.txt 里没锁 torch 版本,默认源装上的是 CPU 版 torch;或者 GPU 驱动太旧,和最新 torch 的 CUDA 运行时要求不匹配。
解决:先nvidia-smi看驱动版本,再用 PyTorch 官方 whl 源重装对应 CUDA 版本的 torch,最后装 ultralytics。排查时可以执行python -c "import torch; print(torch.version.cuda)",如果 cuda 是 None,说明装的就是 CPU 版。机器上没有 NVIDIA 显卡就直接用 CPU 版,小数据集训练照样能出结果,只是别开太大 batch。排查顺序永远是驱动 → torch → ultralytics → 项目代码,跳步只会浪费时间。
5.2 训练时 loss 在降,mAP 却几乎不涨
现象:训练曲线里 box_loss 持续下行,但 metrics/mAP50 在 0.1~0.3 之间徘徊不动。
原因:最常见的是标注类别错位或标注框不干净;其次是数据集太小,模型把背景纹理当成特征学到了,验证集一换场景就露馅。
解决:先按 2.3 节的建议随机抽图看标注,确认框是否贴合目标。标注没问题就看类别文件:从公开数据集里只取 3 类时,没删干净的其他类别标注会让模型一直学一个「幽灵类」。最后再上数据增强,在 data.yaml 里调 hsv_h、flipud、scale,小数据集尤其见效。如果用的是公开预训练权重,还可以把前 10 层冻结住做微调,让模型先拟合新数据的目标分布,再放开全网络训练。
5.3 app 里预测全为空,或框全部偏到图外
现象:同一张图,Python 里用 best.pt 预测正常,换到 app 里要么全部无结果,要么框的位置完全错乱。
原因:预处理不一致。app 端直接 Resize 拉伸了长宽比,没做 letterbox;或者归一化时忘了除以 255;又或者 tflite 内部已经带了归一化,app 端又除了一次 255。通道顺序也可能出问题——cv2 读图是 BGR,而 Android Bitmap 是 RGB,训练用 cv2、部署用 Bitmap,通道顺序错位会得到非常诡异的结果。
解决:把预处理写成一个严格函数:按模型 imgsz 做 letterbox,短边补 114 灰度值,再做归一化。同时确认导出的 tflite 是否内置归一化——ultralytics 的 tflite 导出默认带归一化,这时候 app 端就不要手动除 255。通道顺序统一成 RGB 或 BGR 并在代码注释里写明。这个环节是端侧部署翻车率最高的点,没有之一。
5.4 小目标完全检测不到,手机拍远景尤其严重
现象:近处的大目标检测正常,远处的小物体(鸟、交通标志、行人)几乎全漏。
原因:小目标在深层特征图里只剩几个像素,多轮下采样后信息几乎消失;int8 量化又对浅层特征的数值精度有额外损耗,两个因素叠加,小目标就彻底没了。
解决:一是训练 imgsz 从 640 提到 800 或 1024,小目标获得的像素数变多;二是换更大的骨干网络,比如 yolov8m 或 yolov8l,特征表达能力更强;三是在数据层面做小目标过采样,把包含小目标的图片多复制几份进训练集,或者把这类图片切块放大再训练。另外检查一下 app 后处理的置信度阈值——小目标本身置信度低,把 conf 从 0.25 降到 0.15 往往能找回一部分框,但误检会增多,需要同步调 NMS 阈值。移动小目标检测是目标检测里公认的难点,不要指望一个参数解决,三招一起上才有效。
5.5 导出 tflite 后精度骤降,框偏移超过 10%
现象:pt 模型 mAP 0.7,导出 int8 tflite 后 mAP 掉到 0.4 以下,检测框位置明显偏。
原因:INT8 量化用 256 个离散值近似浮点权重,小模型的冗余少,激活值分布一旦被压缩,检测头的输出直接崩。
解决:先试 fp16 导出,精度损失小很多。如果必须 int8,就用带校准集的量化,让模型统计真实输入的激活值范围——用 data.yaml 里的 val 集做校准即可,200 张左右就够;自己写导出脚本时别忘传 calib 数据。还不行就把模型换成更大的版本再量化,因为大模型参数冗余多,量化损失占比小。为了省 10MB 体积牺牲 30% 精度,在毕设里是不划算的买卖。
6. 答辩前给检测 app 做一次全链路体检
模型能跑、app 能开,不代表项目能交。我每次交付目标检测 app 之前,都会按下面四步做一遍体检,每一条都能直接在答辩时撑起一个「你怎么验证你的系统」的回答。
第一,固定测试集全链路走三遍。挑 50 张没参与训练的真实照片,覆盖正常、暗光、过曝、遮挡、小目标五类场景,依次在训练端、导出端、app 端各跑一遍,统计检测框数量和坐标是否一致。这个测试集要固定放在项目根目录,答辩现场直接跑,比任何截图都有说服力。
第二,测端到端延迟。在 app 里打印从取帧到返回结果的时间。端侧 tflite、640 输入、2 线程时,耗时应该在几十毫秒到 100 毫秒之间;如果超过 200ms,基本是后处理 NMS 写得太慢——常见于 Python 循环遍历 8400 个候选框,改成向量化或 C++ 后处理会立竿见影。把这一条写进论文的性能分析表,比空写「系统实时性好」强得多。
第三,做一次消融对比。用同一数据集分别训练 yolov8n 和 yolov8s,输出一张「模型 / mAP / 单帧耗时」的对比表。表不用大,三行就够,但数据要诚实。答辩老师问「为什么选这个模型」时,这张表就是你全部论据的来源;没有对比的模型选型,在老师眼里就是拍脑袋。
第四,写环境快照。把 torch 版本、CUDA 版本、ultralytics 版本、Python 版本、导出参数全部写进 README,附一条从零安装到复现的命令序列。评阅环境几乎不可能和你的开发机一致,评阅老师能在新机器上照着重跑出结果,项目的工程性评分基本就稳了。
说一个我自己的教训:有一次帮人排查 app 闪退,折腾两天才发现是 tflite 模型文件被放进了 res 目录而不是 assets,构建时被压缩导致 Interpreter 读取失败。从那以后我养成一个习惯:交付前一定在一个全新环境里从零按 README 走一遍,任何没写清的命令都要补全。这些小事才是这类项目真正的分水岭。希望帮到你。
本文还有配套的精品资源,点击获取