如何使用OpenCV去除数字周围的噪声

4

我正在尝试使用Tesseract-OCR获取下面图片的读数,但在检测到斑点背景时无法获得一致的结果。 我在我的pytesseract上有以下配置:

CONFIG = f"—psm 6 -c tessedit_char_whitelist = 01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZÅÄabcdefghijklmnopqrstuvwxyzåäö.,-"

我还尝试了以下图片预处理,取得了一些良好的结果,但仍然不完美。

blur = cv2.blur(img,(4,4))
(T, threshInv) = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

我希望能够一致地识别数字和小数点。哪些图像预处理技术可以帮助在以下图像中获得一致的结果?
以下是需要翻译的内容:

enter image description here enter image description here enter image description here enter image description here enter image description here enter image description here enter image description here


你可以研究形态学,特别是开运算。 - Eumel
这个可能会有帮助。 - beaker
2个回答

5

如果在频域而不是空间域中进行滤波,您可能会找到一种使用稍微复杂的方法来解决问题。阈值可能需要进行一些调整,这取决于tesseract在输出图像方面的表现。

实现:

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('C:\\Test\\number.jpg', cv2.IMREAD_GRAYSCALE)

# Perform 2D FFT
f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)
magnitude_spectrum = 20*np.log(np.abs(fshift))

# Squash all of the frequency magnitudes above a threshold
for idx, x in np.ndenumerate(magnitude_spectrum):
    if x > 195:
        fshift[idx] = 0

# Inverse FFT back into the real-spatial-domain
f_ishift = np.fft.ifftshift(fshift)
img_back = np.fft.ifft2(f_ishift)
img_back = np.real(img_back)
img_back = cv2.normalize(img_back, None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_32F)
out_img = np.copy(img)

# Use the inverted FFT image to keep only the black values below a threshold
for idx, x in np.ndenumerate(img_back):
    if x < 100:
        out_img[idx] = 0
    else:
        out_img[idx] = 255

plt.subplot(131),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(132),plt.imshow(img_back, cmap = 'gray')
plt.title('Reversed FFT'), plt.xticks([]), plt.yticks([])
plt.subplot(133),plt.imshow(out_img, cmap = 'gray')
plt.title('Output'), plt.xticks([]), plt.yticks([])
plt.show()

输出:

output

中值模糊实现:

import cv2
import numpy as np

img = cv2.imread('C:\\Test\\number.jpg', cv2.IMREAD_GRAYSCALE)
blur = cv2.medianBlur(img, 3)

for idx, x in np.ndenumerate(blur):
    if x < 20:
        blur[idx] = 0

cv2.imshow("Test", blur)
cv2.waitKey()

输出:

enter image description here

最终编辑:

因此,使用Eumel的解决方案,并将此代码与其底部的代码组合起来,可以得到100%成功的结果:

img[pat_thresh_1==1] = 255
img[pat_thresh_15==1] = 255
img[pat_thresh_2==1] = 255
img[pat_thresh_25==1] = 255
img[pat_thresh_3==1] = 255
img[pat_thresh_35==1] = 255
img[pat_thresh_4==1] = 255

# Eumel's code above this line

img = cv2.erode(img, np.ones((3,3)))

cv2.imwrite("out.png", img)
cv2.imshow("Test", img)

print(pytesseract.image_to_string(Image.open("out.png"), lang='eng', config='--psm 10 --oem 3 -c tessedit_char_whitelist=0123456789.,'))

输出图像示例:

输入图像描述 输入图像描述 输入图像描述 输入图像描述 输入图像描述 输入图像描述 输入图像描述 输入图像描述

白名单识别 tesseract 字符似乎也对防止误识别有很大帮助。


谢谢,我会尝试并回复结果!由于我对此几乎一无所知,我非常感激这个答案,感谢您花费时间和精力! - MisterButter
@MisterButter 没问题!如果它表现不佳,请告诉我。解决这类问题还有许多技巧,其中一些可能效果更好。唯一棘手的部分是我的机器上没有安装tesseract,因此我无法立即提供性能反馈。 - Abstract
我尝试了你的方法,发现它的表现并不好(比模糊和二值化方法的一致性差),问题似乎在于转换后仍然存在的斑点背景干扰了识别,数字如“0”在过程中被侵蚀,导致 Tesseract 难以正确识别。根据我的经验,Tesseract 在文本较粗时的准确度要高于文本较细的情况。 - MisterButter
@MisterButter 另外,请尝试将“20”阈值替换为“242”。我看到了更多的噪音,但在该阈值下得到了更明显的结果。 - Abstract
看起来在 Eumel 的回答和我的(非常小的)补充之间思考,你已经有了一个很好的解决方案。 - Abstract
显示剩余5条评论

5

这是一项挑战,但我认为我有一个有趣的方法:模式匹配

如果你放大看,你会发现背景中的图案只有4个可能的点,一个完整的像素,一个双倍的完整像素和一个带有中等左侧或右侧的双倍像素。所以我从这张价值17.160.000,00的图片中抓取了这4种图案并开始工作。为了方便加载,我直接在使用时获取它们。

img = cv2.imread('C:/Users/***/17.jpg', cv2.IMREAD_GRAYSCALE)

pattern_1 = img[2:5,1:5]
pattern_2 = img[6:9,5:9]
pattern_3 = img[6:9,11:15]
pattern_4 = img[9:12,22:26]

# just to show it carries over to other pics ;)
img = cv2.imread('C:/Users/****/6.jpg', cv2.IMREAD_GRAYSCALE)

实际模式匹配

接下来,我们匹配所有的模式和阈值,以找到所有的出现次数。我使用了0.7的阈值,但你可以尝试一些不同的值。这些模式会在两边取走一些像素,并且只匹配左侧的一个像素,因此我们需要填充两次(其中一次需要额外填充),以满足前三个模式。最后一个模式只有一个像素,所以不需要填充。

res_1 = cv2.matchTemplate(img,pattern_1,cv2.TM_CCOEFF_NORMED )
thresh_1 = cv2.threshold(res_1,0.7,1,cv2.THRESH_BINARY)[1].astype(np.uint8)
pat_thresh_1 = np.pad(thresh_1,((1,1),(1,2)),'constant')
pat_thresh_15 = np.pad(thresh_1,((1,1),(2,1)), 'constant')
res_2 = cv2.matchTemplate(img,pattern_2,cv2.TM_CCOEFF_NORMED )
thresh_2 = cv2.threshold(res_2,0.7,1,cv2.THRESH_BINARY)[1].astype(np.uint8)
pat_thresh_2 = np.pad(thresh_2,((1,1),(1,2)),'constant')
pat_thresh_25 = np.pad(thresh_2,((1,1),(2,1)), 'constant')
res_3 = cv2.matchTemplate(img,pattern_3,cv2.TM_CCOEFF_NORMED )
thresh_3 = cv2.threshold(res_3,0.7,1,cv2.THRESH_BINARY)[1].astype(np.uint8)
pat_thresh_3 = np.pad(thresh_3,((1,1),(1,2)),'constant')
pat_thresh_35 = np.pad(thresh_3,((1,1),(2,1)), 'constant')
res_4 = cv2.matchTemplate(img,pattern_4,cv2.TM_CCOEFF_NORMED )
thresh_4 = cv2.threshold(res_4,0.7,1,cv2.THRESH_BINARY)[1].astype(np.uint8)
pat_thresh_4 = np.pad(thresh_4,((1,1),(1,2)),'constant')

编辑图片

现在唯一剩下的事情就是从图像中删除所有匹配项。由于我们有一个主要是白色背景,因此我们将它们设置为255以融入其中。

img[pat_thresh_1==1] = 255
img[pat_thresh_15==1] = 255
img[pat_thresh_2==1] = 255
img[pat_thresh_25==1] = 255
img[pat_thresh_3==1] = 255
img[pat_thresh_35==1] = 255
img[pat_thresh_4==1] = 255

输出

无背景

编辑:

请查看Abstracts的答案以改进这个输出和进行Tesseract微调。


哈哈,我在我的端上用 Tesseract 运行了一下,它仍然很难识别。数字 3.114.758,00 的图像输出为 114.758,00,而 17.160.000,00 变成了 160.000,00。但我们已经接近成功了! - Abstract
这看起来很棒,我明天会尽快试一下,你的输出看起来非常干净!感谢@Eumel的撰写和付出的努力和时间,这看起来真的很有前途,我希望它能产生预期的结果,谢谢! - MisterButter
它通过了6/7个测试,只有1个数字错误!这是我见过的最高准确度!它唯一读错的数字是3.114.758,它将其读成了3.414.758,00。这仍然是极高的准确度! - MisterButter
这已被证明是一种稳定的方法,可以返回一致的结果。我添加了一个高斯模糊和大津阈值处理,对于不同的图像,准确性非常好!谢谢你向我展示了这种方法! - MisterButter

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