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

本文涉及的产品
通用文字识别,通用文字识别 200次/月
文档理解,结构化解析 100页
票证核验,票证核验 50次/账号
简介: 【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

检测结果图

相关文章
|
10天前
|
JSON 数据可视化 API
Python 中调用 DeepSeek-R1 API的方法介绍,图文教程
本教程详细介绍了如何使用 Python 调用 DeepSeek 的 R1 大模型 API,适合编程新手。首先登录 DeepSeek 控制台获取 API Key,安装 Python 和 requests 库后,编写基础调用代码并运行。文末包含常见问题解答和更简单的可视化调用方法,建议收藏备用。 原文链接:[如何使用 Python 调用 DeepSeek-R1 API?](https://apifox.com/apiskills/how-to-call-the-deepseek-r1-api-using-python/)
|
3月前
|
机器学习/深度学习 Python
堆叠集成策略的原理、实现方法及Python应用。堆叠通过多层模型组合,先用不同基础模型生成预测,再用元学习器整合这些预测,提升模型性能
本文深入探讨了堆叠集成策略的原理、实现方法及Python应用。堆叠通过多层模型组合,先用不同基础模型生成预测,再用元学习器整合这些预测,提升模型性能。文章详细介绍了堆叠的实现步骤,包括数据准备、基础模型训练、新训练集构建及元学习器训练,并讨论了其优缺点。
158 3
|
1天前
|
JavaScript 前端开发 Android开发
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
30 13
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
|
3天前
|
JavaScript 搜索推荐 Android开发
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
23 8
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
|
3天前
|
数据采集 JavaScript Android开发
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
26 7
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
|
1月前
|
前端开发 搜索推荐 编译器
【01】python开发之实例开发讲解-如何获取影视网站中经过保护后的视频-用python如何下载无法下载的视频资源含m3u8-python插件之dlp-举例几种-详解优雅草央千澈
【01】python开发之实例开发讲解-如何获取影视网站中经过保护后的视频-用python如何下载无法下载的视频资源含m3u8-python插件之dlp-举例几种-详解优雅草央千澈
110 34
【01】python开发之实例开发讲解-如何获取影视网站中经过保护后的视频-用python如何下载无法下载的视频资源含m3u8-python插件之dlp-举例几种-详解优雅草央千澈
|
1月前
|
机器学习/深度学习 运维 数据可视化
Python时间序列分析:使用TSFresh进行自动化特征提取
TSFresh 是一个专门用于时间序列数据特征自动提取的框架,支持分类、回归和异常检测等机器学习任务。它通过自动化特征工程流程,处理数百个统计特征(如均值、方差、自相关性等),并通过假设检验筛选显著特征,提升分析效率。TSFresh 支持单变量和多变量时间序列数据,能够与 scikit-learn 等库无缝集成,适用于大规模时间序列数据的特征提取与模型训练。其工作流程包括数据格式转换、特征提取和选择,并提供可视化工具帮助理解特征分布及与目标变量的关系。
70 16
Python时间序列分析:使用TSFresh进行自动化特征提取
|
26天前
|
人工智能 自然语言处理 算法
随机的暴力美学蒙特卡洛方法 | python小知识
蒙特卡洛方法是一种基于随机采样的计算算法,广泛应用于物理学、金融、工程等领域。它通过重复随机采样来解决复杂问题,尤其适用于难以用解析方法求解的情况。该方法起源于二战期间的曼哈顿计划,由斯坦尼斯拉夫·乌拉姆等人提出。核心思想是通过大量随机样本来近似真实结果,如估算π值的经典示例。蒙特卡洛树搜索(MCTS)是其高级应用,常用于游戏AI和决策优化。Python中可通过简单代码实现蒙特卡洛方法,展示其在文本生成等领域的潜力。随着计算能力提升,蒙特卡洛方法的应用范围不断扩大,成为处理不确定性和复杂系统的重要工具。
65 21
|
25天前
|
数据挖掘 数据处理 开发者
Python3 自定义排序详解:方法与示例
Python的排序功能强大且灵活,主要通过`sorted()`函数和列表的`sort()`方法实现。两者均支持`key`参数自定义排序规则。本文详细介绍了基础排序、按字符串长度或元组元素排序、降序排序、多条件排序及使用`lambda`表达式和`functools.cmp_to_key`进行复杂排序。通过示例展示了如何对简单数据类型、字典、类对象及复杂数据结构(如列车信息)进行排序。掌握这些技巧可以显著提升数据处理能力,为编程提供更强大的支持。
31 10
|
2月前
|
安全
Python-打印99乘法表的两种方法
本文详细介绍了两种实现99乘法表的方法:使用`while`循环和`for`循环。每种方法都包括了步骤解析、代码演示及优缺点分析。文章旨在帮助编程初学者理解和掌握循环结构的应用,内容通俗易懂,适合编程新手阅读。博主表示欢迎读者反馈,共同进步。

热门文章

最新文章