【OCR学习笔记】5、OCR传统特征提取方法(文末附python源码实现下载)(一)

本文涉及的产品
自定义KV模板,自定义KV模板 500次/账号
企业资质识别,企业资质识别 200次/月
车辆物流识别,车辆物流识别 200次/月
简介: 【OCR学习笔记】5、OCR传统特征提取方法(文末附python源码实现下载)(一)

1 简介


虽然现在传统的特征提取方法看见的越来越少,但是实际工业界还是会使用传统的特征提取方法;传统的特征提取方法主要分为两个类别,分别是基于结构形态的特征提取方法基于几何分布的特征提取方法

更为详细的划分如下图所示:


2 基于结构形态的特征提取方法


通常情况下,基于结构形态的特征有两类表示方法:1)轮廓特征2)区域特征

基于结构形态的特征提取方法主要是将字符图像的结构形态转化为特征向量

主要包括:

  • 边界特征法
  • 傅里叶特征算子法
  • 形状不变矩法
  • 几何参数法

2.1 边界特征法

边界特征法主要关注的是图像边界部分的特征。其中,霍夫变换法边界方向直方图法是两种最典型的边界特征法。

2.1.1 霍夫变换法

举个简单的例子来说明霍夫变换法,在图片上,画一条直线,直线方程为。在这条直线上,选取三个点:、、。根据上述原理,可以求出过点的参数方程为,过点的参数方程为,过点的参数方程为,这三个参数方程对应着三条不同的直线。而这三条直线相交于同一点。同理,原图像上的其他点如、对应的参数方程和也都经过点。这个特性说明,可以通过将图像平面上的点映射到参数平面上的线,然后利用统计特征来求出直线所在的位置。即如果图像平面上有两条直线,那么最终的参数平面上就会出现两个峰值点,以此类推。

霍夫变换的基本思想:原始图像坐标系下的一个点对应于参数坐标系中的一条直线,反之,参数坐标系下的一条直线对应于原始图像坐标系下的一个点。然后,将原始图像坐标系下的各个点都投影到参数坐标系之后,会发现有聚集的点,这些聚集的点组成了原始坐标系下的直线。

霍夫直线检测对应的Python实现如下:

# -*- coding: utf-8 -*-
import sys
import numpy as np
import cv2
import math
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 霍夫极坐标变换:直线检测
def HTLine(image, stepTheta=1, stepRho=1):
    # 宽、高
    rows, cols = image.shape
    # 图像中可能出现的最大垂线的长度
    L = round(math.sqrt(pow(rows - 1, 2.0) + pow(cols - 1, 2.0))) + 1
    # 初始化投票器
    numtheta = int(180.0 / stepTheta)
    numRho = int(2 * L / stepRho + 1)
    accumulator = np.zeros((numRho, numtheta), np.int32)
    # 建立字典
    accuDict = {}
    for k1 in range(numRho):
        for k2 in range(numtheta):
            accuDict[(k1, k2)] = []
    # 投票计数
    for y in range(rows):
        for x in range(cols):
            if image[y][x] == 255:  # 只对边缘点做霍夫变换
                for m in range(numtheta):
                    # 对每一个角度,计算对应的 rho 值
                    rho = x * math.cos(stepTheta * m / 180.0 * math.pi) + y * math.sin(stepTheta * m / 180.0 * math.pi)
                    # 计算投票哪一个区域
                    n = int(round(rho + L) / stepRho)
                    # 投票加 1
                    accumulator[n, m] += 1
                    # 记录该点
                    accuDict[(n, m)].append((x, y))
    return accumulator, accuDict
# 主函数
if __name__ == "__main__":
    I = cv2.imread('../picture/line.png')
    # canny 边缘检测
    edge = cv2.Canny(I, 50, 200)
    # 显示二值化边缘
    cv2.imshow("edge", edge)
    # 霍夫直线检测
    accumulator, accuDict = HTLine(edge, 1, 1)
    # 计数器的二维直方图方式显示
    rows, cols = accumulator.shape
    fig = plt.figure()
    ax = fig.gca(projection='3d')
    X, Y = np.mgrid[0:rows:1, 0:cols:1]
    surf = ax.plot_wireframe(X, Y, accumulator, cstride=1, rstride=1, color='gray')
    ax.set_xlabel(u"$\\rho$")
    ax.set_ylabel(u"$\\theta$")
    ax.set_zlabel("accumulator")
    ax.set_zlim3d(0, np.max(accumulator))
    # 计数器的灰度级显示
    grayAccu = accumulator / float(np.max(accumulator))
    grayAccu = 255 * grayAccu
    grayAccu = grayAccu.astype(np.uint8)
    # 只画出投票数大于 60 直线
    voteThresh = 180
    for r in range(rows):
        for c in range(cols):
            if accumulator[r][c] > voteThresh:
                points = accuDict[(r, c)]
                cv2.line(I, points[0], points[len(points) - 1], (255), 2)
    cv2.imshow('accumulator', grayAccu)
    # 显示原图
    cv2.imshow("I", I)
    plt.show()
    cv2.imwrite('accumulator.jpg', grayAccu)
    cv2.imwrite('I.jpg', I)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


237afee0234c134efad2796ecd789284.png

原图

ee0e174b2819e0fd6517acd566a57ae6.png

检测到的直线

6a1e4bd9ad231f13a4f2784d4b56bb08.png

计数器的三维展示

2.1.2 边界方向直方图法

首先讨论一下图像边缘检测。常用的边缘检测算子有:

  • Laplacian算子
  • Sobel算子
  • Prewitt算子
  • Canny算子
  • 等等

一幅图像是由很多个离散的像素点组成的,上面提到的这些算子将通过差分的方式来近似偏导数的值。其中,Canny算子是效果较好的一种图像边缘检测算子。它分为两个阶段,首先对图像进行高斯平滑,然后对平滑之后的图像进行Roberts算子运算。

1、Laplacian算子

Laplacian算子是n维欧几里得空间中的一个二阶微分算子。

850228e69aa71ad6f7855024dee83829.png

2、Sobel算子

Sobel算子是一种一阶微分算子,主要利用单个像素邻近区域的梯度值来计算该像素的梯度值,然后根据一定的规则进行取舍:

725575601643c621bb3c0e49af23af55.png

如果Sobel算子使用的是一个方向3×3的卷积核,并且该卷积核对垂直边缘的响应最大;如果Sobel算子使用的是一个方向3×3的卷积核,并且该卷积核对水平边缘的响应最大。

3、Prewitt算子

Prewitt算子也是一种一阶微分算子:

c59398feb2a22a710853ac69a41c792a.png

如果Prewitt算子使用的是一个方向3×3的卷积核,并且该卷积核对垂直边缘的响应最大;如果Sobel算子使用的是一个方向3×3的卷积核,并且该卷积核对水平边缘的响应最大。

4、Canny算子

Canny边缘检测算子主要包括以下四个步骤。

  • 1 用高斯滤波器对图像进行平滑处理。
  • 2 用一阶偏导的有限差分来计算梯度的幅值和方向。
  • 3 对梯度的幅值进行非极大值抑制处理。
  • 4 用双阈值算法检测和连接图像的边缘。

Canny检测对应的Python实现如下(包含canny和sobel的python实现):

# -*- coding: utf-8 -*-
import numpy as np
import sys
import math
import cv2
# import sobel  # 注意sobel边缘检测
# import cv2.Sobel as sobel
from cv2 import Sobel as sobel
# 边缘检测
# 非极大值抑制
def non_maximum_suppression_default(dx, dy):
    # 边缘强度
    edgeMag = np.sqrt(np.power(dx, 2.0) + np.power(dy, 2.0))
    # 宽、高
    rows, cols = dx.shape
    # 梯度方向
    gradientDirection = np.zeros(dx.shape)
    # 边缘强度非极大值抑制
    edgeMag_nonMaxSup = np.zeros(dx.shape)
    for r in range(1, rows - 1):
        for c in range(1, cols - 1):
            # angle 的范围 [0,180] [-180,0]
            angle = math.atan2(dy[r][c], dx[r][c]) / math.pi * 180
            gradientDirection[r][c] = angle
            # 左 / 右方向
            if (abs(angle) < 22.5 or abs(angle) > 157.5):
                if (edgeMag[r][c] > edgeMag[r][c - 1] and edgeMag[r][c] > edgeMag[r][c + 1]):
                    edgeMag_nonMaxSup[r][c] = edgeMag[r][c]
            # 左上 / 右下方向
            if (angle >= 22.5 and angle < 67.5 or (-angle > 112.5 and -angle <= 157.5)):
                if (edgeMag[r][c] > edgeMag[r - 1][c - 1] and edgeMag[r][c] > edgeMag[r + 1][c + 1]):
                    edgeMag_nonMaxSup[r][c] = edgeMag[r][c]
            # 上 / 下方向
            if ((angle >= 67.5 and angle <= 112.5) or (angle >= -112.5 and angle <= -67.5)):
                if (edgeMag[r][c] > edgeMag[r - 1][c] and edgeMag[r][c] > edgeMag[r + 1][c]):
                    edgeMag_nonMaxSup[r][c] = edgeMag[r][c]
            # 右上 / 左下方向
            if ((angle > 112.5 and angle <= 157.5) or (-angle >= 22.5 and -angle < 67.5)):
                if (edgeMag[r][c] > edgeMag[r - 1][c + 1] and edgeMag[r][c] > edgeMag[r + 1][c - 1]):
                    edgeMag_nonMaxSup[r][c] = edgeMag[r][c]
    return edgeMag_nonMaxSup
# 非极大值抑制:插值比较
def non_maximum_suppression_Inter(dx, dy):
    # 边缘强度
    edgeMag = np.sqrt(np.power(dx, 2.0) + np.power(dy, 2.0))
    # 宽、高
    rows, cols = dx.shape
    # 梯度方向
    gradientDirection = np.zeros(dx.shape)
    # 边缘强度的非极大值抑制
    edgeMag_nonMaxSup = np.zeros(dx.shape)
    for r in range(1, rows - 1):
        for c in range(1, cols - 1):
            if dy[r][c] == 0 and dx[r][c] == 0:
                continue
            # angle的范围 [0,180],[-180,0]
            angle = math.atan2(dy[r][c], dx[r][c]) / math.pi * 180
            gradientDirection[r][c] = angle
            # 左上方和上方的插值 右下方和下方的插值
            if (angle > 45 and angle <= 90) or (angle > -135 and angle <= -90):
                ratio = dx[r][c] / dy[r][c]
                leftTop_top = ratio * edgeMag[r - 1][c - 1] + (1 - ratio) * edgeMag[r - 1][c]
                rightBottom_bottom = (1 - ratio) * edgeMag[r + 1][c] + ratio * edgeMag[r + 1][c + 1]
                if edgeMag[r][c] > leftTop_top and edgeMag[r][c] > rightBottom_bottom:
                    edgeMag_nonMaxSup[r][c] = edgeMag[r][c]
            # 右上方和上方的插值 左下方和下方的插值
            if (angle > 90 and angle <= 135) or (angle > -90 and angle <= -45):
                ratio = abs(dx[r][c] / dy[r][c])
                rightTop_top = ratio * edgeMag[r - 1][c + 1] + (1 - ratio) * edgeMag[r - 1][c]
                leftBottom_bottom = ratio * edgeMag[r + 1][c - 1] + (1 - ratio) * edgeMag[r + 1][c]
                if edgeMag[r][c] > rightTop_top and edgeMag[r][c] > leftBottom_bottom:
                    edgeMag_nonMaxSup[r][c] = edgeMag[r][c]
            # 左上方和左方的插值 右下方和右方的插值
            if (angle >= 0 and angle <= 45) or (angle > -180 and angle <= -135):
                ratio = dy[r][c] / dx[r][c]
                rightBottom_right = ratio * edgeMag[r + 1][c + 1] + (1 - ratio) * edgeMag[r][c + 1]
                leftTop_left = ratio * edgeMag[r - 1][c - 1] + (1 - ratio) * edgeMag[r][c - 1]
                if edgeMag[r][c] > rightBottom_right and edgeMag[r][c] > leftTop_left:
                    edgeMag_nonMaxSup[r][c] = edgeMag[r][c]
            # 右上方和右方的插值 左下方和左方的插值
            if (angle > 135 and angle <= 180) or (angle > -45 and angle <= 0):
                ratio = abs(dy[r][c] / dx[r][c])
                rightTop_right = ratio * edgeMag[r - 1][c + 1] + (1 - ratio) * edgeMag[r][c + 1]
                leftBottom_left = ratio * edgeMag[r + 1][c - 1] + (1 - ratio) * edgeMag[r][c - 1]
                if edgeMag[r][c] > rightTop_right and edgeMag[r][c] > leftBottom_left:
                    edgeMag_nonMaxSup[r][c] = edgeMag[r][c]
    return edgeMag_nonMaxSup
# 判断一个点的坐标是否在图像范围内
def checkInRange(r, c, rows, cols):
    if r >= 0 and r < rows and c >= 0 and c < cols:
        return True
    else:
        return False
def trace(edgeMag_nonMaxSup, edge, lowerThresh, r, c, rows, cols):
    # 大于阈值为确定边缘点
    if edge[r][c] == 0:
        edge[r][c] = 255
        for i in range(-1, 2):
            for j in range(-1, 2):
                if checkInRange(r + i, c + j, rows, cols) and edgeMag_nonMaxSup[r + i][c + j] >= lowerThresh:
                    trace(edgeMag_nonMaxSup, edge, lowerThresh, r + i, c + j, rows, cols)
# 滞后阈值
def hysteresisThreshold(edge_nonMaxSup, lowerThresh, upperThresh):
    # 宽高
    rows, cols = edge_nonMaxSup.shape
    edge = np.zeros(edge_nonMaxSup.shape, np.uint8)
    for r in range(1, rows - 1):
        for c in range(1, cols - 1):
            # 大于高阈值,设置为确定边缘点,而且以该点为起始点延长边缘
            if edge_nonMaxSup[r][c] >= upperThresh:
                trace(edgeMag_nonMaxSup, edge, lowerThresh, r, c, rows, cols)
            # 小于低阈值,被剔除
            if edge_nonMaxSup[r][c] < lowerThresh:
                edge[r][c] = 0
    return edge
# 主函数
if __name__ == "__main__":
    image = cv2.imread("../picture/house.png")
    image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    # ------- canny 边缘检测 -----------
    # 第一步: 基于 sobel 核的卷积
    image_sobel_x = sobel(image, cv2.CV_64F, 1, 0)
    image_sobel_y = sobel(image, cv2.CV_64F, 0, 1)
    # 边缘强度:两个卷积结果对应位置的平方和
    edge = np.sqrt(np.power(image_sobel_x, 2.0) + np.power(image_sobel_y, 2.0))
    # 边缘强度的灰度级显示
    edge[edge > 255] = 255
    edge = edge.astype(np.uint8)
    cv2.imshow("sobel edge", edge)
    # 第二步:非极大值抑制
    edgeMag_nonMaxSup = non_maximum_suppression_default(image_sobel_x, image_sobel_y)
    edgeMag_nonMaxSup[edgeMag_nonMaxSup > 255] = 255
    edgeMag_nonMaxSup = edgeMag_nonMaxSup.astype(np.uint8)
    cv2.imshow("edgeMag_nonMaxSup", edgeMag_nonMaxSup)
    # 第三步:双阈值滞后阈值处理,得到 canny 边缘
    # 滞后阈值的目的就是最后决定处于高阈值和低阈值之间的是否为边缘点
    edge = hysteresisThreshold(edgeMag_nonMaxSup, 60, 180)
    lowerThresh = 40
    upperThresh = 150
    cv2.imshow("canny", edge)
    cv2.imwrite("canny.jpg", edge)
    # -------以下是为了单阈值与滞后阈值的结果比较 ------
    # 大于高阈值 设置为白色 为确定边缘
    EDGE = 255
    # 小于低阈值的设置为黑色 表示不是边缘,被剔除
    NOEDGE = 0
    # 而大于等于低阈值 小于高阈值的设置为灰色,标记为可能的边缘
    POSSIBLE_EDGE = 128
    tempEdge = np.copy(edgeMag_nonMaxSup)
    rows, cols = tempEdge.shape
    for r in range(rows):
        for c in range(cols):
            if tempEdge[r][c] >= upperThresh:
                tempEdge[r][c] = EDGE
            elif tempEdge[r][c] < lowerThresh:
                tempEdge[r][c] = NOEDGE
            else:
                tempEdge[r][c] = POSSIBLE_EDGE
    cv2.imshow("tempEdge", tempEdge)
    lowEdge = np.copy(edgeMag_nonMaxSup)
    lowEdge[lowEdge > 60] = 255
    lowEdge[lowEdge < 60] = 0
    cv2.imshow("lowEdge", lowEdge)
    upperEdge = np.copy(edgeMag_nonMaxSup)
    upperEdge[upperEdge > 180] = 255
    upperEdge[upperEdge <= 180] = 0
    cv2.imshow("upperEdge", upperEdge)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

原图

边缘强度图

2acee57e612016869811ea3e0ac71d41.png

检测结果图

相关文章
|
15天前
|
机器学习/深度学习 Python
堆叠集成策略的原理、实现方法及Python应用。堆叠通过多层模型组合,先用不同基础模型生成预测,再用元学习器整合这些预测,提升模型性能
本文深入探讨了堆叠集成策略的原理、实现方法及Python应用。堆叠通过多层模型组合,先用不同基础模型生成预测,再用元学习器整合这些预测,提升模型性能。文章详细介绍了堆叠的实现步骤,包括数据准备、基础模型训练、新训练集构建及元学习器训练,并讨论了其优缺点。
33 3
|
3天前
|
安全
Python-打印99乘法表的两种方法
本文详细介绍了两种实现99乘法表的方法:使用`while`循环和`for`循环。每种方法都包括了步骤解析、代码演示及优缺点分析。文章旨在帮助编程初学者理解和掌握循环结构的应用,内容通俗易懂,适合编程新手阅读。博主表示欢迎读者反馈,共同进步。
|
10天前
|
JSON 安全 API
Python调用API接口的方法
Python调用API接口的方法
50 5
|
19天前
|
算法 决策智能 Python
Python中解决TSP的方法
旅行商问题(TSP)是寻找最短路径,使旅行商能访问每个城市一次并返回起点的经典优化问题。本文介绍使用Python的`ortools`库解决TSP的方法,通过定义城市间的距离矩阵,调用库函数计算最优路径,并打印结果。此方法适用于小规模问题,对于大规模或特定需求,需深入了解算法原理及定制策略。
27 15
|
14天前
|
JSON 开发工具 git
基于Python和pygame的植物大战僵尸游戏设计源码
本项目是基于Python和pygame开发的植物大战僵尸游戏,包含125个文件,如PNG图像、Python源码等,提供丰富的游戏开发学习素材。游戏设计源码可从提供的链接下载。关键词:Python游戏开发、pygame、植物大战僵尸、源码分享。
WK
|
1月前
|
Python
Python中format_map()方法
在Python中,`format_map()`方法用于使用字典格式化字符串。它接受一个字典作为参数,用字典中的键值对替换字符串中的占位符。此方法适用于从字典动态获取值的场景,尤其在处理大量替换值时更为清晰和方便。
WK
74 36
|
17天前
|
机器学习/深度学习 人工智能 算法
强化学习在游戏AI中的应用,从基本原理、优势、应用场景到具体实现方法,以及Python在其中的作用
本文探讨了强化学习在游戏AI中的应用,从基本原理、优势、应用场景到具体实现方法,以及Python在其中的作用,通过案例分析展示了其潜力,并讨论了面临的挑战及未来发展趋势。强化学习正为游戏AI带来新的可能性。
51 4
|
15天前
|
监控 数据挖掘 数据安全/隐私保护
Python脚本:自动化下载视频的日志记录
Python脚本:自动化下载视频的日志记录
|
23天前
|
Python
Python编程中的魔法方法(Magic Methods)
【10月更文挑战第40天】在Python的世界中,魔法方法就像是隐藏在代码背后的神秘力量。它们通常以双下划线开头和结尾,比如 `__init__` 或 `__str__`。这些方法定义了对象的行为,当特定操作发生时自动调用。本文将揭开这些魔法方法的面纱,通过实际例子展示如何利用它们来增强你的类功能。
9 1
|
2月前
|
开发者 Python
Python中的魔法方法与运算符重载
在Python的奇妙世界里,魔法方法(Magic Methods)和运算符重载(Operator Overloading)是两个强大的特性,它们允许开发者以更自然、更直观的方式操作对象。本文将深入探讨这些概念,并通过实例展示如何利用它们来增强代码的可读性和表达力。