Python OpenCV斜率校正用于OCR

13

目前我正在从标签上读取文字的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

修正前和修正后的标签示例图片

修正前 -> 修正后

如果有人能帮我解决这个问题,将非常有帮助。


你可以尝试从文字周围的轮廓框中获取角度。请参阅 https://www.pyimagesearch.com/2017/02/20/text-skew-correction-opencv-python/ 或搜索谷歌,有很多相关链接。 - fmw42
@fmw42 我已经尝试过这种方法,但是它并没有起作用,而且一直将图像调整了0度。你提供的方法只适用于完美的文本图像,不幸的是,我正在处理的图像远非完美,因此该方法无法正确识别倾斜角度。 - Peter S
你有查看过谷歌搜索中的其他方法吗?你尝试从每个单词的轮廓中获取边界框并查看角度分布或获取平均值了吗? - fmw42
嘿,@PeterS,感谢你的问题。我也正在尝试使用OpenCV实现OCR,但是遇到了一些困难。因此,我想知道是否可以分享你的OCR代码,这样我就可以参考一下。这对我来说将是非常有帮助的。提前感谢你 :) - harsh pamnani
4个回答

24
这是一个投影轮廓法算法的实现,用于估计倾斜角度。将各种角度点投影到累加器数组中,其中倾斜角度可以定义为在搜索间隔内投影角度最大化对齐的角度。思路是在各个角度上旋转图像,并为每次迭代生成像素直方图。为了确定倾斜角度,我们比较峰值之间的最大差异,并使用这个倾斜角度旋转图像以修正倾斜。

原始的 -> 改正后的

偏斜角:-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
起初,这个方法并没有奏效,因为我已经在处理二进制图像,所以必须调整部分代码。我还发现将增量值设为0.05在计算时间和产品质量方面表现最佳。@nathancy 在这方面做得非常好。 - Peter S
1
我已经尝试过了,结果不太好。我也试过非常低到非常高的delta值。大多数情况下它实际上会给图像添加偏斜。 - Sandeep Bhutani
这只是给图像添加倾斜。 - pylearner
2
@pylearner,这对我有用,我认为你必须确保你要执行斜率校正的对象在阈值图像中作为白色处于前景。 - coffeewin
2
https://avilpage.com/2016/11/detect-correct-skew-images-python.html - Tofiq
显示剩余4条评论

5
为了补充@nathancy的回答,对于Windows用户,如果您遇到附加偏差,请在创建numpy数组时添加dtype=float。由于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

假设:

  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聚类算法,将“核心”像素传递给上述解决方案,它们都工作得相当不错。
  • 我认为cv2.minAreaRect()返回的角度值的解释在那里并不清楚,并且代码具有用于检测和校正的相同变量,这更加令人困惑。我使用单独的变量来使其更清晰,我的前两行代码的解释如下。
  • 我必须尊重地反对我们需要在将检测到的旋转角度(教程中第38行和43行)传递给cv2.getRotationMatrix2D()函数之前“取反”,这基于OpenCV文档和我的测试。下面将更详细地讨论。

解决方案说明:

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

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

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

这对我们有两个含义:

  1. 如果不对“正确”的宽高比做出假设,我们无法修正向两侧倾斜超过45度的内容块。
  2. 如果不对内容块的宽高比做出假设,我们必须假设内容向左或向右倾斜的角度小于45度,才能继续进行。这个假设对于只有纵向扫描的文档效果非常好,但对于使用横向扫描扫描其中一页的文档则无法奏效。我还没有解决这个问题。

因此,假设(1)不对内容块的宽高比做出假设,(2)假设倾斜范围为[-45:45],我们可以通过将“高度”旋转值加上90度(如果其小于-45.0)来获得在矩形坐标系中相对于高度和宽度的共同倾斜角度(在[-45:45]范围内)。

一旦我们得到了检测到并计算出的“共同旋转角度”值,我们就可以将其直接传递给cv2.getRotationMatrix2D()函数以修正倾斜。
注意:计算出的现有“共同旋转角度”对于逆时针倾斜是负数,对于顺时针倾斜是正数,这是非常常见的日常约定。然而,如果我们认为cv2.getRotationMatrix2D()函数的angle参数是“要应用的校正角度”(我认为这是意图),那么符号约定是相反的。因此,如果我们想在输出图像中看到它被抵消,则需要按原样传递检测到并计算出的“共同旋转角度”值,这得到了我进行的许多测试的支持。
以下是来自OpenCV文档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


0

补充@full_pr0的答案,您可以通过堆叠图像来加速计算速度4-5倍:

import cv2
import numpy as np

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

def determine_score(arr):
     histogram = np.sum(arr, axis = 2, dtype = float)
     score = np.sum((histogram[..., 1 :] - histogram[..., : -1]) ** 2, \
        axis = 1, dtype = float)
     return score

def correct_skew(image, delta = 0.1, limit = 5):
     thresh = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY_INV + \ 
        cv2.THRESH_OTSU)[1]
     angles = np.arange(-limit, limit + delta, delta)
     img_stack = np.stack([rotate_image(thresh, angle) for angle \ 
        in angles], axis = 0)
     scores = determine_score(img_stack)
     best_angle = angles[np.argmax(scores)]
     corrected = rotate_image(image, best_angle)
     return best_angle, corrected

img_path = 'test.jpg'
img = cv2.imread(img_path, 0)
angle, corrected = correct_skew(img)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接