分割粘粘字符的滴水算法的CSharp代码
本文的代码仅实现了向下滴落的滴水算法本身,不含起始滴落点的计算,而实际上起始滴落点的确定才是分割粘粘字符的关键点。关于滴水算法滴落起始点的文献将在下一篇文章中与大家分享。
滴水算法的原理概述
初识滴水算法是在《基于滴水算法的验证码中粘连字符分割方法》(提取密码:8sg2)一文中,本文和C#实现图像竖直投影以及也是得到的该文献的启示后实现的。但这两篇都是东施效颦,并未实现该文献的精髓,有兴趣的同学可仔细阅读。Ok,我们先来了解滴水算法的原理。
滴水算法根据水滴滴落的方向的不同可分为向上滴落和向下滴落。以向下滴落为例,该算法模拟水滴从高处向低处滴落的过程来对粘连字符进行切分。水滴从字符串顶部在重力的作用下,沿着字符轮廓向下滴落或水平滚动,当水滴陷在轮廓的凹处时,将渗漏到字符笔划中,经穿透笔划后继续滴落,最终水滴所经过的轨迹就构成了字符的分割路径。
选择好水滴的起始滴落点后,水滴的后续路径由下图来决定,值得注意的是,当水滴落入字符轮廓的凹陷处时,情况 5 和情况 6 将会交替出现,即水滴将左右滚动。原文中描述的是”水滴在凹陷处右侧向下竖直渗漏“,但实验验证结果是”水滴在当前位置向下竖直渗漏“效果较好,当然这也许是我验证的不够多,大家对此点可谨慎参考。除了下图中描述的状况外,其他状况均在当前位置竖直滴落即可。
滴水算法的CSharp实现代码
代码中直接选取一个比较确定的起始滴落点来验证滴水算法。若起始滴落点选择得到,那么该代码理论上而言应该具有其通用性。本人是CSharp初学者且是业余自学,很多CSharp的特性不是非常熟悉,因此代码中可能有很多不足之处,还请多多包涵,若有高手愿意指点一二,那更是感激不尽。
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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
/// <summary> /// 根据起始滴落点完成滴水算法,实现粘粘图片的分割 /// </summary> /// <param name="imageSrc">the path of src image</param> public static void DropFall(string imageSrcPath, string imageDestPath) { Bitmap bmp = new Bitmap(imageSrcPath); Int32 threshold = 0; Byte[,] grayArray = bmp.ToBinaryArray(BinarizationMethods.Otsu, out threshold); int imageHeight = grayArray.GetLength(0); int imageWidth = grayArray.GetLength(1); //用于存储分割后的图像的二值化数组 Byte[,] dividedArray = new Byte[imageHeight, imageWidth]; for (Int32 x = 0; x < imageHeight; x++) { for (Int32 y = 0; y < imageWidth; y++) { dividedArray[x, y] = 255; } } //记录图像出现的上下极限位置 int xIndex1 = 0, xIndex2 = 0; //记录图像出现的左右极限位置 int yIndex1 = 0, yIndex2 = 0; byte loopFlg = 0;//当该变量置为1时将跳出双重循环 for (int x = 0; (x < imageHeight) && (0 == loopFlg); x++) { for (int y = 0; (y < imageWidth) && (0 == loopFlg); y++) { if (0 == grayArray[x, y])//从边界开始遇到第一个黑色像素点时,此处为图像出现的在纵轴上的第一个点 { xIndex1 = x; loopFlg = 1; } } } loopFlg = 0;//当该变量置为零时将跳出双重循环 for (int x = (imageHeight - 1); (x > 0)&& (0 == loopFlg); x--) { for (int y = 0; (y < imageWidth) && (0 == loopFlg); y++) { if (0 == grayArray[x, y])//从边界开始遇到第一个黑色像素点时,此处为图像出现的在纵轴上的第一个点 { xIndex2 = x; loopFlg = 1; } } } loopFlg = 0;//当该变量置为零时将跳出双重循环 for(int y = 0; (y < imageWidth) && (0 == loopFlg); y++) { for (int x = 0; (x < imageHeight) && (0 == loopFlg); x++) { if (0 == grayArray[x, y])//从边界开始遇到第一个黑色像素点时,此处为图像出现的在纵轴上的第一个点 { yIndex1 = y; loopFlg = 1; } } } loopFlg = 0;//当该变量置为零时将跳出双重循环 for(int y = (imageWidth - 1); (y > 0) && (0 == loopFlg) ; y--) { for (int x = 0; (x < imageHeight) && (0 == loopFlg); x++) { if (0 == grayArray[x, y])//从边界开始遇到第一个黑色像素点时,此处为图像出现的在纵轴上的第一个点 { yIndex2 = y; loopFlg = 1; } } } //有字符区域的宽度 int validWidth = yIndex2 - yIndex1; //有字符区域的高度 int validHeight = xIndex2 - xIndex1; int addVal = validWidth >> 2; //临时起始滴落点,该滴落点确保能将示例中的第一个字符分割开来 int iniPointY = yIndex1 + addVal; int iniPointX = xIndex1; byte leftRightFlg = 0;//该标志位置位时,表示情况5与情况6可能会循环出现 while ((iniPointX < xIndex2) && (iniPointY < yIndex2)) { Byte pointLeft = grayArray[iniPointX, (iniPointY - 1)]; Byte pointRight = grayArray[iniPointX, (iniPointY + 1)]; Byte pointDown = grayArray[(iniPointX + 1), iniPointY]; Byte pointDownLeft = grayArray[(iniPointX + 1), (iniPointY - 1)]; Byte pointDownRight = grayArray[(iniPointX + 1), (iniPointY + 1)]; for (int y = yIndex1; y < iniPointY; y++) { dividedArray[iniPointX, (y - yIndex1)] = grayArray[iniPointX, y]; } if (((0 == pointLeft) && (0 == pointRight) && (0 == pointDown) && (0 == pointDownLeft) && ((0 == pointDownRight)))// all black || ((255 == pointLeft) && (255 == pointRight) && (255 == pointDown) && (255 == pointDownLeft) && ((255 == pointDownRight))//all white || ((0 == pointDownLeft) && (255 == pointDown))))//情况1与情况3 {//down iniPointX = iniPointX + 1; leftRightFlg = 0; } else if ((0 == pointLeft) && (0 == pointRight) && (0 == pointDown) && (255 == pointDownLeft) && (0 == pointDownRight)) {//left down //情况2 iniPointX = iniPointX + 1; iniPointY = iniPointY - 1; leftRightFlg = 0; } else if ((0 == pointDown) && (0 == pointDownLeft) && (255 == pointDownRight)) {//情况4 iniPointX = iniPointX + 1; iniPointY = iniPointY + 1; leftRightFlg = 0; } else if ((255 == pointRight) && (0 == pointDown) && (0 == pointDownLeft) && (0 == pointDownRight)) {//情况5 if (0 == leftRightFlg) { iniPointY = iniPointY + 1; leftRightFlg = 1; } else//标志位为1时说明上一个点出现在情况6,而本次循环的点出现在情况5,此种情况将垂直渗透 { iniPointX = iniPointX + 1; leftRightFlg = 0; } } else if ((255 == pointLeft) && (0 == pointDown) && (0 == pointDownLeft) && (0 == pointDownRight)) {//情况6 if (0 == leftRightFlg) { iniPointY = iniPointY - 1; leftRightFlg = 1; } else//标志位为1时说明上一个点出现在情况5,而本次循环的点出现在情况6,此种情况将垂直渗透 { iniPointX = iniPointX + 1; leftRightFlg = 0; } } else { iniPointX = iniPointX + 1; } } Bitmap bmpDst = BinaryArrayToBinaryBitmap(dividedArray); bmpDst.Save(imageDestPath, System.Drawing.Imaging.ImageFormat.Jpeg); } |