最近几天比较空闲,于是也模仿网上的文章搞了搞验证码识别,当然是那些工整,规则,无码,无干扰,灰常纯净与单纯的验证码....
当然也看了下网上找到的复杂的验证码(车牌号)识别资料,无奈做为一个半路出家,江湖四流的程序员,面对那么多数学公式只能做罢,一直以自己英语不好而痛心疾首,今天又认识到数学更是障碍, 记的前阵只有人说一个英语不好的程序员能走多远,现在我知道,一个英语不好但是数学很好的程序员能走很远,比英语好但数学不好的程序员要远多了,哎....
步骤
1.获取验证码图片
2.灰度化与二值化图片
3.切割图片
4.建立特征库(这里使用每个像素二值化后的0,1字符串)
5.使用特征库来识别图片
1.获取图片:
HttpWebRequest req = HttpWebRequest.Create("http://www.0576sy.cn/vcode.asp") as HttpWebRequest;
Bitmap bmp=new Bitmap( req.GetResponse().GetResponseStream());
2.灰度化:
首先解释下所谓的黑白图片。其实更准确地应该叫256级灰度图。当一个颜色点的R=G=B时,就是我们所谓的“灰色”。由于RGB的取值范围在[0,255],所以一共只有256种可能。
所以彩色图片转为黑白图片的原理非常简单。只要扫描彩图的每一点,让输出图对应点的R=G=B就成了。现在问题的关键就是如何取值了。
一般有两种,一种是彩图RGB三分量的算数平均值,另一种是加权平均值。加权平均是考虑到人类眼睛对不同分量的敏感程度。
代码1(使用指针)
/** <summary>
/// 变成黑白图
/// </summary>
/// <param name="bmp">原始图</param>
/// <param name="mode">模式。0:加权平均 1:算数平均</param>
/// <returns></returns>
private Bitmap ToGray(Bitmap bmp,int mode)
{
if (bmp == null)
{
return null;
}
int w = bmp.Width;
int h = bmp.Height;
try
{
byte newColor = 0;
BitmapData srcData = bmp.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
unsafe
{
byte* p = (byte*)srcData.Scan0.ToPointer();
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
if (mode == 0) // 加权平均
{
newColor = (byte)((float)p[0] * 0.114f + (float)p[1] * 0.587f + (float)p[2] * 0.299f);
}
else // 算数平均
{
newColor = (byte)((float)(p[0] + p[1] + p[2]) / 3.0f);
}
p[0] = newColor;
p[1] = newColor;
p[2] = newColor;
p += 3;
}
p += srcData.Stride - w * 3;
}
bmp.UnlockBits(srcData);
return bmp;
}
}
catch
{
return null;
}
}
代码二:
使用Bitmap的 GetPixel与SetPixel ,验证码都是小图片,像素不多因此一般应用速度可以接受
public void GrayByPixels(Bitmap bmp)
{
for (int i = 0; i < bmp.Height; i++)
{
for (int j = 0; j < bmp.Width; j++)
{
Color pixel = bmp.GetPixel(j, i);
int tmpValue = (pixel.R * 19595 + pixel.G * 38469 + pixel.B * 7472) >> 16;
//除于2 ^ 16 次方,也就是按加权方式进行
bmp.SetPixel(j, i, Color.FromArgb(tmpValue, tmpValue, tmpValue));
}
}
}
3.切割:
代码1: 找出图片最左,最上,最右,最下的四个有效果点,按照这个矩形切割
/// <summary>
/// 得到有效图形,图形由外面传入
/// </summary>
/// <param name="dgGrayValue">灰度背景分界值</param>
/// <param name="CharsCount">有效字符数</param>
/// <returns></returns>
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);
}
固定切割比较简单,使用Bitmap.Clone 就可以完成,具体查看Clone的函数声明
4.建立特征库: 这里使用矩阵来记录
/// <summary>
/// 返回灰度图片的点阵描述字串,1表示灰点,0表示背景
/// </summary>
/// <param name="singlepic">灰度图</param>
/// <param name="dgGrayValue">背前景灰色界限</param>
/// <returns></returns>
public static 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;
}
最后每个切割出来的图片都会得出 011000111.... 这样的字符串,按0~9的顺序分别建立特征库即可
看这个验证码:
这个好象是动易早年asp产品中的一个验证码,其中的4个数字多是固定出现的,图片是40x10(像素),每个数字站10x10个像素,不过上面加入了少量的干扰像素,干扰像素很少最多一次刷出了6个点, 另外这些噪音很难去处,因为数字本身的像素就很稀薄,用网上找的"中值滤波"什么的一搞数字就没了,再考虑单独出现的像素基本在1,2个点,其他4,5个点都连在数字上,那么对比一个无干扰的数字图片段(10x10 ---100个像素),有干扰点所造成的像素差异应该在6%以内, 后来又对比了全部无干扰数字图片段的(0,1,2.....9等标准数字) 像素二值化后的0,1字符串,发现他们之间最接进的是 3,跟8,字符串中有93%的位是一样的, 基于上面分析于是选择94%作为分界点,将采集到的图片二值化后的字符串跟标准库中的字符串进行对比,如果两者有94%(这里就是94个像素)相同就当这两个图片是一样的.
参考:
车牌识别及验证码识别的一般思路
http://blog.csdn.net/gisfarmer/archive/2009/02/05/3863630.aspx
C# 图片处理之:彩色图片转为黑白图
http://blog.csdn.net/ki1381/archive/2007/03/04/1520544.aspx
用于验证码图片识别的类(C#源码)
http://www.cnblogs.com/yuanbao/archive/2007/09/25/905322.html