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

简介: 【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

检测结果图

相关文章
|
12天前
|
Python
python魔法方法如何应用
【4月更文挑战第12天】这个Python示例展示了类继承和方法重写。`Student`类继承自`Person`,并覆盖了`say_hello`方法。通过`super().__init__(name)`调用父类的`__init__`初始化`name`属性,`Student`添加了`age`属性,并在重写的`say_hello`中使用。创建`Student`实例`student`并调用其`say_hello`,输出定制的问候信息。
19 1
|
1天前
|
人工智能 Python
【Python实用技能】建议收藏:自动化实现网页内容转PDF并保存的方法探索(含代码,亲测可用)
【Python实用技能】建议收藏:自动化实现网页内容转PDF并保存的方法探索(含代码,亲测可用)
9 0
|
6天前
|
存储 关系型数据库 MySQL
Python搭建代理IP池实现存储IP的方法
Python搭建代理IP池实现存储IP的方法
|
6天前
|
Python
Python动态IP代理防止被封的方法
Python动态IP代理防止被封的方法
|
6天前
|
数据采集 存储 安全
python检测代理ip是否可用的方法
python检测代理ip是否可用的方法
|
7天前
|
Python
基于Django的Python应用—学习笔记—功能完善
基于Django的Python应用—学习笔记—功能完善
|
7天前
|
数据可视化 测试技术 Python
在Python和R中使用交叉验证方法提高模型性能
在Python和R中使用交叉验证方法提高模型性能
19 0
|
8天前
|
存储 监控 开发工具
对象存储OSS产品常见问题之python sdk中的append_object方法支持追加上传xls文件如何解决
对象存储OSS是基于互联网的数据存储服务模式,让用户可以安全、可靠地存储大量非结构化数据,如图片、音频、视频、文档等任意类型文件,并通过简单的基于HTTP/HTTPS协议的RESTful API接口进行访问和管理。本帖梳理了用户在实际使用中可能遇到的各种常见问题,涵盖了基础操作、性能优化、安全设置、费用管理、数据备份与恢复、跨区域同步、API接口调用等多个方面。
38 9
|
8天前
|
Python
python面型对象编程进阶(继承、多态、私有化、异常捕获、类属性和类方法)(上)
python面型对象编程进阶(继承、多态、私有化、异常捕获、类属性和类方法)(上)
49 0
|
12天前
|
开发者 Python
Python中使用`requests`库进行文件上传与下载的技术详解
【4月更文挑战第12天】在Python的网络编程中,文件上传和下载是常见的需求。`requests`库作为一个强大且易用的HTTP客户端,为我们提供了简便的文件上传和下载功能。本文将详细介绍如何在Python中使用`requests`库进行文件上传和下载。