YOLO 训练车牌定位模型 + OpenCV C++ 部署完整步骤
YOLO 训练车牌定位模型 + OpenCV C++ 部署完整步骤
一、前期准备(核心工具 / 环境)
- 硬件:GTX 1060 及以上显卡(显存≥6G),避免训练卡顿
- 软件:
- 训练端:Windows/Linux + Python 3.8~3.10 + PyTorch 1.10~2.0 + YOLOv8(ultralytics 库,适配性最优)
- 部署端:Windows + VS2019/2022 + OpenCV 4.5~4.8(需带 DNN 模块,默认编译已包含)
- 数据集:标注好的车牌数据集(格式为 YOLO txt,含 train/val 集,比例 8:2)
二、步骤 1:车牌数据集制作 / 整理(关键:标注精准 + 场景全覆盖)
1. 数据集收集
- 来源:网络公开车牌数据集(如 CCPD、CALTech)+ 实拍图(多角度、逆光、雨雾、遮挡、不同车牌颜色),总量≥1000 张(越多精度越高,建议 2000+)
- 规格:统一缩放至 640×640(适配 YOLO 默认输入,减少训练失真)
2. 标注(用 LabelImg,简单易操作)
- 安装 LabelImg:
pip install labelImg,终端输入labelImg启动 - 标注设置:
- 点击「Change Save Dir」选标注文件保存路径
- 顶部「View」勾选「Auto Save mode」「YOLO」,切换为 YOLO 标注格式
- 标注流程:
- 打开图片文件夹,框选车牌区域,标签名统一为「plate」(仅 1 类,简化模型)
- 标注后自动生成 txt 文件(与图片同名),txt 内容格式:
0 x1 y1 x2 y2(0 为 plate 类别 ID,坐标为归一化后值,无需手动换算)
3. 数据集目录结构(严格按 YOLO 要求)
plaintext
plate_dataset/ ├─ images/ # 所有图片 │ ├─ train/ # 训练集图片(80%) │ └─ val/ # 验证集图片(20%) └─ labels/ # 所有标注txt ├─ train/ # 训练集标注(对应images/train) └─ val/ # 验证集标注(对应images/val) 4. 生成数据集配置文件(plate.yaml)
新建 txt 改名为plate.yaml,内容如下(路径填自己的数据集绝对路径):
yaml
# 数据集路径 path: D:/plate_dataset # 数据集根目录 train: images/train # 训练集图片路径(相对path) val: images/val # 验证集图片路径(相对path) # 类别 names: 0: plate # 仅车牌1类,ID=0 三、步骤 2:YOLOv8 训练车牌定位模型
1. 安装 YOLOv8 库
终端执行:pip install ultralytics(自动适配 PyTorch 环境)
2. 启动训练(单类定位,参数精简高效)
新建train_plate.py,代码如下(直接运行,无需改太多参数):
python
运行
from ultralytics import YOLO # 1. 加载预训练模型(用yolov8n.pt,轻量化,适配C++部署) model = YOLO('yolov8n.pt') # 2. 训练配置(核心参数,按需微调) results = model.train( data='D:/plate_dataset/plate.yaml', # 数据集配置文件路径 epochs=50, # 训练轮数(1000张图50轮足够,多了易过拟合) batch=8, # 批次大小(显卡显存6G设8,8G设16) imgsz=640, # 输入图片尺寸 lr0=0.01, # 初始学习率 classes=[0], # 仅训练第0类(plate) save=True, # 保存模型 device=0, # 用GPU训练(0为GPU编号,CPU填cpu) patience=10 # 10轮精度无提升停止训练,防过拟合 ) 3. 训练完成后取模型
训练结束后,在runs/detect/train/weights/目录下,取best.pt(最优精度模型),后续转 ONNX 格式供 C++ 调用。
四、步骤 3:将 YOLOv8.pt 模型转 ONNX 格式(OpenCV DNN 支持)
YOLO 原生 pt 模型 OpenCV 不兼容,需转 ONNX(通用格式,DNN 模块可直接加载)
1. 模型转换
新建pt2onnx.py,代码如下:
python
运行
from ultralytics import YOLO # 加载最优模型 model = YOLO('runs/detect/train/weights/best.pt') # 转ONNX,简化模型(opset=12适配OpenCV 4.5+) model.export(format='onnx', opset=12, simplify=True) 运行后,在best.pt同目录下生成best.onnx,即部署用模型。
2. 提取类别名(可选,用于显示标签)
新建class_names.txt,仅写 1 行:plate(与训练时标签一致)
五、步骤 4:OpenCV C++ 部署 YOLOv8 ONNX 模型(核心代码,可直接跑)
1. VS 项目配置(关键:关联 OpenCV)
- 新建 VS 控制台项目(空项目,x64 Debug/Release)
- 配置属性(右键项目→属性→配置属性):
- 包含目录:添加 OpenCV 安装目录下
include和include/opencv2(如D:/opencv4.6/build/include) - 库目录:添加 OpenCV
x64/vc16/lib(如D:/opencv4.6/build/x64/vc16/lib) - 链接器→输入→附加依赖项:
- Debug 模式:
opencv_world460d.lib(460 对应 OpenCV 版本,改自己的) - Release 模式:
opencv_world460.lib
- Debug 模式:
- 包含目录:添加 OpenCV 安装目录下
- 复制 OpenCV 动态库到项目 exe 目录:
- 把
opencv/build/x64/vc16/bin下的opencv_world460d.dll(Debug)/opencv_world460.dll(Release),粘贴到 VS 项目x64/Debug或x64/Release目录(与 exe 同路径)
- 把
2. 核心 C++ 代码(完整可运行,含预处理 + 推理 + 后处理)
新建main.cpp,代码如下(注释详细,直接替换模型 / 图片路径即可):
cpp
运行
#include <opencv2/opencv.hpp> #include <opencv2/dnn.hpp> #include <iostream> #include <vector> #include <string> using namespace cv; using namespace cv::dnn; using namespace std; // YOLOv8配置参数(需根据自己模型微调) const float CONF_THRESH = 0.5f; // 置信度阈值(低于0.5过滤) const float NMS_THRESH = 0.4f; // NMS阈值(去重重叠框) const int INPUT_W = 640; // 输入宽 const int INPUT_H = 640; // 输入高 const string MODEL_PATH = "D:/best.onnx"; // ONNX模型路径 const string CLASS_PATH = "D:/class_names.txt"; // 类别名路径 // 读取类别名 vector<string> readClassNames(const string& path) { vector<string> classes; ifstream file(path); string line; while (getline(file, line)) { classes.push_back(line); } return classes; } // YOLOv8推理+后处理(解析结果,画框) void yoloDetect(Mat& img, Net& net, vector<string>& classes) { int img_w = img.cols; int img_h = img.rows; // 1. 图像预处理(归一化+转Blob,YOLOv8输入格式:RGB、0-1归一化) Mat blob; blobFromImage(img, blob, 1.0 / 255.0, Size(INPUT_W, INPUT_H), Scalar(0, 0, 0), true, false); net.setInput(blob); // 2. 模型推理(YOLOv8输出层名固定为"output0") Mat outputs = net.forward("output0"); // outputs尺寸:1×8400×6(8400个预测框,6=xywh+conf+class_id) // 3. 结果解析(过滤低置信度,提取有效框) vector<int> class_ids; // 类别ID vector<float> confs; // 置信度 vector<Rect> boxes; // 检测框(像素坐标) float* data = (float*)outputs.data; int num_boxes = outputs.size[1]; // 8400个预测框 for (int i = 0; i < num_boxes; i++) { float conf = data[4]; // 置信度(当前框是目标的概率) if (conf < CONF_THRESH) continue; // 提取类别ID(取最大概率对应的类别) float max_class_conf = 0; int class_id = 0; for (int j = 5; j < 6; j++) { // 仅1类(plate),j从5到5(6-1) float class_conf = data[j]; if (class_conf > max_class_conf) { max_class_conf = class_conf; class_id = j - 5; // 类别ID=0 } } // 计算检测框像素坐标(YOLO输出为归一化xywh,转像素xyxy) float cx = data[0] * img_w; // 框中心x float cy = data[1] * img_h; // 框中心y float w = data[2] * img_w; // 框宽 float h = data[3] * img_h; // 框高 int x1 = max(0, int(cx - w / 2)); // 框左上角x int y1 = max(0, int(cy - h / 2)); // 框左上角y int x2 = min(img_w - 1, int(cx + w / 2)); // 框右下角x int y2 = min(img_h - 1, int(cy + h / 2)); // 框右下角y // 保存结果 class_ids.push_back(class_id); confs.push_back(conf * max_class_conf); // 最终置信度=目标置信度×类别置信度 boxes.push_back(Rect(x1, y1, x2 - x1, y2 - y1)); // 移动到下一个预测框(每框6个参数) data += 6; } // 4. NMS非极大值抑制(去重重叠框) vector<int> indices; NMSBoxes(boxes, confs, CONF_THRESH, NMS_THRESH, indices); // 5. 画框+显示结果 Scalar color(0, 255, 0); // 绿色框 for (int idx : indices) { Rect box = boxes[idx]; int class_id = class_ids[idx]; float conf = confs[idx]; // 画检测框 rectangle(img, box, color, 2, 8); // 显示标签+置信度 string label = classes[class_id] + ":" + format("%.2f", conf); int label_h = 25; Rect label_rect(box.x, box.y - label_h, box.width, label_h); rectangle(img, label_rect, color, -1); // 标签背景 putText(img, label, Point(box.x + 5, box.y - 5), FONT_HERSHEY_SIMPLEX, 0.6, Scalar(255, 255, 255), 1); } } int main() { // 1. 加载ONNX模型和类别名 Net net = readNetFromONNX(MODEL_PATH); vector<string> classes = readClassNames(CLASS_PATH); if (net.empty() || classes.empty()) { cout << "模型或类别名加载失败!" << endl; return -1; } // 启用GPU加速(有GPU必开,速度提升10倍+) net.setPreferableBackend(DNN_BACKEND_CUDA); net.setPreferableTarget(DNN_TARGET_CUDA); // 2. 读取图片/视频(二选一,按需切换) // 方式1:图片检测 Mat img = imread("D:/test_plate.jpg"); // 测试图片路径 if (img.empty()) { cout << "图片读取失败!" << endl; return -1; } yoloDetect(img, net, classes); imshow("Plate Detection", img); waitKey(0); // 按任意键关闭窗口 // 方式2:视频/摄像头检测(注释图片检测,打开下方代码) // VideoCapture cap(0); // 0=电脑摄像头,也可填视频路径(如"D:/test.mp4") // if (!cap.isOpened()) { // cout << "摄像头/视频打开失败!" << endl; // return -1; // } // Mat frame; // while (cap.read(frame)) { // yoloDetect(frame, net, classes); // imshow("Plate Detection", frame); // if (waitKey(1) == 27) break; // ESC键退出 // } // cap.release(); destroyAllWindows(); return 0; } 3. 运行测试
- 替换代码中
MODEL_PATH(best.onnx 路径)、CLASS_PATH(class_names.txt 路径)、test_plate.jpg(测试图路径) - 选择 x64 Debug/Release 模式,点击运行,即可看到车牌定位结果(绿色框 + plate 标签 + 置信度)
六、常见问题排查
- 模型加载失败:检查 ONNX 路径是否正确,OpenCV 版本是否≥4.5,opset 是否为 12
- 无检测结果:置信度阈值设太低(可降为 0.3),数据集场景与测试图差异大,训练轮数不足
- 速度慢:未启用 GPU 加速(确认 VS 配置 CUDA,代码中已加
setPreferableBackend/Target) - 框不准:标注框未紧贴车牌,数据集不足,可增加训练轮数或扩充数据