標籤:

通過攝像頭自動識別並輸入身份寶驗證碼

最近工作中開始使用opencv來做一些跟圖像相關的機器學習項目,開始對圖像處理產生濃厚的興趣。搜索資料的時候偶然看到有一些使用opencv讀取攝像頭的小demo,在輸入煩人的阿里雲數加身份寶驗證碼的時候,突發奇想,為何不用攝像頭自動識別驗證碼,實現快速輸入。當然,出於學習目的可以用攝像頭來輸入,如果是公司里,追求短平快和穩定,直接對手機截屏進行識別才是正路。

說干就干,公司台式機沒有攝像頭,先買一個。19塊的看上去有點low,太便宜的一個是擔心容易壞,另外是擔心拍出來字元不清晰。29的看上去還可以, 但再加10塊可以買個好看點的,跟公司的顯示器氣質比較搭。所以最終敗了這個:

避免廣告嫌疑,牌子就不說了。 免驅,安裝後到硬體管理器把音頻禁用掉,反正只需要攝像頭。

硬體到手,來列一下接下來我們需要完成的任務:

  1. 使用opencv獲取到攝像頭的圖片
  2. 正確摳出我們需要識別的數字區域(ROI,region of interest)
  3. 用機器學習的方法,正確識別圖片上的數字
  4. 起一個http服務,方便調用
  5. 寫一個瀏覽器小腳本,自動調用我們的http識別服務獲取到6位驗證碼。

opencv讀取攝像頭

我們的需求比較簡單,從 docs.opencv.org/3.0-bet 這裡找到的一個代碼框架完全夠用了。

開發的時候可以先從攝像頭截取幾張圖來調試代碼。這樣圖片是固定的,方便debug和調整參數。然後根據相機方向旋轉圖像。opencv沒有直接旋轉90度,180度這種函數,但是簡單的組合transpose和flip可以。

ROI識別

找到了幾篇相關的文章,比如這篇 icodeit.org/2013/01/bas 。但是沒有完整的代碼,所以自己簡單組裝一下吧。

基本思路是

//垃圾知乎編輯器,不支持表格

因為我們的場景比較簡單,通過opencv的簡單函數基本可以把ROI提取到,其他類型的圖片識別問題就得需要複雜點的方法。比如調試的時候,我不小心截圖把窗口邊框也截圖進去了,這樣找boundingBox識別到的是窗口那個框框,就有問題了。把用到的這幾個函數對應的文檔仔細讀讀,還挺簡單的。 BoundingBox提取出來之後,用一些小trick過濾出我們想要的數字即可。注意數字1比較窄,要特殊照顧一下。

#先按x排序,再按y排序ncandidates = sorted(candidates, key=lambda x: x[0] * 10000 + x[1]nnfor cad in candidates:n x, y, w, h = cadn #面積大小和長寬比例要合適n if image_area * 0.015 > w * h > image_area * 0.0001 and w > 5 and h > 20 and h > 1.2 * w:n if pre_cnt is None:n pre_cnt = [x, y, w, h]n auth_code_img.append(pre_cnt)n continuenn x2, y2, w2, h2 = pre_cntn pre_cnt = [x, y, w, h]n #位置、面積要接近n if abs(x - x2) + abs(y - y2) < w + h and max(w * h, w2 * h2) / min(w * h, w2 * h2) < 5:n auth_code_img.append(pre_cnt)n if len(auth_code_img) == 6:n return auth_code_imgn else:n auth_code_img = [pre_cnt]n

數字OCR

剛開始以為這種列印字體識別起來應該很簡單,比如我之前這篇博客介紹的《Digit Recognizer -- 從KNN,LR,SVM,RF到深度學習》(blog.csdn.net/dinosoft/),結果被現實狠狠打臉了。 先用了一個toy數據集 sklearn.datasets.load_digits,見 scikit-learn.org/stable

可以發現6識別錯了。

算了,放大招mnist數據集

rom sklearn.datasets import fetch_mldatanmnist = fetch_mldata(MNIST original, data_home=custom_data_home) n

可能網路不太好,可以自己到mldata.org下載,打開fetch_mldata的源碼看看就知道了,下載用的是matlab格式的數據。

然後載入的時候發現報錯了

Process finished with exit code -1073741819 (0xC0000005)n

查了一下,是scipy版本過低,github.com/ContinuumIO/。自己升級一下吧。

然後用svm訓練一把,發現訓練很難收斂,結果也很差。最後終於找出來是忘記輸入歸一化。簡直吐血,說多了都是淚。

但是歸一化加上之後,發現效果居然還是比原來的差。

又開始懷疑人生了。。。

冷靜分析了一下,訓練數據是手寫體的,有各種歪歪曲曲的數據,跟我們的機器字體分布不同。而且圖片沒有crop到中心且最大化,灰度保留[0,255]的範圍,沒有離散化。load_digits的數據反而把灰度離散化成1到16。

怎麼辦,svm這種演算法解釋性並不好,訓練出來的模型也難以人工調整。但我們的ground truth是比較明確的。

最後想出一個辦法,直接屏幕截圖把0-9十個數字搞到,然後比較跟哪個數字比較接近,懶得去查到底是什麼字體,反正直接截圖挺快的。這種土方法可以算是knn在n_neighbors=1的特例。所以我們用sklearn提供的KNeighborsClassifier來實現就好了。

識別的時候,會把圖像縮放到固定長寬比,但是數字1提取出來的圖像比較窄,拉伸到標準長寬之後就變得很像7了。所以數字1得稍微特殊處理一下,特別窄的直接輸出1。

結果發現效果賊好。

simple http server

很簡單,例子 coolshell.cn/articles/1

自己重寫一下do_GET就可以了

class AuthCodeRequestHandler(SimpleHTTPRequestHandler): def do_GET(self): print self.path;n if self.path.startswith(/get_authcode):n

但是,隨著https的普及,問題來了。阿里雲的頁面是https的,如果插件嵌入我們的http內容,會報錯

Mixed Content: The page at 『https://XX』 was loaded over HTTPS, but requested an insecure resource 『http://XXXXX『. nThis request has been blocked; the content must be served over HTTPS.

不過還好,python代碼只需要加一行

httpd.socket = ssl.wrap_socket (httpd.socket, certfile=D:/X.cer, keyfile=D:/X.pvk, server_side=True)n

問題是怎麼生成https的證書文件。找了一些方法,比如 這個 雖然勉強可以運行,但是有難看的不安全標籤。

最後還是在這裡找到解決方案 stackoverflow.com/quest

現在好看多了

openssl在git Bash有,如果你有git,可以不用單獨再安裝。

瀏覽器腳本

chrome安裝一個Tampermonkey插件,然後就可以簡單添加各種小腳本到我們需要的頁面去執行。

登陸頁面的url是account.aliyun.com/mfa/*

我們要做的操作其實只有兩步

//第一步,填驗證碼ndocument.getElementsByName(authCode)[0].value= XXXXXX; n//第二步,點提交按鈕ndocument.getElementById(confirm).click(); n

因為是跨域,不能直接用ajax。但邏輯比較簡單,可以不用jsonp,http介面直接返回上面兩行代碼就行。

最終效果

跟顯示器挺搭的。這個角度輸入也比較方便,而且手機平放,數字也比較好識別。

小結

可以發現,用攝像頭直接獲取圖片由於環境和輸入姿勢的不同,會出現畫面傾斜變形旋轉,關照不足等情況,為了提高演算法魯棒性,是需要繼續優化的。但是我們這個場景比較簡單,而且手機屏幕是自發光的,問題不太嚴重。這個識別問題繼續擴展一下就是車牌識別了,繼續學習可以參考EasyPR

另外就是演算法實際中應用會發現各種坑,有時候簡單粗暴的方法確實有效。

代碼 dinosoft/aliyun-authcode-recognizer


推薦閱讀:

TAG:机器学习 |