You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

无需OCR引擎的印刷文本数字字符检测方法需求

无需OCR库的字体无关印刷数字检测方案

我之前做过类似的需求,完全不用Tesseract这类OCR库的话,核心就是抓住数字的几何特征共性——不管字体怎么变,0-9的基础形状规律是不会变的。下面分享一套可落地的方法:

一、先做预处理:把数字“孤立”出来

这一步是基础,目的是消除干扰,让每个数字的轮廓清晰可见:

  • 把图像转灰度图,用自适应阈值二值化(比如Otsu法)转成黑白图,搞定光照不均的问题。
  • 用轮廓检测提取单个数字的 bounding box,把每个数字单独抠出来,统一缩放到固定尺寸(比如28x28),方便后续特征计算。

二、基于几何特征的规则匹配(核心)

针对每个数字的独特几何属性,设计可量化的规则,完全不需要预训练:

数字特征规则清单

  • 0:1个闭合区域,纵横比接近1(0.9-1.1),边缘是连续曲线,无突出笔画。
  • 1:纵横比远小于0.3(瘦高),只有垂直主笔画,即使有短横笔画也占比极小。
  • 2:无闭合区域,水平投影有2个峰值,垂直投影有3个拐点。
  • 3:无闭合区域,水平投影有2个明显峰值,垂直投影有2个拐点。
  • 4:无闭合区域,有横竖笔画交叉,水平投影有2个不连续峰值,左半部分垂直投影强度远大于右半部分。
  • 5:下半部分有半闭合区域,水平投影有3个峰值(高-低-高)。
  • 6:1个闭合区域(下半部分),闭合区域的y坐标大于图像高度的一半。
  • 7:瘦高型,无闭合区域,垂直投影只有1个峰值,且有明显的斜向边缘。
  • 8:2个独立闭合区域(上下各一个),纵横比接近1。
  • 9:1个闭合区域(上半部分),闭合区域的y坐标小于图像高度的一半。

可量化的特征计算项

要实现上面的规则,需要计算这些关键特征:

  • 闭合区域数量:通过轮廓检测的层级结构计数(比如OpenCV的RETR_CCOMP模式可以区分外层轮廓和孔洞)。
  • 纵横比:数字 bounding box 的宽高比。
  • 水平/垂直投影:统计每行/每列的黑色像素数量,分析峰值、谷值的数量和位置。
  • 边缘方向分布:用Canny边缘检测后,统计边缘的角度分布(比如1的边缘以垂直方向为主)。

三、形态学操作辅助验证

用腐蚀、膨胀这类形态学操作强化特征,减少干扰:

  • 检测1时,用垂直方向的膨胀操作,保留竖笔画,消除可能的小横笔画干扰。
  • 检测0/6/8/9的闭合区域时,用轻度腐蚀让孔洞轮廓更清晰,避免笔画粘连导致的误判。

四、代码示例(Python + OpenCV,无OCR依赖)

import cv2
import numpy as np

def count_closed_regions(contours, hierarchy):
    """统计闭合区域(孔洞)的数量"""
    hole_count = 0
    for i in range(len(contours)):
        # hierarchy[0][i][3] != -1 表示当前轮廓是外层轮廓的子轮廓(即孔洞)
        if hierarchy[0][i][3] != -1:
            hole_count += 1
    return hole_count

def analyze_single_digit(digit_img):
    """分析单个数字图像,返回检测到的数字(0-9)或None"""
    # 1. 预处理:二值化
    gray = cv2.cvtColor(digit_img, cv2.COLOR_BGR2GRAY)
    _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
    h, w = binary.shape

    # 2. 提取核心特征
    contours, hierarchy = cv2.findContours(binary, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
    closed_regions = count_closed_regions(contours, hierarchy)
    aspect_ratio = w / h

    # 3. 水平投影分析
    horizontal_proj = np.sum(binary, axis=1)
    non_zero_proj = horizontal_proj[horizontal_proj > 0]
    # 计算投影的峰值数量(二阶导数变负的点)
    peaks = np.where(np.diff(np.sign(np.diff(non_zero_proj))) < 0)[0] + 1
    peak_count = len(peaks)

    # 4. 规则匹配
    if closed_regions == 1:
        if 0.9 < aspect_ratio < 1.1:
            # 区分0、6、9:看闭合区域的位置
            hole_contours = [contours[i] for i in range(len(contours)) if hierarchy[0][i][3] != -1]
            hole_y = cv2.boundingRect(hole_contours[0])[1]
            if hole_y < h/2:
                return 9
            elif hole_y > h/2:
                return 6
            else:
                return 0
    elif closed_regions == 2 and 0.9 < aspect_ratio < 1.1:
        return 8
    elif closed_regions == 0:
        if aspect_ratio < 0.3:
            return 1
        elif peak_count == 2:
            # 区分2、3、4、7:看垂直投影
            vertical_proj = np.sum(binary, axis=0)
            v_peaks = np.where(np.diff(np.sign(np.diff(vertical_proj))) < 0)[0] + 1
            if len(v_peaks) == 3:
                return 2
            elif len(v_peaks) == 2:
                return 3
            elif np.max(vertical_proj[:w//2]) > 2 * np.max(vertical_proj[w//2:]):
                return 4
            else:
                return 7
        elif peak_count == 3:
            return 5
    return None

# 使用示例:假设已经从原图中提取了单个数字的ROI区域
# digit_roi = cv2.imread('single_digit.png')
# detected_digit = analyze_single_digit(digit_roi)
# print(f"检测到数字:{detected_digit}")

五、注意事项

  • 这套方法对印刷体效果极佳,但对手写体适配性差(手写体几何变异太大)。
  • 预处理一定要做好:如果图像有噪声,可以先做高斯模糊;如果字符有粘连,用形态学腐蚀分开。
  • 可以根据实际使用的字体调整规则阈值(比如有些字体的0偏扁,就把纵横比的范围调到0.8-1.2)。

内容的提问来源于stack exchange,提问作者PentaKon

火山引擎 最新活动