2015年5月15日
C#实现验证码中粘连字符分割(一)
验证码中粘连字符分割有助于Tessract识别此类验证码,本文分享的分割算法可以成功分割较简单的粘连字符。在此分享实现思路以及相关代码,期望能抛砖引玉。
写文章时贴代码可能有所遗漏,这里提供完整的工程下载链接:链接: http://pan.baidu.com/s/1sjJoWKt 密码: xdeb
本文主要讲述分割算法的总体框架,如下图所示。
本文分享的分割算法中用到了本博客中曾经分享过的图像二值化,图像骨架提取zhang-suen算法,滴水算法等算法。目前本算法还不完善,以我收集的10个验证码图片为样本,目前算法仅完全成功分割了一张验证码,其他粘连字符的分割或多或少有一些缺陷。接下来将有一系列文章来讲述各子框架的实现思路,实现代码以及相关参考文献,另外本算法也会持续完善并在本博客中分享,欢迎围观拍砖。
分割成功的验证码图片:
分割算法的实现代码:
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 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 |
/* -------------------------------------------------------- * 作者:livezingy * * 博客:https://www.livezingy.com * * 开发环境: * Visual Studio V2012 * .NET Framework 4.5 * * 版本历史: * V1.0 2015年04月19日 * 实现粘粘字符的分割 --------------------------------------------------------- */ using System; using System.Collections.Generic; using System.Collections; using System.Linq; using System.Text; using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; using ImageProcess; using ImageBinarization; namespace ImageSegmention { /// <summary> /// 通常我们关注的字符并不会填满整张图片,找到我们所关注字符的边界并将其记录。 /// </summary> public class ImgBoundary { private int _heightMax = 0; public int heightMax { get { return _heightMax; } set { _heightMax = value; } } private int _heightMin = 0; public int heightMin { get { return _heightMin; } set { _heightMin = value; } } private int _widthMax = 0; public int widthMax { get { return _widthMax; } set { _widthMax = value; } } private int _widthMin = 0; public int widthMin { get { return _widthMin; } set { _widthMin = value; } } } /// <summary> /// 记录图片中顶部的谷点集合和底部的谷点集合 /// </summary> public class ImgValley { private ArrayList _upValley; public ArrayList upValley { get { return _upValley; } set { _upValley = value; } } private ArrayList _downValley; public ArrayList downValley { get { return _downValley; } set { _downValley = value; } } } public class PixPos { private int _widthPos = 0; public int widthPos { get { return _widthPos; } set { _widthPos = value; } } private int _heightPos = 0; public int heightPos { get { return _heightPos; } set { _heightPos = value; } } } public class SegmentFunction { /// <summary> /// 将输入的含粘粘字符的图片转换为分割后的字符图片 /// </summary> /// <param name="imageSrcPath">粘粘字符图片的路径</param> /// <param name="imageSrcPath">分割后图片的存储路径</param> /// <returns>字符分割后的图片为位图</returns> public static Bitmap ImageSegment(string imageSrcPath, string imageDestPath) { Bitmap bmp = new Bitmap(imageSrcPath); int Threshold = 0; Byte[,] BinaryArray = ImageBinarization.ImageUtils.ToBinaryArray(bmp, BinarizationMethods.Otsu, out Threshold); ImgBoundary Boundary = getImgBoundary(BinaryArray); ImgValley iniValley = getImgValley(BinaryArray); ImgValley filterValley = filterImgValley(iniValley, Boundary); ArrayList segPathList = getSegmentPath(filterValley, Boundary, BinaryArray); Bitmap segmentBmp = getDivideImg(BinaryArray, segPathList); segmentBmp.Save(imageDestPath, System.Drawing.Imaging.ImageFormat.Jpeg); return segmentBmp; } /// <summary> /// 获取含粘粘字符图片的谷点 /// </summary> /// <param name="BinaryArray">原始图片的二值化数组</param> /// <returns>谷点列表</returns> public static ImgValley getImgValley(Byte[,] BinaryArray) { int imageHeight = BinaryArray.GetLength(0); int imageWidth = BinaryArray.GetLength(1); ArrayList upValley = new ArrayList(); ArrayList downValley = new ArrayList(); ImgValley Valley = new ImgValley(); Byte[,] m_DesImage = BinarizationThinning.Thining.ThinPicture(BinaryArray); byte flg = 1; byte P0 = 0; byte P1 = 0; byte P2 = 0; byte P3 = 0; byte P4 = 0; byte P5 = 0; byte P6 = 0; byte P7 = 0; byte P8 = 0; byte P9 = 0; byte P10 = 0; byte P11 = 0; byte P12 = 0; byte P13 = 0; byte P14 = 0; //get the up valley point for (int j = 2; j < imageWidth; j++) { flg = 1; for (int i = 2; ((i < imageHeight) && (1 == flg)); i++) { if (0 == m_DesImage[i, j]) { flg = 0; P0 = m_DesImage[i - 2, j - 2]; P1 = m_DesImage[i - 2, j - 1]; P2 = m_DesImage[i - 2, j]; P3 = m_DesImage[i - 2, j + 1]; P4 = m_DesImage[i - 2, j + 2]; P5 = m_DesImage[i - 1, j - 2]; P6 = m_DesImage[i - 1, j - 1]; P7 = m_DesImage[i - 1, j]; P8 = m_DesImage[i - 1, j + 1]; P9 = m_DesImage[i - 1, j + 2]; P10 = m_DesImage[i, j - 2]; P11 = m_DesImage[i, j - 1]; P12 = m_DesImage[i, j]; P13 = m_DesImage[i, j + 1]; P14 = m_DesImage[i, j + 2]; //共同特征:2/3/6/7/8/12/13 if ((255 == P2) && (255 == P3) && (255 == P7) && (255 == P8) && (0 == P12) && (0 == P13) && (0 == P6) && ( ((0 == P0) && (255 == P1)) || ((255 == P4) && (255 == P9) && (0 == P14)))) { PixPos valleyPos = new PixPos(); valleyPos.widthPos = j; valleyPos.heightPos = i; upValley.Add(valleyPos); } //共同特征:1/2/6/7/8/12 if ((255 == P1) && (255 == P2) && (255 == P7) && (0 == P8) && (0 == P12) && (0 == P6) && ( (0 == P0) || ((255 == P3) && (0 == P4)))) { PixPos valleyPos = new PixPos(); valleyPos.widthPos = j; valleyPos.heightPos = i; upValley.Add(valleyPos); } //共同特征:1/2/6/7/8/11/12 if ((255 == P1) && (255 == P2) && (255 == P7) && (0 == P8) && (255 == P6) && (0 == P11) && (0 == P12) && ( ((255 == P0) && (255 == P5) && (0 == P10)) || ((255 == P3) && (0 == P4)))) { PixPos valleyPos = new PixPos(); valleyPos.widthPos = j; valleyPos.heightPos = i; upValley.Add(valleyPos); } } } } //get the down valley point for (int j = imageWidth - 3; j > 2; j--) { flg = 1; for (int i = imageHeight - 3; ((i > 0) && (1 == flg)); i--) { P0 = m_DesImage[i, j - 2]; P1 = m_DesImage[i, j - 1]; P2 = m_DesImage[i, j]; P3 = m_DesImage[i, j + 1]; P4 = m_DesImage[i, j + 2]; P5 = m_DesImage[i + 1, j - 2]; P6 = m_DesImage[i + 1, j - 1]; P7 = m_DesImage[i + 1, j]; P8 = m_DesImage[i + 1, j + 1]; P9 = m_DesImage[i + 1, j + 2]; P10 = m_DesImage[i + 2, j - 2]; P11 = m_DesImage[i + 2, j - 1]; P12 = m_DesImage[i + 2, j]; P13 = m_DesImage[i + 2, j + 1]; P14 = m_DesImage[i + 2, j + 2]; if (0 == m_DesImage[i, j]) { flg = 0; //共同特征:2/3/7/8/11/12/13 if ((0 == P2) && (0 == P3) && (255 == P7) && (255 == P8) && (255 == P11) && (255 == P12) && (255 == P13) && ( ((0 == P6) && (0 == P10)) || ((0 == P1) && (0 == P5) && (255 == P6) && (255 == P10)))) { PixPos valleyPos = new PixPos(); valleyPos.widthPos = j; valleyPos.heightPos = i; downValley.Add(valleyPos); } //共同特征:2/6/7/8/11/12/13 if ((0 == P2) && (0 == P6) && (255 == P7) && (0 == P8) && (255 == P11) && (255 == P12) && (255 == P13) && ( (0 == P14) || (0 == P10))) { PixPos valleyPos = new PixPos(); valleyPos.widthPos = j; valleyPos.heightPos = i; downValley.Add(valleyPos); } //共同特征:1/2/6/7/8/11/12/13 if ((0 == P1) && (0 == P2) && (255 == P7) && (0 == P8) && (255 == P6) && (255 == P11) && (255 == P12) && (255 == P13) && ( ((0 == P0) && (255 == P5) && (255 == P10)) || (0 == P14))) { PixPos valleyPos = new PixPos(); valleyPos.widthPos = j; valleyPos.heightPos = i; downValley.Add(valleyPos); } } } } Valley.upValley = upValley; Valley.downValley = downValley; return Valley; } /// <summary> ///在四字符的前提下对谷点进行过滤,过滤原则: ///1.与左右边界间隔距离小于1/8字符宽度的谷点淘汰; ///2.若两个谷点之间的宽度差距小于3个像素点,则靠左侧的像素点被淘汰 /// </summary> /// <param name="valleyCollection">谷点列表集合</param> /// <param name="Boundary">图片中字符所在区域的边界</param> /// <returns>过滤后的谷点列表</returns> public static ImgValley filterImgValley(ImgValley valleyCollection, ImgBoundary Boundary) { ArrayList filterUpValley = valleyCollection.upValley; ArrayList filterDownValley = valleyCollection.downValley; int upCount = filterUpValley.Count; int downCount = filterDownValley.Count; int leftWidth = Boundary.widthMin; int rightWidth = Boundary.widthMax; int filterWidth = (rightWidth - leftWidth) >> 3; int tmpWidth0 = 0; int tmpWidth1 = 0; // ArrayList removeList = new ArrayList(); PixPos pos0 = new PixPos(); PixPos pos1 = new PixPos(); for (int i = 0; i < upCount; i++) { pos0 = (PixPos)filterUpValley[i]; if (i < (upCount - 1)) { pos1 = (PixPos)filterUpValley[i + 1]; } tmpWidth0 = pos0.widthPos; tmpWidth1 = pos1.widthPos; if ((System.Math.Abs(tmpWidth1 - tmpWidth0) < 3) || ((tmpWidth0 - leftWidth) < filterWidth) || ((rightWidth - tmpWidth0) < filterWidth)) { filterUpValley.RemoveAt(i); upCount--; } } /* for (int i = 0; i < downCount; i++) { pos0 = (PixPos)filterDownValley[i]; if (i < (upCount - 1)) { pos1 = (PixPos)filterDownValley[i + 1]; } tmpWidth0 = pos0.widthPos; tmpWidth1 = pos1.widthPos; if ((System.Math.Abs(tmpWidth1 - tmpWidth0) < 3) || ((tmpWidth0 - leftWidth) < filterWidth) || ((rightWidth - tmpWidth0) < filterWidth)) { // removeList.Add(i); filterDownValley.RemoveAt(i); upCount--; } } */ filterDownValley.TrimToSize(); ImgValley filterValley = new ImgValley(); filterValley.upValley = filterUpValley; filterValley.downValley = filterDownValley; return filterValley; } /// <summary> ///根据谷点获取图片的分割路径 /// </summary> /// <param name="valleyCollection">谷点列表</param> /// <param name="Boundary">图片中字符所在区域的边界</param> /// <param name="BinaryArray">二值化图片数组</param> /// <returns>返回粘粘图片的分割路径</returns> public static ArrayList getSegmentPath(ImgValley valleyCollection, ImgBoundary Boundary, Byte[,] BinaryArray) { ArrayList segPathList = new ArrayList(); int yIndex2 = Boundary.widthMax; int yIndex1 = Boundary.widthMin; int xIndex2 = Boundary.heightMax; int xIndex1 = Boundary.heightMin; int valleyNum = valleyCollection.upValley.Count; //将图像最左侧的点所在直线作为第一条分割路径 // int validHeight = xIndex2 - xIndex1; ArrayList pathList0 = new ArrayList(); for (int a = xIndex1; a < xIndex2; a++) { PixPos newPos = new PixPos(); newPos.widthPos = yIndex1; newPos.heightPos = a; pathList0.Add(newPos); } segPathList.Add(pathList0); for (int i = 0; i < valleyNum; i ++) { PixPos pos = (PixPos)valleyCollection.upValley[i]; ArrayList pathList = new ArrayList(); int iniPointY = pos.widthPos; int iniPointX = xIndex1;//任一分割路径的高度起始点均为有效字符出现的第一个点 byte leftRightFlg = 0;//该标志位置位时,表示情况5与情况6可能会循环出现 while ((iniPointX < xIndex2) && (iniPointY < yIndex2)) { Byte pointLeft = BinaryArray[iniPointX, (iniPointY - 1)]; Byte pointRight = BinaryArray[iniPointX, (iniPointY + 1)]; Byte pointDown = BinaryArray[(iniPointX + 1), iniPointY]; Byte pointDownLeft = BinaryArray[(iniPointX + 1), (iniPointY - 1)]; Byte pointDownRight = BinaryArray[(iniPointX + 1), (iniPointY + 1)]; PixPos newPos = new PixPos(); newPos.widthPos = iniPointY; newPos.heightPos = iniPointX; pathList.Add(newPos); 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; } } segPathList.Add(pathList); } //将图像最右侧的点所在直线作为最后一条分割路径 ArrayList pathListLast = new ArrayList(); for (int a = xIndex1; a < xIndex2; a++) { PixPos newPos = new PixPos(); newPos.widthPos = yIndex2; newPos.heightPos = a; pathListLast.Add(newPos); } segPathList.Add(pathListLast); return segPathList; } /// <summary> ///根据分割路径分割粘粘字符并另存为位图 /// </summary> /// <param name="valleyCollection">谷点列表</param> /// <param name="Boundary">图片中字符所在区域的边界</param> /// <param name="BinaryArray">二值化图片数组</param> /// <returns>返回粘粘图片的分割路径</returns> public static Bitmap getDivideImg(Byte[,] BinaryArray, ArrayList SegPathList) { int imageHeight = BinaryArray.GetLength(0); int imageWidth = BinaryArray.GetLength(1); int segCount = SegPathList.Count; int locationVal = imageWidth / (segCount - 1); int locationPos = 0; Byte[,] divideArray = new Byte[imageHeight, imageWidth]; for (Int32 x = 0; x < imageHeight; x++) { for (Int32 y = 0; y < imageWidth; y++) { divideArray[x, y] = 255; } } PixPos divPosRight = new PixPos(); PixPos divPosLeft = new PixPos(); ArrayList pathListLeft = new ArrayList(); ArrayList pathListRight = new ArrayList(); int indexWidthRight = 0; int indexWidthLeft = 0; int indexHeightRight = 0; int indexHeightLeft = 0; int pointIndexLeft = 0; int pointIndexRight = 0; int posCountLeft = 0; int posCountRight = 0; for (int i = 0; i < segCount - 1; i++) { //每条分割路径的起始点与终点均相同,且不会出现同一个高度值对应两个宽度值的状况,应此每条路径的点数均相同 pathListLeft = (ArrayList)SegPathList[i]; pathListRight = (ArrayList)SegPathList[i + 1]; posCountLeft = pathListLeft.Count; posCountRight = pathListRight.Count; /* int posCount = 0; if (posCountLeft < posCountRight) { posCount = posCountLeft; } else { posCount = posCountRight; } */ locationPos = 5 + locationVal * i; pointIndexLeft = 0; pointIndexRight = 0; //目前所用的滴水算法下,同一高度值最多同时对应两个宽度值 while ((pointIndexLeft < posCountLeft) && (pointIndexRight < posCountRight)) { divPosRight = (PixPos)pathListRight[pointIndexRight]; divPosLeft = (PixPos)pathListLeft[pointIndexLeft]; pointIndexLeft++; pointIndexRight++; indexWidthLeft = divPosLeft.widthPos; indexWidthRight = divPosRight.widthPos; indexHeightRight = divPosLeft.heightPos; indexHeightLeft = divPosRight.heightPos; if(pointIndexLeft < posCountLeft) { divPosLeft = (PixPos)pathListLeft[pointIndexLeft]; if (indexHeightLeft == divPosLeft.heightPos)//若下一点高度值相同,索引值加1,宽度值更新 { pointIndexLeft++; indexWidthLeft = divPosLeft.widthPos; } } if (pointIndexRight < posCountRight) { divPosRight = (PixPos)pathListRight[pointIndexRight]; if (indexHeightRight == divPosRight.heightPos)//若下一点高度值相同,索引值加1,宽度值更新 { pointIndexRight++; indexWidthRight = divPosRight.widthPos; } } for (Int32 y = indexWidthLeft; y < indexWidthRight; y++) { divideArray[indexHeightRight, (y - indexWidthLeft + locationPos)] = BinaryArray[indexHeightRight, y]; } } } Bitmap GrayBmp = ImageBinarization.ImageUtils.BinaryArrayToBinaryBitmap(divideArray); return GrayBmp; } /// <summary> ///获取字符所在区域的边界 /// </summary> /// <param name="BinaryArray">二值化图片数组</param> /// <returns>返回字符所在区域的边界</returns> public static ImgBoundary getImgBoundary(Byte[,] BinaryArray) { ImgBoundary boundary = new ImgBoundary(); int imageHeight = BinaryArray.GetLength(0); int imageWidth = BinaryArray.GetLength(1); //记录图像出现的左右极限位置 int widthLeft = 0, widthRight = 0; //记录图像出现的上下极限位置 int heightUp = 0, heightDown = 0; byte loopFlg = 0;//当该变量置为1时将跳出双重循环 //外层循环从原点开始沿宽度方向值图像右侧 for (int x = 0; (x < imageWidth) && (0 == loopFlg); x++) { //内层循环从原点开始沿高度方向值图像下方 for (int y = 0; (y < imageHeight) && (0 == loopFlg); y++) { //遇到的第一个点为最接近原点宽度方向上的第一个点,左侧极限点 if (0 == BinaryArray[y, x]) { widthLeft = x; loopFlg = 1; } } } loopFlg = 0;//当该变量置为零时将跳出双重循环 //外层循环从图像右侧沿宽度方向到原点 for (int x = (imageWidth - 1); (x > 0) && (0 == loopFlg); x--) { //内层循环从原点开始沿高度方向到图像下方 for (int y = 0; (y < imageHeight) && (0 == loopFlg); y++) { //遇到的第一个点离原点最原的右侧极限点 if (0 == BinaryArray[y, x]) { widthRight = 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 == BinaryArray[x, y]) { heightDown = x; loopFlg = 1; } } } loopFlg = 0;//当该变量置为零时将跳出双重循环 //外层循环从原点开始沿高度方向 for (int x = 0; (x < imageHeight) && (0 == loopFlg); x++) { //内层循环从原点开始沿高度方向 for (int y = 0; (y < imageWidth) && (0 == loopFlg); y++) { if (0 == BinaryArray[x, y])//从边界开始遇到第一个黑色像素点时,此处为图像出现的在纵轴上的第一个点 { heightUp = x; loopFlg = 1; } } } boundary.widthMin = widthLeft; boundary.widthMax = widthRight; boundary.heightMin = heightUp; boundary.heightMax = heightDown; return boundary; } } } |
博主您好,感谢提供这么好的算法。这篇文章提供的代码有两个方法不存在。我按您网站上其他文章的同名方法整合,但并不能得到3C7X的分割结果,请问是否这两个方法有不同?
你好,请问你所说的两个方法指的是哪两个?另外,我会尽快将完整的代码工程以及验证码图片打包放在文章链接中提供下载,谢谢留言,欢迎讨论!
ImageBinarization.ImageUtils.ToBinaryArray(bmp, BinarizationMethods.Otsu, out Threshold);和 ImageBinarization.ImageUtils.BinaryArrayToBinaryBitmap(divideArray); 五点多回复,好早啊。
碰巧碰巧,千年一遇的早起
还有Thining的方法也是没有的。
完整的代码的下载链接我已经加到文章的链接中了,C#纯属自学,业余爱好,不规范之处还请多多指教,谢谢!
好的,谢谢。
另外想问问您这个蓝色的验证码是哪个网站的?
TAO BAO
最励志网http://www.zuilizhi.net/? 路过留个言!