徒手打造一款PK 名片全能王 的名片识别应用--名字篇之(如何100%准确提取名字)

接上文,名片全能王,虽然自称王,且敢当王的肯定不白给,但不代表这款产品没有毛病的地步。

做为专业人士,不得不吐槽一下,中文名字处理问题就很大,片全能王还得有做更多的工作才配那78块钱和那个名字,先看个错误:


正确的处理结果应当是这样的



名片全能王的错误在于:全军识别成了全室,职位还多了个 ”一了“,很是莫名其妙。

出现这类错误,只能说名片全能王 名字提取处理过于简单了,仅仅是联通区测试。


当然,名片识别过程中,名字是最难提取的,因为名字样式极其多变,要100%准确提取名字内容,并非易事。但名片扫描通

ScanZen做了这件事,并且愿意分享这些成果。

那如何才能100%准确提出名字呢?让我们先对名字进行分类以及约定:

  • 大字体名字
    • 名字字符间距大
      • 有Title环绕的名字
        • title与名字距离大
        • title与名字距离小
      • 无Title环绕的名字
    • 名字字符间距小
  • 小字体名字
    • 有Title环绕的名字
    • 无Title环绕的名字
名字提取要考虑因素有:字体大小、字符间距、字体粗细、Title环绕(左右上下),只要针对这多种组合进行处理,名字是完全能准确提取出来的。
处理办法:
在做版面分析时,分阶段强化提取联通区,找到象大字体名字行
[cpp]  view plain copy
  1. LogoParse::LogoParse(Mat& card):m_scale(SCALE_IMG_WHEN_LAYOUT_PARSE)  
  2. {  
  3.     INFO << __FUNCTION__ <<std::endl;  
  4.       
  5.     assert(1==m_scale);  
  6.       
  7.     cv::bitwise_not(card, m_cropped);  
  8.       
  9.     //如果比BCR_5Point更大的话,会引发版面左右粘连  
  10.     rlsaInHorizon(m_cropped, BCR_10POINT/m_scale);  
  11.       
  12.     //x方向不要处理,会引发左右丢字或者 名字名 孟,上面的子被处理没了  
  13.     int erosion_type = MORPH_RECT; // MORPH_RECT, MORPH_CROSS, MORPH_ELLIPSE  
  14.     int erosion_size = 2;  
  15.     Mat erosion_element = getStructuringElement( erosion_type, Size( 2*0 + 1, 2*erosion_size+1), Point( 0, erosion_size));  
  16.       
  17.     cv::erode(m_cropped, m_cropped,erosion_element);  
  18.     cv::dilate(m_cropped, m_cropped,erosion_element);  
  19.       
  20.     //find blobs  
  21.     {  
  22.         IplImage cropped(m_cropped);  
  23.           
  24.         // get blobs and filter them using its area  
  25.         //CBlobResult blobs;  
  26.           
  27.         // find blobs in image  
  28.         try {  
  29.             blobs = CBlobResult( &cropped, NULL, 0 );  
  30.         } catch (...) {  
  31.             ERROR << "CBlobResult throw exception" <<std::endl;  
  32.         }  
  33.           
  34.         int N = blobs.GetNumBlobs();  
  35.           
  36.         forint j=0; j<N; j++)  
  37.         {  
  38.             CBlob *currentBlob = blobs.GetBlob(j);  
  39.             CvRect rect = currentBlob->GetBoundingBox();  
  40.             //scale it  
  41.             rect.x *= m_scale;  
  42.             rect.y *= m_scale;  
  43.             rect.width *= m_scale;  
  44.             rect.height *= m_scale;  
  45.               
  46.             //过滤没有成行的版面(如长宽比不足的,小于2);简章的版面识别  
  47.             if (  
  48.                 rect.height > BCR_1POINT &&  
  49.                 rect.height < BCR_10POINT&&  
  50.                 double(rect.width)/rect.height > 2  
  51.                   
  52.                 )  
  53.             {  
  54.                 currentBlob->FillBlob(&cropped, CV_RGB(0, 0, 0));  
  55.             }  
  56.         }  
  57.           
  58.         rlsaInHorizon(m_cropped, BCR_20POINT*1.5/m_scale);  
  59.         rlsaInVertical(m_cropped, BCR_10POINT/m_scale);  
  60.         int erosion_type = MORPH_RECT; // MORPH_RECT, MORPH_CROSS, MORPH_ELLIPSE  
  61.         int erosion_size = 2;  
  62.         Mat erosion_element = getStructuringElement( erosion_type, Size( 2*erosion_size + 1, 2*erosion_size+1), Point( erosion_size, erosion_size));  
  63.           
  64.         cv::erode(m_cropped, m_cropped,erosion_element);  
  65.         cv::dilate(m_cropped, m_cropped,erosion_element);  
  66.           
  67.         {  
  68.             // find blobs in image  
  69.             try {  
  70.                 blobs = CBlobResult( &cropped, NULL, 0 );  
  71.             } catch (...) {  
  72.                 ERROR << "CBlobResult throw exception" <<std::endl;  
  73.             }  
  74.               
  75.             IplImage editImage(card);  
  76.             int N = blobs.GetNumBlobs();  
  77.               
  78.             forint j=0; j<N; j++)  
  79.             {  
  80.                 CBlob *currentBlob = blobs.GetBlob(j);  
  81.                 CvRect rect = currentBlob->GetBoundingBox();  
  82.                 //scale it  
  83.                 rect.x *= m_scale;  
  84.                 rect.y *= m_scale;  
  85.                 rect.width *= m_scale;  
  86.                 rect.height *= m_scale;  
  87.                   
  88.                 //提取大概的大字体名字行  
  89.                 if (  
  90.                     rect.height > BCR_10POINT &&  
  91.                     rect.height < BCR_20POINT &&  
  92.                     double(rect.width)/rect.height > 2.0 &&  
  93.                     double(rect.width)/rect.height < 10.0  
  94.                     )  
  95.                 {  
  96.                     m_nameBox.push_back(rect);  
  97.                       
  98.                 }  
  99.                 else  
  100.                 {  
  101.                     currentBlob->FillBlob(&editImage, CV_RGB(255, 255, 255));  
  102.                 }  
  103.             }  
  104.         }  
  105.     }  
  106.       
  107.     //尽快释放内存  
  108.     m_cropped.release();  
  109.       
  110.     INFO << __FUNCTION__<< " end" <<std::endl;  
  111. }  


大字体名字行可能包含了Tile,对名字行还需要进行分割处理,行内进行聚类分析,将名字与Title拆开


[cpp]  view plain copy
  1. std::vector<LineStatic> LineStatic::ccl_name_title() const  
  2. {  
  3.     /** 
  4.      *  case 1: NNN TTT (可归为按空格分) 
  5.      *  case 2: NNN ttt (字体高低有别且有别处有空格) 
  6.      *  case 3: N N N ttt 
  7.      */  
  8.       
  9.     //字体高度分类  
  10.       
  11.         此处理省去1000字<img alt="微笑" src="http://static.blog.csdn.net/xheditor/xheditor_emot/default/smile.gif">  
  12.         // 输出分簇结果  
  13.           
  14.         for(int i=0;i<clusters.size();i++)  
  15.         {  
  16.             BCR::Point center = clusters[i].getCenter();  
  17.             mincomplexity = std::min(mincomplexity, center.coordinate[0]);  
  18.             if (mincomplexity == center.coordinate[0]) {  
  19.                 mini = clusters[i].getPoints();  
  20.                 num_eng = mini.size();  
  21.             }  
  22.             else  
  23.             {  
  24.                 num_chi = clusters[i].getPoints().size();  
  25.             }  
  26.             cout << clusters[i] << endl;  
  27.         }  
  28.           
  29.         INFO << "TotalClustersDistance:" << BCR::KMeans::TotalClustersDistance(clusters) << endl;  
  30.           
  31.         for (std::vector<BCR::Point>::iterator it = mini.begin(); it != mini.end(); it++) {  
  32.             mini_bg = std::min(mini_bg, it->coordinate[0]);  
  33.             mini_ed = std::max(mini_ed, it->coordinate[0]);  
  34.         }  
  35.           
  36.         BCR::Point eng_center = clusters[0].getCenter();  
  37.         BCR::Point chi_center = clusters[1].getCenter();  
  38.         if (eng_center.coordinate[0] > chi_center.coordinate[0]) {  
  39.             std::swap(eng_center, chi_center);  
  40.         }  
  41.           
  42.         title_height = eng_center.coordinate[0];  
  43.         name_height = chi_center.coordinate[0];  
  44.           
  45.         title_width = eng_center.coordinate[1];  
  46.         name_width = chi_center.coordinate[1];  
  47.     }  
  48.   
  49.       
  50.     //高低相差大于5号字,分行  
  51.     std::vector<LineStatic> nametitle;  
  52.       
  53.     double averHeightDiff = this->rect.height*0.2;  
  54.       
  55.     if ((std::abs(name_height - title_height) > averHeightDiff ||  
  56.         std::abs(name_width - title_width) > averHeightDiff)  
  57.         &&  
  58.         title_height > BCR_5POINT/2 &&  
  59.         name_height > BCR_5POINT*1.5  
  60.         )  
  61.     {  
  62.         t_boxes name;  
  63.         t_boxes title;  
  64.           
  65.         t_boxes::const_iterator it = words.begin();  
  66.         t_boxes::const_iterator it_pre = it;  
  67.         for (; it != words.end(); it++)  
  68.         {  
  69.             //名字字体大于title,且在空格处  
  70.             int space = it->x - it_pre->x - it_pre->width ;  
  71.             it_pre = it;  
  72.               
  73.             if( std::max(it->width,it->height) < name_height - averHeightDiff  && space > BCR_5POINT/2)  
  74.             {  
  75.                 break;  
  76.             }  
  77.             else  
  78.             {  
  79.                 name.insert(*it);  
  80.             }  
  81.         }  
  82.           
  83.         for (; it != words.end(); it++)  
  84.         {  
  85.             //名字字体大于title  
  86.             title.insert(*it);  
  87.         }  
  88.           
  89.         //double check  
  90.         LineStatic nameL(name);  
  91.         LineStatic titleL(title);  
  92.         if (nameL.rect.height - titleL.rect.height > BCR_DOT_POINT*2) {  
  93.             nametitle.push_back(nameL);  
  94.             nametitle.push_back(titleL);  
  95.         }  
  96.     }  
  97.       
  98.     if (0==nametitle.size()) {  
  99.         nametitle.push_back(words);  
  100.     }  
  101.       
  102.     return nametitle;  
  103.       
  104. }  

还要对OCR结果进行分析,找到最象名字的那个名字框

[cpp]  view plain copy
  1. //(1): first name  
  2. if (double(line.rect.width) / line.rect.height < 5.0  && strlen(name) > 1) {  
  3.     std::string firstCharactor(name,3);  
  4.     char* pos = strstr(ALL_CHINESE_FIRSTNAME, firstCharactor.c_str());  
  5.     if (pos) {  
  6.         weight += 10000;  
  7.         tprintf("Chinese Name checked %s\n", firstCharactor.c_str());  
  8.     }  
  9. }  

经过这样 联通区提取、再分割、再确认,最终总能找到名片中最象名字的那一块内容。整个识别过程与人脑看名字的思路是一样的,先缩小范围,聚焦,再识别。

识别结果是这样的,有图有真相,下面是测试的结果图:




刚刚完善了名字提取功能,app还来的及升级!如您想测试一下,可以到App store中下载 名片扫描通 ScanZen


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值