结合连通块平均分割以及投影矫正的验证码分割算法

上一节 中记录了基于投影的验证码矫正算法的实现。通过矫正,我们可以比较好的将倾斜的字符归一成较为规整的字符,接下来我们需要对矫正后的字符进行分割。简单的方法大概是投影法了,但是很明显,这样做的可靠性并不够。我们也可以找到整张图的最左端和最右端然后平均分割,但是在字符大小不一样的情况下效果也太好。还有个朴素的方法就是找连通块,但是由于存在字符粘连问题,连通块也不能完全区分字符。那么我这里就结合后两种方法,先进行连通块分割,对于能分割的字符直接进行后续处理,对于不能分割的字符再用平均分割的方法分割处理。实践表明这种方法对于那些干扰线不明显的验证码(比如新浪微博的验证码)的分割效果还是不错的。

代码

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
import cv2,os,sys,math
import numpy as np
from pylab import *
%matplotlib inline

def getBinary(path):
'''
输入:图片路径名称
输出:黑底二值化的图片

'''
im=cv2.imread(path,0)
thresh,im=cv2.threshold(255-im,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
return im

def shadow(im,angel):
'''
输入:灰度图片,投影角度
输出:投影大小

'''
x=[]
height , width = im.shape
for i in xrange(height):
for j in xrange(width):
if im[i][j]==255:
x.append(j-1.0*i/math.tan(math.radians(angel)))
x=np.array(x)
return x.max()-x.min()

def shadowTest():
'''
测试shadow函数
'''
directory='weibo'
pics=os.listdir(directory)
for pic in pics[:1]:
im=getBinary(os.path.join(directory,pic))
print shadow(im,50)
figure()
gray()
imshow(im)

def getAngle(im):
'''
输入:灰度图
输出:最优的投影角度
'''
minShadow=500
ans=90
for angle in np.linspace(45,135,91):
thisShadow=shadow(im,angle)
if minShadow>thisShadow:
ans=angle
minShadow=thisShadow
return ans

def getAngleTest():
'''
测试getAngle函数
'''
directory='weibo'
pics=os.listdir(directory)
for pic in pics[:5]:
im=getBinary(os.path.join(directory,pic))
print getAngle(im)
gray()
figure()
imshow(im)

def affine(im,angle=None):
'''
输入:灰度图,仿射变换角度(默认为自适应最优角度)
输出:旋转后的灰度图
'''
height,width=im.shape
if angle==None:
angle=getAngle(im)
pts1=np.float32([[width/2,height/2],[width/2,0],[0,height/2]])
pts2=np.float32([[width/2,height/2],[width/2+height/2/math.tan(math.radians(angle)),0],[0,height/2]])
M=cv2.getAffineTransform(pts1,pts2)
dst=cv2.warpAffine(im,M,(width,height))
return dst

def affineTest():
'''
测试affine函数
'''
directory='weibo'
pics=os.listdir(directory)
for pic in pics[:50]:
im=getBinary(os.path.join(directory,pic))
dst=affine(im)
gray()
figure()
imshow(np.hstack([im,dst]))
axis('off')

def splitImg(im,num):
'''
输入:灰度图,图中包含的字符个数
输出:分割后的图片数组
'''
height,width=im.shape
maxPos=0
minPos=width
for i in xrange(height):
for j in xrange(width):
if im[i][j]==255:
maxPos=max(maxPos,j)
minPos=min(minPos,j)
points=[]
ans=[]
for i in xrange(num+1):
points.append(minPos+(maxPos-minPos)/num*i)
for i in xrange(num):
left=points[i]
right=points[i+1]
p1=np.zeros((height,left))
p2=np.ones((height,right-left+1))*255
p3=np.zeros((height,width-right))
new_im=np.hstack([p1,p2,p3])
for i in xrange(height):
for j in xrange(width):
if im[i][j]==0:
new_im[i][j]=0
ans.append(new_im)
return ans

def seedSplit(im):
'''
输入:灰度图
输出:从图中高亮提取出的字符图片数组
'''
angle=getAngle(im)
height,width = im.shape
vis=np.zeros((height,width))
vis2=np.zeros((height,width))
def mark(i,j):
vis2[i][j]=1
points=[]
points.append((i,j))
for dx in [-1,0,1]:
for dy in [-1,0,1]:
if i+dx>=0 and i+dx<height and j+dy>=0 and j+dy<width and im[i+dx][j+dy]==255 and vis2[i+dx][j+dy]==0:
points.extend(mark(i+dx,j+dy))
return points

def delete(i,j):
vis[i][j]=1
ans=1
for dx in [-1,0,1]:
for dy in [-1,0,1]:
if i+dx>=0 and i+dx<height and j+dy>=0 and j+dy<width and im[i+dx][j+dy]==255 and vis[i+dx][j+dy]==0:
ans+=delete(i+dx,j+dy)
return ans

def getImg(points):
new_im=np.zeros((height,width))
for point in points:
i,j=point
new_im[i][j]=255
return new_im,len(points)

imgs=[]
for i in xrange(height):
for j in xrange(width):
if vis[i][j]==1:
continue
if im[i][j]==255:
num=delete(i,j)
if num<=50:
continue
points=mark(i,j)
imgs.append(getImg(points))
vis[i][j]=1

imgs.sort(lambda left,right:cmp(left[1],right[1]))
new_imgs=[]
for i,j in imgs:
new_imgs.append((affine(i,angle),j))
imgs=new_imgs
ans=[]
if len(imgs)>4:
imgs=imgs[len(imgs)-4:]
for i in imgs[len(imgs)-4:]:
ans.append(i[0])
elif len(imgs)==4:
for i in imgs:
ans.append(i[0])
elif len(imgs)==3:
ans.append(imgs[0][0])
ans.append(imgs[1][0])
ans.extend(splitImg(imgs[2][0],2))
elif len(imgs)==2:
rate=imgs[0][1]*1.0/imgs[1][1]
if rate>0.6:
ans.extend(splitImg(imgs[0][0],2))
ans.extend(splitImg(imgs[1][0],2))
else:
ans.append(imgs[0][0])
ans.extend(splitImg(imgs[1][0],3))
else:
ans.extend(splitImg(imgs[0][0],4))
return ans

def seedSplitTest():
'''
测试seedSplit函数
'''
directory='weibo'
pics=os.listdir(directory)
gray()
for pic in pics[5:15]:
im=getBinary(os.path.join(directory,pic))
figure()
imshow(im)
imgs=seedSplit(im)
for i in imgs:
figure()
imshow(i)

def getTrainPic(im):
'''
输入:图片
输出:依次输出图片中包含的字符,并归一化成统一大小
'''
def getCenterx(img):
height,width=im.shape
points=[]
for i in xrange(height):
for j in xrange(width):
if img[i][j]==255:
points.append(j)
return np.average(points)

def getResult(im,row=30,column=30):
height,width=im.shape
maxX=0
maxY=0
minX=99999
minY=99999
for i in xrange(height):
for j in xrange(width):
if im[i][j]==255:
maxX=max(maxX,j)
minX=min(minX,j)
maxY=max(maxY,i)
minY=min(minY,i)
im=im[minY:maxY+1].T[minX:maxX+1].T
im=cv2.copyMakeBorder(im,3,3,3,3,cv2.BORDER_CONSTANT)
im=cv2.resize(im,(30,30))
return im

imgs=seedSplit(im)
for i in xrange(len(imgs)):
imgs[i]=(imgs[i],getCenterx(imgs[i]))
imgs.sort(lambda x,y:cmp(x[1],y[1]))
ans=[]
for i,j in imgs:
ans.append(getResult(i))
return ans

def getTrainPicTest():
'''
测试getTrain函数
'''
directory='weibo'
pics=os.listdir(directory)
gray()
for pic in pics[:10]:
im=getBinary(os.path.join(directory,pic))
imgs=getTrainPic(im)
res=np.hstack([imgs[0],imgs[1],imgs[2],imgs[3]])
figure()
imshow(im)
axis('off')
figure()
imshow(res)
axis('off')

getTrainPicTest()

效果