ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Matlab的车牌识别系统:从HSV颜色分割到模板匹配实战

基于Matlab的车牌识别系统:从HSV颜色分割到模板匹配实战 简介基于Matlab实现的车牌识别系统参考资料整合了完整算法源码和多个省份的车牌图像样本属于计算机视觉领域的典型应用项目面向计算机、电子信息工程、数学等专业学生可作为课程设计、算法实验或毕业设计的参考。压缩包体积仅936KB共收纳116个文件其中110个为JPG车牌图像样本覆盖鲁、京、辽等省份便于测试和展示不同牌照的识别效果6个为Matlab源码文件围绕车牌定位、字符分割、识别等关键环节提供程序实现。目前已有232人学习/下载尤其适合初涉图像处理、模式识别或Matlab编程的读者作为入门学习与项目复现的素材。通过对照源码与图像能够理清从图像预处理、车牌区域提取到字符识别输出的完整链路理解算法在实际数据上的处理效果。需要说明的是该资料定位为参考资料而非定制代码更适合已有一定Matlab基础、能够自行调试和扩展功能的学习者参考算法思路并结合自己的场景完成二次开发。1. 基于Matlab实现车牌识别系统为什么值得自己写一套基于 Matlab 实现车牌识别系统是图像处理课程里出现频率最高的源码工程之一。多数人能下载到带图像素材的完整压缩包但往往跑完 demo 就结束了遇到逆光、倾斜或者新能源绿牌的照片识别率立刻崩掉。车牌识别的完整链路并不神秘图像输入后依次做色彩分割、形态学处理、连通域分析框出车牌区域再把车牌图二值化用投影法拆成单个字符最后用模板匹配或分类器把字符转成字符串。难点不在最后的识别而在前两步的阈值和参数怎么设。下面这套方案是实战中积累下来的不引入复杂深度学习模型全程用 Matlab 图像处理工具箱函数就能复现方便按自己的数据逐项调整。2. 车牌识别系统整体流程与Matlab工程骨架2.1 为什么用Matlab做车牌识别而不是直接上OpenCV这类工程经常被当作 Matlab 图像处理教程里的综合案例原因不只是语法简单。Matlab 在图像处理领域有一套很完整的函数链rgb2hsv、medfilt2、strel、regionprops、imcrop这些函数可以直接按数据流串起来中间每一步都能随手imshow验证。对于车牌识别这种需要反复调参的视觉任务这种“边跑边看中间结果”的调试方式比编译型语言效率高得多。用 OpenCV 也能做但车牌识别里大量工作是阈值试错颜色范围、形态学核大小、连通域宽高比。Matlab 里改一个数字重新运行就行还能用figure同时对比多组阈值下的分割效果。另一个现实原因是课程设计和毕设场景里Matlab 的代码量更小答辩时讲原理比讲工程链路更容易被理解。2.2 车牌识别流程拆解与关键前提一张汽车照片要变成车牌字符串至少要经过四个环节。下面的表格给出了每个环节的输入输出、关键函数和最常出问题的地方。环节输入 - 输出Matlab 关键函数最常见失败原因图像读取与预处理原图 - 统一尺寸RGB图imread、imresize原图过大导致形态学操作过慢车牌定位RGB图 - 车牌子图rgb2hsv、bwconncomp、regionprops光照导致 HSV 阈值选不到车牌字符分割车牌子图 - 字符序列rgb2gray、投影求和sum二值化方向选反字符变成背景字符识别字符序列 - 字符串imresize、corr2模板库不全误识别 O/0、Z/22.3 主流程代码骨架从图像到字符串把上面的流程先串成一个可运行的主脚本。代码逻辑是“读图、缩放、定位、分割、识别”五步每步调用一个独立函数后面章节再展开。这种分函数写法也符合源码工程的常见组织方式主脚本负责调度子函数负责算法细节。clear; clc; close all; img imread(car.jpg); % 读入汽车照片 img imresize(img, [NaN 640]); % 宽度统一到640保持长宽比 [plate, box] detectPlate(img); % 自定义车牌定位函数 if isempty(plate) error(没有定位到车牌请调整HSV阈值); end figure; imshow(plate); % 先看车牌抠得对不对 chars splitChars(plate); % 字符分割返回 cell 数组 result recognizeChars(chars, templates); % 模板匹配识别 fprintf(识别结果%s\n, result);参数说明imresize的第二个参数[NaN 640]表示只固定宽度高度按原图比例自动缩放这样能显著降低后续形态学运算的耗时。车牌定位函数返回plate和boxbox是矩形坐标用于调试时在原始图像上画框。splitChars与recognizeChars都是后面要写的自定义函数。模板匹配需要预先准备一个模板目录里面放 31 个省份简称汉字加字母数字的字符图。3. 车牌定位HSV色彩分割、形态学闭运算与连通域筛选的Matlab实现3.1 车牌颜色先验HSV阈值与光照影响车牌定位最稳定先验是颜色。国内蓝牌的主色调是蓝色且蓝色区域在整张图中占比不低。直接用 RGB 判断蓝色容易被光照骗到比如阴影下的蓝色会变成深灰强光下会偏白。换成 HSV 色彩空间后色相 H 通道能把“颜色本身”和“亮度”拆开鲁棒性好很多。Matlab 的rgb2hsv返回的 H、S、V 三个通道范围都是 [0,1]这一点和 OpenCV 不同很多人第一次写阈值时把 H 设成 100 多结果掩膜全黑。蓝色车牌的 H 大约落在 0.55 到 0.70 之间也就是色环上蓝到青的方向。S 饱和度要大于 0.35用来过滤掉灰白色的车身和水泥地面。V 亮度大于 0.15防止夜里或阴影下的暗色区域被当成车牌。hsv rgb2hsv(I); mask (hsv(:,:,1) 0.55) (hsv(:,:,1) 0.70) ... (hsv(:,:,2) 0.35) (hsv(:,:,3) 0.15); mask medfilt2(mask, [5 5]); % 中值滤波去掉椒盐噪声 mask imclose(mask, strel(rectangle, [15 15])); % 闭运算连接字符空洞 mask imopen(mask, strel(rectangle, [5 5])); % 开运算去掉孤立小块逻辑说明第一行生成二值掩膜满足颜色条件的像素为 1。medfilt2用 5x5 邻域中值消除单点噪点因为车牌蒙尘或摄像头噪点会让掩膜上出现很多白点。闭运算对白色区域做“先膨胀后腐蚀”作用是填平车牌字符内部的黑色空洞让车牌连成一个完整实心块。开运算正好相反去掉分散在车身、路面上的小色块。3.2 形态学操作的参数怎么调形态学结构元素的大小是最容易抄错也最影响结果的一组参数。以宽度 640 的输入图像为例下面这套参数经过了大量图片验证参数推荐值作用调大的后果调小的后果中值滤波窗口[5 5]去噪车牌边缘被抹平噪点残留闭运算结构元素[15 15]连接字符缝隙把车牌和旁边物体连在一起字符空洞无法填满开运算结构元素[5 5]去掉散点车牌区被削掉一部分保留大量干扰块如果输入图像分辨率不是 640 宽结构元素大致按图像宽度比例缩放。例如 1280 宽的图闭运算核建议放到[30 30]。闭运算核太小的典型症状是车牌区域在掩膜上呈“蜂窝状”每个字符独立成块后续连通域分析根本找不到完整的矩形候选。3.3 连通域几何筛选宽高比与面积比形态学处理之后的掩膜仍然可能残留其他蓝色物体比如路牌、蓝色货车车厢、垃圾桶。这时要利用车牌的形状先验做几何筛选。标准蓝牌的宽高比约为 440:140换算下来接近 3.14实际拍摄角度会带来一点透视变形所以阈值放宽到 2.5 到 5.5 比较稳。cc bwconncomp(mask); stats regionprops(cc, BoundingBox, Area); plateBox []; for i 1:numel(stats) box stats(i).BoundingBox; % [x y width height] ratio box(3) / max(box(4), 1); % 宽高比 areaRatio stats(i).Area / numel(mask); % 面积占整图比例 if ratio 2.5 ratio 5.5 areaRatio 0.001 box(4) 0.04 * size(I, 1) plateBox round(box); break; % 取第一个满足条件的连通域即可 end end if isempty(plateBox) plateImg []; else plateImg imcrop(I, plateBox); end参数说明regionprops返回的BoundingBox是 [x 坐标, y 坐标, 宽度, 高度]。areaRatio用连通域面积除以全图像素总数防止把远处一个小蓝点当成车牌。box(4) 0.04 * size(I, 1)要求车牌高度至少占图像高度的 4%这个约束能过滤掉背景里极小的高分候选。注意如果检测对象是新能源绿牌HSV 阈值要按绿色重新取H 大约在 0.35 到 0.45 之间宽高比也更接近 2.8。写代码时最好把车牌颜色类型做成参数不要写死。4. 字符分割与模板匹配识别投影法的边界条件4.1 二值化方向蓝底白字为什么不能用默认二值化定位成功后车牌子图是蓝底白字。字符分割前需要做二值化把字符变成白色前景、背景变成黑色。Matlab 的imbinarize默认阈值通常会把亮部当背景、暗部当前景用在蓝底白字上正好反过来蓝色变成白色白色字符变成黑色。很多人第一次跑出来所有字符都是空心就是这个原因。正确的做法是直接在灰度图上比较车牌白色字符亮度高蓝色背景亮度低所以gray 120就能得到字符为白色的二值图。120 这个阈值不需要特别精确因为蓝色背景和白色字符的灰度差通常很大只要不是强逆光随便一个中间阈值都能分开。保险的做法是先统计灰度直方图找两个峰之间的谷底做阈值。gray rgb2gray(plate); b gray 120; % 白色字符保留为1蓝色背景置0 proj sum(b, 1); % 按列求和得到竖直投影逻辑说明proj是一个行向量长度等于车牌宽度每个元素是该列上白色像素的总数。字符所在的列投影值高字符间隙的列投影值接近 0。这一步就是“列投影法”的核心依据。投影向量还能顺便看出字符是不是倾斜如果投影曲线的波峰高度从左到右递减说明车牌在水平方向有旋转。4.2 列投影分割与粘连字符拆分的边界条件拿到投影向量后最简单的分割方法是设定一个阈值把连续超过阈值的列划成一个字符段。实际车牌图像存在三个问题铆钉和边框产生额外段字符间有轻微粘连汉字“京”“沪”的内部笔画复杂导致投影曲线起伏大。下面的代码用连续区间提取加宽度约束来处理。function chars splitChars(plate) gray rgb2gray(plate); b gray 120; proj sum(b, 1); th max(proj) * 0.05; % 动态阈值取最大投影值的5% on proj th; d diff([0 on 0]); % 边缘检测1起点-1终点 starts find(d 1); ends find(d -1) - 1; avgW median(ends - starts 1); % 中位字符宽度更抗干扰 chars {}; for i 1:numel(starts) thisW ends(i) - starts(i) 1; if thisW avgW * 1.6 n round(thisW / avgW); % 粘连字符个数估算 step floor(thisW / n); for k 0:n-1 c starts(i) k * step; chars{end1} imcrop(plate, [c 1 step size(plate, 1)]); %#okAGROW end else chars{end1} imcrop(plate, [starts(i) 1 thisW size(plate, 1)]); %#okAGROW end end end参数说明th max(proj) * 0.05是动态阈值避免固定阈值在暗光下失效。median比mean更适合计算常规字符宽度因为汉字内部的断开段会产生窄的伪字符拉低平均值而中位数能抵抗离群值。avgW * 1.6是粘连判断阈值一段连续区域的宽度超过平均字符宽 1.6 倍就认为是两个字符粘在一起。分割完成后用imshow(cell2mat(chars), InitialMagnification, fit)拼一起看一眼能立刻发现分割错误。如果字符序列数量不是 7优先怀疑二值化阈值选错或车牌框没抠正。4.3 模板匹配的归一化相关计算与误识别字符对字符识别最直接的手段是模板匹配。思路很简单把分割出的字符图像缩放成固定大小和模板库里的每个字符图做归一化相关corr2相似度最高的模板就是识别结果。对蓝底白字的标准车牌这一套在没有严重形变时识别率能到 90% 以上足够作为课程设计基线。function result recognizeChars(chars, templateDir) result ; for i 1:numel(chars) c imresize(chars{i}, [24 12]); % 字符统一缩放 files dir(fullfile(templateDir, *.png)); bestScore -inf; bestName ?; for j 1:numel(files) t imread(fullfile(files(j).folder, files(j).name)); t imresize(t, [24 12]); score corr2(double(c), double(t)); % 二维相关系数 if score bestScore bestScore score; [~, name, ~] fileparts(files(j).name); bestName name(1); % 取文件名的第一个字符 end end if bestScore 0.4 result [result bestName]; %#okAGROW else result [result ?]; %#okAGROW end end end参数说明模板字符统一缩放到 24x12这个比例接近车牌的字符宽高比太大的尺寸对匹配精度提升有限但会明显拖慢速度。corr2返回 [-1, 1] 的相关系数1 表示完全一致。0.4 作为置信阈值低于这个值说明当前字符和模板库所有样本都不匹配可能是分割出了半个字符也可能是出现了模板库里没有的字体。模板匹配的实际瓶颈在易混淆字符对。下面这些组合在模板尺寸小、分辨率低时经常互相错认后续优化时要单独处理易混淆对原因处理方式O 与 0轮廓几乎相同车牌规则中 O 不出现模板库直接不放 OZ 与 2笔画结构相似扩大模板库加入多字体样本D 与 O左侧竖线丢失匹配前做一次水平膨胀8 与 B上下两个闭合圈结合字符宽度约束判断1 与 I细笔画差异小放大到 48x24 再匹配提示如果一轮模板匹配的识别率到了瓶颈可以把识别环节换成深度学习方案。Matlab 的 Deep Learning Toolbox 里可以直接搭建小型 CNN训练集用模板字符做随机旋转、缩放、加噪声生成这一套对倾斜车牌的提升非常明显。深度学习matlab 这条路适合在模板匹配已经调不动的时候再走不要一上来就上 CNN。5. 验证识别率批量测试、错误样本导出与Matlab小技巧5.1 批量统计识别率文件名即标签单张图像能识别成功不代表系统可用。车牌识别这种工程真实价值体现在批量测试上。常见的做法是建一个testset目录每张测试图片的文件名直接写成标准车牌号比如粤B12345.jpg然后用脚本遍历整个目录把识别结果和文件名对比自动统计识别率。files dir(fullfile(testset, *.jpg)); correct 0; total 0; for k 1:numel(files) img imread(fullfile(files(k).folder, files(k).name)); try [plate, ~] detectPlate(img); if isempty(plate) warning(定位失败%s, files(k).name); continue; end chars splitChars(plate); result recognizeChars(chars, templates); [~, name, ~] fileparts(files(k).name); % 真实车牌号 total total 1; if strcmp(result, name) correct correct 1; else fprintf(识别错误%s - %s\n, name, result); end catch ME warning(处理异常%s%s, files(k).name, ME.message); end end fprintf(识别率 %.1f%%%d / %d\n, correct * 100 / max(total, 1), correct, total);逻辑说明try catch包裹整条处理链某张图在定位阶段崩溃不会让整个循环退出。打印出每一条错误记录方便按错误类型排序。识别率统计结果可以做后续调参的基准线每改一次阈值、形态学核大小就重跑一遍这个脚本用数据判断修改是正向还是负向。5.2 把错误样本自动导出到debug目录只看终端里的“识别错误粤B12345 - 粤B1234”很难定位问题。更有效率的做法是把识别失败的样本连图带标签一起导出到debug/fail目录文件名用“标准答案_识别结果.jpg”的格式。这样调试时打开文件夹就能按文件排序看到哪类错误最集中。badDir debug/fail; if ~exist(badDir, dir) mkdir(badDir); end if ~strcmp(result, name) outName fullfile(badDir, [name _ result .jpg]); imwrite(img, outName); end导出错误样本后可以顺手在原图上把定位框画出来再存一份用来排查是定位框偏了还是字符分割错了。画框只需要两行rectangle(Position, box, EdgeColor, r, LineWidth, 2)配合imwrite。最后用 Windows 照片查看器按名称排序扫一遍 debug 目录错误的共性会非常明显要么全是车牌偏左要么全是数字 2 被识别成 Z问题定位时间能压缩一半以上。本文还有配套的精品资源点击获取
返回列表