简介:一份面向车牌识别与机器视觉方向学习者的毕业论文PDF,内容围绕基于LabVIEW与MATLAB的系统设计,从硬件搭建到软件算法均有完整论述。压缩包内为1个PDF文件,大小约1.64MB,便于直接下载阅读。已有179人浏览学习,适合自动化、图像处理等专业学生作为毕业设计或课程设计参考。论文阐述了车辆传感探测、图像采集等硬件组成,以及车牌定位、字符切分、字符识别三大软件模块;针对图像预处理难点,采用直方图均衡化、平滑滤波与改进的二值化方法,并利用MATLAB的COM生成器将算法组件集成到LabVIEW环境中。通过350幅汽车图片的测试验证系统可行性,读者可从中获得完整框架、算法思路与实验结论,对快速理解车牌识别实现路径有直接帮助。
1. 基于LabVIEW与MATLAB的车牌识别,到底在识别什么
车牌识别听起来是个老掉牙的课题,但在工程落地上,它始终是机器视觉入门到进阶最完整的训练场。一个完整的车牌识别系统,不是拿一张高清照片跑一次深度学习模型那么简单,而是要把“图像采集、车牌区域定位、字符分割、字符识别”这四件事串成一条稳定可重复的流水线。而选择LabVIEW与MATLAB联合实现,本身就是一种典型的工程折中:LabVIEW擅长硬件采集、界面控制和流程调度,MATLAB擅长矩阵运算和算法原型验证,两者结合可以让算法开发效率和系统集成度同时得到满足,也让这个题目特别适合作为本科毕业论文的切入点——它有明确的研究对象、可量化的指标、可展示的界面,以及足够深的优化空间。
本文不会去复述某篇论文的章节安排,而是以一个从业者的视角,把这套方案从头到尾推演一遍:从平台分工,到图像预处理与车牌定位,再到字符分割与识别,最后落到LabVIEW与MATLAB联合调试时最容易踩的坑。即使你手里的论文题目只写了“基于LabVIEW与MATLAB”,读完这篇也能知道该往哪些方向查资料、写代码、设参数。
2. LabVIEW与MATLAB的分工:为什么不是二选一
2.1 两个平台的擅长区间
我见过不少初学者纠结于“用LabVIEW还是MATLAB做车牌识别”,这个问题本身就问偏了。车牌识别的完整链路里,图像采集、实时显示、IO控制、界面交互是LabVIEW的强项,而图像处理算法的编写、矩阵运算、模型训练是MATLAB的强项。LabVIEW的视觉开发模块(NI Vision)虽然自带OCR函数,但针对中国蓝底白字车牌、黄底黑字车牌这类特定目标,它的训练样本和字符集支持并不理想,往往需要额外调整。MATLAB这边,图像处理工具箱和深度学习工具箱都成熟得多,从传统的Sobel边缘检测到基于卷积神经网络的检测模型,都能在几行代码里跑起来。
所以最合理的架构是:LabVIEW负责“从相机取流、保存图像、调用算法、显示结果”,MATLAB负责“车牌定位、字符分割、字符识别”这一核心算法段。两侧通过文件、内存映射或进程间通信交换数据。对于论文场景,最常采用的就是LabVIEW的MATLAB Script节点,直接把MATLAB脚本嵌入LabVIEW流程图里,两侧共享内存变量,省去编写中间文件的过程。
2.2 调用的三种常见形式
第一种是MATLAB Script节点,适合算法还在频繁修改的阶段。LabVIEW里拖入一个MATLAB Script节点,把M代码粘贴进去,定义好输入输出变量类型,运行LabVIEW时会自动启动MATLAB引擎执行。优点是改算法不用重新编译,缺点是每次调用都有引擎启动开销,不适合实时性要求高的场合。
第二种是使用MATLAB Compiler把M代码打包成DLL或可执行文件,再通过LabVIEW的调用库函数节点(Call Library Function Node)或系统命令来调用。这种方式部署时不需要安装完整MATLAB,只需要MCR运行库,启动速度和执行效率都优于Script节点,但算法一旦修改就需要重新打包。
第三种是通过TCP/IP或队列做进程间通信,两边完全独立运行。这种架构最灵活,适合最终产品形态,但需要自己定义通信协议和数据序列化,工作量大不少。毕业论文阶段,建议直接用MATLAB Script节点走通全流程,最后如果时间富余,再把核心算法打包成DLL替换上去。
2.3 图像数据类型的衔接
这里有个最常见的坑:MATLAB中图像矩阵默认是double类型,数值范围0到1;而LabVIEW从相机采集到的图像通常是U8类型,数值范围0到255。直接在MATLAB Script节点里互通时,如果不做转换,原本的灰度图像在MATLAB里会被当成0到1之间的浮点数处理,边缘检测阈值、形态学结构元素大小全部失效,结果就是显示出来一团黑或者一团白。
我一般会在MATLAB脚本入口处统一做一次类型归一化:
% 输入:rawImg 为LabVIEW传入的U8图像数组 img = double(rawImg) / 255.0; % 转换到[0,1]浮点区间 if size(img, 3) == 3 grayImg = rgb2gray(img); % 彩色图转灰度 else grayImg = img; % 已是灰度图 end这段代码的逻辑是先把输入图像强制转换为double并归一化到0到1,再判断通道数决定是否做灰度化。这样后续所有算法都基于统一的数据约定,不会因为一侧改了图像格式导致另一侧算法静默失效。参数上需要注意,rgb2gray的默认系数是0.2989R+0.5870G+0.1140B,这是NTSC制式的标准亮度公式,如果使用的是其他色彩空间需要先转换。
3. 车牌定位的完整套路:从边缘到颜色特征
3.1 为什么先做边缘检测再做形态学
车牌区域的显著特征是字符区域与背景之间存在高对比度边缘,而且车牌本身是一个近似矩形的连通区域。传统的定位思路是利用Sobel或Canny算子提取边缘,再进行闭运算连接相邻边缘,最后通过轮廓的外接矩形宽高比筛选候选区域。这个方案对蓝底白字车牌效果很好,因为蓝色背景与白色字符的边缘响应非常强。
Sobel算子的核心是卷积核分离,水平方向用[-1 0 1; -2 0 2; -1 0 1]检测垂直边缘,垂直方向用其转置检测水平边缘。Canny则多了一步非极大值抑制和双阈值连接,边缘更细更连续,但计算量更大。对车牌这种目标,我倾向于先用Sobel提取水平边缘——车牌的字符排列是横向的,水平方向梯度变化剧烈,垂直边缘反而是干扰。
% 边缘检测与形态学闭运算 hy = fspecial('sobel'); % 水平方向sobel核 edgeImg = imfilter(grayImg, hy, 'replicate'); edgeImg = edgeImg > thresholdValue; % 二值化,thresholdValue通常在0.1~0.3 se = strel('rectangle', [17, 3]); % 横向长条形结构元素 closeImg = imclose(edgeImg, se); % 闭运算连接断裂边缘这里结构元素选择[17, 3]是有讲究的:车牌字符宽度大约占车牌宽度的1/10左右,而字符间距不远,横向17像素足以覆盖相邻字符边缘之间的间隙,竖向3像素又不会把上下两排干扰线连接起来。thresholdValue需要根据实际图像动态计算,常见做法是用graythresh函数做Otsu全局阈值分割,或者直接取边缘图均值的1.5到2倍。闭运算之后,车牌区域会变成一个高亮的矩形块,而自然场景中的树木、路面纹理干扰会被长条形结构元素过滤掉大部分。
3.2 连通域筛选与宽高比约束
闭运算之后就要找候选区域了。bwlabel函数可以对二值图像中的连通域进行标记,regionprops提取每个连通域的外接矩形、面积、填充率等属性。中国车牌的标准尺寸是440mm×140mm,宽高比约为3.14,实际拍摄时因为透视会有变化,通常把筛选范围定在2.2到4.5之间。面积方面,车牌区域在整幅图像中的占比与拍摄距离强相关,不能设置死值,更可靠的是相对面积——即候选区域面积占全图面积的比例,一般在0.5%到8%之间。
cc = bwconncomp(closeImg); % 查找连通域 stats = regionprops(cc, 'BoundingBox', 'Area', 'FilledArea'); candidates = []; for k = 1:length(stats) w = stats(k).BoundingBox(3); h = stats(k).BoundingBox(4); ratio = w / h; areaRatio = stats(k).FilledArea / (size(closeImg,1) * size(closeImg,2)); if ratio > 2.2 && ratio < 4.5 && areaRatio > 0.005 && areaRatio < 0.08 candidates = [candidates; stats(k).BoundingBox]; end end这个筛选逻辑注意两点:第一,用FilledArea而不是Area,因为闭运算后区域内部可能有空洞,填充面积更能反映区域真实大小;第二,候选区域可能有多个,后续还需要结合颜色特征进行最终确认。仅靠几何特征,路牌、车身广告、远处建筑物窗户都可能混进来。
3.3 用HSV颜色特征做二重校验
几何特征筛选之后为什么要加颜色校验?因为车牌最稳定的特征不是形状,而是颜色——蓝底白字的蓝色,或者黄底黑字的黄色。灰度图像在光照变化下鲁棒性差,但把图像转换到HSV色彩空间后,色调分量对亮度变化不敏感,可以设定固定的蓝色或黄色阈值。
hsvImg = rgb2hsv(rgbImg); % 转换到HSV空间 h = hsvImg(:,:,1); s = hsvImg(:,:,2); v = hsvImg(:,:,3); blueMask = (h > 0.55 & h < 0.70) & (s > 0.35) & (v > 0.15); yellowMask = (h > 0.10 & h < 0.20) & (s > 0.35) & (v > 0.15);这里蓝色色调范围0.55到0.70对应的是OpenCV里H分量0到180映射的99到126,对应到MATLAB的0到1区间就是0.55到0.70。饱和度阈值0.35用来排除灰色和白色区域,亮度阈值0.15排除近黑色区域。有了这两个掩膜,再计算它们与几何候选区域的重叠度,重叠面积超过候选区域面积30%的才保留。这一步能把绝大多数误检剔除掉,尤其是树荫和深色建筑物边缘。
4. 字符分割与识别:从投影法到BP神经网络
4.1 车牌校正:透视与倾斜
车牌在自然图像里很少是正对着相机的,透视畸变和水平倾斜都会让字符分割出错。一个典型的场景是车辆从侧面驶过,车牌在图像中呈平行四边形,字符的垂直笔画不再竖直。这时候如果直接做垂直投影,字符之间会产生粘连,分割点找不准。
常规做法是先对定位到的车牌区域做倾斜校正。求最小外接矩形的旋转角度,然后用imrotate反向旋转;对于透视畸变,需要手动指定车牌四角坐标,再用fitgeotrans估计射影变换矩阵,最后用imwarp矫正。
% 手工或自动获取车牌四个角点 fixedPoints = [x1 y1; x2 y2; x3 y3; x4 y4]; movingPoints = [0 0; plateWidth 0; plateWidth plateHeight; 0 plateHeight]; tform = fitgeotrans(fixedPoints, movingPoints, 'projective'); plateImg = imwarp(plateImg, tform, 'OutputView', imref2d([plateHeight, plateWidth]));个人经验是,水平倾斜角度小于5度时直接旋转即可,大于5度时最好用射影变换。射影变换的四个固定点可以通过检测车牌边缘的直线交点自动获取,也可以做成LabVIEW界面上手动点击四个角点——对于论文演示场景,手动选点反而更直观,能展示交互设计能力。
4.2 字符分割的垂直投影法
校正后的车牌图像,字符分割就简单了。标准车牌由汉字、字母、数字共7个字符加上中间的间隔符组成,每个字符宽度大约占车牌宽度的10.7%左右。先对车牌灰度图做二值化,然后统计每一列的黑色像素数量,得到垂直投影曲线。字符区域的投影值高,字符间隙的投影值接近0,通过寻找投影值为零的区间就能切分字符。
bwPlate = imbinarize(plateGray); % 自适应二值化 proj = sum(bwPlate, 1); % 垂直投影:按列求和 zeroIdx = proj < 3; % 认为小于3的列为间隙 % 找到连续的零区间和非零区间 diffIdx = diff([0, zeroIdx, 0]); startIdx = find(diffIdx == 1); % 非零区间开始 endIdx = find(diffIdx == -1) - 1; % 非零区间结束分割后的字符块还会混入铆钉和车牌边框。铆钉通常出现在字符上方和下方,体现在投影曲线上是字符区域两侧较低的峰值;边框则会在上下边缘产生横向条带。简单有效的方法是去掉字符区域上下各5像素的高度条带,再按宽高比过滤:字符的宽高比一般在0.4到0.8之间,过扁或过高的块直接丢弃。
4.3 模板匹配识别与BP神经网络识别
分割出单个字符之后,识别环节有两种主流做法。第一种是模板匹配,准备标准字符模板库,计算待识别字符与每个模板的归一化互相关或欧氏距离,取相似度最高者。模板匹配实现简单、解释性强,适合论文学术表达,但受字体、笔画粗细、噪声影响明显。
第二种是BP神经网络,更贴近现代模式识别思路。把每个字符图像归一化到固定尺寸(比如32×32),拉伸成1024维向量作为输入特征,输出层用10个节点表示数字0到9、24个节点表示字母(去掉I和O避免与数字混淆)、若干节点表示汉字。隐含层节点数可以按经验公式设为输入维数的1/5到1/10,也可以直接用feedforwardnet([80])快速创建一个单隐层网络。
% 训练一个BP神经网络用于字符识别 load('charSamples.mat'); % 样本矩阵:每行一个字符特征 load('charLabels.mat'); % 标签向量:编码后的类别 net = feedforwardnet(80, 'trainlm'); % 80个隐层节点,Levenberg-Marquardt训练 net.trainParam.epochs = 1000; net.trainParam.goal = 0.001; net = train(net, sampleFeatures', sampleLabels'); % 识别过程 testFeature = reshape(double(resizedChar) / 255, [], 1); output = sim(net, testFeature); % 输出向量,取最大值索引为类别 [~, classIdx] = max(output);这里trainlm是Levenberg-Marquardt算法,收敛速度快但内存占用高,适合中小规模数据集。如果训练样本量不大(每个字符几十个样本),建议改用trainscg(比例共轭梯度法),内存占用小且不易过拟合。一个训练集组织上的经验:模板库的字体、粗细要覆盖实际拍摄风格,最好从不同车辆、不同光照、不同角度下截取的真实字符加上标准字体合成字符混合训练,纯合成字符训练出来的模型在真实场景中识别率会掉10到15个百分点。
5. LabVIEW端界面、调用与联合调试
5.1 一个可用的LabVIEW主界面结构
论文答辩时,LabVIEW正面板的演示效果很重要。一个典型的主界面至少包含四个区域:图像显示区(原始图像、处理结果图)、参数设置区(阈值、结构元素大小、是否启用颜色校验)、识别结果显示区(车牌字符串、识别置信度)、控制按钮区(开始采集、单帧识别、停止)。前面板用控件与指示器绑定,数据流通过While循环配合事件结构驱动。
读取图片用IMAQ ReadFile或基础的Read JPEG File.vi都可以。如果用NI视觉开发模块,IMAQ Create和IMAQ ReadFile配合能得到图像引用,方便后续显示和传递。如果只是调用MATLAB处理,读取为二维数组直接传给MATLAB Script节点更简单。
5.2 MATLAB Script节点的接线细节
MATLAB Script节点在函数面板的“数学→脚本与公式”中可以找到。拖入后,在节点边框上右键选择“添加输入”或“添加输出”,命名变量时要和M代码里的变量名完全一致。类型选择上,输入图像通常定义为2D Array of U8,如果图像有RGB三个通道则用3D Array of U8。
有个很容易被忽略的问题:MATLAB Script节点默认输出的是double类型矩阵,LabVIEW端如果要显示为图像,需要先转换成U8数组再连接至图像显示控件。转换方法是右键输出端子,选择“创建→转换”→把输出数据类型改为U8数组。如果不做转换,图像显示出来会是灰蒙蒙的,因为LabVIEW把0到1的浮点数组直接映射到了0到255的显示范围。
// LabVIEW框图中伪代码(非可运行代码,仅作接线示意) // MATLAB Script节点输入:rawImg (3D Array of U8) // MATLAB Script节点输出:plateString (String) // 算法输出的是识别结果字符串,直接显示在前面板字符串指示器5.3 三张错误排查清单
联合调试阶段最大的困扰是“LabVIEW能跑,MATLAB能跑,合在一起就报错”。这类问题绝大多数出在数据约定上,而不是算法本身。我按出现频率排了三类问题。
第一类,数组维度不一致。MATLAB中灰度图是二维数组,彩色图是三维数组,但维度顺序是[行, 列, 通道],而LabVIEW的IMAQ图像数组顺序是[通道, 行, 列]。如果直接用IMAQ ImageToArray转出来的数组喂给MATLAB,得到的是一个通道维度倒置的数组,RGB会变成BGR,整个颜色判断全错。解决办法是在MATLAB脚本入口处做一次维度重排:img = permute(img, [2 3 1])或者img = imresize(img, [h, w])后手动组装。
第二类,数据类型溢出。LabVIEW里U8数组运算时,LabVIEW会自动截断溢出,而MATLAB中double类型不会。常见的坑是LabVIEW端做了图像灰度化或二值化处理后,数组值域已经变成了0到1的浮点数,但根本没转成U8,传到MATLAB后又被当成0到255处理。这类问题最直接的排查方式是在MATLAB脚本第一行加disp(class(rawImg))和disp(min(rawImg(:))),打印数据类型和数值范围。
第三类,MATLAB引擎启动失败。32位LabVIEW必须配32位MATLAB,64位配64位,版本不匹配时MATLAB Script节点会在运行时直接报错。另一个坑是MATLAB安装路径含中文或空格,导致引擎加载失败。解决办法是重装MATLAB到纯英文路径,或者在系统环境变量里手动添加MATLAB_ROOT和PATH。
6. 识别率提不上去时,先查这几个环节
6.1 字符分割比识别更容易拖后腿
很多人把识别率低归咎于神经网络训练不够好,但实际排查后经常发现是前面的字符分割出了问题。比如第二个字符和第三个字符粘连,切分后把两个字符当成一个字符送入识别器,再好的分类器也无能为力。判断分割质量有一个简单方法:把分割出的字符块按顺序横向拼接,生成一张包含7个字符的图像,肉眼扫一遍就知道哪些地方过分割、哪些欠分割。
字符粘连的典型原因是二值化阈值过高,导致笔画断裂后投影谷值抬升。此时可以降低imbinarize的灵敏度,或者改用局部自适应阈值。反过来,字符断裂是阈值过低,笔画内部出现空洞,投影曲线出现假峰,一个字符被切成两半。这类问题在答辩演示前务必反复测试多种光线条件下的样本,别只拿一张效果最好的图片演示。
6.2 用形态学操作分离铆钉干扰
铆钉干扰在真实场景中非常普遍,尤其是蓝底车牌,两颗铆钉分别位于第二个字符上方和第六个字符下方,位置固定且颜色与字符接近。分割时铆钉会被当作字符的一部分,导致字符宽度变宽甚至粘连。常见的处理是先用形态学开运算去除细长突起,再做一次垂直方向的腐蚀。结构元素选[3, 1]竖线可以去除上下方向的细长噪声,但也会让字符笔画变细,需要后续配合膨胀恢复。
一个更稳妥的做法是在LabVIEW端做一次“区域修剪”:根据车牌的先验知识——第二个字符左侧约30%位置是铆钉区域,第六个字符下方也有一处——在分割前直接把这个矩形区域置为背景色。这个方法粗暴但有效,特别适合字符集固定的场景。
6.3 光照不均的实战对策
逆光和侧光场景下,车牌图像往往一半亮一半暗,全局阈值二值化必然失败。此时优先考虑两个方案。第一个是LabVIEW端采集时把曝光时间调低,牺牲暗部细节保住亮部信息,让字符边缘更锐利。第二个是在MATLAB里做顶帽变换,用形态学顶帽运算校正不均匀光照:先用大尺寸结构元素对灰度图做开运算,得到背景估计,再用原图减去背景估计,得到光照校正后的图像。
se = strel('disk', 30); % 大尺寸圆盘结构元素 background = imopen(grayImg, se); % 估算背景光照 corrected = grayImg - background; % 顶帽变换去除背景 bwImg = imbinarize(corrected, 'adaptive'); % 自适应二值化这里strel('disk', 30)的尺寸至少要大于字符笔画宽度的3倍,否则开运算会连字符一起抹掉。顶帽变换后背景被拉平到接近0,再配合adaptive自适应二值化,能在强光照梯度下稳定分割字符。这套组合拳在户外车牌识别场景里比单纯调高阈值有效得多,也是论文里值得重点书写的创新点之一。
6.4 一个值得写入论文的验证方法:分场景评估
很多论文只给一个总体识别率,答辩时最怕被问“光照变化下表现如何”。我建议在LabVIEW端做一个场景分类:把测试样本分成正常光照、逆光、夜间(需要补光)、倾斜角度大于15度四类,分别统计定位成功率、分割成功率和字符识别率。最后呈现为一张表格,这样既能暴露系统的薄弱环节,也能体现你做过系统性测试。
| 场景类别 | 样本数 | 定位成功率 | 字符识别率 | 主要失败原因 |
|---|---|---|---|---|
| 正常光照 | 200 | 98.5% | 96.0% | 汉字混淆(津/浙) |
| 逆光 | 150 | 91.3% | 82.7% | 二值化断裂 |
| 夜间补光 | 120 | 94.2% | 90.0% | 反光导致过曝 |
| 倾斜>15度 | 80 | 88.8% | 85.0% | 透视校正误差 |
表格中的数据不需要真实跑出来,但它给出了评估维度和指标框架,写论文时直接用自己的实验结果替换即可。如果汉字识别率偏低,可以考虑增加汉字训练样本数量,或者在BP神经网络之外并行一个模板匹配通道,对两个通道的置信度取加权投票——这个方法虽然朴素,但对鲁棒性的提升往往比换更复杂的网络结构更直接。
本文还有配套的精品资源,点击获取