基于CNN的四位数字验证码识别
前⾔
验证码技术作为⼀种反⾃动化技术,使得很多程序的⾃动化⼯作⽌步。今天作者采⽤⼀些数字图像处理和CNN⽅法来识别较为简单的数字验证码
实验步骤
实验步骤主要围绕以下展开
1. 图像预处理即滤除噪声和字符分割
2. CNN搭建和训练
3. 验证码识别
⼀. 图像预处理
接下来,我将⼀张验证码0250为例,使⽤python语⾔和依赖opencv来开展预处理
可以看到在验证码中除了数字字符以外,夹杂着很多斑点噪声以及横线⼲扰。这些斑点噪声⾯积很⼩,在⼆值化图像⾥属于很⼩的连通域,在后续操作中我们可以通过限制连通域⼤⼩来滤除这些⼩斑点。
1.1 滤除噪声
(1)读取灰度图像
# 读取图⽚并保存为灰⾊
img_gray = cv2.imread(r'./images/0250.jpg',flags=cv2.IMREAD_GRAYSCALE)
(2) ⼆值化
因为字符属于低灰度像素,因此采⽤逆向⼆值化,并为了照顾到颜⾊较淡的字符,我们采⽤较⾼的判别阈值,这⾥使⽤0.9
# ⼆值化
_,img_bin = cv2.threshold(img_gray,int(0.9*255),255,cv2.THRESH_BINARY_INV) #⼆值化阈值0.9
(3)滤除⼩连通域
由⼆值图像可以看到,图像存在着很多斑点(⼩连通域)⼲扰,这⾥我们采⽤连通域⾯积限制来滤除这些斑点,⾸先我们先定义滤除⼩连通域⽅法。
# 定义⼆值图像去除⼩连通域
def RemoveSmallCC(bin,small,connectivity=8):
# (1)获取连通域标签
ret, labels = tedComponents(bin, connectivity=connectivity)
# (2)去⼩连通域
for n in range(ret + 1):# 0~max
num = 0 # 清零
for elem in labels.flat:
if elem == n:
num += 1
if num < small: # 去除⼩连通域
bin[np.where(labels == n)] = 0
return bin
connectivity 表⽰连通域构成⽅式为 8邻域 or 4邻域 。在初始阶段,为了缓解斑点和字符粘连的问题,我们希望采⽤4邻域来分解更多的连通域,我们限制连通域⾯积上限为200,接下来我们看看效果:
session如何设置和读取
# 去除斑点
img_bin = RemoveSmallCC(img_bin, 200,connectivity=4)
相⽐较原始图像,这⾥滤出掉了⼤多数斑点,保留了字符、横线等⼤连通域。
(4) 形态学开运算
在滤除斑点的过程中,依然可能存在少部分斑点粘连在字体上,这⾥我们采⽤形态学开运算,即腐蚀
、消除、膨胀的⽅法来断开斑点和字符连通,去除斑点,再恢复初始字符形态。但是开运算的过程中也可能会导致字符结构断开并被消除,因此我们选择较⼩的结构元素(本⽂采⽤3X3),来去除那些较微弱的粘连问题
# 定义结构元素
size = 3
kernel = np.ones((size, size), dtype=np.uint8)
# 形态学腐蚀
img_erosion = de(img_bin, kernel, iterations=1)
# ⼩连通域滤除
img_erosion = RemoveSmallCC(img_erosion,30)
# 形态学膨胀
img_dila = cv2.dilate(img_erosion, kernel, iterations=1)
(5) 去除横线
此时,图像还有令⼈讨厌的横线⼲扰,其中横线穿梭在字符之间。为了保全字符的完整,我们不对字符上的横线进⾏滤除(粘有横线⼲扰的字符交给CNN吧。),仅滤除字符之间的横线。⾸先我们将⼆值图像在x轴上进⾏投影,为此不妨先定义⼀个投影⽅法。
# ⼆值图像⼀维投影
def BIN_PROJECT(bin):
IMG = bin / 255.0 # 浮点型
PROJ = np.zeros(shape=(IMG.shape[1]))
for col in range(IMG.shape[1]):
v = IMG[:, col]# 列向量
PROJ[col] = np.sum(v)
return PROJ
得到投影的统计图
其中红⾊圈圈标记了字符之间横线位置,其中该投影分量较⼩,因此我们可以通过设定阈值的⽅法去除。
flaw_area = np.where(PROJ<5) # 字符间横线
img_dila[:,flaw_area]=0 # 去除横线
IMG = RemoveSmallCC(img_dila,200) # 去除⼩连通区域
这时验证码中的字符差不多显现出来了。
1.2 字符分割
同多个字符识别相⽐,CNN对单个字符识别效率更⾼,⽽且多个字符所需的样本数据⼤,且交杂在⼀起的验证码字符更不易识别,因此有必要对字符串的⼏何特征分析来分割出单个字符。如上述验证码产⽣两个部分,02交杂在⼀起,50交杂在⼀起。
对于不同的字符交杂类型可以分为以下⼏种情况:
4个部分:1+1+1+1 型,即字符之间不存在交杂,可直接分割
3个部分:1+1+2型,我们仅对len=2的部分进⾏⼆分就可以了
2个部分(1): 2+2型,对len =2 的部分均采⽤⼆分法
2个部分(2):1+3型,对len=3的部分采⽤三分法
1个部分: 4型,对len=4的部分采⽤4分法
这⾥我们⽤LOC表⽰每个部分⾸尾地址集合,COUNT表⽰每个部分的⼤⼩集合。观察0250验证码投影属于2+2型,因此每个部分均采⽤⼆分法即可
注意:对于2+2型和1+3型的区分,可以采⽤skew = len(max)/len(min)的⽐值来判定,通常较⼩的为2+2型,较⼤的为1+3型⾸先提取出投影中各个部分的位置以及⼤⼩,定义⽅法:
# 提取数字部分
def Extract_Num(PROJ):
num = 0
COUNT = []
LOC = []
for i in range(len(PROJ)):
if PROJ[i]:# 如果⾮零则累加
num+=1
if i == 0 or PROJ[i-1]==0:# 记录⽚段起始位置
start = i
if i == len(PROJ)-1 or PROJ[i+1]==0 :# 定义⽚段结束标志,并记录⽚段
end = i
if num > 10:# 提取有效⽚段
COUNT.append(num)
LOC.append((start,end))
num = 0 #清0
return LOC,COUNT
在对各个部分分析和分割成4个数字
# 分割数字
def Segment4_Num(COUNT,LOC):
assert len(COUNT)<=4 and len(COUNT)>0
# 数字部分分析
if len(COUNT) ==4:#(1,1,1,1)
return LOC
if len(COUNT)==3:#(1,1,2)
idx = np.argmax(np.array(COUNT))# 最⼤⽚段下标
r = LOC[idx]# 最⼤⽚段位置
start = r[0]
end = r[1]
m = (r[0]+r[1])//2 # 中间位置
# 修改LOC[idx]
LOC[idx] = (start,m)
LOC.insert(idx+1,(m+1,end))
return LOC
if len(COUNT) ==2:#(2,2)or(1,3)
skew = max(COUNT)/min(COUNT)# 计算偏移程度
if skew<1.7:# 认为是(2,2)
start1 = LOC[0][0]
end1 = LOC[0][1]
start2 = LOC[1][0]
end2 = LOC[1][1]
m1 = (start1+end1)//2
m2 = (start2+end2)//2
return [(start1,m1),(m1+1,end1),(start2,m2),(m2+1,end2)] else: # 认为是(1,3)
idx = np.argmax(np.array(COUNT))# 最⼤⽚段下标
start = LOC[idx][0]
end = LOC[idx][1]
m1 = (end-start)//3+start
m2 = (end-start)//3*2+start
# 修改LOC[idx]
LOC[idx] = (start, m1)
LOC.insert(idx+1,(m1+1,m2))
LOC.insert(idx+2,(m2+1,end))
return LOC
if len(COUNT) ==1:# (4)
start = LOC[0][0]
end = LOC[0][1]
m1 = (end-start)//4+start
m2 = (end - start) // 4*2 + start
m3 = (end - start) // 4*3 + start
return [(start,m1),(m1+1,m2),(m2+1,m3),(m3+1,end)]
调⽤⽅法
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论