Python OCR 的 OpenCV 偏差校正

Python OpenCV skew correction for OCR

目前,我正在做一个 OCR 项目,我需要从标签上读取文本(参见下面的示例图片)。我 运行 遇到图像倾斜问题,我需要帮助修复图像倾斜,使文本水平而不是倾斜。目前,我正在使用的过程尝试对给定范围内的不同角度进行评分(下面包含代码),但这种方法不一致,有时会过度校正图像倾斜或平坦化无法识别倾斜并纠正它。请注意,在歪斜校正之前,我将所有图像旋转 270 度以使文本直立,然后通过下面的代码传递图像。传递给函数的图像已经是二进制图像。

代码:


def findScore(img, angle):
    """
    Generates a score for the binary image recieved dependent on the determined angle.\n
    Vars:\n
    - array <- numpy array of the label\n
    - angle <- predicted angle at which the image is rotated by\n
    Returns:\n
    - histogram of the image
    - score of potential angle
    """
    data = inter.rotate(img, angle, reshape = False, order = 0)
    hist = np.sum(data, axis = 1)
    score = np.sum((hist[1:] - hist[:-1]) ** 2)
    return hist, score

def skewCorrect(img):
    """
    Takes in a nparray and determines the skew angle of the text, then corrects the skew and returns the corrected image.\n
    Vars:\n
    - img <- numpy array of the label\n
    Returns:\n
    - Corrected image as a numpy array\n
    """
    #Crops down the skewImg to determine the skew angle
    img = cv2.resize(img, (0, 0), fx = 0.75, fy = 0.75)

    delta = 1
    limit = 45
    angles = np.arange(-limit, limit+delta, delta)
    scores = []
    for angle in angles:
        hist, score = findScore(img, angle)
        scores.append(score)
    bestScore = max(scores)
    bestAngle = angles[scores.index(bestScore)]
    rotated = inter.rotate(img, bestAngle, reshape = False, order = 0)
    print("[INFO] angle: {:.3f}".format(bestAngle))
    #cv2.imshow("Original", img)
    #cv2.imshow("Rotated", rotated)
    #cv2.waitKey(0)
    
    #Return img
    return rotated

校正前后的标签示例图片

修正前->修正后

如果有人能帮我解决这个问题,那会很有帮助。

这是 Projection Profile Method algorithm for skew angle estimation 的一个实现。各种角度点被投影到累加器阵列中,其中倾斜角可以定义为搜索间隔内的投影角度,使对齐最大化。这个想法是以不同角度旋转图像并为每次迭代生成像素直方图。为了确定偏斜角,我们比较峰值之间的最大差异并使用此偏斜角,旋转图像以校正偏斜。


原文->更正

Skew angle: -2

import cv2
import numpy as np
from scipy.ndimage import interpolation as inter

def correct_skew(image, delta=1, limit=5):
    def determine_score(arr, angle):
        data = inter.rotate(arr, angle, reshape=False, order=0)
        histogram = np.sum(data, axis=1, dtype=float)
        score = np.sum((histogram[1:] - histogram[:-1]) ** 2, dtype=float)
        return histogram, score

    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1] 

    scores = []
    angles = np.arange(-limit, limit + delta, delta)
    for angle in angles:
        histogram, score = determine_score(thresh, angle)
        scores.append(score)

    best_angle = angles[scores.index(max(scores))]

    (h, w) = image.shape[:2]
    center = (w // 2, h // 2)
    M = cv2.getRotationMatrix2D(center, best_angle, 1.0)
    corrected = cv2.warpAffine(image, M, (w, h), flags=cv2.INTER_CUBIC, \
            borderMode=cv2.BORDER_REPLICATE)

    return best_angle, corrected

if __name__ == '__main__':
    image = cv2.imread('1.png')
    angle, corrected = correct_skew(image)
    print('Skew angle:', angle)
    cv2.imshow('corrected', corrected)
    cv2.waitKey()

注意:您可能需要根据图像调整 deltalimit 值。 delta 值控制迭代步长,它将迭代到 limit 控制最大角度。此方法通过迭代检查每个角度 + delta 非常简单,目前仅适用于校正 +/- 5 度范围内的倾斜。如果您需要在更大的角度进行校正,请调整 limit 值。对于另一种处理偏斜的方法,.

假设:

  1. 输入图像中的内容在任一方向的倾斜度均未超过 45 度
  2. 所有内容相对适合一个矩形
  3. 您已经应用了阈值处理,然后可能使用侵蚀或聚类算法来消除噪声

解决方案:

hgt_rot_angle = cv2.minAreaRect(your_CLEAN_image_pixel_coordinates_to_enclose)[-1]
com_rot_angle = hgt_rot_angle + 90 if hgt_rot_angle < -45 else hgt_rot_angle

(h, w) = my_input_image.shape[0:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, com_rot_angle, 1.0)
corrected_image = cv2.warpAffine(your_ORIGINAL_image, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE)

原始来源:

https://www.pyimagesearch.com/2017/02/20/text-skew-correction-opencv-python/ - 一个很棒的入门教程(Adrian Rosebrock 的荣誉),但是:

  • 它对干净的文本合成图像进行操作,其中没有降噪步骤,甚至没有对它们的引用,只有阈值处理...然而,在大多数真实场景中,需要旋转的图像在 OCR 之前执行的操作也需要执行显着的降噪。我尝试了 OpenCV 侵蚀操作和 scikit-learn DBSCAN 聚类算法,仅将 "core" 像素传递给上述解决方案,它们都工作得相当好。
  • 我认为cv2.minAreaRect()返回的角度值如何解释的解释不是很清楚,代码有相同的变量用于检测和校正,这更加令人困惑。为了清楚起见,我使用了单独的变量,下面是我对前两行代码的解释。
  • 根据 OpenCV 文档,在将值传递给 cv2.getRotationMatrix2D() 函数之前,我必须恭敬地反对我们需要 "take the inverse" 检测到的旋转角度(教程中的第 38 和 43 行)并根据我的测试。下面还有更多相关信息。

解法说明:

cv2.minAreaRect()函数returns将[-90, 0]范围内的旋转角度值作为返回元组的最后一个元素,角度值绑定到[-90, 0]范围内的HEIGHT值相同的返回元组(它位于 cv2.minAreaRect()[1][1],准确地说,但我们不在这里使用它)。

除非旋转角度是 -90.00.0,否则选择哪个维度作为 "height" 的决定不是任意的 - 它总是必须从左上角开始向右下方,即具有负斜率。

这对我们的用例意味着,根据内容块的宽高比例及其倾斜度,"height" cv2.minAreaRect() 返回的值可以是内容块的逻辑高度或宽度.

这对我们来说意味着两件事:

  1. 我们无法在不对 "proper" 宽高比做出假设的情况下修正任何一侧超过 45 度的倾斜。
  2. 如果没有关于内容块纵横比的假设,我们必须假设内容向任一侧倾斜小于 45 度,以便继续。这个假设对于仅打算纵向方向的扫描非常有效,但对于使用横向方向扫描的许多文档中只有一页的文档会中断。我还没有解决这个问题。

因此,给定 (1) 没有关于内容块的纵横比的假设和 (2) 假设的 [-45:45] 倾斜范围,我们可以得到 common如果 "height" 的旋转值低于 -45.0,只需将 "height" 的旋转值加上 90 度,即可相对于直角坐标系(在 [-45:45] 范围内)倾斜高度和宽度。

一旦我们得到这个检测和计算的 "common rotation angle" 值,我们就可以通过将值直接传递给 cv2.getRotationMatrix2D() 函数来使用它来修复倾斜。
注意:计算出的existing "common rotation angle"逆时针倾斜为负,顺时针倾斜为正,这是很常见的日常惯例。但是,如果我们将 cv2.getRotationMatrix2D()angle 参数视为 "the correction angle to apply"(我认为这是意图),那么符号约定是 OPPOSITE。因此,如果我们希望在输出图像中看到它的反作用,我们需要按原样传递检测和计算的 "common rotation angle" 值,我执行的许多测试都支持这一点。
这是 OpenCV documentation:

angle 参数的直接引用

Rotation angle in degrees. Positive values mean counter-clockwise rotation (the coordinate origin is assumed to be the top-left corner).

如果单个矩形不合适怎么办?

上面的解决方案对于密集填充的整页扫描、干净的标签和类似的东西非常有效,但它对于稀疏填充的图像根本不起作用,其中整体最紧密的拟合不是矩形,即当第二个起始假设不成立。

在后一种情况下,以下可能会起作用 IF 输入图像中的大多数单个形状都可以很好地适合矩形,或者至少比所有内容的组合更好:

  • 应用阈值化/分级/变形/侵蚀操作,最后应用计数以定位和勾勒出可能包含相关内容而非噪声的图像区域。
  • 获取每个轮廓的MAR(最小区域矩形)和每个对应MAR的旋转角度。
  • 汇总结果以得出最可能需要固定的整体倾斜角度(这里的确切方法很多)。

其他来源:

https://www.pyimagesearch.com/2015/11/30/detecting-machine-readable-zones-in-passport-images/

https://docs.opencv.org/master/dd/d49/tutorial_py_contour_features.html

要添加 @nathancy 个答案,对于 windows 用户,如果您有额外的偏差,只需添加 dtype=float。每当您创建一个 numpy 数组时。 windows 存在整数溢出问题,因为它分配 int(32) 位作为数据类型,这与其他系统不同。

见下面的代码;在 np.sum() 方法中添加了 dtype=float

import cv2
import numpy as np
from scipy.ndimage import interpolation as inter

def correct_skew(image, delta=1, limit=5):
    def determine_score(arr, angle):
        data = inter.rotate(arr, angle, reshape=False, order=0)
        histogram = np.sum(data, axis=1, dtype=float)
        score = np.sum((histogram[1:] - histogram[:-1]) ** 2, dtype=float)
        return histogram, score

    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1] 

    scores = []
    angles = np.arange(-limit, limit + delta, delta)
    for angle in angles:
        histogram, score = determine_score(thresh, angle)
        scores.append(score)

    best_angle = angles[scores.index(max(scores))]

    (h, w) = image.shape[:2]
    center = (w // 2, h // 2)
    M = cv2.getRotationMatrix2D(center, best_angle, 1.0)
    rotated = cv2.warpAffine(image, M, (w, h), flags=cv2.INTER_CUBIC, \
          borderMode=cv2.BORDER_REPLICATE)

    return best_angle, rotated

if __name__ == '__main__':
    image = cv2.imread('1.png')
    angle, rotated = correct_skew(image)
    print(angle)
    cv2.imshow('rotated', rotated)
    cv2.imwrite('rotated.png', rotated)
    cv2.waitKey()