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
分析一下这个系统里面涉及了哪些信号与系统处理技术
最新发布