news 2026/9/20 23:56:54

C++和OpenCV车牌识别实战:从定位到识别的完整流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++和OpenCV车牌识别实战:从定位到识别的完整流程

简介:C++结合OpenCV实现的车牌识别系统,是一份面向计算机视觉初学者与智能交通项目开发者的完整工程资料,适用于高速公路收费、停车场管理、城市交通监控等场景的学习与原型验证。系统贯穿图像采集、预处理、车牌定位、字符分割、字符识别全流程,帮助读者理解灰度化、边缘检测、形态学操作、轮廓提取以及SVM/神经网络等算法在字符识别中的实际落地。资源包共125个文件,压缩后约2.21MB,包含9个cpp源文件、13个头文件、5个动态库和88张bmp车牌样本图片,另有sln与vcproj工程文件,可直接打开编译或二次开发;其中dll用于运行时依赖,bmp图片覆盖蓝牌、黄牌等常见样本。目前已有80人学习下载。资源内附带了多张不同省份车牌的测试位图,便于验证算法效果。通过源码阅读与运行测试,可以掌握OpenCV常用图像处理函数、车牌定位与字符分割的工程化实现思路,是一份兼顾原理与代码的参考资源。 车牌识别这事儿,放在停车场闸机、小区门禁、高速收费口,大家早就习以为常了。真到自己动手写一个,才发现里面的门道比想象中多得多。网上搜“车牌识别系统”,出来的大多是Python版,或者直接调百度云的API,真正用C++从底层一点点抠出来的教程反而不多。但这套东西恰恰是入门计算机视觉最实在的练手项目:流程完整、算法经典、踩坑点多、做完特别有成就感。这篇博文我就把自己用C++结合OpenCV实现车牌识别系统的完整过程、思路和坑全部整理出来,给正在做课程设计或者准备入门CV的朋友一个可以直接照着走的参考。

先说清楚,我这里做的是传统视觉方案,没有上深度学习。识别过程分三步走:车牌定位、字符分割、字符识别。不涉及神经网络,不需要标注数据,一台普通电脑就能跑。整个项目代码量不大,核心逻辑全在OpenCV的经典函数上,适合想搞清楚图像处理原理的朋友。如果你正在纠结毕业设计选什么题,或者想从零开始学OpenCV,这个项目我可以说是非常对口的。

1. 项目概述与总体设计思路

车牌识别系统,英文叫License Plate Recognition,又被简写为LPR,是计算机视觉里一个非常经典的方向。它解决的问题很简单:给定一张包含车辆的图片,或者一段路面视频,系统能自动把车牌号码“读”出来。应用场景那是真的广——智慧停车场的出口收费、违章抓拍、小区安防道闸、高速ETC的辅助校验,都是它的地盘。可以说,只要是涉及“车”和“管理”的地方,背后都有这套技术在撑腰。

1.1 车牌识别的核心处理流程

整个识别链路,我自己把它拆成了四个核心环节,按顺序走一遍,逻辑非常清晰:

  1. 图像预处理:读取图像之后,先把无关噪声去掉,恢复图像质量,为后续定位做准备。
  2. 车牌定位:从整幅图中找到车牌在哪。这一环最核心的技术就是颜色分析加轮廓检测,有时还要加上形态学处理。
  3. 字符分割:把定位到的车牌区域进一步细分,切出独立的汉字、字母、数字,为识别做准备。
  4. 字符识别:逐字符比对模板库,输出最终的车牌号码文本。

这四步走完,一个车牌就“读”出来了。其中最难、也最容易翻车的,就是车牌定位。你想想,一张照片里可能有树影、车灯、保险杠、周围的行人,背景乱七八糟,怎么把车牌从这一堆信息里干净利落地揪出来,这是整个系统的核心难点。

1.2 为什么选择传统视觉方案而不是深度学习

很多朋友一上来就问:“现在不都流行用深度学习做车牌识别吗?YOLO一检测,OCR一识别,多省事?”这话确实没毛病,深度学习方案在复杂场景下的鲁棒性肯定更强。但我依然坚持用传统视觉方案作为首选教学和落地方案,原因有三:

  • 可解释性强:每一个处理步骤都看得见摸得着。哪一步导致定位失败了、为什么分割错了,都能准确排查,不会像神经网络那样出问题只能瞎调参。
  • 硬件要求低:深度模型要跑得流畅,至少得有个不错的GPU。传统方案一张纯CPU的工控机就能跑,几万块钱的嵌入式设备也能实时处理,这在工业落地时是成本优势。
  • 算法经典:车牌定位用到的HSV颜色空间、轮廓提取、形态学操作、透视变换,这些是视觉领域的基本功。把这些玩明白了,后面学什么视觉项目都事半功倍。

当然,传统方案也有它的天花板,比如在极端光照、车牌倾斜严重、遮挡比较多的时候就容易翻车。我的态度是:入门和学习阶段先用传统方案把原理吃透,实际工程中如果场景复杂,再叠加深度学习也不迟。

2. 开发环境搭建与工具选型

这块是很多新手第一个卡壳的地方。我见过太多代码写得好好的,结果卡在环境配置上三天没进展的同学了。这里我把自己的配置过程完整写一遍,你看完照着弄就行。

2.1 VS + OpenCV环境配置的完整步骤

我用的组合是Visual Studio 2019 + OpenCV 4.5.5,Windows 10系统。先说为什么选这两个版本——VS2019对C++标准的支持很稳定,社区版免费,而且网上资料最多;OpenCV 4.5.5属于4.x时代比较稳定的版本,不用太激进,也不用太老导致很多新函数用不了。

配置步骤:

  1. 从OpenCV官网下载Windows版安装包,比如opencv-4.5.5-windows.exe,双击解压到指定目录,我习惯放在D:\opencv
  2. 打开VS2019,创建一个空的C++控制台应用项目。
  3. 打开项目属性页(右键项目 → 属性):
    • VC++目录 → 包含目录:添加D:\opencv\build\includeD:\opencv\build\include\opencv2
    • VC++目录 → 库目录:添加D:\opencv\build\x64\vc15\lib
    • 链接器 → 输入 → 附加依赖项:添加opencv_world455.lib(注意Debug模式下是opencv_world455d.lib,带d的才是调试库)
  4. D:\opencv\build\x64\vc15\bin目录加入系统环境变量Path。

注意:这里的vc15目录对应VS2017/2019的运行时库,老教程里还会看到vc14、vc12之类的目录,那是给VS2015和VS2013用的,别选错。另外,如果你的VS提示找不到opencv_world455d.lib,多半是下成了Release版的库,或者库文件名和版本号对不上。

配置完后,写一段简单代码验证环境是否OK:

#include <opencv2/opencv.hpp> #include <iostream> int main() { cv::Mat img = cv::Mat::zeros(300, 300, CV_8UC3); cv::circle(img, cv::Point(150, 150), 100, cv::Scalar(0, 0, 255), 2); cv::imshow("test", img); cv::waitKey(0); return 0; }

能弹出窗口并且显示一个红色圆圈,说明环境OK了。这里有个小细节:控制台程序如果没有cv::waitKey(),图像窗口会一闪而过,这个是OpenCV高GUI的特性,不要慌。

2.2 关于运行时库和平台选择的常见误区

好多同学在环境一旦没跑通,第一反应是代码写错了,其实很多时候是“运行时库”的问题。什么是运行时库?你可以把它理解成一个翻译官,我们编译好的程序在运行的时候,需要它来和操作系统沟通。OpenCV在Windows下是动态链接的,也就是说运行的时候必须要依赖对应的DLL文件。

这里有两个最常踩的坑:

  • Debug模式没设置调试库:Debug模式下链接了Release的库,或者反过来,大概率会报一堆link error。记住:Debug对应xxxd.lib,Release对应xxx.lib,千万别混。
  • x64和x86平台混淆:OpenCV官方库默认提供x64版本,但VS默认的解决方案平台可能是x86,这样链接的时候照样找不到库。解决办法就是在项目属性页里把“平台”切换成x64。

另外还有一点容易被忽略:如果你用的是绿色版/免安装版的VS,或者网上找的“精简版”开发环境,缺失Redistributable的情况会更常见。报错表现就是打开程序时提示“vcruntime140.dll缺失”之类。这种问题的标准解法就是安装对应版本的Microsoft Visual C++ Redistributable包,可以从微软官网下载,64位与32位版本都装上,图个省心。

3. 车牌定位:从图像中找出车牌区域

定位环节做好了,后面的分割和识别就顺水推舟;定位做不好,后面全是白搭。我用的策略是“颜色优先、轮廓辅助、几何筛选”的三板斧。

3.1 颜色空间转换与HSV阈值筛选

国内常见的蓝底白字车牌,最明显的特征就是那个蓝底。但是如果直接在BGR颜色空间里用固定阈值去卡蓝色,效果很糟糕。因为BGR三个通道的数值受光照影响非常大:同一块蓝色,在阴影里和在大太阳底下,RGB数值能差出一大截,阈值根本没法设。

所以第一步是把图像从BGR转到HSV颜色空间。HSV把颜色分解成色调(Hue)、饱和度(Saturation)、明度(Value),其中色调对人眼感知的光照变化相对不敏感。这也正是OpenCV里cvtColor的经典用法:

cv::Mat hsv, blueMask; cv::cvtColor(src, hsv, cv::COLOR_BGR2HSV); cv::inRange(hsv, cv::Scalar(100, 80, 60), cv::Scalar(124, 255, 255), blueMask);

这里的H范围我用的是100~124,对应OpenCV中蓝色区域的色相区间。S和V的下限设得比较低(80/60),是为了把一些暗光下饱和度不高的蓝色也尽量保留下来。S和V的上限直接拉满,因为蓝底正常情况下饱和度不会低。实际调参的时候,你可以把这几个阈值暴露成滑动条(cv::createTrackbar),一边动一边看效果,调起来非常直观。

3.2 形态学操作与轮廓筛选

拿到纯蓝区域的二值掩码(mask)之后,你会发现里面除车牌外,还可能有一堆蓝色车贴、车身装饰条之类的干扰区域。这时候需要在二值图上做形态学操作。先说结论,我用的组合是:先做一次开运算去掉孤立小噪点,再做一次闭运算把车牌上因为螺丝孔、字体遮挡产生的断缝连起来。

cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5)); cv::morphologyEx(blueMask, blueMask, cv::MORPH_OPEN, kernel); cv::morphologyEx(blueMask, blueMask, cv::MORPH_CLOSE, kernel);

形态学操作本质上就是“膨胀”和“腐蚀”的各种组合。开运算是先腐蚀后膨胀,能把细小的白色噪点去掉;闭运算是先膨胀后腐蚀,能把白色区域的小缺口补上。在车牌定位场景下,闭运算尤其重要,因为车牌中间有字符的地方蓝色被占掉了,不做闭运算的话,车牌区域会变成一块一块断裂的碎片,轮廓检测就很难找到完整的外边框。

做完形态学,重头戏就来了——cv::findContours提取轮廓。

std::vector<std::vector<cv::Point>> contours; cv::findContours(blueMask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);

这里我用的是RETR_EXTERNAL,只提取最外层轮廓,避免车牌的汉字和数字形成内部轮廓干扰筛选。然后遍历所有轮廓,用cv::boundingRect获取最小外接矩形,再用车牌的几何先验做筛选。

车牌的长宽比是我筛选的第一道关卡。国内蓝牌的标准尺寸是440mm×140mm,长宽比在3.14左右,实际拍摄中因为视角原因会有点变化,所以我一般把筛选范围放宽到2.5~4.5。同时还会加上面积约束——太小或者太大的轮廓直接排除,这样可以干掉很大一部分噪声。

3.3 倾斜校正与透视变换

筛选出车牌区域之后,别急着分割字符。如果车辆是在转弯或者相机安装角度不正,车牌在图像里可能是倾斜的。直接对倾斜的车牌做字符分割,投影法会完全失效。这时候就需要做透视矫正,把歪歪扭扭的车牌“扶正”。

我的做法是使用cv::minAreaRect拿到车牌区域的最小外接旋转矩形,然后根据矩形的旋转角度判断是否需要校正。如果角度绝对值大于某个阈值,就用cv::getPerspectiveTransform算出变换矩阵,再做透视变换:

cv::RotatedRect rotatedRect = cv::minAreaRect(contour); float angle = rotatedRect.angle; if (angle < -45) angle += 90; // OpenCV的angle范围特殊处理 // 计算车牌四个角点,用getPerspectiveTransform做矫正

这里有个小坑需要注意:OpenCV的RotatedRect::angle返回值的范围是[-90, 0),而且长边和宽边的判断跟直觉不太一样,直接拿这个角度去转,很容易得到转进沟里的结果。我一般会根据返回矩形的宽高哪个大,来决定是转angle还是转angle + 90

透视矫正做完,车牌区域就变成一张规规矩矩的正面矩形图,这时候再交给字符分割,成功率能提升一大截。

4. 字符分割:把车牌拆成一个个独立的字符

字符分割这个环节,我的理解是像是在一堆排队的人里画线,你要找出每个人之间的“空隙”。对一张规整的二值化车牌图来说,字符和字符之间本来就有天然的间隙,把这些间隙找到,自然就切开了。

4.1 二值化与垂直投影法切分

在分割之前,先把车牌灰度图转成二值图。这里推荐用大津法(Otsu),也就是cv::thresholdTHRESH_OTSU模式。它可以根据图像的灰度分布自动算出最优阈值,省去了手工调参的麻烦。不过要注意的是,如果车牌图片光照不均,整幅图直接做大津法效果可能一般,可以先做一次顶帽变换或者高斯滤波把光照先抹平。

拿到二值图以后,最经典的分割方法就是垂直投影法。简单来说,就是把二值图像的每一列像素点加起来,得到一条“投影曲线”。字符所在列,白色像素点的数量会很大;字符间隙那几列,加和值几乎为零。顺着这条曲线找到波谷的位置,就是字符的边界。

我自己没有直接用OpenCV封装好的函数,因为垂直投影这个逻辑它没有现成的API,但是用Mat的colRangecountNonZero组合起来,几行代码就能搞定:

std::vector<int> colSum = std::vector<int>(plateImg.cols, 0); for (int x = 0; x < plateImg.cols; ++x) { int sum = 0; for (int y = 0; y < plateImg.rows; ++y) { if (plateImg.at<uchar>(y, x) == 255) sum++; } colSum[x] = sum; }

然后遍历colSum,找到从0突然变大的位置作为字符起点,从非零掉回0的位置作为字符终点,这样就能得到每个字符的左右边界了。

4.2 常规尺寸归一化与边界处理

切出来的字符宽度大小不一,不能直接丢给模板匹配去比对,必须先统一成一样的尺寸。我这里统一缩放到20×40像素的大小,对应模板库中字符的尺寸。缩放前注意用cv::resize做等比例缩放,然后填充到目标尺寸的中央,避免直接拉伸导致字符变形。

还有一个细节:车牌的第二个字符和第三个字符之间有一个圆点分隔符。这个小圆点如果按字符切出来,宽度很窄,容易和数字“1”之类的混淆。我的处理方式是,在投影法分割之后,把宽度异常小的区域直接丢弃或者合并到相邻区域。一般车牌的排版规则是:第一个字符是汉字(省份简称),第二个是字母(发牌机关代号),第三位开始是字母和数字混合。如果识别结果总是多一位,或者少一位,先检查这里是不是把小圆点当作字符切出来了。

投影法这套逻辑理论上对清晰图片效果很好,但是遇到字符断裂、粘连就会头疼。字符断裂时投影曲线会有“假波谷”,本来一个字符被切成两半。字符粘连时投影曲线又找不到明显的波谷。遇到这种情况,可以结合连通域分析(cv::connectedComponentsWithStats)来辅助切分,先把字符的连通域找出来,再根据连通域尺寸和位置关系合并或拆分。这一招我亲测对车牌字符的鲁棒性提升很明显。

5. 字符识别:模板匹配与结果输出

字符分割完成之后,整张车牌图已经变成了一个一个单独的字符小图。接下来就是最后一步——让程序认出每一个字符是什么。

5.1 模板库的构建与匹配算法选择

传统视觉方案里,字符识别最朴素的思路就是模板匹配。你需要提前准备一套字符模板图片,覆盖所有可能出现的汉字、字母和数字,比如“京”、“津”、“沪”、“A”、“B”、“C”、“0”、“1”、“2”等。模板库的整理是个费工夫的活儿,需要你去搜集足够清晰的车牌字符字体图片,统一缩放成20×40的尺寸。

匹配过程用的是cv::matchTemplate函数,把待识别字符和模板库里的每一个模板依次做比对,计算相似度。相似度最高的那个模板对应的字符,就是识别结果。相似度的度量方式有很多,相关性、相关系数、归一化相关等。我实际用下来,TM_CCOEFF_NORMED这个度量方式表现最稳定,它对光照变化不那么敏感,返回的相似度值在[-1, 1]之间,取最大值对应的字符即可。

cv::Mat result; cv::matchTemplate(charImg, tmplImg, result, cv::TM_CCOEFF_NORMED); double maxVal; cv::minMaxLoc(result, nullptr, &maxVal);

这里实际跑了之后发现,汉字(比如“京”、“苏”、“粤”)之间的相似度有时会异常高,因为汉字笔画结构复杂,模板的字体来源稍微不一样,匹配结果就容易飘。一个亲测有用的技巧是:在模板库里多存几种同字不同粗细的字体,或者对模板做几次形态学膨胀,把模板的笔画变粗一点点。这样可以让匹配的容错性大大提升。

5.2 识别结果的置信度判断

很多初学朋友拿到识别结果后,发现识别错了,第一反应就是换个模板。这当然是一个方向,但更重要的是学会判断结果“可不可信”。单纯拿到一个相似度最高的模板是不够的,我还要求程序把相似度结果排序,看第一名和第二名的差距。

  • 如果最高相似度低于0.6,我直接判为识别失败,宁可输出一个?也不硬猜。
  • 如果第一名和第二名的相似度差距小于0.05,说明这个字符在模板库里有“撞脸”嫌疑,我同样会标记为低置信度,提示人工复核。

这个置信度策略,对实际工程的意义很大。因为车牌识别系统一旦接入到收费系统或者门禁系统,识别错误造成的影响远大于识别不出来造成的。识别不出来,人工帮一下忙就行;识别错误,可能导致扣错费、放错行。我的经验是:宁可输出“未识别”,也不要输出一个错误的车牌号。这个思路在工业项目里非常重要,大家可以仔细体会一下。

6. 完整流程整合与代码实现

前面各个环节都拆开讲了,这里我整合一段主流程代码,把预处理、定位、分割、识别串起来。完整的工程代码量太大,这里我给出核心流程的骨架,相当于把各个模块串联起来:

#include <opencv2/opencv.hpp> #include <vector> #include <string> #include <iostream> using namespace cv; using namespace std; int main() { Mat src = imread("car.jpg"); if (src.empty()) { cout << "读取图片失败" << endl; return -1; } resize(src, src, Size(800, 600)); // 1. 图像预处理 Mat hsv; cvtColor(src, hsv, COLOR_BGR2HSV); // 2. 蓝色车牌提取 Mat blueMask; inRange(hsv, Scalar(100, 80, 60), Scalar(124, 255, 255), blueMask); // 3. 形态学操作 Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5)); morphologyEx(blueMask, blueMask, MORPH_OPEN, kernel); morphologyEx(blueMask, blueMask, MORPH_CLOSE, kernel); // 4. 轮廓检测与筛选 vector<vector<Point>> contours; findContours(blueMask, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); Mat plateRegion; for (size_t i = 0; i < contours.size(); ++i) { RotatedRect rotated = minAreaRect(contours[i]); double ratio = rotated.size.width / rotated.size.height; if (ratio < 2.0 || ratio > 5.0) continue; // 拿到车牌ROI后做透视矫正 // ... // 矫正结果存入plateRegion break; } // 5. 灰度、二值化 Mat gray, binary; cvtColor(plateRegion, gray, COLOR_BGR2GRAY); threshold(gray, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU); // 6. 垂直投影分割字符 vector<Mat> charImgs; // ... 垂直投影切分逻辑 ... // 7. 模板匹配识别 for (const auto& ch : charImgs) { // 统一尺寸、matchTemplate、取最高置信度字符 // 拼接结果字符串 } // 8. 输出结果 cout << "识别车牌号: " << result << endl; return 0; }

这段代码是核心流程的框架,每个注释标记的位置都需要填充对应的模块实现。比如透视矫正部分需要根据minAreaRect的返回值计算旋转矩阵,字符分割部分需要在二值图上做逐列扫描。

6.1 参数调优的实操心得

整个系统跑下来,我最大的感悟是:没有一套万能参数,场景变了参数必须跟着变。从HSV阈值、形态学核大小,到轮廓筛选的长宽比范围,没有一个值是绝对不能改的。比如普通停车场和高速卡口的相机安装高度和角度不一样,同一个HSV阈值可能一个效果好一个效果差。

调试的时候我建议不要一条路走到黑,多利用调试工具看中间结果。我习惯在代码里加一个drawContours的调试窗口,把每次定位到的候选区域都画出来,大概有哪几个、长什么样子、为什么会被筛选出去。这个过程比瞎猜阈值要有用一百倍。另外,cv::createTrackbar也是非常顺手的调参神器,把几个核心阈值暴露成滑动条,实时看效果,调出合适的数值之后再写死到代码里。

6.2 性能优化与多线程的简单讨论

在PC上跑,单张图片的处理时间大概在80到150毫秒左右,其中大部分时间花在图像缩放、形态学操作和模板匹配的遍历上。如果以后要接实时视频流,建议把模板库的匹配过程用多线程并行化,把模板库拆成几组,每个线程匹配一组,最后合并结果。

std::vector<std::thread> threads; // 按模板库划分,每个线程处理一部分模板,最后汇总maxVal

这里涉及C++多线程调度,是另一个比较大的话题了。对于单张图片识别的场景,其实单线程完全够用,不用过度设计。如果你真的想做实时视频流识别,建议把定位步骤缩小感兴趣区域(ROI),只在画面中心区域附近做检测,能省下不少时间。

7. 常见问题与排查技巧实录

写这部分的时候,我回想了自己实际调试过程中遇到的各种问题,整理成一张速查表,方便大家对照排查。这里面很多问题都是不亲自踩一遍很难发现的,属于拿时间换来的经验。

现象可能原因解决思路
定位不到车牌区域HSV阈值范围不对,或形态学开运算核太大把车牌滤掉了先用滑动条实时预览blueMask,确认车牌区域是白色像素且连通
轮廓检测到很多碎片闭运算没做或核太小,车牌区域断裂增大闭运算核尺寸,比如从5×5改成9×9
定位到了错误的区域车身颜色接近蓝色的车辆干扰加严长宽比条件,增加面积限制,必要时用边缘检测辅助验证
字符分割多出一个小碎片车牌中间的圆点分隔符被当作字符切出来了对过窄的字符区域做丢弃或与相邻区域合并
汉字识别老是混淆模板字体来源不一致,或者模板太少多存几种字体,对模板做膨胀处理增加容错
全部识别结果置信度低预处理不到位,图像太暗或模糊检查灰度化和二值化步骤,考虑加直方图均衡化
程序一运行就崩溃读取图片路径错误,或者Mat为空就继续处理每次imread之后都要检查empty(),调试时用绝对路径

7.1 定位不到车牌时的排查思路

这是出现频率最高的问题。我的排查顺序是:先看blueMask长什么样。如果mask里压根没有车牌区域的白色块,说明HSV阈值完全不合适,打开滑动条调;如果mask里有白色块但分散不成片,说明闭运算不够,车牌区域没连起来;如果mask里块很多很杂,说明阈值太宽松,把路边的蓝色指示牌、车身装饰都划进来了。总之先定位先用mask图说话,一步步把中间结果都看一遍,就能精确找到是哪一步出了问题。

7.2 字符分割与识别阶段的避坑指南

分割阶段最值得注意的就是字符与字符之间的间隔处理。车牌字符里,第二个字符和第三个字符之间有个小圆点,如果车牌本身拍得不清晰,这个小圆点的宽度特征很不稳定,要么过窄被丢弃,要么因为投影曲线噪声和相邻字符粘连在一起,导致本该有的间隔被漏掉,结果字符数量不对。识别阶段则要记住:模板库的完整性和统一性决定了识别上限。如果你的模板库没有“京”这个字,那“京”永远识别成别的,这种事神仙算法也救不了。

最后分享一个实际操作中的技巧:你可以在工程里把识别过程和结果可视化地画在原始图上。用cv::rectangle画出定位框,然后用cv::putText直接把识别结果显示在原图上方。这样不管是调试还是测试,一眼就能看到识别效果到底如何。这个输出的可视化环节也算是个小门面,课程设计答辩或者项目展示时特别加分。

这套东西我断断续续磨了两周,最开始识别准确率也就六成,后来一步步把预处理、定位、分割每个环节都调到位,才慢慢提到九成五以上。做这个过程收获最大的不是最后那个识别结果,而是整条链路里每一个函数、每一个参数背后意味着什么。希望这篇笔记能帮你少踩几个坑,早点跑通自己的第一套车牌识别系统。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 23:56:41

大华Java SDK迁移SpringBoot完整实践:从库加载到设备管理

1. 迁移前的整体判断与方案选型 1.1 大华Java SDK到底是个什么东西 先聊一个基本认知问题。大华官方提供的Java SDK&#xff0c;表面上看是一堆 .jar 包加几个 .dll 或 .so 文件&#xff0c;但它的核心底层其实是C实现的native库&#xff0c;Java层通过JNA技术去调用。…

作者头像 李华
网站建设 2026/9/20 23:56:33

PostHog TMDB 数据源 API 盘点:从认证、分页到限流的接入全解

PostHog TMDB 数据源 API 盘点&#xff1a;从认证、分页到限流的接入全解 【免费下载链接】posthog :hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experimen…

作者头像 李华
网站建设 2026/9/20 23:55:46

Claude Code 不走 Anthropic API,改走 TaoToken 行不行

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 23:53:45

Zephyr 在 PHYTEC phyBOARD-Lyra AM62x A53 上的移植与实战指南

Zephyr 在 PHYTEC phyBOARD-Lyra AM62x A53 上的移植与实战指南 【免费下载链接】zephyr Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures. 项目地址: https://gitcode.…

作者头像 李华
网站建设 2026/9/20 23:53:35

Biome 与 Prettier 兼容性挑战报告深度解读:96%+ 相似度的背后

Biome 与 Prettier 兼容性挑战报告深度解读&#xff1a;96% 相似度的背后 【免费下载链接】biome A toolchain for web projects, aimed to provide functionalities to maintain them. Biome offers formatter and linter, usable via CLI and LSP. 项目地址: https://gitco…

作者头像 李华
网站建设 2026/9/20 23:51:11

RapidOCR调优实操:3个参数让推理耗时减半

RapidOCR调优实操&#xff1a;3个参数让推理耗时减半 【免费下载链接】RapidOCR &#x1f4c4; Awesome OCR multiple programing languages toolkits based on ONNX Runtime, OpenVINO, MNN, PaddlePaddle, TensorRT and PyTorch. 项目地址: https://gitcode.com/GitHub_Tre…

作者头像 李华