2019独角兽企业重金招聘Python工程师标准>>>

前面经过各种去除噪点、干扰线,验证码图片现在已经只有两个部分,如果pixel为白就是背景,如果pixel为黑就为字符。正如前面流畅所提到的一样,为了字符的识别,这里需要将图片上的字符一个一个“扣”下来,得到单个的字符,接下来再进行OCR识别。

字符分割可以说是图像验证码识别最关键的一步,因为分割的正确与否直接关系到最后的结果,如果4个字符分割成了3个,即便后面的识别算法识别率达到100%,结果也是错的。当然,前面预处理如果做得够好,干扰因素能够有效的去除,而没有影响到字符的pixel,那么分割来讲要容易得多。反过来,如果前面的干扰因素都没有去除掉,那么分割出来的可能就不是字符了。

字符的粘连是分割的难点,这一点也可以作为验证码安全系数的标准,如果验证码上的几个字符完全是分开的,那么可以保证字符分割成功率百分之百,这样验证码破解的难度就降低了很多,比如下面的字符:

这个就是CSDN的验证码,经过二值化和降噪得到的图片,可以看到这里图片已经非常干净,没有一点多余的信息,字符之间没有重叠的部分,分割起来毫无难度。

当然,大多数IT巨头的网页验证码里地字符都是粘连在一起的,比如谷歌的验证码:

谷歌的验证码不仅粘连成都很大,而且字符扭曲地也特别厉害,所以破解起来那是难度非常大了

至于图片分割,我再这里介绍两种简单地方法。

一、 泛水填充法

泛水填充法在前面降噪的地方就提到过,主要思路还是连通域的思想。对于相互之间没有粘连的字符验证码,直接对图片进行扫描,遇到一个黑的pixel就对其进行泛水填充,所有与其连通的字符都被标记出来,因此一个独立的字符就能够找到了。这个方法优点是效率高,时间复杂度是O(N),N为像素的个数;而且不用考虑图片的大小、相邻字符间隔以及字符在图片中得位置等其他任何因素,任何验证码图片只要字符相互是独立的,不需要对其他任何阀值做预处理,直接就操作;用这种方法分割正确率非常高,几乎不会出现分割错误的情况。但是缺点也很致命:那就是字符之间必须完全隔离,没有粘连的部分,否则会将两个字符误认为一个字符。

代码如下:

[cpp]  view plain  copy

  1. for (i = 0; i < nWidth; ++i)
  2. for (j = 0; j < nHeight; ++j)
  3. {
  4. if ( !getPixel(i,j) )
  5. {
  6. //FloodFill each point in connect area using different color
  7. floodFill(m_Mat,cvPoint(i,j),cvScalar(color));
  8. color++;
  9. }
  10. }
  11. int ColorCount[256] = { 0 };
  12. for (i = 0; i < nWidth; ++i)
  13. {
  14. for (j = 0; j < nHeight; ++j)
  15. {
  16. //caculate the area of each area
  17. if (getPixel(i,j) != 255)
  18. {
  19. ColorCount[getPixel(i,j)]++;
  20. }
  21. }
  22. }
  23. //get rid of noise point
  24. for (i = 0; i < nWidth; ++i)
  25. {
  26. for (j = 0; j < nHeight; ++j)
  27. {
  28. if (ColorCount[getPixel(i,j)] <= nMin_area)
  29. {
  30. setPixel(i,j,WHITE);
  31. }
  32. }
  33. }
  34. int k = 1;
  35. int minX,minY,maxX,maxY;
  36. vector<Image> vImage;
  37. while( ColorCount[k] )
  38. {
  39. if (ColorCount[k] > nMin_area)
  40. {
  41. minX = minY = 100;
  42. maxX = maxY = -1;
  43. //get the rect of each charactor
  44. for (i = 0; i < nWidth; ++i)
  45. {
  46. for (j = 0; j < nHeight; ++j)
  47. {
  48. if(getPixel(i,j) == k)
  49. {
  50. if(i < minX)
  51. minX = i;
  52. else if(i > maxX)
  53. maxX = i;
  54. if(j < minY)
  55. minY = j;
  56. else if(j > maxY)
  57. maxY = j;
  58. }
  59. }
  60. }
  61. //copy to each standard mat
  62. Mat *ch = new Mat(HEIGHT,WIDTH,CV_8U,WHITE);
  63. int m,n;
  64. m = (WIDTH - (maxX-minX))/2;
  65. n = (HEIGHT - (maxY-minY))/2;
  66. for (i = minX; i <= maxX; ++i)
  67. {
  68. for (j = minY; j <= maxY; ++j)
  69. {
  70. if(getPixel(i,j) == k)
  71. {
  72. *(ch->data+ch->step[0]*(n+j-minY)+m+(i-minX)) = BLACK;
  73. }
  74. }
  75. <span style="white-space:pre">    </span>}

这段代码就是使用泛水填充法,每次扫到一个连通域就把连通域所有的pixel的灰度值改为0-255之间的一个值,比如第一个是254,下一个是253...接下来再对每一个灰度值(即每一个连通域)的pixel出现的X,Y坐标的最大、最小的值记录下来,这样就得到了每个字符的最小外包矩形,最后将这个最小外包矩形全部复制到固定大小的一个单独的Mat对象中,这个对象存储的就是一个固定分辨率大小的表现为单独字符的图片。

分割的效果可以见下面的图:

可以看到,分割效果非常好。

二、X像素投影法

对于粘连的字符,也并非没有方法分割。一个方法就是将两个粘连的验证码一刀切开,从哪里切?当然是从粘连的薄弱的地方切。前面提到过图片的像素就像一个二维的矩阵,对每一个x值,统计所有x值为这个值的pixel中黑色的数目,直观来讲就是统计每一条竖线上黑色点的数目。显而易见的是,如果这一条线为背景,那么这一条线肯定都是白色的,那么黑色点的数目为0,如果一条竖线经过字符,那么这条竖线上的黑色点数目肯定不少。

对于完全独立的两个字符之间,肯定有黑色点数目为0的竖线,但是如果粘连,那么不会有黑色点数为0的竖线存在,但是字符粘连最薄弱的地方一定是黑色点数目最少的那条竖线,因此切就要从这个地方切。

在代码的实现的过程中,可以先从左到右扫描一遍,统计投影到每个X值的黑色点的数目,然后设定一个阀值范围,这个阀值大概就是一个字符的宽度。从左到右,先找到第一个x黑色点投影不为0的x值,然后在这个x值加上大概一个字符宽度的大小找到x投影数目最小的x值,这两个x值分割出来就是一个字符了。

这个方法的特点就是能够分割粘连的字符,但是缺点就是容易分割不干净,可能会出现分割错误的情况,另外就是需要提供相应的阀值。

代码如下:

[cpp]  view plain  copy

  1. void Image::xProjectDivide(int nMin_thsd,int nMax_thsd)
  2. {
  3. int i,j;
  4. int nWidth = getWidth();
  5. int nHeight = getHeight();
  6. int *xNum = new int[nWidth];
  7. //inital the x-projection-num
  8. memset(xNum,0,nWidth*sizeof(int));
  9. //compute the black pixel num in X coordinate
  10. for (j = 0; j < nHeight; ++j)
  11. for (i = 0; i < nWidth; ++i)
  12. {
  13. if ( getPixel(i,j) == BLACK ) xNum[i]++;
  14. }
  15. /*-----------------show x project map-------------------*/
  16. Mat xProjectResult(nHeight/2,nWidth,CV_8U,Scalar(WHITE));
  17. for (i = 0; i < xProjectResult.cols-1; ++i)
  18. {
  19. int begin,end;
  20. if(xNum[i] > xNum[i+1])
  21. {
  22. begin = xNum[i+1];
  23. end = xNum[i];
  24. }
  25. else {
  26. begin = xNum[i];
  27. end = xNum[i+1];
  28. }
  29. for (j = begin; j <= end; ++j)
  30. {
  31. *(xProjectResult.data+xProjectResult.step[0]*(nHeight/2 - j - 1)+i) = BLACK;
  32. }
  33. }
  34. std::cout << "The porject of BLACK pixel in X coordinate is in the window" << std::endl;
  35. namedWindow("xProjectResult");
  36. imshow("xProjectResult",xProjectResult);
  37. waitKey();
  38. /*-----------------show x project map-------------------*/
  39. /*-------------------divide the map---------------------*/
  40. vector<int> vPoint;
  41. int nMin,nIndex;
  42. if (xNum[0] > BOUNDRY_NUM) vPoint.push_back(0);
  43. for(i = 1;i < nWidth-1 ;)
  44. {
  45. if( xNum[i] < BOUNDRY_NUM)
  46. {
  47. i++;
  48. continue;
  49. }
  50. vPoint.push_back(i);
  51. //find minimum between the min_thsd and max_thsd
  52. nIndex = i+nMin_thsd;
  53. nMin = xNum[nIndex];
  54. for(j = nIndex;j<i+nMax_thsd;j++)
  55. {
  56. if (xNum[j] < nMin)
  57. {
  58. nMin = xNum[j];
  59. nIndex = j;
  60. }
  61. }
  62. vPoint.push_back(nIndex);
  63. i = nIndex + 1;
  64. }
  65. if (xNum[nWidth-1] > BOUNDRY_NUM) vPoint.push_back(nWidth-1);
  66. //save the divided characters in map vector
  67. int ch_width = nWidth / (vPoint.size()/2) + EXPAND_WIDTH;
  68. vector<Image> vImage;
  69. for (j = 0; j < (int)vPoint.size(); j += 2)
  70. {
  71. Mat *mCharacter = new Mat(nHeight,ch_width,CV_8U,Scalar(WHITE));
  72. for (i = 0; i < nHeight; ++i)
  73. memcpy(mCharacter->data+i*ch_width+EXPAND_WIDTH/2,m_Mat.data+i*nWidth+vPoint.at(j),vPoint.at(j+1)-vPoint.at(j));
  74. Image::ContoursRemoveNoise(*mCharacter,2.5);
  75. Mat *mResized = new Mat(SCALE,SCALE,CV_8U);
  76. resize(*mCharacter,*mResized,cv::Size(SCALE,SCALE),0,0,CV_INTER_AREA);
  77. Image iCh(*mResized);
  78. vImage.push_back(iCh);
  79. delete mCharacter;
  80. }
  81. //show divided characters
  82. char window_name[12];
  83. for (i = 0; i < (int)vImage.size(); ++i)
  84. {
  85. sprintf(window_name,"Character%d",i);
  86. //vImage.at(i).NaiveRemoveNoise(1.0f);
  87. vImage.at(i).ShowInWindow(window_name);
  88. }
  89. delete []xNum;
  90. }

代码首先统计每个x坐标对应的黑色点的数目,然后根据参数提供的阀值,找到字符之间的分割点,然后将分割点入栈,如果有4个字符,就入栈8个边界。最后每次出栈两个x值,将这两个x值之间的所有像素都拷贝到一个新的Mat对象中去,这样就得到了一个独立的字符图片。

下面给出X像素投影法的运行结果图:

转载于:https://my.oschina.net/u/1426828/blog/796027

图像验证码识别(七)——字符分割相关推荐

  1. Python 识别携程中文验证码(95%正确率)并自动登陆携程+图灵图像验证码识别平台

    这两天有一个业务需求,需要登陆不同的携程账号获取订单信息,但是由于携程有验证码检测机制,而且是个中文验证码比较难识别,试了几家人工打码平台,要么贵,要么延时高,要么没办法24小时运行.最后总算让我找到 ...

  2. [验证码识别技术]字符验证码杀手--CNN

    字符验证码杀手--CNN 1 abstract 目前随着深度学习,越来越蓬勃的发展,在图像识别和语音识别中也表现出了强大的生产力.对于普通的深度学习爱好者来说,一上来就去跑那边公开的大型数据库,比如I ...

  3. 图像验证码识别(三)——基本流程讨论

    图像验证码的识别很类似OCR,不过验证码的功能就是防止机器人暴力破解,因此相比于OCR,图片上的干扰因素要多的多.因此如果直接读取图片的特征值进行训练,这样正确率会非常低. 常见的验证码干扰有很多种 ...

  4. [验证码识别技术] 字符型验证码终结者-CNN+BLSTM+CTC

    验证码识别(少样本,高精度)项目地址:https://github.com/kerlomz/captcha_trainer 1. 前言 本项目适用于Python3.6,GPU>=NVIDIA G ...

  5. opencv实现车牌识别之字符分割

    简介 在前一篇中,我们已经定位出来了在图片中车牌号的位置,并且将车牌号图片复制成了新图片,并显示出来,本章在这些被截取出来的图片上继续处理. 截取出来的新图片如下: 图像灰阶/二值化 首先也是选择将图 ...

  6. python图像验证码识别_python 简单图像识别--验证码

    python  简单图像识别--验证码 记录下,准备工作安装过程很是麻烦. 首先库:pytesseract,image,tesseract,PIL windows安装PIL,直接exe进行安装更方便( ...

  7. 图像数字识别、数字分割(OCR识别,毕业设计)

    基本图像处理流程 这是我在测试图像处理中使用的原始图像.它有一些眩光点,但是图像相当干净.让我们逐步完成获取此源图像的过程,并尝试将其分解为单个数字. 影像准备 在开始图像处理流程之前,我们决定先调整 ...

  8. 验证码识别之w3cschool字符图片验证码(easy级别)

    起因: 最近在练习解析验证码,看到了这个网站的验证码比较简单,于是就拿来解析一下攒攒经验值,并无任何冒犯之意... 验证码所在网页: https://www.w3cschool.cn/checkmph ...

  9. python编程胡牌将是什么意思_OpenCV+Python识别车牌和字符分割的实现

    本篇文章主要基于python语言和OpenCV库(cv2)进行车牌区域识别和字符分割,开篇之前针对在python中安装opencv的环境这里不做介绍,可以自行安装配置! 车牌号检测需要大致分为四个部分 ...

最新文章

  1. android cts测试二
  2. System.Web.Optimization找不到引用
  3. [转] GDBT详解
  4. Wijmo 更优美的jQuery UI部件集:复合图表(CompositeChart)
  5. java指定sql生成xml_SQL Server根据查询结果,生成XML文件
  6. js并发上传文件到不同服务器,simple-uploader.js 功能强大的上传组件 - 文章教程
  7. 三星Galaxy Note10前脸照曝光:下巴比iPhone还要窄
  8. bzoj 4002: [JLOI2015]有意义的字符串(特征根法+矩阵快速幂)
  9. MySQL主从复制: MHA
  10. 【语音处理】基于matlab低通滤波器语音信号加噪与去噪【含Matlab源码 1709期】
  11. SPSS方差分析应该如何进行
  12. [技术贴]网络共享与便携式WiFi热点之USB绑定模式
  13. 高校讲座信息APP的设计与实现
  14. 台式电脑接路由器步骤_台式电脑连接网络步骤
  15. 从输入URL到页面加载的过程?由一道题完善自己的Web前端知识体系!
  16. input range: vue自定义进度条
  17. 怎么将服务器上的文件下载到本地电脑上
  18. gunicorn配置文件
  19. 学习vi编辑器 —— ex 编辑器
  20. Python编程实例-Tkinter GUI编程基础超级详解

热门文章

  1. Python的包管理工具Pip
  2. 如何破解Red Hat Enterprise 4的root密码(救援有密码)
  3. Learning Perl(Perl语言入门)学习笔记(3)
  4. easyui的datagrid的使用记录
  5. [记录] 解决img的1px空白问题
  6. 《高性能Linux服务器构建实战》笔记
  7. Hadoop源代码分析(包mapreduce.lib.input)
  8. [Hibernate]在VS2010中应用NHibernate 3.2与MySQL
  9. (附下载地址)制作RPM包(星际译王词典包)
  10. 11月2日科技联播:销量不及预期苹果市值跌破万亿美元;腾讯表示封杀抖音因微信规则...