导语

文字识别在现实场景中的用途非常广泛,现在已经有很多公司将这项技术用于实际中。比如车牌识别,图片转换成文档,拍照搜题,拍照翻译等。这让很多人有了错觉,感觉文字识别的技术已经炉火纯青,可以广泛应用。其实不然,车牌识别里面字体和字的类型比较单一,并且有一些矩形等辅助的特征。而拍照翻译的图片一般是文档类型,较容易识别,但也有不小的错误率。文字识别的首要问题是找到文字,其次才是识别。而在自然场景下找文字的难度比一般情况下难度要高很多,因为自然场景背景十分复杂,字的类型大小多种多样,视角,污渍,反光等也是需要考虑的问题。谷歌曾经想推出谷歌翻译眼镜,但据个人所知,这几年已经没有多少宣传,应该是技术上遇到了一些困难。

这系列文章主要介绍一些自然场景下文字检测(Scene Text或 Textin the wild),ICDAR会议2上有很多SceneText论文,大家有兴趣可以去主页上看卡,它里面也有数据集可以供你训练和测试。主要介绍三篇文章[1,2,3]里面的技术“Detectingtext in natural scenes with stroke width transform”,“Robust text detection in natural scene images”,和“Efficient Scene Text Localization and Recognition with LocalCharacter Refinement”,简单起见,分别称之为SWT,RST,EST,还有其他一些本人看过的可以查阅下面的参考文献。

自然场景下文字检测一般分为以下这么几步,产生候选(candidate),字符过滤,字符合并成文本行,文本行过滤和后处理。需要注意的是有些论文采用字符和文本行双重过滤,有些论文则只采用其中一种过滤。评价一个算法的好坏一般有两个指标,一个是精准度precision,一个是召回率recall,这名字有点拗口,不过非常简单。比如说一幅图片中有100个字,但是算法找出了120个框,其中80是个是字,20个不是字,那么精准度就是80/120,召回率是80/100,可以用f这个单一的指标去描述P和R,f = 2/(1/p+1/r),从中可以看出,P和R只要有一个很低,f值都会很低。

产生候选(Extract candidates

现在很多文章都采用连通域类方案,如SWT采用的连通域,文献[12]采用的ER(extremal region),RST和EST采用的MSER(maximally stable extremal regions),还有的采用了MSER的衍生版,如[7]采用的是CER,还有的比较“另类”,[5]用的是对称特征检测器,[8]用的是edge box,用于物体检测的,[16]为了召回率利用一些特征合并产生非常非常的候选,并通过word级别的识别来过滤,因此速度特别慢(Titan下一到两分钟)。但是大部分用的还是MSER类的,虽然在ICDAR的数据库中还有一些字母MSER检测不出来,但是从性能和效果上说,MSER还是具有一些优势(请注意以下所讲的都是灰度图的MSER,彩色图的MSER用的是不同的算法)。因此下面主要以opencv的实现讲MSER,为了照顾到后面的过滤步骤,会提取出MSER的树形结构。首先给两张图片让大家有个直观的感受。左图是在灰色通道上的MSER结果,右图是经过文献[2]MSER Tree过滤的结果

opencv的代码参考的是文献[15],最开始一般去看代码和文献都会有点晕,个人感觉最好的理解的方法就是找个例子,然后按照论文和代码的流程一步步去推演。opencv的代码做了一些优化,对c++和算法不是很熟悉的话可能要看很久,另外一个版本会更好理解,但是请注意它是GNU license

ER

opencv里并没有提取出树的信息,所以先依照opencv的代码介绍ER。ER代表着是图片中一个连通(比如4连通或8连通)区域的集合,此集合内所有的像素值都小于等于某一值,而这个区域内的边界都大于这个值。我们可以把像素的值想象成地势,而把一个ER想象成一个填满水的坑洼的水坑(在这里我们采用4连通)。在这个水坑里,有一个水位淹没了所里面所有的像素但,也就是说这个区域里所有的地势(像素值)都要低于这个水位,并且水也流不出去,因为水盆有个边缘(边缘像素值要高于这个水位)。虽然水流的方式跟现实中有些区别,但是大体意思是一致的,后面会提到。

考虑如下一个简单的3*3的一个图片

3

2

2

2

3

1

1

2

3

它的提取方式如下图,为了方便讲解,在每个操作上都打了ID(上方的红色数字),参考流程图和代码,详细过程和流程如下

注意一般在最开始会放一个水位最高的256的dummy component作为根节点,因为图像的最高值在255.另外开始点从(1,0)开始(坐标行在前,列在后),可以稍微节省点时间。边界存储的是与当前ER连接的边界坐标,也就是水盆边界的位置。GrowHistory存的是一个ER从低水位到高水位的过程,所有的ER(除了全图)都会存于这个history中,另外需要说明两件事情,一个是opencv中history中代表parent的是shortcut,这在计算MSER的时候就不应是父节点,但在我们这里是一样的,另外一个是history中parent child变量跟MSER中是不一致的,不然opencv的代码就已经提取出树的信息。

(1)执行'1' ' 2' ' 3',红色位置代表当前像素,如果某个位置被黄色填充,代表这个像素已经被访问。这部分主要是些初始化的工作。主要的意思是我们在(1,0)的像素点上放充分量的水,水位的值也就等于当前的像素值2.

(2)现在有水停留在红色位置(1,0),并且水位为2。人往高处走,水往低处流。在这里唯一的不同是水每次只流向一个方向,而不能同时扩散。我们跟opencv的代码保持一致采用右下左右的顺序。首先执行'7'->'8'->'10',水尝试往右流到(1,1),发现那里的地势为3,比我们当前的水位要高,自然流不过去,因此应该是个边缘,所以把(1,1)加入到地势为3的边缘中。同理执行'7'->'8'->'4'->'3'现在水尝试往下流,发现坐标为(2,0)地势为1的像素。很显然我们的水位可以流向那,这时我们的水位降低为1,先增一个ER区,而地势为2的(1,0)成了边界。

(3)现在我们在(2,0),水位为1,。执行'7'->'8'->'10',水尝试流向地势为2的(2,1),流不通,将坐标(2,1)压入边界中。

(4)执行'7'->'6'->'5',这时发现(2,0)处的周围全都尝试流通过了,我们确认当前的像素是属于当前的ER,因此将此像素压入ER栈顶的点集中。并且我们找到地势最低的边界点,作为当前点。

(5)执行'9'->'12'->'13'.刚刚的水位是1,没道理说现在就流到2了。让我们回顾一下刚才的情况,刚才的水位是1,然后发现边界的最低的地势为2,说明我们已经找到了一个ER,在这个区域已经没有邻域的地势小于等于1,并且边界都大于1.因此我们现在能做的就是提高水位。而且根据ER的定义,高地势的区域会包含连通的低地势区域,因此我们要将其合并。为了方便,grow history的ID从10开始

(6)现在又来到了熟悉的流程,执行'14'->'7'->'8'->'10',将(2,2)压入边界。执行'7'->'6'->'5',发现当前位置已经都访问过了,将该点压入栈顶的er,因此弹出边界(1,0),发现边界的地势跟当前的水位是一样的,因此直接将其作为该当前点。

(7)执行'7'->'8'->'10',继续探索,还有未访问邻域(0,0)压入边界。

(8)所有的邻域都已访问,执行'7'->'6'->'5',将当前的点压入ER栈顶,并弹出边界(0,0)

(9)又到了要提高水位的时候,发现栈的第二个水位是256,如果提高到256,水位太大了。因此我们将当期的er保存的history,并把它的水位提高到当前位置的地势值3。而且到了这一步我们可以检查地势为1的ER是否为MSER了,依旧是Grow History ID 10保存的内容。

(10)执行'7'->'8'->'4"->'3',访问到地势为2的(0,1),因此水位再次下降

(11)继续往外探索,执行'7'->'8'->'10',将(0,2)压入边界

(12)执行'7'->'6'->'5'->'9',没有未访问的邻域点,将(0,1)压入ER栈,并弹出边界,发现发现当前的像素还在一个水位上,因此不需要合并或者升水位

(13)继续探索,发现低地势的(1,2),水位下降,将当前点压入边界

(14)现在所有的点都已访问了,将坐标(1,2)压入ER栈,并弹出边界

(15)上一步中的边界水位比我们的要高,并观察ER栈的gray level,因此现合并栈顶的两个ER

(16)与上面的情况类似,压入当前点到ER栈,弹出边界,并合并栈顶量ER

(17)按照之前的过程,连续压入对角线上的3,已经没有边界了,推出。自此我们找出了所有的ER.

MSER Tree

按照上面的流程,我们提取了所有的ER,他们的ID分别为1,10,11,12,13.要构建树,需要定义父子关系,我们把合并过程中高地势的为父,低水位的为子,因此构建树如下

那怎么判断一个ER是不是MSER呢?对于单通道图像来说主要有五个参数,delta, maxVariation,minDiversity, minArea, maxArea.其中minArea,maxArea在opencv中代表的点数,如ID11的点数是3,ID10的点数是10。

delta是为了计算variation.不管是MSER还是特征点或者BING,Edge box而言,它的核心思想都是要找到一块区域,能跟周围的有明显的变化。在MSER里,这个是通过variation定义的。打个不恰当的比方,一个脸盆和一个水桶,脸盆底部是个ER,水桶的底部也是一个ER。但是脸盆的底部跟边缘的高度相差不大,我只要把水位增加一点,水就溢出来,脸盆的边缘和底部合成了一个新的ER。但如果是水桶,你需要加很多水才能行成新的ER。因此水桶的ER更稳定,它跟周围的对比度更强。一个正式的定义是

其中S代表的ER的点数,在Opencv中简化为

比如delta= 2,要计算ID1的vatiation,可以看出S(ERlevel) =9,ID1的gray level是3,因此要找到3-2 =1gray level的ER,我们去点数最多的,都是1,因此按照上面的公式是(9-1)/1,variation是8.而opencv默认的是0.25.另外还有个限制是当前ER的variation要小于父和子的variation,对于这一点,也简化了。

minDiversity是为了解决两个MSER靠的很近的问题。公式如下,MSERson代表的是子节点代数最近的已经确认是MSER的区域。如果有个子MSER,而且两个点数比较接近,我们认为两个ER相隔太近,父的ER就不能当成MSER.Openv默认的值是0.2

最后如果我们对上面的ER tree做假设,只有ID10,11,13是MSER,那么我们的MSER tree就分成了两个Tree.

另外很重要的一点,如字有黑底白字和白底黑字,要把原来的图像像素反转一下img=255-img,按照流程再算一遍。

至此,这部分就已讲完。由于本人水平有限,难免疏漏与错误,恳请批评与指正。

附opencv这部分的核心代码

static void extractMSER_8UC1_Pass( int* ioptr,int* imgptr,int*** heap_cur,LinkedPoint* ptsptr,MSERGrowHistory* histptr,MSERConnectedComp* comptr,int step,int stepmask,int stepgap,MSERParams params,int color,CvSeq* contours,CvMemStorage* storage )
{comptr->grey_level = 256;comptr++;comptr->grey_level = (*imgptr)&0xff;initMSERComp( comptr );*imgptr |= 0x80000000;heap_cur += (*imgptr)&0xff;int dir[] = { 1, step, -1, -step };
#ifdef __INTRIN_ENABLED__unsigned long heapbit[] = { 0, 0, 0, 0, 0, 0, 0, 0 };unsigned long* bit_cur = heapbit+(((*imgptr)&0x700)>>8);
#endiffor ( ; ; ){// take tour of all the 4 directionswhile ( ((*imgptr)&0x70000) < 0x40000 ){// get the neighborint* imgptr_nbr = imgptr+dir[((*imgptr)&0x70000)>>16];if ( *imgptr_nbr >= 0 ) // if the neighbor is not visited yet{*imgptr_nbr |= 0x80000000; // mark it as visitedif ( ((*imgptr_nbr)&0xff) < ((*imgptr)&0xff) ){// when the value of neighbor smaller than current// push current to boundary heap and make the neighbor to be the current one// create an empty comp(*heap_cur)++;**heap_cur = imgptr;*imgptr += 0x10000;heap_cur += ((*imgptr_nbr)&0xff)-((*imgptr)&0xff);
#ifdef __INTRIN_ENABLED___bitset( bit_cur, (*imgptr)&0x1f );bit_cur += (((*imgptr_nbr)&0x700)-((*imgptr)&0x700))>>8;
#endifimgptr = imgptr_nbr;comptr++;initMSERComp( comptr );comptr->grey_level = (*imgptr)&0xff;continue;} else {// otherwise, push the neighbor to boundary heapheap_cur[((*imgptr_nbr)&0xff)-((*imgptr)&0xff)]++;*heap_cur[((*imgptr_nbr)&0xff)-((*imgptr)&0xff)] = imgptr_nbr;
#ifdef __INTRIN_ENABLED___bitset( bit_cur+((((*imgptr_nbr)&0x700)-((*imgptr)&0x700))>>8), (*imgptr_nbr)&0x1f );
#endif}}*imgptr += 0x10000;}int imsk = (int)(imgptr-ioptr);ptsptr->pt = cvPoint( imsk&stepmask, imsk>>stepgap );// get the current locationaccumulateMSERComp( comptr, ptsptr );ptsptr++;// get the next pixel from boundary heapif ( **heap_cur ){imgptr = **heap_cur;(*heap_cur)--;
#ifdef __INTRIN_ENABLED__if ( !**heap_cur )_bitreset( bit_cur, (*imgptr)&0x1f );
#endif} else {
#ifdef __INTRIN_ENABLED__bool found_pixel = 0;unsigned long pixel_val;for ( int i = ((*imgptr)&0x700)>>8; i < 8; i++ ){if ( _BitScanForward( &pixel_val, *bit_cur ) ){found_pixel = 1;pixel_val += i<<5;heap_cur += pixel_val-((*imgptr)&0xff);break;}bit_cur++;}if ( found_pixel )
#elseheap_cur++;unsigned long pixel_val = 0;for ( unsigned long i = ((*imgptr)&0xff)+1; i < 256; i++ ){if ( **heap_cur ){pixel_val = i;break;}heap_cur++;}if ( pixel_val )
#endif{imgptr = **heap_cur;(*heap_cur)--;
#ifdef __INTRIN_ENABLED__if ( !**heap_cur )_bitreset( bit_cur, pixel_val&0x1f );
#endifif ( pixel_val < comptr[-1].grey_level ){// check the stablity and push a new history, increase the grey levelif ( MSERStableCheck( comptr, params ) ){CvContour* contour = MSERToContour( comptr, storage );contour->color = color;cvSeqPush( contours, &contour );}MSERNewHistory( comptr, histptr );comptr[0].grey_level = pixel_val;histptr++;} else {// keep merging top two comp in stack until the grey level >= pixel_valfor ( ; ; ){comptr--;MSERMergeComp( comptr+1, comptr, comptr, histptr );histptr++;if ( pixel_val <= comptr[0].grey_level )break;if ( pixel_val < comptr[-1].grey_level ){// check the stablity here otherwise it wouldn't be an ERif ( MSERStableCheck( comptr, params ) ){CvContour* contour = MSERToContour( comptr, storage );contour->color = color;cvSeqPush( contours, &contour );}MSERNewHistory( comptr, histptr );comptr[0].grey_level = pixel_val;histptr++;break;}}}} elsebreak;}}
}

参考文献

[1]Epshtein B, Ofek E, WexlerY. Detecting text in natural scenes with stroke width transform[C]//ComputerVision and Pattern Recognition (CVPR), 2010 IEEE Conference on. IEEE, 2010:2963-2970.

[2]Yin X C, Yin X, Huang K, etal. Robust text detection in natural scene images[J]. Pattern Analysis andMachine Intelligence, IEEE Transactions on, 2014, 36(5): 970-983.

[3]Neumann L, Matas J.Efficient Scene Text Localization and Recognition with Local CharacterRefinement[J]. arXiv preprint arXiv:1504.03522, 2015.

[4]Zhu Y, Yao C, Bai X. Scenetext detection and recognition: Recent advances and future trends[J]. Frontiersof Computer Science, 2015.

[5]Zhang Z, Shen W, Yao C, etal. Symmetry-Based Text Line Detection in Natural Scenes[C]//Proceedings of theIEEE Conference on Computer Vision and Pattern Recognition. 2015: 2558-2567.

[6]Huang W, Qiao Y,Tang X. Robust scene text detection with convolution neural network inducedmser trees[M]//Computer Vision–ECCV 2014.Springer International Publishing, 2014: 497-511.

[7]Sun L, Huo Q, Jia W, et al.Robust Text Detection in Natural Scene Images by Generalized Color-EnhancedContrasting Extremal Region and Neural Networks[C]//Pattern Recognition (ICPR),2014 22nd International Conference on. IEEE, 2014: 2715-2720.

[8]Jaderberg M, Simonyan K,Vedaldi A, et al. Reading text in the wild with convolutional neuralnetworks[J]. International Journal of Computer Vision, 2014: 1-20.

[9]Jaderberg M,Vedaldi A, Zisserman A. Deep features for text spotting[M]//Computer Vision–ECCV 2014. Springer International Publishing, 2014: 512-528.

[10]Gomez L, Karatzas D. A fasthierarchical method for multi-script and arbitrary oriented scene textextraction[J]. arXiv preprint arXiv:1407.7504, 2014.

[11]Coates A, Carpenter B, CaseC, et al. Text detection and character recognition in scene images withunsupervised feature learning[C]//Document Analysis and Recognition (ICDAR),2011 International Conference on. IEEE, 2011: 440-445.

[12]Neumann L, Matas J.Real-time scene text localization and recognition[C]//Computer Vision andPattern Recognition (CVPR), 2012 IEEE Conference on. IEEE, 2012: 3538-3545.

[13]Shi B, Yao C, Zhang C, etal. Automatic Script Identification in the Wild[J]. arXiv preprintarXiv:1505.02982, 2015.

[14]Wang T, Wu D J, Coates A,et al. End-to-end text recognition with convolutional neuralnetworks[C]//Pattern Recognition (ICPR), 2012 21st International Conference on.IEEE, 2012: 3304-3308.

[15]Nistér D, Stewénius H.Linear time maximally stable extremal regions[M]//Computer Vision–ECCV 2008.Springer Berlin Heidelberg, 2008: 183-196.

[16]Gomez-Bigorda, Lluis, and Dimosthenis Karatzas. "TextProposals: a Text-specific Selective Search Algorithm for Word Spotting in the Wild." arXiv preprint arXiv:1604.02619 (2016).

[17]Lee C Y, Osindero S. Recursive Recurrent Nets with Attention Modeling for OCR in the Wild[J]. arXiv preprint arXiv:1603.03101, 2016.

文字检测与识别1-MSER相关推荐

  1. OpenCV 文字检测与识别模块

    OpenCV 文字检测与识别模块 该模块在扩展模块中,需自行下载 下载地址:https://github.com/opencv/opencv_contrib/tree/4.0.0 说明文档: 文字检测 ...

  2. 文字检测与识别资料整理

    博主关注文字检测和识别,资料整理和论文解读都非常详细: https://www.cnblogs.com/lillylin/p/6893500.html#4033329 博主的阅读习惯,积累和输出输出: ...

  3. 基于YOLOv3 与CRNN的中文自然场景文字检测与识别

    (欢迎关注"我爱计算机视觉"公众号,一个有价值有深度的公众号~) 52CV君曾经分享过多篇关于文字检测与识别的文章: 华科白翔老师团队ECCV2018 OCR论文:Mask Tex ...

  4. 中文文字检测及识别(ORC)

    中文文字检测及识别(ORC) https://github.com/471417367/chinese_ocr_api 首先基于CTPN检测到文字(可以是中英文以及数字),然后基于RCNN进行文字识别 ...

  5. 金连文:“文字检测与识别:现状及展望” | CAAI AIDL 演讲实录

    点击我爱计算机视觉标星,更快获取CVML新技术 CAAI原创 丨 作者金连文 转自中国人工智能学会,52CV获得金老师授权转载,严禁二次转载. 8月31日-9月1日,由中国人工智能学会主办,华中科技大 ...

  6. 【AI实战】手把手教你深度学习文字识别(文字检测篇:基于MSER, CTPN, SegLink, EAST等方法)

    附Java/C/C++/机器学习/算法与数据结构/前端/安卓/Python/程序员必读书籍书单大全: 书单导航页(点击右侧 极客侠栈 即可打开个人博客):极客侠栈 ①[Java]学习之路吐血整理技术书 ...

  7. 【AI实战】手把手教你深度学习文字识别(文字检测篇:基于MSER, CTPN, SegLink, EAST等方法)...

    2019独角兽企业重金招聘Python工程师标准>>> 文字检测是文字识别过程中的一个非常重要的环节,文字检测的主要目标是将图片中的文字区域位置检测出来,以便于进行后面的文字识别,只 ...

  8. 【计算机视觉】文字检测与识别资源

    本文写成时主要参考了[1,2], 后面加了一些自己收集的,不过大家都在更新,所以区别不是很大~ 综述 [2015-PAMI-Overview]Text Detection and Recognitio ...

  9. 【项目实践】中英文文字检测与识别项目(CTPN+CRNN+CTC Loss原理讲解)

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文转自:opencv学堂 OCR--简介 文字识别也是图像领域一 ...

最新文章

  1. 实现gps与服务器通信协议,一种自适应GPS通信协议分析算法的设计与实现
  2. svn里ignore不需要提交的用户文档
  3. Git多人协作工作流程
  4. Java Properties的使用
  5. xp和win7安装telnet服务
  6. miniui单元格点击弹框_miniui 给表格行添加监听事件的几种方法以及点击某列列名数据不能排序的问题...
  7. JavaScript原型与原型链
  8. 22 模块:宏伟蓝图
  9. android 组件内部实现触摸事件,更改背景
  10. visio连接线文字背景填充_visio教程:如何调整连接线上文字的位置?
  11. List集合序列排序的两种方法
  12. DTCloud 报表开发
  13. java main()方法必须都写在类里面
  14. 从动物科学到乐队鼓手,腾讯技术小哥的开源人生
  15. PX4源码分析__传感器数据“sensor_combined”的来龙去脉
  16. Unity报错:InvalidOperationException:You are tring to read lnput using the UnityEngine. ……的解决办法
  17. 系统语言为繁体中文时适配APP
  18. Python数据分析2-pandas入门和实战
  19. pycharm调试时显示图片
  20. css高度根据宽度自适应显示,按比例显示宽高

热门文章

  1. 如何手动下载并安装 Visual Studio Code 的 SAP Fiori tools - Extension Pack 扩展
  2. PKI 公钥基础设施
  3. 百度地图标记打点展示
  4. bat 格式化年月日时分秒
  5. 对LBP与HOG的理解
  6. 数字签名、证书,RSA加密、解密
  7. Tekton系列之实践篇-我的第一条Pipeline
  8. 计算机农业类的sci,农学类比较好投的SCI期刊有哪些
  9. 计算机网络传输层之SR协议
  10. python1到100奇数相加_Python:从inpu将奇数相加