ARTICLE DETAIL

资讯详情

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

基于模板匹配的Matlab车牌字符识别与准确率提升实践

基于模板匹配的Matlab车牌字符识别与准确率提升实践 简介基于模板匹配的汽车牌照字符识别方法提供一套完整的MATLAB实现适用于智能交通、车辆管理与高校图像处理研究。项目以匹配准确率为核心指标串联车牌图像预处理、牌照区域定位、单个字符切割、模板比对和结果输出等环节并给出百分之八十的测试准确率基准可作为传统识别方法的对照基线。压缩包共93个文件大小约4.24MB主体是86张JPG图片包含测试车牌图像与字符模板另有3个M脚本实现主流程与字符分割配合BMP样例图、TXT运行说明和BAK备份文件便于直接运行与二次开发。当前已有235人学习适合需要快速搭建车牌识别流程、验证模板匹配优缺点的读者也适合以此为起点尝试引入卷积神经网络等深度方案进一步优化准确率与鲁棒性。1. 模板匹配在车牌识别里的价值与准确率瓶颈在交通卡口、停车场和车检系统里LPR汽车牌照识别的字符识别环节并不总是需要深度学习。蓝底白字、字体相对统一的车牌用模板匹配做成一个离线识别器在MATLAB里几百行代码就能跑通难点从来不是能不能识别而是匹配准确率能不能达到上线要求。实际项目中常见的情况是单字符在干净样本上准确率能到98%但放到整张车牌上7个字符只要有一个错整牌就判错。字符级准确率和整牌准确率之间不是线性关系0.98的七次方只剩87%左右这就是模板匹配LPR最常见的瓶颈。接下来要聊的是把“能做演示”的模板匹配字符识别改成“能统计结果”的牌照识别流程。2. 牌照识别前的字符图像准备Matlab模板匹配的关键预处理不管匹配函数选得多好字符分割差了后面全白做。模板匹配假设“待识别字符”和“模板字符”处于同一个度量空间所以车牌定位、字符分割和归一化决定了匹配准确率的上限。很多人在字符识别阶段反复调阈值却发现准确率上不去原因多半在预处理环节。2.1 车牌定位用到的形态学与投影分割车牌定位最稳的方式仍然是“边缘检测 形态学闭合 区域筛选”。蓝底白字车牌的字符和底色之间有明显垂直边缘先做垂直边缘响应再用一个横向长条结构元做闭运算就能把字符边缘连成一块近似车牌的区域。MATLAB里可以直接用regionprops拿候选区域的长宽比筛选。function plateROI locatePlate(img) I im2gray(img); % 新版本推荐老版本用 rgb2gray I imadjust(I); % 增强对比度默认按2%饱和度拉伸 bwEdge edge(I, sobel, 0.08, vertical); bwClose imclose(bwEdge, strel(rectangle, [15 5])); bwFill imfill(bwClose, holes); stats regionprops(bwFill, BoundingBox, Area); plateROI []; for k 1:numel(stats) bb stats(k).BoundingBox; % [x y w h] ratio bb(3) / max(bb(4), eps); if stats(k).Area 1500 ratio 2.5 ratio 5.5 plateROI bb; return; end end end这段代码里的imclose结构元是横向长方形宽度根据图像分辨率调整。如果原图是1080p[15 5]够用如果是200万像素以上的大图建议把宽度放到25。regionprops的Area阈值要配合摄像头距离调整否则容易把车头散热器格栅也选进来。长宽比阈值按中国大陆车牌约3:1设计新能源车牌也是宽矩形所以2.5到5.5这个范围足够宽松。拿到plateROI后做字符分割。最常用的是垂直投影分割把二值化后的车牌逐列求和字符位置会出现波峰字符间隙接近零波峰之间的区间就是一个候选字符。function chars segmentPlate(I, plateROI) plate imcrop(I, plateROI); level graythresh(plate); BW imbinarize(plate, level * 0.9); % 阈值压低一点防止笔画断裂 BW bwareaopen(BW, 30); % 去掉面积小于30的离散点 colProj sum(BW, 1); isCharCol colProj max(colProj) * 0.12; d diff([false, isCharCol, false]); startCols find(d 1); endCols find(d -1) - 1; chars arrayfun((s, e) BW(:, s:e), ... startCols, endCols, UniformOutput, false); end垂直投影分割在字符粘连时会失效常见处理是先做一次腐蚀把字符断开同时注意不要腐蚀掉1、I这类细笔画。分割后的 cell 数组长度通常不是7因为车牌上的螺丝、边框和铆钉也会被分割出来。工程上不要直接靠固定个数去截断最好加一个宽度过滤去掉宽度小于牌照宽度十分之一的区域再做一次细分割。2.2 字符归一化和二值化对匹配准确率的影响模板匹配对字符的尺寸和笔画宽度非常敏感。同一个“京”字在720p和1080p下宽度可能差一倍如果直接拿原始尺寸匹配相关峰值会被拉伸失真淹没。所以每个字符在进入匹配前都要缩放到固定尺寸。function T normalizeChar(ch, h, w) ch imresize(ch, [h w], bilinear); ch ch 0.5; % 插值后会出灰边重新二值化 T padarray(ch, [1 2], 0, both); end这里有几个容易忽略的细节第一imresize的默认插值会把二值图变成灰度图不重新二值化会引入伪影第二padarray补的空白边很重要normxcorr2在图像边缘处会做零填充如果字符贴到边框上相关峰容易跑到边缘第三模板和待识别图必须用同一个规范化函数否则匹配函数的输出基准不一致。2.2.1 模板库的目录组织与命名规范字符识别模板库建议按角色分成两个目录省份简称模板和“字母数字”模板。省份简称是汉字字符结构比数字复杂最好不要和字母数字混在一个匹配池里否则匹配运算量变大误判率也会增加。templates/ Province/ 京.png 津.png 沪.png ... LetterDigit/ A.png B.png 0.png 1.png ...模板统一做成黑背景、白笔画和normalizeChar输出的方向一致。如果一个模板本身是反色的匹配结果会完全失真。建议在加载模板时统一检查模板图(20,10)位置的值如果是1而背景是0说明笔画是白色如果反了用imcomplement翻转一次。这一步看起来不起眼却是在多台机器上复现准确率最容易出问题的地方。3. 用Matlab实现字符识别的模板匹配算法与准确率提升预处理准备好之后字符识别就是一个标准的模板匹配问题。Matlab图像处理工具箱里最常用的函数是normxcorr2它对灰度图像做归一化互相关能够抵抗一定程度的线性光照变化适合车牌这种固定字体的场景。3.1 单模板匹配的最小实现假设现在已经拿到一个归一化字符charImg模板库是结构数组tmplSet每个元素包含name和img字段。单模板匹配的核心循环如下function [bestName, score] matchSingle(charImg, tmplSet) maxScore -1; bestName ; for k 1:numel(tmplSet) t imresize(tmplSet(k).img, size(charImg)); c normxcorr2(double(charImg), double(t)); s max(c(:)); if s maxScore maxScore s; bestName tmplSet(k).name; end end score maxScore; endnormxcorr2返回的矩阵尺寸是(H1H2-1) x (W1W2-1)当输入两幅尺寸相同的图时最大值对应的位置就是最佳对齐点。之所以取max(c(:))是因为我们只关心相似度不关心模板在字符上的偏移。注意两个输入都要转成doubleuint8图直接计算会有截断误差。这段代码还有一个性能问题每个模板都要imresize一次模板库到几百个时速度明显变慢。正确的做法是在模板加载阶段就把所有模板统一缩放到与归一化字符相同的尺寸然后存成.mat文件匹配阶段只做normxcorr2。3.2 匹配函数的选型与参数设定模板匹配不是只有一个normxcorr2能选实际项目中需要根据二值化质量选择不同方案。匹配函数计算方式优点缺点normxcorr2归一化互相关对线性光照变化不敏感计算量大均匀区域容易虚高SAD绝对灰度差和速度快阈值直观对光照和二值化噪声敏感余弦相似度展平后向量点积简单适合高维特征不能处理局部形变和位移如果二值化做得好SAD的准确率和normxcorr2差距不大速度却快很多。但如果车牌图像出现反光、阴影二值化后的笔画宽度会变化SAD会立刻劣化normxcorr2的相对优势就体现出来了。我的常见做法是先保留normxcorr2的分数做首轮排序再取排序前三名用SAD做复核两组分数都接受时才认定结果。这个“双函数投票”思路比单纯调高单一阈值稳得多。3.3 多模板与得分融合提高匹配准确率的关键手段同一个字符在上海车牌和北京车牌上的打印字体不完全一样加上磨损、污渍、铆钉遮挡单模板很难覆盖所有形态。字符识别阶段一定要为每个字符准备多个模板模板组内做融合。function [bestName, score] matchWithFusion(charImg, tmplGroups) scores zeros(1, numel(tmplGroups)); for g 1:numel(tmplGroups) tmpMax -inf; for k 1:numel(tmplGroups(g).templates) t tmplGroups(g).templates{k}; c normxcorr2(double(charImg), double(t)); tmpMax max(tmpMax, max(c(:))); end scores(g) tmpMax; end [score, idx] max(scores); bestName tmplGroups(idx).name; end这里每个tmplGroups(g)对应一个字符templates是该字符的多张模板图。组内取最大值意味着只要有一张模板能匹配上就认为该字符命中。这个策略对“同一字符不同形态”比较友好缺点是某个模板如果恰好和背景噪声很像会把分数拉高。更稳的融合方式是取一组模板得分的均值或者取前两个高分的均值而不是直接取最大值。实际调参时可以先看每个组的得分分布如果某个组的最大得分经常超过第二组但结果还是错的就要检查该组模板里是否有过于极端的样本。多模板同时解决了另一个问题模板匹配 LPR 字符识别对噪声敏感单个模板分数普遍偏低。模板组越大分数越稳定匹配准确率对二值化阈值的依赖越小。4. 汽车牌照易混字符与后处理规则修正字符识别不是孤立地对每个分割块做独立决策。汽车牌照本身有严格的字符位置约束把这些约束写进识别流程能过滤掉大量“分数接近但位置明显不对”的错误判断。4.1 中文车牌字符集的组成与易混配对中国大陆蓝牌通常为7位字符第一位是省级行政区简称汉字第二位是发牌机关字母其余位是字母和数字混合新能源车牌是8位。汉字模板之间差异并不想想象中那么明显“京”和“津”在低分辨率下轮廓非常接近字母和数字中0/O、1/I、8/B、2/Z也经常被模板匹配混到一起。如果只做单字符匹配0和O这种组合几乎无法区分因为模板匹配本质上比的是像素重叠程度而0和O在没有上下文时像素分布高度一致。位置约束才是真正的突破口第二位禁止出现O和I所以“A0”这种组合在候选阶段就要被排除。4.2 用Matlab实现候选列表与位置约束修正与其只保留最高分不如对每个字符保存前3个候选再根据位置规则重新选。这样即使第一候选不是正确字符只要错误字符限制在候选列表中规则修正就有机会拉回来。function seq recognizePlate(segments, tmplLib) kBest 3; cand cell(numel(segments), kBest); srt zeros(numel(segments), kBest); for i 1:numel(segments) [srt(i, :), names] getTopK(segments{i}, tmplLib, kBest); cand(i, :) names; end % 第一位必须是省份简称第二位必须是字母 seq cand(:, 1); seq{1} forceAllow(cand(1, :), srt(1, :), tmplLib.provinceSet); seq{2} forceAllow(cand(2, :), srt(2, :), tmplLib.letterSet); % 第三位之后是字母数字混合 for i 3:numel(segments) seq{i} forceAllow(cand(i, :), srt(i, :), tmplLib.alnumSet); end end function picked forceAllow(candNames, candScores, allowedSet) valid ismember(candNames, allowedSet); idx find(valid, numel(candNames)); if isempty(idx) picked candNames{1}; return; end [~, best_i] max(candScores(idx)); picked candNames{idx(best_i)}; endgetTopK内部就是前面写的多模板匹配循环只改成返回排序后的多个候选。forceAllow做的事情是从当前字符的候选列表里选出允许出现的字符再在这些允许字符里挑分数最高的。这里要注意kBest不能太小取3比较平衡如果取1规则修正没有任何回旋空间。这种做法的价值在于它把“字符识别”从单点分类问题变成了带约束的序列解码问题。识别分数只是其中一项证据字符位置是另一项证据二者结合之后0和O的误判会显著下降因为第二位的O直接被规则排除5和S、2和Z这类易混配对也能依赖位置信息做二次筛选。4.3 评估匹配准确率字符级混淆矩阵与错误样本统计改后处理规则必须用数据验证。最强的方法是字符级混淆矩阵它能直接看出哪些模板之间互相抢占分数。trueVec []; predVec []; for i 1:numel(results) trueStr results(i).trueStr; predStr results(i).predStr; if length(trueStr) length(predStr) trueVec [trueVec, trueStr(:)]; predVec [predVec, predStr(:)]; end end cm confusionmat(trueVec, predVec); confusionchart(cm); charAcc sum(diag(cm)) / sum(cm(:)); fprintf(字符级准确率: %.4f\n, charAcc);更贴近项目的是按位置统计准确率第1位、第2位、第3到7位分别统计。通常第1位汉字准确率最低第2位由于只有字母候选准确率较高最后的数字位准确率最高。如果发现第1位把“京”误判成“津”不要急着加模板先看归一化后的字符宽度是否和模板一致。中文字符原本是近正方形而字母数字模板是长方形如果强行把所有字符压成同样尺寸汉字会丢失关键笔画匹配分数自然下降。常见做法是给汉字单独设定归一化尺寸比如[42 42]字母数字用[42 24]。5. 在Matlab中调模板匹配LPR的验证技巧最后说几个能直接落地的验证技巧都是在真实样本上调匹配准确率时最有效的动作。5.1 用多尺度匹配消除归一化误差分割出来的字符宽度并不总是刚好等于模板宽度imresize一次缩放难免丢失细节。更稳的做法是在字符宽度上下浮动百分之五的范围内逐一缩放匹配取所有尺度中最高的相关分数。这个改进能吸收分割框前后偏差带来的误差尤其适合处理字符宽度不稳定、车牌带有一定倾斜的图片。代价是计算量增加但模板匹配 LPR 本来就是对离线图片做识别多跑几轮可接受。5.2 用失败样本回注法决定模板是加还是删每跑完一批测试图片把识别错误的分割块单独存成一个文件夹文件名带上真实标签。连续积累两三百张后按标签分类统计就能看到哪些字符的失败样本最集中。如果是“鲁”和“晋”频繁互混说明这两个模板在笔画轮廓上确实接近需要补充字体变体模板如果是所有字符分数普遍偏低但方向正确优先检查二值化阈值不要继续堆模板。模板不是越多越好同一个字符放太多相似样本会导致组内最高分被噪声样本占据反而拉低准确率。5.3 脚本里加暂停点逐张看分割框在recognizePlate返回后插入一句imshow(plateROI)同时把分割边界画出来按下任意键继续。这个动作看起来原始却能最快发现分割框偏移、宽度过滤过猛、投影粘连三类问题。很多匹配准确率问题根本不是匹配函数造成的而是分割出的字符已经把“1”的右边切掉了一截或者把“0”和“8”的上下口连成了实心块。模板匹配算法本身没有记忆能力输入字符错了后面所有参数调优都没有意义。建议每次调整预处理步骤后先跑完这组视觉检查再进入混淆矩阵统计。本文还有配套的精品资源点击获取
返回列表