如何使用 Python OpenCV 在 OCR 之前清理图像?

How to clean images before OCR with Python OpenCV?

我一直在尝试为 OCR 清除图像:(行)

有时我需要删除这些行以进一步处理图像,我已经非常接近了,但很多时候阈值从文本中带走了太多:

    copy = img.copy()
    blur = cv2.GaussianBlur(copy, (9,9), 0)
    thresh = cv2.adaptiveThreshold(blur,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV,11,30)

    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9,9))
    dilate = cv2.dilate(thresh, kernel, iterations=2)

    cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if len(cnts) == 2 else cnts[1]

    for c in cnts:
        area = cv2.contourArea(c)
        if area > 300:
            x,y,w,h = cv2.boundingRect(c)
            cv2.rectangle(copy, (x, y), (x + w, y + h), (36,255,12), 3)

编辑:此外,如果字体发生变化,使用常量数字将不起作用。 有没有通用的方法来做到这一点?

这是一个主意。我们把这个问题分成几个步骤:

  1. 确定平均矩形轮廓面积。我们设置阈值然后使用边界矩形区域找到轮廓并进行过滤轮廓。我们这样做的原因是因为观察到任何典型字符只会这么大,而大噪声会跨越更大的矩形区域。然后我们确定平均面积。

  2. 移除大的异常值轮廓。 我们再次遍历轮廓并移除大轮廓,如果它们比平均轮廓面积大 5x填充轮廓。我们没有使用固定的阈值区域,而是使用这个动态阈值来提高鲁棒性。

  3. 用垂直内核扩展连接字符。这个想法是利用字符在列中对齐的观察结果。通过使用垂直内核进行扩张,我们将文本连接在一起,因此此组合轮廓中不会包含噪声。

  4. 去除小噪声。现在要保留的文本已连接,我们找到轮廓并删除小于 4x 平均轮廓面积的任何轮廓。

  5. 按位与重建图像。由于我们只有想要的轮廓保留在我们的蒙版上,所以我们按位并保留文本并得到我们的结果。


这是该过程的可视化:

我们Otsu's threshold to obtain a binary image then find contours to determine the average rectangular contour area. From here we remove the large outlier contours highlighted in green by filling contours

接下来我们构造一个vertical kernel and dilate来连接字符。此步骤连接所有需要保留的文本并将噪声隔离为单个斑点。

现在我们找到轮廓并使用contour area过滤以去除小噪声

这里是所有移除的噪声粒子,以绿色突出显示

结果

代码

import cv2

# Load image, grayscale, and Otsu's threshold
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# Determine average contour area
average_area = [] 
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    x,y,w,h = cv2.boundingRect(c)
    area = w * h
    average_area.append(area)

average = sum(average_area) / len(average_area)

# Remove large lines if contour area is 5x bigger then average contour area
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    x,y,w,h = cv2.boundingRect(c)
    area = w * h
    if area > average * 5:  
        cv2.drawContours(thresh, [c], -1, (0,0,0), -1)

# Dilate with vertical kernel to connect characters
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2,5))
dilate = cv2.dilate(thresh, kernel, iterations=3)

# Remove small noise if contour area is smaller than 4x average
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    area = cv2.contourArea(c)
    if area < average * 4:
        cv2.drawContours(dilate, [c], -1, (0,0,0), -1)

# Bitwise mask with input image
result = cv2.bitwise_and(image, image, mask=dilate)
result[dilate==0] = (255,255,255)

cv2.imshow('result', result)
cv2.imshow('dilate', dilate)
cv2.imshow('thresh', thresh)
cv2.waitKey()

注意:传统的图像处理仅限于阈值化、形态学操作和轮廓过滤(轮廓近似、面积、纵横比或斑点检测)。由于输入图像会根据字符文本大小而变化,因此找到一个单一的解决方案非常困难。您可能希望通过 machine/deep 学习动态解决方案来训练您自己的分类器。