【www.shanpow.com--作文素材】
第一篇验证码图片:验证码图片识别
因朋友需求,对某网站的验证码图片进行自动识别,原以为是个复杂的问题,后来查看了网上的一些资料,总体思路上参考了:
http://www.cr173.com/html/16871_1.html
尝试用Delphi做了Demo,过程如下
1、获取到验证码图片生成的URL,如http://www.aaa.bbb.cn/ValidateCode.aspx;
2、使用TIdHTTP控件通过URL获取图片,由于URL获取验证码图片是以数据流形式传递过来的,所以处理非常方便,主要代码如下:
procedure TfrmMain.Button1Click(Sender: TObject);
var
ms: TMemoryStream;
pi : TPngImage;
begin
ms := TMemoryStream.Create;
pi := TPngImage.Create;
try
try
{ 获取校验图片,存入数据流中 }
IdHTTP1.Get(Edit1.Text, ms);
{ 从流中载入png图片 }
ms.Position := 0;
pi.LoadFromStream(ms);
{ 显示图片 }
Image1.Picture.Graphic := pi;
…
end;
识别主要分为三个步骤:第一步进行图片处理,最好形成二值化,第二步需要进行学习并保存特征码,第三步完成正常的识别功能。
3、图片处理
图片处理过程包括:
(1)将png图片转换为bmp图片,在Delphi下非常简单:
bmp1.Assign(Image1.Picture.Graphic);
(2)进行图片二值化
对于本次识别的图片,非常简单,如下图:
进行色彩分析,将相同颜色的像素进行计数后发现只有三种颜色:背景色、干扰色和字体蓝色。做二值化处理时就比较简单了,逐个处理像素点,将干扰色处理成背景色:
for w := 0 to ABmpDesc.Width -1 do
for h := 0 to ABmpDesc.Height -1 do
begin
c := ABmpDesc.Canvas.Pixels[w, h];
{ 不是字体颜色的都处理成背景色 }
if c <> AFontColor then
ABmpDesc.Canvas.Pixels[w, h] := ABkColor;
end;
效果如下:
(3)进行图片分割
其实花时间最多的就是这步了,开始时考虑字符之间存在间隙,所以算法上以2个字符间的间隙为标准划分,结果发现存在多个字符连接在一起的情况,如下图:
2个TT实际是连在一起的,中间没有分割间隙,所以无法区分。后来又考虑采用等宽字符方式进行切割(网上常用算法),但是碰到了验证码图片中的字符是不等宽的,还是不行。后采用了字符轨迹法,由于都是英文字符和数字,单个字符或数字的笔画都是连在一起的,就把所有连在一起的笔画内容识别为同一个字符。采用递归算法,取左上第一个字符的第一个像素,然后对该像素周围8个像素进行扫描,记录相邻的像素,并对符合字体颜色的像素再次递归运算,直到没有符合字体颜色的像素为止。效果还是可以,但是仍然没有解决2个字符连在一起的情况,最后跳出这个思维圈子,干脆将连在一起的2个或多个字符共同识别为一个整体,即TT就当是一个字符处理,问题基本得到解决。
(4)进行分割后图片特征码提取
特征码非常简单,采用从上往下,从左向右依次获取每个像素,如果是字体颜色像素则为1,否则为0。当然也可以采用其他算法的特征码,只要能确定唯一性即可。然后将特征码与字符关联,并保存起来。
function GetbmpFlag(Abmp: TBitmap; AFontColor : TColor): String;
var
w, h: Integer;
begin
Result := "";
{ 获取图片特征码 }
for h := 0 to Abmp.Height -1 do
for w := 0 to Abmp.Width -1 do
if Abmp.Canvas.Pixels[w, h] = AFontColor then
Result := Result + "0"
else
Result := Result + "1";
end;
4、学习
反复获取验证码图片,进行上述处理,并填写人工识别后的验证码,以便软件将字符对应的图片特征码与字符关联起来,我的学习界面如下:
将学习好的特征码保存到文件,以便在下次识别时载入。
5、正常识别
特征码学习差不多后保存在磁盘文件上,当正式识别时,载入该特征码,按照上述步骤进行处理:
(1)获取验证码图片
(2)二值化处理
(3)图片分割
(4)获取图片特征码
(5)根据图片特征码,在已学习的特征码中进行查找,找到后返回其对应的字符,将所有分割后图片的特征码对应的字符组合起来就是验证码。
上述工作真正的难点在于2个:
1、二值化图片
本次试验的图片比较简单,如果遇到非常复杂的图片,如:
且字体颜色还是随机的,就比较麻烦了,必须对所有像素进行颜色统计,取其最多的4个颜色,我的统计图如下:
颜色最多的4个即为字体颜色。但是也有特殊情况,如下图:
其统计图就比较杂乱了,如下图:
字体颜色统计被背景颜色掩盖了,按照上文提示,应采用HSL对色彩进行变化,然后进行统计,这项工作待有时间再进行。
2、图片切割
本例中的图片切割还是比较简单的,对于复杂的、不规则的图片,其切割可能更复杂。尤其是遇到多个字符上下部分位置有交叉,字符有随机大小,字符与字符之间有粘连时,这种图片切割更加复杂,这里不再讨论,今后有时间再试试。
第二篇验证码图片:用于验证码图片识别的类
最近写了几个网站的验证码图片自动识别程序,尽管每个网站的验证码图片都不相同,识别的方法有所差别。但写得多了,也总结出不少相同之处。今天抽空封装出一个基础类来,发现可以很好地重复利用,编写不同的验证码识别程序,效率提高了不少。好东东不能独享,现放出来供大家共同研究,请网友们妥善用之。 封装后的类使用很简单,针对不同的验证码,相应继承修改某些方法,即可简单几句代码就可以实现图片识别了: GrayByPixels(); //灰度处理 GetPicValidByValue(128, 4); //得到有效空间 Bitmap[] pics = GetSplitPics(4, 1); //分割 string code = GetSingleBmpCode(pics[i], 128); //得到代码串
具体使用,请参见我做的例子: 投票程序示例.exe 投票程序源码 (例子说明:使用进程投票,可自动清除Alert弹出窗口,可自动换IP,ADSL用户自行修改Restart.bat中第三行内容)
using System;using System.Collections.Generic;using System.Text;using System.Collections;using System.Drawing;using System.Drawing.Imaging;using System.Runtime.InteropServices;
namespace BallotAiying2{ public Bitmap bmpobj; public UnCodebase(Bitmap pic) { bmpobj = new Bitmap(pic); //转换为Format32bppRgb } /// /// 根据RGB,计算灰度值 /// /// ?name="posclr">Color值 /// 灰度值,整型 private int GetGrayNumColor(System.Drawing.Color posClr) { return (posClr.R * 19595 + posClr.G * 38469 + posClr.B * 7472) >> 16; } /// /// 灰度转换,逐点方式 /// public void GrayByPixels() { for (int i = 0; i < bmpobj.Height; i++) { for (int j = 0; j < bmpobj.Width; j++) { int tmpValue = GetGrayNumColor(bmpobj.GetPixel(j, i)); bmpobj.SetPixel(j, i, Color.FromArgb(tmpValue, tmpValue, tmpValue)); } } } /// /// 去图形边框 /// /// ?name="borderwidth"> public void ClearPicBorder(int borderWidth) { for (int i = 0; i < bmpobj.Height; i++) { for (int j = 0; j < bmpobj.Width; j++) { if (i < borderWidth || j < borderWidth || j > bmpobj.Width - 1 - borderWidth || i > bmpobj.Height - 1 - borderWidth) bmpobj.SetPixel(j, i, Color.FromArgb(255, 255, 255)); } } } /// /// 灰度转换,逐行方式 /// public void GrayByLine() { Rectangle rec = new Rectangle(0, 0, bmpobj.Width, bmpobj.Height); BitmapData bmpData = bmpobj.LockBits(rec, ImageLockMode.ReadWrite, bmpobj.PixelFormat);// PixelFormat.Format32bppPArgb); // bmpData.PixelFormat = PixelFormat.Format24bppRgb; IntPtr scan0 = bmpData.Scan0; int len = bmpobj.Width * bmpobj.Height; int[] pixels = new int[len]; Marshal.Copy(scan0, pixels, 0, len); //对图片进行处理 int GrayValue = 0; for (int i = 0; i < len; i++) { GrayValue = GetGrayNumColor(Color.FromArgb(pixels[i])); pixels[i] = (byte)(Color.FromArgb(GrayValue, GrayValue, GrayValue)).ToArgb(); //Color转byte } bmpobj.UnlockBits(bmpData); } /// /// 得到有效图形并调整为可平均分割的大小 /// /// ?name="dggrayvalue">灰度背景分界值 /// ?name="charscount">有效字符数 /// public void GetPicValidByValue(int dgGrayValue, int CharsCount) { int posx1 = bmpobj.Width; int posy1 = bmpobj.Height; int posx2 = 0; int posy2 = 0; for (int i = 0; i < bmpobj.Height; i++) //找有效区 { for (int j = 0; j < bmpobj.Width; j++) { int pixelValue = bmpobj.GetPixel(j, i).R; if (pixelValue < dgGrayValue) //根据灰度值 { if (posx1 > j) posx1 = j; if (posy1 > i) posy1 = i; if (posx2 < j) posx2 = j; if (posy2 < i) posy2 = i; }; }; }; // 确保能整除 int Span = CharsCount - (posx2 - posx1 + 1) % CharsCount; //可整除的差额数 if (Span < CharsCount) { int leftSpan = Span / 2; //分配到左边的空列 ,如span为单数,则右边比左边大1 if (posx1 > leftSpan) posx1 = posx1 - leftSpan; if (posx2 + Span - leftSpan < bmpobj.Width) posx2 = posx2 + Span - leftSpan; } //复制新图 Rectangle cloneRect = new Rectangle(posx1, posy1, posx2 - posx1 + 1, posy2 - posy1 + 1); bmpobj = bmpobj.Clone(cloneRect, bmpobj.PixelFormat); } /// /// 得到有效图形,图形为类变量 /// /// ?name="dggrayvalue">灰度背景分界值 /// ?name="charscount">有效字符数 /// public void GetPicValidByValue(int dgGrayValue) { int posx1 = bmpobj.Width; int posy1 = bmpobj.Height; int posx2 = 0; int posy2 = 0; for (int i = 0; i < bmpobj.Height; i++) //找有效区 { for (int j = 0; j < bmpobj.Width; j++) { int pixelValue = bmpobj.GetPixel(j, i).R; if (pixelValue < dgGrayValue) //根据灰度值 { if (posx1 > j) posx1 = j; if (posy1 > i) posy1 = i; if (posx2 < j) posx2 = j; if (posy2 < i) posy2 = i; }; }; }; //复制新图 Rectangle cloneRect = new Rectangle(posx1, posy1, posx2 - posx1 + 1, posy2 - posy1 + 1); bmpobj = bmpobj.Clone(cloneRect, bmpobj.PixelFormat); } /// /// 得到有效图形,图形由外面传入 /// /// ?name="dggrayvalue">灰度背景分界值 /// ?name="charscount">有效字符数 /// public Bitmap GetPicValidByValue(Bitmap singlepic, int dgGrayValue) { int posx1 = singlepic.Width; int posy1 = singlepic.Height; int posx2 = 0; int posy2 = 0; for (int i = 0; i < singlepic.Height; i++) //找有效区 { for (int j = 0; j < singlepic.Width; j++) { int pixelValue = singlepic.GetPixel(j, i).R; if (pixelValue < dgGrayValue) //根据灰度值 { if (posx1 > j) posx1 = j; if (posy1 > i) posy1 = i; if (posx2 < j) posx2 = j; if (posy2 < i) posy2 = i; }; }; }; //复制新图 Rectangle cloneRect = new Rectangle(posx1, posy1, posx2 - posx1 + 1, posy2 - posy1 + 1); return singlepic.Clone(cloneRect, singlepic.PixelFormat); } /// /// 平均分割图片 /// /// ?name="rownum">水平上分割数 /// ?name="colnum">垂直上分割数 /// 分割好的图片数组 public Bitmap [] GetSplitPics(int RowNum,int ColNum) { if (RowNum == 0 || ColNum == 0) return null; int singW = bmpobj.Width / RowNum; int singH = bmpobj.Height / ColNum; Bitmap [] PicArray=new Bitmap[RowNum*ColNum]; Rectangle cloneRect; for (int i = 0; i < ColNum; i++) //找有效区 { for (int j = 0; j < RowNum; j++) { cloneRect = new Rectangle(j*singW, i*singH, singW , singH); PicArray[i*RowNum+j]=bmpobj.Clone(cloneRect, bmpobj.PixelFormat);//复制小块图 } } return PicArray; } /// /// 返回灰度图片的点阵描述字串,1表示灰点,0表示背景 /// /// ?name="singlepic">灰度图 /// ?name="dggrayvalue">背前景灰色界限 /// public string GetSingleBmpCode(Bitmap singlepic, int dgGrayValue) { Color piexl; string code = ""; for (int posy = 0; posy < singlepic.Height; posy++) for (int posx = 0; posx < singlepic.Width; posx++) { piexl = singlepic.GetPixel(posx, posy); if (piexl.R < dgGrayValue) // Color.Black ) code = code + "1"; else code = code + "0"; } return code; } }
第三篇验证码图片:Discuz 验证码图片识别
Discuz 验证码图片识别
出处:西西整理 作者:佚名 日期:2012/9/5 11:26:24 [大 中 小] 评论: 0 | 我要发表看法
Crossday Discuz! Board(简称 Discuz!)是北京康盛新创科技有限责任公司推出的一套通用的社区论坛软件系统
官方网站:
http://www.discuz.net/
中国内有相当数量的论坛是采用的 Discuz 的程序,一般在注册,发帖的时候都会有图片的验证码,如果我们要通过编写程序来批量注册论坛账号、批量发帖那就必须得用到这个验证码的识别功能。
这编文章只是告诉大家一个原理,至于如何用代码去实现就需要大家自已去动手了。
Gif动画验证码破解
1、分析Gif动画,得到总帧数,和每帧的相关信息
2、取出延迟时间最长的那一帧
3、用第一行的每个像素颜色来去除背景(要限制去除范围,不然可能去掉文字)
4、使用Closing降噪、阀值处理得到比较整洁的黑白验证码
5、利用字符间空白分割字符
6、提取样例特征进行机器学习
7、样例200个的情况下,识别率可以达到>80%,如果继续学习,识别率可以更高。
复杂背景的验证码破解
首先我们要去除它的背景,对于这样稍微复杂的背景,用过去的方法很难做到,上图的例子还不是很明显,我发现很多图片背景色和字母色近似,而且字母颜色是不断变化的,背景也是不断变化的
那我初始的想法是找到图片中使用颜色最多的方法,于是我们用HSL表示各点颜色,接着进行统计,得到最大的几个峰值,这里便是图片中几个最丰富的颜色的L值得累加值
其余的都可以认为是噪音,我们对每个峰值进行分割,得到如下图片
你看这样就把单个颜色图片分割出来了,接下来就是找到图片中除去黑色和白色后的图片
然后进行灰化处理,阀值处理,降噪,得到
接着根据边界检测出来的最左侧x位置,来排序字母顺序
接下来的事情就轻车熟路了,把图片转成标准模板,通过少量学习就达到了95%以上的识别率
c:15 j:8 8:7 t:9 9:4 x:7 4:6 2:4 h:7 f:8 e:18 b:5 y:3 k:4 w:3 g:5 3:5 7:6 r:2 m:3 q:4 v:2 p:3 6:2以上数据表示 c学习15次 j学习8次…
只要字符不粘连,大部分验证码干扰技术都是可以有办法,所以为什么google验证码看起来很简单,但是没有人能够很好的破解它得原因。
补充,rise在留言中发现有一些字符加入杂点的问题,由于这种验证码不是很普遍,稍微做了研究
CY3E 这个图片3字中有杂点,其他没有,按照文章中介绍的办法,怎么知道这个3不是像其他颜色杂点一样的图片呢?我觉得需要加入一个步骤,就是对每次过滤颜色生成出来的图片,进行填充找到3的杂点原图:然后我们进行算法填充
这个图片与其他全部是杂点的图片之间的差别进行过滤,我考虑可以通过以下方法:1、连贯点的宽度2、连贯点的个数这样剩下的就只剩下CY3E的过滤后的图片
至于字符倾斜的问题,我觉得完全可以在机器学习过程中,我们自己旋转正在学习的图片一定角度,例如从-10到+10度,只不过这样的学习库会大一些,但是就10个数字的验证码来说,这点性能损失应该可以忽略不计。