粘粘字符“花式分割”___Color Filling
Color Filling 的基本原理是以给定点为起点,将与该点连通的区域均变为指定的颜色。与“扫雷游戏”中翻开空白区域的原理一致,EMGU的cvFloodFill可以实现该功能,但因还不习惯使用cvInvoke函数,故用函数迭代来实现该功能。
无论用哪一种方式实现,都需要选择4领域或8领域。如下图所示,可以清楚的知道Color Filling在4领域和8领域下的差异,本系列算法均选用4领域来进行Color Filling。
Color Filling给验证码图片制造了“阴影”,现在需要求“阴影部分的面积”(字符Loop的坐标范围)。事实上,我们知道loop的左下角与右上角的边界点即可,求“阴影部分面积”的方法:
1.对Color Filling后的验证码图片进行竖直投影,投影目标是LOOP部分颜色的像素。
2.求出竖直投影中连续包含LOOP像素的范围,这样可以得到每个验证码图片中每一个LOOP在Y轴上的范围。
3.在已知的LOOP所在的Y轴范围内对LOOP颜色进行水平投影,得到每一个LOOP在X轴上的范围。
4.遍历每一个LOOP的坐标范围,要求该坐标范围内至少包含一个完整的8领域均为LOOP像素,否则认为其为无效的LOOP。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<span>/* -------------------------------------------------------- * 作者:livezingy * * 博客:https://www.livezingy.com * * 开发环境: * Visual Studio V2012 * .NET Framework 4.5 --------------------------------------------------------- */ /// <summary> /// 在二值化数组中寻找与给定坐标点连通的区域,并用涂上灰色 /// </summary> /// <param name="nRow">给定坐标行</param> /// <param name="nCol">给定坐标列</param> /// <param name="imageHeight">二值化图像的高度</param> /// <param name="imageWidth">二值化图像的宽度</param> /// <param name="BinaryArray">二值化数组</param> /// <returns>返回与给定坐标点连通区域设定为灰色的灰度数组</returns> public static Byte[,] FillConnectedArea(int nRow, int nCol, int imageHeight, int imageWidth, Byte[,] BinaryArray) { for (int x = nRow - 1;x < nRow + 2;x ++) { for (int y = nCol - 1;y < nCol + 2;y ++) { if ((x >= 0) && (x < imageHeight) && (y >= 0) && (y < imageWidth)) { //if (255 == BinaryArray[x, y])//8邻域的方式寻找连通域 if ((255 == BinaryArray[x, y]) && ((x == nRow) || (y == nCol)))//4邻域的方式寻找连通域 { BinaryArray[x, y] = 128; FillConnectedArea(x, y, imageHeight, imageWidth, BinaryArray); } } } } return BinaryArray; }</span> |
在求得“阴影部分的面积”(字符Loop的坐标范围)后,我们还需要借助字符笔画宽度才可以确定字符更确切的分割位置。
纵观我们的研究对象,不同的验证码图片之间字符宽度有差异,但同一张验证码图像的字符宽度基本一致,只是有少数验证码图片有两个字符共用笔画的现象。我计算字符笔画宽度的方法为:分别从纵向与横向统计黑色像素的宽度,取宽度值最多的作为字符宽度,或者也可以取前几位平均一下作为字符宽度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
/* -------------------------------------------------------- * 作者:livezingy * * 博客:https://www.livezingy.com * * 开发环境: * Visual Studio V2012 * .NET Framework 4.5 --------------------------------------------------------- */ /// <summary> /// 获取验证码字符的笔画宽度。横向与纵向分别统计黑色像素的宽度,默认在这两个方向上最多的宽度值为字符笔画宽度。 /// 该方法默认有效字符为黑色,不适用于统计倾斜严重的验证码, /// </summary> /// <param name="imageHeight">二值化图像的高度</param> /// <param name="imageWidth">二值化图像的宽度</param> /// <param name="BinaryArray">二值化数组</param> /// <returns>返回字符所在区域的边界</returns> public static Int32 GetStrokeWid(int imageHeight, int imageWidth, Byte[,] BinaryArray) { int i, j; int nCount = 0; ArrayList nCountArray = new ArrayList(); //从宽度方向上统计黑色像素的宽度 for (i = 0; i < imageHeight; i++) { for (j = 0; j < imageWidth; j++) { if (0 == BinaryArray[i, j]) { nCount++; } else { if (nCount != 0) { nCountArray.Add(nCount); } nCount = 0; } } } //从高度方向上统计黑色像素的宽度 for (j = 0; j < imageWidth; j++) { for (i = 0; i < imageHeight; i++) { if (0 == BinaryArray[i, j]) { nCount++; } else { if (nCount != 0) { nCountArray.Add(nCount); } nCount = 0; } } } //将nCountArray中的数值按照从小到大的顺序进行排列 nCountArray.Sort(); int tmpNum = nCountArray.Count; tmpNum = (int)nCountArray[tmpNum - 1] + 1; //统计宽度数最多的值 int[] Histogram = new int[tmpNum]; Array.Clear(Histogram, 0, tmpNum); foreach (int val in nCountArray) { Histogram[val]++; } tmpNum = Histogram.Max(); int StrokeWid = 0; for (i = 0; i < 2; i ++ ) { tmpNum = Histogram.Max(); j = Array.IndexOf(Histogram, tmpNum); Histogram.SetValue(0, j); StrokeWid = StrokeWid + j; } return (int)Math.Round((Double)StrokeWid / 2); } |
粘粘字符“花式分割”___fix broken characters
粘粘字符“花式分割”___loop and guideline
粘粘字符“花式分割”___guideline principle
作者,你好 !!最近在搜集学习图像处理的相关算法。是用验证码去学习处理的。现在是在想对验证码做个识别库,但是不知道 怎么把验证码合理切成小的图片。因为,验证码各字符之间的距离不一样,导致有时多切了,有时少切了。如:1 3 4 5 这里的4可能会被 分到二个小图里了。请作者指点下,有CODE参考,最好。谢谢。
你好,我对于你提出的问题也谈不上指点,我们一起学习。
不同的验证码需要不同的分割方法:
1. 如果验证码不是粘连的,那么你通过竖直或水平投影就可以轻松的将字符分割开。我博客里有关于竖直投影的文章,你可以参考。
2. 如果验证码是粘连的,那还需要分是否有倾斜,字符类型,以及粘连的程度。这种类型的就比较复杂了,就需要考虑用滴水算法或者去分析字符特征,再考虑具体的方案了。