图片验证码算是网络数据采集上的一道拦路虎,虽然有诸多公开的ORC接口、云打码平台,一旦大规模应用起来,还是内部写程序进行识别处理比较好。
而自己写代码进行识别的话,又有很多种方案,比如最近火热的神经网络,一顿炼丹猛如虎,识别准确率99%妥妥的。神经网络训练模型来识别验证码虽然效果好,但是却有两个先天的缺陷:
所以对于一部分小伙伴而言,很现实的状况,还是对图片进行传统的ORC识别比较靠谱。要对图片进行传统的ORC识别,对图片进行各种降噪处理就必不可少,本文,州的先生就介绍一些实际使用到的图片降噪处理方法。
本文所用的示例图片来自于某网站的登录验证码:
可以看到,图片里面大的数据混杂着小的数字和字母,人眼可以迅速地看出来真实的数字,但是对于传统的ORC的话,可能会产生巨大的干扰。我们使用百度AI开放平台中的通用文字识别接口来测试一下:
可以看到,百度的通用文字识别接口将值为3885的数字识别成了38.852,虽然主要的数字都识别出来了,但是却多了许多无关的字符。
我们下面借助OpenCV的Python封装包cv2,对其进行一些降噪处理,使得图片更新清晰和无干扰。
在图片处理中,二值化是一个很常见的操作,我们首先将图片转为灰度,然后再根据特定的阈值将图片进行黑白的二值化处理。
import cv2
import matplotlib.pyplot as plt
img = cv2.imread("201910302114.png")
img2 = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
img2 = cv2.inRange(img2, lowerb=160, upperb=255)
plt.subplot(121), plt.imshow(img) # 原始图片
plt.subplot(122), plt.imshow(img2) # 降噪图片
plt.show()
在上面的代码中,我们先将图片转换为灰度(cvtColor()步骤),得到的图片如下所示:
左边是原始的图片,右边则是灰度处理后的图片。接着用cv2.inRange()将将阈值内的像素设置黑色,就完成了图片二值化的转换,如下图所示:
阈值不同,得到的二值化图片也会不一样,比如我们之前设置的是160,如果我们将其设置为180,那么得到的二值化图像又会不一样,如下图所示:
可以发现,很多干扰字符也出来的。如何选择合适的阈值,需要根据具体的图片来进行判断。我们将效果最好的图片保存下来,如下图所示:
使用这个图片,我们再去百度AI的通用文字识别接口上测试一下,可以发现相比于之前的识别出错,现在已经完全识别正确了,如下动图所示:
除了上面简单的二值化之外,我们再来看一个更加复杂一点的例子。很多的验证码图片会加上很多干扰点,不仅机器认不出来,有时候连人都认不出来,比如下图这样的验证码图片:
干扰块大量地附着在字符上,甚至有喧宾夺主的趋势。这样的验证码,百度的AI接口也是没辙:
面对这样的验证码,我们要怎么处理呢。下面来看一下。
因为这个图片的底色是白色的,所以我们直接使用cv2的threshold()方法对图像进行二值化处理,小于某个阈值直接置位白色:
import cv2
import matplotlib.pyplot as plt
img = cv2.imread("vcode_2019-10-29141155879867.png")
# 二值化
ret, img2 = cv2.threshold(img, 160, 255, cv2.THRESH_BINARY)
plt.subplot(121), plt.imshow(img) # 原始图片
plt.subplot(122), plt.imshow(img2) # 降噪图片
plt.show()
运行上述代码,我们可以得到如下所示的图片:
对比与原始图像,经过二值化后的图像锐利了很多,边缘不再有过渡性的颜色。下一步,我们把那些孤立在图像上的像素点清除掉即可。
如何清除图像中的孤立像素,我们可以选用效果较好的邻域降噪算法。邻域降噪算法通过计算一个像素点邻域的非白色数量来判断是否将其置为白色。其Python代码的实现如下所示:
# 计算邻域非白色个数
def calculate_noise_count(img_obj, w, h):
"""
计算邻域非白色的个数
Args:
img_obj: img obj
w: width
h: height
Returns:
count (int)
"""
count = 0
width, height,s = img_obj.shape
for _w_ in [w - 1, w, w + 1]:
for _h_ in [h - 1, h, h + 1]:
if _w_ > width - 1:
continue
if _h_ > height - 1:
continue
if _w_ == w and _h_ == h:
continue
if (img_obj[_w_, _h_,0] < 233) or (img_obj[_w_, _h_,1] < 233) or (img_obj[_w_, _h_,2] < 233):
count += 1
return count
# k邻域降噪
def operate_img(img,k):
w,h,s = img.shape
# 从高度开始遍历
for _w in range(w):
# 遍历宽度
for _h in range(h):
if _h != 0 and _w != 0 and _w < w-1 and _h < h-1:
if calculate_noise_count(img, _w, _h) < k:
img.itemset((_w,_h,0),255)
img.itemset((_w, _h,1), 255)
img.itemset((_w, _h,2), 255)
return img
我们将读取的图像传入operate_img()函数,其就会返回降噪处理后的图像,而且这个函数还可以重复进行调用,每一次调用都是在前一次降噪的基础上进行降噪。
img2 = operate_img(img2, 4)
在进行第一次邻域降噪后,图像如下图所示:
可以看到,图像上已经减少了很多孤立像素块了。
img2 = operate_img(img2, 4)
img2 = operate_img(img2, 4)
对二值化后的图像调用两次邻域降噪,降噪的效果更加明显了,如下图所示:
可以发现,图像中孤立的像素点都清除掉了,但是图像四周边缘的噪点还是很顽固。我们再来编写一个简单的算法,将图像四周全部置为白色,代码如下所示:
# 四周置白色
def around_white(img):
w, h, s = img.shape
for _w in range(w):
for _h in range(h):
if (_w <= 5) or (_h <= 5) or (_w >= w-5) or (_h >= h-5):
img.itemset((_w, _h, 0), 255)
img.itemset((_w, _h, 1), 255)
img.itemset((_w, _h, 2), 255)
return img
然后,我们再来编写一个简单的处理算法,就是如果一个像素点上下左右四个连接的像素都跟像素点不是同一个颜色,那么我们将其置为白色:
# 邻域非同色降噪
def noise_unsome_piexl(img):
'''
查找像素点上下左右相邻点的颜色,如果是非白色的非像素点颜色,则填充为白色
:param img:
:return:
'''
# print(img.shape)
w, h, s = img.shape
for _w in range(w):
for _h in range(h):
if _h != 0 and _w != 0 and _w < w - 1 and _h < h - 1:# 剔除顶点、底点
center_color = img[_w, _h] # 当前坐标颜色
# print(center_color)
top_color = img[_w, _h + 1]
bottom_color = img[_w, _h - 1]
left_color = img[_w - 1, _h]
right_color = img[_w + 1, _h]
cnt = 0
if all(top_color == center_color):
cnt += 1
if all(bottom_color == center_color):
cnt += 1
if all(left_color == center_color):
cnt += 1
if all(right_color == center_color):
cnt += 1
if cnt < 1:
img.itemset((_w, _h, 0), 255)
img.itemset((_w, _h, 1), 255)
img.itemset((_w, _h, 2), 255)
return img
最后,我们结合上面的各种降噪算法,最后得到一个处理得比较好的图像,如下图所示:
用它再去百度AI接口上测试,发现已经完全识别正确的:
以上就是本文介绍的两种验证码图片降噪处理方法,欢迎留言讨论:)