您的位置:首页 > 其它

文字检测与识别1-MSER

2015-11-19 20:10 405 查看
导语

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



这系列文章主要介绍一些自然场景下文字检测(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是为了计算maxVariation.不管是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);
#endif
for ( ; ; )
{
// take tour of all the 4 directions
while ( ((*imgptr)&0x70000) < 0x40000 )
{
// get the neighbor
int* imgptr_nbr = imgptr+dir[((*imgptr)&0x70000)>>16];
if ( *imgptr_nbr >= 0 ) // if the neighbor is not visited yet
{
*imgptr_nbr |= 0x80000000; // mark it as visited
if ( ((*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;
#endif
imgptr = imgptr_nbr;
comptr++;
initMSERComp( comptr );
comptr->grey_level = (*imgptr)&0xff;
continue;
} else {
// otherwise, push the neighbor to boundary heap
heap_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 location
accumulateMSERComp( comptr, ptsptr );
ptsptr++;
// get the next pixel from boundary heap
if ( **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 )
#else
heap_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 );
#endif
if ( pixel_val < comptr[-1].grey_level )
{
// check the stablity and push a new history, increase the grey level
if ( 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_val
for ( ; ; )
{
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 ER
if ( 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;
}
}
}
} else
break;
}
}
}


参考文献

[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.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: