如何使用Python OpenCV从图像中去除噪声并进行OCR?

4
我有一些包含数字的图像子集。每个子集都由Tesseract进行OCR阅读。不幸的是,对于一些图像,从原始图像中裁剪出来的部分并不理想。

enter image description here

因此,图像顶部和底部的一些工件/残留物会妨碍Tesseract识别图像上的字符。因此,我希望摆脱这些工件,并获得类似的结果:

enter image description here

首先,我考虑了一个简单的方法:将第一行像素设置为参考。如果在x轴上发现了伪影(即,如果图像被二值化,则为白色像素),则沿y轴删除它,直到下一个黑色像素。这种方法的代码如下:

import cv2
inp = cv2.imread("testing_file.tif")
inp = cv2.cvtColor(inp, cv2.COLOR_BGR2GRAY)
_,inp = cv2.threshold(inp, 150, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

ax = inp.shape[1]
ay = inp.shape[0]

out = inp.copy()
for i in range(ax):
    j = 0
    while j in range(ay):
        if out[j,i] == 255:
            out[j,i] = 0
        else:
            break
        j+=1

out = cv2.bitwise_not(out)    
cv2.imwrite('output.png',out)

但结果一点也不好:

enter image description here

然后我偶然发现了来自scipy的flood_fill函数(这里),但发现它太耗时间且仍不够高效。类似的问题在Stack Overflow上有人问过(这里),但并没有提供太多帮助。也许可以考虑k近邻方法?我还发现,一些将相邻像素根据某些标准合并的方法被称为生长方法,其中单链接是最常见的(这里)。
你会推荐什么方法来消除上下的伪影?
1个回答

4

以下是一个简单的方法:

  • 将图像转换为灰度
  • 使用Otsu阈值得到二进制图像
  • 创建特殊的水平核并进行膨胀处理
  • 检测水平线条,按最大轮廓进行排序,并在掩模上绘制
  • 进行按位与操作

将图像转换为灰度后,我们使用Otsu阈值得到二进制图像。

enter image description here

# Read in image, convert to 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]

接下来,我们创建一个长的水平核并进行膨胀以连接数字。

enter image description here

# Create special horizontal kernel and dilate 
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (70,1))
dilate = cv2.dilate(thresh, horizontal_kernel, iterations=1)

我们从这里检测水平线并筛选出最大轮廓。想法是最大轮廓将是数字的中间部分,数字都是“完整”的。任何较小的轮廓都将是不完整或被切断的数字,因此我们在这里过滤掉它们。我们将这个最大轮廓绘制到一个遮罩上。

enter image description here

# Detect horizontal lines, sort for largest contour, and draw on mask
mask = np.zeros(image.shape, dtype=np.uint8)
detected_lines = cv2.morphologyEx(dilate, cv2.MORPH_OPEN, horizontal_kernel, iterations=1)
cnts = cv2.findContours(detected_lines, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
for c in cnts:
    cv2.drawContours(mask, [c], -1, (255,255,255), -1)
    break

现在,我们已经有了期望数字的轮廓,我们只需将其与原始图像进行按位与运算,并将背景涂成白色,即可得到我们想要的结果。 enter image description here
# Bitwise-and to get result and color background white
mask = cv2.cvtColor(mask,cv2.COLOR_BGR2GRAY)
result = cv2.bitwise_and(image,image,mask=mask)
result[mask==0] = (255,255,255)

完整代码以供参考

import cv2
import numpy as np

# Read in image, convert to 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]

# Create special horizontal kernel and dilate 
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (70,1))
dilate = cv2.dilate(thresh, horizontal_kernel, iterations=1)

# Detect horizontal lines, sort for largest contour, and draw on mask
mask = np.zeros(image.shape, dtype=np.uint8)
detected_lines = cv2.morphologyEx(dilate, cv2.MORPH_OPEN, horizontal_kernel, iterations=1)
cnts = cv2.findContours(detected_lines, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
for c in cnts:
    cv2.drawContours(mask, [c], -1, (255,255,255), -1)
    break

# Bitwise-and to get result and color background white
mask = cv2.cvtColor(mask,cv2.COLOR_BGR2GRAY)
result = cv2.bitwise_and(image,image,mask=mask)
result[mask==0] = (255,255,255)

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

它在其他子集上也能产生出色的结果,考虑最大轮廓的方法非常直接,非常感谢! - florian
很好,我不确定它的稳健性,因为将轮廓连接在一起的步骤可能会意外地将顶部或底部字母与中间部分连接在一起。如果发生这种情况,您可以修改内核大小和迭代次数。 - nathancy
我用大小为(120,1)的核心,并应用闭形态变换而不是建议的膨胀和开放,得到了更强大的结果。再次感谢。 - florian

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