需验证码识别,对常用论坛的验证码识别的时候大家用来做群发是最合适不过了。一个非常有意义的参考
注:非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;
}