1. 程式人生 > >c#破解驗證碼示例程式碼

c#破解驗證碼示例程式碼

驗證碼破解是一個很大的課題,也有很多種不同的方式,下面程式碼採用的方法是首先準備驗證碼圖片的樣本圖片。然後將驗證碼圖片做灰度處理,並根據字元間距切割驗證碼圖片,最後將切割後的驗證碼小圖和樣本之間做餘袨值比較,從而計算出驗證碼圖片的字元。

如下是c#程式碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.IO;
using System.Configuration;

namespace ConsoleApplication4
{
    /// <summary>
    /// 根據樣本做驗證碼破解
    /// 
    /// 需要在.config檔案中的appSettings配置節中新增key為sampleOcr.sampleDir value設定為樣本圖片所在路徑
    /// 驗證碼:https://investorservice.cfmmc.com/ https://investorservice.cfmmc.com/veriCode.do?t=1335521167762&ip=202.99.16.22
    /// 
    /// outofmemory.cn 20120427
    /// 100個樣例準確數為88個,錯誤主要發生在389這三個字元的混淆上
    /// </summary>
    public abstract class SampleOcr
    {
        /// <summary>
        /// 灰度中間值
        /// </summary>
        static int MiddleGrayValue = 200;

        /// <summary>
        /// 分割圖片的差異容忍度
        /// </summary>
        static int ColorToleranceForSplit = 30;

        /// <summary>
        /// 樣本字典
        /// </summary>
        static Dictionary<string, Bitmap> _samples;

        /// <summary>
        /// 破解驗證碼
        /// </summary>
        /// <param name="bm">驗證碼圖片</param>
        /// <returns>驗證碼文字</returns>
        public static string Ocr(Bitmap bm)
        {
            //做灰度處理
            GrayByPixels(bm);

            bm = RemoveVerticalSpaceRegion(bm);

            Bitmap[] splitBms = SplitBitmaps(bm);

            char[] result = new char[splitBms.Length];
            for (int i = 0; i < splitBms.Length; i++)
            {
                result[i] = OcrChar(splitBms[i]);
                splitBms[i].Dispose();
            }
            return new string(result);
        }

        /// <summary>
        /// 分割圖片
        /// </summary>
        /// <param name="bm">圖片</param>
        /// <returns>分割後的圖片物件</returns>
        static Bitmap[] SplitBitmaps(Bitmap bm)
        {
            //找出垂直分割線
            List<int> removeXs = new List<int>();
            for (int x = 0; x < bm.Width; x++)
            {
                bool hasDiffPoint = false;
                Color color = Color.White;
                for (int y = 0; y < bm.Height; y++)
                {
                    if (y == 0)
                    {
                        color = bm.GetPixel(x, y);
                    }
                    else
                    {
                        Color currentColor = bm.GetPixel(x, y);
                        int diff = CalculateColorDifference(currentColor, color);
                        if (diff > ColorToleranceForSplit)
                        {
                            hasDiffPoint = true;
                            break;
                        }
                        // color = currentColor;
                    }
                }

                if (!hasDiffPoint)
                {
                    removeXs.Add(x);
                }
            }

            //根據空白區域,計算各個字元的點陣圖          
            List<Rectangle> charRects = new List<Rectangle>();
            for (int i = 1; i < removeXs.Count; i++)
            {
                int diff = removeXs[i] - removeXs[i - 1];
                if (diff > 5)
                {
                    if (diff >= 20)
                    {
                        Rectangle rect = new Rectangle(removeXs[i - 1], 0, diff / 2, bm.Height);
                        charRects.Add(rect);

                        rect = new Rectangle(removeXs[i - 1] + diff / 2, 0, diff / 2, bm.Height);
                        charRects.Add(rect);
                    }
                    else
                    {
                        Rectangle rect = new Rectangle(removeXs[i - 1], 0, diff, bm.Height);
                        charRects.Add(rect);
                    }
                }
            }

            int count = charRects.Count;
            Bitmap[] charBms = new Bitmap[count];
            int charBmIndex = 0;
            foreach (Rectangle item in charRects)
            {
                Bitmap bmChar = bm.Clone(item, bm.PixelFormat);
                charBms[charBmIndex] = bmChar;
                charBmIndex += 1;
            }
            return charBms;
        }

        /// <summary>
        /// 解析字元
        /// </summary>
        /// <param name="bm">分割後的小圖</param>
        /// <returns>字元</returns>
        static char OcrChar(Bitmap bm)
        {
            Dictionary<string, Bitmap> samples = LoadSamples();

            double diff = .0;
            string mayBe = null;
            foreach (string key in samples.Keys)
            {
                double diffRate = CalcImageDiffRate(samples[key], bm);
                if (diffRate == 1)
                    return key[0];

                if (diffRate > diff)
                {
                    mayBe = key;
                    diff = diffRate;
                }
            }

            if (mayBe == null) throw new ApplicationException();

            return mayBe[0];
        }

        /// <summary>
        /// 載入樣本字典
        /// </summary>
        /// <returns>樣本字典</returns>
        private static Dictionary<string, Bitmap> LoadSamples()
        {
            if (_samples == null)
            {
                _samples = new Dictionary<string, Bitmap>();
                string sampleDir = ConfigurationManager.AppSettings["sampleOcr.sampleDir"] ?? @"D:\SampleOcr\samples";
                DirectoryInfo dirInfo = new DirectoryInfo(sampleDir);
                FileInfo[] files = dirInfo.GetFiles("*.jpg");
                foreach (FileInfo item in files)
                {
                    Bitmap bm = (Bitmap)Bitmap.FromFile(item.FullName);
                    string key = Path.GetFileNameWithoutExtension(item.FullName);
                    _samples.Add(key, bm);
                }
            }

            return _samples;
        }

        /// <summary>
        /// 根據RGB,計算灰度值
        /// </summary>
        /// <param name="posClr">Color值</param>
        /// <returns>灰度值,整型</returns>
        static int GetGrayNumColor(System.Drawing.Color posClr)
        {
            return (posClr.R * 19595 + posClr.G * 38469 + posClr.B * 7472) >> 16;
        }

        /// <summary>
        /// 灰度轉換,逐點方式
        /// </summary>
        static void GrayByPixels(Bitmap bm)
        {
            for (int i = 0; i < bm.Height; i++)
            {
                for (int j = 0; j < bm.Width; j++)
                {
                    int tmpValue = GetGrayNumColor(bm.GetPixel(j, i));
                    bm.SetPixel(j, i, Color.FromArgb(tmpValue, tmpValue, tmpValue));
                }
            }
        }

        /// <summary>
        /// 刪除垂直方向上的空白區域
        /// </summary>
        /// <param name="bm">源圖片</param>
        /// <returns>刪除空白之後的圖片</returns>
        static Bitmap RemoveVerticalSpaceRegion(Bitmap bm)
        {
            int topSpaceHeight = 0;
            for (int y = 0; y < bm.Height; y++)
            {
                bool hasDiffPoint = false;
                Color color = Color.White;
                for (int x = 0; x < bm.Width; x++)
                {
                    if (x == 0)
                    {
                        color = bm.GetPixel(x, y);
                    }
                    else
                    {
                        Color currentColor = bm.GetPixel(x, y);
                        int diff = CalculateColorDifference(currentColor, color);
                        if (diff > ColorToleranceForSplit)
                        {
                            hasDiffPoint = true;
                            break;
                        }
                    }
                }

                if (hasDiffPoint)
                {
                    break;
                }
                else
                {
                    topSpaceHeight += 1;
                }
            }

            int bottomSpaceHeight = 0;
            for (int y = bm.Height - 1; y > 0; y--)
            {
                bool hasDiffPoint = false;
                Color color = Color.White;
                for (int x = 0; x < bm.Width; x++)
                {
                    if (x == 0)
                    {
                        color = bm.GetPixel(x, y);
                    }
                    else
                    {
                        Color currentColor = bm.GetPixel(x, y);
                        int diff = CalculateColorDifference(currentColor, color);
                        if (diff > ColorToleranceForSplit)
                        {
                            hasDiffPoint = true;
                            break;
                        }
                        color = currentColor;
                    }
                }

                if (hasDiffPoint)
                {
                    break;
                }
                else
                {
                    bottomSpaceHeight += 1;
                }
            }

            Rectangle rectValid = new Rectangle(0, topSpaceHeight, bm.Width, bm.Height - topSpaceHeight - bottomSpaceHeight);
            Bitmap newBm = bm.Clone(rectValid, bm.PixelFormat);
            bm.Dispose();
            return newBm;
        }

        private static double CalcImageDiffRate(Bitmap bmSample, Bitmap bmCalc)
        {
            int[] eSample = new int[bmSample.Height];
            int[] eCalc = new int[bmSample.Height];
            for (int y = 0; y < bmSample.Height; y++)
            {
                eSample[y] = GetHorizontalValue(bmSample, y);
                eCalc[y] = GetHorizontalValue(bmCalc, y);
            }
            return GetCosine(eSample, eCalc);
        }

        /// <summary>
        /// 獲得向量的cos值
        /// </summary>
        /// <param name="e1"></param>
        /// <param name="e2"></param>
        /// <returns></returns>
        static double GetCosine(int[] e1, int[] e2)
        {
            double fenzi = 0;
            for (int i = 0; i < e1.Length; i++)
            {
                fenzi += e1[i] * e2[i];
            }

            double fenmuLeft = 0;
            double fenmuRight = 0;
            for (int i = 0; i < e1.Length; i++)
            {
                fenmuLeft += e1[i] * e1[i];
                fenmuRight += e2[i] * e2[i];
            }

            double fenmu = Math.Sqrt(fenmuLeft) * Math.Sqrt(fenmuRight);
            if (fenmu == 0.0) return 0;

            return fenzi / fenmu;
        }

        /// <summary>
        /// 計算水平方向上的差異點數
        /// </summary>
        /// <param name="bm">點陣圖</param>
        /// <param name="y">y座標值</param>
        /// <returns>差異點數</returns>
        private static int GetHorizontalValue(Bitmap bm, int y)
        {
            if (y >= bm.Height) return 0;
            int val = 0;
            for (int x = 0; x < bm.Width; x++)
            {
                Color color = bm.GetPixel(x, y);

                int grayVal = GetColorGrayValue(color);
                if (grayVal > MiddleGrayValue)
                {
                    val |= (1 << x);
                }
            }
            return val;
        }

        static int GetColorGrayValue(Color color)
        {
            return (int)(.299 * color.R + .587 * color.G + .114 * color.B);
        }

        /// <summary>
        /// 計算顏色之間的差值,這個只是一個簡單的計算,真正的色差計算很複雜
        /// </summary>
        /// <param name="colorA">A色</param>
        /// <param name="colorB">B色</param>
        /// <returns>差值</returns>
        static int CalculateColorDifference(Color colorA, Color colorB)
        {
            int diff = GetColorGrayValue(colorA) - GetColorGrayValue(colorB);
            return Math.Abs(diff);
        }
    }
}