个人认为验证码识别的核心并不是学习算法的实现,而是图片的前期处理,处理的好直接丢进学习也能得到很高的识别成功率。
其实使用什么语言没有关系,原理都是一样的,所以各位不用过于纠结是什么语言实现,主要是思路。
本文代码均用python示例,源作者均为各博客,我只是拿来修改了部分,添加了一些可配置的参数,毕竟原代码已经写的很好了。
言归正传,让我们看看一张普通的图形验证码:
可以看到图片中有干扰线,干扰点,颜色不统一,位置不平均。
我们来分析一下需要怎么做才能提高我们后期机器学习的识别率:
1、黑白两色。
2、没有干扰线,干扰点,最好是图片中只有字母和背景。
3、分割为单独的数字或字母。
这也是处理一般的图形码需要做的事情,对应的处理方法就可以得出:
1、二值化。
2、去噪点,去干扰。
3、切割。
让我们一个个来看:
二值化:
我们知道,图片都是由一个个像素点组成的,每个点都有对应的颜色,组合起来就能看到一张图片了。
而二值化就是将图片转化为黑白两色,即RGB中的(255,255,255)和(0,0,0)两种颜色,用0,1表示,方便我们后续的处理。
所以这里很简单,我们只要指定对应的方法将每个点的颜色分类即可。
你可以取平均值大于某个值,或者指定某个颜色的权重稍微大一点,总归一点,深颜色变为黑色,浅颜色变为白色即可。
python代码如下,先灰度化再二值化:
img = img.convert('L') #灰度化
threshold = 125
table = []
for i in range(256):
if i < threshold:
table.append(0)
else:
table.append(1)
img = img.point(table, '1')
灰度化后的图片:
二值化后的图片:
(这里还用到了一点点别的操作,待会再说)
去噪点,去干扰:
我们得到这种图片后,发现上面有一些不需要的杂质,我们需要将其去掉,但是在去掉的时候,又不能影响到本身的字母。
分析一下杂质和字母的区别:
很明显,杂质较为分散,字母都连在一起。
那处理方法就很简单了:
1、若连接在一起的点多于某个值,便认为是字母,少于某个值,则为杂质,变为白色。
2、若某个点周围的8个点中黑色点的数量少于某个值,便认为是杂质,变为白色。
(若干扰线很粗很长的话,我们需要另外处理,我们后续再说)
python代码:
# 去掉二值化处理后的图片中的噪声点
def cut_noise(image, pixel_node):
rows, cols = image.size # 图片的宽度和高度
change_pos = [] # 记录噪声点位置
# 遍历图片中的每个点,除掉边缘
for i in range(1, rows-1):
for j in range(1, cols-1):
# pixel_set用来记录该店附近的黑色像素的数量
pixel_set = 0
# 取该点的邻域为以该点为中心的九宫格
for m in range(i-1, i+2):
for n in range(j-1, j+2):
if image.getpixel((m, n)) != 1: # 1为白色,0位黑色
pixel_set += 1
if pixel_set <= pixel_node:
change_pos.append((i,j))
for pos in change_pos:
image.putpixel(pos, 1)
return image
去噪点后的图片:
可以发现,已经没有明显的小点点了。
切割图片:
得到这样的图片后,我们就可以切割为单个字母以提高识别率了。
切割图片一般都是找到单个字母左右两边界的y轴座标,然后按照这个座标来切割。
而我们得到这些切割线的y座标,就可以有两种思路:
1、遍历y轴座标,黑点多的区域内大概率为字母。(投影)
2、获取所有连接在一起的黑点,个数越多,是字母的概率越大。(连通域)
python代码如下:
投影:
def vertical(img, cut_node):
"""传入二值化后的图片进行垂直投影"""
pixdata = img.load()
w,h = img.size
ver_list = []
# 开始投影
for x in range(w):
black = 0
for y in range(h):
if pixdata[x,y] == 0:
black += 1
ver_list.append(black)
# 判断边界
l,r = 0,0
flag = False
cuts = []
for i,count in enumerate(ver_list):
# 阈值这里为0
if flag is False and count > cut_node:
l = i
flag = True
if flag and count <= cut_node:
r = i-1
flag = False
cuts.append((l,r))
for item in cuts:
if item[1] - item[0] < cut_node:
cuts.remove(item)
return cuts
连通域:
def cfs(img, cut_pot):
"""传入二值化后的图片进行连通域分割"""
pixdata = img.load()
w,h = img.size
visited = set()
q = queue.Queue()
offset = [(-1,-1),(0,-1),(1,-1),(-1,0),(1,0),(-1,1),(0,1),(1,1)]
cuts = []
for x in range(w):
for y in range(h):
x_axis = []
#y_axis = []
if pixdata[x,y] == 0 and (x,y) not in visited:
q.put((x,y))
visited.add((x,y))
while not q.empty():
x_p,y_p = q.get()
for x_offset,y_offset in offset:
x_c,y_c = x_p+x_offset,y_p+y_offset
if (x_c,y_c) in visited:
continue
visited.add((x_c,y_c))
try:
if pixdata[x_c,y_c] == 0:
q.put((x_c,y_c))
x_axis.append(x_c)
#y_axis.append(y_c)
except:
pass
if x_axis:
min_x,max_x = min(x_axis),max(x_axis)
if max_x - min_x > cut_pot:
# 宽度小于3的认为是噪点,根据需要修改
cuts.append((min_x,max_x))
return cuts
处理后的图片:
这样就快乐地可以丢进学习算法中进行识别了。
以上均为较为简单的验证码处理,根据各验证码不同会有一些不同的算法,挖个坑,过两天来填上。
tasks = [
'深色背景验证码图片',
'又粗又长干扰线处理',
'多处重合但颜色不同字母处理',
'点击or拖动验证码处理',
]