Aforge.net之旅——开篇:从识别验证码开始
时间过得真快啊,转眼今年就要过去了,大半年都没有写博客了,要说时间嘛,花在泡妹子和搞英语去了,哈哈。。。前几天老大问我
怎么这么长时间都没写博客了,好吧,继续坚持,继续分享我的心得体会。
这个系列我们玩玩aforge.net,套用官方都话就是一个专门为开发者和研究者基于C#框架设计的,这个框架提供了不同的类库和关于类库的
资源,还有很多应用程序例子,包括计算机视觉与人工智能,图像处理,神经网络,遗传算法,机器学习,机器人等领域,这个系列研究的重点
就是瞎几把搞下AForge.Imaging这个命名空间下面的东东,下载网址:http://www.aforgenet.com/framework/downloads.html
对了,不知道有多少公司是用得仕卡作为员工的福利卡,我们公司就是这样的,每个月公司都会充值一些money,然后我们这些屁码农每个
月15号就都开心的去看看发了多少。
上去看了后,哟呵~ 还有个90年代的验证码,我想这年头估计找到这样验证码的网站已经不多了,如果懂一点图像处理都话,这张验证码
跟没有一个样,谢谢。。。这篇我们看看怎么去识别它。
一: 验证码处理
1. 一般处理原则
这种验证码为什么说跟没有一样,第一点:字体规范工整,第二点:不旋转扭曲粘连,第三点:字体颜色单一,下面看处理步骤
这里要注意的是,aforge只接受像素格式为24/32bpp的像素格式图片,所以处理前,先进行格式转化。
//转化图片像素格式 var bnew = new Bitmap(b.Width, b.Height, PixelFormat.Format24bppRgb); Graphics g = Graphics.FromImage(bnew); g.DrawImage(b, 0, 0); g.Dispose();
<1>图片灰度化
这是图像识别通常都要走的第一步,图片灰度化有助于减少后续对rgb的计算量,同时也方便我们进行二值化,在aforge中我们有
专门的类一步搞定,简洁方便。
//灰度化 b = new Grayscale(0.2125, 0.7154, 0.0721).Apply(b);
<2>二值化
二值化顾名思义就是二种值,比如非白即黑,非黑即白,那么白和黑的标准就需要提供一个阈值,大于或者小于怎么样,在aforge同样
也有相似的类进行处理
//二值化 b = new Threshold(50).Apply(b);
<3> 去噪点
从上面的图片可以发现有很多红点点,搞得像皮肤病一样,仔细观察可以看到这种噪点具有独立,体积小的特征,所以判断的标准就是如果
图中某个区块的大小在我设置的阈值内,就将其去掉,同样也有专门的类进行处理。
//去噪点 new BlobsFiltering(1, 1, b.Width, b.Height).Apply(b);
这里具体怎么传递参数,后续系列会慢慢解读。
<4>切割图片
切图片的好处在于我们需要知道真正要识别的元素的有效范围是多大,同时也方便我们将这些图片作为模板保存下来。
代码如下:
1 /// <summary> 2 /// 按照 Y 轴线 切割 3 /// (丢弃等于号) 4 /// </summary> 5 /// <param name="?"></param> 6 /// <returns></returns> 7 public List<Bitmap> Crop_Y(Bitmap b) 8 { 9 var list = new List<Bitmap>(); 10 11 //统计每一列的“1”的个数,方便切除 12 int[] cols = new int[b.Width]; 13 14 /* 15 * 纵向切割 16 */ 17 for (int x = 0; x < b.Width; x++) 18 { 19 for (int y = 0; y < b.Height; y++) 20 { 21 //获取当前像素点像素 22 var pixel = b.GetPixel(x, y); 23 24 //说明是黑色点 25 if (pixel.R == 0) 26 { 27 cols[x] = ++cols[x]; 28 } 29 } 30 } 31 32 int left = 0, right = 0; 33 34 for (int i = 0; i < cols.Length; i++) 35 { 36 //说明该列有像素值(为了防止像素干扰,去噪后出现空白的问题,所以多判断一下,防止切割成多个) 37 if (cols[i] > 0 || (i + 1 < cols.Length && cols[i + 1] > 0)) 38 { 39 if (left == 0) 40 { 41 //切下来图片的横坐标left 42 left = i; 43 } 44 else 45 { 46 //切下来图片的横坐标right 47 right = i; 48 } 49 } 50 else 51 { 52 //说明已经有切割图了,下面我们进行切割处理 53 if ((left > 0 || right > 0)) 54 { 55 Crop corp = new Crop(new Rectangle(left, 0, right - left + 1, b.Height)); 56 57 var small = corp.Apply(b); 58 59 //居中,将图片放在20*50的像素里面 60 61 list.Add(small); 62 } 63 64 left = right = 0; 65 } 66 } 67 68 return list; 69 } 70 71 /// <summary> 72 /// 按照 X 轴线 切割 73 /// </summary> 74 /// <param name="b"></param> 75 /// <returns></returns> 76 public List<Bitmap> Crop_X(List<Bitmap> list) 77 { 78 var corplist = new List<Bitmap>(); 79 80 //再对分割的图进行上下切割,取出上下的白边 81 foreach (var segb in list) 82 { 83 //统计每一行的“1”的个数,方便切除 84 int[] rows = new int[segb.Height]; 85 86 /* 87 * 横向切割 88 */ 89 for (int y = 0; y < segb.Height; y++) 90 { 91 for (int x = 0; x < segb.Width; x++) 92 { 93 //获取当前像素点像素 94 var pixel = segb.GetPixel(x, y); 95 96 //说明是黑色点 97 if (pixel.R == 0) 98 { 99 rows[y] = ++rows[y]; 100 } 101 } 102 } 103 104 int bottom = 0, top = 0; 105 106 for (int y = 0; y < rows.Length; y++) 107 { 108 //说明该行有像素值(为了防止像素干扰,去噪后出现空白的问题,所以多判断一下,防止切割成多个) 109 if (rows[y] > 0 || (y + 1 < rows.Length && rows[y + 1] > 0)) 110 { 111 if (top == 0) 112 { 113 //切下来图片的top坐标 114 top = y; 115 } 116 else 117 { 118 //切下来图片的bottom坐标 119 bottom = y; 120 } 121 } 122 else 123 { 124 //说明已经有切割图了,下面我们进行切割处理 125 if ((top > 0 || bottom > 0) && bottom - top > 0) 126 { 127 Crop corp = new Crop(new Rectangle(0, top, segb.Width, bottom - top + 1)); 128 129 var small = corp.Apply(segb); 130 131 corplist.Add(small); 132 } 133 134 top = bottom = 0; 135 } 136 } 137 } 138 139 return corplist; 140 }
<5> 图片精处理
这里要注意的是,比如数字“2”,切除上下左右的空白后,再加上噪点的干扰,不一定每次切下来的图片大小都一样,所以这里
为了方便更好的识别,我们需要重置下图片的大小,并且将“数字2”进行文字居中。
1 /// <summary> 2 /// 重置图片的指定大小并且居中 3 /// </summary> 4 /// <param name="list"></param> 5 /// <returns></returns> 6 public List<Bitmap> ToResizeAndCenterIt(List<Bitmap> list, int w = 20, int h = 20) 7 { 8 List<Bitmap> resizeList = new List<Bitmap>(); 9 10 11 for (int i = 0; i < list.Count; i++) 12 { 13 //反转一下图片 14 list[i] = new Invert().Apply(list[i]); 15 16 int sw = list[i].Width; 17 int sh = list[i].Height; 18 19 Crop corpFilter = new Crop(new Rectangle(0, 0, w, h)); 20 21 list[i] = corpFilter.Apply(list[i]); 22 23 //再反转回去 24 list[i] = new Invert().Apply(list[i]); 25 26 //计算中心位置 27 int centerX = (w - sw) / 2; 28 int centerY = (h - sh) / 2; 29 30 list[i] = new CanvasMove(new IntPoint(centerX, centerY), Color.White).Apply(list[i]); 31 32 resizeList.Add(list[i]); 33 } 34 35 return resizeList; 36 }
其实精处理后,这些图片就可以作为我们的模板库的图片了,可以将每张模板图都标记下具体的数字,后续我们再遇到时,计算下其相似度
就可以了,下面就是已经制作好的模板。
<6> 模板匹配识别
既然模板图片都制作好了,一切都差不多水到渠成了,下次来的验证码我都切好后做成精图片后跟模板进行匹配,在afroge里面
有一个ExhaustiveTemplateMatching,专门用来进行模板匹配用的,很方便。
ExhaustiveTemplateMatching templateMatching = new ExhaustiveTemplateMatching(0.9f);
这里的0.9f就是设定的阈值,只有大于0.9的阈值,我才认为该模板与目标图片相似,然后在所有大于0.9的相似度中取到最大的一个作为
我们最后识别的图像。
1 var files = Directory.GetFiles(Environment.CurrentDirectory + "\\Template\\"); 2 3 var templateList = files.Select(i => { return new Bitmap(i); }).ToList(); 4 var templateListFileName = files.Select(i => { return i.Substring(30, 1); }).ToList(); 5 6 var result = new List<string>(); 7 8 ExhaustiveTemplateMatching templateMatching = new ExhaustiveTemplateMatching(0.9f); 9 10 //这里面有四张图片,进行四张图的模板匹配 11 for (int i = 0; i < list.Count; i++) 12 { 13 float max = 0; 14 int index = 0; 15 16 for (int j = 0; j < templateList.Count; j++) 17 { 18 var compare = templateMatching.ProcessImage(list[i], templateList[j]); 19 20 if (compare.Length > 0 && compare[0].Similarity > max) 21 { 22 //记录下最相似的 23 max = compare[0].Similarity; 24 index = j; 25 } 26 } 27 28 result.Add(templateListFileName[index]); 29 }
最后的效果还是不错的,识别率基本100%吧。