马建华的博客

提供:软件开发、数据采集、搜索引擎、数据备份及企业信息化解决方案!
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

简单验证码识别 (转)

Posted on 2009-07-05 11:56  马建华  阅读(1759)  评论(0编辑  收藏  举报
  最近几天比较空闲,于是也模仿网上的文章搞了搞验证码识别,当然是那些工整,规则,无码,无干扰,灰常纯净与单纯的验证码....
  当然也看了下网上找到的复杂的验证码(车牌号)识别资料,无奈做为一个半路出家,江湖四流的程序员,面对那么多数学公式只能做罢,一直以自己英语不好而痛心疾首,今天又认识到数学更是障碍, 记的前阵只有人说一个英语不好的程序员能走多远,现在我知道,一个英语不好但是数学很好的程序员能走很远,比英语好但数学不好的程序员要远多了,哎....

 

 步骤

      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