从图像中删除字母伪影

Posted

技术标签:

【中文标题】从图像中删除字母伪影【英文标题】:How to remove noise artifacts from an image for OCR with Python OpenCV? 【发布时间】:2020-02-06 06:14:33 【问题描述】:

我有包含数字的图像子集。 Tesseract 为 OCR 读取每个子集。不幸的是,对于某些图像,原始图像的裁剪不是最佳的。

因此,图像顶部和底部存在一些伪影/残留物,并妨碍 Tesseract 识别图像上的字符。然后我想摆脱这些工件并得到类似的结果:

首先我考虑了一种简单的方法:我将第一行像素设置为参考:如果沿 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)

但结果一点都不好:

然后我偶然发现了 scipy (here) 的 flood_fill 函数,但发现它太耗时而且效率不高。在 SO here 上提出了类似的问题,但没有太大帮助。也许可以考虑k-最近邻方法?我还发现在某些条件下合并相邻像素的方法称为增长方法,其中单链接是最常见的(here)。

你会推荐什么来移除上下伪影?

【问题讨论】:

【参考方案1】:

这是一个简单的方法:

将图像转换为灰度 Otsu 获取二值图像的阈值 Cerate 特殊水平内核和扩张 检测水平线,排序最大轮廓,然后在蒙版上绘制 按位与

转换成灰度后,我们用Otsu的阈值得到二值图像

# 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)

完整的代码

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()

【讨论】:

它在其他子集上也给出了很好的结果,考虑最大轮廓的方法非常简单,非常感谢! 很好,我不确定它有多健壮,因为将轮廓连接在一起的步骤可能会意外地将顶部或底部字母与中间部分连接在一起。如果确实发生这种情况,您可以修改内核大小和迭代次数 仅供参考,我使用大小为 (120,1) 的内核,并通过应用闭合形态学变换而不是建议的扩张和开放来获得更稳健的结果。不过,再次感谢。

以上是关于从图像中删除字母伪影的主要内容,如果未能解决你的问题,请参考以下文章

如何从带有文本的图像中获取每个字母的图像[关闭]

opencv从噪声很大的图像中剪切出字母(java)

从 numpy 数组中删除类似方波的伪影

sed:从文件中删除字母数字单词

从单词中删除一个字母[重复]

从 Pandas 列中删除缩写(字母+点的组合)