毕设YOLO入门实战:从零部署目标检测模型的避坑指南
摘要:许多计算机视觉方向的本科生在毕设中选择YOLO系列模型,却常因环境配置、模型选型或推理部署问题卡壳。本文面向新手,系统梳理YOLOv5/v8的本地训练与ONNX导出流程,对比不同框架(PyTorch/TensorRT/OpenVINO)的部署成本,并提供可复现的轻量级推理代码。读者将掌握端到端的模型落地能力,避免常见依赖冲突与性能陷阱,高效完成毕设核心模块。
1. 毕设里用YOLO,新手最容易踩的五个坑
做毕设时,导师一句“用YOLO检测一下”听起来轻飘飘,真动手才发现全是坑。下面这五个,90%的同学都会遇到:
- CUDA版本与PyTorch版本对不上,训练脚本一跑就报“CUDA capability sm_86 not supported”,回头发现显卡驱动太老。
- 标注文件格式写错,YOLO要求“class x_center y_center width height”全部归一化,结果直接给像素坐标,训练几百轮mAP还是0。
- 数据集划分随意,test集里混入训练图片,答辩时被评委一句“过拟合了吧”直接问懵。
- 推理阶段直接调用
.pt权重,CPU跑一张图3秒,现场演示直接社死。 - 路径写死为
D:\graduation\yolov5\runs\exp6\weights\best.pt,换电脑就找不到文件,PPT翻车了。
提前知道这些坑,就能少走很多弯路。下面从选型开始,一步步带你落地。
2. YOLOv5 vs YOLOv8:毕设场景下该怎么选?
官方仓库迭代飞快,YOLOv5稳定、社区大,YOLOv8精度高、API新。本科毕设更关注“能跑通+好写论文”,对比如下:
| 维度 | YOLOv5 | YOLOv8 |
|---|---|---|
| 上手速度 | 文档多,Issue基本搜得到 | 文档新,示例少,需啃源码 |
| 训练资源 | 6G显存可跑yolov5s | 同尺寸模型略吃显存 |
| 精度(COCO) | 37.4 mAP | 44.9 mAP |
| 导出ONNX | 官方脚本一行搞定 | 官方脚本一行搞定 |
| 下游部署 | 社区提供TensorRT、OpenVINO、ncnn全套示例 | 官方主推Ultralytics-SDK,生态还在补 |
结论:
- 想稳、想快速出图发论文 → YOLOv5
- 想冲高分、肯踩新坑 → YOLOv8
下文以YOLOv5为主线,代码同样适用于YOLOv8,只需把模型名替换即可。
3. 环境准备:30分钟搞定可复现的Python环境
别急着pip install yolov5,先建独立环境,防止系统Python被污染。
# 1. 创建环境 conda create -n yolo python=3.9 conda activate yolo # 2. 安装稳定版本(以v5.0.6为例) pip install torch==1.13.0+cu117 torchvision==0.14.0+cu117 -f https://download.pytorch.org/whl/torch_stable.html git clone -b v5.0.6 https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt验证:
import torch print(torch.cuda.is_available()) # True即OK4. 数据准备:标注→划分→校验一条龙
4.1 标注
推荐开源工具LabelImg,快捷键:
w创建框d下一张- 保存后生成XML?记得改设置直接输出YOLO txt,省得转换。
4.2 目录结构
保持官方格式,后期脚本不用改路径:
dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── dataset.yamldataset.yaml示例:
train: ./images/train val: ./images/val nc: 3 names: ['cat', 'dog', 'person']4.3 划分脚本
很多新人手动复制,比例全靠感觉。下面脚本一次性按9:1划分,并同步复制标签:
import os, random, shutil from sklearn.model_selection import train_test_split img_dir = 'raw_images' label_dir = 'raw_labels' train_img = 'dataset/images/train' val_img = 'dataset/images/val' images = [f for f in os.listdir(img_dir) if f.endswith('.jpg')] train, val = train_test_split(images, test_size=0.1, random_state=42) def copy_set(file_list, set_name): for img in file_list: shutil.copy(os.path.join(img_dir, img), os.path.join(f'dataset/images/{set_name}', img)) lbl = img.replace('.jpg', '.txt') shutil.copy(os.path.join(label_dir, lbl), os.path.join(f'dataset/labels/{set_name}', lbl)) copy_set(train, 'train') copy_set(val, 'val')跑完检查图片与标签数量一致即可。
5. 训练:单卡3060,12分钟完成yolov5s
python train.py --img 640 --batch 32 --epochs 100 \ --data dataset/dataset.yaml --weights yolov5s.pt \ --project runs/train --name exp --exist-ok关键参数说明:
--img输入分辨率,越大越吃显存--batch32能压进6G显存,OOM就降到16--epochs100轮足够收敛,后期想冲点可150
训练完runs/train/exp/weights/best.pt就是最终模型。
6. 导出ONNX:一条命令+三行代码验证
python export.py --weights best.pt --include onnx --opset 12 --simplify生成best.onnx后,用ONNX Runtime跑一张图验证节点无误:
import onnxruntime as ort, cv2, numpy as np img = cv2.imread('test.jpg') blob = cv2.dnn.blobFromImage(img, 1/255.0, (640,640), swapRB=True) sess = ort.InferenceSession('best.onnx') outs = sess.run(None, {sess.get_inputs()[0].name: blob}) print(outs[0].shape) # (1,25200,5+nc)7. Clean Code:封装推理类,毕设答辩不尴尬
把C++、Python、前端都调用同一份逻辑,需要接口干净。下面给出一个最小但完整的YOLOInfer:
# yolo_infer.py import cv2, numpy as np, onnxruntime as ort class YOLOInfer: def __init__(self, onnx_path, conf_thres=0.5, iou_thres=0.45): self.conf_thres = conf_thres self.iou_thres = iou_thres self.session = ort.InferenceSession(onnx_path, providers=['CUDAExecutionProvider', 'CPUExecutionProvider']) self.input_name = self.session.get_inputs()[0].name _, _, self.h, self.w = self.session.get_inputs()[0].shape def _preprocess(self, img): # 等比缩放+灰条填充 scale = min(self.h/img.shape[0], self.w/img.shape[1]) new_w, new_h = int(img.shape[1]*scale), int(img.shape[0]*scale) resized = cv2.resize(img, (new_w, new_h)) top = (self.h - new_h)//2 bottom = self.h - new_h - top left = (self.w - new_w)//2 right = self.w - new_w - left padded = cv2.copyMakeBorder(resized, top, bottom, left, right, cv2.BORDER_CONSTANT, value=(114,114,114)) # HWC→CHW & normalize padded = padded.transpose((2,0,1))[::-1] # BGR→RGB padded = np.ascontiguousarray(padded/255.0, dtype=np.float32) return padded[np.newaxis,:], scale, (left, top) def _postprocess(self, preds, scale, pad): # preds: (1,25200,85) preds = np.squeeze(preds) scores = preds[:, 4] * preds[:, 5:].max(1) # obj*cls keep = scores > self.conf_thres boxes = preds[keep, :4] scores = scores[keep] cls_ids = preds[keep, 5:].argmax(1) # 坐标还原到原图 boxes -= np.tile(pad, 2) boxes /= scale # NMS indices = cv2.dnn.NMSBoxes(boxes, scores, self.conf_thres, self.iou_thres) return boxes[indices], scores[indices], cls_ids[indices] def __call__(self, img): blob, scale, pad = self._preprocess(img) preds = self.session.run(None, {self.input_name: blob})[0] return self._postprocess(preds, scale, pad)调用示例:
infer = YOLOInfer('best.onnx') img = cv2.imread('test.jpg') boxes, scores, cls = infer(img) for b,s,c in zip(boxes, scores, cls): x1,y1,x2,y2 = b.astype(int) cv2.rectangle(img, (x1,y1), (x2,y2), (0,255,0), 2) cv2.imwrite('result.jpg', img)Clean Code要点:
- 单一职责:预处理/推理/后处理分函数
- 不硬编码路径,传参注入
- 支持CPU/GPU自动回退,答辩电脑没显卡也能跑
8. 性能基准:CPU/GPU到底差多少?
测试平台:i7-12700H + RTX3060 Laptop,输入640×640,单张图平均耗时:
| 后端 | 耗时(ms) | 内存占用(MB) |
|---|---|---|
| ONNX Runtime CPU | 260 | 420 |
| ONNX Runtime CUDA | 27 | 850 |
| TensorRT FP16 | 18 | 650 |
| OpenVINO CPU | 95 | 380 |
结论:
- 笔记本CPU也能跑,但260ms对实时演示不友好
- 同一张显卡,TensorRT比ONNX Runtime CUDA再快30%,但转换步骤多,毕设阶段可先用ONNX Runtime CUDA,足够交差
9. 生产环境避坑清单
- 路径硬编码 → 用
pathlib.Path+相对路径,打包zip到任何PC解压即跑 - 模型版本不一致 → 把
best.onnx写进git-lfs,或MD5校验,防止“我电脑明明可以” - 输入预处理偏差 → 训练用RGB,推理却用BGR,mAP掉5个点;统一在代码里注释颜色通道顺序
- batch=1和batch=N混用 → ONNX导出固定维度,动态轴需加
--dynamic,否则Web端并发调用直接崩 - 忘记关OpenCV线程 →
cv2.setNumThreads(0),防止多进程推理时CPU打满
10. 下一步:量化压缩 & Web端部署
毕设答辩完,想继续加分?
- 用ONNX Runtime + quantization把float32模型压成int8,大小减至1/4,CPU提速1.7×,代码只需加两行:
from onnxruntime.quantization import quantize_dynamic quantize_dynamic('best.onnx', 'best_int8.onnx', weight_type=QuantType.QInt8) - 前端用WebAssembly跑ONNX.js,浏览器里实时检测,把演示链接放在简历上,面试加分
写在最后
整篇流程跑下来,你会发现YOLO的“坑”基本都集中在环境+预处理+路径这三步。把训练、导出、推理各封装一次,后续换v8、换TensorRT都是改一行的事。毕设不是搞科研,能稳定复现、快速出图、讲清指标就足够。
量化压缩和Web部署的代码已经给到,剩下的就是动手跑通。祝你答辩顺利,代码一遍过!