在邦购登陆时,选择了人工检验验证码,这次用机器检测试试。
先说基本逻辑:载入图像,转灰度,二值化,连通域检测,去除连通域小的,根据各连通域的范围切割图像。
先下载图片。
def getcaptcha():
for i in range(1,10):
with open('test1-{}.png'.format(i),'wb') as f:
url ='https://passport.banggo.com/CASServer//custom/loginCode.do'
res = requests.get(url,stream =True)
f.write(res.content)
得到的验证码长这样:
可以看到验证码有干扰线,好在干扰线比较细,而且验证码之间没有粘连。看看别家怎么弄得,在python验证码识别
写到了一些验证码识别的方法,但是几乎都是针对像素级别,不过提供了一个思路叫连通域,就是一个笔画里含有的像素较多,删除那些小的联通域就能去除干扰线了。
我看到的大多是自己写的像素级别的函数,既然这是降噪领域的主要思想,就会有现成的轮子啊,直到我发现了scikit-image,有自己的文档,这篇文章进行了少量总结api总结,搬运如下
其中有专门针对连通域的api,
from skimage import measure
labels = measure.label(二值图像,connectivity=None)
通过label_att = measure.regionprops(labels)得到各连通域的属性如下。label_att是一个list,有几个连通域就有几项。
更好的是skimage还提供了剔除较小连通域的函数,
from skimage import morphology
img1 = morphology.remove_small_objects(ar, min_size=要删除的连通域大小阈值, connectivity=1,in_place=False)
怎么知道连通域的大小呢,就是label属性里的area,第二个bbox用来切割图像简直不要太方便。
二者联合起来就是现成的去掉干扰线+切割图像的方法。
from skimage import io,filters,measure,morphology
import warnings
from matplotlib import pyplot as plt
def skim():
img = io.imread('1.png',as_gray=True) #变成灰度图
thresh = filters.threshold_otsu(img) #自动确定二值化的阈值
bwimg =(img<=thresh) #留下小于阈值的部分,及黑的部分
b = morphology.remove_small_objects(bwimg, 40) #去掉小于40的连通域,可以先全局看看连通域的大小和位置后决定去掉的阈值
labels = measure.label(b)
label_att = measure.regionprops(labels)
arr =[]
for la in label_att:
(x,y,w,h) =la.bbox
#print((x,y,w,h))
bei = round((h-y)/15)
if bei >1: #宽度超过30像素的,说明有粘连,从中切开
for i in range(bei):
arr.append((x, y+round((h-y)/2)*i, w, y+round((h-y)/2)*(i+1)))
elif (y>10) and (h<100) :
arr.append((x, y, w, h))
b =b*img
b[np.where(b != 0)] = 1
with warnings.catch_warnings():
warnings.simplefilter("ignore")
fig = plt.figure()
for id, (x, y, w, h) in enumerate(arr):
roi = b[x:w,y:h]
thr = roi.copy()
# io.imsave('seg1-{}.jpg',thr)
ax = fig.add_subplot(1,4,id+1)
ax.imshow(thr)
plt.show()
反思:
1.这个验证码相对规整,可以发现他家的验证码多是验证码本身是有色彩的,而干扰线一直是黑色的,可以直接将小于15的像素变成255,效果也不错,但是出现的问题是会把验证码本身连通域打断,需要再次填充。
def ty():
# for i in range(1,10):
img = io.imread('{}.png'.format(4))
img[np.where(img>235)] =255
img[np.where(img < 15)] = 255
imgray = color.rgb2gray(img)
thresh = filters.threshold_otsu(imgray)
# bwimg =(imgray <= thresh)
bwimg = morphology.closing(imgray <= thresh, morphology.square(3))
b = morphology.remove_small_objects(bwimg, 20)
labels = measure.label(b)
label_att = measure.regionprops(labels)
arr =[]
for la in label_att:
(x, y, w, h) = la.bbox
bei = round((h - y) / 15)
if bei > 1:
for i in range(bei):
arr.append((x, y + round((h - y) / 2) * i, w, y + round((h - y) / 2) * (i + 1)))
elif (y > 10) and (h < 100) :
arr.append((x, y, w, h))
if (len(arr)>1) and (h-y)<12 and (arr[-2][3]-arr[-2][1] <12): #干扰线去狠了导致断开验证码断开了要拼接一下
arr[-1] = ((x, arr[-2][1], w, h))
del arr[-2]
b = b * imgray
b[np.where(b != 0)] = 1
with warnings.catch_warnings():
warnings.simplefilter("ignore")
fig = plt.figure()
for id, (x, y, w, h) in enumerate(arr):
roi = b[x:w, y:h]
thr = roi.copy()
# io.imsave('new{}-{}.png'.format(1,id+1), thr)
ax = fig.add_subplot(1, len(arr), id + 1)
ax.imshow(thr)
plt.show()
2.验证码相对规整,而且放置位置也相对固定,可以直接用经验值切割,但是有些比较胖就比较麻烦了。
3.在使用skimage时,确是遇到二值图直接转rgb会变成的黑图或者很灰的图,像这样。b = b * imgray
b[np.where(b != 0)] = 1
就是把图调亮,因为float类型,[0,1]0是黑色,1是白色,这样存的图就是黑白的了。
另在float转rgb的时候会引发warnings说,向下保存会引起精度下降,用with就好了,官方文档推荐。
4.验证码需观察其特点对症下药比较快。