最近武夷山幣要開始準備預定了,公衆號後臺也有不少朋友向我諮詢和交流腳本。本着技術交流的想法,把以前的代碼從新梳理了一遍,發現識別驗證碼的成功率並無想象中那麼高。本着簡化流程和「殺雞就要用牛刀」的精神,這篇文章會經過YoloV3
來解決這個問題。本期文章較長,比較熟悉的同窗能夠粗略看看。html
以前的作法是把每一個字符切割出來,而後用一個3
層卷積加2
層全鏈接的卷積神經網絡模型對字符分類。若是能精確切割,那效果天然沒的說,但實際操做下來發現要想對全部組合的驗證碼做完美的切割仍是有困難的(其實主要仍是笨,沒想到完美健壯的切割方法)。python
目前的切割方法常常會把字符切割成下面這種樣子:git
這明顯是不對的,也致使最終驗證碼的錯誤。雖然頁面上驗證碼填寫錯誤會繼續識別刷新出的新驗證碼,但考慮到這是一個相似於搶購的使用環境,「一擊即中」的驗證碼識別方法是頗有必要的。github
YoloV3
最近Yolo
系列的目標檢測算法可謂是至關熱鬧。YoloV3
提出後,Yolo
系列算法好久都沒有比較大的進展,可近段時間YoloV4
和YoloV5
卻如雨後春筍般冒了出來。先不說官方暫時還沒承認YoloV5
以Yolo
命名,但單從算法性能來看,在必定程度上YoloV5
已經算是現階段速度與精度並存的SoTA
了。算法
新算法層出不窮,真是苦了算法工程師們了,很多同窗苦笑:學不動了!windows
但發牢騷歸發牢騷,新算法出來那是必須得好好擺弄一番的了。從全球最大的男性交友網站上下載了YoloV5
的代碼,固然同時也不忘給了一個大大的Star
。我平時用Keras
比較多,但這套代碼是基於Pytorch
編寫的。考慮到對Keras
和YoloV3
更加熟悉,此次暫時仍是先用YoloV3
來作,因而又下載了Keras
版本的YoloV3
代碼。須要下載地址的能夠在公衆號後臺發送Yolo
獲取。微信
正好武夷山幣即將開始預定,又是一次調試代碼交流技術的好機會,下面就來看看YoloV3
是怎麼解決驗證碼的識別問題的吧。網絡
在以前的文章中,咱們已經拿到了大量的驗證碼圖片,沒有的同窗能夠到下面這個地址獲取。app
https://github.com/TitusWongCN/WeChatSubscriptionArticlesAutoTokenAppointment/ABC/CaptchaHandler/captchas
(熟悉YoloV3
數據集格式的同窗能夠直接跳到下一節)dom
YoloV3
是一個目標檢測模型,訓練它須要的不僅是圖片自己,還要一塊兒提供要檢測目標的類別和位置,也就是常說的「標籤」。給數據集打標的工具備不少,好比LabelImg
和Labelme
等,這裏使用第一種工具。LabelImg
是一款專門用於爲目標檢測數據集生成標籤的開源軟件,用戶能夠很方便的爲目標「畫框貼標籤」。下面提供已經打包好的exe
可執行文件,使用Windows 10
系統的同窗能夠直接到下面的連接下載使用(其餘Windows
系統沒有測試,能夠下載來本身測試):
https://github.com/tzutalin/labelImg/files/2638199/windows_v1.8.1.zip
下載好以後,先把data
文件夾下的predefined_classes.txt
文件內容改成實際的驗證碼的全部類別。要注意,每一行只能有一個類別名,類別名的順序沒有強制要求,好比:
A B C ... 0 2 5 ...
這樣作會爲打標籤和後面準備開始訓練提供方便。
打開labelImg.exe
出現下面的界面:
點擊左側菜單欄的Open Dir
,在彈出的文件夾選擇框中選擇驗證碼圖片所在的文件夾,這時候軟件就會顯示找到的第一張圖片了。而後在驗證碼圖片文件夾同級目錄新建一個labels
文件夾,接着點擊左側菜單欄的Change Save Dir
選擇labels
文件夾,這樣軟件會默認把生成的標記文件存儲在labels
文件夾。
下面能夠正式爲圖片打標了。點擊左側菜單欄的Create\nRectBox
,這時鼠標會變成一個十字叉,用鼠標在圖片上畫出一個包裹目標的最小的矩形框。鬆開鼠標的時刻會出來一個對話框要選擇這個框內目標的類別,前面已經配置過類別,在下面的框中選擇對應的類別而後確認便可。
上面就標記好了一個字符,每張驗證碼圖片都有四個字符,全部每張驗證碼圖片應該都有四個框。
每一個文件打標後會生成對應的一個.xml
格式的標記文件,文件內容以下(省略了部份內容):
<annotation> <folder>captchas</folder> <filename>1.jpg</filename> <path>ABC\CaptchaHandler\captchas\1.jpg</path> <source> <database>Unknown</database> </source> <size> <width>0</width> <height>0</height> <depth>3</depth> </size> <segmented>0</segmented> <object> <name>4</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>38</xmin> <ymin>10</ymin> <xmax>50</xmax> <ymax>26</ymax> </bndbox> </object> <object> ... </object> <object> ... </object> <object> ... </object> </annotation>
生成的文件中有被標記文件的路徑和對應圖片中目標的位置以及類別等相關信息,這些信息下面都會用到。
Yolo
特定格式相對於其餘的目標檢測模型來講,Yolo
系列模型須要的訓練數據的格式比較獨特。參考下載的YoloV3
源碼的說明文件,能夠找到支持的數據集格式以下:
One row for one image;Row format: image_file_path box1 box2 ... boxN;
Box format: x_min,y_min,x_max,y_max,class_id (no space).
這個解釋簡單明瞭,也就是說:
最後還給出了示例:
path/to/img1.jpg 50,100,150,200,0 30,50,200,120,3path/to/img2.jpg 120,300,250,600,2
既然人家都說的這麼清楚了,那咱們須要作的就是依葫蘆畫瓢了。代碼以下:
import os import xml.etree.ElementTree as ET # 用來讀取XML文件的包 import cv2 # 首先根據前面寫的predefined_classes.txt文件內容定義好類別的順序 labels = ['A','B','C','D','E','F','G','H','J','K','L','M','N','P','Q', 'R','S','T','U','V','W','X','Y','Z','2','3','4','5','6','7','8'] dirpath = r'./capchars/labels' # 存放xml文件的目錄 for fp in os.listdir(dirpath): root = ET.parse(os.path.join(dirpath, fp)).getroot() path = root.find('path').text img = cv2.imread(path, cv2.IMREAD_GRAYSCALE) height, width = img.shape boxes = [path, ] for child in root.findall('object'): # 找到圖片中的全部框 label = child.find('name') label_index = labels.index(label.text) # 獲取類別名稱的ID sub = child.find('bndbox') # 找到框的標註值並進行讀取 xmin = sub[0].text ymin = sub[1].text xmax = sub[2].text ymax = sub[3].text boxes.append(','.join([xmin, ymin, xmax, ymax, str(label_index)])) # 將數據寫入data.txt文件 with open('./capchars/data.txt', 'a+') as f: f.write(' '.join(boxes) + '\n')
寫好的數據是這樣的:
F:\...\capchars\images\1.png 1,9,9,24,24 19,14,29,28,29 38,11,50,26,26 58,7,73,22,17 F:\...\images\10.png 1,8,15,23,7 18,11,29,26,26 39,15,49,29,30 58,14,73,27,17 F:\...\images\100.png 1,6,10,19,26 18,11,29,26,25 38,10,59,25,20 60,9,73,24,6 F:\...\images\101.png 1,8,14,24,21 18,8,33,23,1 38,13,55,28,9 57,12,69,26,8 F:\...\images\102.png 1,12,18,28,11 18,9,34,24,19 38,5,49,20,29 59,10,74,29,14 F:\...\images\103.png 1,10,18,25,20 18,5,29,19,30 38,7,54,22,12 59,7,74,25,14 F:\...\images\104.png 1,10,12,24,18 17,8,32,22,5 38,10,54,25,18 58,14,74,28,9 F:\...\images\105.png 1,13,9,27,8 18,9,34,24,22 37,12,50,27,8 58,11,74,29,14 F:\...\images\106.png 1,14,13,29,12 18,8,34,22,18 39,6,55,24,14 59,13,74,27,6 F:\...\images\107.png 1,8,13,22,2 17,9,32,24,4 38,8,53,23,4 58,11,73,26,11
到這裏,用於YoloV3
訓練用的數據集就算是建好了。
打開源代碼根目錄的train.py
,須要把主函數_main
的前幾行更改爲咱們對應的文件。這裏要注意,因爲要解決的是驗證碼檢測的問題,並非十分複雜,因此選擇使用Yolo
的tiny
版本,同時模型的輸入尺寸爲(416,416)
。下面是更改後的代碼:
annotation_path = 'data/capchars/data.txt' log_dir = 'logs/' classes_path = 'model_data/cap_classes.txt' # 與前面的predefined_classes.txt文件內容同樣 anchors_path = 'model_data/tiny_yolo_anchors.txt'
YoloV3
默認的輸入圖像通道數爲3
,考慮到要解決的問題的難度,但通道足矣,爲了簡化模型,把train.py
的create_model
方法和create_tiny_model
方法中的第二行代碼都修改成:
image_input = Input(shape=(None, None, 1))
查看源代碼中讀取數據的部分,發現yolo3/utils.py
中的get_random_data
方法在讀取數據的同時作了一些數據加強的操做。但在這個問題中是不太須要這些操做的,因此選擇刪除這些代碼。
【本文來自微信公衆號Titus的小宇宙,ID爲TitusCosmos,轉載請註明!】
【爲了防止網上各類爬蟲一通亂爬還故意刪除原做者信息,故在文章中間加入做者信息,還望各位讀者理解】
另外,前面說到輸入圖像的尺寸應該是(416,416)
,而驗證碼圖片的尺寸明顯不同,所以就要作一些更改尺寸的操做。更改以後的get_random_data
方法是這樣的:
from PIL import Image import numpy as np def get_random_data(annotation_line, input_shape, max_boxes=4): line = annotation_line.split() image = Image.open(line[0]) iw, ih = image.size h, w = input_shape # (416,416) boxes = np.array([np.array(list(map(int,box.split(',')))) for box in line[1:]]) image_resize = image.resize((w, h), Image.BICUBIC) box_data = np.zeros((max_boxes,5)) np.random.shuffle(boxes) x_scale, y_scale = float(w / iw), float(h / ih) # 計算驗證碼圖片在橫縱方向上縮放的倍數 for index, box in enumerate(boxes): box[0] = int(box[0] * x_scale) box[1] = int(box[1] * y_scale) box[2] = int(box[2] * x_scale) box[3] = int(box[3] * y_scale) box_data[index, :] = box image_data = np.expand_dims(image_resize, axis=-1) image_data = np.array(image_data)/255. return image_data, box_data
一切準備工做就緒,就能夠運行train.py
開始訓練了,訓練開始後控制檯會輸出以下信息:
... Epoch 2/100 1/16 [>.............................] - ETA: 6s - loss: 511.1613 2/16 [==>...........................] - ETA: 6s - loss: 489.3632 3/16 [====>.........................] - ETA: 5s - loss: 474.8986 4/16 [======>.......................] - ETA: 5s - loss: 458.0243 5/16 [========>.....................] - ETA: 4s - loss: 443.5792 6/16 [==========>...................] - ETA: 4s - loss: 430.4511 7/16 [============>.................] - ETA: 4s - loss: 416.0158 8/16 [==============>...............] - ETA: 3s - loss: 402.7111 9/16 [===============>..............] - ETA: 3s - loss: 390.4001 10/16 [=================>............] - ETA: 2s - loss: 378.4502 11/16 [===================>..........] - ETA: 2s - loss: 368.6907 12/16 [=====================>........] - ETA: 1s - loss: 359.1886 13/16 [=======================>......] - ETA: 1s - loss: 350.0055 14/16 [=========================>....] - ETA: 0s - loss: 342.0475 15/16 [===========================>..] - ETA: 0s - loss: 333.6687 16/16 [==============================] - 8s 482ms/step - loss: 325.5808 - val_loss: 211.4188 ...
源代碼中默認是先用預訓練模型訓練50
輪,此時只解凍最後兩層;而後解凍全部層再訓練50輪。咱們觀察loss
和val_loss
再也不降低時,模型就訓練的差很少了,固然就直接等待程序運行結束通常也是能夠的。
程序運行結束會在logs
文件夾下自動生成一個trained_weights_final.h5
文件,這就是咱們須要的訓練好的模型文件。
模型訓練完成,接下來固然就是激動人心的測試環節啦。
不過,心急吃不了熱豆腐,咱們還得先修改源代碼中的部分測試代碼才能開始對咱們的模型進行測試。
打開根目錄下的yolo.py
,模型測試的時候會調用這個腳本生成一個Yolo
類的對象,而後用這個對象來預測,因此須要在運行腳本以前先配置好。其實跟前面配置訓練腳本差很少,修改後的代碼以下:
_defaults = { "model_path": 'logs/trained_weights_final.h5', "anchors_path": 'model_data/tiny_yolo_anchors.txt', "classes_path": 'model_data/cap_classes.txt', ... }
這時就能夠開始運行測試模型的腳本yolo_video.py
來開始預測了,這時程序須要輸入要預測的圖片路徑。咱們輸入一個沒有訓練過的驗證碼圖片:
Input image filename:data/capchars/images/416.png
很快,結果就出來了:
很明顯,結果是LFG8
,與實際狀況一致。
再測試幾個:
這個方法不須要對驗證碼圖片做不穩定的切割,避免了在切割過程當中致使的錯誤。所以,這個驗證碼的識別方法在成功率上是要高於以前的方法的。固然,這個方法還有能夠優化的地方。好比訓練前先根據系列前幾篇文章的內容把驗證碼圖片中的干擾線去掉,這樣準確率確定會更高。
至此,YoloV3
識別驗證碼就講完了。
本系列的全部源代碼都會放在下面的github倉庫裏面,有須要能夠參考,有問題歡迎指正,歡迎交流,謝謝!
https://github.com/TitusWongCN/WeChatSubscriptionArticles
【Python盤記念幣系列】往期推薦:
第六期:Python盤記念幣系列之三:自動預定腳本編寫 01
第七期:Python盤記念幣系列之三:自動預定腳本編寫 02
第八期:Python盤記念幣系列之三:自動預定腳本編寫 03 & 系列總結
下面是個人公衆號,有興趣能夠掃一下: