/// <summary> /// 将输入的含粘粘字符的图片转换为分割后的字符图片 /// </summary> /// <param name="imageSrcPath">粘粘字符图片的路径</param> /// <param name="imageSrcPath">分割后图片的存储路径</param> /// <returns>字符分割后的图片为位图</returns> public static Bitmap CaptchaSegmentFun(Bitmap srcBmp)//, string imageDestPath) { Byte[,] grayArraySrc = Preprocess.Preprocess.ToGrayArray(srcBmp); BinaryArray = Preprocess.Preprocess.Sauvola(grayArraySrc); FixBrokenCharacter(); StrokeWid = Preprocess.Preprocess.GetStrokeWid(ImageHeight, ImageWidth, BinaryArray); ImgBoundary = Preprocess.Preprocess.getImgBoundary(ImageHeight, ImageWidth, BinaryArray); GetGuidePoint(); Byte[,] grayArray = new Byte[ImageHeight, ImageWidth]; for (int i = 0; i < ImageHeight; i++) { for (int j = 0; j < ImageWidth; j++) { grayArray[i, j] = BinaryArray[i, j]; if (0 == BinaryArray[i, j]) { bPixSum++; } } } grayArray = Preprocess.Preprocess.FillConnectedArea(1, 1, ImageHeight, ImageWidth, grayArray); ArrayList loopArray = GetLoopBoundary(grayArray); SegByLoop(loopArray, grayArray); int times = 0;//在loop特征分割完成后,允许根据guideline分割的次数,现暂定为5次 while ((SegPathList.Count < 5) && (times < 5)) { GetMergedArea(); SegAreaList.Clear(); for (int k = 0; k < (SegPathList.Count - 1); k++) { ArrayList pathLeft = (ArrayList)SegPathList[k]; ArrayList pathRight = (ArrayList)SegPathList[k + 1]; SegAreaInfo aInfo = GetSegInfo(pathLeft, pathRight); SegAreaList.Add(aInfo); } times++; } return (GetSegImg()); }
/// <summary> ///获取字符所在区域的边界 /// </summary> /// <param name="imageHeight">二值化图像的高度</param> /// <param name="imageWidth">二值化图像的宽度</param> /// <param name="BinaryArray">二值化数组</param> /// <returns>返回字符所在区域的边界</returns> public static Boundary getImgBoundary(int imageHeight, int imageWidth, Byte[,] BinaryArray) { Boundary boundary = new Boundary(); Point leftDownP = new Point(); Point rightUpP = new Point(); Int32[] verticalPoints = new Int32[imageWidth]; Array.Clear(verticalPoints, 0, imageWidth); int x = 0, y = 0; for (x = 0; x < imageHeight; x++) { for (y = 0; y < imageWidth; y++) { if (0 == BinaryArray[x, y]) { verticalPoints[y]++; } } } //用于存储当前横坐标水平方向上的有效像素点数量(组成字符的像素点) Int32[] horPoints = new Int32[imageHeight]; Array.Clear(horPoints, 0, imageHeight); //统计源图像的二值化数组中在每一个横坐标的水平方向所包含的像素点数 for (y = 0; y < imageWidth; y++) { for (x = 0; x < imageHeight; x++) { if (0 == BinaryArray[x, y]) { horPoints[x]++; } } } //从原点开始,在竖直投影中找到左下角的Y坐标 for(y = 0; y < imageWidth; y++) { if(verticalPoints[y] != 0) { if(y != 0) { leftDownP.Y = y - 1; } else { leftDownP.Y = y; } break; } } //从离原点最远的点开始,在竖直投影中找到右上角的Y坐标 for(y = imageWidth - 1; y > 0; y--) { if(verticalPoints[y] != 0) { if (y != imageWidth - 1) { rightUpP.Y = y + 1; } else { rightUpP.Y = y; } break; } } //从原点开始,在竖直投影中找到左下角的X坐标 for (x = 0; x < imageHeight; x++) { if (horPoints[x] != 0) { if(x != 0) { leftDownP.X = x - 1; } else { leftDownP.X = x; } break; } } //从离原点最远的点开始,在竖直投影中找到右上角的X坐标 for (x = imageHeight - 1; x > 0; x--) { if (horPoints[x] != 0) { if (x != imageHeight - 1) { rightUpP.X = x + 1; } else { rightUpP.X = x; } break; } } boundary.leftDownPoint = leftDownP; boundary.rightUpPoint = rightUpP; return boundary; }
/// <summary> /// 根据两条分割路径获取已分割路径之间区域的如下信息: /// 1.两个路径之间包含的有效字符的像素点数(黑色像素的数量) /// 2.已分割区域的与baseline的交点 /// 3.已分割区域的与meanine的交点 /// 4.已分割区域的最高点(离原点最远的,X坐标较大值) /// 5.已分割区域的最低点(离原地最低点,X坐标较小值) /// </summary> public static SegAreaInfo GetSegInfo(ArrayList pathLeft, ArrayList pathRight) { //遍历两条路径之间的区域用 Point posRight = new Point(); Point posLeft = new Point(); int widthRight = 0; int widthLeft = 0; int heightRight = 0; int heightLeft = 0; int indexLeft = 0; int indexRight = 0; int posCountLeft = 0; int posCountRight = 0; int bPixCount = 0; SegAreaInfo segArea = new SegAreaInfo(); Boundary tmpBoundary = new Boundary(); //用于存储已分割区域的二值化数组 Byte[,] binaryArr = new Byte[ImageHeight, ImageWidth]; for(int m = 0; m < ImageHeight; m++) { for (int n = 0; n < ImageWidth;n ++ ) { binaryArr[m, n] = 255; } } bPixCount = 0; posCountLeft = pathLeft.Count; posCountRight = pathRight.Count; indexLeft = 0; indexRight = 0; //两条路径之间的点数可能不一致,可能会出现同一个X值对应多个Y值的状况 while ((indexLeft < posCountLeft) && (indexRight < posCountRight)) { posRight = (Point)pathRight[indexRight]; posLeft = (Point)pathLeft[indexLeft]; indexLeft++; indexRight++; widthLeft = posLeft.Y; widthRight = posRight.Y; heightRight = posLeft.X; heightLeft = posRight.X; //路径中可能会出现多个宽度值对应同一高度值的状况,找到同一高度值下最左侧的宽度值 while (indexLeft < posCountLeft) { posLeft = (Point)pathLeft[indexLeft]; if (widthLeft != posLeft.X) { break; } else { if (widthLeft > posLeft.Y) { widthLeft = posLeft.Y; } indexLeft++; } } //路径中可能会出现多个宽度值对应同一高度值的状况,找到同一高度值下最右侧的宽度值 while (indexRight < posCountRight) { posRight = (Point)pathRight[indexRight]; if (heightRight != posRight.X) { break; } else { if (widthRight < posRight.Y) { widthRight = posRight.Y; } indexRight++; } } //从左至右,从上至下,遍历两条路径之间的像素点 for (Int32 y = widthLeft; y < widthRight; y++) { binaryArr[heightLeft, y] = BinaryArray[heightLeft, y]; if (0 == BinaryArray[heightLeft, y]) { //将两条路径之间的有效像素点的总数 bPixCount++; } } } tmpBoundary = Preprocess.Preprocess.getImgBoundary(ImageHeight, ImageWidth, binaryArr); segArea.blackPixCount = bPixCount; segArea.segAreaB = tmpBoundary; segArea.binaryArr = binaryArr; return segArea; }
/// <summary> /// 根据指定loop的信息获取相应的分割点 /// </summary> /// <param name="beRight">用于标记当前需要确认的分割点在LOOP的左侧或右侧,true:右侧;false:左侧</param> /// <param name="Loop">当前研究的LOOP的坐标范围</param> /// <param name="grayArray">有效字符为黑色,loop区域为白色,背景为灰色的灰度数组</param> public static Point GetSementLoopPoint(bool beRight, Boundary Loop, Byte[,] grayArray) { Point segPoint = new Point(); int tmpH = 0; int tmpW = 0; if (beRight) { //确认LOOP右侧的分割点,先找到LOOP位于最右侧点的坐标 for (tmpH = Loop.leftDownPoint.X; tmpH < Loop.rightUpPoint.X; tmpH++) { if (255 == grayArray[tmpH, Loop.rightUpPoint.Y]) { break; } } //以LOOP最右侧点的高度坐标为分割点的高度坐标;以右侧点的宽度坐标+笔画宽度作为分割点的宽度坐标 segPoint.X = tmpH; tmpW = Loop.rightUpPoint.Y + StrokeWid; //若确认的分割点处为灰色,那么从该点开始逐点后退找到紧贴LOOP的灰色像素作为分割点 while(128 == grayArray[tmpH,tmpW]) { tmpW --; } segPoint.Y = tmpW + 1; } else { //确认LOOP左侧的分割点,先找到LOOP位于最左侧点的坐标 for (tmpH = Loop.leftDownPoint.X; tmpH < Loop.rightUpPoint.X; tmpH++) { if (255 == grayArray[tmpH, Loop.leftDownPoint.Y]) { break; } } //以LOOP最左侧点的高度坐标为分割点的高度坐标;以左侧点的宽度坐标-笔画宽度作为分割点的宽度坐标 segPoint.X = tmpH; tmpW = Loop.leftDownPoint.Y - StrokeWid; //若确认的分割点处为灰色,那么从该点开始逐点前进找到紧贴LOOP的灰色像素作为分割点 while(128 == grayArray[tmpH,tmpW]) { tmpW ++; } segPoint.Y = tmpW - 1; } return segPoint; }
/// <summary> /// 根据仅loop为白色的灰度数组定位每个loop的坐标范围 /// </summary> public static ArrayList GetLoopBoundary(Byte[,] grayArray) { int i = 0, j = 0, k = 0; int[] wHistogram = new int[ImageWidth]; int[] hHistogram = new int[ImageHeight]; //对字符所包含的封闭区间进行竖直投影 for (i = ImgBoundary.leftDownPoint.X; i < ImgBoundary.rightUpPoint.X; i++) { for (j = ImgBoundary.leftDownPoint.Y; j < ImgBoundary.rightUpPoint.Y; j++) { if (255 == grayArray[i, j]) { wHistogram[j] ++; } } } ArrayList loopArray = new ArrayList();//compose of rightUpPoint and leftDownPoint j = ImgBoundary.leftDownPoint.Y; //根据封闭区间的竖直投影获取封闭区间的左右与上下极限位置 while (j < ImgBoundary.rightUpPoint.Y) { if(wHistogram[j] != 0) { Boundary loop = new Boundary(); //高为X轴,宽为Y轴,图片的左上角为原点。 //Y轴方向离原点较远的顶点,X轴方向离原点较远的顶点,这两个点为rightUp Point posRightUp = new Point(); //Y轴方向离原点较近的顶点,X轴方向离原点较近的顶点,这两个点为LeftDown Point posLeftDown = new Point(); posLeftDown.Y = j; while ((wHistogram[j] != 0) && (j < ImgBoundary.rightUpPoint.Y)) { j ++; } posRightUp.Y = j - 1; Array.Clear(hHistogram, 0, ImageHeight); //在已确定的左右区间内对封闭区域进行水平方向的投影 for (j = posLeftDown.Y; j < posRightUp.Y; j++) { for (i = ImgBoundary.leftDownPoint.X; i < ImgBoundary.rightUpPoint.X; i++) { if (255 == grayArray[i, j]) { hHistogram[i]++; } } } //寻找水平投影起点的i的初始值 i = ImgBoundary.leftDownPoint.X; while ((0 == hHistogram[i]) && (i < ImgBoundary.rightUpPoint.X)) { i++; } posLeftDown.X = i; i = ImgBoundary.rightUpPoint.X; while ((0 == hHistogram[i]) && (i > ImgBoundary.leftDownPoint.X)) { i--; } posRightUp.X = i; loop.leftDownPoint = posLeftDown; loop.rightUpPoint = posRightUp; loopArray.Add(loop); } j ++; } //遍历找到的封闭区间内的像素,至少包含一个8邻域的区域被认为是有效封闭区间,否则将被滤除 //此处的处理方式需要根据实际验证码类型中loop的大小酌情调整, for (k = 0; k < loopArray.Count; k ++ ) { Boundary tmpLoop = (Boundary)loopArray[k]; bool tmpFlg = true; for (i = tmpLoop.leftDownPoint.X + 1; (i < tmpLoop.rightUpPoint.X - 1) && (tmpFlg); i ++ ) { for (j = tmpLoop.leftDownPoint.Y + 1; (j < tmpLoop.rightUpPoint.Y - 1) && (tmpFlg); j++) { if ((255 == grayArray[i,j]) && (255 == grayArray[i,j - 1]) && (255 == grayArray[i,j + 1]) && (255 == grayArray[i + 1, j]) && (255 == grayArray[i + 1, j - 1]) && (255 == grayArray[i + 1, j + 1]) && (255 == grayArray[i - 1, j]) && (255 == grayArray[i - 1, j - 1]) && (255 == grayArray[i - 1, j + 1])) { tmpFlg = false; } } } if (tmpFlg) { loopArray.RemoveAt(k); k--; } } return loopArray; }
public static ArrayList GetProBoundary(Point proRange, bool beBaseline,int iniX, int endX) { int i = 0, j = 0, k = 0; int[] wHistogram = new int[ImageWidth]; int[] hHistogram = new int[ImageHeight]; //对字符所包含的封闭区间进行竖直投影 for (i = iniX; i < endX; i++) { for (j = proRange.X; j < proRange.Y; j++) { if (0 == BinaryArray[i, j]) { wHistogram[j]++; } } } ArrayList loopArray = new ArrayList();//compose of rightUpPoint and leftDownPoint j = proRange.X; //根据封闭区间的竖直投影获取封闭区间的左右与上下极限位罿 while (j < proRange.Y) { if (wHistogram[j] != 0) { Boundary loop = new Boundary(); //高为X轴,宽为Y轴,图片的左上角为原点 //Y轴方向离原点较远的顶点,X轴方向离原点较远的顶点,这两个点组成rightUp Point posRightUp = new Point(); //Y轴方向离原点较近的顶点,X轴方向离原点较近的顶点,这两个点组成LeftDown Point posLeftDown = new Point(); posLeftDown.Y = j; while ((wHistogram[j] != 0) && (j < proRange.Y)) { j++; } posRightUp.Y = j - 1; Array.Clear(hHistogram, 0, ImageHeight); //在已确定的左右区间内对封闭区域进行水平方向的投影 for (j = posLeftDown.Y; j < posRightUp.Y; j++) { for (i = iniX; i < endX; i++) { if (0 == BinaryArray[i, j]) { hHistogram[i]++; } } } //投影区域的左下角的X轴坐标为baseline的X值 posLeftDown.X = iniX; i = endX; //寻找投影区域右上角的X轴坐标 while ((0 == hHistogram[i]) && (i > iniX)) { i--; } posRightUp.X = i; loop.leftDownPoint = posLeftDown; loop.rightUpPoint = posRightUp; loopArray.Add(loop); } j++; } if (beBaseline)//若是以baseline为基准的投影,则需要判断该投影区域是否有效 { //遍历找到的封闭区间内的像素,至少包含一丿邻域的区域被认为是有效封闭区间,否则将被滤除 for (k = 0; k < loopArray.Count; k++) { Boundary tmpLoop = (Boundary)loopArray[k]; //若投影区域的X轴最大值离baseline的距离小于3个像素,则认为该投影区域为无效区域 if (tmpLoop.rightUpPoint.X - iniX < 3) { loopArray.RemoveAt(k); k--; } } } return loopArray; }
public static bool SegByBaseLine(Point segP, VerPro baseArr, Boundary mBoundary, int bThVal, int newPos, bool bLeft) { bool bValid = false; //以交点为起点分别执行向上滴落和向下滴落 ArrayList pathListA = dropFallUp(segP); ArrayList pathListB = dropFallDown(segP); SegAreaInfo tmpInfo = new SegAreaInfo(); int k = 0; for (k = 0; k < pathListA.Count; k++) { pathListB.Add(pathListA[k]); } //按字符的规则而言,符合该条件的可能是字符y,j,那么分割完成后,左侧区域有一定几率没有有效字符 //确认按照该规则分割出的左侧区域是否包含有效字符像素 pathListA.Clear(); //当前粘连区域左侧竖线作为判断基准 if (bLeft) { for (k = mBoundary.leftDownPoint.X; k < mBoundary.rightUpPoint.X; k++) { Point tmpP = new Point(); tmpP.X = k; tmpP.Y = mBoundary.leftDownPoint.Y; pathListA.Add(tmpP); } tmpInfo = GetSegInfo(pathListA, pathListB); } else { for (k = mBoundary.leftDownPoint.X; k < mBoundary.rightUpPoint.X; k++) { Point tmpP = new Point(); tmpP.X = k; tmpP.Y = mBoundary.rightUpPoint.Y; pathListA.Add(tmpP); } tmpInfo = GetSegInfo(pathListB, pathListA); } if (tmpInfo.blackPixCount > bThVal)//当前分割路径为有效路径 { //SegAreaList.Add(tmpInfo); SegPathList.Insert(newPos, pathListB); bValid = true; } return bValid; }
public static bool SegByMeanRight(Point segP, VerPro meanArr, Boundary mBoundary, int bThVal, int newPos) { bool bValid = false; ArrayList pathListA = dropFallUp(segP); ArrayList pathListB = dropFallDown(segP); int k = 0; for (k = 0; k < pathListA.Count; k++) { pathListB.Add(pathListA[k]); } pathListA.Clear(); for (k = mBoundary.leftDownPoint.X; k < mBoundary.rightUpPoint.X; k++) { Point tmpP = new Point(); tmpP.X = k; tmpP.Y = mBoundary.rightUpPoint.Y; pathListA.Add(tmpP); } SegAreaInfo tmpInfo = GetSegInfo(pathListB, pathListA); if (tmpInfo.blackPixCount > bThVal)//当前分割路径为有效路径 { //SegAreaList.Add(tmpInfo); SegPathList.Insert(newPos, pathListB); bValid = true; } return bValid; }
public static bool SegByMeanLeft(Point segP, VerPro meanArr, Boundary mBoundary, int bThVal, int newPos) { bool bValid = false; //此类交点仅执行从上向下滴落,取meanline以上区域均归为左侧区域 ArrayList pathListA = dropFallUp(segP); ArrayList pathListB = new ArrayList(); Point point1 = (Point)meanArr.vRange[0]; int k = 0; //从meanline以上区域第一个区块的左下角至与meanline交点在竖直方向以竖直线分割 for (k = mBoundary.leftDownPoint.X; k < GuidePoint.Y; k++) { Point tmpP = new Point(); tmpP.X = k; tmpP.Y = point1.X; pathListB.Add(tmpP); } //从从meanline以上区域第一个区块的左下角至meanline交点处在水平方向以横线分割 for (k = point1.X; k < meanArr.vInter.X; k++) { Point tmpP = new Point(); tmpP.X = GuidePoint.Y; tmpP.Y = k; pathListB.Add(tmpP); } for (k = 0; k < pathListA.Count; k++) { pathListB.Add(pathListA[k]); } //按字符的规则而言,符合该条件的可能是字符A,Z,7,若为字符Z/7,那么分割完成后,左侧区域有一定几率没有有效字符 //确认按照该规则分割出的左侧区域是否包含有效字符像素 pathListA.Clear(); //当前粘连区域左侧竖线作为判断基准 for (k = mBoundary.leftDownPoint.X; k < mBoundary.rightUpPoint.X; k++) { Point tmpP = new Point(); tmpP.X = k; tmpP.Y = mBoundary.leftDownPoint.Y; pathListA.Add(tmpP); } SegAreaInfo tmpInfo = GetSegInfo(pathListA, pathListB); if (tmpInfo.blackPixCount > bThVal)//当前分割路径为有效路径 { //SegAreaList.Add(tmpInfo); SegPathList.Insert(newPos, pathListB); bValid = true; } return bValid; }