OTSU(大津律法)数学原理及源码解析

本篇博客在书写过程中参考了一下两篇博客,在此贴出来,以示对原创者的尊重:OTSU算法(大津法—最大类间方差法)原理及实现详细及易读懂的 大津法(OTSU)原理 和 算法实现。再次向二位大佬致敬!

1.OTSU(最大类间差法):

OTST算法是用来对灰度图像(单通道)图像进行二值化阈值分割的基础图像分割算法,利用此方法进行图像二值化分割后,前景与背景图像的类间方差最大
常用场合:求解图像的全局最佳阈值。(原因如下:OTSU按图像的灰度特性,将图像分成背景和前景两部分。因方差是灰度分布均匀性的一种度量,背景和前景之间的类间方差越大,说明构成图像的两部分的差别越大,当部分前景错分为背景或部分背景错分为前景都会导致两部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。)
缺点**:**对图像噪声敏感;只能针对单一目标分割;当目标和背景大小比例悬殊、类间方差函数可能呈现双峰或者多峰,这个时候效果不好。

2.OTSU具体数学原理

(1)背景像素占比:: 在这里插入图片描述

其中(N1代表前景像素的个数,S代表图像总的像素数)
(2)前景像素占比: 在这里插入图片描述

(3)背景像素的平均灰度值: 在这里插入图片描述
(其中,Pi表示的是背景中,灰度值为i的像素点的个数,μt1表示的是背景像素相对于整个图像的灰度的数学期望值。)
(4)前景像素的平均灰度值: 在这里插入图片描述
(其中,μt2表示的是前景像素相对于整个图像的灰度到的数学期望值。)
(5) 0-M灰度范围内的图像的灰度均值:在这里插入图片描述

(6)类间方差: 在这里插入图片描述

(7)将(5)代入(6)得:在这里插入图片描述

3.OTSU源码及应用示例。

# -*- coding: utf-8 -*-
"""
Created on Thu Apr 23 17:48:57 2020

@author: Administrator
"""
'''
OTSU算法详解:
(1)背景像素占比:  (N1代表前景像素的个数,S代表图像总的像素数)
(2)前景像素占比:  
(3)背景像素的平均灰度值:  (其中,Pi表示的是背景中,灰度值为i的像素点的个数,μt1表示的是背景像素相对于整个图像的灰度的数学期望值。)
(4)前景像素的平均灰度值: (其中,μt2表示的是前景像素相对于整个图像的灰度到的数学期望值。) 
(5) 0-M灰度范围内的图像的灰度均值:: 
(6)类间方差:  
(7)将(5)代入(6)得:  

'''
import numpy as np
import cv2


class OTSU_Segmentation
function recognize_math_expression_Line() % 创建主窗口 fig = figure('Name', '鲁棒公式识别系统', 'NumberTitle', 'off', ... 'Position', [100, 100, 1200, 800], 'MenuBar', 'none', ... 'Color', [0.95 0.95 0.95]); % 创建UI控件 uicontrol('Style', 'pushbutton', 'String', '上传图像', ... 'Position', [30, 750, 100, 30], 'Callback', @uploadImage, ... 'FontSize', 11, 'BackgroundColor', [0.3 0.6 0.9], 'ForegroundColor', 'white'); uicontrol('Style', 'pushbutton', 'String', '识别公式', ... 'Position', [150, 750, 100, 30], 'Callback', @recognizeFormula, ... 'FontSize', 11, 'BackgroundColor', [0.1 0.7 0.3], 'ForegroundColor', 'white'); uicontrol('Style', 'pushbutton', 'String', '清除结果', ... 'Position', [270, 750, 100, 30], 'Callback', @clearResults, ... 'FontSize', 11, 'BackgroundColor', [0.9 0.5 0.1], 'ForegroundColor', 'white'); % 新增语音识别按钮 uicontrol('Style', 'pushbutton', 'String', '语音识别(SVM)', ... 'Position', [390, 750, 120, 30], 'Callback', {@recognizeSpeech, 'svm'}, ... 'FontSize', 11, 'BackgroundColor', [0.5 0.2 0.8], 'ForegroundColor', 'white'); uicontrol('Style', 'pushbutton', 'String', '语音识别(CNN)', ... 'Position', [520, 750, 120, 30], 'Callback', {@recognizeSpeech, 'cnn'}, ... 'FontSize', 11, 'BackgroundColor', [0.2 0.5 0.8], 'ForegroundColor', 'white'); % 进度条 progress_ax = axes('Parent', fig, 'Position', [0.4, 0.78, 0.2, 0.02], ... 'XLim', [0 100], 'YLim', [0 1], 'Box', 'on', 'XTick', [], 'YTick', []); progress_bar = rectangle('Parent', progress_ax, 'Position', [0,0,0,1], ... 'FaceColor', [0.1 0.8 0.1], 'EdgeColor', 'none'); progress_text = uicontrol('Style', 'text', 'String', '就绪', ... 'Position', [580, 770, 200, 20], 'FontSize', 10, ... 'HorizontalAlignment', 'center', 'BackgroundColor', [0.95 0.95 0.95]); % 创建图像显示区域 ax_original = axes('Parent', fig, 'Position', [0.05, 0.5, 0.4, 0.35]); title(ax_original, '原始图像'); axis(ax_original, 'off'); ax_processed = axes('Parent', fig, 'Position', [0.55, 0.5, 0.4, 0.35]); title(ax_processed, '处理图像'); axis(ax_processed, 'off'); ax_equal = axes('Parent', fig, 'Position', [0.05, 0.1, 0.4, 0.35]); title(ax_equal, '等号检测'); axis(ax_equal, 'off'); % 创建结果展示区域 result_panel = uipanel('Title', '识别结果', 'FontSize', 12, ... 'BackgroundColor', 'white', 'Position', [0.55, 0.1, 0.4, 0.35]); uicontrol('Parent', result_panel, 'Style', 'text', 'String', '识别公式:', ... 'Position', [20, 200, 80, 20], 'FontSize', 11, 'HorizontalAlignment', 'left'); formula_text = uicontrol('Parent', result_panel, 'Style', 'text', 'String', '', ... 'Position', [110, 200, 300, 20], 'FontSize', 11, 'HorizontalAlignment', 'left', ... 'BackgroundColor', 'white'); uicontrol('Parent', result_panel, 'Style', 'text', 'String', '计算结果:', ... 'Position', [20, 170, 80, 20], 'FontSize', 11, 'HorizontalAlignment', 'left'); calc_text = uicontrol('Parent', result_panel, 'Style', 'text', 'String', '', ... 'Position', [110, 170, 300, 20], 'FontSize', 11, 'HorizontalAlignment', 'left', ... 'BackgroundColor', 'white'); uicontrol('Parent', result_panel, 'Style', 'text', 'String', '用户答案:', ... 'Position', [20, 140, 80, 20], 'FontSize', 11, 'HorizontalAlignment', 'left'); answer_text = uicontrol('Parent', result_panel, 'Style', 'text', 'String', '', ... 'Position', [110, 140, 300, 20], 'FontSize', 11, 'HorizontalAlignment', 'left', ... 'BackgroundColor', 'white'); uicontrol('Parent', result_panel, 'Style', 'text', 'String', '验证结果:', ... 'Position', [20, 110, 80, 20], 'FontSize', 11, 'HorizontalAlignment', 'left'); result_text = uicontrol('Parent', result_panel, 'Style', 'text', 'String', '', ... 'Position', [110, 110, 300, 20], 'FontSize', 11, 'HorizontalAlignment', 'left', ... 'BackgroundColor', 'white'); % 新增语音识别结果显示 uicontrol('Parent', result_panel, 'Style', 'text', 'String', '语音识别:', ... 'Position', [20, 80, 80, 20], 'FontSize', 11, 'HorizontalAlignment', 'left'); speech_text = uicontrol('Parent', result_panel, 'Style', 'text', 'String', '', ... 'Position', [110, 80, 300, 20], 'FontSize', 11, 'HorizontalAlignment', 'left', ... 'BackgroundColor', 'white'); % 等号检测日志 equal_log = uicontrol('Parent', result_panel, 'Style', 'listbox', ... 'String', {}, 'Position', [20, 30, 360, 70], 'FontSize', 10, ... 'BackgroundColor', 'white'); % 存储GUI句柄 handles = struct(); handles.ax_original = ax_original; handles.ax_processed = ax_processed; handles.ax_equal = ax_equal; handles.formula_text = formula_text; handles.calc_text = calc_text; handles.answer_text = answer_text; handles.result_text = result_text; handles.speech_text = speech_text; % 新增语音文本句柄 handles.equal_log = equal_log; handles.progress_bar = progress_bar; handles.progress_text = progress_text; handles.progress_ax = progress_ax; handles.current_image = []; handles.binary_image = []; handles.region_stats = []; handles.region_bboxes = []; handles.equal_sign_index = []; handles.ocr_results = []; handles.fig = fig; % 存储figure句柄 guidata(fig, handles); %% ======================== 回调函数 ======================== function uploadImage(~, ~) % 获取全局 handles 结构体 handles = guidata(gcbf); % 使用 gcbf 获取当前回调的 figure 句柄 updateProgress(0, '准备上传图像...'); [filename, pathname] = uigetfile({'*.jpg;*.png;*.bmp', '图像文件 (*.jpg, *.png, *.bmp)'}, ... '选择公式图像'); if isequal(filename, 0) updateProgress(0, '取消上传'); return; end updateProgress(10, '读取图像...'); img_path = fullfile(pathname, filename); img = imread(img_path); % 更新 handles handles.current_image = img; % 显示原始图像 axes(handles.ax_original); imshow(img); title('原始图像'); % 预处理图像 updateProgress(20, '图像预处理...'); if size(img, 3) == 3 % 判断是否为彩色图像 gray_img = rgb2gray(img); % 将彩色图像转换为灰度图像 else gray_img = img; % 如果已经是灰度图像,则直接使用 end % 显示灰度图像 axes(handles.ax_processed); imshow(gray_img); title('灰度图像'); % 图像锐化 - 增强边缘 sharpened_img = imsharpen(gray_img, 'Radius', 1.5, 'Amount', 1.2, 'Threshold', 0.1); % 使用大律法计算全局阈值 global_level = graythresh(sharpened_img); % 计算图像的标准差(衡量对比度) image_contrast = std(double(sharpened_img(:))) / 255; % 动态调整敏感度 if image_contrast > 0.1 % 对比度较好 sensitivity = global_level * 0.9; % 较低的敏感度 if sensitivity<0.7 sensitivity=0.7; end else % 对比度较差 sensitivity = global_level * 1.05; % 较高的敏感度 end % 自适应二值化结合敏感度 binary_img = imbinarize(sharpened_img, 'adaptive', 'Sensitivity', sensitivity); inverted_img = ~binary_img; % 显示结果 axes(handles.ax_processed); imshow(inverted_img); title('结合大律法与自适应二值化的图像'); % 形态学去噪 - 定义结构元素 se = strel('disk', 2); % 创建一个半径为 2 的圆形结构元素 % 开运算:先腐蚀后膨胀,去除小的噪声点 cleaned_img = imopen(inverted_img, se); % 更新 handles handles.binary_image = cleaned_img; % 显示处理图像 axes(handles.ax_processed); imshow(cleaned_img); title('大律法二值化图像'); % 重置结果 set(handles.formula_text, 'String', ''); set(handles.calc_text, 'String', ''); set(handles.answer_text, 'String', ''); set(handles.result_text, 'String', ''); set(handles.speech_text, 'String', ''); % 重置语音文本 set(handles.equal_log, 'String', {}); axes(handles.ax_equal); cla; title('等号检测'); % 保存更新后的 handles guidata(gcbf, handles); % 关键修复:更新全局 handles updateProgress(100, '图像上传完成'); end function recognizeFormula(~, ~) handles = guidata(gcbf); if isempty(handles.binary_image) errordlg('请先上传图像', '错误'); return; end try updateProgress(0, '开始公式识别...'); inverted_img = handles.binary_image; [img_h, img_w] = size(inverted_img); % ================= 第一步:区域分割 ================= axes(handles.ax_processed); imshow(inverted_img); title('区域分割'); drawnow; updateProgress(10, '区域分割...'); % 动态计算膨胀参数 se_radius = calculateDilationRadius(inverted_img, img_h, img_w); se = strel('disk', se_radius); dilated_img = imdilate(inverted_img, se); % 区域分割 cc = bwconncomp(dilated_img, 8); stats = regionprops(cc, 'BoundingBox', 'Area', 'Eccentricity', 'Orientation', 'Solidity'); [stats.IsEqual] = deal(0); [stats.Row] = deal(0); % 过滤噪点 min_area = max(5, img_h*img_w*0.0005); valid_idx = [stats.Area] > min_area; stats = stats(valid_idx); % 按垂直位置排序 bboxes = vertcat(stats.BoundingBox); [~, order] = sort(bboxes(:,2)); bboxes = bboxes(order, :); stats = stats(order); % 存储区域信息 handles.region_stats = stats; handles.region_bboxes = bboxes; % 显示分割结果 imshow(inverted_img); hold on; for i = 1:size(bboxes,1) rectangle('Position', bboxes(i,:), 'EdgeColor', [0.8 0.2 0.2], 'LineWidth', 1); end hold off; title(sprintf('分割出 %d 个区域', length(stats))); drawnow; updateProgress(20, '按行分组...'); row_threshold = mean([bboxes(:,4)]) * 1; % 行间距阈值为平均字符高度的1倍 % 更新 stats.Row 字段 current_row = 0; prev_center_y = -Inf; for i = 1:length(stats) bbox = stats(i).BoundingBox; center_y = bbox(2) + bbox(4)/2; if center_y - prev_center_y > row_threshold current_row = current_row + 1; end stats(i).Row = current_row; prev_center_y = center_y; end % 准备存储行图像和识别结果 row_images = {}; row_texts = {}; row_bboxes = []; % 创建行图像并执行OCR for row_num = 1:max([stats.Row]) % 提取当前行的字符索引 row_indices = [stats.Row] == row_num; row_stats = stats(row_indices); row_bboxes = vertcat(row_stats.BoundingBox); if isempty(row_bboxes) continue; end % 计算当前行的边界框 min_x = min(row_bboxes(:,1)); max_x = max(row_bboxes(:,1) + row_bboxes(:,3)); min_y = min(row_bboxes(:,2)); max_y = max(row_bboxes(:,2) + row_bboxes(:,4)); % 添加边距 margin = 5; % 像素边距 min_x = max(1, min_x - margin); max_x = min(img_w, max_x + margin); min_y = max(1, min_y - margin); max_y = min(img_h, max_y + margin); % 裁剪行图像 row_img = inverted_img(floor(min_y):ceil(max_y), floor(min_x):ceil(max_x)); % 转换为OCR需要的格式(白底黑字) row_img_ocr = ~row_img; % 执行行级OCR updateProgress(30 + row_num*10, sprintf('识别行 %d...', row_num)); ocrResults = ocr(row_img_ocr, 'TextLayout', 'Line', ... 'CharacterSet', '0123456789÷+-*/=xX×', ... 'Language', 'en'); % 获取并处理识别文本 detectedText = strtrim(ocrResults.Text); correctedText = regexprep(detectedText, '[xX]', '×'); % 存储结果 row_images{row_num} = row_img; row_texts{row_num} = correctedText; row_bboxes = [row_bboxes; min_x, min_y, max_x-min_x, max_y-min_y]; % 在图像上显示行边界 axes(handles.ax_processed); hold on; rectangle('Position', [min_x, min_y, max_x-min_x, max_y-min_y], ... 'EdgeColor', [0 0.8 0], 'LineWidth', 1.5); text(min_x, min_y-10, sprintf('行%d: %s', row_num, correctedText), ... 'Color', [0 0.6 0], 'FontSize', 10, 'BackgroundColor', [1 1 1 0.7]); hold off; end % 显示所有识别结果 full_text = ''; for i = 1:length(row_texts) full_text = sprintf('%s\n行 %d: %s', full_text, i, row_texts{i}); end set(handles.formula_text, 'String', full_text); % 重置其他结果显示 set(handles.calc_text, 'String', ''); set(handles.answer_text, 'String', ''); set(handles.result_text, 'String', ''); updateProgress(100, '公式识别完成'); catch ME updateProgress(0, sprintf('错误: %s', ME.message)); errordlg(sprintf('识别失败: %s', ME.message), '错误'); end end function clearResults(~, ~) handles = guidata(gcbf); % 清除结果文本 set(handles.formula_text, 'String', ''); set(handles.calc_text, 'String', ''); set(handles.answer_text, 'String', ''); set(handles.result_text, 'String', ''); set(handles.speech_text, 'String', ''); % 清除语音文本 set(handles.equal_log, 'String', {}); % 清除图像 axes(handles.ax_processed); cla; title('处理图像'); axes(handles.ax_equal); cla; title('等号检测'); % 清除进度条 set(handles.progress_bar, 'Position', [0,0,0,1]); set(handles.progress_text, 'String', '就绪'); % 如果有原始图像,重新显示 if ~isempty(handles.current_image) axes(handles.ax_original); imshow(handles.current_image); title('原始图像'); end % 保存handles状态 guidata(gcbf, handles); end % 新增语音识别回调函数 function recognizeSpeech(~, ~, modelType) handles = guidata(gcbf); updateProgress(0, '准备语音识别...'); try % 根据选择的模型类型加载相应模型 if strcmp(modelType, 'svm') updateProgress(20, '加载SVM模型...'); if ~exist('svm_model.mat', 'file') trainSvmModel(); % 如果模型不存在则训练 end load('svm_model.mat', 'model', 'featureMean', 'featureStd'); app = SVMSpeechRecognitionGUI(model); else updateProgress(20, '加载CNN模型...'); if ~exist('dwt_cnn_net.mat', 'file') trainCnnModel(); % 如果模型不存在则训练 end load('dwt_cnn_net.mat', 'net'); app = CNNSpeechRecognitionGUI(net); end % 等待语音识别完成 updateProgress(40, '等待语音输入...'); while isvalid(app.UIFigure) pause(0.5); end % 获取识别结果 result = app.ResultLabel.Text; % 提取识别到的数学表达式 expression = extractMathExpression(result); if isempty(expression) error('无法解析数学表达式'); end % 显示语音识别结果 set(handles.speech_text, 'String', expression); % 计算表达式 updateProgress(80, '计算表达式...'); try % 安全计算表达式 result_value = evalMathExpression(expression); set(handles.calc_text, 'String', num2str(result_value)); set(handles.formula_text, 'String', expression); % 同时显示在公式区域 updateProgress(100, '语音识别完成'); catch set(handles.calc_text, 'String', '计算错误'); updateProgress(100, '计算失败'); end catch ME updateProgress(0, sprintf('错误: %s', ME.message)); errordlg(sprintf('语音识别失败: %s', ME.message), '错误'); end end % 更新进度条函数 function updateProgress(percent, message) handles = guidata(gcbf); % 获取当前 figure 的 handles % 确保进度条存在 if isfield(handles, 'progress_bar') && ishandle(handles.progress_bar) set(handles.progress_bar, 'Position', [0,0,percent,1]); end if isfield(handles, 'progress_text') && ishandle(handles.progress_text) set(handles.progress_text, 'String', message); end drawnow; % 立即更新显示 % 保存 handles 状态 guidata(gcbf, handles); end end % ======================== 新增辅助函数 ======================== function expression = extractMathExpression(text) % 移除识别结果中的无关文本 text = regexprep(text, '识别结果:', ''); text = regexprep(text, '结果:', ''); text = strtrim(text); % 替换语音识别中的特殊字符 text = strrep(text, '加', '+'); text = strrep(text, '减', '-'); text = strrep(text, '乘', '*'); text = strrep(text, '除', '/'); text = strrep(text, '等于', '='); % 提取数学表达式 match = regexp(text, '[\d\.\+\-\*/=]+', 'match'); if ~isempty(match) expression = match{1}; else expression = ''; end end function result = evalMathExpression(expr) % 安全计算数学表达式 expr = strrep(expr, '×', '*'); expr = strrep(expr, '÷', '/'); % 分割表达式和等号后的部分 parts = strsplit(expr, '='); if numel(parts) < 2 % 如果没有等号,直接计算整个表达式 result = eval(expr); else % 只计算等号前的部分 calc_part = parts{1}; result = eval(calc_part); end end % ======================== 语音识别模型函数 ======================== % SVM模型训练函数(简化版) function trainSvmModel() % 生成或加载数据集 [features, labels] = generateSvmDataset(); % 特征标准化 featureMean = mean(features); featureStd = std(features); normalizedFeatures = (features - featureMean) ./ featureStd; % 训练SVM模型 template = templateSVM('KernelFunction', 'rbf', 'BoxConstraint', 1); model = fitcecoc(normalizedFeatures, labels, 'Learners', template); % 保存模型 save('svm_model.mat', 'model', 'featureMean', 'featureStd'); disp('SVM模型已保存'); end % CNN模型训练函数(简化版) function trainCnnModel() % 生成或加载数据集 [images, labels] = generateCnnDataset(); % 创建CNN网络 layers = [ imageInputLayer([size(images,1) size(images,2) 1]) convolution2dLayer(5, 20) reluLayer() maxPooling2dLayer(2, 'Stride', 2) fullyConnectedLayer(15) softmaxLayer() classificationLayer() ]; % 训练选项 options = trainingOptions('sgdm', ... 'MaxEpochs', 10, ... 'Shuffle', 'every-epoch', ... 'Verbose', false); % 训练网络 net = trainNetwork(images, categorical(labels), layers, options); % 保存模型 save('dwt_cnn_net.mat', 'net'); disp('CNN模型已保存'); end % ======================== 原有辅助函数(保持不变)==================== % [此处包含原有的辅助函数,如: % calculateDilationRadius() % detectOperator() % correctDigit() % improvedOCR() % thinningPreprocessing() % edgeEnhancement() % detectCharacterByGeometry() % 等] % 由于篇幅限制,这些函数保持原样,不重复列出 % ======================== 运算符检测函数 ======================== function [is_operator, operator_type] = detectOperator(region_img) [h, w] = size(region_img); % 计算形状特征 aspect_ratio = w / h; eccentricity = regionprops(region_img, 'Eccentricity'); if ~isempty(eccentricity) eccentricity = eccentricity.Eccentricity; else eccentricity = 0; end is_operator = false; operator_type = ''; % 减号检测 if aspect_ratio > 3 && eccentricity > 0.9 is_operator = true; operator_type = '-'; return; end % 加号检测 if aspect_ratio > 1.2 && aspect_ratio < 2.5 % 中心区域分析 center_y = round(h/2); center_x = round(w/2); % 检查水平和垂直线段 horizontal_line = sum(region_img(center_y, :)) > w*0.7; vertical_line = sum(region_img(:, center_x)) > h*0.7; if horizontal_line && vertical_line is_operator = true; operator_type = '+'; return; end end % 乘号检测(斜线) if aspect_ratio > 0.8 && aspect_ratio < 1.2 % 使用Hough变换检测斜线 [H, theta, rho] = hough(region_img, 'Theta', -45:5:45); peaks = houghpeaks(H, 2); if size(peaks,1) >= 2 angles = theta(peaks(:,2)); angle_diff = abs(diff(angles)); % 检查是否接近垂直的斜线对 if abs(angle_diff) > 80 && abs(angle_diff) < 100 is_operator = true; operator_type = '*'; return; end end end % 除号检测(点) if aspect_ratio > 0.8 && aspect_ratio < 1.2 % 检测中心点 center_y = round(h/2); center_x = round(w/2); if center_x > 0 && center_y > 0 && center_x <= w && center_y <= h if region_img(center_y, center_x) is_operator = true; operator_type = '/'; return; end end end % 等号检测(特殊情况) if aspect_ratio > 2.5 && eccentricity > 0.95 is_operator = true; operator_type = '='; end end % ======================== 数字校正函数 ======================== function char = correctDigit(region_img, original_char) [h, w] = size(region_img); % 1的特征:高宽比大,顶部无横线,底部较宽 aspect_ratio = h / w; top_region = region_img(1:round(h*0.3), :); bottom_region = region_img(round(h*0.7):end, :); top_pixels = sum(top_region(:)); bottom_pixels = sum(bottom_region(:)); % 7的特征:顶部有横线,右上角有折角 top_line = sum(region_img(1, :)) > w*0.6; right_top_corner = false; if w > 1 && h > 1 right_top_corner = region_img(1, end) && region_img(2, end); end % 0的特征:封闭轮廓 filled_area = bwarea(region_img); convex_area = regionprops(region_img, 'ConvexArea'); if ~isempty(convex_area) convex_area = convex_area.ConvexArea; solidity = filled_area / convex_area; else solidity = 0; end % 8的特征:两个封闭区域 holes = regionprops(region_img, 'EulerNumber'); if ~isempty(holes) num_holes = 1 - holes.EulerNumber; % 欧拉数 = 1 - 孔洞数 else num_holes = 0; end switch original_char case '7' % 检查是否为1的特征 if aspect_ratio > 3 && top_pixels < numel(top_region)*0.2 && bottom_pixels > numel(bottom_region)*0.5 char = '1'; return; end case '1' % 检查是否为7的特征 if aspect_ratio < 2 && top_line && right_top_corner char = '7'; return; end case '0' % 检查是否为8的特征(有孔洞) if num_holes > 0 char = '8'; return; end case '8' % 检查是否为0的特征(无孔洞) if num_holes == 0 && solidity > 0.9 char = '0'; return; end case '5' % 检查是否为6的特征(底部封闭) bottom_closed = sum(region_img(end, :)) > w*0.6; if bottom_closed char = '6'; return; end case '6' % 检查是否为5的特征(底部开放) bottom_open = sum(region_img(end, :)) < w*0.4; if bottom_open char = '5'; return; end end char = original_char; % 保持原识别结果 end function [char, confidence] = improvedOCR(region_img) % 确保输入图像为二值图像 if ~islogical(region_img) error('输入图像必须是二值图像'); end % 获取图像尺寸 [height, width] = size(region_img); % 定义目标尺寸 target_size = 200; % 计算填充量 pad_top = max(0, floor((target_size - height) / 2)); pad_bottom = max(0, ceil((target_size - height) / 2)); pad_left = max(0, floor((target_size - width) / 2)); pad_right = max(0, ceil((target_size - width) / 2)); % 对图像进行填充 region_img_padded = padarray(region_img, [pad_top, pad_left], 'post'); region_img_padded = padarray(region_img_padded, [pad_bottom, pad_right], 'pre'); % 确保填充后的图像大小符合要求 if size(region_img_padded, 1) < target_size || size(region_img_padded, 2) < target_size error('填充后的图像大小不符合要求'); end %region_img_processed = thinningPreprocessing(region_img_padded); region_img_processed = edgeEnhancement(region_img_padded); % 定义扩展字符集; char_set = '0123456789+-*/=Xx'; % 设置置信度阈值 confidence_threshold = 0.3; % 使用 OCR 函数 try ocr_result = ocr(region_img_processed, 'CharacterSet', char_set, 'TextLayout', 'Character' ); % 检查 OCR 返回值是否为有效的 ocrText 对象 if ~isa(ocr_result, 'ocrText') || isempty(ocr_result.Text) % 如果 OCR 未能识别出任何字符,则尝试基于几何特征进行校正 char = detectCharacterByGeometry(region_img); confidence = 0; else % 提取 Text 的第一个字符 first_char = ocr_result.Text(1); % 直接提取第一个字符 % 检查第一个字符是否在定义的字符集中 if contains(char_set, first_char) char = first_char; % 如果有效,则记录该字符 else char = '?'; % 如果无效,则返回问号 end % 优先使用 WordConfidence word_confidence = ocr_result.WordConfidences; if word_confidence >= confidence_threshold confidence = word_confidence; else char = '?'; % 如果无法获取任何置信度信息,返回问号 confidence = 0; end end catch ME % 记录错误日志 fprintf('OCR 错误: %s\n', ME.message); fprintf('OCR 返回类型: %s\n', class(ocr_result)); char = '?'; % 如果 OCR 失败,返回问号 confidence = 0; end % 特殊字符校正 char = strrep(char, 'x', '*'); char = strrep(char, 'X', '*'); char = strrep(char, '÷', '/'); char = strrep(char, ' ', ''); % 针对 '6' 的检测与校正逻辑 if strcmp(char, '?') % 如果 OCR 未能识别出字符,则尝试基于几何特征进行校正 char = detectCharacterByGeometry(region_img); end end % ======================== 字符检测函数 ======================== function detected_char = detectCharacterByGeometry(region_img) % 计算形状特征 stats = regionprops(region_img, 'BoundingBox', 'Eccentricity', 'Solidity', 'Orientation', 'EulerNumber', 'Extent'); if isempty(stats) detected_char = '?'; return; end bbox = stats.BoundingBox; aspect_ratio = bbox(4) / bbox(3); % 高宽比 eccentricity = stats.Eccentricity; % 离心率 solidity = stats.Solidity; % 实心度 orientation = stats.Orientation; % 方向 euler_number = stats.EulerNumber; % 欧拉数 extent = stats.Extent; % 填充比例 % 判断是否为 '6' if aspect_ratio > 1.2 && aspect_ratio < 2.5 && ... eccentricity < 0.8 && solidity > 0.4 && ... abs(orientation) > 80 && euler_number ~= 1 detected_char = '6'; % 判断是否为 '3' elseif aspect_ratio > 0.8 && aspect_ratio < 1.8 && ... eccentricity > 0.7 &&eccentricity < 0.9... && solidity > 0.4 && ... euler_number == 1 && ... % '3' 的欧拉数通常为 1(有两个连通区域) abs(orientation) > 80&&... extent > 0.35 &&extent < 0.40 detected_char = '3'; % 判断是否为 '7' elseif aspect_ratio > 1.5 && ... % '7' 的高宽比较大 eccentricity > 0.9 && ... % '7' 的离心率较高 solidity > 0.45 && ... % '7' 的实心度较高 extent < 0.35 && ... % '7' 的填充比例较高 abs(orientation) > 75 &&abs(orientation) < 85 % '7' 的方向接近水平 detected_char = '7'; % 判断是否为 '5' elseif aspect_ratio > 1.0 && aspect_ratio < 2.0 && ... eccentricity < 0.85 && solidity > 0.45 && ... euler_number == 1 && ... % '5' 的欧拉数通常为 1(有两个连通区域) abs(orientation) > 85 &&...% '5' 的方向接近水平 extent > 0.40 detected_char = '5'; else detected_char = '?'; end end function [thinned_img] = thinningPreprocessing(img) % 确保输入为二值图像 if ~islogical(img) img = imbinarize(rgb2gray(im2double(img))); end % 字符瘦身(Thinning) thinned_img = bwmorph(img, 'thin', Inf); % 确保输出为二值图像 thinned_img = imbinarize(thinned_img); end function [sharpened_img] = edgeEnhancement(img) % 确保输入为二值图像 if ~islogical(img) img = imbinarize(rgb2gray(im2double(img))); end % 定义结构元素 se = strel('disk', 1); % 腐蚀操作 eroded_img = imerode(img, se); % 提取边缘 edge_img = img - eroded_img; % 边缘增强:将边缘信息加回到原始图像中 sharpened_img = img + edge_img; % 确保输出为二值图像 sharpened_img = imbinarize(sharpened_img); end function se_radius = calculateDilationRadius(inverted_img, img_h, img_w) % 计算连通域 cc = bwconncomp(inverted_img, 4); % 使用 4 连通性 stats = regionprops(cc, 'Area', 'BoundingBox'); % 提取所有连通域的面积 areas = [stats.Area]; bboxes = vertcat(stats.BoundingBox); % 去除小面积噪点 min_area_threshold = img_h * img_w * 0.0005; % 动态最小面积阈值 valid_idx = areas > min_area_threshold; valid_areas = areas(valid_idx); % 有效区域的面积 (1xN 数组) valid_bboxes = bboxes(valid_idx, :); % 有效区域的包围盒 (Nx4 数组) % 如果没有有效区域,则设置默认半径 if isempty(valid_areas) se_radius = 2; % 默认半径 return; end % 计算平均宽度和高度 widths = valid_bboxes(:,3); % 宽度 (Nx1 数组) heights = valid_bboxes(:,4); % 高度 (Nx1 数组) avg_width = mean(widths); % 平均宽度 avg_height = mean(heights); % 平均高度 % 计算有效区域的平均分辨率 avg_res = sqrt(avg_width * avg_height); % 平均分辨率 % 调整维度以确保逐元素运算正确 compactness = valid_areas ./ (widths' .* heights'); % 每个连通域的紧凑性 avg_compactness = 20 * mean(compactness(:)); % 紧凑性的平均值 % 边缘检测以获取字符边缘分布 edge_img = edge(inverted_img, 'Canny'); edge_density = 20 * sum(edge_img(:)) / numel(edge_img); % 边缘密度 % 综合计算字符粗细 char_thickness = sqrt(avg_width * avg_height * avg_compactness * edge_density); % 定义幂次参数 p p = 1.8; % 幂次参数,可以调整以控制非线性程度 % 使用对数函数结合幂次增强非线性度 adjusted_radius = (log(1 + avg_res / (char_thickness + 1e-6)))^p * 5; % 根据字符粗细动态调整膨胀半径 se_radius = round(max(1, adjusted_radius)); % 根据字符粗细动态调整膨胀半径 % se_radius = round(max(1, 20 * char_thickness / avg_res)); % 限制膨胀半径范围 if se_radius > 10 se_radius = 10; % 最大半径限制 elseif se_radius < 1 se_radius = 1; % 最小半径限制 end end 分析一下这个系统里面涉及了哪些信号与系统处理技术
最新发布
06-24
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值