ARTICLE DETAIL

资讯详情

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

MATLAB GUI实现的模板匹配车牌识别方法详解

MATLAB GUI实现的模板匹配车牌识别方法详解 简介基于 MATLAB GUI 的车牌识别项目资料包采用模板匹配算法完成车牌识别任务完整覆盖车牌定位、字符分割、汉字识别、数字与字母识别等关键环节适合正在做课程设计、毕业设计或希望入门图像识别的 MATLAB 学习者参照使用。整套资料包含十二个 m 源码文件、图形用户界面 fig 文件以及演示视频可直接运行并观察各阶段处理效果同时内置五十二张 bmp 格式模板图片分别构成数字模板库、字母模板库和汉字模板库另有二十张 jpg 汽车图像用于测试全包共八十七个文件压缩后大小为十四点六二兆字节目录结构清晰体量轻量化便于按需查阅。资源还附带运行结果截图和识别效果演示视频能够辅助验证代码正确性方案可继续扩展到蓝色、绿色、黄色以及新能源车牌等更多应用场景。目前已有二百五十一人学习该资源适合需要透过完整实例理解模板匹配识别原理并动手实践的 MATLAB 使用者。1. 模板匹配车牌识别在 matlab GUI 里解决什么问题停车场道闸、园区门禁这类场景里车辆基本正向驶过识别任务被压成三步定位蓝底白字的矩形区域切出 7 位字符逐个与标准模板比对取最高相似度。模板匹配车牌识别就是这么一套流程不需要训练样本不依赖 GPU一套 matlab 源码加 GUI 就能跑通。对新手它把灰度化、颜色分割、形态学、连通域、投影这些基础模块串成一条看得见中间结果的流水线排错时每步都能可视化对老手真正的难点在定位和分割这套技巧换到 OCR、工业字符检测里照样能用。下文按开发顺序写定位与预处理参数、分割与匹配代码、GUI 回调结构、3 个必调参数和批量验证脚本。2. 车牌识别第一步HSV 颜色空间与形态学定位的参数选择模板匹配自身的分类能力上限有限它区分不了看起来像字但不是字的区域所以定位质量直接决定最终识别率。定位车牌常见做法有两条线边缘检测加形态学以及颜色分割。蓝底白字的物理特征让颜色路线的区分度明显更高我一般从 HSV 分割入手。2.1 用 HSV 颜色空间筛选蓝色车牌区域直接从 RGB 做颜色判断对光照太敏感同一块蓝在阴影和高光下 RGB 分量差别很大HSV 把色相和亮度分开H 通道对比度波动的适应能力更强。matlab 里 rgb2hsv 的输出都归一化到 [0,1]写阈值时最容易在这里踩坑。hsvI rgb2hsv(rawImg); h hsvI(:, :, 1); s hsvI(:, :, 2); v hsvI(:, :, 3); blueMask (h 0.55) (h 0.70) (s 0.30) (v 0.15);参数说明h 取 0.550.70 对应色相环上的蓝色区间很多资料写成 110°130°那是角度制表达直接搬进 matlab 必然查不到区域。s 下限 0.30 用于滤掉灰白物体v 下限 0.15 保证夜间弱光图像里车牌仍能保留。三个边界是定位调试里最常动的参数第 5 章排错还会回到它们。提示黄底黑字的教练车、工程车要加一组黄色阈值h 在 0.100.18、s 在 0.35 以上两组 mask 做或运算再进形态学流程。2.2 形态学闭运算把字符碎片连成车牌带蓝色阈值分割出来的 mask 通常是字符笔画的碎片直接做连通域会得到几十个小块。处理分两步小尺寸结构元素补笔画内部的洞再用横向长条把字符连成整体。mask imclose(blueMask, strel(rectangle, [3, 3])); % 补笔画内部的洞 mask imclose(mask, strel(rectangle, [1, 20])); % 横向连通相邻字符 mask imopen(mask, strel(rectangle, [1, 5])); % 削掉细长噪声1×20 的横向结构元素是关键参数。在 640×480 输入图像里单个字符宽度约 2030 像素1525 像素宽的矩形能把字符间隙填平又不会把车窗轮廓并入候选区。分辨率更高时按比例放大常见做法是取图像宽度的 1/30 到 1/20。最后的 imopen 用 1×5 细长结构元素只削孤立细线不影响车牌整体形状。2.3 连通域筛选规则与候选框参数表闭运算后用 bwconncomp 提连通域regionprops 统计宽高比和填充率按物理尺寸粗筛后取面积最大的候选。cc bwconncomp(mask); stats regionprops(cc, BoundingBox, Area); bestBox []; bestArea 0; for i 1:numel(stats) box stats(i).BoundingBox; w box(3); h box(4); if w 40 || h 10 continue; end ratio w / h; fillRate stats(i).Area / (w * h); if ratio 2 ratio 6 fillRate 0.3 if w * h bestArea bestArea w * h; bestBox box; end end end plateCrop imcrop(rawImg, bestBox);家用蓝牌物理尺寸 440×140mm宽高比约 3.14新能源绿牌 480×140mm约 3.43所以候选区间放宽到 26。填充率是白色像素占外接矩形的比例用来排除车标、反光带这些和车牌同色的干扰块。最后取面积最大者因为画面里车牌通常是最大的蓝色矩形块。参数推荐范围筛选目的最小宽度≥40 px剔除噪声连通域最小高度≥10 px剔除细线宽高比2.06.0匹配车牌物理尺寸填充率≥0.3排除反光带、车标候选面积取最大锁定主车牌3. 字符分割与模板匹配从投影法到归一化互相关定位输出一块车牌子图分割的任务是把 7 位字符逐个切出来。分割质量直接决定匹配得分粘连字符即使模板完全正确也拿不到高分。这一章把投影分割和模板匹配的核心代码一次给全。3.1 垂直投影分割字符与二值化极性切到车牌后先转灰度再二值化。蓝牌白字蓝底的灰度分布是双峰的Otsu 自适应阈值比固定值可靠。这里有一个容易踩的极性坑imbinarize 把高于阈值的像素置 1蓝牌白字直接得到字符1不少教程为了视觉统一强行取反结果把背景当成了字符只有黄底黑字才需要取反处理。plateGray rgb2gray(plateCrop); t graythresh(plateGray); % Otsu 自适应阈值 bwPlate imbinarize(plateGray, t); % 白字灰度高于阈值直接为 1 proj sum(bwPlate, 1); % 垂直投影每列白点数 gapMask proj 3; % 白点少于 3 的列视为字符间隔proj 3 里的 3 是噪声容忍度字符边缘不可避免有孤立白点完全按 0 计数会出现大量假间隔。把间隔序列转成字符段的代码是标准 diff 技巧d diff([1, gapMask, 1]); starts find(d -1); % 1-0进入字符段 ends find(d 1) - 1; % 0-1离开字符段 chars {}; for i 1:numel(starts) w ends(i) - starts(i) 1; if w 5 chars{end 1} bwPlate(:, starts(i):ends(i)); %#okAGROW end end头尾补 1 是为了让首尾字符段也能被检测到w 5 排除噪点形成的窄段。这套起止点提取在各类字符切分里通用OCR 的列切分也是同一套逻辑。提示二值化极性拿不准时先 imshow(bwPlate) 看字符是否为白色再决定要不要加 ~。3.2 汉字分割的固定位置处理7 位车牌第一位是汉字各省简称结构差异很大投影谷底深浅不一按谷底切很容易把鄂赣这类笔画密集的汉字拆成两段。常见做法是不依赖汉字自身的投影谷先定位第二位字母与第三位数字之间的间隔再按字符宽度比例往前推出汉字右边界。这个前提是候选框横向形变不大。如果框有倾斜先用 regionprops(cc, Orientation) 拿角度超过 ±10° 就用 imrotate 转正再分割否则宽度比例推算会失真。3.3 模板匹配核心代码corr2 归一化互相关分割出的字符尺寸不一必须先统一到同一尺寸再比较。corr2 计算二维相关系数等价于去均值、归一化后的内积对比度差异不敏感适合字符笔画粗细波动的场景。function [label, score] matchChar(charImg, templates) charImg imresize(charImg, [40, 24]); bestLabel ; bestScore -1; for i 1:numel(templates) tpl imresize(templates(i).img, [40, 24]); s corr2(charImg, tpl); % 归一化互相关值域 [-1,1] if s bestScore bestScore s; bestLabel templates(i).label; end end label bestLabel; score bestScore; end40×24 是兼顾笔画细节和计算量的常用尺寸。corr2 的弱点是零位移容错分割框偏 2 个像素得分可能从 0.9 掉到 0.6改进做法见 5.4 的 normxcorr2 平移搜索。corr2 和 normxcorr2 都在 Image Processing Toolbox 里没有该工具箱时可以用 corrcoef 对向量化像素做替代。3.4 模板库构成65 个模板一个不能少模板匹配的准确率上限由模板库决定。中国车牌字符集是固定的31 个省级行政区简称、24 个大写字母AZ 不含 I 和 O避免与 1、0 混淆、10 个数字共 65 个模板。少一个小类那一类字符永远识别不对。模板来源两种从实际车牌图截取后手工清洗或用标准字体渲染。第一种贴合现场字形第二种干净但遇到喷绘车牌容易失配。文件名直接用作标签最省事京.png、A.png、0.png装载时用 dir 扫目录文件主名就是 label。字符类别数量覆盖范围汉字31省级行政区简称大写字母24AZ不含 I、O数字1009合计65模板文件按标签命名4. 用 matlab GUI 封装模板匹配识别主流程前三章的函数组织好后GUI 层只是把函数挂到控件回调上。这类教学源码常见做法是用 GUIDE 路线搭一套 .fig 布局文件加 .m 回调文件老版本 matlab 也能打开。App Designer 界面更现代但 .fig 加回调的结构在各类源码包里更常见兼容面更广。4.1 GUIDE 布局与控件清单界面按左上原图、右上过程、下侧控制排布控件分工如下。滑块放在二值化阈值上方便实时对比分割效果比改代码重跑直观得多。控件Tag作用AxesaxesOriginal显示原始图像AxesaxesProcessed显示定位或分割结果Push ButtonbtnOpen选择图片Push ButtonbtnRecog一键识别Edit TexteditResult显示识别结果Edit TexteditScore显示平均相似度SlidersliderThresh调节二值化阈值GUIDE 里双击控件生成回调骨架Slider 记得在属性面板设 Min、Max、Value并在回调里把当前值同步显示到某个文本位上否则调了半天不知道阈值落在哪。4.2 打开图片回调与 handles 数据传递GUI 各回调之间靠 handles 结构体传数据改动手柄后必须用 guidata 写回这是新手最容易漏的一步。function btnOpen_Callback(hObject, eventdata, handles) [fname, fpath] uigetfile({*.jpg;*.jpeg;*.png;*.bmp, 图像文件}, 选择含车牌图片); if isequal(fname, 0) return; end handles.rawImg imread(fullfile(fpath, fname)); axes(handles.axesOriginal); imshow(handles.rawImg); title(原始图像); guidata(hObject, handles); % 写回其他回调才能读到 enduigetfile 返回 0 表示取消必须先 return否则 imread 直接报错。fullfile 拼路径避免手写分隔符的跨平台问题。guidata(hObject, handles) 是句柄写回的固定格式少了这一行识别回调里 isfield(handles, rawImg) 永远为假。4.3 识别回调定位、分割、匹配串成一条线识别按钮按定位→二值化→分割→匹配→写结果顺序执行每步失败都给可读提示而不是让异常崩出红字。function btnRecog_Callback(hObject, eventdata, handles) if ~isfield(handles, rawImg) msgbox(请先点击“打开图片”, 提示); return; end plateCrop locatePlate(handles.rawImg); if isempty(plateCrop) msgbox(未定位到车牌区域请调整 HSV 阈值, 提示); return; end axes(handles.axesProcessed); imshow(plateCrop); title(定位结果); gray rgb2gray(plateCrop); bwPlate imbinarize(gray, graythresh(gray)); chars segmentChars(bwPlate); if numel(chars) ~ 7 msgbox(sprintf(分割出 %d 个字符家用车牌应为 7 位, numel(chars)), 提示); return; end result ; sumScore 0; for i 1:numel(chars) [label, score] matchChar(chars{i}, handles.templates); result [result, label]; %#okAGROW sumScore sumScore score; end set(handles.editResult, String, result); set(handles.editScore, String, sprintf(平均相似度 %.3f, sumScore / numel(chars))); endnumel(chars) ~ 7 的判空逻辑要放在匹配之前分割阶段发现位数不对问题多半在定位框或二值化而不是模板。平均相似度写出来是为了给阈值调整一个可量化的反馈后面第 6 章批量验证时它也是重要参考。4.4 模板装载与 OpeningFcn模板只在 GUI 启动时装载一次放在 OpeningFcn 里避免每次点击识别都重复读盘。function openingFcn(hObject, eventdata, handles, varargin) handles.templates loadTemplates(templates/); guidata(hObject, handles); end function templates loadTemplates(folder) files dir(fullfile(folder, *.png)); templates struct(label, {}, img, {}); for i 1:numel(files) [~, name, ~] fileparts(files(i).name); im imread(fullfile(folder, files(i).name)); if size(im, 3) 3 im rgb2gray(im); end templates(i).label name; templates(i).img im; end endtemplates 目录和 .m 文件放同一层级用相对路径避免换机器改路径。struct(label, {}, img, {}) 创建空结构体数组循环里按下标赋值即可。imread 读彩色图时在装载处统一转灰度否则 matchChar 里 corr2 收到三维数组会直接报错。5. 模板匹配的 3 个必调参数与识别失败排错模板匹配项目跑通容易跑稳难三处参数几乎决定识别率上限二值化阈值、尺寸归一化方式、相似度判据。按这个顺序调能覆盖大多数识别失败。5.1 二值化阈值固定、Otsu 与局部自适应三选一三种方案适用场景不同我按光照条件切换。% 1. 固定阈值顺光且曝光一致时可用最省事 bw1 imbinarize(gray, 0.6); % 2. Otsu 自适应直方图双峰明显时效果好 bw2 imbinarize(gray, graythresh(gray)); % 3. 局部自适应适合车牌一半亮一半暗 bw3 imbinarize(gray, adaptive, Sensitivity, 0.45);前两种是全局阈值整块车牌共用一个门限局部自适应按像素邻域逐点计算能处理阴影遮挡的车牌。Sensitivity 越大保留前景越多0.45 是常用起点。极性规则同 3.1白字蓝底直接用黄底黑字取反。imbinarize 在 R2016a 引入老版本用 im2bw 替代参数语义要重新核对。5.2 尺寸归一化汉字与字母数字按比例分组把全部字符硬缩到 40×24 会让汉字横向压缩变形。汉字接近方块字字母数字是窄体字混在一起用同一个目标宽高比不合理。function normImg resizePreserveAspect(charImg, targetH, targetW) scale targetH / size(charImg, 1); newW max(1, round(size(charImg, 2) * scale)); resized imresize(charImg, [targetH, newW]); if newW targetW c floor((newW - targetW) / 2) 1; normImg resized(:, c:c targetW - 1); else normImg zeros(targetH, targetW, like, resized); left floor((targetW - newW) / 2); normImg(:, left 1:left newW) resized; end end保持长宽比缩放到统一高度宽了居中裁剪窄了左右补零。汉字和字母用同一函数但模板库分组第一位汉字走 32×32 的方形模板第二位起字母数字走 40×24匹配时按字符在车牌里的位置选组而不是让所有模板混在一起竞争。实际替换时把 3.3 的 matchChar 里 imresize(charImg, [40, 24]) 换成 resizePreserveAspect 即可。5.3 相似度阈值与 top2 歧义判定corr2 输出落在 [-1,1]但最高分是谁比分数多高更有信息量。0 和 O、1 和 I、B 和 8 这类相近字形最高分与第二高分差距极小这个差距本身就是风险信号。function [label, score, margin] matchCharTop2(charImg, templates) scores zeros(1, numel(templates)); for i 1:numel(templates) scores(i) corr2(charImg, templates(i).img); end [sortedScores, order] sort(scores, descend); label templates(order(1)).label; score sortedScores(1); margin sortedScores(1) - sortedScores(2); end使用规则score 0.60 直接拒识管理系统里显示无法识别而不是给个错误字符margin 0.08 标记低置信度悬着 top2 候选让人工复核。这两条叠加后系统从总会给答案变成拿不准时承认拿不准对道闸事后追查反而更有用。matchCharTop2 要求输入的样本和模板已经按 5.2 分组归一化否则相似度对比没有意义。5.4 用 normxcorr2 平移搜索救回分割误差corr2 零位移容错的问题用 normxcorr2 解决它一次性计算模板在样本上所有平移位置的归一化互相关峰值即最佳对齐得分。function score bestShiftScore(charImg, tpl) % 把模板四周各裁掉 4 像素留出平移搜索空间 inner tpl(5:end - 4, 5:end - 4); c normxcorr2(inner, charImg); score max(c(:)); % 峰值即最佳对准时的相似度 endnormxcorr2 输出比样本小一圈取 max 即可。它对位移的容忍度来自 inner 相对 charImg 的尺寸差裁 4 像素约等于允许 ±4 像素的错位适合替换 3.1 分割质量不佳时的直接 corr2。计算量比逐个平移循环小一个量级但每字符仍多几十毫秒批量场景按需使用。故障现象根因处置第一位汉字总错汉字模板缺失或字体不符核对 31 个省级简称模板0/O、B/8 混认未按长宽比分组归一化换 5.2 的 resizePreserveAspect定位框切掉半个字符横向闭运算结构元素过宽se2 宽度减半夜间识别率骤降全局二值化扛不住暗光改用 adaptive 局部阈值相似度普遍低于 0.6模板与现场字体差异大用现场样本重建模板库6. 批量验证识别率与模板匹配的扩展方向6.1 用目录名做真值一次跑完整批测试GUI 里一张张点图只能验证功能不能量化效果。我把测试集按真实车牌号.jpg命名写脚本直接调底层识别函数保证和 GUI 回调走同一套 locatePlate、segmentChars、matchChar。function text recognizePlate(img, templates) plateCrop locatePlate(img); if isempty(plateCrop) text ; return; end gray rgb2gray(plateCrop); bwPlate imbinarize(gray, graythresh(gray)); chars segmentChars(bwPlate); text ; for i 1:numel(chars) [label, ~] matchChar(chars{i}, templates); text [text, label]; %#okAGROW end end templates loadTemplates(templates/); files dir(testset/*.jpg); correct 0; for k 1:numel(files) img imread(fullfile(files(k).folder, files(k).name)); pred recognizePlate(img, templates); [~, truth, ~] fileparts(files(k).name); if strcmp(pred, truth) correct correct 1; else fprintf(误识: 真值%s 输出%s 文件%s\n, ... truth, pred, files(k).name); end end fprintf(识别率: %.2f%% (%d/%d)\n, ... 100 * correct / numel(files), correct, numel(files));误识样本逐条打印攒够一批后归类定位框偏了、分割切歪了、匹配选错了三类问题分开统计。按我的经验模板匹配项目里定位和分割贡献七成错误真正匹配选错的不到三成所以排错顺序永远是先看定位框再看切分边界最后才怀疑模板库。6.2 模板匹配的边界与升级路线模板匹配的失效集中在旋转、模糊和强透视车牌偏转超过 15° 或者运动模糊明显归一化互相关就开始不可靠。开源车牌检测识别模型用检测加分类在这个场景能拿更高精度代价是标注数据和算力。对道闸、园区这类可控场景模板匹配仍然够用这也是它在教学源码包里长盛不衰的原因。一个性价比很高的升级保留模板匹配的定位、分割和置信度过滤逻辑只把最后分类器换成轻量 CNN。改造量最小又能吃到深度模型对形变的适应能力。同样的分割匹配管线换一套模板库还能迁移到指纹识别、表计读数识别等项目上这是这类 GUI 源码最常见的二次开发方向。本文还有配套的精品资源点击获取
返回列表