Skip to content

期末综合实战:电子时钟数码管识别与 GUI 封装

这是本课程的期末大作业。我们将编写一个程序,自动识别照片中的电子时钟七段数码管(7-Segment Display)数值。

硬件开发中常通过单片机控制数码管显示数字。这个项目则是反向操作:输入一张数码管照片,用 MATLAB 提取并识别屏幕上的数值。

数码管原始图像

(测试素材:可右键保存上图作为本次实验的标准输入图像。)


1. 图像预处理与阈值分割

图像识别的第一步,我们需要将目标对象(红色的 LED 发光管)从黑色的背景中单独提取出来。提取后的结果将是一张只有黑白两色的“二值化掩膜(Mask)”图像,其中白色的区域代表数字,黑色的区域代表背景。

1.1 原理铺垫

在看代码前,我们需要先理解两个底层机制:

  1. 图像在内存中的表示 (imread): 使用 imread 读入一张彩色数字照片时,MATLAB 会将其解析为一个三维矩阵(长 ×× 3)。这第三个维度代表了红 (R)、绿 (G)、蓝 (B) 三个色彩通道。这也是为什么稍后我们可以使用 img(:,:,1) 单独把红色层提取出来。
  2. 逻辑矩阵生成: 当对一个矩阵进行大小比较运算时,MATLAB 不会报错,而是会返回一个同等大小的、只包含 01 的“逻辑型矩阵”。例如,对矩阵 A = [10 50 100] 执行 mask = A > 90,就会得到结果 [0 0 1]。我们可以利用这个特性,判断哪些像素的色彩值满足我们的要求。

1.2 算法实现

由于目标数码管是高亮红色的,它的红色通道 (R) 像素值必然显著高于绿色 (G) 和蓝色 (B) 通道。我们可以设定合适的阈值,利用逻辑运算符(>&)将红色区域筛选出来:

matlab
% 读取图像文件,img 是一个 MxNx3 的三维矩阵
img = imread('digital_clock.png');

% 提取 R、G、B 通道分量矩阵
R = img(:,:,1);
G = img(:,:,2);
B = img(:,:,3);

% 色彩差值分割:提取红色分量显著大于绿色和蓝色的区域
% 设定阈值:红通道绝对值大于 150,且与蓝绿通道的差值大于 50
bw = (R > 150) & (R - G > 50) & (R - B > 50);

figure;
imshow(bw);
title('预处理二值图像');

闯关题目 1

  1. 举一反三:如果我们面对的是一张蓝色的指示灯照片,想要把蓝色的发光区域提取出来,那么上面的阈值提取逻辑代码(即 bw = ... 这一行)应该做怎样的思路调整?
  2. 算子辨析:在上面的代码中,我们使用了 & 符号连接三个条件。在 MATLAB 中,表示“逻辑与”和“逻辑或”的符号分别是什么?
▶ 查看答案
  1. 举一反三:需要以蓝色通道为主进行比较。代码逻辑应调整为提取“蓝色通道绝对值较大,且显著大于红色和绿色通道差值”的区域。例如可能写为:bw = (B > 150) & (B - R > 50) & (B - G > 50);
  2. 算子辨析:“逻辑与”的符号是 &,“逻辑或”的符号是 |。在进行矩阵维度的条件筛选时,这两个算子非常常用。

2. 形态学运算与连通域分析

提取二值化图像后,因为七段数码管的相邻线段在物理上是断开的,如果直接查找连通域,一个数字会被拆成多个碎片。

2.1 核心知识点

  • 形态学闭运算 (imclose):形态学中的闭运算是指“先膨胀后腐蚀”的过程。它能够在不明显改变目标区域总面积的前提下,填补目标内部的细小空洞,并“缝合”断裂的微小间隙。
  • 结构元素 (strel):用于定义形态学运算的邻域范围和形状,如矩形、圆形等。
  • 连通域特征提取 (regionprops):MATLAB 用于测量图像区域属性的函数,可快速获取目标的面积、质心坐标以及用于裁切的边界框 (BoundingBox)。

2.2 算法实现

通过构建合适的结构元素并执行闭运算,我们将同一个数字内断开的段缝合为单一的连通域。

matlab
% 1. 构造结构元素:采用 15x15 的矩形窗口以覆盖线段间的缝隙
se = strel('rectangle', [15, 15]); 

% 2. 执行闭运算:缝合物理断裂
bw_closed = imclose(bw, se);       

% 3. 噪声过滤:移除面积小于 500 像素的孤立噪点
bw_clean = bwareaopen(bw_closed, 500); 

% 4. 特征提取:获取连通域的边界框 (BoundingBox) 和面积 (Area)
stats = regionprops(bw_clean, 'BoundingBox', 'Area');

% 5. 坐标排序(关键防坑点)
% regionprops 找出的连通域不一定是严格从左到右的。
% 为了保证最后读出来的时间顺序不乱套,我们得按 X 坐标给它们重新排个序:
num_regions = length(stats);
x_coords = zeros(1, num_regions);
for i = 1:num_regions
    x_coords(i) = stats(i).BoundingBox(1); % 提取每个框的 X 坐标
end
[~, sort_idx] = sort(x_coords); % 从小到大排序
stats = stats(sort_idx);

figure;
imshow(bw_clean); hold on;

% 遍历并在处理后的二值图像上绘制定位框
for i = 1:length(stats)
    bbox = stats(i).BoundingBox;
    % 依据先验知识过滤非目标区域(如中间的冒号,其高度通常较小)
    if bbox(4) > 50  
        rectangle('Position', bbox, 'EdgeColor', 'g', 'LineWidth', 2);
    end
end
title('形态学运算与目标定位结果');

闯关题目 2

  1. 结构元素的尺度:在上述代码中,如果将结构元素修改为 se = strel('rectangle', [3, 3]),最可能导致什么负面结果?
  2. 特征属性应用:除了使用高度 (bbox(4)) 过滤中间的冒号外,结合 regionprops 提供的信息,还可以使用什么特征参数来剔除面积远小于正常数字的噪点?
▶ 查看答案
  1. 结构元素的尺度3x3 的结构元素尺寸过小,可能无法完全覆盖并“跨越”数码管各段之间的物理缝隙。这将导致闭运算失败,同一个数字依然保持为多个独立的碎片连通域。
  2. 特征属性应用:可以直接使用 stats(i).Area 属性。由于冒号的像素面积远小于正常数字,通过设定 if stats(i).Area > 阈值 即可有效剔除。

3. 区域采样与译码算法

找到每一个数字所在的精确区域后,就可以提取图像数据并识别数字了。

3.1 核心知识点

  • 矩阵局部切片:结合 BoundingBox 的坐标与尺寸,使用矩阵索引提取局部图像块。
  • 网格采样与逻辑映射:模拟硬件的“七段译码器”。将数字区域划分为特定的网格。通过判定这 7 个对应区域(A 至 G 段)内的像素白点分布,生成一个形如 [1 1 1 1 1 1 0] 的数组,再对应到具体的数字。

3.2 算法实现思路

由于要求学生独立完成该核心算法模块,在此提供算法伪代码与设计指引:

matlab
% 提取单个数字图像块的伪代码
% bbox 的格式为 [x, y, width, height]
digit_img = bw_clean(round(y) : round(y+height), round(x) : round(x+width));

% ==============================================
% 核心识别流程:切图 -> 提特征 -> 译码
% ==============================================

% 【前置特判】:单独处理数字 1
% 数字 1 太窄了,按常规方法切片容易切乱。
% 可以加个特判:如果算出来宽高比 w/h < 0.35,直接判定它是数字 1,然后 continue 跳过后续步骤就行。

% 【第 1 步:切图】划定采样范围(避开干扰和斜体)
% 先用 size 拿到切片的高 H 和宽 W。
% 取横向线段(上、中、下)时,宽度别取满,只抠中间一小块(比如 W 的 40% 到 60%),不然容易把左右两边的竖线也框进来。
% 取纵向线段(左上、右上、左下、右下)时刚好相反,高度要避开横线,宽度可以往中间放宽点到 40% 左右,这样即使数码管带点斜体也不怕切歪。

% 【第 2 步:提特征】算出这 7 个区域是不是亮的
% 按照上一步的范围,把你切出来的 7 个小方块存好。
% 用 mean(region(:)) 算一下里面白色像素的比例。
% 阈值不用设太高,大于 0.15 基本就算亮起(逻辑 1)了,否则就是灭的(逻辑 0)。
% 最后把这 7 个结果拼成一个特征码数组,比如 code = [1, 1, 1, 1, 1, 1, 0]。

% 【第 3 步:译码】查表对暗号
% 拿到 code 后,用 isequal 函数去跟 0 到 9 的标准特征码比对。
% 比如 isequal(code, [1,1,1,1,1,1,0]) 就是数字 0。
% 写一排 if-elseif 搞定。

% 💡 进阶小挑战:
% 嫌全写在主循环里太长?你可以试着把“抠图取特征”和“查表认字”这两步,
% 分别封装成两个独立的辅助函数(比如 extract_7seg_features 和 seg7_decode),
% 你的主程序会变得非常清爽和专业!

4. 综合实践项目:GUI 智能识别应用

在前三节的代码跑通后,你需要把它们整合成一个带有交互界面的桌面程序。

作业要求与交付标准

  1. 前端界面设计:在 MATLAB 命令行启动 appdesigner,设计一个合理的桌面应用程序布局。需至少包含:
    • 图像导入触发按钮。
    • 核心算法执行触发按钮。
    • 两个以上的 UIAxes 用于比对显示原始图像与图像分割定位中间过程图。
    • 一个 Label 文本标签,用于以大号字体显示最终拼接出的时间字符串结果(例如 11:58 19)。
  2. 后端逻辑封装:将第 1 至 3 节实现的过程代码封装入自定义函数,并绑定至相应的组件回调函数 (Callback) 内。
  3. 交付物要求
    • *.mlapp 源程序文件。
    • 独立的作业报告(PDF 格式)。需包含:程序流程图、0-9 的特征码定义列表,以及形态学操作前后的效果对比。

最后更新于:

数学思维探究社微信公众号图标

不想公开提问?

关注公众号 数学思维探究社,后台发送“微积分+问题”,获取一对一解答。

数学思维探究社