首页 / 资讯中心 / 文章详情

MATLAB车牌识别源码与论文:从定位到识别的全流程解析

MATLAB车牌识别源码与论文:从定位到识别的全流程解析 ★ FEATURED ARTICLE
简介这是一份基于 MATLAB 的车牌识别方法研究论文及配套代码说明面向数字图像处理、模式识别方向的本科生与入门研究者。内容覆盖车牌识别完整流程包括图像灰度化、Roberts 算子边缘检测、形态学腐蚀与平滑处理、边界值计算等车牌区域提取步骤基于垂直投影扫描的字符切割方法以及字符归一化与识别的实现思路文档中配有对应程序代码与处理效果图便于实操对照。资源为 1 个 doc 文档压缩包整体约 594KB目前已有 314 人学习。作为数字图像处理课程论文文档按摘要、关键词、研究背景、主要理论概况、研究内容与系统程序设计等模块展开系统介绍从图像采集到字符识别输出的完整过程同时梳理了车牌识别的研究现状与技术难点如车牌格式多样、颜色种类多、污渍模糊与偏斜干扰等并提供基于模板匹配与特征分类的字符识别方法综述可帮助读者快速建立完整技术路线适合课程论文参考或 MATLAB 图像处理入门实践。1. MATLAB车牌识别论文加源码从定位到识别的一整套可复现流程如果你正在找一份能直接跑通、又配得上课程设计或毕业设计的MATLAB车牌识别项目这套「论文加源码」基本就是为你准备的。它不像网上那些只丢一段残缺代码的资源而是把“车牌区域提取、字符切割、字符识别”三个模块完整串了起来还附带了一份排版好的课程论文从研究背景写到结果分析连参考文献和附录源码都有。我拆过不少图像处理类的资源这套的最大价值在于它的流程是教科书级的——灰度化、Roberts边缘检测、腐蚀、闭运算、投影法定位、垂直投影分割、模板匹配识别每一步都有对应的MATLAB代码和效果图说明。适合三类人一是做数字图像处理课程设计的学生拿来改改就能交二是想快速跑通车牌识别全流程的初学者代码注释虽然粗糙但路线清晰三是需要一份可扩展基座的研究者后面换算法换数据集都方便。2. 车牌定位从灰度化到投影边界的一整套形态学操作车牌定位是整个识别系统的第一步也是最容易翻车的一步。定位不准后面分割和识别全是白搭。这套代码用的是纯形态学路线先把RGB图转灰度再用Roberts算子做边缘检测接着腐蚀掉细碎边缘闭运算把车牌区域连成一块再用bwareaopen删除小对象最后用水平垂直双向投影确定车牌的四个边界。2.1 图像读取与灰度化RGB到灰度的转换逻辑程序启动后先用uigetfile弹出文件选择框读取一张JPG图片。然后调用rgb2gray把彩色图转成灰度图这一步是关键——车牌底色和字符颜色对比度大转灰度后两者灰度值拉开后续边缘检测才好用。源码里还用了imadjust做线性灰度变换把灰度范围从[0.3, 0.7]映射到[0, 1]相当于把中间灰度段拉伸车牌区域的对比度会明显增强。[filename, filepath] uigetfile(*.jpg, 输入一个需要识别的车牌图像); I imread(strcat(filepath, filename)); I1 rgb2gray(I); % RGB转灰度降低数据维度 I1 imadjust(I1, [0.3, 0.7], []); % 线性灰度拉伸增强对比度 I1 medfilt2(I1); % 中值滤波去噪保留边缘灰度化后的直方图可以用imhist查看如果车牌区域和背景的灰度峰没有明显分离就要调整imadjust的输入范围。中值滤波用3×3窗口就够窗口太大会把车牌的边缘细节也磨掉。2.2 Roberts边缘检测与腐蚀去掉多余边界点边缘检测用的是edge函数算子选roberts阈值0.25方向both。Roberts算子用对角方向相邻像素之差检测边缘对陡峭边缘敏感计算量小适合车牌这种字符边缘清晰的目标。但实测下来Roberts对噪声也敏感背景复杂时会出现大量伪边缘所以源码紧接着做了一次腐蚀结构元素用的是竖着的三像素列向量。I2 edge(I1, roberts, 0.25, both); % 边缘检测阈值0.25 se [1; 1; 1]; % 3x1竖直结构元素 I3 imerode(I2, se); % 腐蚀消除细碎边缘腐蚀的作用是让白色边缘变细、变短那些由噪声或纹理产生的短边缘会被直接消掉。结构元素选竖直方向是因为车牌字符边缘以竖线为主竖直腐蚀能保留车牌字符轮廓同时干掉横向噪点。阈值0.25这个值需要说明一下论文正文写的是0.15但主程序里实际用的是0.25这个不一致我在后面避坑章节会细说。2.3 闭运算与移除小对象把车牌区域连成连通域腐蚀完之后车牌区域的边缘线还是断续的。这时候用imclose做闭运算——先膨胀后腐蚀把临近的边缘点连接起来。结构元素用的是20×20的矩形只要两个封闭域之间的距离小于这个矩形尺寸就会被连成一个连通域。论文正文里写的是25×25程序里却是20×20又是一个版本差异。se strel(rectangle, [20, 20]); % 矩形结构元素决定连通域合并范围 I4 imclose(I3, se); % 闭运算连接断裂的轮廓 I5 bwareaopen(I4, 1000); % 删除面积小于1000像素的对象闭运算的矩形尺寸直接影响定位结果太小小区域连不起来车牌轮廓断裂太大把车的标志、车窗边框也连进来后面还得靠bwareaopen删。bwareaopen删除面积小于1000的连通域这个值也不是固定的——图像分辨率高时车标、车灯的面积可能超过1000得按实际图像调整。2.4 双向投影法确定车牌边界代码里的像素统计定界这一步是车牌定位的核心源码用的是一个有点绕的写法先统计每一行白色像素的数量找到最大值所在行作为基准然后从该行向上下两个方向扫描直到白色像素数小于阈值5才停。这个阈值5就是临界值用来过滤零星噪点。水平方向同理阈值取3。[y, x, z] size(I5); Blue_y zeros(y, 1); for i 1:y for j 1:x if myI(i, j, 1) 1 % 统计白色像素二值图为1 Blue_y(i, 1) Blue_y(i, 1) 1; end end end [temp, MaxY] max(Blue_y); PY1 MaxY; while ((Blue_y(PY1, 1) 5) (PY1 1)) PY1 PY1 - 1; end这段代码的逻辑是先找白色像素最多的行——这一定是车牌所在的行因为车牌区域的白色像素密度远高于背景。然后从这一行往上走只要某行的白色像素还大于等于5就说明还在车牌区域小于5说明到了背景停止。这个“5像素”就是投影法里的临界值源码注释里有人打了个问号实际上这个值的选取取决于图像分辨率分辨率越高这个值应该调大。边界确定后还有一段修正逻辑PY1-2、PX1-2、PX23、PY210这些偏移量是为了把边界的毛刺去掉保证裁剪出来的区域完整包含车牌。然后用imcrop从原图中把车牌区域裁出来存成dw.jpg。到这里车牌定位就完成了后面所有操作都基于dw.jpg。3. 字符分割垂直投影与面积比的膨胀腐蚀策略车牌裁出来后第二步是把车牌上的7个字符一个个切出来。这套源码的做法是先对车牌灰度图做增强和二值化然后用中值滤波去噪接着根据字符面积与车牌面积的比值决定膨胀还是腐蚀最后用垂直投影法把每个字符的左右边界找出来逐个切割。3.1 二值化与滤波im2bw阈值0.4的选择逻辑定位裁剪后的dw.jpg先转灰度图再做一次imadjust线性拉伸然后im2bw以0.4为阈值二值化。这个0.4阈值的意思是灰度值小于0.4的像素置为0黑色大于等于0.4的置为1白色。车牌字符在灰度图上通常比底色暗二值化后字符变黑、底色变亮视具体图像而定也可能反过来这一步直接决定后续投影分割能不能干净。b rgb2gray(a); b imadjust(b, [0.3, 0.7], []); % 增强灰度对比度 d im2bw(b, 0.4); % 阈值0.4二值化 d medfilt2(d); % 中值滤波去孤立噪点中值滤波对二值图像的孤立噪点非常有效——单个白点或黑点会被邻域中值替换掉而字符笔画的连续性不会受影响。实测发现这步滤波对后续的垂直投影影响很大不滤波的话投影直方图上会出现大量毛刺找分割点时容易误判。3.2 面积比驱动的膨胀腐蚀0.235到0.365的经验区间二值化后的车牌图字符和噪声混合在一起需要根据字符的实际占比做形态学修正。源码里用bwarea算字符面积其实是白色像素面积除以车牌总面积得到占比然后按经验阈值判断占比大于0.365说明膨胀过度或噪声太多做腐蚀收缩占比小于0.235说明字符太细或二值化掉了笔画做膨胀加粗介于两者之间不动se eye(2); % 2x2单位矩阵作为结构元素 [m, n] size(d); if bwarea(d) / m / n 0.365 d imerode(d, se); % 字符占比过高腐蚀收缩 elseif bwarea(d) / m / n 0.235 d imdilate(d, se); % 字符占比过低膨胀加粗 end d qiege(d); % 调用切割子程序去掉边缘干扰0.235到0.365这个区间是这个项目作者在实验里反复调出来的。不同分辨率的车牌图这个比值会变化但区间范围基本适用。如果二值化后字符大量断裂就把下限调到0.2上限调到0.4多试几次找到合适区间。3.3 垂直投影分割连续白色列与字符宽度的双重判断垂直投影分割的原理很简单把二值图像按列求和有字符的列和值大字符间隙的列和值接近0。源码用一个while循环扫描整个投影数组找到一段连续非零的列区间这段区间就是一个字符。但问题来了——如果两个字符粘连它们的投影区间会连在一起一个区间里可能包含两个字符。s sum(d); % 每列白色像素数 j 1; while j ~ n while s(j) 0 % 跳过空白列 j j 1; end k1 j; % 字符起始列 while s(j) ~ 0 j n - 1 j j 1; end k2 j - 1; % 字符结束列 if k2 - k1 round(n / 6.5) [val, num] min(sum(d(:, [k1 5:k2 - 5]))); d(:, k1 num 5) 0; % 在字符内部找投影谷值强制分割 end end这段代码的精髓在if语句里当一段连续白色区域的宽度超过车牌总宽度的1/6.5时判定它包含了多个字符此时在区域内部寻找投影值最小的列——也就是字符间最细的连接点强制把这一列置黑完成分割。1/6.5是怎么来的标准车牌有7个字符考虑左右留白单个字符宽度约占总宽度的1/6.5到1/7。这个阈值如果设太大粘连字符分不开设太小会把单个字符拦腰切断。3.4 切割出7个字符干扰处理与汉字/字母/数字的区分逻辑主分割循环跑完后还有一段单独的切割逻辑处理第一个字符——也就是省简称汉字。汉字和字母数字不同它有左右结构垂直投影会出现中间低谷直接按连续区域切会把汉字切成两半。源码的解法是切割时检查字符中间三分之一区域的像素占比如果中间区域的白色像素占比超过25%就认为这个字符是完整的汉字否则当成干扰丢弃。while flag 0 [m, n] size(d); wide 0; while sum(d(:, wide 1)) ~ 0 wide wide 1; end if wide 10 d(:, [1:wide]) 0; % 宽度小于10认为是左侧干扰删除 d qiege(d); else temp qiege(imcrop(d, [1 1 wide m])); [m, n] size(temp); all sum(sum(temp)); two_thirds sum(sum(temp([round(m/3):2*round(m/3)], :))); if two_thirds / all 0.25 flag 1; word1 temp; % 判断为汉字作为第一个字符 end d(:, [1:wide]) 0; d qiege(d); end end [word2, d] getword(d); % 依次分割第2到第7个字符 [word3, d] getword(d);getword是源码配套的子函数逻辑和上面这段类似从左边开始找连续白色区域切割下来然后把这一块从原图中抹掉继续切下一个。注意word1是用特殊逻辑切出来的word2到word7统一用getword因为第2位一定是字母后面五位是字母或数字都不存在左右结构问题。汉字字符切割是这套代码里最脆弱的环节后面避坑章节我详细说。3.5 字符归一化与模板匹配识别40×20尺寸的差分比较法识别模块用的是纯模板匹配把切割出的字符归一化成40×20像素然后和模板库里的每个模板逐像素做差统计差异超过±10的像素个数差异最小的模板就是识别结果。模板库按照车牌规则分了三段第1位是汉字模板索引37到43第2位是字母11到36第3位以后是字母或数字1到36。word1 imresize(word1, [40 20]); % 归一化到40x20 for I 1:7 ii int2str(I); t imread([ii, .jpg]); % 读入第I个字符图像 SegBw2 imresize(t, [40 20], nearest); if I 1 kmin 37; kmax 43; % 汉字模板范围 elseif I 2 kmin 11; kmax 36; % 字母模板范围 else kmin 1; kmax 36; % 字母数字模板范围 end for k2 kmin:kmax fname strcat(字符模板\, liccode(k2), .jpg); SamBw2 imread(fname); SubBw2 SegBw2 - SamBw2; % 逐像素差分 Dmax sum(abs(SubBw2(:)) 10); % 统计差异点 Error(k2) Dmax; end Error1 Error(kmin:kmax); MinError min(Error1); findc find(Error1 MinError); Code(l) liccode(findc(1) kmin - 1); endliccode是按固定顺序排的字符表0到9、A到Z、京辽桂陕苏渝浙。模板文件名就是对应的字符名加.jpg比如汉字“京”的模板就是字符模板\京.jpg。模板匹配对归一化质量要求很高——字符不够居中、笔画粗细不一致差分值都会变大识别时容易错判到相近模板上比如O和0、B和8这类相似字符。4. 这份资源里的坑我从代码里挖出的五个血泪问题这套源码能跑通但离“开箱即用”还有距离。我实际拆解和测试时踩了不少坑这里挑影响最大的五个每条按现象、原因、解决来写。4.1 模板文件夹缺失运行到字符识别直接报错现象整个定位和分割流程走完到字符识别阶段imread读模板文件时报错找不到文件或者——更隐蔽的——程序用了错误尺寸的模板图识别结果全错。原因源码附录只给了主程序和qiege、getword子程序但没有附带字符模板库。模板库需要自己准备每个字符一张归一化好的40×20二值图。没有模板库识别模块根本无法运行。解决最实用的办法是从现有车牌的字符截图里抠模板。找几张不同车牌的清晰照片把7个字符用imcrop逐一切出来归一化成40×20再用imwrite存成“字符模板\字符名.jpg”。注意所有模板必须是二值图而且是白字黑底还是黑字白底要跟你的二值化结果保持一致否则差分计算出来的误差全是错的。4.2 灰度图直接进投影定位全部像素都是白色现象程序跑到投影定位那一段统计出来的白色像素数异常定位框要么覆盖全区要么完全错位。原因源码里有一处注释写“判断蓝色像素”但实际代码判断的是myI(i,j,1)1也就是二值图像素值为1的白色点。如果输入的不是二值图而是灰度图像素值分布在整个0到255范围等于1的点可能几乎没有或到处都是统计出来的行列投影完全失真。解决确保进入投影定位阶段的是形态学处理完的二值图即代码里的I5而不是I1或I2。在定位代码前加一行检查if ~islogical(I5)转成logical类型再跑统计避免灰度值等于1的巧合。4.3 Roberts边缘检测的阈值0.15还是0.25论文和代码不一致现象按论文里的0.15阈值跑背景复杂的图像边缘图一片白腐蚀和闭运算后车牌区域淹没在杂波里按代码里的0.25跑边缘图干净一些但有些光照弱图像的车牌边缘变得很稀疏。原因论文正文和附录代码存在版本差异——正文写的是0.15代码用的是0.25。阈值小则边缘点多但噪声重阈值大则边缘干净但可能漏检弱边缘。没有哪个值在所有场景下都对。解决建议把阈值设为可调参数先跑一张简单背景的图用0.15跑复杂背景的图用0.25。如果边缘明显太碎阈值往上加到0.3如果边缘断得厉害往下减到0.1。另外注意edge函数的thresh参数直接对应梯度幅值阈值改动要小步调不要一次跳0.1。4.4 闭运算结构元素20×20和25×25对连通域合并影响巨大现象用20×20的矩形做闭运算某些车牌的汉字和字母之间断开形不成完整连通域后面bwareaopen删小对象时把应该保留的车牌部分也删了用25×25又容易把车标或车窗边缘一起连进来。原因结构元素尺寸决定了两个封闭区域要被连接所需的最大距离。20×20意味着水平方向最多连20像素、垂直方向最多连20像素——如果字符笔画间隔超过这个值就断。25×25容忍度更高但误连风险也更大。代码用的是20×20论文写的是25×25真实原因大概率是作者在不同图片上分别调出来的。解决根据实际图像的车牌宽度来设矩形尺寸。经验值是取车牌宽度像素的1/20作为矩形边长。比如车牌区域宽200像素就用10×10的矩形宽450像素就用22×22。这个比例关系比固定值更可靠。4.5 汉字字符分割容易碎京字被切成左右两半现象定位准确、二值化干净的情况下第一个汉字——比如“京”——在垂直投影时中间出现明显的谷值被误判为字符间隙整个汉字被切成两半后面的识别结果完全错乱。原因汉字和字母数字不同很多汉字是左右结构“京”“苏”“浙”这类字的中间笔画少垂直投影会出现低谷。而分割算法是按连续白色列区间来切的遇到这种谷值就容易在汉字中间下刀。源码特别处理了第一个字符——用two_thirds/all0.25判断中间区域是否还有足够像素但阈值0.25不见得对所有汉字都适用。解决如果你只需要处理固定地区车牌比如只识别“京”字就单独测一下这个字的投影特征把0.25阈值调整到刚好能让该字中间谷值被容忍的程度。另一种做法是提前对二值图做一次闭运算结构元素选水平方向的1×5矩形可以把汉字内部的小间断连起来再跑分割。实测这个预处理对左右结构汉字效果明显代价是字符宽度会被轻微撑大微调一下1/6.5那个宽度阈值即可。5. 从复现到改进把识别率从“能跑”提到“能用”如果你已经照着源码跑通了一遍接下来值得做的事是给这套代码加一个批量验证框架。单独一张图的识别结果说明不了问题我习惯的做法是收集20到30张不同环境下的车牌图片跑一遍完整的定位、分割、识别流程最后统计每一阶段的成功率定位失败、分割失败、识别错误分开计这样才知道瓶颈在哪。5.1 批量测试脚本三个指标分别统计% 批量测试脚本框架 files dir(test_images/*.jpg); results {}; for i 1:length(files) try [plate, seg_chars, rec_code] plate_recognize(files(i).name); results{i}.定位 ~isempty(plate); results{i}.分割 length(seg_chars) 7; results{i}.识别 rec_code; catch ME results{i}.定位 false; results{i}.分割 false; results{i}.识别 ; end end % 统计定位率、分割成功率、识别准确率把主程序封装成plate_recognize函数输入图片路径输出定位结果、7个字符和识别结果。try-catch保证单张图片出错不会中断整个循环。三个指标分别统计——如果定位率已经90%以上分割率只有50%说明问题集中在形态学参数和二值化上你就不用再去动识别模块了。5.2 参数敏感性验证哪个参数最值得调我按重要程度给这套代码的参数排个序第一是im2bw的二值化阈值0.4直接决定分割质量第二是Roberts边缘检测的thresh值0.25第三是bwareaopen的面积阈值1000第四是字符宽度判断的1/6.5第五是面积比的0.235/0.365区间。排这个序的根据是二值化错了后面全是错边缘检测错了定位就错分割宽度错了字符切分就错。要验证参数敏感性固定其他参数不动只改变一个参数的值观察识别率变化。这个方法能告诉你哪些参数需要精调、哪些参数可以大胆放宽。比如把0.4改成0.35和0.45各跑一遍如果识别率波动超过10%这个参数就是你的项目重点——大概率是因为二值化后字符笔画粗细变化太大模板匹配吃不消。5.3 模板匹配的下一步换特征或换分类器模板匹配的好处是简单、可解释坏处是扛不住变化:字符倾斜、粗细不均、光照差异都能让差分值突增。想在不换语言、不换框架的前提下提升识别率有两个方向一是把逐像素差分换成特征向量匹配——提取字符的网格特征或投影特征类似把40×20的图像划分成8×4的网格统计每格像素占比再用欧氏距离或余弦距离匹配二是把模板匹配结果作为候选集对差异最小的前三个候选做二次判别比如用字符的宽高比、笔画密度做规则过滤能明显减少O/0、B/8这类相似字符的误判。我从那以后每次写图像识别相关的课程设计都会强制自己先铺一个批量测试脚本再调算法。不要一遍遍点运行、肉眼看图来验证效果那样既慢又不客观。这套MATLAB车牌识别论文加源码最适合的用法就是先跑通、再测数据、再改参数三步走完你对整个识别流程的掌控感会完全不一样。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站