Character Filter

转载自:http://blog.csdn.net/PeaceInMind/article/details/50003319

导语

在上一章节中我们介绍了怎么在一幅图片中提取潜在的字符(character proposals)。一般情况下基本上都会发生两种不想要的情况。第一种就是有些字符没提取出来,称之为false negative,这个可以通过多通道如梯度幅值或者其他颜色通道)提取MSER来减轻。另外一种是提取出来的字符有很多不是真的字符,称之为false positive,这个需要一些过滤算法来过滤.这一节主要关注第二点,怎么去过滤false positive,这其中主要介绍文献[2]RSTMSER Tree过滤,文献[3]EST特征分类过滤和CNN过滤,CNN在论文中用的不多,不过个人做了一点实验,顺便也对比下,其中疏漏与错误,也请批评与指正。

 

1 MSER Tree过滤

MSER tree过滤的核心思想是字不能包含字。MSER就是一个个的字符,“包含”代表的就是父子关系。因此它的意思就是如果一个MSER节点是字,那么它的子节点、孙节点、曾孙节点里面都不会是字,同理它的父节点,祖父节点也都不会字。那么我们就可以过滤掉很多不是字的MSER。那我们怎么去判断一个MSER到底是不是字呢,在这里,论文并没有用特征分类器的方法,而是用了一个取巧的方法。我们不需要去判断一个MSER到底是不是字,只要判断一棵树里面哪些最像字。而像不像用的是MSER长宽比(aspect ratio)矫正后的variation进行评价,参见下面的公式,矫正后的variation越小,是字的可能性越大,其中的参数如下,论文没有提怎么得到,应该是多次试验得到或者搜索得到。



因此接下来我们就要去分析哪些最像字,去提取"disconnectedMSER"。这里并不是想象中那么非常简单,只要比较父节点和子节点,其实还是比叫孙节点,曾孙节点,祖父节点,曾祖父节点点。另外一颗树中也不一定只有一个字,也有多个"disconnectedMSER"的可能,比如在一个父节点下有多个子节点,而父节点的variation较大,我们就选了下面的几个子节点。一个可能的例子是一个大框里面有多个字符。因此这里面会涉及一些算法的问题,主要有两个算法,LinearReductionTree Accumulation,两者顺序执行。

LinearReduction主要是应对子节点只有一个的MSER。如果是当前节点更像MSER,我们就可以把它的子节点去掉,把孙节点直接链接到当前节点,反之类似。如果一个节点有多个子节点,我们就对这几个子节点做Linear Reduction,经过这一步处理后,MSER节点的子节点数目就不会等于1.伪代码如下


 

个人写的OPENCV代码如下(由于更好的演示,修改了一些Contour的一些成员名)

  

  1.       CvContour* LinearReduction(CvContour* root)  
  2.     {  
  3.         switch (root->childNum)  
  4.         {  
  5.         case 0:  
  6.             {  
  7.                 return root;  
  8.                 break;  
  9.             }  
  10.         case 1:  
  11.             {  
  12.                 CvContour* c = LinearReduction((CvContour*)(root->v_next));  
  13.                 if (c->variation  < root->variation)  
  14.                 {  
  15.                     return c;  
  16.                 }  
  17.                 else  
  18.                 {  
  19.                     //link c's children to root  
  20.                     CvSeq* cc = c->v_next;  
  21.                     root->v_next = cc;  
  22.                     while (cc != NULL)  
  23.                     {  
  24.                         cc->v_prev = (CvSeq*)root;  
  25.                         cc = cc->h_next;  
  26.                     }  
  27.                     return root;  
  28.                 }  
  29.                 break;  
  30.             }  
  31.   
  32.   
  33.         default:  
  34.             {  
  35.                 CvSeq* c = root->v_next;  
  36.                 vector<CvContour*> children;  
  37.                 while (c != NULL)  
  38.                 {  
  39.                     CvContour* tmp = LinearReduction((CvContour*)c);  
  40.                     children.push_back(tmp);  
  41.                     tmp->v_prev = (CvSeq*)root;// reset parents;  
  42.                     c = c->h_next;  
  43.                 }  
  44.   
  45.   
  46.                 root->v_next = (CvSeq*)(children[0]);  
  47.                 for (size_t i = 0; i < children.size() - 1; ++i)//reset prev and next  
  48.                 {  
  49.                     children[i]->h_next = (CvSeq*)(children[i + 1]);  
  50.                     children[i + 1]->h_prev = (CvSeq*)(children[i]);  
  51.                 }  
  52.   
  53.   
  54.                 return  root;  
  55.                 break;  
  56.             }  
  57.         }  
  58.     }  
      CvContour* LinearReduction(CvContour* root)
    {
        switch (root->childNum)
        {
        case 0:
            {
                return root;
                break;
            }
        case 1:
            {
                CvContour* c = LinearReduction((CvContour*)(root->v_next));
                if (c->variation  < root->variation)
                {
                    return c;
                }
                else
                {
                    //link c's children to root
                    CvSeq* cc = c->v_next;
                    root->v_next = cc;
                    while (cc != NULL)
                    {
                        cc->v_prev = (CvSeq*)root;
                        cc = cc->h_next;
                    }
                    return root;
                }
                break;
            }


        default:
            {
                CvSeq* c = root->v_next;
                vector<CvContour*> children;
                while (c != NULL)
                {
                    CvContour* tmp = LinearReduction((CvContour*)c);
                    children.push_back(tmp);
                    tmp->v_prev = (CvSeq*)root;// reset parents;
                    c = c->h_next;
                }


                root->v_next = (CvSeq*)(children[0]);
                for (size_t i = 0; i < children.size() - 1; ++i)//reset prev and next
                {
                    children[i]->h_next = (CvSeq*)(children[i + 1]);
                    children[i + 1]->h_prev = (CvSeq*)(children[i]);
                }


                return  root;
                break;
            }
        }
    }


 

Tree Accumulation是用于子节点大于1的情况下,经过LinearReduction后,节点的子节点数目要么是0。要么就大于1.对于子节点数目大于2的,我们去找到子树里面像字的,然后看子树里有没有比当前节点更像字的节点,如果有,则保存子树中像字的,否则,就保存当前节点。执行完后我们就获得了disconnected MSER,伪代码和c++的代码如下,c++代码为了演示方便,修改了contour的一些成员名。


  1. vector<CvContour*> RobustSceneText::TreeAccumulation(CvContour* root)  
  2.     {  
  3.         //need to recalcu childNum due to linear reduction  
  4.         vector<CvContour*> vTmp;  
  5.         vTmp.push_back(root);  
  6.         CalcChildNum(vTmp);  
  7.         assert(root->childNum!=1);  
  8.         vector<CvContour*> result;  
  9.         if ( root->childNum >= 2 )  
  10.         {  
  11.             CvContour* c = (CvContour*)(root->v_next);  
  12.             while ( c != NULL )  
  13.             {  
  14.                 vector<CvContour*> tmp;  
  15.                 tmp = TreeAccumulation(c);  
  16.                 result.insert( result.end(), tmp.begin(), tmp.end());  
  17.                 c = (CvContour*)c->h_next;  
  18.             }  
  19.             for (size_t i = 0; i < result.size(); ++i)  
  20.             {  
  21.                 if ( std::abs(result[i]->variation) < std::abs( root->variation) )  
  22.                 {  
  23.                     return result;  
  24.                 }  
  25.             }  
  26.             result.clear();  
  27.             result.push_back(root);  
  28.             return result;  
  29.         }  
  30.         else  
  31.         {  
  32.             result.push_back(root);  
  33.             return result;  
  34.         }  
  35.     }  
vector<CvContour*> RobustSceneText::TreeAccumulation(CvContour* root)
    {
        //need to recalcu childNum due to linear reduction
        vector<CvContour*> vTmp;
        vTmp.push_back(root);
        CalcChildNum(vTmp);
        assert(root->childNum!=1);
        vector<CvContour*> result;
        if ( root->childNum >= 2 )
        {
            CvContour* c = (CvContour*)(root->v_next);
            while ( c != NULL )
            {
                vector<CvContour*> tmp;
                tmp = TreeAccumulation(c);
                result.insert( result.end(), tmp.begin(), tmp.end());
                c = (CvContour*)c->h_next;
            }
            for (size_t i = 0; i < result.size(); ++i)
            {
                if ( std::abs(result[i]->variation) < std::abs( root->variation) )
                {
                    return result;
                }
            }
            result.clear();
            result.push_back(root);
            return result;
        }
        else
        {
            result.push_back(root);
            return result;
        }
    }


 


下图展示了对比图,MSER是依照文献[3]HSVHV两通道中提取

 


 

2 特征分类过滤

特征分类过滤主要是利用人工设计的一些特征,比如说Stroke width, Stroke variance, Aspectratio, hull ratio等等并送到分类器中进行分类,我们就知道哪些是字符,哪些不是字符。但是请注意由于特征设计和分类器的不同,会导致判别的错误。在这里我们主要是介绍stroke相关的知识,主要是文献[1]里的SWTstroke widthtransform)和文献[3]stroke supportpixels(SPPs).

Stroke width称之为笔画宽度,最早见于文献[1],并且作者申请了专利,一般算法很难申请专利,所以可见其独到之处。当我们在用中性笔在写字的时候,一撇一划的笔画宽度一般都固定在一定的范围之类,与你笔芯的滚珠有关系,比如下图的'h',它的笔画宽度大约在2左右。


文献[1]中的计算算法比较复杂,本人也实现了c++的版本,但今天介绍一种更简单的近似解法,方便讲解,利用opencv也能很快实现。具体的计算方法如下

1)第一步首先根据MSER或者连通域等构建出二值图像,比如背景是0,前景是255


2)接着对所有前景像素计算它与离其最近的0点的距离,这里的参数与文献[3]保持一致,得到distance map


3)接着提取出字符的骨架,我这里用的是Guo_Huo_Thinner算法,得到skeleton


4)计算骨架上像素的distance的均值,就得到了我们的stroke width.过滤的时候还可以用strokewidth variance,这幅图上计算出来Stroke均值和方差分别为2.270.52

 

个人觉得stroke的特征还是非常好用的,能区分很多的字与非字。文献[3]stroke均值和方差特征进行了改变,提出了strokesupport pixels(SSPs)Strke Area ratio. SSPs跟上面的步骤的不同之处是不再找字符的骨架,而是利用第二步中的ditance map图找局部最大点,如下图的红色点(论文中局部是3*3的),我们把这些点称之为SSPs


SSPs一般都在stroke的中间位置。最后通过这些点去估计整个字符的stroke Area ratio,计算公式如下,Ni3*3局部区域内SSPs的的个数。当你的笔画很工整时,一般这个值会接近1(一个特殊的情况是当stroke width1的时候这个值远超1,按照论文的公式strokeAreaRatio0.88左右。



 

按照文献[3]训练的分类器的精度不是特高,在85%-90%左右,可能是本人训练的原因或是程序bug.


 

3 CNN字符过滤

现在的论文用CNN过滤的不太很多。CNN用起来比较简单,不需要手工去设计特征,但是对硬件要求比较高,如果说硬件比较挫,速度可能就跟不上。但是CNN有一个好处是它能根据你的数据集提从更高层面上区分字和非字符。以英文为例,我可以设计出很多不是字,但是按照人工特征很难区分的图形,比如下图


但是CNN会一定程度上会判断这个图像是不是像26个字母中的一个,因此能过滤更多的非字符。个人做了一些实验,如下图,请注意这里采用了了梯度幅值通道,并且首先利用了MSER tree进行过滤。但是这样一来,它不能适用于所有文字。不过个人感觉这种情况现实中不太多。


至此,这一小节就已讲完,错误与疏漏,恳请批评和指正。

上一博客 文字检测与识别1-MSER

下一博客 文字检测与识别3-字符合并

[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 VisionECCV 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 VisionECCV 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.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值