基于阈值分割的车牌图像识别MATLAB实现

基于阈值分割的车牌图像识别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. 图像预处理模块

2. 车牌定位模块

3. 字符分割模块

4. 字符识别模块

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

问题解决方法

  1. 车牌定位失败

    • 尝试调整颜色阈值:params.blueThreshold
    • 使用备选的边缘检测方法
    • 添加图像预处理步骤(如光照均衡)
  2. 字符分割错误

    • 调整投影分割阈值:threshold = minVal + 0.2*(maxVal-minVal)
    • 使用连通区域分析的备选分割方法
    • 添加字符粘连处理算法
  3. 字符识别错误

    • 扩充模板库,添加更多字体变体
    • 使用动态时间规整(DTW)算法改进匹配
    • 引入机器学习分类器(SVM、CNN)
  4. 处理低质量图像

    • 添加图像增强步骤(去噪、对比度增强)
    • 使用超分辨率技术提升图像质量
    • 采用多帧融合技术提高识别率

应用建议

  1. 系统集成

    • 封装为独立函数库或类
    • 开发GUI界面方便用户操作
    • 提供API接口供其他系统调用
  2. 性能优化

    • 使用并行计算加速处理
    • 优化算法减少计算复杂度
    • 利用GPU加速图像处理
  3. 部署方案

    • 桌面应用程序(Windows/Linux/macOS)
    • Web服务(RESTful API)
    • 嵌入式系统(ARM平台)
  4. 持续改进

    • 收集误识别案例建立测试集
    • 定期更新字符模板库
    • 引入机器学习方法提升识别率

 

专注于matlab/simulink,电子电路,编程