Python实现图片验证码识别工具——宜人贷SRC出品

来源:互联网 发布:淘宝卖家代理 编辑:程序博客网 时间:2024/04/29 01:09
背景简介

最近开始接触业务安全,说到业务安全就不得不提薅羊毛了,而薅羊毛的人手里一定都有图片验证码的识别工具,所以想研究一下图片验证码的识别,本文使用python实现了一个图片验证码识别工具,可以通过提高训练样本的数量不断提高识别准确率。现在只能识别一些简单的图片验证码,如果想识别复杂度更高的验证码,可以在此基础上优化图片预处理的模块。本文最后会附上完整代码供大家参考交流。


使用工具

  • Python
  • PIL:图片处理库
  • Libsvm : python实现的支持向量机SVM


原理

验证码识别主要分成三部分:预处理,字符分割,字符识别。

预处理:主要是将验证码图片进行色度空间转换、去除干扰、降噪、旋转等操作,为字符分割的时候提供一个质量较好的图片。(本文由于使用的图片验证码较简单,预处理只需做色度空间转换,即灰度化及二值化)

字符分割:得到经过预处理的图片后需要将每个字符单独分隔出来。

字符识别:本文利用libsvm,收集训练样本,生成识别模型,再利用识别模型来识别图片验证码。


基本流程

一、训练样本生成识别模型文件

1.      获取样本图片验证码
2.      图片处理:灰度化,二值化,切割成4张有序的单字符图片
3.      人工识别每张切割好的图片,按数字放在不同的数字目录中
4.      提取每张切割图片的特征生成特征文件
5.      训练特征文件中的数据集生成识别模型文件

二、识别图片验证码

1.      获取样本图片验证码
2.      图片处理:灰度化,二值化,切割成4张有序的单字符图片
3.      提取每张切割图片的特征生成特征文件
4.      利用识别模型文件和生成的特征文件识别图片验证码


具体实现

一、训练样本生成识别模型文件

1.  获取样本图片验证码

根据验证码url获取图片保存到文件夹中,代码如下:

[Python] 纯文本查看 复制代码
?
1
2
3
4
5
6
defgetpic(num, url, file_path):  # 获取图片验证码
    url_path=url  # 验证码的地址
    i=open(file_path+"\%d.jpg" % num, "wb")
    i.write(urllib2.urlopen(url_path).read())
    i.close()
    print'获取成功'


可以使用多线程获取验证码,代码如下:

[Python] 纯文本查看 复制代码
?
1
2
3
4
5
defthread(num, url, file_path):   #多线程获取图片验证码
    threads=[]
    t=threading.Thread(target=getpic,args=([num,url,file_path]))
    t.start()
    t.join()


2.  图片处理:灰度化,二值化,切割成4张单字符图片

利用PIL来实现图片的灰度化和二值化:
灰度化结果如下:
 
二值化结果如下:
之后对图片进行切割,切割的方式很简单,图片有横轴x和纵轴y,首先进行纵向切割,遍历出所有存在黑色像素点的列,以此来确定要切割的x值。再对每个数字进行横向切割,遍历出所有存在黑色像素点的行,以此来确定要切割的y值。切割的效果如下图:

代码实现如下:
[Python] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
defcut_pic(filename):  #图片处理(灰度化,二值化,切割图片)
    filepath=filename
    im=Image.open(filepath)
    imgry=im.convert('L'#灰度化
    #imgry.show()
    #二值化
    threshold=130
    table=[]
    cut=[]
    realcut=[]
    fori inrange(256):
        ifi <threshold:
            table.append(0)
        else:
            table.append(1)
    out=imgry.point(table,'1')
    #out.show()
 
    #分割图片
    width=out.width
    height=out.height
    #取有像素0的列
    forx inrange(0,width):
        fory inrange(0,height):
            ifout.getpixel((x, y)) ==0:
                cut.append(x)
                break
            else:
                continue
    #保存要切割的列
    realcut.append(cut[0]-1)
    fori inrange(0,len(cut)-1):
        ifcut[i+1]-cut > 1:
            realcut.append(cut+1)
            realcut.append(cut[i+1]-1)
        else:
            continue
    realcut.append(cut[-1]+1)
    #切割图片
    count=[0,2,4,6]
    child_img_list=[]
    fori incount:
        child_img=out.crop((realcut,0,realcut[i+1],height))
        child_img_list.append(child_img)
    #保存切割的图片
    #for i in range(0,4):
        #child_img_list.save("E:\%d.jpg" % i)
 
    #横向切割
    cut_second=[]
    final_img_list=[]
    fori inrange(0,4):
        width=child_img_list.width
        height=child_img_list.height
        #取有像素0的列
        fory inrange(0,height):
            forx inrange(0,width):
                ifchild_img_list.getpixel((x, y)) ==0:
                    cut_second.append(y)
                    break
                else:
                    continue
        #切割图片
        final_img=child_img_list.crop((0,cut_second[0]-1,width,cut_second[-1]+1))
        final_img_list.append(final_img)
    # 返回切割的图片
    returnfinal_img_list


3.  人工识别每张切割好的图片,按数字放在不同的数字目录中

本文收集的样本较少,每个数字只使用15张样本。


4.  提取每张切割后的图片的特征生成特征文件

针对每张图片,我们将每行上的黑色像素点个数和每列上的黑色像素点个数作为特征生成特征文件。这个特征文件按照libsvm指定的格式生成一组带特征值和标记值的向量文件,如下图:

格式说明:
[label] [index1]:[value1][index2]:[value2] ... 
[label] [index1]:[value1] [index2]:[value2] ...

第一列label是标签列,就是图片对应的人工标记的数字(09)。后面的列是一组组的特征值,冒号前面是有顺序的索引index,通常是放连续的整数,冒号后面是用来训练的数据(即黑色像素的个数)。
实现代码如下:
[Python] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
defmodetxt():   # 生成特征文件及识别模型文件
    fori inrange(0,10): # 0到9的目录
        foru inrange(0,15): # 目录里的15张图
            b=''
            b="E:\\" + str(i) + "\\"+str(u)+'.jpg'
            img=Image.open(b)
            width, height =img.size
            pixel_cnt_list=[]
            fory inrange(height):
                pix_cnt_x=0
                forx inrange(width):
                    ifimg.getpixel((x, y)) ==0# 黑色像素点
                        pix_cnt_x+=1
                pixel_cnt_list.append(pix_cnt_x)
 
            forx inrange(width):
                pix_cnt_y=0
                fory inrange(height):
                    ifimg.getpixel((x, y)) ==0# 黑色像素点
                        pix_cnt_y+=1
                pixel_cnt_list.append(pix_cnt_y)
            f=open('E:\mode.txt','a')
            list= []
            forq inrange(0,len(pixel_cnt_list)):
                a=''
                a=str(q+1)+':' + str(pixel_cnt_list[q])
                list.append(a)
            f.write(str(i)+' ' + " ".join(list)+'\n')
            f.close


5.  训练特征文件中的数据集生成识别模型文件

使用libsvm生成识别模型文件,代码如下:

[Python] 纯文本查看 复制代码
?
1
2
3
4
os.system('C:\Python27\Lib\site-packages\libsvm-3.21\windows\svm-scale.exe -l -1 -u 1 E:\mode.txt>E:\mode_scale.txt')      #将特征文件中的值进行尺寸变换
y, x =svmutil.svm_read_problem('E:\mode_scale.txt')
m=svmutil.svm_train(y,x,'-c 0.5 -g 0.015625')  #c 和 g 的最佳参数值可通过调用findparam()获得
svmutil.svm_save_model('E:\svm_mode_file', m)     #生成最终的模型文件
其中使用svm-scale.exe对特征文件进行尺度变换,来提高预测正确率, 因为原始数据可能范围过大或过小 , svm-scale.exe可以先将数据重新 scale ( 缩放 ) 到适当范围。并且训练的时候可以指定一些参数,来提高识别的准确率。其中最重要的两个参数是cg,这两个参数可以利用libsvm提供的grid脚本来自动寻找最佳值。代码如下:
[Python] 纯文本查看 复制代码
?
1
2
3
4
deffindparam():   #寻找最佳参数
    rate,param=grid.find_parameters('E:\mode.txt','-log2c   -8,8,1  -log2g -8,8,1 -svmtrain   C:\Python27\Lib\site-packages\libsvm-3.21\windows\svm-train.exe')
    print('rate: ',rate)
    print('param: ',param)


二、识别图片验证码

1,2 两步与训练模型的1,2两步一样,第3步是生成特征文件,此时由于没有人工识别label,所以在label处都标为0,并且也要使用svm-scale.exe对特征文件进行尺度变换,代码如下:

[Python] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
deftesttxt(num,file_path):    #生成要识别的图片验证码的特征文件
    f=open('E:\ptest.txt','w')
    f.truncate()    #清空txt文件
    f.close
    fori inrange(0,4):
        img=Image.open(file_path+'\%d_%d.jpg' % (num,i))
        width, height =img.size
        pixel_cnt_list=[]
        fory inrange(height):
            pix_cnt_x=0
            forx inrange(width):
                ifimg.getpixel((x, y)) ==0# 黑色点
                    pix_cnt_x+=1
            pixel_cnt_list.append(pix_cnt_x)
 
        forx inrange(width):
            pix_cnt_y=0
            fory inrange(height):
                ifimg.getpixel((x, y)) ==0# 黑色点
                    pix_cnt_y+=1
            pixel_cnt_list.append(pix_cnt_y)
        f=open('E:\ptest.txt','a')
        list= []
        forq inrange(0,len(pixel_cnt_list)):
            a=''
            a=str(q+1)+':' + str(pixel_cnt_list[q])
            list.append(a)
        f.write('0'+ ' ' + " ".join(list)+'\n')
        f.close()
    os.system('C:\Python27\Lib\site-packages\libsvm-3.21\windows\svm-scale.exe -l -1 -u 1 E:\ptest.txt>E:\ptest_scale.txt')  #将特征文件中的值进行尺寸变换


最后第4步,利用识别模型和特征文件识别图片验证码。代码如下:
[Python] 纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
defverify():   #识别验证码
    yt ,xt =svmutil.svm_read_problem('E:\ptest_scale.txt')
    m=svmutil.svm_load_model('E:\svm_mode_file')
    p_label,p_acc,p_vals=svmutil.svm_predict(yt,xt,m)
    f=open('E:\presult.txt','w')
    f.truncate() #清空txt文件
    f.close
    foritem inp_label:
        print('%d'% item)
        f=open('E:\presult.txt','a')   #存放识别结果
        f.write(str(int(item)))
        f.close


识别结果如下图:



后期可改进
  • 针对复杂的图片验证码,增加对图片的去干扰,降噪等处理。
  • 对图片的切割可以使用更优化的算法。
  • 收集有英文字母的验证码,生成英文字母及数字的识别模型文件。
  • 图片验证码识别过程耗时很少,主要的耗时在请求图片验证码url时。

完整代码

[Python] 纯文本查看 复制代码
?
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
#coding:utf-8
fromPIL importImage
importurllib2
importrequests
importthreading
importsvm
importsvmutil
importgrid
importos
 
defgetpic(num, url, file_path):  # 获取图片验证码
    url_path=url  # 验证码的地址
    i=open(file_path+"\%d.jpg" % num, "wb")
    i.write(urllib2.urlopen(url_path).read())
    i.close()
    print'获取成功'
defthread(num, url, file_path):   #多线程获取图片验证码
    threads=[]
    t=threading.Thread(target=getpic, args=([num,url,file_path]))
    t.start()
    t.join()
defcut_pic(filename):  #图片处理(灰度化,二值化,切割图片)
    filepath=filename
    im=Image.open(filepath)
    imgry=im.convert('L'#灰度化
    #imgry.show()
    #二值化
    threshold=130
    table=[]
    cut=[]
    realcut=[]
    fori inrange(256):
        ifi <threshold:
            table.append(0)
        else:
            table.append(1)
    out=imgry.point(table,'1')
    out.show()
    #分割图片
    width=out.width
    height=out.height
    #取有像素0的列
    forx inrange(0,width):
        fory inrange(0,height):
            ifout.getpixel((x, y)) ==0:
                cut.append(x)
                break
            else:
                continue
    #保存要切割的列
    realcut.append(cut[0]-1)
    fori inrange(0,len(cut)-1):
        ifcut[i+1]-cut > 1:
            realcut.append(cut+1)
            realcut.append(cut[i+1]-1)
        else:
            continue
    realcut.append(cut[-1]+1)
    #切割图片
    count=[0,2,4,6]
    child_img_list=[]
    fori incount:
        child_img=out.crop((realcut,0,realcut[i+1],height))
        child_img_list.append(child_img)
    #保存切割的图片
    #for i in range(0,4):
        #child_img_list.save("E:\%d.jpg" % i)
    #横向切割
    cut_second=[]
    final_img_list=[]
    fori inrange(0,4):
        width=child_img_list.width
        height=child_img_list.height
        #取有像素0的列
        fory inrange(0,height):
            forx inrange(0,width):
                ifchild_img_list.getpixel((x, y)) ==0:
                    cut_second.append(y)
                    break
                else:
                    continue
        #切割图片
        final_img=child_img_list.crop((0,cut_second[0]-1,width,cut_second[-1]+1))
        final_img_list.append(final_img)
    # 返回切割的图片
    returnfinal_img_list
defsavepic(piclist, num,filepath):   # 保存切割的图片
    pic_list=piclist
    forn inrange(0,4):
        pic_list[n].save(filepath+"\%d_%d.jpg" % (num,n))
defmodetxt():   # 生成特征文件及识别模型文件
    fori inrange(0,10): # 0到9的目录
        foru inrange(0,15): # 目录里的15张图
            b=''
            b="E:\\" + str(i) + "\\"+str(u)+'.jpg'
            img=Image.open(b)
            width, height =img.size
            pixel_cnt_list=[]
            fory inrange(height):
                pix_cnt_x=0
                forx inrange(width):
                    ifimg.getpixel((x, y)) ==0# 黑色像素点
                        pix_cnt_x+=1
                pixel_cnt_list.append(pix_cnt_x)
 
            forx inrange(width):
                pix_cnt_y=0
                fory inrange(height):
                    ifimg.getpixel((x, y)) ==0# 黑色像素点
                        pix_cnt_y+=1
                pixel_cnt_list.append(pix_cnt_y)
            f=open('E:\mode.txt','a')
            list= []
            forq inrange(0,len(pixel_cnt_list)):
                a=''
                a=str(q+1)+':' + str(pixel_cnt_list[q])
                list.append(a)
            f.write(str(i)+' ' + " ".join(list)+'\n')
            f.close
    os.system('C:\Python27\Lib\site-packages\libsvm-3.21\windows\svm-scale.exe -l -1 -u 1 E:\mode.txt>E:\mode_scale.txt')      #将特征文件中的值进行尺寸变换
    y, x =svmutil.svm_read_problem('E:\mode_scale.txt')
    m=svmutil.svm_train(y,x,'-c 0.5 -g 0.015625')  #c 和 g 的最佳参数值可通过调用findparam()获得
    svmutil.svm_save_model('E:\svm_mode_file', m)     #生成最终的模型文件
 
deftesttxt(num,file_path):    #生成要识别的图片验证码的特征文件
    f=open('E:\ptest.txt','w')
    f.truncate()    #清空txt文件
    f.close
    fori inrange(0,4):
        img=Image.open(file_path+'\%d_%d.jpg' % (num,i))
        width, height =img.size
        pixel_cnt_list=[]
        fory inrange(height):
            pix_cnt_x=0
            forx inrange(width):
                ifimg.getpixel((x, y)) ==0# 黑色点
                    pix_cnt_x+=1
            pixel_cnt_list.append(pix_cnt_x)
 
        forx inrange(width):
            pix_cnt_y=0
            fory inrange(height):
                ifimg.getpixel((x, y)) ==0# 黑色点
                    pix_cnt_y+=1
            pixel_cnt_list.append(pix_cnt_y)
        f=open('E:\ptest.txt','a')
        list= []
        forq inrange(0,len(pixel_cnt_list)):
            a=''
            a=str(q+1)+':' + str(pixel_cnt_list[q])
            list.append(a)
        f.write('0'+ ' ' + " ".join(list)+'\n')
        f.close()
    os.system('C:\Python27\Lib\site-packages\libsvm-3.21\windows\svm-scale.exe -l -1 -u 1 E:\ptest.txt>E:\ptest_scale.txt')  #将特征文件中的值进行尺寸变换
deffindparam():   #寻找最佳参数
    rate,param=grid.find_parameters('E:\mode.txt','-log2c -8,8,1  -log2g -8,8,1 -svmtrain C:\Python27\Lib\site-packages\libsvm-3.21\windows\svm-train.exe')
    print('rate: ',rate)
    print('param: ',param)
defverify():   #识别验证码
    yt ,xt =svmutil.svm_read_problem('E:\ptest_scale.txt')
    m=svmutil.svm_load_model('E:\svm_mode_file')
    p_label,p_acc,p_vals=svmutil.svm_predict(yt,xt,m)
    f=open('E:\presult.txt','w')
    f.truncate() #清空txt文件
    f.close
    foritem inp_label:
        print('%d'% item)
        f=open('E:\presult.txt','a')   #存放识别结果
        f.write(str(int(item)))
        f.close
defmodeprocess():   #生成样本图片流程
    num=50  #获取图片验证码数量
    url=''  # 图片验证码地址
    file_path='E:\pic'          #存放图片验证码的文件路径
    filepath='E:\cutpic'        #存放切割后的图片验证码的文件路径
    fori inrange(0, num):
        getpic(i, url, file_path)  # 获取图片验证码     如果需要多线程获取验证码可调用thread(num, url, file_path)方法
        piclist=cut_pic(file_path +'\%d.jpg' % i)  # 图片处理
        savepic(piclist, i ,filepath)  # 保存处理切割过的样本图片
 
 
 
deftestprocess(num,url,file_path,filepath):   #识别图片验证码流程
    fori inrange(0, num):
        getpic(i, url, file_path)  # 获取图片验证码    如果需要多线程获取验证码可调用thread(num, url, file_path)方法
        piclist=cut_pic(file_path +'\%d.jpg' % i)  # 图片处理
        savepic(piclist, i, filepath)  # 保存处理切割过的测试图片
        testtxt(i, filepath)    #生成特征文件
        verify()
defmain():
    num=1     #设定需要识别的图片验证码数量
    url='https://XXXXXXXXXXXXXXXXXXXXX' # 图片验证码地址
    file_path='E:\pest'      #存放图片验证码的文件路径
    filepath='E:\pest1'      #存放切割后的图片验证码的文件路径
    testprocess(num,url,file_path,filepath) #调用testprocess()前,需先调用modeprocess(),将保存好的处理切割过的样本图片分别按数字放到0到9的目录中,再调用modetxt()生成模型文件
if__name__ =='__main__':
    main()



参考

1、LibSvm 使用说明 学习心得:http://t.cn/Rci95Hr

2、字符型图片验证码识别完整过程及Python实现:http://t.cn/RtvCEVK

3、如何利用python使用libsvm:http://t.cn/Rci9al4

0 0
原创粉丝点击