距离上次写博客又很长时间了,这个验证码识别模块其实自己早写出来就是懒的写博客,现在离职了有时间把它拿出来。 总体说来这个验证码还是有一定难度的:字母数量不固定、位置不固定、带倾斜角度、带粘连、有噪点和干扰线。所以识别率还是比较低的,有个十分之一吧,但是 识别出来就可以了,反正是软件识别,又不是人识别。这个验证码识别模块是专门针对此类验证码优化的,所以如果想识别其他验证码估计稍改一下源码就行。
首先看界面:

界面有很多按钮,我按照顺序说一下,其实就是识别的顺序
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%的专业识别软件。
最后再说一下这个验证码识别全凭自己的琢磨和借鉴网上资料,没有太多复杂的算法,所以对大多程序员来说应该不算难。还是希望大家也多思考,多提供宝贵意见。
Mikel
