Python Opencv:为文本检测过滤图像

4

未经滤波的图像

我有一组图像需要去噪以便进行OCR:

我试图从图像中读取1973年的信息。

我尝试过:

import cv2,numpy as np


img=cv2.imread('uxWbP.png',0)
img = cv2.resize(img, (0, 0), fx=2, fy=2)
copy_img=np.copy(img)
#adaptive threshold as the image has different lighting conditions in different areas
thresh = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 21, 2)

contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
#kill small contours
for i_cnt, cnt in enumerate(sorted(contours, key=lambda x: cv2.boundingRect(x)[0])):
    _area = cv2.contourArea(cnt)
    x, y, w, h = cv2.boundingRect(cnt)
    x_y_area = w * h
    if 10000 < x_y_area and x_y_area < 400000:
        pass
        # cv2.rectangle(copy_img, (x, y), (x + w, y + h), (255, 0, 255), 2)
        # cv2.putText(copy_img, str(int(x_y_area)) + ' , ' + str(w) + ' , ' + str(h), (x, y + 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
        # cv2.drawContours(copy_img, [cnt], 0, (0, 255, 0), 1)
    elif 10000 > x_y_area:
        #write over small contours
        cv2.drawContours(thresh, [cnt], -1, 255, -1)

cv2.imshow('img',copy_img)
cv2.imshow('thresh',thresh)
cv2.waitKey(0)

这将显著改善图像,使其更清晰:

filtered image

对于如何足够过滤此图像或从一开始就进行完全更改的任何建议,以便我可以运行OCR或一些ML检测脚本来检测数字。我希望拆分数字以进行检测,但也可以接受其他方法。


2
啊,淘气的家伙。想要破解那些验证码吗?它们之所以难以破解是有原因的。正如你所看到的,文本分割并不是一件简单的事情。在你的特定图像中,有很多高频噪声,你可以先尝试一些频率过滤,看看会得到什么结果。 - stateMachine
1
由于他们这样做是为了保护他们的带宽和/或业务。但是,这个验证码并没有保护任何类似的东西。你只需要相信我的话。任何好的服务器都会使用像谷歌的reCAPTCHA这样的东西,它是机器学习图像识别,可以识别像人行道这样的东西,这不是这里的问题。 - ohyesyoucan
1
能否发一下原始版本的验证码?我想尝试高斯模糊,然后做自适应阈值轮廓处理。但是,即使我试图用小的彩色验证码重现您的步骤,我也会得到 cv2.error: OpenCV(4.2.0) C:\projects\opencv-python\opencv\modules\imgproc\src\thresh.cpp:1647: error: (-215:Assertion failed) src.type() == CV_8UC1 in function 'cv::adaptiveThreshold' 的错误提示。 - bballdave025
1
等等……你是怎么读入这张图片的?我试过 img = cv2.imread("uxWbp.png") 。然而,当我按照你的步骤进行操作并使用你的不同值之后,我仍然得到了同样的错误。抱歉,我对音频处理更熟悉,但我喜欢玩OCR(光学字符识别)。所有这些都是为了说我需要你的帮助来重现你的原始结果。哦,你刚刚贴出了代码。我可能明天会更详细地看一下。希望你那时已经解决了问题,我可以从中学习到一些东西。 - bballdave025
2
@bballdave025,现在看代码。我添加了读取和调整大小的功能,应该能让你得到接近黑白图像的结果。 - ohyesyoucan
显示剩余7条评论
2个回答

3

我的第一个想法是使用高斯模糊来实现“非锐化滤镜”。(我认为我的第二个想法更好;它将这种模糊和腐蚀/膨胀游戏相结合。我将其作为单独的答案发布,因为我认为这是一种不同的策略,值得这样做。)@eldesgraciado注意到了频率问题,这基本上就是我们在这里做的事情。我会放一些代码和解释。(这里是一个SO帖子的答案,其中有很多关于锐化的内容-链接的答案是用Python编写的更可变的非锐化掩蔽。请花时间查看其他答案-包括这个, 其中许多简单的实现看起来与我的类似-尽管有些是用不同的编程语言编写的。)您需要调整参数。这可能行不通,但这是我想到的第一件事。

>>> import cv2
>>> im_0 = cv2.imread("FWM8b.png")
>>> cv2.imshow("FWM8b.png", im_0)
>>> cv2.waitKey(0)
## Press any key.
>>> ## Here's where we get to frequency. We'll use a Gaussian Blur.
    ## We want to take out the "frequency" of changes from white to black
    ## and back to white that are less than the thickness of the "1973"
>>> k_size = 0 ## This is the kernal size - the "width frequency",
               ## if you will. Using zero gives a width based on sigmas in
               ## the Gaussian function.
               ## You'll want to experiment with this and the other
               ## parameters, perhaps trying to run OCR over the image
               ## after each combination of parameters.
               ## Hint, avoid even numbers, and think of it as a radius
>>> gs_border = 3
>>> im_blurred = cv2.GaussianBlur(im_0, (k_size, k_size), gs_border)
>>> cv2.imshow("gauss", im_blurred)
>>> cv2.waitKey(0)

Gaussian blur with kernel size determined by sigmas

好的,我的参数可能没有模糊得够多。你想要去除的部分并不是真正模糊的。我怀疑你甚至看不到与原始图像的区别,但希望你能理解这个想法。

我们将通过一个值来乘以原始图像,通过一个值来乘以模糊图像,并从value*orig中减去value*blurry。我希望代码会更清晰。

>>> orig_img_multiplier = 1.5
>>> blur_subtraction_factor = -0.5
>>> gamma = 0
>>> im_better = cv2.addWeighted(im_0, orig_img_multiplier, im_blurred, blur_subtraction_factor, gamma)
>>> cv2.imshow("First shot at fixing", im_better)

First attempted fix

是的,没什么太大的区别。尝试调整参数,在自适应阈值之前进行模糊处理,并尝试一些其他方法。我不能保证它会起作用,但希望它能让你开始有所作为。

编辑 这是一个很好的问题。回应@eldesgraciado的反讽批评

啊,淘气,淘气。想要破解CAPTCHA代码,对吧?它们很难破解的原因是字体分割并不简单。在你特定的图像中,有很多高频噪声,你可以先尝试一些频率过滤,看看你得到的结果。

我从reCAPTCHA维基百科文章已存档)中提交以下内容。

自2011年起,reCAPTCHA已经完全数字化了《纽约时报》的档案和来自Google Books的书籍。可以从《纽约时报》文章档案中搜索该档案。通过大规模协作,截至2015年,reCAPTCHA正在帮助数字化电脑无法扫描的难以辨认的书籍,并将书籍翻译成不同的语言。请参阅本文(存档)。我认为这个验证码不是大规模在线协作的一部分。此外,需要进行其他类型的锐化处理。我刚刚意识到,我正在对通常接近0或255的像素应用1.5和-0.5倍乘数,这意味着我可能只是在锐化后恢复原始图像。我欢迎任何关于此的反馈。同时,与@eldesgracio的评论如下:
有人可能知道比我用的更好的锐化算法。足够模糊,然后在n×n网格(像素密度)上对平均值进行阈值处理。我不太了解整个自适应阈值然后轮廓的事情。也许在模糊之后可以重新进行这个过程...

我给你一些想法...

这是一个模糊效果,k_size = 5

The Gaussian-blurred image

这是一个模糊效果,k_size = 25

enter image description here

请注意这些是模糊,而不是修复。根据频率,您可能需要调整orig_img_multiplierblur_subtraction_factor(我不能确切地记得如何做,所以无法告诉您具体操作方法)。不要犹豫,尝试调整gs_bordergamma以及您在我展示的方法文档中找到的任何其他内容。
祝您好运。
顺便说一下,频率更多地基于二维快速傅里叶变换,可能还基于内核细节。我自己也曾经摆弄过这些东西 - 绝对不是专家,并且如果有人想提供更多细节,我会很高兴 - 但我希望我已经给出了一个基本的想法。添加一些抖动噪声(上下或左右模糊,而不是基于半径),也可能有所帮助。

1
是的,那是一个可能的解决方案。你也可以尝试在最后一张图像上进行一点腐蚀处理,以尝试将噪点从真实的图像中分离出来,但是数字7可能会很容易被降级。 - stateMachine
1
关于七的问题,@eldesgraciado提出了一个好观点。我认为最好在原始图像上尝试高斯模糊,但我没有正确大小的图像。即便如此,我认为数字7可能仍然是个问题。 - bballdave025
1
有人可能知道比我使用的更好的锐化算法。将其模糊足够,然后在n×n网格上对平均值进行阈值处理。我不太了解整个自适应阈值-轮廓的事情。也许在模糊之后可以重新进行这个过程... - bballdave025

3

还有一件事可以尝试——与模糊(或与之结合)分开使用的侵蚀/膨胀游戏,正如@eldesgraciado在评论中所暗示的那样。我认为这些答案中应该归功于他。

这两个操作(侵蚀和膨胀)可以反复应用。我认为窍门在于改变核大小。无论如何,我知道过去我曾用它来减少噪音。以下是一个膨胀的例子:

>>> import cv2
>>> import numpy as np
>>> im_0 = cv2.imread("FWM8b.png")
>>> k_size = 3
>>> kernel = np.ones((k_size, k_size), np.uint8)
>>> im_dilated = cv2.dilate(im_0, kernel, iterations=1)
>>> cv2.imshow("d", im_dilated)
>>> cv2.waitKey(0)

Quick Dilation

制作任何你想要的腐蚀核,并查看效果。
>>> im_eroded = cv2.erode(im_0, kernel, iterations=1)
>>> cv2.imshow("erosion", im_eroded)
>>> cv2.waitKey(0)

编辑并可能作出改进:

>>> im_blurred = cv2.GaussianBlur(im_dilated, (0, 0), 3)
>>> im_better = cv2.addWeighted(im_0, 0.5, im_blurred, 1.2, 0) 
# Getting closer.

Dilated, blurred, and combined with original, 1st

^ 通过扩张、模糊和合并(添加)到原始图像中,第一种方法


# Even better, I think.
im_better2 = cv2.addWeighted(im_0, 0.9, im_blurred, 1.7, 0)

Dilated, blurred, and combined with original, 2

^将dilated,blurred和合并(added)到原始图像中,2种方法

您可以进行伪影去除,但要注意不要去掉7的主干。如果您可以将7保持在一起,则可以进行连通组件分析并保留最大的连通组件。

您可以对每列和每行上的像素值求和,这可能会导致类似于此的结果(非常近似-几乎到了工作时间)。请注意,我对绿色曲线(列之和)更加谨慎,但比例的一致性可能有所偏差。

Sums of rows and columns

请注意,这更像是(255-像素值)的总和。这可以找到你要查找的字形(数字)所在的矩形。你可以制作一个列像素总和+行像素总和的二维地图,或者只做一些近似计算,就像我下面所做的那样。

Sums and rectangles

也可以自由旋转图像(或在不同角度上取像素和),并将每个旋转的信息合并。
还有很多其他可以尝试的事情...... @eldesgraciado提出的噪声模型suggestion特别有趣。

您可以尝试的另一件事是创建“噪声模型”并从原始图像中减去它。首先,对图像进行高斯模糊处理,参数非常低,只是轻微模糊,然后从图像中减去这个掩膜。从这里开始,步骤是实验性的:差异应该再次被模糊处理并进行阈值处理。保存此图像。您使用各种参数运行此预处理,并每次保存最终的二进制图像,然后平均得到迄今为止获得的掩膜。持久的斑点应该就是您要寻找的...就像某种空间带阻滤波器,我想...

继续尝试。 在此结果图像上使用反锐化掩蔽(我的另一个答案)。噪声更少了,但7受到了伤害。

1
im_blurred = cv2.GaussianBlur(im_dilated, (0, 0), 3) im_better = cv2.addWeighted(im_0, 0.5, im_blurred, 1.2, 0) 越来越接近了。
- bballdave025
1
im_better = cv2.addWeighted(im_0, 0.9, im_blurred, 1.7, 0) - bballdave025
1
你能否编辑你的帖子并发布图片,不要跟随模糊的东西。 - ohyesyoucan
2
你可以尝试的另一件事是创建一个“噪声模型”,并从原始图像中减去它。首先,取图像并应用极低参数的高斯模糊,仅轻微模糊它,接下来从图像中减去这个掩模。从这里开始,步骤是实验性的:差异应再次进行模糊处理和阈值化。保存此图像。您可以使用各种参数运行此预处理,并每次保存最终二进制图像,然后平均获得迄今为止获得的掩模。持久的斑点应该就是你正在寻找的东西...像某种空间带阻一样,我想... - stateMachine
1
尝试在结果上运行一个非锐化掩蔽(来自我的另一个答案的想法)。我使用了天真的参数,得到了更干净的东西,但它抹掉了数字7的柄。这就是为什么我转向了列和行的总和。>>> im_even_better = cv2.GaussianBlur(im_better, (0,0), 3); >>> im_even_betterer = cv2.addWeighted(im_even_better, 1.5, im_better, -0.5, 0)图片 - bballdave025
1
对于@ohyesyoucan:我得到了你想要的图片吗?我不确定哪些是被请求的。 - bballdave025

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