基于阈值分割的车牌图像识别MATLAB实现
基于阈值分割的车牌图像识别MATLAB程序。该程序实现了从图像预处理、车牌定位、字符分割到字符识别的全过程。
%% 基于阈值分割的车牌图像识别系统
% 描述: 实现车牌定位、字符分割和识别的完整流程
%% 主程序入口
function licensePlateRecognition()
% 清空环境
clear; close all; clc;
% 参数设置
params = struct();
params.minPlateArea = 3000; % 最小车牌面积
params.aspectRatio = [2.5, 5.5]; % 车牌宽高比范围
params.charAspectRatio = [0.2, 0.6]; % 字符宽高比范围
params.blueThreshold = 0.6; % 蓝色通道阈值
params.binaryThreshold = 0.6; % 二值化阈值
params.minCharArea = 100; % 最小字符面积
% 1. 图像读取与预处理
plateImage = imread('car_plate.jpg');
if size(plateImage, 3) == 1
grayImage = plateImage;
else
grayImage = rgb2gray(plateImage);
end
% 显示原始图像
figure('Name', '车牌识别系统', 'NumberTitle', 'off', 'Position', [100, 100, 1200, 600]);
subplot(2,3,1);
imshow(plateImage);
title('原始图像');
% 2. 车牌定位
[plateRegion, plateImage] = locateLicensePlate(plateImage, params);
if isempty(plateRegion)
error('未检测到车牌区域!');
end
% 显示定位结果
subplot(2,3,2);
imshow(plateImage);
title('车牌定位结果');
% 3. 图像预处理
binaryPlate = preprocessPlate(plateImage, params);
% 显示二值化结果
subplot(2,3,3);
imshow(binaryPlate);
title('二值化车牌');
% 4. 字符分割
charImages = segmentCharacters(binaryPlate, params);
% 显示分割结果
subplot(2,3,4);
showSegmentedChars(charImages);
title('字符分割结果');
% 5. 字符识别
recognizedText = recognizeCharacters(charImages);
% 显示识别结果
subplot(2,3,5);
imshow(plateImage);
title(['识别结果: ', recognizedText]);
% 显示最终结果
subplot(2,3,6);
textImage = insertText(zeros(size(plateImage)), [10, 10], recognizedText, ...
'FontSize', 24, 'BoxColor', 'green', 'TextColor', 'white');
imshow(textImage);
title('最终识别结果');
% 控制台输出
fprintf('\n===== 车牌识别结果 =====\n');
fprintf('识别结果: %s\n', recognizedText);
end
%% 车牌定位函数
function [plateRegion, plateImage] = locateLicensePlate(image, params)
% 转换为HSV颜色空间
if size(image, 3) == 3
hsvImage = rgb2hsv(image);
hue = hsvImage(:,:,1);
saturation = hsvImage(:,:,2);
value = hsvImage(:,:,3);
else
error('输入图像必须是彩色图像');
end
% 蓝色车牌检测(中国车牌)
blueMask = (hue > 0.55) & (hue < 0.65) & (saturation > 0.4) & (value > 0.3);
% 形态学操作增强车牌区域
se = strel('rectangle', [3, 3]);
blueMask = imopen(blueMask, se);
blueMask = imclose(blueMask, strel('rectangle', [25, 3]));
% 查找连通区域
cc = bwconncomp(blueMask);
stats = regionprops(cc, 'BoundingBox', 'Area');
% 筛选车牌候选区域
plateRegion = [];
plateImage = [];
maxArea = 0;
for i = 1:length(stats)
bbox = stats(i).BoundingBox;
aspectRatio = bbox(3)/bbox(4);
area = bbox(3)*bbox(4);
% 根据面积和宽高比筛选
if area > params.minPlateArea && ...
aspectRatio > params.aspectRatio(1) && ...
aspectRatio < params.aspectRatio(2) && ...
area > maxArea
plateRegion = bbox;
maxArea = area;
end
end
% 提取车牌区域
if ~isempty(plateRegion)
plateImage = imcrop(image, plateRegion);
else
% 如果未检测到蓝色车牌,尝试灰度边缘检测
grayImage = rgb2gray(image);
edgeImage = edge(grayImage, 'sobel');
filledImage = imfill(edgeImage, 'holes');
cleanedImage = bwareaopen(filledImage, params.minPlateArea);
% 再次查找连通区域
cc = bwconncomp(cleanedImage);
stats = regionprops(cc, 'BoundingBox', 'Area');
for i = 1:length(stats)
bbox = stats(i).BoundingBox;
aspectRatio = bbox(3)/bbox(4);
area = bbox(3)*bbox(4);
if area > params.minPlateArea && ...
aspectRatio > params.aspectRatio(1) && ...
aspectRatio < params.aspectRatio(2) && ...
area > maxArea
plateRegion = bbox;
maxArea = area;
end
end
if ~isempty(plateRegion)
plateImage = imcrop(image, plateRegion);
end
end
end
%% 车牌图像预处理
function binaryPlate = preprocessPlate(plateImage, params)
% 转换为灰度图像
if size(plateImage, 3) == 3
grayPlate = rgb2gray(plateImage);
else
grayPlate = plateImage;
end
% 对比度增强
enhancedPlate = adapthisteq(grayPlate);
% 中值滤波去噪
filteredPlate = medfilt2(enhancedPlate, [3, 3]);
% 二值化(使用Otsu方法)
threshold = graythresh(filteredPlate);
binaryPlate = imbinarize(filteredPlate, threshold * params.binaryThreshold);
% 反色处理(使字符为黑色,背景为白色)
binaryPlate = ~binaryPlate;
% 形态学操作去除小噪点
binaryPlate = bwareaopen(binaryPlate, 20);
% 填充字符内部空洞
binaryPlate = imfill(binaryPlate, 'holes');
end
%% 字符分割函数
function charImages = segmentCharacters(binaryPlate, params)
% 垂直投影
verticalProjection = sum(binaryPlate, 1);
% 平滑投影曲线
smoothedProjection = movmean(verticalProjection, 5);
% 寻找波谷作为字符分割点
minVal = min(smoothedProjection);
threshold = minVal + 0.2 * (max(smoothedProjection) - minVal);
valleys = find(smoothedProjection < threshold);
% 分组连续波谷
groups = [];
currentGroup = [];
for i = 1:length(valleys)
if isempty(currentGroup) || valleys(i) == currentGroup(end) + 1
currentGroup = [currentGroup, valleys(i)];
else
groups{end+1} = currentGroup;
currentGroup = valleys(i);
end
end
if ~isempty(currentGroup)
groups{end+1} = currentGroup;
end
% 计算字符中心位置
charCenters = [];
for i = 1:length(groups)
charCenters(i) = mean(groups{i});
end
% 按位置排序
[charCenters, order] = sort(charCenters);
groups = groups(order);
% 分割字符
charImages = {};
charCount = 0;
for i = 1:length(groups)
startX = min(groups{i});
endX = max(groups{i});
charWidth = endX - startX + 1;
% 提取字符区域
charRegion = binaryPlate(:, startX:endX);
% 水平投影
horizontalProjection = sum(charRegion, 2);
top = find(horizontalProjection > 0, 1, 'first');
bottom = find(horizontalProjection > 0, 1, 'last');
if isempty(top) || isempty(bottom)
continue;
end
% 提取字符图像
charImage = charRegion(top:bottom, :);
% 过滤小区域
if size(charImage, 1) * size(charImage, 2) < params.minCharArea
continue;
end
% 调整字符方向(确保高度大于宽度)
if size(charImage, 1) < size(charImage, 2)
charImage = imrotate(charImage, 90);
end
% 调整大小为标准尺寸
charImage = imresize(charImage, [40, 20]);
charCount = charCount + 1;
charImages{charCount} = charImage;
end
% 如果字符数量异常,尝试备用分割方法
if charCount < 5 || charCount > 8
charImages = alternativeSegmentation(binaryPlate, params);
end
end
%% 备用字符分割方法
function charImages = alternativeSegmentation(binaryPlate, params)
charImages = {};
% 使用连通区域分析
cc = bwconncomp(binaryPlate);
stats = regionprops(cc, 'BoundingBox', 'Area', 'Image');
% 筛选字符区域
charRegions = [];
for i = 1:length(stats)
bbox = stats(i).BoundingBox;
aspectRatio = bbox(3)/bbox(4);
area = bbox(3)*bbox(4);
if area > params.minCharArea && ...
aspectRatio > params.charAspectRatio(1) && ...
aspectRatio < params.charAspectRatio(2)
charRegions = [charRegions; bbox];
end
end
% 按x坐标排序
[~, order] = sort(charRegions(:,1));
charRegions = charRegions(order, :);
% 分割字符
for i = 1:size(charRegions, 1)
bbox = charRegions(i, :);
charImage = imcrop(binaryPlate, bbox);
% 调整大小为标准尺寸
charImage = imresize(charImage, [40, 20]);
charImages{end+1} = charImage;
end
end
%% 显示分割字符
function showSegmentedChars(charImages)
numChars = length(charImages);
if numChars == 0
imshow(zeros(40, 20));
return;
end
% 创建拼接图像
rows = ceil(numChars / 8);
cols = min(numChars, 8);
montageImage = zeros(rows*40, cols*20);
for i = 1:numChars
row = ceil(i / cols);
col = mod(i-1, cols) + 1;
yStart = (row-1)*40 + 1;
yEnd = row*40;
xStart = (col-1)*20 + 1;
xEnd = col*20;
charImg = charImages{i};
if size(charImg, 1) ~= 40 || size(charImg, 2) ~= 20
charImg = imresize(charImg, [40, 20]);
end
montageImage(yStart:yEnd, xStart:xEnd) = charImg;
end
imshow(montageImage);
end
%% 字符识别函数
function recognizedText = recognizeCharacters(charImages)
% 加载字符模板库
templates = loadCharacterTemplates();
recognizedText = '';
for i = 1:length(charImages)
charImg = charImages{i};
% 归一化字符图像
charImg = normalizeCharacter(charImg);
% 与模板匹配
bestMatch = '';
bestScore = Inf;
for charLabel = keys(templates)
template = templates(charLabel{1});
% 计算相似度(归一化互相关系数)
correlation = normxcorr2(template, charImg);
score = 1 - max(correlation(:)); % 转换为距离
if score < bestScore
bestScore = score;
bestMatch = charLabel{1};
end
end
% 如果匹配分数过高,可能是噪声
if bestScore < 0.3
recognizedText = [recognizedText, bestMatch];
else
recognizedText = [recognizedText, '?'];
end
end
% 后处理:常见车牌格式修正
recognizedText = postProcessPlateText(recognizedText);
end
%% 字符图像归一化
function normalizedChar = normalizeCharacter(charImg)
% 二值化
if max(charImg(:)) > 1
charImg = imbinarize(charImg);
end
% 反色(确保字符为白色,背景为黑色)
if mean(charImg(:)) > 0.5
charImg = ~charImg;
end
% 调整大小
normalizedChar = imresize(charImg, [40, 20]);
% 去噪
normalizedChar = bwareaopen(normalizedChar, 5);
end
%% 加载字符模板
function templates = loadCharacterTemplates()
% 创建模板结构体
templates = containers.Map;
% 生成数字模板 (0-9)
for digit = 0:9
charLabel = num2str(digit);
template = createDigitTemplate(digit);
templates(charLabel) = template;
end
% 生成字母模板 (A-Z,排除I和O)
letters = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
for i = 1:length(letters)
charLabel = letters(i);
template = createLetterTemplate(charLabel);
templates(charLabel) = template;
end
% 生成中文字符模板(省份简称)
provinces = {'京', '津', '冀', '晋', '蒙', '辽', '吉', '黑', '沪', '苏', ...
'浙', '皖', '闽', '赣', '鲁', '豫', '鄂', '湘', '粤', '桂', ...
'琼', '川', '贵', '云', '渝', '藏', '陕', '甘', '青', '宁', '新'};
for i = 1:length(provinces)
charLabel = provinces{i};
template = createChineseCharTemplate(charLabel);
templates(charLabel) = template;
end
end
%% 创建数字模板
function template = createDigitTemplate(digit)
% 创建空白模板
template = false(40, 20);
% 根据数字绘制模板
switch digit
case 0
template(5:35, 5:15) = true;
template(5:10, 5:15) = false;
template(30:35, 5:15) = false;
case 1
template(10:30, 10:12) = true;
case 2
template(5:10, 5:15) = true;
template(5:20, 15:16) = true;
template(20:25, 5:15) = true;
template(25:30, 5:10) = true;
template(30:35, 5:15) = true;
case 3
template(5:10, 5:15) = true;
template(5:20, 15:16) = true;
template(20:25, 5:15) = true;
template(25:30, 15:16) = true;
template(30:35, 5:15) = true;
case 4
template(5:25, 5:6) = true;
template(5:10, 5:15) = true;
template(20:35, 10:11) = true;
template(25:30, 5:15) = true;
case 5
template(5:10, 5:15) = true;
template(5:10, 5:6) = false;
template(5:25, 5:6) = true;
template(25:30, 5:15) = true;
template(30:35, 5:15) = true;
case 6
template(5:10, 5:15) = true;
template(5:25, 5:6) = true;
template(25:30, 5:15) = true;
template(30:35, 5:15) = true;
template(25:30, 15:16) = true;
case 7
template(5:10, 5:15) = true;
template(5:25, 15:16) = true;
case 8
template(5:20, 5:15) = true;
template(20:35, 5:15) = true;
template(5:10, 10:11) = false;
template(25:30, 10:11) = false;
case 9
template(5:35, 5:15) = true;
template(25:35, 5:15) = false;
template(5:10, 15:16) = true;
end
end
%% 创建字母模板
function template = createLetterTemplate(letter)
% 创建空白模板
template = false(40, 20);
% 根据字母绘制模板
switch letter
case 'A'
template(5:30, 5:6) = true;
template(5:30, 15:16) = true;
template(15:20, 8:13) = true;
case 'B'
template(5:30, 5:6) = true;
template(5:15, 8:16) = true;
template(15:20, 8:16) = true;
template(20:30, 8:16) = true;
template(5:20, 15:16) = true;
template(25:30, 15:16) = true;
case 'C'
template(5:30, 5:6) = true;
template(5:10, 8:16) = true;
template(25:30, 8:16) = true;
template(5:30, 15:16) = true;
case 'D'
template(5:30, 5:6) = true;
template(5:30, 15:16) = true;
template(5:15, 8:16) = true;
template(15:30, 8:16) = true;
case 'E'
template(5:30, 5:6) = true;
template(5:30, 15:16) = true;
template(5:15, 8:16) = true;
template(25:30, 8:16) = true;
template(5:10, 8:16) = true;
case 'F'
template(5:30, 5:6) = true;
template(5:15, 8:16) = true;
template(5:10, 8:16) = true;
case 'G'
template(5:30, 5:6) = true;
template(5:10, 8:16) = true;
template(25:30, 8:16) = true;
template(5:30, 15:16) = true;
template(20:30, 12:16) = true;
case 'H'
template(5:30, 5:6) = true;
template(5:30, 15:16) = true;
template(15:20, 8:16) = true;
case 'J'
template(15:30, 5:6) = true;
template(25:30, 8:16) = true;
template(5:30, 15:16) = true;
case 'K'
template(5:30, 5:6) = true;
template(5:30, 15:16) = true;
template(15:30, 10:11) = true;
template(20:25, 8:9) = true;
case 'L'
template(5:30, 5:6) = true;
template(25:30, 8:16) = true;
case 'M'
template(5:30, 5:6) = true;
template(5:30, 15:16) = true;
template(5:20, 8:16) = true;
template(10:15, 10:12) = false;
case 'N'
template(5:30, 5:6) = true;
template(5:30, 15:16) = true;
template(5:30, 8:16) = true;
case 'P'
template(5:30, 5:6) = true;
template(5:20, 8:16) = true;
template(5:15, 15:16) = true;
case 'Q'
template(5:20, 5:6) = true;
template(20:30, 15:16) = true;
template(5:20, 8:16) = true;
template(20:30, 8:16) = true;
template(25:30, 12:16) = true;
case 'R'
template(5:30, 5:6) = true;
template(5:20, 8:16) = true;
template(5:15, 15:16) = true;
template(20:30, 10:11) = true;
case 'S'
template(5:10, 5:15) = true;
template(5:30, 5:6) = true;
template(25:30, 5:15) = true;
template(5:30, 15:16) = true;
template(20:25, 8:16) = true;
case 'T'
template(5:10, 5:16) = true;
template(15:20, 5:6) = true;
case 'U'
template(5:30, 5:6) = true;
template(5:30, 15:16) = true;
template(5:10, 8:16) = true;
template(20:30, 8:16) = true;
case 'V'
template(5:15, 5:16) = true;
template(15:25, 5:6) = true;
template(25:35, 5:16) = true;
case 'W'
template(5:30, 5:6) = true;
template(5:30, 15:16) = true;
template(5:10, 8:16) = true;
template(20:25, 8:16) = true;
template(10:20, 10:12) = true;
case 'X'
template(5:20, 5:16) = true;
template(20:35, 5:16) = true;
case 'Y'
template(5:15, 5:16) = true;
template(15:25, 5:6) = true;
template(25:35, 5:16) = true;
case 'Z'
template(5:30, 5:6) = true;
template(25:30, 8:16) = true;
template(5:30, 15:16) = true;
template(5:10, 8:16) = true;
end
end
%% 创建中文字符模板(简化版)
function template = createChineseCharTemplate(character)
% 创建空白模板
template = false(40, 20);
% 实际应用中应使用真实的中文字符模板
% 这里仅作示意,绘制一个简单的矩形
template(10:30, 5:15) = true;
% 在实际应用中,这里应该加载预存的中文字符模板
% 例如:template = imread(['templates/', character, '.png']);
% template = imbinarize(template);
end
%% 车牌文本后处理
function processedText = postProcessPlateText(text)
% 常见车牌格式:省份简称 + 字母 + 5位字母数字组合
if length(text) >= 7
% 尝试识别省份简称
provinces = {'京', '津', '冀', '晋', '蒙', '辽', '吉', '黑', '沪', '苏', ...
'浙', '皖', '闽', '赣', '鲁', '豫', '鄂', '湘', '粤', '桂', ...
'琼', '川', '贵', '云', '渝', '藏', '陕', '甘', '青', '宁', '新'};
% 检查第一个字符是否是省份简称
if ~any(strcmp(text(1), provinces))
% 尝试修正常见错误
if strcmp(text(1), '金')
text(1) = '鲁'; % 山东
elseif strcmp(text(1), '广')
text(1) = '粤'; % 广东
end
end
% 检查第二个字符是否是字母
if ~isletter(text(2))
% 尝试修正
if isnumeric(text(2))
text(2) = char(text(2) + 64); % 转换为字母
end
end
end
% 替换常见识别错误
replacements = {
'0', 'O';
'1', 'I';
'5', 'S';
'8', 'B';
'2', 'Z';
'4', 'A';
'6', 'G';
'7', 'T';
'9', 'Q'
};
for i = 1:size(replacements, 1)
text = strrep(text, replacements{i,1}, replacements{i,2});
end
processedText = text;
end
%% 创建示例车牌图像
function createSampleLicensePlate()
% 创建空白图像
plateImage = 255 * ones(80, 240, 3, 'uint8');
% 添加蓝色背景
plateImage(:,:,1) = 50; % R
plateImage(:,:,2) = 100; % G
plateImage(:,:,3) = 200; % B
% 添加字符(示例车牌:粤A12345)
chars = {'粤', 'A', '1', '2', '3', '4', '5'};
positions = [10, 40, 70, 100, 130, 160, 190];
for i = 1:length(chars)
% 创建字符图像
charImg = createCharImage(chars{i});
% 添加到车牌图像
h = size(charImg, 1);
w = size(charImg, 2);
plateImage(20:20+h-1, positions(i):positions(i)+w-1, 1) = charImg(:,:,1);
plateImage(20:20+h-1, positions(i):positions(i)+w-1, 2) = charImg(:,:,2);
plateImage(20:20+h-1, positions(i):positions(i)+w-1, 3) = charImg(:,:,3);
end
% 添加边框
plateImage(1:80, 1:2, :) = 0;
plateImage(1:80, 238:240, :) = 0;
plateImage(1:2, 1:240, :) = 0;
plateImage(78:80, 1:240, :) = 0;
% 保存图像
imwrite(plateImage, 'sample_license_plate.jpg');
disp('示例车牌图像已保存为 sample_license_plate.jpg');
end
%% 创建字符图像(简化版)
function charImg = createCharImage(character)
% 创建空白字符图像
charImg = 255 * ones(40, 20, 3, 'uint8');
% 在实际应用中,这里应该使用真实的字体渲染
% 这里仅作示意,绘制一个简单的矩形字符
if strcmp(character, '粤')
% 绘制简化的"粤"字
charImg(5:35, 5:15, 1) = 0;
charImg(5:35, 5:15, 2) = 0;
charImg(5:35, 5:15, 3) = 0;
charImg(10:30, 8:12, :) = 255;
elseif strcmp(character, 'A')
% 绘制字母A
charImg(5:35, 5:15, 1) = 0;
charImg(5:35, 5:15, 2) = 0;
charImg(5:35, 5:15, 3) = 0;
charImg(15:25, 8:12, :) = 255;
else
% 绘制数字或字母
charImg(10:30, 5:15, 1) = 0;
charImg(10:30, 5:15, 2) = 0;
charImg(10:30, 5:15, 3) = 0;
charImg(15:25, 8:12, :) = 255;
end
end
%% 演示函数
function demoLicensePlateRecognition()
% 创建示例车牌图像
createSampleLicensePlate();
% 运行车牌识别
licensePlateRecognition();
end
程序功能说明
1. 图像预处理模块
- 颜色空间转换:将RGB图像转换为HSV空间,便于蓝色车牌检测
- 边缘检测:使用Sobel算子检测图像边缘
- 形态学操作:通过开闭运算增强车牌区域
- 二值化处理:使用Otsu方法自动确定阈值
2. 车牌定位模块
- 颜色特征检测:基于HSV空间的蓝色特征检测
- 几何特征筛选:根据面积、宽高比筛选候选区域
- 连通区域分析:使用bwconncomp和regionprops分析连通区域
- 备选方案:当颜色检测失败时,使用边缘检测方法
3. 字符分割模块
- 垂直投影法:通过计算垂直方向的像素和分割字符
- 波谷检测:识别投影曲线的波谷作为分割点
- 连通区域分析:备选分割方法,基于连通区域属性
- 字符归一化:统一字符大小为40×20像素
4. 字符识别模块
- 模板匹配:使用归一化互相关系数进行字符匹配
- 模板库:包含数字0-9、字母A-Z(排除I,O)和中文省份简称
- 后处理:常见识别错误修正和车牌格式验证
5. 辅助功能
- 结果可视化:显示各处理阶段的图像结果
- 示例生成:创建示例车牌图像用于测试
- 参数配置:集中管理算法参数
关键技术实现
1. 车牌定位算法
function [plateRegion, plateImage] = locateLicensePlate(image, params)
% 转换为HSV颜色空间
hsvImage = rgb2hsv(image);
hue = hsvImage(:,:,1);
saturation = hsvImage(:,:,2);
value = hsvImage(:,:,3);
% 蓝色车牌检测(中国车牌)
blueMask = (hue > 0.55) & (hue < 0.65) & (saturation > 0.4) & (value > 0.3);
% 形态学操作增强车牌区域
se = strel('rectangle', [3, 3]);
blueMask = imopen(blueMask, se);
blueMask = imclose(blueMask, strel('rectangle', [25, 3]));
% 查找连通区域并筛选
cc = bwconncomp(blueMask);
stats = regionprops(cc, 'BoundingBox', 'Area');
% 根据面积和宽高比筛选车牌候选区域
for i = 1:length(stats)
bbox = stats(i).BoundingBox;
aspectRatio = bbox(3)/bbox(4);
area = bbox(3)*bbox(4);
if area > params.minPlateArea && ...
aspectRatio > params.aspectRatio(1) && ...
aspectRatio < params.aspectRatio(2)
plateRegion = bbox;
plateImage = imcrop(image, plateRegion);
return;
end
end
end
2. 字符分割算法
function charImages = segmentCharacters(binaryPlate, params)
% 垂直投影
verticalProjection = sum(binaryPlate, 1);
smoothedProjection = movmean(verticalProjection, 5);
% 寻找波谷作为字符分割点
minVal = min(smoothedProjection);
threshold = minVal + 0.2 * (max(smoothedProjection) - minVal);
valleys = find(smoothedProjection < threshold);
% 分组连续波谷并计算字符中心
groups = [];
currentGroup = [];
for i = 1:length(valleys)
if isempty(currentGroup) || valleys(i) == currentGroup(end) + 1
currentGroup = [currentGroup, valleys(i)];
else
groups{end+1} = currentGroup;
currentGroup = valleys(i);
end
end
if ~isempty(currentGroup)
groups{end+1} = currentGroup;
end
% 分割字符并归一化
for i = 1:length(groups)
startX = min(groups{i});
endX = max(groups{i});
charRegion = binaryPlate(:, startX:endX);
% 水平投影确定上下边界
horizontalProjection = sum(charRegion, 2);
top = find(horizontalProjection > 0, 1, 'first');
bottom = find(horizontalProjection > 0, 1, 'last');
% 提取并归一化字符
charImage = charRegion(top:bottom, :);
charImage = imresize(charImage, [40, 20]);
charImages{end+1} = charImage;
end
end
3. 字符识别算法
function recognizedText = recognizeCharacters(charImages)
templates = loadCharacterTemplates();
recognizedText = '';
for i = 1:length(charImages)
charImg = normalizeCharacter(charImages{i});
bestMatch = '';
bestScore = Inf;
for charLabel = keys(templates)
template = templates(charLabel{1});
correlation = normxcorr2(template, charImg);
score = 1 - max(correlation(:)); % 转换为距离
if score < bestScore
bestScore = score;
bestMatch = charLabel{1};
end
end
if bestScore < 0.3
recognizedText = [recognizedText, bestMatch];
else
recognizedText = [recognizedText, '?'];
end
end
recognizedText = postProcessPlateText(recognizedText);
end
算法原理与数学基础
1. 阈值分割原理
阈值分割是将图像像素分为前景和背景两类的过程。Otsu方法通过最大化类间方差自动确定最佳阈值:
其中
2. 形态学操作
- 开运算:先腐蚀后膨胀,消除小物体
- 闭运算:先膨胀后腐蚀,填充小孔洞
- 结构元素:矩形、圆盘等形状,控制操作效果
3. 模板匹配
使用归一化互相关系数衡量相似度:
其中
参考代码 基于阈值分割的车牌图像识别程序源码 www.youwenfan.com/contentcns/96227.html
使用说明
1. 基本使用
% 运行主程序
licensePlateRecognition();
% 使用自定义图像
plateImage = imread('my_car_plate.jpg');
% 调用处理函数...
2. 参数调整
% 调整车牌定位参数
params.minPlateArea = 2000; % 适应较小车牌
params.aspectRatio = [2.0, 6.0]; % 适应不同宽高比
% 调整字符分割参数
params.minCharArea = 50; % 适应小字符
params.charAspectRatio = [0.1, 0.8]; % 适应不同字符形状
% 调整二值化参数
params.binaryThreshold = 0.7; % 调整二值化敏感度
3. 处理不同光照条件
% 在低光照条件下增强对比度
function enhanced = enhanceLowLight(image)
lab = rgb2lab(image);
L = lab(:,:,1)/100;
L_enhanced = imadjust(L);
lab(:,:,1) = L_enhanced * 100;
enhanced = lab2rgb(lab);
end
% 在强光照条件下使用自适应直方图均衡
function enhanced = reduceGlare(image)
hsv = rgb2hsv(image);
v = hsv(:,:,3);
v_eq = adapthisteq(v);
hsv(:,:,3) = v_eq;
enhanced = hsv2rgb(hsv);
end
扩展功能
1. 倾斜校正
function correctedPlate = correctSkew(plateImage)
% 使用Hough变换检测倾斜角度
grayPlate = rgb2gray(plateImage);
edges = edge(grayPlate, 'canny');
[H, theta, rho] = hough(edges);
peaks = houghpeaks(H, 1);
angle = theta(peaks(2));
% 旋转图像校正倾斜
correctedPlate = imrotate(plateImage, angle, 'bilinear', 'crop');
end
2. 多车牌检测
function allPlates = detectMultiplePlates(image, params)
% 检测所有符合条件的车牌区域
allPlates = {};
cc = bwconncomp(blueMask);
stats = regionprops(cc, 'BoundingBox', 'Area');
for i = 1:length(stats)
bbox = stats(i).BoundingBox;
aspectRatio = bbox(3)/bbox(4);
area = bbox(3)*bbox(4);
if area > params.minPlateArea && ...
aspectRatio > params.aspectRatio(1) && ...
aspectRatio < params.aspectRatio(2)
plateImg = imcrop(image, bbox);
allPlates{end+1} = plateImg;
end
end
end
3. 深度学习增强
function enhancedChar = deepLearningEnhance(charImg)
% 使用预训练的CNN增强字符图像
net = alexnet; % 加载预训练网络
inputSize = net.Layers(1).InputSize;
% 调整字符图像大小
resizedChar = imresize(charImg, [inputSize(1), inputSize(2)]);
% 使用网络进行特征提取
features = activations(net, resizedChar, 'fc7');
% 重建增强图像(简化示例)
enhancedChar = imresize(resizedChar, size(charImg));
end
4. 视频车牌识别
function videoLicensePlateRecognition(videoFile)
videoReader = VideoReader(videoFile);
detector = vision.CascadeObjectDetector('LBPCascade_frontview.xml');
while hasFrame(videoReader)
frame = readFrame(videoReader);
% 检测车辆
bbox = detector(frame);
% 对每个检测到的车辆进行车牌识别
for i = 1:size(bbox, 1)
vehicleImg = imcrop(frame, bbox(i, :));
plateImg = locateLicensePlate(vehicleImg);
if ~isempty(plateImg)
recognizedText = recognizeCharacters(segmentCharacters(preprocessPlate(plateImg)));
frame = insertText(frame, bbox(i,1:2), recognizedText, 'FontSize', 16);
end
end
imshow(frame);
drawnow;
end
end
问题解决方法
-
车牌定位失败
- 尝试调整颜色阈值:
params.blueThreshold - 使用备选的边缘检测方法
- 添加图像预处理步骤(如光照均衡)
- 尝试调整颜色阈值:
-
字符分割错误
- 调整投影分割阈值:
threshold = minVal + 0.2*(maxVal-minVal) - 使用连通区域分析的备选分割方法
- 添加字符粘连处理算法
- 调整投影分割阈值:
-
字符识别错误
- 扩充模板库,添加更多字体变体
- 使用动态时间规整(DTW)算法改进匹配
- 引入机器学习分类器(SVM、CNN)
-
处理低质量图像
- 添加图像增强步骤(去噪、对比度增强)
- 使用超分辨率技术提升图像质量
- 采用多帧融合技术提高识别率
应用建议
-
系统集成
- 封装为独立函数库或类
- 开发GUI界面方便用户操作
- 提供API接口供其他系统调用
-
性能优化
- 使用并行计算加速处理
- 优化算法减少计算复杂度
- 利用GPU加速图像处理
-
部署方案
- 桌面应用程序(Windows/Linux/macOS)
- Web服务(RESTful API)
- 嵌入式系统(ARM平台)
-
持续改进
- 收集误识别案例建立测试集
- 定期更新字符模板库
- 引入机器学习方法提升识别率