尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

C++与OpenCV实现车牌识别:从图像定位到CNN字符分类

C++与OpenCV实现车牌识别:从图像定位到CNN字符分类 简介面向计算机视觉方向的高校毕业生和图像处理开发者这份毕业设计资源以OpenCV与C为基础融入深度学习技术实现车牌识别全流程覆盖车牌定位、字符分割与识别等核心模块。项目为个人原创毕业设计评审得分95分以上源码经过严格调试可直接运行验证可在此基础上进行二次开发。压缩包内包含完整项目源码与配套毕业设计论文目录结构清晰可帮助读者理解从图像预处理、特征提取到模型推断的完整工程实现也为类似车牌识别或目标检测课题提供可复用的代码框架与写作参考。包体为ZIP格式大小约6.61MB轻量易下载非常适合课程设计或课后自学。目前已有991人学习使用适合需要快速落地毕业设计或入门OpenCVC深度学习应用的开发者参考借鉴。1. 车牌识别毕设项目一条C与OpenCV串起的完整技术链路毕业设计选车牌识别等于把颜色空间转换、形态学运算、边缘检测、字符切分、卷积神经网络训练和C工程部署一次性全过了一遍。表面上这是图像识别题目拆开看实际是两套技术路线的拼接OpenCV负责定位和字符分割深度学习模型负责字符分类。用C做载体而不是Python除了性能更好之外还在于工程链路更完整从库依赖配置到模型文件加载每一步都需要真正踩一遍这正好是答辩和后续面试时能讲清楚的部分。下面按落地顺序展开先搭好C和OpenCV的环境再做车牌定位与字符分割接着训练一个轻量CNN模型最后把模型接回C识别循环并给出论文实验设计和答辩演示可以直接用的方法。2. 环境搭建与车牌定位C/OpenCV的核心工作流2.1 版本与环境选择OpenCV 4.x与Visual Studio的理由写车牌识别项目时我更推荐直接用OpenCV 4.5.2之后的版本。原因有两个DNN模块在这一代已经能原生读取ONNX格式模型训练好的网络可以直接在C端跑前向推理不需要额外引入TensorRT或ONNX Runtime预编译包对新人友好网上一搜opencv安装教程就有大量资料可以参考省去自己编译的麻烦。如果对部署体积要求高也可以选择源码编译把不需要的模块裁掉链接体积能缩小三分之一但毕设阶段不建议在编译期给自己增加太多障碍。Windows下最常见的配置组合是Visual Studio 2019/2022加OpenCV预编译包。流程是下载release包、配置环境变量、在项目属性里填入include和lib路径。Debug和Release模式下各要一份lib配置且位数要和运行时dll一致。也有人用vscode配置c/c环境通过MinGW或MSVC插件控制编译参数但调试体验不如VS顺手断点查看图像中间结果还是VS方便。这里有一个高频踩坑点OpenCV调试版lib文件名带d后缀。Debug项目链接了release版lib或者反过来链接阶段就会报LNK2019错误信息只指向几个无法解析的符号很难往依赖不匹配上想。判断方法很简单检查linker输入里Debug对应的是否是opencv_world460d.libRelease对应opencv_world460.lib。2.2 车牌定位HSV颜色阈值加形态学闭运算的组合拳定位阶段的目标是把车牌区域从背景里框出来。直接按BGR颜色特征筛光照一变就全偏了转HSV之后色相H受光照影响小配合饱和度和明度阈值能覆盖从白天到傍晚的大部分场景。车牌底色主要分蓝、黄、绿三类毕设以蓝牌为主最省事。下面这段代码完成蓝牌候选区域的初筛cv::Mat hsv, mask; cv::cvtColor(src, hsv, cv::COLOR_BGR2HSV); cv::inRange(hsv, cv::Scalar(100, 80, 80), cv::Scalar(124, 255, 255), mask); cv::Mat kernel cv::getStructuringElement(cv::MORPH_RECT, cv::Size(17, 7)); cv::morphologyEx(mask, mask, cv::MORPH_CLOSE, kernel);cvtColor把BGR帧转换为HSVinRange用低阈值和高阈值两个三元组卡出蓝色区域。注意OpenCV的H分量范围是0到180蓝色分布在100到124这个区间。形态学处理用17×7的矩形结构元做闭运算先膨胀再腐蚀把字符间隙填起来让原本断开的字母连成一个整体下一步轮廓查找才能把整个车牌当作一个连通域处理。紧接着用连通域加几何约束过滤候选std::vectorstd::vectorcv::Point contours; cv::findContours(mask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE); for (const auto contour : contours) { double area cv::contourArea(contour); if (area 5000) continue; cv::Rect rect cv::boundingRect(contour); double ratio (double)rect.width / rect.height; if (ratio 2.0 ratio 5.0) { cv::Mat plate src(rect); // 到这里得到候选车牌区域交给字符分割流程 } }findContours输出的是点集合boundingRect拿到轴对齐外接矩形。宽高比范围按车牌物理比例来设普通蓝牌宽高比接近3.0到3.5放宽到2.0到5.0是为了容纳新能源车牌和轻微透视形变。面积阈值5000假定输入图片尺寸在800×600以上换更高分辨率的相机时这两个参数都要按比例调整。调试阶段最常用的两个函数是cv::drawContours和cv::fillPoly。前者把轮廓画回原图肉眼确认每个候选框是否准确后者用来生成掩码比如手动遮掉某一区域再观察分割结果。把轮廓和矩形框画在原图上核对比看log打印坐标直观得多。2.3 Canny边缘二次确认与倾斜校正颜色分割的弱点在白色车身和强反光场景下会被放大大量非车牌蓝色区域会混进候选。所以实际项目中我在颜色初筛之外再跑一条Canny边缘检测对两组结果取交集能明显降低误检率。车牌边框、字符、铆钉都会形成密集边缘车身蒙皮反而是大片平滑区两者在边缘密度上差异显著。cv::Mat gray, edges; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); cv::GaussianBlur(gray, gray, cv::Size(5, 5), 0); cv::Canny(gray, edges, 60, 180); cv::dilate(edges, edges, cv::Mat(), cv::Point(-1, -1), 3);GaussianBlur用来去掉传感器噪声Canny双阈值60和180控制边缘灵敏度。下限太低会把路面裂缝和阴影纹理都当成边缘上限太高又容易把浅色字符滤掉。dilate膨胀3次是为了让断裂的边缘片段连起来否则一个车牌轮廓会碎成几十段后续根本筛不出来。这里要单独说清楚cv::Rect和cv::RotatedRect的区别。前者坐标轴对齐适合粗筛后者带旋转角能输出四个顶点适合做倾斜校正。车牌在画面中有明显旋转时先用minAreaRect求最小外接矩形拿到角度后反向旋转原图再交给分割模块。C里这个角度范围在-90度到0度之间正负号方向和数学课本相反新手极易写反校正结果变成头朝下的车牌。表格定位阶段的主要参数模块参数参考值调整提示HSV颜色H下限/H上限100/124黄昏时把下限降到95形态学结构元尺寸17×7约为字符宽度的1.2倍轮廓筛选最小面积5000按输入分辨率线性缩放Canny边缘双阈值60/180逆光时调低上限到40/140倾斜校正有效角度范围-30°到30°超出范围视为无效候选3. 字符分割与归一化连接传统视觉和深度学习的那道桥3.1 垂直投影法切分车牌字符拿到校正后的车牌图像后下一步是切成单个字符。最基本也最实用的是垂直投影法逐列统计非零像素数量字符列数值高间隙列接近0波谷就是分割点。cv::Mat gray, binary; cv::cvtColor(plate, gray, cv::COLOR_BGR2GRAY); cv::threshold(gray, binary, 0, 255, cv::THRESH_BINARY_INV | cv::THRESH_OTSU); cv::Mat column_sum; cv::reduce(binary, column_sum, 0, cv::REDUCE_SUM, CV_32S); std::vectorstd::pairint, int segments; bool in_segment false; int seg_start 0; for (int col 0; col column_sum.cols; col) { int sum column_sum.atint(0, col); if (sum 0 !in_segment) { in_segment true; seg_start col; } else if (sum 0 in_segment) { in_segment false; if (col - seg_start 8) { segments.emplace_back(seg_start, col); } } }THRESH_OTSU根据直方图自动计算二值化阈值比固定阈值抗光照变化。车牌字符本身是白色背景偏深所以用THRESH_BINARY_INV把字符像素置为255再统计投影才有意义。reduce把二维矩阵折叠成一行每列求和得到的值直接反映该列有多少白色像素。循环里的in_segment记录当前是否处在字符区间sum为0时认为字符结束宽度达到8像素才保留。这个宽度下限是过滤噪声的关键铆钉、灰尘形成的短投影会被直接剔除。如果字符间隙里有小噪点可以把下限提到10代价是窄字符如数字1和字母I可能被误删需要结合下一个条件判断。这个方法的假设是字符互不相连且垂直对齐。边框没有完全清除时白色边缘会和两端字符粘连把“京”和边框识别成一个超宽区域。所以分割前必须先剥边框对二值图做一次erode腐蚀掉最外圈像素或者遍历第一行和最后一行把白色直接置黑。3.2 字符归一化、字符表映射与数据增强切出的字符宽高比例不一数字1窄汉字京宽直接送进CNN会把尺寸信息弄乱。常见做法是先按比例缩放到统一尺寸再贴到固定画布上cv::Mat pad cv::Mat::zeros(32, 32, CV_8UC1); cv::resize(segment, segment, cv::Size(28, 28), 0, 0, cv::INTER_AREA); segment.copyTo(pad(cv::Rect(0, 0, 28, 28)));不直接resize成32×32是为了保留字符原始宽高比。硬拉伸会让“1”和“7”这类对形状敏感的字符产生误判识别率掉得莫名其妙。INTER_AREA是缩小图像时抗锯齿效果最好的插值方式。字符表映射在C里一般用字符串数组初始化const char* kIndexToChar[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V, W, X, Y, Z, 京, 沪, 粤, 苏, 浙, 鲁, 川 };注意字母表里去掉了I和O因为这两个字符在视觉上和数字1、0几乎无法区分这是车牌识别的通用做法。训练端和C端必须共用同一份字符表顺序一乱模型输出全部错位。数据增强方面平移正负3像素、旋转正负5度、加高斯噪声是最常用也最难出错的三种方式。这三类增强模拟的是摄像头安装角度差异和夜间传感器噪点幅度不要过大过大会让字符本身变形。表格数据增强参数范围增强方式参数范围模拟场景平移±3像素摄像机安装抖动旋转±5度车牌安装倾斜缩放0.9到1.1倍车距变化高斯噪声σ0.01到0.03夜间低照度噪点3.3 分割出问题的常见表现与容错策略演示时最容易暴露的问题就是摄像头角度稍微一歪字符分割全部乱套。有两个容错思路第一切出的字符段数不符合预期时自动调整参数重切第二某字符置信度太低时标记为待人工复核不要硬输出结果。段数判断直接看segments.size()。普通蓝牌是7个字符新能源车牌8个。段数不够说明有字符粘连把最小宽度阈值往上调或者对二值图做一次腐蚀把连接处打断段数多了说明背景噪声被当成了字符调大最小宽度并增加面积过滤。开发阶段每个中间步骤都应该通过cv::imwrite输出图像定位框截图、二值图、字符切分图各存一份。光盯着最终准确率很难定位问题把分割图逐张翻一遍错误原因马上就能看出来。这个做法写进论文里可以当作中间过程可视化的素材也能体现调试过程是完整的。4. 基于CNN的车牌字符识别从训练到C推理4.1 选轻量CNN做字符分类不用目标检测的原因字符已经从背景中抠出来了剩下的任务就是分类输入32×32灰度图输出65个类别中的一个。这类任务用轻量CNN完全够用准确率高推理快。YOLO这类目标检测网络擅长定位和分类同时完成这里替它做就显得笨重训练难度高推理还慢。轻量CNN用两层卷积加两个全连接层就能达到实用水平参数量小训练快C端部署没有任何额外负担。算一笔开销32×32灰度图两层卷积各32和64个卷积核单次前向推理在一颗i5处理器上大约2到3毫秒。视频连续识别时耗时瓶颈通常不在模型推理而在定位阶段的颜色转换和形态学操作上。4.2 训练数据组织与标签映射训练数据一般有两类来源开源的中文车牌字符数据集以及自己拍摄实景照片后用前面的分割流程自动切出的字符。两类混合效果最好。数量上每个类别不少于500张原始图增强后不少于5000张基本能训练出可用模型。目录结构用分层式最省事dataset/ train/ 0/ 1/ A/ ... 京/ val/ 0/ 1/ ...PyTorch的ImageFolder可以直接读这个结构做推理时C端维护同一个kIndexToChar顺序即可。训练集和验证集划分有个容易忽略的坑不能用同一辆车的不同视频帧分别进训练和验证模型会记住车牌整体而不是字符形状验证分数虚高。正确做法是先按车辆分组再切分保证验证集里的车辆完全没有出现在训练过程中。4.3 用OpenCV DNN加载ONNX模型做前向推理训练用PyTorch完成训练好后导出为ONNX格式C端直接用OpenCV DNN模块读取不需要引入PyTorch库和ONNX Runtime依赖部署时只需OpenCV自带的dll。导出时固定好输入输出名C端调用才不会对不上。cv::dnn::Net net cv::dnn::readNetFromONNX(license_plate_cnn.onnx);推理部分的实现cv::Mat blob cv::dnn::blobFromImage(character_img, 1.0 / 255.0, cv::Size(32, 32), cv::Scalar(0), true); net.setInput(blob); cv::Mat output net.forward(); double maxVal; cv::Point maxLoc; cv::minMaxLoc(output, nullptr, maxVal, nullptr, maxLoc); int classId maxLoc.x;blobFromImage最后一个参数true表示swapRB因为训练样本通常是RGB格式OpenCV读进来是BGR通道顺序不一致会让模型识别率骤降。如果训练时直接用的BGR这里传false。输出是1×65的矩阵minMaxLoc在单行矩阵上直接找出最大值下标maxLoc.x取到的就是类别索引。表格训练与推理参数对照环节参数数值说明训练输入尺寸32×32单通道灰度训练初始学习率0.001Adam30轮后衰减一次训练batch size128视显存调整训练类别数6531汉字加24字母加10数字训练轮数50验证loss不降就提前停止推理输入尺寸32×32必须与训练一致推理swapRBtrue通道顺序保持一致训练和推理看着是两头但参数必须严格对齐输入尺寸、通道顺序、字符表顺序这三样任何一处不一致最终整牌识别率都会明显下降。5. 整牌输出与论文实验设计从识别字符串到毕业设计答辩5.1 识别结果串接与合法性校验字符分类全部完成后按横坐标排序把类别名拼成字符串。串接之后必须加校验。车牌长度限定为7或8位第二个字符必须是发牌机关字母而不是汉字否则大概率是分割顺序错了。校验可以用正则表达式也可以直接按省份简称和字母范围写判断逻辑。发现不合法就直接丢弃这一帧结果不要硬输出错误车牌。后校验逻辑在论文里也是一处加分点对应的是工程系统中的容错设计。5.2 准确率统计口径与鲁棒性实验写法写实验部分最容易犯的错误是把单字符准确率当作整牌准确率来写。单字符98%时7个字符整牌全对的概率大约在87%左右两者差一个数量级。实验表格要分两行呈现字符级准确率和整牌级准确率并写明测试集总张数和场景来源。鲁棒性测试上把测试集按光照、角度、遮挡三个维度各挑30张分别统计识别率比只给一个总准确率有说服力也能说明算法在哪些条件下还存在短板。5.3 答辩演示的环境准备演示现场优先使用Release模式编译把所有dll放在exe同目录目标机器提前安装对应版本的Visual C Redistributable。程序里保留保存日志和逐帧输出中间结果的功能实时摄像头效果不稳定时直接切换本地图片目录批量识别日志回放也能证明系统处理能力。不要在演示现场改代码也不要依赖IDE启动环境。代码里给定位、分割、识别、后校验四个模块各留一个debug输出开关后续改数据、补实验、写论文都会顺畅很多。本文还有配套的精品资源点击获取
返回列表