滴水算法概述
滴水算法是一种用于分割手写粘连字符的算法,与以往的直线式地分割不同 ,它模拟水滴的滚动,通过水滴的滚动路径来分割字符,可以解决直线切割造成的过分分割问题。
引言
之前提过对于有粘连的字符可以使用滴水算法来解决分割,但智商捉急的我实在是领悟不了这个算法的精髓,幸好有小伙伴已经实现相关代码。
我对上面的代码进行了一些小修改,同时升级为python3的代码。
还是以这张图片为例:
在以前的我们已经知道这种简单的粘连可以通过控制阈值来实现分割,这里我们使用滴水算法。
首先使用之前文章中介绍的垂直投影或者连通域先进行一次切割处理,得到结果如下:
针对于最后粘连情况来使用滴水算法处理:
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
|
from itertools import groupby def binarizing(img,threshold): """传入image对象进行灰度、二值处理""" img = img.convert( "L" ) # 转灰度 pixdata = img.load() w, h = img.size # 遍历所有像素,大于阈值的为黑色 for y in range (h): for x in range (w): if pixdata[x, y] < threshold: pixdata[x, y] = 0 else : pixdata[x, y] = 255 return img def vertical(img): """传入二值化后的图片进行垂直投影""" pixdata = img.load() w,h = img.size result = [] for x in range (w): black = 0 for y in range (h): if pixdata[x,y] = = 0 : black + = 1 result.append(black) return result def get_start_x(hist_width): """根据图片垂直投影的结果来确定起点 hist_width中间值 前后取4个值 再这范围内取最小值 """ mid = len (hist_width) / / 2 # 注意py3 除法和py2不同 temp = hist_width[mid - 4 :mid + 5 ] return mid - 4 + temp.index( min (temp)) def get_nearby_pix_value(img_pix,x,y,j): """获取临近5个点像素数据""" if j = = 1 : return 0 if img_pix[x - 1 ,y + 1 ] = = 0 else 1 elif j = = 2 : return 0 if img_pix[x,y + 1 ] = = 0 else 1 elif j = = 3 : return 0 if img_pix[x + 1 ,y + 1 ] = = 0 else 1 elif j = = 4 : return 0 if img_pix[x + 1 ,y] = = 0 else 1 elif j = = 5 : return 0 if img_pix[x - 1 ,y] = = 0 else 1 else : raise Exception( "get_nearby_pix_value error" ) def get_end_route(img,start_x,height): """获取滴水路径""" left_limit = 0 right_limit = img.size[ 0 ] - 1 end_route = [] cur_p = (start_x, 0 ) last_p = cur_p end_route.append(cur_p) while cur_p[ 1 ] < (height - 1 ): sum_n = 0 max_w = 0 next_x = cur_p[ 0 ] next_y = cur_p[ 1 ] pix_img = img.load() for i in range ( 1 , 6 ): cur_w = get_nearby_pix_value(pix_img,cur_p[ 0 ],cur_p[ 1 ],i) * ( 6 - i) sum_n + = cur_w if max_w < cur_w: max_w = cur_w if sum_n = = 0 : # 如果全黑则看惯性 max_w = 4 if sum_n = = 15 : max_w = 6 if max_w = = 1 : next_x = cur_p[ 0 ] - 1 next_y = cur_p[ 1 ] elif max_w = = 2 : next_x = cur_p[ 0 ] + 1 next_y = cur_p[ 1 ] elif max_w = = 3 : next_x = cur_p[ 0 ] + 1 next_y = cur_p[ 1 ] + 1 elif max_w = = 5 : next_x = cur_p[ 0 ] - 1 next_y = cur_p[ 1 ] + 1 elif max_w = = 6 : next_x = cur_p[ 0 ] next_y = cur_p[ 1 ] + 1 elif max_w = = 4 : if next_x > cur_p[ 0 ]: # 向右 next_x = cur_p[ 0 ] + 1 next_y = cur_p[ 1 ] + 1 if next_x < cur_p[ 0 ]: next_x = cur_p[ 0 ] next_y = cur_p[ 1 ] + 1 if sum_n = = 0 : next_x = cur_p[ 0 ] next_y = cur_p[ 1 ] + 1 else : raise Exception( "get end route error" ) if last_p[ 0 ] = = next_x and last_p[ 1 ] = = next_y: if next_x < cur_p[ 0 ]: max_w = 5 next_x = cur_p[ 0 ] + 1 next_y = cur_p[ 1 ] + 1 else : max_w = 3 next_x = cur_p[ 0 ] - 1 next_y = cur_p[ 1 ] + 1 last_p = cur_p if next_x > right_limit: next_x = right_limit next_y = cur_p[ 1 ] + 1 if next_x < left_limit: next_x = left_limit next_y = cur_p[ 1 ] + 1 cur_p = (next_x,next_y) end_route.append(cur_p) return end_route def get_split_seq(projection_x): split_seq = [] start_x = 0 length = 0 for pos_x, val in enumerate (projection_x): if val = = 0 and length = = 0 : continue elif val = = 0 and length ! = 0 : split_seq.append([start_x, length]) length = 0 elif val = = 1 : if length = = 0 : start_x = pos_x length + = 1 else : raise Exception( 'generating split sequence occurs error' ) # 循环结束时如果length不为0,说明还有一部分需要append if length ! = 0 : split_seq.append([start_x, length]) return split_seq def do_split(source_image, starts, filter_ends): """ 具体实行切割 : param starts: 每一行的起始点 tuple of list : param ends: 每一行的终止点 """ left = starts[ 0 ][ 0 ] top = starts[ 0 ][ 1 ] right = filter_ends[ 0 ][ 0 ] bottom = filter_ends[ 0 ][ 1 ] pixdata = source_image.load() for i in range ( len (starts)): left = min (starts[i][ 0 ], left) top = min (starts[i][ 1 ], top) right = max (filter_ends[i][ 0 ], right) bottom = max (filter_ends[i][ 1 ], bottom) width = right - left + 1 height = bottom - top + 1 image = Image.new( 'RGB' , (width, height), ( 255 , 255 , 255 )) for i in range (height): start = starts[i] end = filter_ends[i] for x in range (start[ 0 ], end[ 0 ] + 1 ): if pixdata[x,start[ 1 ]] = = 0 : image.putpixel((x - left, start[ 1 ] - top), ( 0 , 0 , 0 )) return image def drop_fall(img): """滴水分割""" width,height = img.size # 1 二值化 b_img = binarizing(img, 200 ) # 2 垂直投影 hist_width = vertical(b_img) # 3 获取起点 start_x = get_start_x(hist_width) # 4 开始滴水算法 start_route = [] for y in range (height): start_route.append(( 0 ,y)) end_route = get_end_route(img,start_x,height) filter_end_route = [ max ( list (k)) for _,k in groupby(end_route, lambda x:x[ 1 ])] # 注意这里groupby img1 = do_split(img,start_route,filter_end_route) img1.save( 'cuts-d-1.png' ) start_route = list ( map ( lambda x : (x[ 0 ] + 1 ,x[ 1 ]),filter_end_route)) # python3中map不返回list需要自己转换 end_route = [] for y in range (height): end_route.append((width - 1 ,y)) img2 = do_split(img,start_route,end_route) img2.save( 'cuts-d-2.png' ) if __name__ = = '__main__' : p = Image. open ( "cuts-2.png" ) drop_fall(p) |
执行后会得到切分后的2个照片:
从这张图片来看,虽然切分成功但是效果比较一般。另外目前的代码只能对2个字符粘连的情况切分,参悟了滴水算法精髓的小伙伴可以试着改成多个字符粘连的情况。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。
原文链接:http://www.hi-roy.com/2017/09/22/Python验证码识别4/