滴水算法是一种用于分割手写粘连字符的算法,与以往的直线式地分割不同,它模拟水滴的滚动,通过水滴的滚动路径来分割字符,可以解决直线切割造成的过分分割问题。
之前提过对于有粘连的字符可以使用滴水算法来解决分割,但智商捉急的我实在是领悟不了这个算法的精髓,幸好有小伙伴已经实现相关代码。
我对上面的代码进行了一些小修改,同时升级为python3的代码。
还是以这张图片为例:
在以前的我们已经知道这种简单的粘连可以通过控制阈值来实现分割,这里我们使用滴水算法。
首先使用之前文章中介绍的垂直投影或者连通域先进行一次切割处理,得到结果如下:
针对于最后粘连情况来使用滴水算法处理:
出现从itertools进口groupby def binarizing (img,阈值): ”““传入图像对象进行灰度,二值处理”“ img=img.convert (“L”) #转灰度 pixdata=https://www.yisu.com/zixun/img.load () w h=img.size #遍历所有像素,大于阈值的为黑色 y的范围(h): x的范围(w): 如果pixdata (x, y) <阈值: pixdata (x, y)=0 其他: pixdata (x, y)=255 返回img def垂直(img):”“传入二值化后的图片进行垂直投影”“ pixdata=https://www.yisu.com/zixun/img.load () w h=img.size 结果=[] x的范围(w): 黑色=0 y的范围(h): 如果pixdata (x, y)==0: 黑色+=1 result.append(黑) 返回结果 def get_start_x (hist_width):“”“根据图片垂直投影的结果来确定起点 hist_width中间值前后取4个值再这范围内取最小值 ”“”=len (hist_width)/中期/2 #注意py3除法和py2不同 temp=hist_width [mid-4中期+ 5): 返回4 + temp.index中期(min(临时)) def get_nearby_pix_value (img_pix, x, y, j): ”“”获取临近5个点像素数据“”“ 如果j==1: 返回0,如果img_pix (x - 1, y + 1)==0其他1 elif j==2: 返回0,如果img_pix [x, y + 1]==0 1 elif j==3: 返回0,如果img_pix [x + 1, y + 1]==0 1 elif j==4: 返回0,如果img_pix [x + 1]==0 1 elif j==5: 返回0,如果img_pix (x, y)==0其他1 其他: 提高异常(“get_nearby_pix_value错误”) def get_end_route (img、start_x、高度): ”“”获取滴水路径”“ left_limit=0 right_limit=img。大小[0]- 1 end_route=[] cur_p=(start_x, 0) last_p=cur_p end_route.append (cur_p) 虽然cur_p [1] & lt;(高为1): sum_n=0 max_w=0 next_x=cur_p [0] next_y=cur_p [1] pix_img=img.load () 我的范围(1,6): cur_w=get_nearby_pix_value (pix_img cur_p [0], cur_p [1], i) *(我) sum_n +=cur_w 如果max_w & lt;cur_w: max_w=cur_w 如果sum_n==0: #如果全黑则看惯性 max_w=4 如果sum_n==15: max_w=6 如果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: 如果next_x比;cur_p [0]: #向右 next_x=cur_p [0] + 1 next_y=cur_p [1] + 1 如果next_x & lt;cur_p [0]: next_x=cur_p [0] next_y=cur_p [1] + 1 如果sum_n==0: next_x=cur_p [0] next_y=cur_p [1] + 1 其他: 提高异常(“得到最终的路线错误”) 如果last_p [0]==next_x和last_p [1]==next_y: 如果next_x & lt;cur_p [0]: max_w=5 next_x=cur_p [0] + 1 next_y=cur_p [1] + 1 其他: max_w=3 next_x=cur_p [0] - 1 next_y=cur_p [1] + 1 last_p=cur_p 如果next_x比;right_limit: next_x=right_limit next_y=cur_p [1] + 1 如果next_x & lt;left_limit: next_x=left_limit next_y=cur_p [1] + 1 cur_p=(next_x next_y) end_route.append (cur_p) 返回end_route def get_split_seq (projection_x): split_seq=[] start_x=0 长度=0 pos_x, val列举(projection_x): 如果val===0=0和长度: 继续 elif val==0和长度!=0: split_seq。追加([start_x、长度) 长度=0 elif val==1: 如果长度==0: start_x=pos_x 长度+=1 其他: 提高异常(产生分割序列发生错误) #循环结束时如果长度不为0,说明还有一部分需要追加 如果长度!=0: split_seq。追加([start_x、长度) 返回split_seq def do_split (source_image开始,filter_ends): ”“” 具体实行切割 :param开始:每一行的起始点元组的列表 :param结束:每一行的终止点 ”“” 左=开始[0][0] 顶级=开始[0][1] 正确的=filter_ends [0] [0] 底=filter_ends [0] [1] pixdata=https://www.yisu.com/zixun/source_image.load () 因为我在范围(len(开始): 左=min([我][0]开始,左) 顶级=min(开始[我][1],顶部) 正确的=max (filter_ends[我][0],右) 底=max (filter_ends[我][1],底部) 宽度=右-左+ 1 身高=底- + 1 形象=形象。新(RGB,(宽度、高度),(255255255)) 我的范围(高度): 开始=开始[我] 结束=filter_ends[我] x的范围([0]开始,结束[0]+ 1): 如果pixdata [x,开始[1]]==0: 的形象。putpixel ((x -左,开始[1]-前),(0,0,0)) 返回图像 def drop_fall (img): ””“滴水分割”“” 宽度,高度=img.size # 1二值化 b_img=binarizing (img, 200) # 2垂直投影 hist_width=垂直(b_img) # 3获取起点 start_x=get_start_x (hist_width) # 4开始滴水算法 start_route=[] y的范围(高度): start_route.append ((0, y)) end_route=get_end_route (img、start_x、高度) filter_end_route=[马克斯(列表(k)) _, k groupby (end_route,λx: x[1])] #注意这里groupby img1=do_split (img, start_route filter_end_route) img1.save(削减- d - 1. - png) start_route=列表(地图(λx (x [0] + 1, x [1]), filter_end_route)) # python3中地图不返回列表需要自己转换 end_route=[] y的范围(高度): end_route.append((宽度,y)) img2=do_split (img, start_route end_route) img2.save(削减- d - 2. - png) if __name__==癬_main__”: p=Image.open(“削减- 2. png”) drop_fall (p)python验证码识别教程之利用滴水算法分割图片