用于 OCR 的 Python OpenCV 偏斜校正

Posted

技术标签:

【中文标题】用于 OCR 的 Python OpenCV 偏斜校正【英文标题】:Python OpenCV skew correction for OCR 【发布时间】:2020-01-17 18:00:41 【问题描述】:

目前,我正在做一个 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

校正前后的标签示例图片

修正前-&gt;修正后

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

【问题讨论】:

您可以尝试从轮廓框中获取有关单词的角度。见pyimagesearch.com/2017/02/20/text-skew-correction-opencv-python 或搜索谷歌。该主题有很多链接。 @fmw42 这个方法我已经试过了,这个方法不行,一直把图片调0度。您链接的方法仅适用于完美的文本图像,不幸的是,我正在使用的图像远非完美,因此该方法无法正确识别倾斜角度。 您是否查看过 Google 搜索中的其他方法?您是否尝试从每个单词的轮廓中获取边界框并查看角度分布或获得平均值? 嘿,@PeterS 感谢您的提问。我也在尝试使用 OpenCV 实现 OCR。我在这方面面临一些困难。因此,我想知道您是否可以分享您的 OCR 代码,我可以将其作为参考。这对我会有很大帮助。提前致谢:) 【参考方案1】:

要添加到 @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()

【讨论】:

【参考方案2】:

假设:

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

解决方案:

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 聚类算法,只将“核心”像素传递给上述解决方案,它们都运行得相当好。 我认为那里对如何解释cv2.minAreaRect()返回的角度值的解释不是很清楚,并且代码具有相同的用于检测和校正的变量,这更加令人困惑。为了清楚起见,我使用了单独的变量,我对前两行代码的解释如下。 我必须恭敬地不同意,在将值传递给cv2.getRotationMatrix2D() 函数之前,我们需要“取反”检测到的旋转角度(教程中的第 38 和 43 行),基于 OpenCV 文档和我的测试。下面还有更多关于这方面的内容。

解决方案说明:

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

除非旋转角度是-90.00.0,否则选择哪个维度作为“高度”的决定不是任意的 - 它总是必须从左上角到右下角,即有一个负斜率。

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

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

    如果不对“正确”的纵横比做出假设,我们就无法将超过 45 度的倾斜固定到任一侧。 如果不假设内容块的纵横比,我们必须假设内容向任一侧倾斜小于 45 度,以便继续。这种假设对于仅打算纵向的扫描非常有效,但对于使用横向扫描的许多文档中只有一页的文档会中断。我还没有解决这个问题。

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

一旦我们得到这个检测并计算出的“公共旋转角度”值,我们就可以使用它来修复倾斜,只需将该值直接传递给cv2.getRotationMatrix2D() 函数。注意:计算出的现有“共同旋转角度”对于逆时针倾斜为负,对于顺时针倾斜为正,这是非常常见的日常惯例。但是,如果我们将cv2.getRotationMatrix2D()angle 参数视为“要应用的校正角度”(我认为这是意图),那么符号约定就是OPPOSITE。因此,如果我们想在输出图像中看到它被抵消,我们需要按原样传递检测和计算的“共同旋转角度”值,这得到了我执行的许多测试的支持。 这是来自OpenCV documentation 的angle 参数的直接引用:

以度为单位的旋转角度。正值表示逆时针旋转(假设坐标原点为左上角)。

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

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

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

应用阈值处理/分级/变形/腐蚀操作,最后应用计数,以定位和勾勒出可能包含相关内容而非噪声的图像区域。 获取 每个 轮廓的 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

【讨论】:

【参考方案3】:

这是用于确定偏斜的投影轮廓方法的实现。在获得二值图像后,想法是将图像旋转到各个角度,并在每次迭代中生成像素的直方图。为了确定倾斜角度,我们比较了峰值之间的最大差异,并使用这个倾斜角度,旋转图像来纠正倾斜


左(原始),右(更正)

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)
        score = np.sum((histogram[1:] - histogram[:-1]) ** 2)
        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()

【讨论】:

一开始,这个方法不起作用,因为我已经在处理二进制图像,所以必须调整一些代码,我还发现将 delta 值设置为 0.05 在以下方面效果最好计算时间和产品质量。 @nathancy 在这方面做得很好。 我已经尝试过了.. 效果不佳.. 也尝试了从低到高的 delta 值.. 大多数时候它实际上是在给图像添加倾斜 这只是给图像添加倾斜。 @pylearner 这对我有用,我认为您必须确保您尝试对其执行倾斜校正的对象在阈值图像中的前景为白色。 avilpage.com/2016/11/detect-correct-skew-images-python.html

以上是关于用于 OCR 的 Python OpenCV 偏斜校正的主要内容,如果未能解决你的问题,请参考以下文章

(OpenCV/Python)实现OCR银行票据

用于 OCR 的 OpenCv pytesseract

Python + OpenCV:OCR 图像分割

Python/OpenCV - 基于机器学习的 OCR(图像到文本)

使用Python,OpenCV进行卡类型及16位卡号数字的OCR

使用Python,OpenCV进行Tesseract-OCR绑定及识别