驗證碼識別技術

模擬精靈是首個公開最有效的驗證碼識別技術的軟件,
使用模擬精靈製作了大量的免費、商用羣發軟件,對很多複雜BT的驗證碼都能成功的識別。
但是驗證碼仍然需要精湛的技術與足夠的耐心。請牢記這一點。
驗證碼識別不適合浮躁的人去做。

驗證碼識別是一項特殊的技術,任何一個公開的驗證碼識別代碼都會很快的失效。
因爲代碼的公開後相關網站都會很快的更改驗證碼。
所以下面我只會介紹其原理。

在這裏討論驗證碼識別技術純粹基於技術研究目的。
公開此技術也是爲了讓更多的網站採取更有效的防範措施。
禁止任何人利用這裏介紹的驗證碼識別技術濫發垃圾信息。

本文介紹的驗證碼識別適用於比較複雜的圖片驗證碼,也是大多數網站採用的方法。
有一些網站的驗證碼極簡單,例如在網頁中直接顯示驗證碼字符而不是圖片,或者圖片的文件名直接就是驗證碼上的字符。
或者有其他規律可循,或者有其他明顯的漏洞可以利用(例如通過改寫訪問驗證碼頁面的源代碼使驗證碼不刷新)。

這一類的驗證碼識別極其簡單,只要熟練掌握web庫element庫的函數即可,不需要使用下面介紹的方法。

一、下載驗證碼樣本

打開c:/test文件夾,選“查看縮略圖”,
然後重複運行下面的LAScript腳本,每運行一次,就查看c:/test下自動生成的圖片,把圖片上的字符改爲文件名.
例如圖片上面顯示5,就把文件名改爲5.jpg.

如果變化比較複雜的驗證碼,可以對每個字符多用幾個樣本,第一個字符爲驗證碼字符,第二個字符可以爲任意字符。
例如:5a.jpg , 5b.jpg , 5c.jpg ...........等等。
樣本多就會識別能力就越強。

img = image.new();

--下載圖像,沒有後綴名要顯示指定*.bmp格式
img:getURL(
"http://www.***.com/test.asp","*.png");
assert(img:ok(),
"下載驗證碼失敗");

img:Crop(
4 ,3 , 56 ,18 )
img:save(
"c:/test/test.jpg"--保存到硬盤


--折分圖片,指定一行四列
img2,img3,img4,img5 
= img:split(1,4);

img2:save(
"c:/test/0001.jpg")
img3:save(
"c:/test/0002.jpg")
img4:save(
"c:/test/0003.jpg")
img5:save(
"c:/test/0004.jpg")

image.del(img);

  如何確定圖片後綴名

在整個驗證碼識別過程中,格式與後綴名一定不能搞錯,否則就會失敗。
通常:asp的驗證碼是bmp格式,php的驗證碼是png格式,其他驗證碼很多是jpg格式。
簡單的,在驗證碼上右鍵點選“圖片另存爲”,就可以看到格式(不一定準確)。

另外,你可以用UltraEdit等以二進制方式打開看文件頭部

首先下載:
str = web.getURL("http://www.***.com/test.asp")
string.save( str,"c://test.bin")

然後用UE打開test.bin看文件頭部(第一行)

jpg文件頭部有 JFIF 字眼
png文件頭部 有 PNG 字眼
gif文件頭部有 GIF字眼

如果你搞不清楚,這時候就不要指定後綴名
img:getURL("http://vwww.***.com/test.asp","")
這樣就可以下載了

二、生成驗證碼樣本數據庫

複製下面的代碼並粘貼到fap程序的「腳本區塊」內,然後點擊"回放運行",最後再點擊"讀取源代碼"。

你就可以在ApeML源代碼最後面的「數據區塊」中看到生成的驗證碼樣本了。
將「數據區塊」的內容複製需要使用驗證碼識別的fap模擬程序中覆蓋「數據區塊」即可。

local tkey ={A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0};
 
--在字典中添加所有數字鍵
for i =0,9,1 do
    tkey[ tostring(i) ] = 0;
end;

--如果一個字符有多個樣本,例如 5A.jpg 5B.jpg 5C.jpg
for k,v in pairs(tkey) do 
    if((#k)~=2)then --如果元素鍵名不是兩位字符
        tkey[k.."A" ]=0;
        tkey[k.."B" ]=0;
        tkey[k.."C" ]=0;
        tkey[k]=nil;--刪除單字符的鍵名
    end;
end;
 
--k參數爲鍵,v參數表示值 一個典型的tkeyle迭代器回調函數
loadtkey = function(k,v)
    local img = image.new();
   
    img:load("C://test//"..k..".jpg");
    assert(img:ok(),"C://test//"..k..".jpg".."/n不是有效的圖片");
   
    img:bpp(1);
    img:bpp(24);
    --通過上面兩句,輕鬆去掉驗證碼上的雜色雜點
   
    img:Crop( 1 , 0 , 9 , 10);--修剪單個字符
    img:median(2);--中值濾波進一步去雜點
   
    tkey[k]= string.encode( img:getBytes("*.jpg") , ""); --因爲轉換到字符串還是二進制,所以用base64進行編碼
    image.del(img);
end;
 
--遍歷表tkey的所有元素,調用loadtkey加載圖片文件
for k,v in pairs(tkey) do
    loadtkey(k,v);
end;
 
--把所有圖片保存到數據島,
ape:saveTable(tkey,"驗證碼樣本")

三、驗證碼識別

將下面的代碼添加到fap模擬程序最前面的init腳本區塊中即可

--從數據區塊讀取base64編碼的圖片數據
codekey = ape:loadTable("驗證碼樣本");
local timg = {}; --這是一個圖像數組,用來儲存還原後的驗證碼樣本的圖片數據
--必須進行一個轉換,因爲codekey裏面只是base64編碼的普通字符串,而timg 將是真正的圖片對象(二進制數據)
 
--還原到圖片對象
toImage = function(k,v)
    local img = image.new();
    local str = string.decode( v ,"");--首先進行base64解碼,將純文本轉換爲二進制數據
    img:setBytes( str ,"*.jpg");--將二進制數據還原爲圖像
    timg[k] = img;
end;
 
--載入驗證碼樣本
tkey = ape:loadTable("驗證碼樣本");
for k,v in pairs(tkey) do  --驗證樣本
    toImage(k,v); --轉換爲圖像
end;
   
--轉換圖片驗證碼到字符串的函數
function ImgToString(img)
    function test(imgX) --test是一個被包含在函數中的內部函數
        sleep(0);
        local limit = (60 * 20) + (60 * 20); --最小相似度 local關鍵字聲明爲局部變量
        local chr = "A"; --讀取的字符
   
   
        --testimg是一個被包含在函數中的內部函數,作爲table.foreach的回調函數,k參數表示鍵,v參數表示值
        testimg = function(k,v)

            --調用image.testXX()函數得出相似度,類似的函數還有image.testX() image.test()
            local n = imgX:testXX(timg[k]);
            if(n<limit)then --比較最小相似度
                  limit = n;
                  chr = k.."";
            end;
        end;
   
        --遍歷timg表,並調用testimg函數
        for k,v in pairs(timg) do 
           testimg(k,v)
        end;

        return string.left(chr,1); --返回讀取到的字符串首字符(如果每個字符有多個樣本)
    end;
   
   
    --修剪圖片   
    image.Crop(img, 4 ,3 , 56 ,18 )
    img:bpp(1);
    img:bpp(24);
    --上面的過程必須與下載樣本時的代碼完全一致。
   
    --使用split函數分割圖片
    local img2,img3,img4,img5 = img:split(1,4);
    win.messagePrint("正在檢測圖片,請稍候....");
    return test(img2)..test(img3)..test(img4)..test(img5);
 
end;

需要識別驗證碼的地方添加類似下面的代碼:

img = image.new()
img:getURL("http://www.***.com/test.asp","*.jpg")

--因爲刷新了驗證碼與頁面不一致,把驗證碼畫到屏幕上
local x,y = mouse.getPos()
img:paint(x,y,60 ,20 )

local str = ImgToString(img);

--下面我們把驗證碼的每個字符都轉換爲大寫,並控制鍵盤順序按鍵
code1 = string.upper( string.sub(str,1,1) );
code2 = string.upper( string.sub(str,2,2) );
code3 = string.upper( string.sub(str,3,3) );
code4 = string.upper( string.sub(str,4,4) );
key.press(100,code1,code2,code3,code4);

上面我們用了模擬按鍵的方法輸入驗證碼。
實際上大多時候可以用更簡單的方法,如下:

ele = wb:getEle("驗證碼控件名字");
ele:setAttribute("value",str)

爲什麼我的驗證碼與頁面上不一樣

因爲我們使用img:getURL讀取驗證碼時已經刷新了驗證碼。
所以驗證碼與頁面上顯示的並不一樣,您只需要識別最新的驗證碼即可。

如何直接獲取頁面的上圖片,而不是重新下載

有些驗證碼是綁定頁面的,必須識別頁面上的驗證碼才行。
那麼可以使用image.capture函數直接抓屏屏幕上的圖片即可。
請參考:image.capture函數

更好的方法是使用ele:exec("Copy")函數直接拷貝頁面上的圖片到剪貼板。
然後使用 img:getClipBD() 獲取圖片。
請參考:ele:exec("Copy")函數 img:getClipBD()函數

四、關於剪切圖片



看上面的示意圖,Crop就是選取綠色方框內的區域去清除綠色方框外面的區域.
必須保證裏面的面積正好可以平均分成四塊(假設這裏是四個驗證碼字符)

這樣以後調用 img:split(1,4) 就正好分成四個字符了
分成四份的小圖片其寬度應當正好是上面的紅色小方塊的寬度。
高度與綠色方框一樣,我這裏畫的參次不齊是爲了讓大家看清楚。

如果你Crop的參數值不對,那麼split就出錯了.
下載驗證碼圖片以後,可以使用圖像編輯軟件打開高倍放大。

五、使用種子填充算法去除驗證碼上的干擾線

模擬精靈識別驗證碼的能用是強大的,一個函數即可以去除雜色雜點。

img:bpp(1)
img:bpp(24)

經過上面兩句代碼的處理,速度很快,所有背景、干擾點、雜色蕩然無存。

但是有時候驗證碼中有大量的干擾線,並且位置隨機變動的太歷害,
這時候我們在處理驗證碼以前首先去除這些干擾線並準確的去除背景提取字符.

下面是一個模擬精靈初步處理後的驗證碼圖片.已經去除了雜色、雜點.但是上面還是有干擾線.

一個可選的辦法是用中值濾波再處理一下。img:median(2); 一個函數調用就可以,但
是這樣雖然去掉了干擾線,原來的字符也被少量的破壞了。

下面是使用種子填充算法去除干擾線的源代碼,不但能去除雜點,
而且可以去除周圍的空白(提取位置隨機變化的驗證碼),
稍加修改還能有更多的用途.

下面是自動處理以後的效果

下面是全部的源代碼:

--[[
用一個table結構{x=0; y=0}表示圖像上的「座標點」
用一組點構成table結構表示圖像上的一條「線」。所有相連的黑色的點被認爲是一條「連通線」。
找出最長的一條「連通線」,被認爲是字符,其他的認爲是雜點。
 
 
算法原理與種子填充算法相似。
 
首先讓用img:bpp函數處理爲黑白圖片,並初步去除雜色。
 
先找到一個黑點,創建一個表示「座標點」對象,並添加到「連通線」中。
然後在黑點周圍8個點中,再找黑色的點,找到就添加到「連通線」,這樣一直遞歸下去
直到遍歷圖像所有點,可能有幾塊。
 
清除雜點使用方法
image.scan(img);
 
清除雜點並切去掉周圍的空白
image.scan(img,true);
--]]
f
unction image.scan(img,crop)
   
    --用一個table數組記錄所有的「連通線」
     assert(img:ok(),"image.scan 的參數必須是一個有效的圖片");
   
     local tlines ={};
    
     --首先計算出圖片的高度寬度,避免重複的調用
     local w = img:width();
     local h = img:height();
         
 
    --[[以table形式定義一個數組,對應圖象中的每個點。
    作用相當一個開關,首先值爲false,但黑點首次被遍歷到時。把這個值變爲true。
    下次,再找到這個點時忽略。避免重複加入連通線。 
    --]]
    local tchked ={};
    for i=0,w,do
        tchked[i]={};
        for j=0,h,1  do
            tchked[i][j]=false;
        end;
    end;
    
    -----去噪
    img:bpp(1);
    img:bpp(24);
    
    --首先計算出各點的顏色值,避免在循環遞歸中重複的取
    local tcl={};
    for i=0,w,1  do
        tcl[i]={};
        for j=0,h,1   do
            tcl[i][j]=img:getPos(i,j);
        end;
    end;
 
   
    --[[
    算點數函數
    參數x,y 座標
    參數tab 所屬連通線;
    --]]
    local   function  seed(x,y,tab)
   
        ---出界了則返回
        if(x<0 or y<0 or x>w or y>h) then
            return;
        end;
             
        ---點的顏色爲白色時,返回,不處理。
        if(tcl[x][y]==16777215)  then
            return;
        end;
       
        ---值爲1,則計數加1,返回
        if ( tchked[x][y]) then
            return ;
        else
            table.insert(tab,{x=x,y=y} );--添加到連通線裏
            tchked[x][y]=true;---當值爲0時,把值置爲1。
            seed(x+1,y-1,tab);
            seed(x,y-1,tab);
            seed(x-1,y-1,tab);
            seed(x-1,y,tab);
            seed(x+1,y,tab);
            seed(x-1,y+1,tab);
            seed(x,y+1,tab);
            return seed(x+1,y+1,tab); --這裏可以用一個尾調用(參考教程中的函數部份),加快遞歸的速度。
        end;
    end;
 
   
    ---------------------------
      
    ----遍歷圖像中的所有點
    for i=0,w,1   do
        for j=0,h,1  do
            ---如果是黑色的點,而且沒有被計過數,則調用seed函數。
            if(tcl[i][j]==0 and (not tchked[i][j])) then       
                local tab = {}
                seed(i,j,tab);
                table.insert(tlines,tab); --添加一條連通線
   
            end;
        end;
    end;
         
    --現在tlines 裏記錄了的有的連通線,我們現在需要根據連通線的長度排序 
    sproc =  function(l,l2)  
        return table.maxn(l) > table.maxn(l2);--長的連通線排到前面
    end;
    table.sort(tlines,sproc)
              
    --把圖像全部畫成白色的點     
    for i=0,w,1  do
        for j=0,h,1  do
            img:setPos( i , j, 16777215);
        end;
    end;
         
    --然後把最長的一條連通線畫上去
    for i,point in  ipairs(tlines[1])  do
        img:setPos( point.x, point.y , 0);  
    end;
   
 
    --如果需要去掉周圍的空白
    if(crop)then
        local n = table.maxn(tlines[1])
           
        --排序最長連通線中的所有座標點
        sproc =  function(pt,pt2)  
            return  (pt.x <pt2.x );--*左的排前面
        end;
        table.sort(tlines[1],sproc);
        local x,x2 = tlines[1][1].x, tlines[1][n].x;
   
        --排序最長連通線中的所有座標點
        sproc =  function(pt,pt2)  
            return (pt.y <pt2.y );--*上的排前面
        end;
        table.sort(tlines[1],sproc);
        local y,y2 = tlines[1][1].y, tlines[1][n].y;
       
        img:Crop( x,y,x2+1,y2)
    end;
   
end;
發佈了73 篇原創文章 · 獲贊 0 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章