距離上次寫博客又很長時間了,這個驗證碼識別模塊其實自己早寫出來就是懶的寫博客,現在離職了有時間把它拿出來。 總體說來這個驗證碼還是有一定難度的:字母數量不固定、位置不固定、帶傾斜角度、帶粘連、有噪點和干擾線。所以識別率還是比較低的,有個十分之一吧,但是識別出來就可以了,反正是軟件識別,又不是人識別。這個驗證碼識別模塊是專門針對此類驗證碼優化的,所以如果想識別其他驗證碼估計稍改一下源碼就行。
源代碼
首先看界面:

界面有很多按鈕,我按照順序說一下,其實就是識別的順序
1、獲取驗證碼(動態從某網址獲取)
2、去背景
3、去干擾
4、二值化
5、分割
6、識別
7、如果識別錯誤,同時某字母有識別的價值,就對應分割字母的順序找到下面對應的框填上正確的字母,然後點擊學習。下次再出現類似字母就可以正確識別了。

比如這次識別h錯誤,那就可以在對應的框中填寫h然後點擊學習。但是這個 h字母左上角有個黑色干擾塊,沒有學習的價值。
下面貼上一些源代碼,大家也可以點擊上面的鏈接下載源代碼查看,注意不能刪除Debug下的Sample文件夾。代碼裡面都有注釋:
1、去背景代碼:
原理:把圖片中顏色數量最多的一部分看成背景並替換為白色,相當於去除背景。
1 /// <summary>
2 /// 去背景
3 /// 把圖片中最多的一部分顏色視為背景色 選出來後替換為白色
4 /// </summary>
5 /// <param name="sender"></param>
6 /// <param name="e"></param>
7 private void btnDropBG_Click(object sender, EventArgs e)
8 {
9 if (picbox.Image == null)
10 {
11 return;
12 }
13 Bitmap img = new Bitmap(picbox.Image);
14 //key 顏色 value顏色對應的數量
15 Dictionary<Color, int> colorDic = new Dictionary<Color, int>();
16 //獲取圖片中每個顏色的數量
17 for (var x = 0; x < img.Width; x++)
18 {
19 for (var y = 0; y < img.Height; y++)
20 {
21 //刪除邊框
22 if (y == 0 || y == img.Height)
23 {
24 img.SetPixel(x, y, Color.White);
25 }
26
27 var color = img.GetPixel(x, y);
28 var colorRGB = color.ToArgb();
29
30 if (colorDic.ContainsKey(color))
31 {
32 colorDic[color] = colorDic[color] + 1;
33 }
34 else
35 {
36 colorDic[color] = 1;
37 }
38 }
39 }
40 //圖片中最多的顏色
41 Color maxColor = colorDic.OrderByDescending(o => o.Value).FirstOrDefault().Key;
42 //圖片中最少的顏色
43 Color minColor = colorDic.OrderBy(o => o.Value).FirstOrDefault().Key;
44
45 Dictionary<int[], double> maxColorDifDic = new Dictionary<int[], double>();
46 //查找 maxColor 最接近顏色
47 for (var x = 0; x < img.Width; x++)
48 {
49 for (var y = 0; y < img.Height; y++)
50 {
51 maxColorDifDic.Add(new int[] { x, y }, GetColorDif(img.GetPixel(x, y), maxColor));
52 }
53 }
54 //去掉和maxColor接近的顏色 即 替換成白色
55 var maxColorDifList = maxColorDifDic.OrderBy(o => o.Value).Where(o => o.Value < bjfz).ToArray();
56 foreach (var kv in maxColorDifList)
57 {
58 img.SetPixel(kv.Key[0], kv.Key[1], Color.White);
59 }
60 picbox.Image = img;
61 pbNormal.Image = picbox.Image;
62 }
2、去干擾代碼、
原理:如果一個像素和周圍上下左右四個像素點中的兩面或三面顏色差別都很大,則認為這個點是噪點或干擾線。看到網上資料中有用中值濾波算法的,但是效果不好。
1 /// <summary>
2 /// 去干擾
3 /// </summary>
4 /// <param name="sender"></param>
5 /// <param name="e"></param>
6 private void btnDropDisturb_Click(object sender, EventArgs e)
7 {
8 if (picbox.Image == null)
9 {
10 return;
11 }
12 Bitmap img = new Bitmap(picbox.Image);
13 byte[] p = new byte[9]; //最小處理窗口3*3
14 byte s;
15 //去干擾線
16 for (var x = 0; x < img.Width; x++)
17 {
18 for (var y = 0; y < img.Height; y++)
19 {
20 Color currentColor = img.GetPixel(x, y);
21 int color = currentColor.ToArgb();
22
23 if (x > 0 && y > 0 && x < img.Width - 1 && y < img.Height - 1)
24 {
25 #region 中值濾波效果不好
26 ////取9個點的值
27 //p[0] = img.GetPixel(x - 1, y - 1).R;
28 //p[1] = img.GetPixel(x, y - 1).R;
29 //p[2] = img.GetPixel(x + 1, y - 1).R;
30 //p[3] = img.GetPixel(x - 1, y).R;
31 //p[4] = img.GetPixel(x, y).R;
32 //p[5] = img.GetPixel(x + 1, y).R;
33 //p[6] = img.GetPixel(x - 1, y + 1).R;
34 //p[7] = img.GetPixel(x, y + 1).R;
35 //p[8] = img.GetPixel(x + 1, y + 1).R;
36 ////計算中值
37 //for (int j = 0; j < 5; j++)
38 //{
39 // for (int i = j + 1; i < 9; i++)
40 // {
41 // if (p[j] > p[i])
42 // {
43 // s = p[j];
44 // p[j] = p[i];
45 // p[i] = s;
46 // }
47 // }
48 //}
49 //// if (img.GetPixel(x, y).R < dgGrayValue)
50 //img.SetPixel(x, y, Color.FromArgb(p[4], p[4], p[4])); //給有效值付中值
51 #endregion
52
53 //上 x y+1
54 double upDif = GetColorDif(currentColor, img.GetPixel(x, y + 1));
55 //下 x y-1
56 double downDif = GetColorDif(currentColor, img.GetPixel(x, y - 1));
57 //左 x-1 y
58 double leftDif = GetColorDif(currentColor, img.GetPixel(x - 1, y));
59 //右 x+1 y
60 double rightDif = GetColorDif(currentColor, img.GetPixel(x + 1, y));
61 //左上
62 double upLeftDif = GetColorDif(currentColor, img.GetPixel(x - 1, y + 1));
63 //右上
64 double upRightDif = GetColorDif(currentColor, img.GetPixel(x + 1, y + 1));
65 //左下
66 double downLeftDif = GetColorDif(currentColor, img.GetPixel(x - 1, y - 1));
67 //右下
68 double downRightDif = GetColorDif(currentColor, img.GetPixel(x + 1, y - 1));
69
70 ////四面色差較大
71 //if (upDif > threshold && downDif > threshold && leftDif > threshold && rightDif > threshold)
72 //{
73 // img.SetPixel(x, y, Color.White);
74 //}
75 //三面色差較大
76 if ((upDif > threshold && downDif > threshold && leftDif > threshold)
77 || (downDif > threshold && leftDif > threshold && rightDif > threshold)
78 || (upDif > threshold && leftDif > threshold && rightDif > threshold)
79 || (upDif > threshold && downDif > threshold && rightDif > threshold))
80 {
81 img.SetPixel(x, y, Color.White);
82 }
83
84 List<int[]> xLine = new List<int[]>();
85 //去橫向干擾線 原理 如果這個點上下有很多白色像素則認為是干擾
86 for (var x1 = x + 1; x1 < x + 10; x1++)
87 {
88 if (x1 >= img.Width)
89 {
90 break;
91 }
92
93 if (img.GetPixel(x1, y + 1).ToArgb() == Color.White.ToArgb()
94 && img.GetPixel(x1, y - 1).ToArgb() == Color.White.ToArgb())
95 {
96 xLine.Add(new int[] { x1, y });
97 }
98 }
99 if (xLine.Count() >= 4)
100 {
101 foreach (var xpoint in xLine)
102 {
103 img.SetPixel(xpoint[0], xpoint[1], Color.White);
104 }
105 }
106
107 //去豎向干擾線
108
109 }
110 }
111 }
112 picbox.Image = img;
113 pbNormal.Image = picbox.Image;
114 }
3、二值化代碼
原理:這個簡單,就是亮度大於某閥值設為白色否則設為黑色。
1 /// <summary>
2 /// 二值化 遍歷每個點 點的亮度小於閥值 則認為是黑色 否則是白色
3 /// </summary>
4 private void EZH()
5 {
6 if (picbox.Image != null)
7 {
8 var img = new Bitmap(picbox.Image);
9 for (var x = 0; x < img.Width; x++)
10 {
11 for (var y = 0; y < img.Height; y++)
12 {
13 Color color = img.GetPixel(x, y);
14 if (color.GetBrightness() < ezFZ)
15 {
16 img.SetPixel(x, y, Color.Black);
17 }
18 else
19 {
20 img.SetPixel(x, y, Color.White);
21 }
22 }
23 }
24 picbox.Image = img;
25 pbNormal.Image = picbox.Image;
26 }
27 }
4、分割、識別的代碼太多,大家還是自己看源碼吧,這裡就說一下原理:
分割就是把每個字母都分割出來和Sample文件夾下的圖片對比,找出黑色區域重疊度最高的那個就是識別的過程。那Sample文件夾下的圖片從哪來的呢?還記得上面說的學習嗎?對,就是手動分割後如果字母比較正規,沒有干擾點或線後就可以納為學習字符,點擊學習後它就自動保存到對應的字符目錄中作為對比圖片。呵呵,有點人工智能的趕腳。
再補充一些吧,分割其實也是比較麻煩的地方,主要的困難是干擾線、噪點、 粘連影響了分割的准確度,尤其是粘連。所以代碼中就把黑色像素和白色像素的邊界當做分割點,當然還要上下左右檢測,如果黑色像素太少,就繼續移動找邊界。還有就是如果結束點離起始點距離明顯是兩個字符的時候就認為它是粘連,從中間分開,這個做法不是很好,但期望推薦更好的辦法。
所以知道怎麼識別驗證碼才能制作出難以識別的驗證碼,光靠噪點和干擾線是不行的,你看看谷歌的驗證碼只用了粘連和變形就擊敗了那些號稱破解驗證碼90%的專業識別軟件。
最後再說一下這個驗證碼識別全憑自己的琢磨和借鑒網上資料,沒有太多復雜的算法,所以對大多程序員來說應該不算難。還是希望大家也多思考,多提供寶貴意見。
驗證碼可以通過PHP程序生成(設定為從數字和字母中隨機抽取),生成的同時,程序知道生成的內容。用戶輸入同樣的內容後,程序比對之前記錄的生成值,一致則驗證通過。這是我了解的邏輯。那麼不論何種形式的驗證碼圖片,其背後都有一個簡單的語言信息。如果要識別圖片,特別是復雜的圖片,有難度。但是如果可以抓取程序記錄的生成值(例如數據庫抓取)就回避了圖片識別的問題。這只是我的思路,可能仍然很困難。
驗證碼可以通過PHP程序生成(設定為從數字和字母中隨機抽取),生成的同時,程序知道生成的內容。用戶輸入同樣的內容後,程序比對之前記錄的生成值,一致則驗證通過。這是我了解的邏輯。那麼不論何種形式的驗證碼圖片,其背後都有一個簡單的語言信息。如果要識別圖片,特別是復雜的圖片,有難度。但是如果可以抓取程序記錄的生成值(例如數據庫抓取)就回避了圖片識別的問題。這只是我的思路,可能仍然很困難。