https://blog.csdn.net/qq_35923581/article/details/79487579html
這是我嘗試寫的第一篇技術博客,借鑑了不少博客和教程,寫出了本身的代碼,代碼較爲冗雜並且程序十分耗時。因此本文主要提供驗證碼識別的一個簡單的思路,代碼實現的部分還望各位大佬指點。python
看了好幾篇驗證碼圖片識別的博文,不難概括出驗證碼識別的大概思路是收集訓練集——>圖像處理——>獲得圖片特徵值——>訓練——>識別,其中圖像處理部分又包括了灰度化、二值化、去噪、分割等過程。本文將盡可能有詳有略地講述整個過程,以及本菜雞在開發過程當中遇到的問題。git
話很少說,如今開始。github
要收集足夠多的驗證碼,只須要寫一個簡單的爬蟲,去一些各大網站的登陸系統爬取就好。本菜雞爬的是本身學校教務系統的登陸網站,因此不太方便附上代碼。本菜雞總共爬取了500張驗證碼做爲訓練集,以及200張驗證碼用於測試,並已將本身獲得的驗證碼放到了github上,github地址在文末給出。算法
這一過程須要注意,必需要手動輸入這些驗證碼的答案,並將這些答案保存下來。並且爲了提升識別的正確率,須要檢查本身輸入的驗證碼內容是否有誤數組
接下來咱們就要對收集到的訓練集進行處理。app
如下用到了Python3的PIL(Pillow)庫:ide
讓咱們先來看看獲得驗證碼圖片:函數
咱們能夠看到,每一張驗證碼圖片上,都有不少的干擾線,雖然這些干擾線不會影響到咱們本身識別驗證碼,但它們會極大地干擾到機器對驗證碼識別的正確率。本菜雞曾經測試過,若不去掉這些干擾線,不管怎麼優化算法,最終的識別正確率都在40%左右徘徊,因此這些干擾線是必定要儘可能去消除的。固然,咱們能夠選擇將圖片灰度化、二值化後再去嘗試去除干擾線,但本菜雞試過不少次後,發如今原圖片消除干擾線的效果是最好的。工具
那麼問題來了,咱們要怎麼去掉這些干擾線呢?
其實你們能夠經過目測發現:這些干擾線都是黑色的!
那咱們是否是把圖片上的全部黑色去掉就行了呢?其實也並不徹底是如此。咱們都知道,每一種顏色都有它的RGB值,本菜雞在看了一遍收集到的驗證碼以後,就開始猜測:這些干擾線顏色的RGB值是否是都在一個範圍內?
咱們假定兩種顏色的RGB的紅黃藍三個值相差均小於10,則認爲這兩種顏色是同一種顏色,例如,假定(0,0,0)和(10,10,10)是同一種顏色,基於這個假定,咱們能夠將一張驗證碼圖片全部像素點的顏色分類,並存儲不一樣種類顏色的相應點的座標。例如:紅色:(1,1),(1,2)……
1 from PIL import Image 2 from PIL import ImageDraw 3 import os 4 5 now_path = str(os.getcwd()).replace('\\','/') + "/" #獲得當前目錄 6 7 # 判斷是否爲同一種類的顏色 8 def isSameColor(a,b): 9 return abs(a[0]-b[0])<10 and abs(a[1]-b[1])<10 and abs(a[2]-b[2])<10 10 11 # 獲得不一樣種類顏色的座標 12 def divideColor(image): 13 color_map = {} 14 for i in range(image.size[0]): 15 for j in range(image.size[1]): 16 now_color = image.getpixel((i,j)) #獲得該點的RGB值,注意參數是一個元組 17 flag = 1 18 for key in color_map: 19 if isSameColor(key,now_color): 20 flag = 0 21 color_map[key].append((i,j)) #如果相同種類的顏色,記錄當前座標 22 if flag: 23 color_map[now_color] = [(i,j)] #不然添加一種新的顏色 24 return color_map
獲得了各類顏色對應的座標,接下來咱們能夠用這些顏色和座標在空白圖上畫點,找出干擾線所對應的顏色
1 # 在空白圖上畫點 2 def divideDraw(color_map): 3 save_path = now_path + "pixel/" # 新圖的存儲目錄 4 for key in color_map: 5 now_image = Image.new('RGB',(90,32),(255,255,255)) # 新建一張圖,它是RGB類型(第一個參數爲類型)的、 6 # 尺寸爲90*32(第二個參數爲尺寸)、背景爲白色(第三個參數爲背景色) 7 drawer = ImageDraw.Draw(now_image)# 新建畫筆 8 for x in color_map[key]: 9 drawer.point(x,key)# 描點畫圖 10 now_image.save(save_path + str(key) + ".png")# 將獲得的圖存起來 11 12 test_image = Image.open(now_path + "captcha/0001.png")# 打開一張驗證碼 13 divideDraw(divideColor(test_image))
對第一張圖處理後,咱們獲得了不少張圖,根據截圖裏的三張圖,咱們能夠知道干擾線的RGB的三個值在0~21之間。對多張圖處理後,本菜雞發現干擾線的RGB值大概在0~15之間,咱們能夠看看RGB值和顏色的對應表來證明咱們的猜想 (注意,若是RGB範圍設置的過大,會致使驗證碼也被清除)。
因而,咱們能夠根據獲得的干擾線的RGB範圍,來去除驗證碼中的干擾線。
# 清除全部黑色點,after_table_b是用於描點畫圖的 def clear_black(image, x, y, after_table_b): now = image.getpixel((x,y)) if now[0]<=15 and now[1]<=15 and now[2]<=15: after_table_b[y][x] = (255,255,255) #若是是該像素點是黑色的,則直接將它暴力設置爲白色 else: after_table_b[y][x] = now #不然就設置成它原來的顏色
前面三張驗證碼去除了干擾線後的效果以下:
咱們能夠看到,雖然仍有不少黑點殘留,但大部分的干擾線已經被去除掉,而且這些黑點咱們在後續的圖像處理中也能將它們消除掉,因此本菜雞決定先無論它們,再將處理後的驗證碼進行下一步的處理。
在本菜雞看來,之因此要將圖片灰度化和二值化,是爲了簡化後續的去噪工做,並讓圖片的特徵值變得更簡單。
這一部分我只簡單介紹一下原理,並介紹一下怎麼用Pillow庫(能夠認爲Pillow是PIL庫的Python 3版本)完成驗證碼的灰度化和二值化
什麼叫灰度圖?任何顏色都有紅、綠、藍三原色組成,假如原來某點的顏色爲RGB(R,G,B),那麼,咱們能夠經過下面幾種方法,將其轉換爲灰度:
1.浮點算法:Gray=R*0.3+G*0.59+B*0.11 2.整數方法:Gray=(R*30+G*59+B*11)/100 3.移位方法:Gray =(R*76+G*151+B*28)>>8; 4.平均值法:Gray=(R+G+B)/3; 5.僅取綠色:Gray=G; 經過上述任一種方法求得Gray後,將原來的RGB(R,G,B)中的R,G,B統一用Gray替換,造成新的顏色RGB(Gray,Gray,Gray),用它替換原來的RGB(R,G,B)就是灰度圖了。
(以上介紹來自百度百科)
雖然看起來很是複雜,但運用了強大的pillow庫後,完成灰度化的操做只須要一條語句:
now_image = now_image.convert("L") # "L"表示灰度圖
灰度化後的成果圖:
接下來咱們進行二值化。
二值化的原理特別簡單,就是把圖片上全部像素點的灰度值,轉化成0(0表示純黑色)或255(表示純白色),(注:二值圖中每一個像素點的值爲0或1,即將一個像素點的灰度值設爲255,等價於在二值圖中將其像素值設爲1) 這一步驟本來也能夠由一條語句完成,如:
now_image = now_image.convert("1") # "1"表示二值圖
可是,這樣直接獲得的二值圖不必定知足咱們的要求。本菜雞嘗試過,獲得的驗證碼的線會比較細,不利於咱們進一步的操做,因此咱們要用一個稍微複雜一點的方法。
根據二值化的原理,咱們很容易能夠知道將圖片二值化的一種簡單方法,即設置一個閾值thresold,當某個像素點的灰度值小於它時,將這個像素點的像素值設爲0;不然,將其像素值設爲1,而後根據這個方法,從新描點畫出二值圖。
def get_bin_table(thresold = 170): table = [] for i in range(256): if i < thresold: table.append(0) else: table.append(1) return table # 獲得的一個list,其0~thresold-1項爲0,thresold~255項爲1 table = get_bin_table() # thresold的值能夠自行調節 bin_image = now_image.point(table, '1') # 用從新描點畫圖的方式獲得二值圖
二值化的成果圖:
咱們能夠看到,獲得的二值圖中仍有一些黑點殘留,這些黑點會影響到咱們的識別,那麼,咱們該如何去除掉這些黑點呢?
最簡單的方法,便是咱們能夠在去除干擾線的步驟,調節要去除的像素點的RGB值的範圍,這樣能減小黑點數量,但也很容易致使驗證碼的部分被去除。
那對於已經二值化的圖片,要如何去除這些干擾點呢?這裏咱們須要進行一個圖像去噪的步驟,來將這些噪點去除。
常見的圖像去噪方法有中值濾波、均值濾波等,但本菜雞看了不少博客以後,決定用一種比較簡單粗暴的方法。
經過觀察獲得的二值圖,咱們能夠發現,圖中的噪點,主要是一些孤立的「黑點」。即在某一個黑點造成的九宮格中,黑色像素點的數量較少,或者說只有它自身一個黑點,咱們能夠直接將知足這個特色的黑點給去除掉(像素值設爲1)便可。
1 # 判斷某個點是否超出了圖的邊界 2 def isvalid(image, x, y): 3 if x<0 or x>=image.size[0] or y<0 or y>=image.size[1]: 4 return False 5 return True 6 7 # 判斷某個點是否爲噪點,after_table_b用於描點畫圖,能夠改變level以調節去噪深度 8 def clear_noise_pixel_binary(image, x, y, after_table_b, level): 9 now = image.getpixel((x,y)) 10 flag = 0 11 for i in range(-1,2): 12 for j in range(-1,2): 13 if i==0 and j==0: 14 continue 15 if isvalid(image, x+i, y+j): 16 if image.getpixel((x+i,y+j))==0: 17 flag+=1 # 計算該點周圍黑色點的數量 18 if now==0 and flag<level: 19 after_table_b[y][x] = 1 # 去除操做,若該點爲黑點,且周圍黑點的數量小於level,則將該點變爲白點 20 elif now==1 and flag>=4: 21 after_table_b[y][x] = 0 # 補充操做,若該點爲白點,且周圍黑點的數量大於等於4,則將該點變爲黑點 22 else: 23 after_table_b[y][x] = now
咱們可再設一個clear_noise()函數對一整張圖片進行去噪,至此,圖片預處理的部分就結束了。如下是整個預處理的步驟代碼(代碼中出現的xxpath和xxroad是儲存路徑的變量):
1 now_image = Image.open(read_path + now_road) # 打開一張圖片 2 clear_noise(now_image,2) # 第一步,去除干擾線 3 now_image.save(first_path + now_road) # 可省略,存儲清除了干擾線的圖片 4 now_image = now_image.convert("L") # "L"表示灰度圖 5 now_image.save(gray_path + now_road) # 可省略,存儲灰度圖 6 bin_image = now_image.point(table, '1') # 用從新描點畫圖的方式獲得二值圖 7 bin_image.save(binary_path + now_road) # 可省略,存儲二值圖 8 clear_noise(bin_image,1) # 最後一步,對二值圖去噪 9 bin_image.resize((64, 24),Image.ANTIALIAS).save(need_path + now_road) # 改變圖片的分辨率後,將最終的圖片存儲下來
最終獲得的完成圖以下:
能夠看到,通過去噪後的圖片基本去除了干擾點,但也致使驗證碼的一些部分也被去除,但對驗證碼識別並沒有太大影響。咱們能夠經過改變一些參數的值(例如:識別爲干擾線的RGB範圍、二值化的閾值、去噪的深度,去噪的次數)來獲得更理想的圖。然而本菜雞這樣處理以後的500張驗證碼訓練集,已經可以獲得較好的識別效果了。
須要注意的是,最後改變圖片分辨率的操做並非必須的,改變圖片分辨率會致使識別的精度有所降低。可是改變了分辨率以後,圖片的分割和特徵值的提取會更方便一點,因此本菜雞仍是將圖片的分辨率改了。
還須要注意的是,灰度化、二值化、去噪等操做的順序並非固定的,若是能獲得更好的效果,徹底能夠直接對RGB圖和灰度圖直接進行去噪。同時,不建議先改變分辨率再對圖片進行處理,由於改變了分辨率後,每一個點的像素值也會發生改變。
咱們能夠看到,每一張驗證碼都有四個字符,經過觀察收集的訓練集,咱們能夠知道字符的類型(本菜雞收集的驗證碼裏面的字符都是字母或數字,並且支持將大寫字母識別爲小寫字母)
根據字符的類型,咱們先新建幾個文件夾,用於存儲分割後的驗證碼圖片。
1 divide_path = now_path + "divide/" # 分割後的驗證碼路徑 2 # 在路徑下新建文件夾,名字爲a-z,0-9,用於存儲分割後的驗證碼 3 for i in range(26): 4 if not os.path.exists(divide_path + chr(i+97)): 5 os.mkdir(divide_path + chr(i+97)) 6 for j in range(10): 7 char_vectors[str(j)] = [] 8 if not os.path.exists(divide_path + str(j)): 9 os.mkdir(divide_path + str(j))
而後根據以前保存下來的驗證碼訓練集的答案,對處理好的驗證碼圖片進行分割並保存。
答案的存儲方式以下(節選):
對於複雜的驗證碼圖片,它的字符可能有斜體,字符之間可能出現連筆,字符的位置和大小可能也不固定,這就加大了分割的難度。因此,要識別複雜的驗證碼圖片,好的分割算法是相當重要的。但本菜雞收集到的驗證碼都較爲簡明規範,因此對圖片直接進行均分,代碼以下:
1 fp = open(read_path + "/result.txt") # 驗證碼訓練集的答案路徑 2 divide_name = [] 3 for x in fp.readlines(): 4 divide_name.append(str(x).strip()) 5 fp.close() 6 for i in range(1,len(os.listdir(need_path))): 7 now_road = "/0" 8 if i<100: 9 now_road += "0" 10 if i<10: 11 now_road += "0" 12 read_road = now_road + str(i) + ".png" 13 now_image = Image.open(need_path + read_road) # 讀取處理後的驗證碼 14 for j in range(4):# 每張驗證碼有四個字符 15 child_image = now_image.crop((j*16,0,(j+1)*16,24)) # 分割驗證碼圖片(均分) 16 write_road = now_road + str(i) + "-" + str(j) + ".png" 17 child_image.save(divide_path + divide_name[i-1][j] + "/" + write_road) # 存儲分割後的圖片
下圖是分割好的訓練集(節選):
(事實上作到這一步就已經能夠用python的tesseract庫直接對圖片進行識別了,但本菜雞用過以後發現效果並非很好)
接下來提到的驗證碼圖片都是已經預處理並分割完成的。
首先咱們要明白,並不能直接將圖像與其對應的字符相匹配,而是將圖像與圖像的特徵值相匹配,再將圖像的特徵值與相應的字符相匹配。因此要對圖像裏的文字進行識別,首先要提取出該圖像中的特徵值。每一個圖像能夠有多個特徵值,咱們將圖像的全部特徵值組成一條向量,將圖像與它的特徵向量相匹配。
可是,根據選取規則的不一樣,一個圖像能夠有不一樣的特徵向量。例如:選取圖像不一樣顏色點的個數組成特徵向量、選取圖像不一樣顏色像素點佔總像素點的比例組成特徵向量等。所以,咱們須要先肯定一個特徵向量的選取規則,以後可根據識別效果對選取規則進行修改
在肯定了選取規則後,咱們即可以提取每張驗證碼的特徵向量了。在此以後,咱們能夠再作一步工做,即將訓練集中每張驗證碼的特徵向量按照其對應字符存儲下來,例如:‘a’:(1,1,1),(2,2,2),(3,3,3),代表特徵向量爲(1,1,1)、(2,2,2)、(3,3,3)的圖像識別爲a。
在選取特徵向量的問題上,本菜雞一開始用了最粗暴的方法,即將每張圖片的全部點的像素值組成該圖的特徵向量。
1 # 獲得圖像中全部點的像素 2 def get_pixel(image): 3 fp = open("test.txt","w") 4 for i in range(0,image.size[0]): 5 for j in range(0,image.size[1]): 6 fp.write(str(image.getpixel((i,j)))+" ") 7 fp.write("\n") 8 fp.close()
這樣子獲得的特徵向量雖然能更準確地匹配一張圖,可是會致使後面的識別步驟效率較低,因而後面嘗試另外一種選取方式:將每張驗證碼再分割成4*4的小圖,將這些小圖中黑色像素點所佔比例和整張圖中黑色像素點所佔比例共十七個特徵值組成一個特徵向量
1 # 根據選取規則獲得圖像特徵值 2 def get_all_eigen_b(image): 3 res = [0 for i in range(17)] 4 sum_pixel = 0 5 for i in range(4): 6 for j in range(4): 7 now_image = image.crop((j*4,i*6,(j+1)*4,(i+1)*6)) # 分割圖像 8 now_pixel = 0 9 for x in range(now_image.size[0]): 10 for y in range(now_image.size[1]): 11 if now_image.getpixel((x,y))==0: 12 now_pixel += 1 # 計算黑色點數量 13 res[i*4+j] = now_pixel/24 # 計算黑色點比例 14 sum_pixel += now_pixel 15 res[16] = sum_pixel/384 16 return res
而後咱們將特徵值保存下來,以便以後的識別。
1 def save_vectors(select): 2 for i in range(26): 3 char_vectors[chr(i+97)] = [] 4 for j in range(10): 5 char_vectors[str(j)] = [] # 存放不一樣字符及其對應的特徵向量 6 for key in char_vectors: 7 for x in os.listdir(divide_path + key): 8 now_png = str(x) 9 if now_png[-4:]==".png": 10 image = Image.open(divide_path + key + "/" + now_png, "r") # 打開一張圖片 11 if select=="2": 12 char_vectors[key].append(get_all_pixel(image)) # 像素值做爲特徵值 13 else: 14 char_vectors[key].append(get_all_eigen_b(image)) # 黑點比例做爲特徵值 15 fp = open(divide_path + key + "/vectors.txt", "w") # 保存 16 for i in range(len(char_vectors[key])): 17 for j in range(len(char_vectors[key][i])): 18 fp.write(str(char_vectors[key][i][j])) 19 fp.write(" ") 20 fp.write("\n") 21 fp.close()
像素值做爲特徵值(節選):
黑點比例做爲特徵值(節選):
按常規而言,完成特徵向量的提取,咱們應該要用這些特徵向量,訓練出一個模型。但事實上,獲得了這些特徵向量以後,咱們就已經可以對驗證碼進行簡單的識別了。
對於一張新得到的驗證碼,咱們須要對它進行與處理訓練集相同的處理操做(灰度化、二值化、去噪、分割等),並用相同的規則獲得它的特徵向量(記爲V)。以後咱們只須要遍歷訓練集,找到與V最「接近」的向量,並獲得該向量所對應的字符。
那麼,如何定義這個「接近」呢?咱們認爲兩個向量接近,即是認爲它們的夾角較小(小於90°),即它們夾角的餘弦值較大。因此,咱們只須要找出訓練集特徵向量中與V的夾角餘弦值最大的向量。(本菜雞是經過看一篇博客獲得的啓發,在此鳴謝:http://www.cnblogs.com/TTyb/p/6144740.html)
求兩個向量夾角餘弦值的公式以下(圖片來自百度百科):
代碼以下(我這裏沒有用numpy庫,用numpy庫應該能大大減小代碼量):
# 求向量點積 def add_vectors(a,b): res = 0 for i in range(len(a)): res += float(a[i])*float(b[i]) return res # 求向量的模 def module_vectors(a): return math.sqrt(sum([float(x)**2 for x in a])) # 求向量夾角餘弦值 def get_cos(a,b): add_a, add_b = module_vectors(a),module_vectors(b) if add_a!=0 and add_b!=0: return add_vectors(a,b)/(add_a*add_b) return 0
以後即可以對驗證碼進行簡單的識別了(核心部分代碼以下)
1 # all_vectors是前文所述的向量V,learn_vectors是訓練集的特徵向量 2 for k in range(len(all_vectors)): 3 res = 0 4 res_key = "Null" 5 for key in learn_vectors: 6 for i in range(len(learn_vectors[key])): 7 now = get_cos(all_vectors[k],learn_vectors[key][i]) # 計算cos值 8 if now>res: 9 res = now # 找到最大值 10 res_key = str(key) 11 print(res_key + ": " + str(res)) 12 res_str += str(res_key) # res_str爲識別結果
咱們能夠用收集到的200張測試驗證碼,用兩種不一樣的特徵值選取方式,進行測試,測試的結果以下:
用黑色點比例做爲特徵值: 用像素值做爲特徵值:
能夠看到,這種粗暴的方法雖然也可以獲得較爲準確的結果,但效率十分低下。若是改變了特徵值的選取規則,雖然提升了效率,卻下降了準確率(由於每一次識別都要遍歷500張驗證碼訓練集和2000組特徵向量訓練集,因此效率天然不高)。這種方法對訓練集較少、每一組特徵向量的特徵值數量較少的狀況能有較高的效率,但減小訓練集數量和減小特徵值數量又不可避免地會下降正確率,因此這種方法雖然簡單粗暴,但並不可取。
得知暴力並不能獲得理想的結果後,本菜雞仍是老老實實地決定用工具訓練模型。
接下來的部分涉及到SVM算法,因爲它的原理較爲複雜,本菜雞並不能很好地掌握並解釋清楚,這裏只大概介紹一下怎麼使用一個強大的工具——libsvm(這是一個強大的SVM模式識別與迴歸的軟件包,安裝過程詳見https://baijiahao.baidu.com/s?id=1580049402237436090&wfr=spider&for=pc)。
首先,libsvm用到的訓練數據格式必須是這樣的:
y1 1:x11 2:x12 3:x13 4:x14 …… n:x1n y2 1:x21 2:x22 3:x23 4:x24 …… n:x2n …… ym 1:xm1 2:xm2 3:xm3 4:xm4 …… n:xmn (其中y1~ym需爲int或double)(y表示結果,x表示特徵值)
咱們將字符a-z轉換成數值10-35,根據以前的訓練集特徵向量(先選用像素值做爲特徵值),獲得了下面的訓練數據(節選):
接下來,咱們須要對訓練數據進行交叉檢驗,以肯定模型的最佳參數。libsvm中有自帶的用於交叉檢驗的工具,咱們只須要將訓練數據放到libsvm根目錄下的tools文件夾裏(result.txt即爲訓練數據文件)。
再進入這個tools文件夾,運行grid.py,輸入:
cd E:\Libsvm\libsvm-3.22\tools
python grid.py result.txt
通過一段時間後,能獲得模型的最佳參數(c和g):
根據獲得的最佳參數,咱們即可以開始訓練模型了:
1 # 模型的訓練函數 2 def train_model(choose): 3 y,x = svm_read_problem(svm_road + "result.txt") # 讀取訓練數據 4 if choose=="2": 5 model = svm_train(y,x,'-c 32 -g 0.0078125 -b 1') # -c和-g是與核函數相關的參數,-b 1表示預測結果帶機率 6 svm_save_model(svm_road + "svm_model.model", model) # 保存模型(特徵值爲像素值) 7 else: 8 model = svm_train(y,x,'-c 8.0 -g 8.0 -b 1') 9 svm_save_model(svm_road + "svm_model_b.model", model) # 保存模型(特徵值爲黑點比例) 10 return model
接下來,咱們就能夠用訓練好的模型,去識別驗證碼了(代碼節選以下)
1 res_str = "" # res_str存放預測的答案 2 y_label = [] # 驗證碼識別的正確答案(如果用於測試,須要讀取並轉化手動輸入的答案,若僅是用於預測,則可初始化爲[0,0,0,0]) 3 for vector in all_vectors: 4 now_x = {} 5 for i in range(1,len(vector)+1): 6 now_x[i] = float(vector[i-1]) 7 x_value.append(now_x) # 獲得特徵向量,格式爲1:x1,2:x2…… 8 if choose=="2": 9 model = svm_load_model(model_road) # 讀取模型 10 else: 11 model = svm_load_model(model_road_b) 12 p_label, p_acc, p_val = svm_predict(y_label,x_value,model,'-b 1') # p_label是預測值,p_acc是預測值與正確答案的比較,p_val是機率 13 for x in p_label: 14 if int(x)<10: 15 res_str += str(int(x)) 16 else: 17 res_str += chr(int(x)+87) # 將預測值轉化爲答案
(關於libsvm的使用以及其函數中各個參數的含義,能夠去看看https://www.jianshu.com/p/e9cd040de6ce,或者閱讀libsvm自帶的README)
對獲得的模型進行測試,結果以下:
用像素值做爲特徵值: 用黑色點比例做爲特徵值:
從測試效果來看,相比簡單粗暴的方法,用模型不只提升了準確率,還大大提升了效率。
最終的識別結果(對一張驗證碼進行識別)樣例如圖:
這是本菜雞寫的第一篇能夠稱做技術博客的東西,如果發現其中的代碼有錯誤或者太冗雜,或是有表述上的錯誤,還請各位大佬多多指正。
同時,本菜雞在此聲明雖然驗證碼來自於本校教務系統登陸網站,但只是由於那裏的驗證碼比較好拿,本菜雞並無對本校教務系統作任何壞事。
本菜雞的github地址是:https://github.com/Frostmoune/Captcha,裏面有一些驗證碼的訓練集和測試集,或許能對各位有所幫助。