因爲公司的需求,這幾天研究下了驗證碼識別。對驗證碼識別大體分這幾個過程,第一步獲取驗證碼,第二對驗證碼處理,若是顏色單一沒什麼背景雜色就直接二值化處理,注意闕值,有干擾線的把干擾線和背景去掉,最終變爲背景爲白色,驗證碼前景色爲黑色。第三步就是切割,把驗證碼從圖片中切割出來,第四創建識別庫,切割後的圖片分類存入識別庫,讓後須要讓程序學習一些驗證碼後,識別庫就有了樣例。第四步就是那當前是別的驗證碼和識別庫的驗證碼進行比對,達到識別驗證碼的結果。數組
識別類源碼:學習
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; using System.Net; using System.Collections; using System.IO; namespace CheckCodeRecognizeLib { public class CheckCodeRecognize { #region 成員變量 //色差閥值 越小消除的雜色越多 private double threshold = 150; //二值閥值 越大效果越不明顯 private double ezFZ = 0.6; //背景近似度閥值 private double bjfz = 80; //圖片路徑 private string imgPath = string.Empty; //每一個字符最小寬度 public int MinWidthPerChar = 7; //每一個字符最大寬度 public int MaxWidthPerChar = 18; //每一個字符最小高度 public int MinHeightPerChar = 10; //學習庫保存的路徑 private readonly string samplePath = AppDomain.CurrentDomain.BaseDirectory + "Sample\\"; #endregion #region 圖片處理 /// <summary> /// 對傳入的圖片二值化 /// </summary> /// <param name="bitmap">傳入的原圖片</param> /// <returns>處理事後的圖片</returns> private Bitmap EZH(Bitmap bitmap) { if (bitmap != null) { var img = new Bitmap(bitmap); for (var x = 0; x < img.Width; x++) { for (var y = 0; y < img.Height; y++) { Color color = img.GetPixel(x, y); if (color.GetBrightness() < ezFZ) { img.SetPixel(x, y, Color.Black); } else { img.SetPixel(x, y, Color.White); } } } return img; } return null; } /// <summary> /// 去背景 /// 把圖片中最多的一部分顏色視爲背景色 選出來後替換爲白色 /// </summary> /// <param name="bitmapImg">將要處理的圖片</param> /// <returns>返回去過背景的圖片</returns> private Bitmap RemoveBackGround(Bitmap bitmapImg) { if (bitmapImg == null) { return null; } //key 顏色 value顏色對應的數量 Dictionary<Color, int> colorDic = new Dictionary<Color, int>(); //獲取圖片中每一個顏色的數量 for (var x = 0; x < bitmapImg.Width; x++) { for (var y = 0; y < bitmapImg.Height; y++) { //刪除邊框 if (y == 0 || y == bitmapImg.Height) { bitmapImg.SetPixel(x, y, Color.White); } var color = bitmapImg.GetPixel(x, y); var colorRGB = color.ToArgb(); if (colorDic.ContainsKey(color)) { colorDic[color] = colorDic[color] + 1; } else { colorDic[color] = 1; } } } //圖片中最多的顏色 Color maxColor = colorDic.OrderByDescending(o => o.Value).FirstOrDefault().Key; //圖片中最少的顏色 Color minColor = colorDic.OrderBy(o => o.Value).FirstOrDefault().Key; Dictionary<int[], double> maxColorDifDic = new Dictionary<int[], double>(); //查找 maxColor 最接近顏色 for (var x = 0; x < bitmapImg.Width; x++) { for (var y = 0; y < bitmapImg.Height; y++) { maxColorDifDic.Add(new int[] { x, y }, GetColorDif(bitmapImg.GetPixel(x, y), maxColor)); } } //去掉和maxColor接近的顏色 即 替換成白色 var maxColorDifList = maxColorDifDic.OrderBy(o => o.Value).Where(o => o.Value < bjfz).ToArray(); foreach (var kv in maxColorDifList) { bitmapImg.SetPixel(kv.Key[0], kv.Key[1], Color.White); } return bitmapImg; } /// <summary> /// 獲取色差 /// </summary> /// <param name="color1"></param> /// <param name="color2"></param> /// <returns></returns> private double GetColorDif(Color color1, Color color2) { return Math.Sqrt((Math.Pow((color1.R - color2.R), 2) + Math.Pow((color1.G - color2.G), 2) + Math.Pow((color1.B - color2.B), 2))); } /// <summary> /// 去掉目標干擾線 /// </summary> /// <param name="img">將要處理的圖片</param> /// <returns>去掉乾乾擾線處理過的圖片</returns> private Bitmap btnDropDisturb_Click(Bitmap img) { if (img == null) { return null; } byte[] p = new byte[9]; //最小處理窗口3*3 //去幹擾線 for (var x = 0; x < img.Width; x++) { for (var y = 0; y < img.Height; y++) { Color currentColor = img.GetPixel(x, y); int color = currentColor.ToArgb(); if (x > 0 && y > 0 && x < img.Width - 1 && y < img.Height - 1) { #region 中值濾波效果很差 ////取9個點的值 //p[0] = img.GetPixel(x - 1, y - 1).R; //p[1] = img.GetPixel(x, y - 1).R; //p[2] = img.GetPixel(x + 1, y - 1).R; //p[3] = img.GetPixel(x - 1, y).R; //p[4] = img.GetPixel(x, y).R; //p[5] = img.GetPixel(x + 1, y).R; //p[6] = img.GetPixel(x - 1, y + 1).R; //p[7] = img.GetPixel(x, y + 1).R; //p[8] = img.GetPixel(x + 1, y + 1).R; ////計算中值 //for (int j = 0; j < 5; j++) //{ // for (int i = j + 1; i < 9; i++) // { // if (p[j] > p[i]) // { // s = p[j]; // p[j] = p[i]; // p[i] = s; // } // } //} //// if (img.GetPixel(x, y).R < dgGrayValue) //img.SetPixel(x, y, Color.FromArgb(p[4], p[4], p[4])); //給有效值付中值 #endregion //上 x y+1 double upDif = GetColorDif(currentColor, img.GetPixel(x, y + 1)); //下 x y-1 double downDif = GetColorDif(currentColor, img.GetPixel(x, y - 1)); //左 x-1 y double leftDif = GetColorDif(currentColor, img.GetPixel(x - 1, y)); //右 x+1 y double rightDif = GetColorDif(currentColor, img.GetPixel(x + 1, y)); //左上 double upLeftDif = GetColorDif(currentColor, img.GetPixel(x - 1, y + 1)); //右上 double upRightDif = GetColorDif(currentColor, img.GetPixel(x + 1, y + 1)); //左下 double downLeftDif = GetColorDif(currentColor, img.GetPixel(x - 1, y - 1)); //右下 double downRightDif = GetColorDif(currentColor, img.GetPixel(x + 1, y - 1)); ////四面色差較大 //if (upDif > threshold && downDif > threshold && leftDif > threshold && rightDif > threshold) //{ // img.SetPixel(x, y, Color.White); //} //三面色差較大 if ((upDif > threshold && downDif > threshold && leftDif > threshold) || (downDif > threshold && leftDif > threshold && rightDif > threshold) || (upDif > threshold && leftDif > threshold && rightDif > threshold) || (upDif > threshold && downDif > threshold && rightDif > threshold)) { img.SetPixel(x, y, Color.White); } List<int[]> xLine = new List<int[]>(); //去橫向干擾線 原理 若是這個點上下有不少白色像素則認爲是干擾 for (var x1 = x + 1; x1 < x + 10; x1++) { if (x1 >= img.Width) { break; } if (img.GetPixel(x1, y + 1).ToArgb() == Color.White.ToArgb() && img.GetPixel(x1, y - 1).ToArgb() == Color.White.ToArgb()) { xLine.Add(new int[] { x1, y }); } } if (xLine.Count() >= 4) { foreach (var xpoint in xLine) { img.SetPixel(xpoint[0], xpoint[1], Color.White); } } //去豎向干擾線 } } } return img; } /// <summary> /// 對圖片先豎向分割,再橫向分割 /// </summary> /// <param name="img">將要分割的圖片</param> /// <returns>全部分割後的字符圖片</returns> private Bitmap[] SplitImage(Bitmap img) { if (img == null) { return null; } List<int[]> xCutPointList = GetXCutPointList(img); List<int[]> yCutPointList = GetYCutPointList(xCutPointList, img); Bitmap[] bitmapArr = new Bitmap[5]; //對分割的部分劃線 for (int i = 0; i < xCutPointList.Count(); i++) { int xStart = xCutPointList[i][0]; int xEnd = xCutPointList[i][1]; int yStart = yCutPointList[i][0]; int yEnd = yCutPointList[i][1]; if (i >= 4) break; bitmapArr[i]= (Bitmap)AcquireRectangleImage(img, new Rectangle(xStart, yStart, xEnd - xStart + 1, yEnd - yStart + 1)); } return bitmapArr; } /// <summary> /// 分別從圖片的上下尋找像素點大於闕值的地方,而後獲取有黑色像素的有效區域 /// </summary> /// <param name="xCutPointList">x軸範圍的x座標集合</param> /// <param name="img">目標圖片</param> /// <returns>y軸座標開始和結束點,其實就是黑色像素圖片的有效區域</returns> private List<int[]> GetYCutPointList(List<int[]> xCutPointList, Bitmap img) { List<int[]> list = new List<int[]>(); //獲取圖像最上面Y值 int topY = 0; //獲取圖像最下面的Y值 int bottomY = 0; foreach (var xPoint in xCutPointList) { for (int ty = 1; ty < img.Height; ty++) { int xStart = xPoint[0]; int xEnd = xPoint[1]; int blackCount = GetBlackPXCountInY(ty, 2, xStart, xEnd, img); if (blackCount > 3) { topY = ty; break; } } for (int by = img.Height; by > 1; by--) { int xStart = xPoint[0]; int xEnd = xPoint[1]; int blackCount = GetBlackPXCountInY(by, -2, xStart, xEnd, img); if (blackCount > 3) { bottomY = by; break; } } list.Add(new int[] { topY, bottomY }); } return list; } /// <summary> /// 獲取分割後某區域的黑色像素 /// </summary> /// <param name="startY"></param> /// <param name="offset"></param> /// <param name="startX"></param> /// <param name="endX"></param> /// <param name="img"></param> /// <returns></returns> private int GetBlackPXCountInY(int startY, int offset, int startX, int endX, Bitmap img) { int blackPXCount = 0; int startY1 = offset > 0 ? startY : startY + offset; int offset1 = offset > 0 ? startY + offset : startY; for (var x = startX; x <= endX; x++) { for (var y = startY1; y < offset1; y++) { if (y >= img.Height) { continue; } if (img.GetPixel(x, y).ToArgb() == Color.Black.ToArgb()) { blackPXCount++; } } } return blackPXCount; } /// <summary> /// 獲取一個垂直區域內的黑色像素 /// </summary> /// <param name="startX">開始x</param> /// <param name="offset">左偏移像素</param> /// <returns></returns> private int GetBlackPXCountInX(int startX, int offset, Bitmap img) { int blackPXCount = 0; for (int x = startX; x < startX + offset; x++) { if (x >= img.Width) { continue; } for (var y = 0; y < img.Height; y++) { if (img.GetPixel(x, y).ToArgb() == Color.Black.ToArgb()) { blackPXCount++; } } } return blackPXCount; } /// <summary> /// 獲取豎向分割點 /// </summary> /// <param name="img"></param> /// <returns>List int[xstart xend]</returns> private List<int[]> GetXCutPointList(Bitmap img) { //分割點 List<int[xstart xend]> List<int[]> xCutList = new List<int[]>(); int startX = -1;//-1表示在尋找開始節點 for (var x = 0; x < img.Width; x++) { if (startX == -1)//開始點 { int blackPXCount = GetBlackPXCountInX(x, 2, img); //若是大於有效像素則是開始節點 ,0-x的矩形區域大於3像素,認爲是字母,防止一些噪點被切割 if (blackPXCount > 5) { startX = x; } } else//結束點 { if (x == img.Width - 1)//判斷是否最後一列 { xCutList.Add(new int[] { startX, x }); break; } else if (x >= startX + MinWidthPerChar)//隔開必定距離才能結束分割 { int blackPXCount = GetBlackPXCountInX(x, 2, img);//判斷後面區域黑色像素點的個數 //小於等於閥值則是結束節點 if (blackPXCount < 2) { if (x > startX + MaxWidthPerChar)//儘可能控制不執行 { //大於最大字符的寬度應該是兩個字符粘連到一塊了 從中間分開 int middleX = startX + (x - startX) / 2; xCutList.Add(new int[] { startX, middleX }); xCutList.Add(new int[] { middleX + 1, x }); } else { //驗證黑色像素是否太少 blackPXCount = GetBlackPXCountInX(startX, x - startX, img); if (blackPXCount <= 10) { startX = -1;//重置開始點 } else { xCutList.Add(new int[] { startX, x }); } } startX = -1;//重置開始點 } } } } return xCutList; } /// <summary> /// 截取圖像的矩形區域 /// </summary> /// <param name="source">源圖像對應picturebox1</param> /// <param name="rect">矩形區域,如上初始化的rect</param> /// <returns>矩形區域的圖像</returns> private Image AcquireRectangleImage(Image source, Rectangle rect) { if (source == null || rect.IsEmpty) return null; //Bitmap bmSmall = new Bitmap(rect.Width, rect.Height, System.Drawing.Imaging.PixelFormat.Format32bppRgb); Bitmap bmSmall = new Bitmap(rect.Width, rect.Height, source.PixelFormat); using (Graphics grSmall = Graphics.FromImage(bmSmall)) { grSmall.DrawImage(source, new System.Drawing.Rectangle(0, 0, bmSmall.Width, bmSmall.Height), rect, GraphicsUnit.Pixel); grSmall.Dispose(); } return bmSmall; } #endregion #region 圖片識別 /// <summary> /// 返回兩圖比較的類似度 最大1 /// </summary> /// <param name="compareImg">對比圖</param> /// <param name="mainImg">要識別的圖</param> /// <returns></returns> private double CompareImg(Bitmap compareImg, Bitmap mainImg) { int img1x = compareImg.Width; int img1y = compareImg.Height; int img2x = mainImg.Width; int img2y = mainImg.Height; //最小寬度 double min_x = img1x > img2x ? img2x : img1x; //最小高度 double min_y = img1y > img2y ? img2y : img1y; double score = 0; //重疊的黑色像素 for (var x = 0; x < min_x; x++) { for (var y = 0; y < min_y; y++) { if (compareImg.GetPixel(x, y).ToArgb() == Color.Black.ToArgb() && compareImg.GetPixel(x, y).ToArgb() == mainImg.GetPixel(x, y).ToArgb()) { score++; } } } double originalBlackCount = 0; //對比圖片的黑色像素 for (var x = 0; x < img1x; x++) { for (var y = 0; y < img1y; y++) { if (Color.Black.ToArgb() == compareImg.GetPixel(x, y).ToArgb()) { originalBlackCount++; } } } return score / originalBlackCount; } /// <summary> /// 用全部的學習的圖片對比當前圖,經過黑色和圖片比率獲取最大類似度的字符圖片,從而識別 /// </summary> /// <param name="imgArr">要識別圖片的數組</param> /// <returns>識別後的字符串</returns> public string RecognizeCheckCodeImg(Bitmap bitImg) { Bitmap EZHimg = EZH(bitImg); Bitmap[] imgArr = SplitImage(EZHimg); string returnString = string.Empty; for (int i = 0; i < imgArr.Length; i++) { if (imgArr[i] == null) { continue; } var img = imgArr[i]; if (img == null) { continue; } string[] detailPathList = Directory.GetDirectories(samplePath); if (detailPathList == null || detailPathList.Length == 0) { continue; } string resultString = string.Empty; //config.txt 文件中指定了識別字母的順序 string configPath = samplePath + "config.txt"; if (!File.Exists(configPath)) { Console.WriteLine("config.txt文件不存在,沒法識別"); return null; } string configString = File.ReadAllText(configPath); double maxRate = 0;//類似度 最大1 foreach (char resultChar in configString) { string charPath = samplePath + resultChar.ToString();//特徵目錄存儲路徑 if (!Directory.Exists(charPath)) { continue; } string[] fileNameList = Directory.GetFiles(charPath); if (fileNameList == null || fileNameList.Length == 0) { continue; } foreach (string filename in fileNameList) { Bitmap imgSample = new Bitmap(filename); //過濾寬高相差太大的 if (Math.Abs(imgSample.Width - img.Width) >= 2 || Math.Abs(imgSample.Height - img.Height) >= 3) { continue; } //當前類似度 double currentRate = CompareImg(imgSample, img); if (currentRate > maxRate) { maxRate = currentRate; resultString = resultChar.ToString(); } imgSample.Dispose(); } } returnString = returnString + resultString; } return returnString; } #endregion } }
第2、測試程序代碼測試
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net; using System.Drawing; using System.IO; namespace CheckCodeRecognizeLibTest { class Program { static void Main(string[] args) { Console.WriteLine("正在下載驗證碼......"); //CookieContainer cc = new CookieContainer(); //byte[] imgByte = HttpWebRequestForBPMS.GetWebResorce("http://hd.cnrds.net/hd/login.do?action=createrandimg",cc); //MemoryStream ms1 = new MemoryStream(imgByte); // Bitmap bm = (Bitmap)Image.FromStream(ms1); Bitmap img = HttpWebRequestForBPMS.GetWebImage("http://hd.cnrds.net/hd/login.do?action=createrandimg"); Console.WriteLine("驗證碼下載成功,正在識別....."); CheckCodeRecognizeLib.CheckCodeRecognize regImg = new CheckCodeRecognizeLib.CheckCodeRecognize(); string regResult= regImg.RecognizeCheckCodeImg(img); Console.WriteLine("驗證碼識別成功,驗證碼結果爲:"+regResult); } } }