個人認爲驗證碼識別的核心並不是學習算法的實現,而是圖片的前期處理,處理的好直接丟進學習也能得到很高的識別成功率。
其實使用什麼語言沒有關係,原理都是一樣的,所以各位不用過於糾結是什麼語言實現,主要是思路。
本文代碼均用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拖動驗證碼處理',
]