使用Canvas進行驗證碼識別


前兩天首頁上有篇文章,講用C#生成驗證碼.今天又看到一篇文章,講用python識別驗證碼.於是我就寫了這篇文章,講用Canvas識別驗證碼

我們今天要識別的是那種最最簡單的驗證碼,只有隨機顏色和隨機背景,而沒有隨機變形,隨機噪點.

為了方便試驗,我從谷歌中隨便搜了一個使用了這種驗證碼的網站,這種驗證碼的確很常見 (點擊可更換).

分析

首先,我們需要分析驗證碼的生成規律,多次刷新頁面就能看出,該驗證碼由四個數字組成,且每個數字除了顏色隨機改變,形狀和位置是固定不變的.

打開Photoshop,把網頁中的驗證碼圖片拖進來,然后再拖出幾根參考線,讓每兩根參考線包圍一個數字,一開始肯定有偏差,再次刷新頁面,按住shift鍵把驗證碼圖片拖到photoshop中,微調參考線的位置,經過多次操作,最終會成為下面這樣

上圖中,從橫向上來看,驗證碼圖片中的第一個數字是從7px開始的,然后每個數字占了9px,中間間隔是4px.於是我們可以得出一個公式,x=13*i+7,i表示數字的索引[0,1,2,3],x表示橫坐標

縱向上比較簡單,y坐標的范圍是恆定的,3px到16px.

圖像處理

首先,我並沒有專業的圖像處理知識,所以下面說的專業詞匯肯定是有問題的,以理解為主.由於數字和背景都是隨機顏色的,那么我們生成的模板字符串豈不是每次都會變嗎.的確是這樣的,由於canvas在獲取某個像素點的像素值時,返回的是rgba值.也就是一共有四個值.我們需要使用一個公式,把rgba顏色轉換成灰度值:gray = r*0.3 + g*0.59 + b*0.11,灰度值的范圍是0~255,我這里把128看成臨界點,也就是把0~128看成是暗,用0表示,128~255看成是明,用1表示,我把明暗簡寫為ld(Light and Dark).也就是公式,ld = gray>128?1:0.為什么通過明暗值能把數字和背景色區分開來呢,因為這種驗證碼在進行灰度化以后,背景明顯是屬於亮的,偏白色,而數字是屬於暗的,偏黑色.所以能夠區分.

下面是在photoshop中演示的過程.

原圖,RGB顏色:

灰度化:

二值化,在photoshop里叫位圖顏色模式.有多種轉換的算法.我們用的應該是50%閾值

轉換后就是

生成模板

既然每個數字的形狀和位置都是一定的,那我們就能把0-9這10個數字的像素信息存儲下來作為模板,在識別驗證碼時,取出驗證碼圖片中的數字依次對比.如果相等說明就是這個數字.下面是我寫的生成模板的代碼.

var image = document.querySelector(".greyfont2 img");                 //獲取到驗證碼圖片
var canvas = document.createElement('canvas');                        //新建一個canvas
var ctx = canvas.getContext("2d");                                    //獲取2D上下文
var numbers = [];                                                     //存儲數字模板的數組
canvas.width = image.width;                                           //設置canvas的寬度
canvas.height = image.height;                                         //設置canvas的高度
document.body.appendChild(canvas);                                    //將canvas添加進文檔
ctx.drawImage(image, 0, 0);                                           //將驗證碼繪制到canvas上
for (var i = 0; i < 4; i++) {                                         //循環四次,識別四個數字
    var pixels = ctx.getImageData(13 * i + 7, 3, 9, 16).data;         //按照公式獲取到每個數字上的像素點
    var ldString = "";                                                //用來存儲明暗值的字符串
    for (var j = 0, length = pixels.length; j < length; j += 4) {                 //每次循環取四個值,分別是一個像素點的r,g,b,a值
        ldString = ldString + (+(pixels[j] * 0.3 + pixels[j + 1] * 0.59 + pixels[j + 2] * 0.11 >= 128));     //灰度化+二值化,但我們並沒有真正的處理圖像
    }                 
    console.log(ldString);                 //輸出存儲着明暗值的字符串
}          

在控制台中執行上面的腳本.

我們就知道了,5,9,0的模板字符串各是什么,然后刷新頁面,繼續執行腳本,一直到到收集齊0-9的模板字符串.把這些模版字符串組成數組賦值給numbers變量.

然后接着寫識別程序.識別程序和輸出模板程序差別不大,就多了一個對比的過程.

識別驗證碼

控制台中執行下面的代碼,就會自動填寫好驗證碼.

var image = document.querySelector(".greyfont2 img");       //如果要用在greasemonkey腳本里,可以把下面的代碼放在image的onload事件里          
var canvas = document.createElement('canvas');                 
var ctx = canvas.getContext("2d");                 
var numbers = [                           //模板,依次是0-9十個數字對應的明暗值字符串
"111000111100000001100111001001111100001111100001111100001111100001111100001111100001111100100111001100000001111000111111111111111111111111111111",
"111000111100000111100000111111100111111100111111100111111100111111100111111100111111100111111100111100000000100000000111111111111111111111111111",
"100000111000000011011111001111111001111111001111110011111100111111001111110011111100111111001111111000000001000000001111111111111111111111111111",
"100000111000000001011111001111111001111110011100000111100000011111110001111111001111111001011110001000000011100000111111111111111111111111111111",
"111110011111100011111100011111000011110010011110010011100110011100110011000000000000000000111110011111110011111110011111111111111111111111111111",
"000000001000000001001111111001111111001111111000001111000000011111110001111111001111111001011110001000000011100000111111111111111111111111111111",
"111000011110000001100111101100111111001111111001000011000000001000111000001111100001111100100111000100000001111000011111111111111111111111111111",
"100000000100000000111111100111111101111111001111110011111110111111100111111101111111001111111001111110011111110011111111111111111111111111111111",
"110000011100000001100111001100111001100011011110000011110000011100110001001111100001111100000111000100000001110000011111111111111111111111111111",
"110000111100000001000111001001111100001111100000111000100000000110000100111111100111111001101111001100000011110000111111111111111111111111111111"];
var captcha = "";                         //存放識別后的驗證碼
canvas.width = image.width;
canvas.height = image.height;
document.body.appendChild(canvas);
ctx.drawImage(image, 0, 0);
for (var i = 0; i < 4; i++) {
    var pixels = ctx.getImageData(13 * i + 7, 3, 9, 16).data;
    var ldString = "";
    for (var j = 0,length = pixels.length; j < length; j += 4) {
        ldString = ldString + (+(pixels[j] * 0.3 + pixels[j + 1] * 0.59 + pixels[j + 2] * 0.11 >= 140));
    }
    var comms = numbers.map(function (value) {                      //為了100%識別率,這里不能直接判斷是否和模板字符串相等,因為可能有個別0被計算成1,或者相反
        return ldString.split("").filter(function (v, index) {
            return value[index] === v
        }).length
    });
    captcha += comms.indexOf(Math.max.apply(null, comms));          //添加到識別好的驗證碼中
}
document.querySelector("input[name=validateCode]").value = captcha; //寫入目標文本框

 

 

總結

你可以看到,這種級別的驗證碼,用js識別才用了幾行代碼.如果是更強勁的perl,python寫的話,更沒什么困難.所以驗證碼除了在邏輯上要防止繞過以外,本身還要有一定的復雜程度.否則一點意義也么有,只有浪費用戶的時間.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM