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) - 框不准:标注框未紧贴车牌,数据集不足,可增加训练轮数或扩充数据