使用OpenCV在Python中进行适当的图像阈值处理以准备进行OCR。

7
我是一位初学者,对opencv和python都很陌生。
我有这张图片:

original bmp 24bit image

我希望能够适当使用阈值处理来保留仅有的6个数字。
更大的问题是,我打算尝试分别对每个数字执行手动OCR,使用k最近邻算法在每个数字级别上进行(kNearest.findNearest)。
问题在于我无法清理数字,特别是“7”数字,其上有这种蓝色水印通过。
到目前为止我尝试过以下步骤:
我正在从磁盘读取图像。
# IMREAD_UNCHANGED is -1
image = cv2.imread(sys.argv[1], cv2.IMREAD_UNCHANGED)

然后我只保留蓝色通道,以消除数字“7”周围的蓝色水印,从而有效地将其转换为单通道图像。
image = image[:,:,0] 
# openned with -1 which means as is, 
# so the blue channel is the first in BGR

single channel - red only - image

然后我将其乘以一定倍数,以增加数字和背景之间的对比度:
image = cv2.multiply(image, 1.5)

multiplied image to increase contrast

最后我执行二值加Otsu阈值处理:
_,thressed1 = cv2.threshold(image,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)

binary Oahu thresholded image

正如您所看到的,最终结果相当不错,除了数字“7”保留了很多噪音。

如何改善最终结果?如果可能,请提供图像示例结果,这比仅有代码片段更易理解。


因此,红色通道是RGB中的第一个。OpenCV的imread默认使用BGR顺序,因此实际上你使用的是蓝色通道,而不是红色。 - Dan Mašek
你是对的,只使用蓝色通道似乎更有效地消除蓝色水印。我已经在代码中更正了注释。谢谢。 - Nikolas Kazepis
4个回答

10
你可以尝试对灰度(模糊)图像使用不同的内核(如3、51)进行medianBlur,将得到的模糊结果相除,然后进行阈值处理。类似这样的操作:

enter image description here


#!/usr/bin/python3
# 2018/09/23 17:29 (CST) 
# (中秋节快乐)
# (Happy Mid-Autumn Festival)

import cv2 
import numpy as np 

fname = "color.png"
bgray = cv2.imread(fname)[...,0]

blured1 = cv2.medianBlur(bgray,3)
blured2 = cv2.medianBlur(bgray,51)
divided = np.ma.divide(blured1, blured2).data
normed = np.uint8(255*divided/divided.max())
th, threshed = cv2.threshold(normed, 100, 255, cv2.THRESH_OTSU)

dst = np.vstack((bgray, blured1, blured2, normed, threshed)) 
cv2.imwrite("dst.png", dst)

结果:

在此输入图片描述

(注:此内容为HTML代码,已翻译为中文)

1
优秀的结果和简单的方法。+1 - alkasm
@Silencer,您能否详细解释一下“分割模糊结果”的含义?可以提供一段代码片段吗?我对opencv的概念还不是很熟悉。您是将模糊结果与原始图像相除吗?您会同时分割多于两个图像吗?如何操作?非常抱歉问这些愚蠢的问题。先谢谢您了! - Nikolas Kazepis
消音器确实解决了一些更难的情况。非常感谢!我已经尝试了很多不同的方法(断断续续地几个月),例如Bradley Roth阈值处理,但都没有成功或者结果比你的更糟糕,远远不如你的好。因此,这是真诚的感谢,你是MVP!也要感谢@Yves,因为从我看到的来看,他建议了相同的方法,但是Silencer先生,您花时间提供了更好的结果和代码片段。对于所有这些,我感激不尽!+1! - Nikolas Kazepis

1

为什么不直接将图像中高于某个阈值的值保留下来呢?

例如这样:

import cv2
import numpy as np

img = cv2.imread("./a.png")[:,:,0]  # the last readable image

new_img = []
for line in img:
    new_img.append(np.array(list(map(lambda x: 0 if x < 100 else 255, line))))

new_img = np.array(list(map(lambda x: np.array(x), new_img)))

cv2.imwrite("./b.png", new_img) 

看起来很棒:

你可以调整阈值并获得更好的结果。


如果解决方案需要自动化(通常情况下),这种方法就无法使用。而且有很多情况全局阈值并不适用。 - user1196549
那是一个很好的观点。然而,我检查了一下,即使将图像再次放大2倍,也会产生相当有利的结果:https://toast-for.life/iEm9O.png - Plutoberth
如果解决方案需要自动化(通常情况下),这种方法就不能使用。而且有很多情况下,全局阈值并不适用。 - user1196549
那么,你认为有什么更好的想法呢? - Plutoberth
@Yves Daoust,没错,你的确提供了更好的解决方案。我可能只应该坚持自己百分之百自信的线程。 - Plutoberth
@Plutoberth,不幸的是,这需要一个自动化的过程,而且在调整阈值时,虽然它可以改善一些图像,但对于其他一些图像来说,情况会变得更糟。也许我今晚可以发布更多的例子,以便你了解我的意思。无论如何,谢谢! - Nikolas Kazepis

1

完全消除烦人的印章似乎并不容易。

您可以通过以下方式使背景强度变平:

  • 计算低通图像(高斯滤波器,形态学闭运算);滤波器大小应稍大于字符大小;

  • 将原始图像除以低通图像。

然后您可以使用Otsu。

enter image description here

如您所见,结果并不完美。

请问您能否提供一个代码片段来生成低通图像,然后再演示如何进行除法操作?这个答案与@Silencer的回答有关吗? - Nikolas Kazepis

0

我在蓝色通道上尝试了与Yves略有不同的方法:蓝色通道

  • 应用中值滤波器(r=2):

Filtered image

  • 使用边缘检测(例如Sobel算子):

Edges detected

  • 自动阈值化(Otsu)

Thresholded image

  • 图片的关闭

Closed image

这种方法似乎可以使输出结果更加清晰。然而,我们需要解决数字中的空洞问题。可以通过检测被白色像素完全包围的黑色轮廓,并将其填充为白色来解决这个问题。


这个方法可能可行,但似乎比上面的解决方案更复杂,所以也许我会最后尝试这个。如果有代码片段就更好了。谢谢! - Nikolas Kazepis
Sobel 算子和 Otsu 阈值可能被 Canny 边缘检测所替代。 - Slawomir Orlowski

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