您的位置:首页 > 大数据 > 人工智能

web数据采集核心技术分享系列(三)如何破解验证码?图像分析?特征匹配?人工智能?第三方集成?...哪个最强大?

2012-08-06 16:16 1016 查看
先加个目录,方便大家查看

web数据采集核心技术分享系列(一)做一个强大的web数据采集系统,你需要什么?

web数据采集核心技术分享系列(二)如何提取信息?字符串?正则?xpath?xslt?自定义?...什么才是王道?

web数据采集核心技术分享系列(三)如何破解验证码?图像分析?特征匹配?人工智能?第三方集成?...哪个最强大?

web数据采集核心技术分享系列(四)利用神经网络实现网页验证码破解

应各位热心看客的要求建了个QQ群:254764602,欢迎大家加群一起讨论,互相学习进步。

加群请输入暗号“数据采集”,否则不加

速度进入主题,这次的话题有点大,也有点难度,所以可能一篇说不完,先写一篇,回头根据大家的反馈我再写第二篇。

道高一尺魔高一丈,在验证码这个领域,道高一尺不难,魔高一丈非常难,所以我们就通常的验证码来做讨论,比较特殊的或者变态的验证码就不做深入探讨了。

一个普通的验证码通常是一个图片,有几个字符,然后有一些背景色,前景色,杂点(俗称噪点),干扰线,字符可能会有倾斜,扭曲,粘连,变形,甚至手写体,破解的过程总结起来就是一句话,去除干扰,简化特征,匹配特征,得到验证码,我不是写书的,不能面面俱到,我们从简单点的开始,看图说话,从下图可以看出,最后一步猜验证码的方法有三个,分别是简单的图像分析+特征匹配,基于神经网络的人工智能特征匹配,以及采用第三方google组件继承的方式,更强大的方式依赖于集成多个第三方类库(包含C以及C++代码)的实现,更为复杂,为了方便大家理解,先从第一种看起



第一步,获取验证码图片

在web数据采集过程中,如果采用获取网页源文件的方式,获取到图片地址在获取图片,应该不会有问题,如果借助浏览器获取到网页,再得到验证码地址,再去获取图片,则会导致问题,因为一般验证码的地址都是随机生成的,再次访问会得到另外一个验证码,所以借助浏览器的童鞋们,请直接从浏览器中获取图片。

第二部,变形

对于倾斜,字体变形的验证码,不做变形还原是很难继续处理的,所以必须变形,变形的原理针对不同的变形会有不同,没有哪一个方法可以包治百病且药到病除,所以我们也针对性讨论,比如对于倾斜, 要获取到字符区域四个角,然后计算倾斜四个边的倾斜角度,然后再向想法方向拉伸(我不是学计算机的,也不是学图形算法的,这些都是我的个人经验,说的不对还请不吝赐教)

贴一段代码给大家

1 Bitmap output = input;

2 int x = input.Width;
3 int y = input.Height;
4 int startPointsCount = 10;
5 int[] yBlackCount = new int[y];
6
7 Point leftTop = new Point(0, 0);
8 Point leftBottom = new Point(0, 0);
9 Point rightTop = new Point(0, 0);
Point rightBottom = new Point(0, 0);

for (int j = 0; j < y; j++)
{
for (int i = 0; i < x; i++)
{
if (input.GetPixel(i, j).R == 0)
{
yBlackCount[j]++;
}
}
}

for (int j = 1; j < y - 1; j++)
{
Point letterStart = new Point(0, 0);
Point letterEnd = new Point(0, 0);
for (int i = 1; i < x - 1; i++)
{
if (input.GetPixel(i, j).R == 0)
{
letterStart = new Point(i, j);
break;
}
}
for (int i = x - 2; i > 0; i--)
{
if (input.GetPixel(i, j).R == 0)
{
letterEnd = new Point(i, j);
break;
}
}
if (yBlackCount[j] > startPointsCount && yBlackCount[j + 1] > yBlackCount[j] && leftTop.Y == 0)
{
//top of letters
leftTop = letterStart;
rightTop = letterEnd;
}
if (leftTop.Y > 0 && yBlackCount[j + 1] < startPointsCount && yBlackCount[j] > yBlackCount[j + 1] && leftBottom.Y == 0)
{
//botton of letters
leftBottom = letterStart;
rightBottom = letterEnd;
}
}
if (leftTop.Y != 0 && leftBottom.Y != 0)
{
int lDistince = ((leftBottom.X - leftTop.X) * y) / (leftBottom.Y - leftTop.Y);
int rDistince = ((rightBottom.X - rightTop.X) * y) / (rightBottom.Y - rightTop.Y);
if (lDistince > 20)
{
lDistince = 20;
}
if (lDistince < -20)
{
lDistince = -20;
}
if (rDistince > 20)
{
rDistince = 20;
}
if (rDistince < -20)
{
rDistince = -20;
}

Graphics g = Graphics.FromImage(output);
Brush b = new TextureBrush(source);

//g.FillRectangle(b, this.ClientRectangle);
g.FillRectangle(b, rectangle);

Point[] destinationPoints = {
new Point(lDistince, 0), // destination for upper-left point of original
new Point(x+rDistince, 0), // destination for upper-right point of original
new Point(0, y)}; // destination for lower-left point of original
g.DrawImage(source, destinationPoints);
}

return output;

其他的变形暂且不在这里深入,要针对具体变形才能深入展开。

3,继续我们简单验证码处理的流程,说实话web数据采集中任何一点都可以拿出来单独写一个系列,要想做一个强大的采集系统,不是一个人花一两个月可以完成的,这里面的艰难只有你真正去做了,真正拿给客户运行才能体会到,如果各位大牛都能无私的把牛逼的解决方案和源代码开源,那么程序员的生活就会容易很多,大家都是一条船上的同路人,互相扶持多好。不好意思废话几句,继续说灰度化,这个网上代码很多,为了方便大家,我还是贴出来,如果大家觉得简单代码没必要贴,下次我就不贴了。

protected static Color Gray(Color c)

{
int rgb = Convert.ToInt32((double)(((0.3 * c.R) + (0.59 * c.G)) + (0.11 * c.B)));
return Color.FromArgb(rgb, rgb, rgb);
}

4.转化为黑白图片,俗称二值话,其实3和4都是为了简化特征,为后续处理打好基础,二值化的关键步骤是取得门限值,或者叫阀值,就是说什么样的点应该看做黑点,什么样的点应该看做白点,上代码(为啥总是第一行或者最后一行就没格式呢,谁告诉我?)

Bitmap output = new Bitmap(input.Width, input.Height);
int tv = ComputeThresholdValue(input);
int x = input.Width;
int y = input.Height;
int blackCount = 0;
int whiteCount = 0;
int nearDots;
for (int i = 0; i < x; i++)
{
for (int j = 0; j < y; j++)
{
//suppose the background is white,set the border to white
if (i == 0 || i == input.Width - 1 || j == 0 || j == input.Height - 1)
{
output.SetPixel(i, j, Color.White);
whiteCount++;
continue;
}
//white point, background
if (input.GetPixel(i, j).R >= tv)
{
output.SetPixel(i, j, Color.White);
whiteCount++;
}
//black point, char
else
{
output.SetPixel(i, j, Color.Black);
blackCount++;
}
}
}

5.切分,切分的目的是把一个字符串中的单个字符找出来,单个字符的特征处理起来就要简单很多,切分的原理就是主要是定位到字符边界,然后切分图片,经过上面几个步骤之后,图片上是一个个的黑白字符,假设白色为底色,黑色为字符,那么对黑色点在XY坐标系里的分布进行统计,即可得到字符边界。

/// <summary>

/// Split picture, and get the codes into a list
/// </summary>
/// <param name="map"></param>
/// <param name="count"></param>
/// <returns></returns>
public static List<Bitmap> Split(Bitmap map)
{
List<Bitmap> resultList = new List<Bitmap>();

int x = map.Width;
int y = map.Height;
int maxNoisyWidth = 4;//code with width nor more thal 4 is treated as noisy code
int maxNoisyCount = 4; //points no more than 4 is treated as noisy points

//black is char
//black points count per column
int[] xBlackCount = new int[x];
for (int i = 0; i < x; i++)
{
for (int j = 0; j < y; j++)
{
if (map.GetPixel(i, j).R == 0)
{
xBlackCount[i]++;
}
}
}
//white points count per column
int[] yBlackCount = new int[y];
for (int j = 0; j < y; j++)
{
for (int i = 0; i < x; i++)
{
if (map.GetPixel(i, j).R == 0)
{
yBlackCount[j]++;
}
}
}

//split picture
bool charFlag = false;
int xStart = 0;
int xEnd = 0;
int yStart = 0;
int yEnd = 0;
for (int j = 0; j < yBlackCount.Length; j++)
{
if (yBlackCount[j] >= maxNoisyCount && charFlag == false)
{
//start to scan the top of all char
yStart = j;
charFlag = true;
}
if (yBlackCount[j] < maxNoisyCount && charFlag == true)
{
//end of scan the bottom of all char
yEnd = j;
charFlag = false;
}
if (yStart != 0 && yEnd != 0)
{
//got the top and bottom of all char
break;
}
}
for (int i = 0; i < xBlackCount.Length; i++)
{
if (xBlackCount[i] >= maxNoisyCount && charFlag == false)
{
//start to scan a char
xStart = i;
charFlag = true;
}
if (xBlackCount[i] < maxNoisyCount && charFlag == true)
{
//end of scan a char
xEnd = i;
charFlag = false;
}
if (xStart != 0 && xEnd != 0)
{
//got the start and end of a char,check whether it's noise
if (xEnd - xStart < maxNoisyWidth)
{
//reset start and end
xStart = 0;
xEnd = 0;
continue;
}
//create new map for a char
Bitmap newMap = new Bitmap(xEnd - xStart + 1, yEnd - yStart + 1);
for (int ni = xStart; ni <= xEnd; ni++)
{
for (int nj = yStart; nj <= yEnd; nj++)
{
newMap.SetPixel(ni - xStart, nj - yStart, map.GetPixel(ni, nj));
}
}
newMap = new Bitmap(newMap, 16, 16);
resultList.Add(newMap);
//reset start and end
xStart = 0;
xEnd = 0;
}
}
return resultList;
}

6.切分完成之后,我们得到一组图片,每一个代表一个字符,然后进行特征计算,这里的思路首先把图片转化为一个矩阵,矩阵是啥不知道?查一下吧,还是有必要的,然后使用幂法求一个方阵的最大特征值和它所对应的特征向量, 向量也不知道??我敢判定你肯定跟我一样,大学数据没及格过。哈哈。然后要把该向量与我们知识库(一堆向量,每个向量都对应一个字符,这个知识库需要通过人工对程序进行训练得到,也就是你告诉程序,这个向量是2,那个是3,后面会讲)里面的向量进行比较,求出向量之间的举例,与其距离最小的向量就表明其特征最相近,也就是说,这两个字符很像,我们就认为他们是同一个字符,从而得出判断结果。

input = new Bitmap(input, 16, 16);

Double[,] doublemap = new Double[input.Width, input.Height];
for (int i = 0; i < input.Width; i++)
{
for (int j = 0; j < input.Height; j++)
{
if (input.GetPixel(i, j).R == 255)
{
doublemap[i, j] = Convert.ToDouble(1);
}
else
{
doublemap[i, j] = Convert.ToDouble(0);
}
}
}

Double[] W = new double[input.Width]; ;
Double max = 0;
MatrixLab mat = new MatrixLab(input.Width, 0.001, doublemap);
mat.returnResult(ref W, ref max);

SampleVector vector = new SampleVector(W, "");
Double minDistance = Double.MaxValue;
SampleVector similarVector = null;
foreach (SampleVector target in this._studyList)
{
double distance = _metric.Compute(vector, target);
if (distance < minDistance)
{
similarVector = target;
output = target.Code;
minDistance = distance;
}
}

最简单的原理先讲到这里,下一篇我们深入点讲解,开头说了,现写一篇,回头根据大家的反馈我再写第二篇,欢迎大家交流

本系列 web数据采集核心技术分享注重分享思路,所有的代码都是为了配合思路的讲解,想要关注如何搭建一个完整的采集系统的童鞋稍安勿躁,后续会关注这个话题,不想关注思路,只想复制代码,F5运行,点鼠标进行数据抓取的童鞋请理解。

PS: 因本人能力有限,虽在web数据采集领域奋战多年,却也不可能在web数据采集的各个方面都提供最牛逼的解决方案和思路,还请各位看官本着互相交流学习,一起进步成长的态度来批评指正,欢迎留言。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐