ARTICLE DETAIL

资讯详情

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

MATLAB车牌识别GUI实战:HSV定位、形态学与模板匹配

MATLAB车牌识别GUI实战:HSV定位、形态学与模板匹配 简介这份MATLAB车牌识别GUI实现资源包主要面向高校计算机、电子信息工程及数学等专业学生可用于课程设计、期末大作业或毕业设计中的部分功能参考资源基于Matlab语言完成包含GUI交互界面、车牌字符样本和核心识别处理程序帮助使用者理解车牌定位、字符分割、模板匹配等完整流程。压缩包共89个文件以80张jpg字符样本图片为主这些图片对应各地车牌字符模板便于搭建模板库另含3个MATLAB源文件、fig界面文件、doc说明文档及data数据文件分别承担核心算法、GUI界面、导入说明与数据支持整体包体仅662KB结构清晰下载后即可快速解压、检索和使用。目前已有26人浏览学习。资源内提供“如何导入程序”说明文档方便完成环境配置字符样本覆盖多个省份简称可对照代码运行后的识别结果并观察不同省份字符样本对识别效果的影响适合据此调试与二次开发。对于有一定MATLAB基础、能够独立排查问题的读者这套资源能作为课程设计或毕设项目的有效起点。1. 车牌识别 GUI 到底在做什么程序、GUI、思路三件套很多人以为车牌识别最难的是最后那段字符识别实际把一张白色车身、蓝色招牌或傍晚逆光的照片丢进去最先撑不住的是车牌定位。这个标题把「程序、GUI、思路」三样东西打包在一起思路指 HSV 定位、形态学处理和字符分割这一整条图像处理链路程序指把这串逻辑封装成可复用的函数GUI 指在 MATLAB 里用按钮、坐标轴、编辑框把整条链路变成可以点开使用的界面。适合做 matlab 课程设计、毕业设计的人也适合想系统看一条图像处理流程怎么从前端图像走到后端结果的工程师。下文按「先立思路、再搭 GUI、后调参数、最后封装验证」推进。2. 识别思路先立住HSV 定位、形态学闭运算与字符分割2.1 要用 matlab 的 rgb2hsv 先转一次色空间别直接在 RGB 上卡阈值车牌定位最常用、也最容易讲清楚的是基于颜色的方法尤其是蓝底白字的常规车牌。RGB 三个通道都和光照强度耦合傍晚光线一变、车漆反光一强固定阈值基本失效HSV 把色相H、饱和度S、亮度V拆开定位只依赖 H 和 SV 的变化可以用放宽下界来容忍。matlab 图像处理工具箱里rgb2hsv、strel、regionprops都是现成函数不需要额外安装扩展。使用时注意 H 的取值范围是 0 到 1不是 0 到 360这是刚上手时最容易看反的地方。img imread(car.jpg); hsvImg rgb2hsv(img); H hsvImg(:, :, 1); S hsvImg(:, :, 2); V hsvImg(:, :, 3); mask (H 0.55 H 0.70) ... (S 0.30 S 1.00) ... (V 0.20 V 1.00);mask 是逻辑矩阵1 的位置表示“像蓝色车牌”的像素。后面所有操作都以 mask 为起点这比直接在原图上做边缘检测再筛选矩形要稳因为颜色信息天然把车身上的白色、黑色、灰色区域排除掉了。注意 V 的下界取了 0.20是给阴天和傍晚留的余量如果白天光线好取 0.35 能把暗色车漆的噪点压掉不少。2.2 形态学闭运算把散点“焊”成车牌候选区再交给 bwareaopen 筛选颜色分割之后的 mask 不会是一整块干净矩形。车牌上的铆钉、字符间隙、边缘反光都会把掩膜切成碎片直接做连通域会得到几十个候选块。此时用闭运算imclose先膨胀再腐蚀能把间距不大的碎片拼成一块完整区域。因为车牌是横向长条结构元素应该设计成横向扁矩形宽度大、高度小让横向的缝隙被优先填上纵向则不要拆掉。se strel(rectangle, [10, 30]); maskClose imclose(mask, se); maskClean bwareaopen(maskClose, 4000); maskClean imfill(maskClean, holes); stats regionprops(maskClean, BoundingBox, Area);strel(rectangle, [10, 30])的第一个元素是高度10 像素第二个是宽度30 像素别写反。宽度 30 意味着横向 30 像素以内的缝隙都会被堵上这通常能盖住字符间距但如果图像分辨率高、字符间距本身超过 30 像素闭完还是一个断的此时把宽度加到 50 再看看。bwareaopen(maskClose, 4000)把面积小于 4000 像素的连通域直接删掉这个值要参考图像尺寸改1080p 图上 4000 合适缩略图就得降到 1000 量级。imfill补洞是为了把车牌字符内部的空隙填成实心这样字符不会再被当成候选块的一部分拆散。2.3 字符分割不是数连通域而是先按宽高比筛再用投影法切单字maskClean 可能同时框住车牌和一个蓝色的货车头所以要先遍历所有 BoundingBox按宽高比筛掉不合理的候选。常规蓝牌长度约 440mm、高度约 140mm宽高比大约 3 左右即使透视变形落在 2 到 5 之间都值得保留。接着把车牌区域灰度化、二值化对每一列做投影列投影为零的间断点就是字符之间的边界。for k 1 : numel(stats) bb stats(k).BoundingBox; if bb(3) / bb(4) 2.0 bb(3) / bb(4) 5.0 plate imcrop(img, bb); gray rgb2gray(plate); bw imbinarize(gray, adaptive); bw imopen(bw, strel(rectangle, [2, 2])); colProj sum(bw, 1); % colProj 为 0 的列是字符间隙 % 把连续非零段切成一个个单字符块 end endimbinarize(gray, adaptive)用的是局部自适应阈值能避免光照不均导致整个字符区变黑或变白比全局graythresh稳。二值化后字符内部偶尔有空洞用imopen做一次 2×2 的去除孤立点开运算对细小毛刺效果好不会像闭运算那样把相邻字符粘在一起。列投影sum(bw, 1)返回一个 1×宽度 的行向量值等于 0 表示这一列没有白色像素两个值为 0 的列之间就是一个字符。切出来的字符还要再做一次宽高比检查常规车牌字符宽高比约 0.3 到 0.7过宽的区域大概率是两个字粘连过窄的是噪声。2.4 识别环节模板归一化加相关系数够用于课程设计级识别分割出的字符大小不一模板匹配前统一imresize到 40×20。模板库可以用同一套代码切出来的多组干净车牌图自己建字符要包含 31 个省市区汉字至少把题目、课程设计里出现的那几个写了、24 个大写字母和 10 个数字。匹配时用corr2算相关系数取最大值对应的模板作为识别结果。charStd imresize(bwChar, [40, 20]); maxScore -1; for t 1 : numel(templates) score corr2(charStd, templates{t}); if score maxScore maxScore score; resultChar templateLabels{t}; end endcorr2对整体的亮度偏移不敏感因为内部做了去均值处理即使二值化后模板和待测字符的笔画粗细略有差别相关系数也能维持在 0.6 以上。如果某个字符经常识别错优先检查是不是imresize时默认双线性插值把细笔画磨掉了改成imresize(bwChar, [40 20], nearest)再看。到这里定位、分割、识别这条链路已经能跑通下一步是把它放回 GUI 里让按钮能调得起这段逻辑。3. MATLAB GUI 外壳控件布局、回调和 handles 数据传递3.1 用 GUIDE 还是 App Designer先把手里的 MATLAB 版本和示例代码对齐MATLAB 里做 GUI 有两条路线。老教程和课程设计代码绝大多数基于 GUIDE它在 R2012a 之前就存在打开 .fig 画布拖控件保存后生成一对 .m 和 .fig 文件逻辑写在 .m 的回调函数里。App Designer 是后来主推的方向界面更现代控件树更清晰但 .mlapp 是打包格式回调函数用属性保存数据旧代码搬迁麻烦。标题里明确写了 GUI 实现没有指明框架通常最简单可靠的做法是如果手里是 matlab r2023b 这类新版本又想少碰老接口直接选 App Designer如果打算套用网上现成的课程设计源码大概率是 GUIDE 工程把 .m、.fig 一起复制到当前文件夹在 MATLAB 里输入guide打开就能看到控件树。维度GUIDEApp Designer文件形态.m .fig 成对.mlapp 单文件坐标轴对象axes1普通句柄UIAxes回调函数头push_Callback(hObject, ~, handles)ButtonPushed(app, event)数据保存handles 结构体 guidataapp.属性 或 app.UserData旧示例代码量多课程设计几乎都是这套相对少两者数据管理方式差异很大动手前确认自己手上的示例代码是哪一套比纠结哪个更好用重要。App Designer 里坐标轴对象叫UIAxes回调里访问控件要用app.ButtonPushed这类带前缀的写法GUIDE 里则是handles.axes1、handles.edit1。3.2 handles、guidata 和 setappdata三个人都能用的数据搬运手段GUIDE 生成的每个回调函数第一行都有handles guidata(hObject)这个 handles 是一个结构体所有带 Tag 的控件都挂在它下面比如handles.axes1就是画布。往 handles 里塞自定义字段再写回界面要用guidata(hObject, handles)。App Designer 的等价物是app.UserData或直接app.字段名 值。如果数据不是单纯的图像矩阵而是识别参数、中间掩膜、字符模板库用setappdata(handles.figure1, templates, templates)更顺手它把数据存在 figure 对象上任何回调都能用getappdata取出来不会因为重绘界面被清掉。% 打开图片按钮的回调 function openButton_Callback(hObject, ~, handles) [file, path] uigetfile({*.jpg;*.png;*.bmp, 图片文件}); if isequal(file, 0) return; end img imread(fullfile(path, file)); handles.img img; guidata(hObject, handles); imshow(img, Parent, handles.axes1); % 识别按钮的回调 function recognizeButton_Callback(hObject, ~, handles) if ~isfield(handles, img) set(handles.resultEdit, String, 请先打开图片); return; end [plateNo, plateImg] myPlateRecognition(handles.img); set(handles.resultEdit, String, plateNo); imshow(plateImg, Parent, handles.axes2);uigetfile带了文件过滤条件用户不选文件直接关窗口时返回的 file 是 0所以先判isequal再往下走。handles.img存的是原图它不会自动盖到界面上需要手动imshow(..., Parent, handles.axes1)这是新手最容易漏的一步认定“读进来了就应该显示出来”。第二个回调里判断isfield(handles, img)防止用户没选图就点识别否则handles.img不存在会直接报错。myPlateRecognition就是把第 2 章那串代码收拢成的函数GUI 回调里不要写冗长的图像处理算法否则定位问题时分不清是界面 bug 还是算法 bug。3.3 一个最小识别 GUI 的布局顺序两个 axes、一个编辑框、一个按钮组实操时不需要把界面做得花哨布局按这个顺序放就够用左上 axes1 显示原图右上 axes2 显示裁剪出的车牌图下方一个 edit 显示识别结果右侧竖排三个按钮分别是打开图片、开始识别、清空。GUIDE 画布上拖好后按钮 Tag 分别叫 openButton、recognizeButton、clearButton编辑框 Tag 叫 resultEdit。清空按钮就是把两个 axes 的句柄清了function clearButton_Callback(hObject, eventdata, handles) cla(handles.axes1); cla(handles.axes2); set(handles.resultEdit, String, );如果想让一个界面既跑识别又展示中间过程可以在 axes2 用subplot摆放原图、HSV 掩膜、二值字符图三个子图subplot(1, 3, 1, Parent, handles.axes2)。这样参数调试时不用切到命令行看图识别链路每一步是死是活一眼就能看到。4. 调参与排错HSV 阈值的经验区间和字符粘连处理4.1 先按这张参数表跑通一遍再谈优化把下面这张表当作起点而不是终点。图片分辨率、车距、天气都会拉伸参数调参时只动一个变量动完看中间产物不要同时改三个。处理环节参数建议初值跑偏时的表现HSV 定位H 范围0.55 ~ 0.70蓝色货车误检下限调高蓝车漏检上限放宽到 0.72HSV 定位S 下界0.30灰暗车牌漏检降到 0.20代价是蓝灰色车身噪声变多HSV 定位V 下界0.20傍晚漏检往下降白天误检往上升形态学闭运算结构元[10, 30]字符被焊死宽度降到 20车牌断成两段宽度升到 40形态学bwareaopen 面积4000小图全删光降到 1000大图筛不干净升到 8000字符分割字符宽高比0.3 ~ 0.7汉字被拆下限降到 0.2多字粘一起上限升到 0.85模板匹配相关系数阈值0.6乱识别说明模板库太少不是阈值问题注意strel(rectangle, [10, 30])里的 10 和 30 是像素数不是比例换成不同分辨率的图这个值必须跟着图面宽度重新估。按经验闭运算结构元宽度只要覆盖图像上车牌宽度的二十分之一到十五分之一碎片就能被焊上同时不至于把字符间隙全填掉。4.2 车牌定位准了但字符总是粘成一团先动这个参数二值化后的车牌字符常见故障有两种一种是相邻字符之间还有一点点白色像素搭着列投影分不开另一种是字符被形态学处理搞断投影切出碎块。前者先把imopen的结构元从 2×2 加到 3×3把搭桥的毛刺去掉还不行就改用腐蚀一次再接bwmorph(bw, thin, 1)细线化能把搭桥处优先减掉。后者问题多半出在闭运算结构元太高[10, 30] 里的 10 意味着纵向 10 像素都会被动过把高度降到 4 或 5只让横向补缝。bw imopen(bw, strel(rectangle, [3, 3])); % 还分不开时用细线化破坏粘连桥 bwThin bwmorph(bw, thin, 1); colProj sum(bwThin, 1);细线化会改变笔画宽度所以这一步只用来找字符边界分割坐标拿到后回到未细线化的 bw 上按列坐标切图避免把细笔画切掉。调试期可以在 GUI 的 axes2 上直接用imshow(bw)覆盖显示二值图比反复读坐标直观。4.3 把中间结果打出来fprintf 加断点比对着最终图猜快识别错误时先在每个阶段输出关键数字HSV 掩膜的连通域数量、筛完后的候选个数、切出的字符个数、每个字符的最佳相关系数。数量对不上就知道问题出在哪一层。fprintf(候选区域: %d\n, numel(stats)); fprintf(分割字符数: %d\n, numel(charBoxes)); fprintf(%d %s %.3f\n, t, resultChar, maxScore);在 MATLAB 编辑器里给imcrop(plate)那一行设断点运行时用imtool(plate)直接看裁剪结果比在主界面上放大看原图清楚。字符数不等于 7蓝牌是汉字字母6 位数字字母先把分割修好不要急着折腾模板。5. 把程序封装成函数批量验证识别率和小步改进技巧识别链路跑通、GUI 按钮能点出结果只完成了一半。后面真正耗时间的是改参数后反复验证所以要把算法收敛成一个纯函数GUI 只是它的调用方function [plateNo, plateImg] myPlateRecognition(img, params) % params 是带默认值的结构体避免改一处要翻整个函数 if nargin 2 params.hRange [0.55 0.70]; params.sMin 0.30; params.vMin 0.20; end % ... 第 2 章的定位、分割、模板匹配代码 end有了这个函数就可以对一批图片做批量验证。用dir(test/*.jpg)拿到文件列表循环里调用函数把识别结果和文件名记下来手工核对后统计识别率。测试集最好包含正常光线、傍晚、蓝色货车、有横幅干扰这几类每类单独统计能看出参数是全局有效还是只对这个场景有效。小步改进上优先做两件事。一是给二值化加一个方向判断正常蓝牌切出来的是白字黑底如果sum(bw(:))大于像素总数一半说明反了bw ~bw后再进模板匹配这一步能直接救回一批因为反光被反转的字符。二是模板匹配不满足时准备一个极简的基准对模板和测试字符都做imresize(..., nearest)再做corr2排除插值方法带来的干扰。等模板匹配稳定到能处理 90% 以上的干净样本再考虑用分类器或 YOLO 这类检测网络替换这一层到那时前面 HSV 定位和形态学参数这些经验仍然有用至少能提供可靠的训练正样本。最后留一个检查习惯每次改完参数把 params 结构体连同测试图片号和识别结果存成一个 .mat文件名带上日期和参数 ID比如 params_20250612_v3.mat下次跑同一批图时直接对照两组输出很快能看出参数改动到底影响了哪一步。本文还有配套的精品资源点击获取
返回列表