過分割和beam search

轉載自:http://blog.csdn.net/peaceinmind/article/details/51347679

前面的章節已經介紹了提取文本行的方法。本文主要介紹傳統的依賴over segmentation過分割,beam search和字符分類器的識別方法。主要參考文獻[1]opencv contributetext module中的代碼[5]。一般情況下我們會通過二值化,投影、連通域分割,分類器判別這套程序來做文字識別,但是一方面二值化現在還沒有一統江湖的方法,另一方面就算某些情況下二值化做的很好,如果有些字連起來,或者像中文單詞中這種有偏旁部首的,分割也不是非常好解決。因此,研究人員就提出了過分割。

過分割


圖片摘自參考文獻[3]

如上圖,核心思想就是過量分割,一個可能只有幾個字的圖片,我們可以割10刀,20刀,甚至100刀,當然要儘量把真正的分割點包含在其中,然後可以靠beam search來選擇最合適的分割組合(下面介紹)。過分割的想法很簡單,但是怎麼得到過分割點呢?不同的論文中可能採用不同的方法。

如文獻[3]用的是一個double edge相關的特徵來進行二值化,文獻[1]用的是滑窗加二分類器,opencv中用的是滑窗加多分類器,如果某個窗口是個字或者是某個字的概率很大,那麼就作爲一個潛在的分割點。接下來就是要想方法選擇一個合適的分割組合。

分割評分算法

在進行beam search搜索最優分割組合之前呢,我們需要先知道怎麼定義和計算一個分割組合的評分。這裏我們挑選opencv中用的滑窗加多分類器的方法來講解。首先我們有一個英文單詞文本行,然後定義一個滑窗,滑窗有兩個重要的參數,窗口大小和滑動歩距,窗口的高度一般跟文本行高度一致(比如說是32*32),歩距比如說5個像素(文獻1採用的是文本行高度的十分之一),那麼Rectx:0,y:0,width:32,height:32,Rect(5,0,32,32)Rect(10,0,32,32)..就是一些待評估的窗口,每隔一個歩距5就是一個潛在的分割點。如下圖,圖示中窗口選的稍微有點大,但是意思是差不多的(博主比較懶,不想再畫了)。


     滑動窗口示意圖[6]

接着我們要有已經訓練好的一個分類器,輸入是一個窗口的圖像,輸出的是每一個類別的概率,比如a的概率是多少,b的概率是多少,其他類別的概率是多少。文獻[1]用的是HOG+ANNopencv[5]用的是單層CNN[7].

這樣呢一個直觀的感覺就是選取每一個窗口裏面最大的概率求平均,比如一個文本行做了兩次分割,那麼就有三個窗口,第一個窗口是最大概率的類別是b,概率是0.3,第二個窗口是10.2,第三個窗口是s0.4,那麼這樣子這個分割的分數就是3個概率的平均值0.3.但是這樣有一個缺陷是沒有考慮上下文關係,比如前面的例子中,第一個如果是b,雖然第二個的1的概率最大,但是1l有時候很像,現實中bl一起出現的概率也比b1的概率高,所以第二個窗口的類別更有可能是1.那怎麼處理這種情況呢?我們在分數里加入轉移概率,下圖中截取opencv中統計的62個類別(小寫字母+大寫字母+數字)轉移概率


當加入轉移概率後,分割的分數計算就會變得相對複雜,這就需要維特比算法[2]Viterbi algorithm,在本人轉載的HMM帖子中有些涉及,主要是動態規劃的想法,這裏不再'贅述',附上opencv的代碼作爲參考

  1.     double score_segmentation(vector<int> &segmentation, string& outstring )  
  2.     {  
  3.    
  4.         // Score Heuristics:  
  5.         // No need to use Viterbi to know agiven segmentation is bad  
  6.         // e.g.: in some cases we discard asegmentation because it includes a very large character  
  7.         //      in other cases we do it because the overlapping between two chars is toolarge  
  8.         // TODO Add more heuristics (e.g. penalize large inter-character variance)  
  9.    
  10.         Mat interdist((int)segmentation.size()-1, 1, CV_32F, 1);  
  11.         for (size_t i=0;i<segmentation.size()-1; i++)  
  12.         {  
  13.           interdist.at<float>((int)i,0) =(float)oversegmentation[segmentation[(int)i+1]]*step_size  
  14.                                           -(float)oversegmentation[segmentation[(int)i]]*step_size;  
  15.           if((float)interdist.at<float>((int)i,0)/win_size > 2.25) // TODO explainhow did you set this thrs  
  16.           {  
  17.              return -DBL_MAX;  
  18.           }  
  19.           if((float)interdist.at<float>((int)i,0)/win_size < 0.15) // TODO explainhow did you set this thrs  
  20.           {  
  21.              return -DBL_MAX;  
  22.           }  
  23.         }  
  24.         Scalar m, std;  
  25.         meanStdDev(interdist, m, std);  
  26.         //double interdist_std = std[0];  
  27.    
  28.         //TODO Extracting start probs fromlexicon (if we have it) may boost accuracy!  
  29.         vector<double>start_p(vocabulary.size());  
  30.         for (int i=0;i<(int)vocabulary.size(); i++)  
  31.             start_p[i] =log(1.0/vocabulary.size());  
  32.    
  33.    
  34.         Mat V =Mat::ones((int)segmentation.size(),(int)vocabulary.size(),CV_64FC1);  
  35.         V = V * -DBL_MAX;  
  36.         vector<string>path(vocabulary.size());  
  37.    
  38.         // Initialize base cases (t == 0)  
  39.         for (int i=0;i<(int)vocabulary.size(); i++)  
  40.         {  
  41.             V.at<double>(0,i) =start_p[i] + recognition_probabilities[segmentation[0]][i];  
  42.             path[i] = vocabulary.at(i);  
  43.         }  
  44.    
  45.    
  46.         // Run Viterbi for t > 0  
  47.         for (int t=1;t<(int)segmentation.size(); t++)  
  48.         {  
  49.    
  50.             vector<string>newpath(vocabulary.size());  
  51.    
  52.             for (int i=0;i<(int)vocabulary.size(); i++)  
  53.             {  
  54.                 double max_prob = -DBL_MAX;  
  55.                 int best_idx = 0;  
  56.                 for (int j=0;j<(int)vocabulary.size(); j++)  
  57.                 {  
  58.                     double prob =V.at<double>(t-1,j) + transition_p.at<double>(j,i) +recognition_probabilities[segmentation[t]][i];  
  59.                     if ( prob > max_prob)  
  60.                     {  
  61.                         max_prob = prob;  
  62.                         best_idx = j;  
  63.                     }  
  64.                 }  
  65.    
  66.                 V.at<double>(t,i) =max_prob;  
  67.                 newpath[i] = path[best_idx] +vocabulary.at(i);  
  68.             }  
  69.    
  70.             // Don't need to remember the oldpaths  
  71.             path.swap(newpath);  
  72.         }  
  73.    
  74.         double max_prob = -DBL_MAX;  
  75.         int best_idx = 0;  
  76.         for (int i=0;i<(int)vocabulary.size(); i++)  
  77.         {  
  78.             double prob =V.at<double>((int)segmentation.size()-1,i);  
  79.             if ( prob > max_prob)  
  80.             {  
  81.                 max_prob = prob;  
  82.                 best_idx = i;  
  83.             }  
  84.         }  
  85.    
  86.         outstring = path[best_idx];  
  87.         return (max_prob /(segmentation.size()-1));  
  88.     }  
  89.    
  90. }  
    double score_segmentation(vector<int> &segmentation, string& outstring )
    {
 
        // Score Heuristics:
        // No need to use Viterbi to know agiven segmentation is bad
        // e.g.: in some cases we discard asegmentation because it includes a very large character
        //      in other cases we do it because the overlapping between two chars is toolarge
        // TODO Add more heuristics (e.g. penalize large inter-character variance)
 
        Mat interdist((int)segmentation.size()-1, 1, CV_32F, 1);
        for (size_t i=0;i<segmentation.size()-1; i++)
        {
          interdist.at<float>((int)i,0) =(float)oversegmentation[segmentation[(int)i+1]]*step_size
                                          -(float)oversegmentation[segmentation[(int)i]]*step_size;
          if((float)interdist.at<float>((int)i,0)/win_size > 2.25) // TODO explainhow did you set this thrs
          {
             return -DBL_MAX;
          }
          if((float)interdist.at<float>((int)i,0)/win_size < 0.15) // TODO explainhow did you set this thrs
          {
             return -DBL_MAX;
          }
        }
        Scalar m, std;
        meanStdDev(interdist, m, std);
        //double interdist_std = std[0];
 
        //TODO Extracting start probs fromlexicon (if we have it) may boost accuracy!
        vector<double>start_p(vocabulary.size());
        for (int i=0;i<(int)vocabulary.size(); i++)
            start_p[i] =log(1.0/vocabulary.size());
 
 
        Mat V =Mat::ones((int)segmentation.size(),(int)vocabulary.size(),CV_64FC1);
        V = V * -DBL_MAX;
        vector<string>path(vocabulary.size());
 
        // Initialize base cases (t == 0)
        for (int i=0;i<(int)vocabulary.size(); i++)
        {
            V.at<double>(0,i) =start_p[i] + recognition_probabilities[segmentation[0]][i];
            path[i] = vocabulary.at(i);
        }
 
 
        // Run Viterbi for t > 0
        for (int t=1;t<(int)segmentation.size(); t++)
        {
 
            vector<string>newpath(vocabulary.size());
 
            for (int i=0;i<(int)vocabulary.size(); i++)
            {
                double max_prob = -DBL_MAX;
                int best_idx = 0;
                for (int j=0;j<(int)vocabulary.size(); j++)
                {
                    double prob =V.at<double>(t-1,j) + transition_p.at<double>(j,i) +recognition_probabilities[segmentation[t]][i];
                    if ( prob > max_prob)
                    {
                        max_prob = prob;
                        best_idx = j;
                    }
                }
 
                V.at<double>(t,i) =max_prob;
                newpath[i] = path[best_idx] +vocabulary.at(i);
            }
 
            // Don't need to remember the oldpaths
            path.swap(newpath);
        }
 
        double max_prob = -DBL_MAX;
        int best_idx = 0;
        for (int i=0;i<(int)vocabulary.size(); i++)
        {
            double prob =V.at<double>((int)segmentation.size()-1,i);
            if ( prob > max_prob)
            {
                max_prob = prob;
                best_idx = i;
            }
        }
 
        outstring = path[best_idx];
        return (max_prob /(segmentation.size()-1));
    }
 
}


三 Beam search

opencv中第一步做的就是最大值抑制(NMS),如果鄰近的框有重合,且判別的是同一個類別,那麼較小概率的那個被抑制,然後在從合適的潛在分割點中找到最優的分割組合。從上面的分析知道,如果潛在分割點有10個,那麼分割的組合大概有2^10= 1024種,那麼搜索的空間還是比較大的。Beam sarch就是在寬度搜索的基礎了做了一些剪枝。

比如我們設最大的beam10

(1)那麼最開始的時候我們把所有的分割數是1的集合加入候選解中

{{分割點1}{分割點2}{分割點3},…,{分割點10}}

(2)候選解按分數從大到小排列,如果候選解超過beam的大小,就刪掉末尾的

(3)加入新的分割點形成候選解帶有2個分割點的解

{

{分割點1}{分割點2}{分割點3},…,{分割點10}

{分割點1,分割點2}{分割點1,分割點3}…,{分割點1,分割點10}

{分割點2,分割點3}{分割點2,分割點4}…,{分割點2,分割點10}

{分割點9,分割點10}
}

(4)候選解按分數從大到小排列,如果候選解超過beam的大小,就刪掉末尾的

迭代,直到“遍歷”到候選解帶有10個分割點的出現,然後分數最大的就是我們想要的分割點。

 

本文就講到這,錯誤與疏漏還請批評和指正。

 

參考文獻

[1]Bissacco A, Cummins M,Netzer Y, et al. Photoocr: Reading text in uncontrolledconditions[C]//Proceedings of the IEEE International Conference on ComputerVision. 2013: 785-792.

[2]統計學習方法[M].清華大學出版社, 2012.

[3]Bai, Jinfeng, et al."Chinese image text recognition on grayscale pixels." Acoustics,Speech and Signal Processing (ICASSP), 2014 IEEE International Conference on.IEEE, 2014.

[4]Xiangyun Ye,M. Cheriet, andC.Y. Suen, “Stroke-model-based character extraction from gray-level documentimages,” Image Processing, IEEE Transactions on, vol. 10, no. 8, pp. 1152 –

1161, aug 2001.

[5]Opencv text module: https://github.com/Itseez/opencv_contrib/tree/master/modules/text

[6]He, Pan, et al."Reading scene text in deep convolutional sequences." arXiv preprintarXiv:1506.04395 (2015).

[7]Coates, Adam, Andrew Y. Ng,and Honglak Lee. "An analysis of single-layer networks in unsupervisedfeature learning." International conference on artificial intelligence andstatistics. 2011.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章