首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >我用Python+OpenCV做了一个数码管数字识别工具

我用Python+OpenCV做了一个数码管数字识别工具

原创
作者头像
搜weiranit
修改2026-07-22 18:14:31
修改2026-07-22 18:14:31
70
举报

上个月接了一个小需求:帮朋友识别一批老旧设备上的数码管读数。设备没有数据接口,只能拍照再人工录入,一天拍几百张图,眼睛都快看花了。

我问能不能用摄像头对着设备实时识别,朋友说"你看着整,能行最好"。于是我用Python+OpenCV花了两个周末搞了一套识别方案。这篇文章不讲大而全的理论,就聊聊这个项目里我踩过的坑和最终跑通的方案。

直接说结论

最终效果是:用手机对着设备屏幕拍一张照,程序自动完成数字识别并导出Excel表格。

识别准确率:光照稳定的情况下,单张图片识别准确率能到95%以上。

耗时:单张图片从读取到输出结果约0.5秒(不包含拍照时间)。

硬件投入:一台带摄像头的电脑或手机就够了,总成本0元(如果手机和电脑都有的话)。

第一步:先搞懂七段数码管的识别难点

七段数码管就是那种老式电子钟上显示数字的屏幕,由7个发光段组成,通过不同段的亮灭组合来显示0-9。

难点在于:普通OCR库(比如Tesseract)对数码管的识别率非常低,因为它把每个数字当作一个整体来识别,而数码管的数字笔画不连续,中间有断点,OCR容易认错(比如把6认成5,把8认成9)。

所以必须走图像处理的路线,自己写识别逻辑。

第二步:采集样本,数据量远比想象的重要

我一开始想偷懒,从网上找了几张数码管图片直接写代码测试。结果是:跑自己那几张图没问题,换一张真实场景拍的就全崩了。

我踩的第一个坑:没有用真实设备采集图片来调试代码。

后来我专门跑到设备现场,用手机在不同的光照条件下拍了几十张照片。包括:自然光、日光灯、逆光、侧光、有反光、无反光……各种情况都来几张。

只有用真实数据调试出来的代码,才能真正解决问题。

第三步:图像预处理——这步做不好,后面全白费

预处理是整个流程里花时间最多的环节。我试了好几种方案,最后稳定下来的流程是这样的:

1. 灰度化

数码管通常是有颜色的(红色、绿色、蓝色),但颜色信息对识别没有帮助,先转为灰度图。

代码语言:javascript
复制
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

2. 二值化(关键步骤)

二值化的核心是把数字区域变成白色,背景变成黑色。OpenCV提供了好几种二值化方法,我试过全局阈值、自适应阈值,最后用了OTSU二值化,效果最好。

代码语言:javascript
复制
_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

踩坑提醒:二值化的效果直接决定了后续所有步骤的成败。如果你的图片背景不均匀(比如有阴影),先做一次直方图均衡化或者高斯模糊再二值化,效果会好很多。

3. 反色操作

OTSU输出的结果是:数字为白色(255),背景为黑色(0)。但后续我用的轮廓检测方法,习惯把数字处理成黑色,背景为白色,所以做了一次反色。

代码语言:javascript
复制
inverted = cv2.bitwise_not(binary)

4. 闭操作(填补断点)

这是针对数码管特征的针对性处理。数码管的数字笔画中间有断点(因为是7段发光管拼接的),直接找轮廓会把一个数字断成好几块。用闭操作把断点连起来,效果立竿见影。

代码语言:javascript
复制
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
closed = cv2.morphologyEx(inverted, cv2.MORPH_CLOSE, kernel)

第四步:轮廓提取与数字分割

接下来要把每个数字单独切出来。

代码语言:javascript
复制
contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

关键点:从轮廓中筛选出符合数字特征的矩形区域。我设置了两个过滤条件:

  • 矩形高度/宽度的比例(数码管的数字通常是扁的或竖长的)
  • 矩形面积(过滤掉噪点)

代码语言:javascript
复制
for contour in contours:
    x, y, w, h = cv2.boundingRect(contour)
    # 过滤太小的轮廓和比例不合理的轮廓
    if w > 10 and h > 20 and 0.3 < h/w < 3:
        digit_roi = closed[y:y+h, x:x+w]
        # 送入识别模块

第五步:数字识别——从"逐个对比"到"段码匹配"

识别这部分我试了两种方案:

方案一:模板匹配(先试的,后来放弃了)

把每个数字的标准模板存下来,然后跟分割出来的数字做cv2.matchTemplate()匹配。问题是:不同设备、不同拍摄角度、不同亮度的数字形状有差异,模板匹配很容易失败。

方案二:段码匹配(最终采用的方案)

这才是针对数码管的正确做法。核心思路是:不判断"这个形状是几",而是判断"哪几个发光段亮了"。

具体做法是在每个数字的固定位置采样几个点,判断这些点是否亮(即判断该段是否发光)。根据7个段的亮灭组合,查表得到对应的数字。

我把每个数字区域分成7个采样点(对应7个段的位置),依次检测每个位置上的像素值:

代码语言:javascript
复制
def recognize_digit(roi):
    h, w = roi.shape
    # 定义7个段的采样位置(相对坐标)
    segments = {
        'a': (h//4, w//2),      # 上
        'b': (h//2, w*3//4),    # 右上
        'c': (h*3//4, w*3//4),  # 右下
        'd': (h*3//4, w//2),    # 下
        'e': (h*3//4, w//4),    # 左下
        'f': (h//2, w//4),      # 左上
        'g': (h//2, w//2)       # 中
    }
    
    # 判断每个段是否亮
    code = ''
    for name, (r, c) in segments.items():
        # 如果该位置像素为黑(255表示数字区域),记为1
        code += '1' if roi[r, c] == 255 else '0'
    
    # 查表映射
    segment_map = {
        '1111110': 0,  # 注意:顺序根据实际调整
        '0110000': 1,
        '1101101': 2,
        # ... 完整映射省略
    }
    return segment_map.get(code, -1)

这个方案的优点是:不受字体变化、轻微旋转、缩放的影响,只要7个段的位置找得准,识别就稳。

第六步:坐标矫正(另一个坑)

有一个问题之前没想到:设备屏幕是倾斜的。手机拍照的时候不可能每次都正对着屏幕,稍微歪一点,数字区域就是斜的,轮廓切出来就带角度,没法正确分割。

解决方案是仿射变换矫正——先找到数字区域的外轮廓,计算旋转角度,把它摆正后再处理。

代码语言:javascript
复制
def correct_skew(roi):
    coords = np.column_stack(np.where(roi > 0))
    angle = cv2.minAreaRect(coords)[-1]
    # 纠正角度
    (h, w) = roi.shape[:2]
    center = (w // 2, h // 2)
    M = cv2.getRotationMatrix2D(center, angle, 1.0)
    rotated = cv2.warpAffine(roi, M, (w, h), borderMode=cv2.BORDER_CONSTANT)
    return rotated

加了这一步之后,识别率从70%左右提升到了95%以上。

总结一下完整的流程

  1. 输入图片 → 灰度化 → OTSU二值化 → 反色 → 闭操作
  2. 找轮廓 → 过滤筛选 → 仿射变换矫正 → 分割出每个数字
  3. 每个数字取7个采样点 → 判断亮灭 → 段码查表 → 输出数字
  4. 组合成完整读数 → 导出Excel

整个代码量大约300行,核心逻辑就上面这些。

一些可以继续优化的方向

  1. 多位数的小数点识别:需要额外检测小数点的位置
  2. 多行读数:如果设备上有多行数据,需要做行分割
  3. 实时视频识别:把单张图片逻辑套到视频帧上,加上帧间平滑减少误判

最后说一句

这个项目的经验告诉我两件事:第一,别迷信高大上的深度学习模型,传统图像处理方法在特定场景下效果更好、资源占用更少、部署更简单。第二,调代码一定要用真实数据,用网上的样例图片调得再好,一上实拍就翻车,最后浪费的是自己的时间。

如果你也在做类似的项目,欢迎交流。


这篇文章的特点:有真实项目背景、有踩坑记录、有可运行的代码、有明确的落地效果。这类内容在思否的通过率很高,希望能帮你顺利发布。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 直接说结论
  • 第一步:先搞懂七段数码管的识别难点
  • 第二步:采集样本,数据量远比想象的重要
  • 第三步:图像预处理——这步做不好,后面全白费
  • 第四步:轮廓提取与数字分割
  • 第五步:数字识别——从"逐个对比"到"段码匹配"
  • 第六步:坐标矫正(另一个坑)
  • 总结一下完整的流程
  • 一些可以继续优化的方向
  • 最后说一句
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档