资讯动态

基于Matlab的车牌识别:从颜色分割到字符投影分割的完整实现

发布时间:2026/8/12 20:12:05 来源:尧图企业网站定制
1. 项目缘起与核心目标最近在整理一些老项目翻到了一个用Matlab做的车牌识别系统。当时的需求很明确就是处理一批停车场抓拍的车辆图片把车牌区域抠出来再把上面的字符一个个切分开。这个需求听起来简单但实际做起来你会发现从一张背景复杂的图片里精准地找到那块小小的车牌再把它上面的“京A·88888”这样的字符完整、干净地切出来每一步都藏着不少门道。特别是国内车牌颜色多样蓝牌、绿牌新能源最常见黄牌、白牌、黑牌也各有各的规矩不同颜色的车牌在反光、污损、光照不均的情况下呈现出的图像特征差异很大。我这次分享的就是针对最常见的蓝色和绿色车牌用Matlab实现的一套从定位到字符分割的完整流程。网上很多教程要么只讲定位要么分割算法过于理想化对实际图片中的噪声、倾斜、光照变化处理得不够。我这个方案是从实际项目里打磨出来的重点不在于追求最高深的算法而在于稳定、可解释、好调整。你会看到我大量使用了图像处理的基本操作比如色彩空间转换、形态学处理、投影分析等通过合理的组合和参数调优来应对大部分常规场景。对于Matlab用户尤其是做课程设计、毕业设计或者刚接触图像处理的同学来说这套代码的结构清晰每一步都有可视化中间结果非常适合理解和上手改造。2. 车牌图像预处理为分割打好地基拿到一张车辆图片第一步不是直接找车牌而是先把它“收拾”干净并转换成更容易让我们找到车牌的形式。这一步做得好后面的定位和分割会事半功倍。2.1 图像读取与初步降噪首先我们读入图像。这里要注意Matlab的imread函数会保持图像的原始数据类型uint8, uint16等。对于常见的8位JPEG图片像素值范围是0-255。% 读取原始图像 original_img imread(car_image.jpg); % 转换为double类型便于计算但注意值域变为0-1 img_double im2double(original_img);车辆图片通常来自监控或手机拍摄难免有噪声。一个简单有效的方法是使用高斯滤波进行平滑它能较好地保留边缘信息。% 创建高斯滤波器 h fspecial(gaussian, [5 5], 1.5); % 对RGB三个通道分别滤波 smoothed_img imfilter(img_double, h, replicate);注意滤波核大小和标准差需要根据图像分辨率调整。图片尺寸大如1920x1080核可以稍大如7x7尺寸小如640x480核应减小如3x3。过度平滑会模糊车牌边缘导致定位失败。2.2 色彩空间转换与车牌颜色特征提取这是本项目的关键。蓝色和绿色车牌在RGB空间里虽然肉眼容易区分但直接用RGB分量做阈值分割效果很差因为光照变化会极大影响RGB值。我们需要转换到对亮度不敏感的色度空间。HSVHue, Saturation, Value空间是一个非常好的选择其中H色调通道直接反映了颜色种类受光照强度影响小。% 将图像从RGB转换到HSV色彩空间 hsv_img rgb2hsv(smoothed_img); H hsv_img(:,:,1); % 色调通道范围0-1 S hsv_img(:,:,2); % 饱和度通道 V hsv_img(:,:,3); % 明度通道对于蓝色车牌其主色调在HSV空间中对应H值大约在0.55-0.65对应220-240度的区间。对于绿色新能源车牌其H值大约在0.25-0.4对应90-144度的区间。我们可以通过设定阈值来创建二值掩膜。% 定义蓝色和绿色的色调阈值范围需要微调 blue_low 0.55; blue_high 0.65; green_low 0.25; green_high 0.40; % 创建蓝色车牌掩膜 blue_mask (H blue_low) (H blue_high) (S 0.3) (V 0.3); % 创建绿色车牌掩膜 green_mask (H green_low) (H green_high) (S 0.3) (V 0.3); % 合并掩膜 color_mask blue_mask | green_mask;这里对饱和度S和明度V也做了阈值0.3是为了过滤掉那些颜色很淡饱和度低或者太暗明度低的像素它们可能是阴影或者深色车身容易造成干扰。2.3 形态学处理连接与净化上一步得到的color_mask通常是稀疏的、带有孔洞和噪声点的。车牌区域应该是连成一片的矩形块。我们使用形态学操作来优化它。闭运算先膨胀后腐蚀。可以填充车牌字符内部以及字符之间的细小间隙让车牌区域变成一个更完整的连通域。se_close strel(rectangle, [5, 20]); % 结构元素水平方向长用于连接字符 closed_mask imclose(color_mask, se_close);开运算先腐蚀后膨胀。可以消除小的白色噪声点比如车身上反光的蓝色/绿色小点。se_open strel(rectangle, [3, 3]); cleaned_mask imopen(closed_mask, se_open);填充孔洞确保车牌区域是实心的。filled_mask imfill(cleaned_mask, holes);经过这些操作图像中符合蓝/绿色特征的、且连成片的区域就会被凸显出来为下一步的定位做好了准备。务必在每一步之后用imshow查看中间结果这是调试参数的黄金法则。3. 车牌区域定位与校正预处理后我们得到了一个二值掩膜filled_mask白色区域就是候选的车牌区域。但可能不止一个比如车身上的其他蓝色/绿色装饰我们需要找到最像车牌的那一个。3.1 连通域分析与候选区筛选使用bwconncomp或regionprops函数来分析掩膜中的每一个白色连通区域。% 标记连通域 cc bwconncomp(filled_mask); % 获取每个连通域的特性 stats regionprops(cc, BoundingBox, Area, Eccentricity, Solidity); % 初始化最佳车牌区域 best_box []; max_score -inf; % 遍历所有连通域计算一个“车牌似然”分数 for i 1:length(stats) box stats(i).BoundingBox; % [x, y, width, height] area stats(i).Area; aspect_ratio box(3) / box(4); % 宽高比 % 典型车牌宽高比大约在 3:1 到 4:1 之间 if aspect_ratio 2 || aspect_ratio 5 continue; % 宽高比不符合跳过 end % 面积不能太小排除噪声也不能太大可能是一整片车身 img_area size(filled_mask, 1) * size(filled_mask, 2); if area 0.0005 * img_area || area 0.05 * img_area continue; end % 计算一个综合评分鼓励面积适中、宽高比接近3.5、形状扎实的区域 aspect_score 1 / abs(aspect_ratio - 3.5); area_score 1 - abs(area/(0.01*img_area) - 1); % 假设车牌面积约占图像1% solidity stats(i).Solidity; % 实心度越接近1形状越实 score aspect_score * 0.4 area_score * 0.3 solidity * 0.3; if score max_score max_score score; best_box box; end end这个评分机制非常实用它把我们对车牌的先验知识长条形、特定大小、实心编码了进去能有效过滤掉大部分错误候选区。3.2 车牌区域提取与透视校正找到best_box后我们就可以从原图上把车牌区域抠出来了。if ~isempty(best_box) % 为了确保不越界对边界进行约束 x1 max(1, round(best_box(1))); y1 max(1, round(best_box(2))); x2 min(size(original_img, 2), round(best_box(1)best_box(3))); y2 min(size(original_img, 1), round(best_box(2)best_box(4))); % 提取车牌区域彩色图像 plate_region_color original_img(y1:y2, x1:x2, :); % 转换为灰度图用于后续字符分割 plate_region_gray rgb2gray(plate_region_color); else error(未检测到符合条件的车牌区域); end然而抓拍的车牌图像很可能存在倾斜水平倾斜或垂直倾斜。严重的倾斜会直接影响字符分割的准确性。我们需要进行校正。这里主要处理水平倾斜车牌旋转可以使用Radon变换或Hough直线检测来估计倾斜角度。一个更稳健的方法是对二值化的车牌区域进行边缘检测如Canny然后使用Hough变换检测最长的几条直线计算它们的平均角度。% 对车牌灰度图进行二值化采用自适应阈值以应对光照不均 plate_bw imbinarize(plate_region_gray, adaptive, Sensitivity, 0.6); % 边缘检测 plate_edge edge(plate_bw, canny); % Hough变换检测直线 [H, T, R] hough(plate_edge); P houghpeaks(H, 5, threshold, ceil(0.3*max(H(:)))); % 找5个峰值点 lines houghlines(plate_edge, T, R, P, FillGap, 20, MinLength, 30); % 计算检测到的直线的平均角度过滤掉接近垂直的线 angles []; for k 1:length(lines) theta lines(k).theta; % 我们关心的是接近水平线的倾斜theta接近0或180度 if abs(theta) 45 || abs(theta) 135 angles [angles, theta]; end end if ~isempty(angles) % 将角度转换到-45到45度范围 angles(angles 90) angles(angles 90) - 180; avg_angle mean(angles); % 旋转校正 plate_region_corrected imrotate(plate_region_color, avg_angle, bilinear, crop); plate_gray_corrected imrotate(plate_region_gray, avg_angle, bilinear, crop); else plate_region_corrected plate_region_color; plate_gray_corrected plate_region_gray; end校正后我们得到了一个相对端正的车牌图像为字符的精确分割创造了条件。4. 车牌字符的精细分割这是整个流程中最考验细节的一步。目标是得到7个普通车牌或8个新能源车牌独立的字符图像。4.1 字符区域二值化与去噪首先将校正后的灰度车牌图像二值化把字符变成白色前景背景变成黑色。% 使用局部自适应阈值对光照不均更鲁棒 plate_bw_corrected imbinarize(plate_gray_corrected, adaptive, Sensitivity, 0.55); % 反转使字符为白色1背景为黑色0 plate_bw_corrected ~plate_bw_corrected; % 去除小面积噪声点 plate_bw_clean bwareaopen(plate_bw_corrected, 30); % 移除面积小于30像素的连通域注意Sensitivity参数是关键值越高更多的像素被归为前景字符。对于对比度低的车牌需要调高此值。务必通过imshowpair对比原图和二值图来调整。4.2 垂直投影分析与字符初步分割字符分割的核心思想是投影法。垂直投影Vertical Projection统计二值图像每一列上白色像素字符的数量。在字符之间的间隙处投影值会很小或为零这些位置就是天然的分割点。% 计算垂直投影 vertical_projection sum(plate_bw_clean, 1); % 平滑投影曲线减少毛刺干扰 smoothed_vp smoothdata(vertical_projection, gaussian, 10); % 寻找投影的波谷分割点 % 方法找到投影值低于平均值的连续区域的中点 mean_vp mean(smoothed_vp); valley_zones smoothed_vp (mean_vp * 0.3); % 阈值设为平均值的30%但是直接找波谷可能会因为字符粘连如“京”字的左右部分或断裂而产生错误分割。我们需要引入一些启发式规则字符宽度先验一个车牌的字符宽度大致相等。我们可以先估计平均字符宽度。% 粗略估计字符区域投影值大于阈值的列 char_cols find(smoothed_vp mean_vp * 0.5); if ~isempty(char_cols) estimated_char_width round(length(char_cols) / 7.5); % 假设平均7.5个字符块 else estimated_char_width round(size(plate_bw_clean, 2) / 8); end动态合并与分割遍历图像宽度当处于字符区域投影值高时开始记录遇到波谷且当前记录的宽度接近estimated_char_width时认为一个字符结束。如果记录的宽度远大于平均宽度则可能是两个字符粘连需要尝试在这个字符块内部寻找一个较深的局部波谷进行强制分割。4.3 水平校正与字符高度归一化即使车牌区域校正了单个字符也可能有轻微的倾斜。我们可以对每个切分出来的字符图像块再进行一次水平投影Horizontal Projection分析其上下边界并进行裁剪使每个字符图像的高度一致去除上下多余的空白或边框。for char_idx 1:length(char_bboxes) % char_bboxes是上一步得到的每个字符的边界框 single_char plate_bw_clean(char_bboxes{char_idx}); % 水平投影找到字符的上下边界 horizontal_proj sum(single_char, 2); % 找到投影值大于最大投影值5%的行作为字符的上下界 top_row find(horizontal_proj max(horizontal_proj)*0.05, 1, first); bottom_row find(horizontal_proj max(horizontal_proj)*0.05, 1, last); if ~isempty(top_row) ~isempty(bottom_row) char_cropped single_char(top_row:bottom_row, :); % 可以在这里将字符图像缩放到统一高度比如40像素 target_height 40; char_resized imresize(char_cropped, [target_height, NaN]); % 保持宽高比 % 存储处理好的字符图像 chars_segmented{char_idx} char_resized; end end4.4 处理特殊字符与粘连情况车牌字符包括汉字、字母和数字。汉字结构复杂容易粘连如“苏”、“浙”字母数字相对简单但“1”和“I”等字符较窄。算法需要处理这些情况粘连字符分割如果某个字符块的宽度明显大于平均宽度的1.5倍且其垂直投影曲线中间存在一个明显的“凹陷”局部最小值低于某个阈值则在此凹陷位置进行分割。窄字符处理对于宽度过窄如小于平均宽度0.6倍的字符块需要检查其前后字符块。如果它本身投影特征明显是一个独立字符则保留否则可能是噪声或断裂字符的一部分需考虑与相邻块合并。字符数量验证最终分割出的字符数量应在7或8个。如果数量不对比如只有6个很可能是发生了过度分割一个字符被切成两个或欠分割两个字符粘在一起。可以根据字符块的宽度分布进行回溯调整。例如如果有一个块特别宽而总数又少就优先尝试在这个宽块里寻找分割点。5. 核心代码模块与参数调优指南将上述步骤模块化是构建一个健壮系统的关键。下面给出一个主流程的框架代码并重点讨论几个需要手动调优的关键参数这些参数直接决定了算法在不同场景下的适应性。function [plate_region, segmented_chars] license_plate_segmentation(img_path) % 1. 读取与预处理 img imread(img_path); img im2double(img); % ... (高斯滤波等) % 2. 基于HSV的颜色分割 hsv_img rgb2hsv(img); H hsv_img(:,:,1); S hsv_img(:,:,2); % 关键参数1: 颜色阈值 (blue_low, blue_high, green_low, green_high, S_thresh) % 这些值需要根据你的图片集调整。建议用imtool查看典型蓝/绿车牌的H、S值范围。 blue_mask (H 0.56) (H 0.64) (S 0.35); green_mask (H 0.28) (H 0.38) (S 0.35); color_mask blue_mask | green_mask; % 3. 形态学处理 % 关键参数2: 形态学结构元素大小 % se_close的尺寸[height, width]影响字符连接效果。字符间隙大则需增加width。 se_close strel(rectangle, [4, 25]); closed_mask imclose(color_mask, se_close); se_open strel(square, 3); cleaned_mask imopen(closed_mask, se_open); filled_mask imfill(cleaned_mask, holes); % 4. 车牌定位 % ... (连通域分析评分机制) % 关键参数3: 车牌宽高比和面积范围 % aspect_ratio的上下限、面积占比(0.0005, 0.05)需要根据你的摄像头视角和距离调整。 % 5. 倾斜校正 % ... (Hough变换) % 关键参数4: Hough峰值阈值和线段最小长度 % houghpeaks中的threshold0.3*max(H(:))和houghlines中的MinLength影响检测到的直线数量和质量。 % 6. 字符分割 % ... (二值化投影分析) % 关键参数5: 二值化灵敏度与投影分割阈值 % imbinarize的Sensitivity (0.55)以及寻找波谷时的阈值(mean_vp * 0.3)是字符分割成败的核心。 % 对比度低的图片需提高Sensitivity字符间隙小的需降低波谷阈值。 % 返回结果 plate_region plate_region_corrected; segmented_chars chars_segmented; % 单元格数组存储每个字符图像 end参数调优实战心得 不要试图找一套“万能参数”。最好的方法是准备一个包含20-30张典型图片不同光照、不同角度、不同清晰度的小测试集。编写一个脚本用不同的参数组合批量运行并自动评估定位成功率和字符分割正确率。评估指标可以很简单定位成功车牌区域被完整框出分割成功字符数量正确且肉眼观察分割合理。通过这种半自动化的网格搜索你能快速找到针对你特定图片集的最优参数范围。6. 常见问题排查与算法局限性分析在实际运行中你肯定会遇到各种“翻车”现场。下面列几个我踩过的坑和解决办法问题根本检测不到车牌区域color_mask全黑或只有零星点。原因HSV阈值设置不正确或者图片色偏严重比如傍晚偏黄的光照会影响H值。排查用imtool或imshow分别查看H、S、V三个通道的图像。确认车牌区域在H通道上是否与背景有明显区别。解决放宽颜色阈值范围特别是S和V的下限。或者尝试其他颜色空间如YCrCb。对于蓝色车牌在YCrCb空间中Cr分量红色差和Cb分量蓝色差的组合有时比HSV更稳定。可以尝试用rgb2ycbcr转换后针对蓝色和绿色设定Cr、Cb的阈值。问题检测到多个候选区但选中了错误区域比如车身上的蓝色广告。原因连通域评分机制中宽高比和面积的权重或阈值设置不合理。排查打印出每个候选区域的aspect_ratio,area,solidity和最终得分。解决增加solidity实心度的权重因为车牌区域通常是实心矩形而车身上的文字或图案可能是镂空的。也可以加入纹理特征判断车牌区域有规律的字符纹理可以通过计算水平方向的灰度变化频率来识别。问题车牌定位正确但字符分割数量不对最常见。场景1字符粘连如“京”被切成左右两半。解决在垂直投影分析阶段不要简单以波谷为零作为分割点。先估算平均字符宽度对于宽度超过平均宽度1.8倍的区域强制在其内部寻找投影的局部最小值点进行分割。可以结合字符的连通域分析如果一个宽区域内包含两个明显的独立连通域则应在它们之间分割。场景2字符断裂如“1”或“I”太细被当成噪声去除。解决在bwareaopen去噪时面积阈值不要设得太大我代码中的30是个起点可能需要降到15。或者在投影分割后对于宽度过窄的字符块检查其垂直投影是否仍然具有单峰特征如果是则保留。场景3车牌边框干扰。解决在字符分割前先进行上下边框去除。利用水平投影找到车牌图像上下方大片的连续白色区域边框并裁剪掉。也可以使用形态学的腐蚀操作轻微腐蚀掉边框。问题倾斜校正失败反而校正歪了。原因Hough变换检测到的直线不是车牌的上下边可能是字符内部的斜线或噪声。解决不要直接用所有直线的平均角度。可以先计算所有直线角度的直方图选择出现频率最高的那个角度众数作为校正角度这通常对应车牌边框的方向。或者更简单粗暴但有效的方法是如果检测到的直线角度方差很大说明检测不可靠干脆跳过校正步骤前提是原始倾斜不严重。算法局限性 必须承认这个基于传统图像处理的方案有其天花板极端光照夜间低光照、强光直射导致车牌过曝或反光成白色颜色特征会完全失效。严重污损车牌被泥土、积雪部分覆盖字符区域不完整。非标准车牌早期车牌、武警车牌等颜色规格不统一。大角度倾斜本文的校正算法对水平倾斜有效但对垂直倾斜俯仰角效果有限。对于这些复杂场景就需要引入更强大的工具例如深度学习。你可以用YOLO、SSD等网络做车牌检测用CRNN或分割网络如PSPNet做字符识别它们对复杂环境的鲁棒性远高于传统方法。本项目的价值在于它清晰地揭示了车牌识别任务的内在逻辑和挑战为你理解更高级的深度学习方案打下了坚实的基础。当你用CNN训练一个车牌检测器时你会更清楚为什么需要海量且多样的数据——因为我们要让模型学会应对所有我们上面提到的、传统方法难以处理的 corner cases。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价