自制正方软件系统验证码的识别程序(2/4)

文件组成

为了实现训练以及识别的过程,我总共设计了6个文件,作用如下:

文件 作用
split.py 用于将验证码中四个小字符分割出来,并分类保存。
util.py 用于保存一些常用的函数
logistic_sgd.py 这是官网上的样例代码,实现了softmax的分类算法,当然还要进行下修改
package.py 这个用来将图像数据进行处理并打包压缩成为方便使用的数据集
train.py 这是开始训练的接口
check.py 这是利用训练结果进行识别的接口

还有两个文件夹:

文件夹 作用
recognized/ 用于保存可作为训练集的图片,图片以内容的实际值命名
number/ 用于保存分离子图后的小图片,以类似0/ 1/ a/….的文件夹的形式进行分类

运行依赖

这里我使用的是python2.7版,需要以下必要的运行库的支持:

theano:

安装: $sudo pip install theano

目的:这是学习算法的运行依赖

numpy:

安装$sudo apt-get install python-numpy

目的:提供了很多必要的数学运算方法

PIL:

安装$sudo apt-get install python-pil

目的:进行图片的处理

split.py

由于牵涉到文件写入,而且与其他代码不存在依赖关系,所以把他独立出来方便修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import os
import numpy as np
import Image
import shutil

print '... spliting'

source_dir='recognized/'
dest_dir='number/'

if os.path.exists(dest_dir):
shutil.rmtree(dest_dir)

os.mkdir(dest_dir)
list=os.listdir(source_dir)
directory='123456780abcdefghijklmnpqrstuvwxy'
cnt={}
for i in directory:
if not os.path.exists(dest_dir+i):
os.mkdir(dest_dir+i+'/')
cnt[i]=0

for name in list:
img=Image.open(source_dir+name)
img.crop((5,2,17,20)).save(dest_dir+name[0]+'/'+str(cnt[name[0]]),'png')
cnt[name[0]]+=1
img.crop((17,2,29,20)).save(dest_dir+name[1]+'/'+str(cnt[name[1]]),'png')
cnt[name[1]]+=1
img.crop((29,2,41,20)).save(dest_dir+name[2]+'/'+str(cnt[name[2]]),'png')
cnt[name[2]]+=1
img.crop((41,2,53,20)).save(dest_dir+name[3]+'/'+str(cnt[name[3]]),'png')
cnt[name[3]]+=1

其实也很简单,就是从recognized/中读取源图,分割成4张子图保存在number/中对应的文件夹下,并以数字序数命名。这里用了PIL进行图片分割。但其实这里的难点不是代码本身,而是要观察每张图片分隔的界限。由于普通图片查看器的并没有标尺,为了观察的更细致,我用的是GIMP(Ubuntu中的ps)进行观察。

为了方便纠正训练错误,在每次训练前我会把之前的图片删除重新写入。

package.py

这个文件用来将之前分割过的图片进行转化,也是最关键的部分。

我的思路就是将每个图片用PIL转化成灰度图,在转化为二值图,成为一个12*18的矩阵,再转化为单行数组。数组中每一个点就是一个特征。但是在后来的评估中,为了提高识别准确率,又加入了三个特征,即:空白的联通块数、字符的边界像素个数、字符的填充像素个数。事实证明这三个特征极大的提高了验证的准确性。

1
2
3
4
5
6
7
8
9
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
68
69
70
71
72
import cPickle,os,Image,gzip
import numpy as np
from util import *

print '... packaging'

source_dir='number/'

os.chdir(source_dir)
list=os.listdir('./')
data=[]
ans=[]

dict='012345678abcdefghijklmnpqrstuvwxy'
for num in list:
os.chdir(num)
list_pic=os.listdir('./')
for pic in list_pic:
img=Image.open(pic)
img=blur1(img)
cnt1=seed_fill(img)
cnt2=count_fill(img)
cnt2=(cnt2-50)/10.0
cnt3=count_border(img)
cnt3=(cnt3-50)/10.0
img=blur2(img)
arr=np.array(img)
arr=arr.reshape(12*18).astype('int').astype('bool').astype('float32')
arr=arr.tolist()
arr.append(cnt1)
arr.append(cnt2)
arr.append(cnt3)
arr=np.array(arr).astype('float32')
data.append(arr)
ans.append(float(dict.find(num)))
os.chdir('../')

os.chdir('../')
data=np.array(data)
ans=np.array(ans)

size=len(data)

rand_arr=np.arange(size)
np.random.shuffle(rand_arr)

rand_data=[]
rand_ans=[]

for i in rand_arr:
rand_data.append(data[i])
rand_ans.append(ans[i])

rand_data=np.array(rand_data)
rand_ans=np.array(rand_ans)

len_train=size*0.6
len_valid=size*0.2
len_test=size-len_train-len_valid

output=((rand_data[0:len_train],rand_ans[0:len_train]),(rand_data[len_train:len_train+len_valid],rand_ans[len_train:len_train+len_valid]),(rand_data[size-len_test:size],rand_ans[size-len_test:size]))

outputfile=open('vericode.pkl','wb')
cPickle.dump(output,outputfile)
outputfile.close()

outputfile=open('vericode.pkl','rb')
outputgzipfile=gzip.open('vericode.pkl.gz','wb')
outputgzipfile.writelines(outputfile)
outputfile.close()
outputgzipfile.close()
os.remove('vericode.pkl')

没写注释,就列下注意点吧:

  • 在进行图像格式处理的过程中,很多小细节需要注意,特别是图片到数组的格式转化。
  • 对于新加特征值的计算,我放在了util.py中实现。
  • 对于新加的特征值还要进行特征值的优化,用以提高迭代效率。
  • 对数据需要进行训练集、评估集和测试集分类,比例为6:2:2,并且首先需要进行随机化处理。
  • 输出的验证集我是用cPickle直接输出之后再用gzip进行了压缩。
  • 尤其注意输出的格式,我是用的mnist数据集的标准格式进行处理。