【问题标题】:How to remove noise around numbers using cv2如何使用 cv2 去除数字周围的噪音
【发布时间】:2021-07-28 13:52:20
【问题描述】:

我正在尝试使用 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)

我想要的是始终能够识别数字和小数点分隔符。哪些图像预处理有助于在图像上获得一致的结果,如下所示?

【问题讨论】:

  • 你可以研究形态,尤其是开放
  • This 可能会有所帮助。

标签: python opencv tesseract cv2 python-tesseract


【解决方案1】:

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

如果你放大,你会发现背面的图案只有 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,但你可以尝试一下。这些图案在侧面去掉了一些像素,只匹配左侧的一个像素,所以我们填充两次(一个有额外的)以命中前 3 个图案。最后一个是单个像素所以不需要它

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,0017.160.000,00 变为160.000,00。但我们越来越近了!
  • 这看起来很棒,我明天尽快试试,你的输出看起来非常干净!感谢@Eumel 的撰写以及您的努力和时间,这看起来很有希望,我希望它能产生预期的结果,谢谢!
  • 它通过了 6/7 测试,只有 1 位错误!这是我见过的最高准确度!它读错的唯一数字是 3.114.758,它读为 3.414.758,00。这仍然是非常好的准确性!
  • 这已被证明是一种稳定的方法,可以返回一致的结果,我添加了 Guassianblur 和 otsu 脱粒,并且对于不同图像的准确性非常好!感谢您向我展示可以通过这种方式完成!
【解决方案2】:

您可以通过在频域而不是空间域中进行过滤来使用稍微复杂的方法找到解决方案。根据 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()

输出:

中值模糊实现:

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

输出:

最终编辑:

因此使用 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 没问题!如果效果不好,请告诉我。解决这样的问题还有很多技巧,有些可能效果更好。唯一棘手的部分是我的机器上没有 tesseract 设置,所以我不能轻易提供有关性能的反馈。
  • 我尝试了你的方法,发现它并没有那么好,(不如模糊和脱粒一致),对我来说,问题似乎是转换后残留的斑点背景干扰了这个数字例如,“0”在这个过程中会被侵蚀,使得 tesseract 难以读取。我的经验是,当文本更厚而不是更薄时,tesseract 的准确性更高
  • 在我的回答中添加了一个额外的方法。尝试简单的中值模糊。虽然我们可能需要对第一个数字 1 做点什么,但如果输出的其余部分看起来不错,我们可以修复它。
  • @MisterButter 另外,尝试用242 替换20 阈值。我看到了更多的噪音,但在该阈值处的结果更加明显
猜你喜欢
  • 1970-01-01
  • 2015-07-10
  • 2020-03-31
  • 2020-07-24
  • 1970-01-01
  • 1970-01-01
  • 2011-11-10
  • 1970-01-01
  • 2017-02-27
相关资源
最近更新 更多