ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

MATLAB模板匹配车牌识别:定位分割与normxcorr2打分

MATLAB模板匹配车牌识别:定位分割与normxcorr2打分

简介:面向计算机视觉入门者、课程设计学生及需要复现车牌识别流程的MATLAB用户,这套模板匹配车牌识别示例覆盖从图像输入到字符识别的完整基础链路。内容涉及车牌预处理、边缘检测与二值化分割、单字符切割、模板相似度比较以及GUI交互展示,形成“图像输入—车牌提取—字符识别—界面查看”的实践路径,适合用于教学演示、毕业设计原型和算法对比实验。压缩包约156KB,文件总数显示为0、类型明细暂无数据,具体以压缩包实际内容为准;按内容说明,核心为MATLAB脚本与GUI配置文件。已有2047人学习,说明该案例具有一定参考热度。读者可据此理解模板匹配的实现思路、各模块分工及预处理对识别结果的影响,并借鉴其目录组织与调试方向,为后续改用更鲁棒的识别方案打基础。

1. 从一张抓拍图说起:MATLAB 模板匹配做车牌识别到底靠什么

一张卡口抓拍里,车牌只占几十个像素,还带着运动模糊和逆光。先别急着上深度学习:在 MATLAB 里用模板匹配把车牌识别拆成定位、分割、逐字打分三段,样本少、算力紧的时候更容易先把链路跑通。这里说的模板匹配不是拿整张车牌去碰一张大图,而是先定位车牌、再切出单个字符,最后用归一化互相关给每个字符找最像的模板。

这套方案适合课程设计、小规模卡口原型、嵌入式算法验证,也适合作为深度学习之前的基线:它能让你清楚看到定位、分割、识别每一段分别错在哪。它不适合字库极多、车牌严重倾斜或大角度透视的量产场景,尤其是省级汉字,模板匹配的边界很明显。下面按定位、分割、匹配、调参、验证五段拆开讲,能照着复现,也能看到参数和坑在哪。

2. 车牌定位:Sobel 边缘加形态学闭运算的候选框筛选

车牌定位的第一步不是识别,而是把“可能是车牌”的区域从整张图里捞出来。模板匹配对输入非常敏感,定位框多扩一圈、少切一刀,后面字符分割就会跟着翻车。我一般先用灰度化加 Sobel 边缘做粗筛,再用形态学闭运算把密集的字符边缘连成块,最后用长宽比、面积、填充率三个硬指标筛候选框。颜色先验可以作为补充,但不要一上来就把蓝牌、黄牌、绿牌写死,换一批光照条件就容易失效。

2.1 灰度化与 Sobel 边缘:为什么先做粗筛而不是直接匹配整牌

整牌模板匹配听起来省事,实际上很难用:车牌字体、底色、光照、拍摄距离一变,整牌模板的归一化互相关分数就会抖。更稳的做法是先找字符密集的垂直边缘。车牌字符排列紧密,垂直边缘响应强,Sobel 算子计算轻,阈值调起来也直观。Canny 边缘更细,但断点多,后面闭运算要补的洞更多,调参反而更玄学。

读图后先做一次轻高斯滤波,压制压缩噪声和路面纹理。Sobel 阈值不要死记,先看边缘图:车牌字符边缘应连续,背景纹理应被压掉大半。如果边缘图里树叶、栅栏、广告牌全是边缘,说明阈值太低;如果车牌字符只剩零散点,说明阈值太高。常见做法是把阈值放在 0.06 到 0.12 之间,再根据分辨率微调。

I = imread('car_01.jpg'); G = rgb2gray(I); G = imgaussfilt(G, 1.2); % 轻滤波,压掉压缩噪声 E = edge(G, 'sobel', 0.08); % Sobel 垂直边缘粗筛 se = strel('rectangle', [5 19]); % 横向闭运算,把字符连成块 M = imclose(E, se); M = imfill(M, 'holes'); M = bwareaopen(M, 400); % 去掉小面积噪点 stats = regionprops(M, 'BoundingBox', 'Area'); cands = []; for k = 1:numel(stats) bbox = stats(k).BoundingBox; % [x y w h] w = bbox(3); h = bbox(4); if h < 20 || w < 60 continue; end ar = w / h; % 长宽比 fillRatio = stats(k).Area / (w * h); if ar > 2.2 && ar < 5.0 && fillRatio > 0.25 cands(end+1, :) = bbox; end end

逻辑说明:edge负责把字符的垂直边缘留下来,imclose用横向矩形结构元素把相邻字符边缘连成一条候选带,imfill补内部空洞,bwareaopen清掉小噪点。regionprops拿到的BoundingBox是[x y w h],后面裁剪时不要按[x y w h]直接索引图像,否则会错位。候选框筛选先看高度和宽度下限,再看长宽比和填充率,能把大部分路面纹理和广告牌挡掉。

参数说明:imgaussfilt的1.2是经验值,分辨率翻倍时可以加到2.0左右;Sobel 阈值0.08不是固定答案,边缘太少就降到0.06,边缘太脏就升到0.12;闭运算核[5 19]里,高度负责连接字符上下笔画,宽度负责把字符横向串起来。更稳的做法是按车牌预期高度反推:核高约为车牌高度的0.08倍,核宽约为0.30倍。bwareaopen的400也要跟分辨率走,图像缩小一半时面积阈值大约要降到四分之一。

2.2 形态学闭运算与候选连通域:把字符连成一块“车牌形状”

闭运算是定位环节最影响召回率的一步。核太小,字符连不成块,车牌会被切成七八个碎连通域;核太大,车牌会和保险杠、车标、格栅粘在一起,候选框直接跑偏。我的习惯是先用小核跑一遍,看车牌区域是否形成稳定长条;如果没有,再按候选高度动态加核,而不是全图用一个固定大核硬怼。

横向闭运算之后,车牌区域通常表现为一个横向长条,内部有空洞,边缘有毛刺。imfill补洞之后,再用imopen去掉细小的横向粘连,可以明显减少“车牌和格栅粘在一起”的情况。注意imopen的结构元素不要太大,否则会把车牌两端切掉。定位阶段的目标不是完美分割,而是给后面字符分割一个大致正确的裁剪框,所以召回率优先,精确率靠候选筛选补。

% 在粗候选基础上,按候选高度重新闭运算 for k = 1:size(cands, 1) h = cands(k, 4); se2 = strel('rectangle', [max(3, round(0.08*h)), max(9, round(0.30*h))]); M2 = imclose(E, se2); M2 = imopen(M2, strel('rectangle', [3 5])); M2 = imfill(M2, 'holes'); % 后续可在 M2 上重新取连通域,替换 cands(k,:) end

逻辑说明:这段不是必须每次都跑,而是在固定核效果不稳定时用的二次修正。先拿粗候选的高度h,再反推闭运算核,能避免“近处车牌核太小、远处车牌核太大”的问题。imopen放在闭运算后面,用来削掉细横向粘连,结构元素保持小尺寸,避免切掉车牌边缘。

参数说明:0.08*h控制上下连接强度,0.30*h控制横向连接长度;[3 5]的开运算核只做轻微去毛刺,不要超过[5 9]。如果车牌在图像里只占 30 到 50 像素高,核高不要低于 3,否则闭运算基本不起作用。二次修正会增加计算量,实时场景里可以只对前三个候选框做。

2.3 候选框打分:长宽比、面积、投影密度三个硬指标

候选框筛完后,不要直接取面积最大的那个。面积最大的往往是车身或广告牌,车牌经常排在第二、第三。更稳的做法是给每个候选框打一个综合分:长宽比越接近车牌真实比例越好,面积占比落在合理区间加分,候选区域内的边缘投影密度越高加分。中国蓝牌的长宽比大约在 3.1 左右,但透视和裁剪会让它落到 2.5 到 4.5 之间,所以区间不要卡太死。

指标常用范围作用调大后的影响调小后的影响
长宽比2.2 到 5.0排除方形广告牌、车标候选变少,倾斜车牌易漏候选变多,误检增加
面积占比全图 0.3% 到 12%排除太远和太近的块近处大车牌易漏远处小车牌易漏
填充率大于 0.25排除稀疏纹理字符断裂时易漏噪点块易混入
边缘投影密度大于 0.08看字符密集程度模糊车牌易漏栅栏纹理易混入

综合分可以用简单加权,不必上机器学习。长宽比偏差做归一化,面积占比做对数归一化,投影密度直接线性归一化,三项相加取最高分。投影密度这样算:把候选框裁出来,在边缘图上按列求和,再除以候选高度,最后取均值。字符密集的车牌,这个值会明显高于路面和车身。

bestScore = -inf; bestBBox = []; for k = 1:size(cands, 1) bbox = round(cands(k, :)); x = max(1, bbox(1)); y = max(1, bbox(2)); w = min(size(E,2)-x+1, bbox(3)); h = min(size(E,1)-y+1, bbox(4)); cropE = E(y:y+h-1, x:x+w-1); colDensity = sum(cropE, 1) / max(1, h); density = mean(colDensity); ar = w / h; arScore = exp(-abs(ar - 3.1) / 1.2); areaScore = exp(-abs(log((w*h) / (size(E,1)*size(E,2))) - log(0.02)) / 1.5); score = 0.45*arScore + 0.25*areaScore + 0.30*min(1, density/0.15); if score > bestScore bestScore = score; bestBBox = [x y w h]; end end

逻辑说明:cropE是候选框内的边缘图,按列求和再除以高度,得到每一列的平均边缘数,最后取均值作为投影密度。长宽比用高斯型打分,越接近 3.1 分越高;面积占比用对数打分,避免大面积候选一家独大;投影密度做上限截断,防止极密纹理把分数拉爆。三项加权后取最高分,比单纯取最大面积稳得多。

参数说明:3.1是蓝牌常见比例,黄牌、新能源牌可以改成对应比例或放宽区间;1.2和1.5是打分衰减系数,调大更宽松,调小更严格;权重0.45/0.25/0.30可以按场景改,字符模糊时提高投影密度权重,候选框乱飞时提高长宽比权重。定位阶段不要追求一张图只出一个框,保留前三个候选给后面分割做备用,能减少整条链路因为一次定位失败而全盘皆输的概率。

3. 字符分割:Otsu 二值化、垂直投影和七字符切分

定位框拿到后,真正决定识别上限的是字符分割。模板匹配是逐字比对,切多一个边框、少切半个字符,分数都会掉。车牌字符不是等宽排列,第一位汉字通常更宽,第二位字母和后面数字之间可能有小圆点,新能源车牌还是八位。分割阶段要先校正候选框、统一高度、二值化、去边框,再用垂直投影找字符间的波谷,最后用连通域和高度过滤修掉碎块。

3.1 候选车牌校正与 Otsu 二值化:先让字符和底色分开

候选框裁剪出来先不要直接二值化。透视会让车牌上下边不平行,轻微倾斜会让垂直投影的波谷变浅。常见做法是用候选框的最小外接矩形做一次仿射校正,或者用小角度搜索:在负 8 度到正 8 度之间旋转,取垂直投影方差最大的角度。这个操作不复杂,但对分割稳定性提升很明显。校正后再把车牌高度统一到 80 像素左右,宽度按比例缩放,避免固定宽高比把字符拉变形。

二值化优先用 Otsu,不要一上来就自适应阈值。Otsu 对蓝底白字、黄底黑字这类对比度高的车牌足够稳,计算也轻。自适应阈值在污损、逆光、阴影下更容易出局部噪点,后面还要花时间清。二值化后要判断极性:蓝牌白字在灰度图里字是亮的,二值化后字是 1;黄牌黑字反过来。简单判断方法是比较前景和背景的平均灰度,如果前景比背景暗,就取反。

function [bw, plateGray] = binarizePlate(plateGray) plateGray = imresize(plateGray, [80 NaN]); % 统一高度,宽度按比例 level = graythresh(plateGray); bw = imbinarize(plateGray, level); if mean(plateGray(bw)) < mean(plateGray(~bw)) bw = ~bw; % 保证字符为白 end bw = imclearborder(bw); % 去掉贴边边框 bw = bwareaopen(bw, 12); % 去掉小噪点 end

逻辑说明:imresize(plateGray, [80 NaN])把高度固定到 80,宽度按原比例自动算,避免车牌被横向拉长。graythresh给全局阈值,imbinarize生成二值图。极性判断用前景和背景的平均灰度比较,能同时兼容蓝牌和白底黑字。imclearborder清掉与图像边界相连的连通域,车牌边框通常会被去掉;bwareaopen清掉小噪点,但面积阈值不能太大,否则会把数字1或字符笔画去掉。

参数说明:高度80是模板匹配常用的归一化高度,模板可以按20x40建,字符图留到24x44做搜索边距。bwareaopen的12是像素面积,分辨率更高时要按比例加。如果字符笔画被削断,先把12降到6到8;如果噪点太多,再往15到20调。Otsu 在车牌区域占满裁剪框时效果最好,如果裁剪框里带了很多车身,先缩小框再二值化。

3.2 去边框与垂直投影切分:七个字符不是等宽

垂直投影是字符分割的核心:把二值图按列求和,字符所在列投影值高,字符间隙投影值低。理想情况下,投影曲线会出现七个峰和六个谷,按谷切就能拿到七个字符。实际图像里,字符粘连、边框残留、铆钉和污损都会让谷变浅。我的做法是先平滑投影曲线,再用相对阈值找连续段,最后用宽度和高度过滤修掉碎段。

投影阈值不要用固定值。车牌高度归一化到 80 后,字符高度大约在 40 到 70 像素之间,单列投影值最大可能到 70。阈值取最大投影的10%到15%,再设一个绝对下限3,能适应大部分情况。平滑用三点滑动平均,压掉单列噪点。找到连续段后,宽度太窄的段当噪点丢掉,宽度超过车牌宽度四分之一的段当粘连块,后面用连通域再切。

v = sum(bw, 1); v = movmean(v, 3); thr = max(3, 0.12 * max(v)); mask = v > thr; d = diff([0 mask 0]); starts = find(d == 1); ends = find(d == -1) - 1; chars = {}; for k = 1:numel(starts) w = ends(k) - starts(k) + 1; if w < 6 || w > 0.25 * size(bw, 2) continue; end col = bw(:, starts(k):ends(k)); rows = find(any(col, 2)); if isempty(rows) continue; end h = rows(end) - rows(1) + 1; if h < 0.35 * size(bw, 1) || h > 0.95 * size(bw, 1) continue; end charImg = col(rows(1):rows(end), :); charImg = imresize(charImg, [24 44]); chars{end+1} = charImg; end

逻辑说明:v是列投影,movmean做三点平滑,thr同时受相对值和绝对值约束。diff找上升沿和下降沿,得到每个连续段的起止列。宽度过滤去掉噪点和过宽粘连块,高度过滤去掉上下边框残留。rows找字符实际上下边界,裁掉空白行后再缩放到24x44,给模板匹配留出平移余量。

参数说明:0.12是投影阈值比例,字符间隙浅时降到0.08,噪点多时升到0.15。w < 6是最小字符宽度,分辨率低时降到4。0.25 * size(bw,2)是最大字符宽度,汉字可能接近这个上限,如果第一位汉字总被丢掉,可以单独放宽到0.30。0.35和0.95是字符高度范围,低于下限说明是边框或噪点,高于上限说明上下边框没清干净。

3.3 连通域修正与尺寸归一化:给模板匹配准备统一输入

垂直投影切出来的字符不一定刚好七个。常见情况是数字1太窄被丢掉,或者两个字符粘连被当成一个宽块。这时候不要硬凑七个,先用连通域做一次修正:在二值图上取连通域,按外接矩形宽度和高度过滤,再把水平重叠的连通域合并或拆分。对于粘连块,可以在块内重新做垂直投影,找局部谷点切开;对于碎块,可以按水平距离合并。

尺寸归一化是模板匹配的前置条件。模板库按20x40建,字符图缩放到24x44,这样normxcorr2可以在字符图内滑动,允许上下左右各 2 像素的平移。不要把字符图直接缩放到20x40再和同尺寸模板做相关,同尺寸时相关图的边界效应会让分数虚高,看起来每个字符都很像,实际错得离谱。归一化时保持字符宽高比,不要强行拉成固定宽高,否则1会变胖、0会变瘦。

% 对粘连块做局部投影再切 function parts = splitWideBlock(block) v = sum(block, 1); v = movmean(v, 3); thr = max(2, 0.10 * max(v)); mask = v > thr; d = diff([0 mask 0]); starts = find(d == 1); ends = find(d == -1) - 1; parts = {}; for k = 1:numel(starts) if ends(k) - starts(k) + 1 < 4 continue; end part = block(:, starts(k):ends(k)); part = imresize(part, [24 44]); parts{end+1} = part; end end

逻辑说明:宽块内部的投影曲线通常还会保留一个小谷,重新做局部投影能把粘连字符切开。阈值用0.10倍局部最大值,比全局阈值更敏感。切出来的小块再缩放到24x44,和正常字符保持同一输入规格。这个函数只对宽块调用,不要全图跑,否则会把正常字符切碎。

参数说明:0.10是局部阈值比例,粘连严重时降到0.06,字符内部笔画间隙大时升到0.14。4是最小切块宽度,低于这个值基本是笔画碎片。如果切开后字符数量超过预期,不要继续硬切,回到二值化检查是不是边框没清干净。分割阶段保留一份中间图很有必要,后面识别错了还能回头看是切歪了还是模板不像,没有中间结果排错就是黑匣子。

4. 模板匹配识别:normxcorr2 打分与模板库构建

分割出来的字符图进入识别阶段,模板匹配的核心就一件事:给每个字符图找最像的模板,并给出一个可信分数。MATLAB 里常用normxcorr2做归一化互相关,它对整体亮度线性变化不敏感,适合不同曝光下的车牌。但模板库怎么建、字符集怎么定、阈值怎么设,比函数本身更影响结果。模板库不是越多越好,字体、尺寸、极性不统一,分数会互相打架。

4.1 模板库怎么建:字符集、字体和尺寸的三个约定

模板库先定字符集。中国车牌后六位主要是数字和字母,字母里通常不用I和O,避免和1、0混淆。省级汉字单独建一套,不要和字母数字混在一起打分,因为汉字笔画复杂,模板匹配分数天然偏低,混在一起容易被字母数字抢走。常见做法是第一位汉字走单独模板库或省份候选表,后六位走数字字母模板库。

字体和尺寸要统一。模板图统一缩放到20x40,二值化后字符为白、背景为黑,保存为 PNG。每个字符至少准备三种字体或三种粗细,比如黑体、宋体、等线,再加一个加粗版本。模板文件名用字符_序号.png,加载时按文件名前缀取标签。模板不要用截图里直接裁下来的字符,那样会把这些样本的噪声、倾斜、断笔画带进模板库,换一批图就翻车。

files = dir(fullfile('templates', '*.png')); templates = struct('label', {}, 'img', {}); for i = 1:numel(files) [~, name] = fileparts(files(i).name); label = extractBefore(name, '_'); img = imread(fullfile('templates', files(i).name)); if size(img, 3) == 3 img = rgb2gray(img); end img = imresize(img, [20 40]); if mean(img(:)) > 127 img = 255 - img; % 统一成白字黑底 end templates(end+1) = struct('label', label, 'img', img); end

逻辑说明:dir遍历模板目录,extractBefore从文件名取标签,比如A_01.png取到A。彩色模板先转灰度,再统一缩放到20x40。极性判断用全图均值,均值偏亮说明是黑字白底,取反成白字黑底。结构体数组保存标签和图像,后面匹配时直接遍历。

参数说明:模板尺寸20x40对应字符图24x44,搜索边距各 2 像素。模板数量按字符集乘字体数控制,数字 10 个、字母 24 个、汉字按需,每个字符 3 到 5 个模板就够起步。模板不是越多越好,同一字符模板太杂会让 top1 和 top2 分数差变小,拒识率上升。模板保存前最好人工看一眼,断笔画、带边框、倾斜严重的模板直接删掉。

4.2 用 normxcorr2 给单字打分的最小脚本

normxcorr2的用法是R = normxcorr2(T, C),T是模板,C是待匹配字符图。模板必须不大于字符图,否则会报错。返回值R是相关图,取最大值作为匹配分。因为字符图是24x44、模板是20x40,相关图会有 5 行 5 列,最大值对应最佳平移位置。这个平移余量能吸收分割时的一两像素偏差,比同尺寸直接算相关系数稳。

匹配分范围在负 1 到 1 之间,越接近 1 越像。实际车牌字符受污损、断笔画、二值化噪点影响,正确字符的分数 often 在 0.55 到 0.85 之间,不一定很高。不要看到 0.6 就以为匹配失败,关键看 top1 和 top2 的差距。如果 top1 是 0.62、top2 是 0.60,说明模板区分度不够,硬输出很容易错;如果 top1 是 0.62、top2 是 0.35,置信度就高得多。

function [label, score, secondLabel, secondScore] = matchChar(charImg, templates) charImg = imresize(charImg, [24 44]); scores = -inf(numel(templates), 1); for k = 1:numel(templates) T = templates(k).img; R = normxcorr2(T, charImg); scores(k) = max(R(:)); end [sorted, idx] = sort(scores, 'descend'); label = templates(idx(1)).label; score = sorted(1); secondLabel = templates(idx(2)).label; secondScore = sorted(2); if score < 0.55 || (score - secondScore) < 0.03 label = '?'; end end

逻辑说明:先把字符图统一到24x44,再遍历所有模板做normxcorr2,取相关图最大值。排序后拿 top1 和 top2,如果 top1 分数低于阈值,或者 top1 和 top2 差距太小,就输出?表示拒识。拒识不是失败,是把不确定样本留给人工复核或二次处理,比硬输出一个错字更有用。

参数说明:0.55是拒识下限,样本干净时可以升到0.65,样本模糊时降到0.45。0.03是 top1 和 top2 的最小分差,易混字符多时升到0.05,模板区分度高时降到0.02。templates为空或只有一个模板时,idx(2)会越界,实际代码里要先判断模板数量。字符图缩放到24x44后不要再做其他形态学操作,否则模板和输入的处理不一致,分数没有可比性。

4.3 多模板投票与拒识阈值:别让低分字符硬输出

单模板匹配容易被字体和笔画粗细带偏,多模板投票更稳。每个字符有多个模板时,可以取所有模板的最大分,也可以取每个字符 top2 的平均分,再按字符类别投票。我的习惯是先用最大分找候选类别,再对同一类别的多个模板取平均,最后看类别之间的分差。这样既保留多字体的覆盖能力,又不会因为某个模板偶然高分就定结果。

拒识阈值要按验证集调,不要拍脑袋。先跑一批已知车牌,统计正确字符的分数分布和错误字符的分数分布,取一个能挡住大部分错误、又不会拒掉太多正确字符的阈值。如果业务允许人工复核,阈值可以保守一点,宁可多拒识;如果要求全自动输出,阈值就要放低,同时接受更高的错字率。这个取舍没有标准答案,取决于场景。

% 按类别聚合分数 labels = unique({templates.label}); classScore = -inf(numel(labels), 1); for i = 1:numel(labels) idx = find(strcmp({templates.label}, labels{i})); s = -inf; for k = idx R = normxcorr2(templates(k).img, charImg); s = max(s, max(R(:))); end classScore(i) = s; end [best, bestIdx] = max(classScore);

逻辑说明:先取所有模板标签的唯一值,再对每个类别遍历其模板,取该类别最高分。这样每个字符类别只有一个分数,避免模板多的类别靠数量占优。后面可以按classScore排序,取 top1 和 top2 做拒识判断。

参数说明:类别分数聚合用最大值,不要用平均值,平均值会被差模板拉低。如果某个类别模板特别多,可以在类别内部先取 top2 平均,再参与类别排序。拒识阈值仍然作用在类别分数上,不要作用在单模板分数上,否则模板数量会影响阈值含义。

5. 模板匹配避坑:定位偏大、字符粘连、易混字符的 5 个排查记录

模板匹配车牌识别看起来链路短,实际翻车点很集中:定位框偏大、字符粘连、分数虚高、汉字识别崩、换一批图参数全废。下面五条是我踩过的血泪经验,每条按现象、原因、解决写。排错时一定要保存中间结果,定位框、二值图、分割字符、匹配分数都留一份,不然只能靠猜,连后悔药都没得吃。

5.1 现象:定位框把整辆车都框进来;原因:闭运算核过大;解决:按车牌高度反推核尺寸

现象很直观:候选框宽度是车牌的两三倍,里面带保险杠、车标、格栅,字符分割时投影曲线全是峰,根本找不到七个字符。原因通常是闭运算核固定得太大,横向把车牌和车身纹理连成一片,bwareaopen又没把大块去掉。另一个原因是 Sobel 阈值太低,车身边缘全被保留,闭运算后直接糊成一大块。

解决分两步。先把闭运算核改成按候选高度反推,粗定位时用小核,拿到候选高度后再用0.08*h和0.30*h重跑。第二,在候选筛选里加填充率上限,填充率太高的块往往是车身或纯色区域,不是车牌。车牌区域内部有字符间隙,填充率通常在 0.3 到 0.7 之间,接近 0.9 的块要警惕。

% 粗定位用小核,避免一开始就粘连 seCoarse = strel('rectangle', [3 9]); M = imclose(E, seCoarse); M = imfill(M, 'holes'); M = bwareaopen(M, 200); % 候选筛选加填充率上限 fillRatio = stats(k).Area / (bbox(3) * bbox(4)); if fillRatio > 0.85 continue; % 太满的块多半是车身或色块 end

逻辑说明:粗定位用小核保召回,拿到候选后再按高度动态闭运算。填充率上限挡掉纯色块和车身,减少后续分割压力。这个改动不会让定位变慢太多,但能明显减少“整辆车被框进来”的情况。

参数说明:粗核[3 9]适合车牌高度 40 到 80 像素的图像,分辨率更高时按比例加。填充率上限0.85可以放到0.90,但不要取消,否则大色块很容易混进来。如果车牌本身字符断裂严重,填充率会偏低,这时候不要用填充率下限卡太死。

5.2 现象:字符粘连成一块;原因:二值化阈值和低分辨率;解决:局部自适应加投影波谷

现象是垂直投影只出现三四个峰,切出来的字符宽度过大,模板匹配时一个块里塞了两个字符,分数普遍偏低。原因有两个:一是车牌分辨率太低,字符间隙只有一两个像素,二值化后间隙被填满;二是 Otsu 全局阈值在阴影或反光下把背景也变成前景,字符和边框粘在一起。

解决先查分辨率。车牌高度低于 30 像素时,字符间隙基本保不住,先做超分辨率或放大再二值化,放大用双三次插值,不要用最近邻。二值化可以试局部自适应,但窗口不要太小,Sensitivity调低一点,减少噪点。如果还是粘连,在宽块内部重新做垂直投影,找局部波谷切开,切完再用高度过滤。

% 低分辨率车牌先放大再二值化 if size(plateGray, 1) < 40 plateGray = imresize(plateGray, 2, 'bicubic'); end % 局部自适应阈值,窗口取车牌高度的 1/3 左右 bw = imbinarize(plateGray, 'adaptive', ... 'WindowSize', 2*floor(size(plateGray,1)/6)+1, ... 'Sensitivity', 0.42, ... 'ForegroundPolarity', 'bright');

逻辑说明:先放大再二值化,能把字符间隙从一两个像素拉到三四个像素。自适应阈值按局部窗口算,窗口太大接近全局,窗口太小会把字符内部切成噪点。ForegroundPolarity设成bright适合蓝牌白字,黄牌黑字要改成dark或二值化后取反。

参数说明:放大

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

返回列表