需驗證碼識別,對常用論壇的驗證碼識別的時候大家用來做羣發是最合適不過了。一個非常有意義的參考
注:非crazycoder原裝,文章是轉載的,原文出處不祥了,很多地方都有,找不到原出處了
驗證碼(captcha)是伴隨自動提交程序(spam)的出現而出現的。現在各種論壇、博客、投票等程序都帶有驗證碼功能。大部分驗證碼都比較容 易識別,只需要簡單對照一下特徵碼就可以得到百分之百準確的結果。也有稍微複雜一點的,比如phpwind和discuz的驗證碼。
前段時間做了phpwind和discuz的驗證碼識別,phpwind6.0以前的驗證碼和discuz最新的驗證碼如果在不改變後臺驗證碼配置的情況下,識別正確率幾乎可以達到100%,現在跟大家分享一下識別方法。
驗證碼識別一般分爲以下幾個步驟:
- 取出字模
- 二值化
- 計算特徵
- 對照樣本
各種驗證碼在具體的步驟上操作會有所不同。
我的程序是用VC++寫的,這裏不貼詳細代碼了,只講識別方法。
先說phpwind驗證碼的識別方法:
具體分爲這幾步:
- 把圖片橫向等寬分爲4塊
- 找出每塊圖片中分佈最多的一種顏色
- 與已有樣本比較得出結果
因爲這個驗證碼的字符分佈幾乎是等寬的,所以我們首先把圖片切爲4份,這樣方便取出每一個字符。分成4塊後,通過每一塊中的顏色值對比可以很快得到 字符的特徵。因爲每一塊中字符的顏色值比雜點顏色值要多很多,而且字符是純色的,所以只要統計出最多的一種顏色,然後去除其它顏色就可以得到只有字符的幹 淨圖片。然後對圖片二值化(即構造一個二維數組對應圖片上有顏色的點,把有顏色的點的數組值置爲1,無顏色的置爲0),與樣本比對即可得到字符。當然首先 要得到樣本。樣本的製作與上面分析的步驟一致。經過測試,上面這種方法的識別率是100%的,不會有差錯。
discuz的驗證碼識別較之phpwind要稍難一點。因爲圖片帶有不太容易去掉的背景。字符也不是單純的字符,有陰影邊框,而且不等寬,位置不確定。我們具體分爲以下幾步:
- 去除背景色
- 分出每一個字符區域
- 用輪廓法(berg)得到特徵碼
- 與樣本比較得出結果
這個背景色是漸變的而字符的顏色是不變的。首先去除對角線上找不到相同顏色的點,然後統計出每一種顏色佔用的區域的寬度和高度。去除佔用區域高度小 於圖片總高度1/5或大於圖片總高度2/3的點,因爲一個字符不可能達到這種尺寸。再去除密度(即顏色點數/顏色所佔的區域寬高的積)小於15%的點。剩 下的就只有乾淨的字符的顏色點了。
把這些點分爲4份。(分割的辦法爲從左到右用一條豎直的線掃描,掃描線經過的連續區域就是字符區域。)分成4個字符塊後,我們就可以對每一個字符塊進行輪廓特徵取值。
什麼是輪廓法?我是由berg(berg是網易社區的牛人,對我幫助不少)那裏獲知驗證碼識別中的輪廓法。即將一個字模點陣,以四條直線由上下左右 4個方向向字符中心掃描,遇到點即停下,把每一條線通過的路徑長度記下。然後以比路徑長度和其它一些相關的參數得到正確的字符。
我稍微變換了一下輪廓法。即把4個方向上的路徑長度變爲波的形式。波峯記爲1,波谷記爲0,最後得到一個由1和0組成的特徵串,與樣本串比較即可得 到匹配結果。有幾個字符,如V和Y、H和M、4和6等,得到的特徵串可能是一樣的,這樣需要通過其它的一些參數來輔助得到結果。
下面是我計算的特徵串和字符的對照樣本: 10-10-10-10- X 1-1010-1-10101- W -1010--- W 1--1-101- T -1-1010-10- R --10-10- R 101-101-1010-1010- Q -1-101-1- P --1-1- P -10--1010- M -10-10-10- K 10-10-1-1- J 10-10--- J 101-101-1010-10- G --101-1- F --1010-- E 101-101-10101-101- C 101-10-10-10- C -1-10101-1- B ---- B 10101-101-101-101- 9 1---10- 9 -101--- 8 10--1-101- 7 -1-10-- 6 1-101-10-101- 4 1010---- 3 1010-101-1010-- 2 10--10-- 2 //below is equivocal 1-10-1-101- V //Y 10-10-10-- G //Q -10--10- H //M 10101-101-10101-101- 3 //8 101-101-101-101- 4 //6
上面這種方法,對discuz的默認驗證碼,即如圖所示的驗證碼識別正確率爲100%
上面對phpwind和discuz的驗證碼識別方法均沒有用到高級的算法,更加沒有用到人工智能的知識,不免有點遺憾,不過準確率相當高,也容易看懂。
在實際應用中可能遇到一些問題,比如discuz驗證碼可以在後臺設爲gif圖片格式。如何把gif動畫中那一個字符幀轉爲bmp圖片呢?下面是VC裏面的方法:
BOOL CCaptchaBreak::Gif2Bmp(CString &sPath) { ULONG_PTR GdippToken; GdiplusStartupInput GdippStart; GdiplusStartup(&GdippToken,&GdippStart,0); BSTR bsTemp = sPath.AllocSysString(); Bitmap bmp(bsTemp); ::SysFreeString(bsTemp);
int FrameCount,FramePos,size,pause; PropertyItem* pPropItem; GUID pageGuid; GUID* pDimID; UINT count;
count=bmp.GetFrameDimensionsCount();
pDimID=new GUID[count];
bmp.GetFrameDimensionsList(pDimID,count); FrameCount=bmp.GetFrameCount(&pDimID[0]);
if (1==FrameCount) //if is bmp then exit { delete[]pDimID; return FALSE; }
size=bmp.GetPropertyItemSize(PropertyTagFrameDelay); pPropItem=(PropertyItem*)malloc(size);
bmp.GetPropertyItem(PropertyTagFrameDelay,size,pPropItem); delete[]pDimID;
pageGuid=FrameDimensionTime; FramePos=0;
int iFontFramePos = 0; int iMaxPause = 0; while (FramePos
return TRUE; }
BOOL CCaptchaBreak::GetEncoderClsid(const WCHAR* format, CLSID* pClsid) { UINT num = 0; // number of image encoders UINT size = 0; // size of the image encoder array in bytes
ImageCodecInfo* pImageCodecInfo = NULL;
GetImageEncodersSize(&num, &size); if(size == 0) return FALSE;
pImageCodecInfo = (ImageCodecInfo*)(malloc(size)); if(pImageCodecInfo == NULL) return FALSE;
GetImageEncoders(num, size, pImageCodecInfo);
for(UINT j = 0; j < num; ++j) { if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 ) { *pClsid = pImageCodecInfo[j].Clsid; free(pImageCodecInfo); return TRUE; // Success } }
free(pImageCodecInfo); return FALSE; }
|