当前位置:   article > 正文

OCR图片相似度对比和分类算法_ocr比对原理

ocr比对原理

目录

算法清单

前提知识

一、像素点对比

二、重心对比

三、投影比对

四、分块对比

Logistic回归的直观认识

带入数据进行训练

使用训练好的模型

训练模型的代码

参考文献:


Ocr文字识别其中的一大关键就是两张图片相似与否的判断,所以我们希望寻找一种或多种算法来计算图片的相似度。本文将对于项目中使用的比对算法进行介绍,并将其联合运用进行初步文字识别。

算法清单

 

  1. 像素点对比
  2. 重心对比
  3. 投影对比
  4. 分块对比

前提知识

        计算机处理图片并不像人这样可以直观的理解处理,在计算机中的图像可以看成一个矩阵,矩阵中的元素是一个颜色值,这个值由RGB三个参数构成,这三个参数的取值范围为0~255。当然图片的表示不只有RGB这一种,其他类型不再详述。由于0~255的范围太大了,我们应该进行图片的降维——二值化。二值化将图片变成只由黑色和白色,可以使用OTSU算法。我们再将黑色用1表示,白色用0表示,这样就得到一个矩阵,矩阵中只有数字0和1组成。

              

(图1:二值化后的图片)                   (图二:转换为01矩阵后的图片)

 

一、像素点对比

       我们将标准图与匹配图的每个像素点进行比对,如果相等,则相似点加一。这样扫描两张完图片,我们可以得到二者之间相似点的多少,再用相似点除以总点数,就可以得到一个0~1之间的数值,这就是相似度。

     

(标准图)                                                      (匹配图)

        如上图所示,共100个点,只有红色点[5,1]是不一样的,所以相似度等于99/100。

        这种比对算法是最简单的,但是实际应用效果不尽人意。试想,如果将匹配图的左上角多了一个点,如下图。那么将会直接导致匹配率下降很多87/100。实际中的情况远远比这复杂,所以效果欠佳。

(坏点图)

 

二、重心对比

       有些字符除了位置不一样,二者是很相似的。如[.]与[·],所以我们需要对其进行位置的比对。可以计算其重心,判断黑色点主要是集中在什么区域。

 

        我们循环扫描每个黑色的点,将它们的横坐标与纵坐标累加起来,得到横坐标的和与纵坐标的和。再除以点的个数,得到平均横坐标与平均纵坐标。再分别将其除以横、纵坐标的总长,得到两个个在0~1区间的数,这就代表它的重心。最后计算二者重心的距离(距离的计算将会在后文介绍),得出相似度。这种算法的缺陷也是很明显的,因为除了特殊的符号外,其他字符的重心总是相差不大的,所以精确度很低。

 

三、投影比对

       投影比对是对行与列的黑色点个数进行统计,得到一个关于图片的特征向量,再计算二者特征向量的距离(距离的计算将在后文介绍),得出相似度。

                       

(标准图的投影)                                                             (匹配图的投影)

       如上图所示,我们可以得到两组向量:

        X:{10,102,2,10,10,2,2,10,10}Y:{8,8,6,6,6,6,6,6,8,8}

        X:{10,102,2,9,10,2,2,10,10}  Y:{7,8,6,6,6,6,6,6,8,8}

        计算两组向量的距离,得出相似度。

        这种算法将图片的特征缩小化了,比如二者第一列的统计结果都为9,我们无法知道缺的那个1的具体位置。但是将二维的匹配简化到向量距离的计算,这样更加利于计算机处理。

 

四、分块对比

       上文中说到投影对比,它的缺陷是让图片的特征丢失过多。为了减少特征的丢失,我们可以将图片切割成几块,在分别对每一块进行匹配计算相似度,得到相似度向量,再计算向量距离,得到相似度。

        分块对比不是一个具体算法,而是一种优化思路,让一张大图片的匹配变成许多小图片的匹配,这样可以使结果更为精确。

 

向量距离的计算

       上文一直在提向量间距离的计算,可见向量间的距离是至关重要的。目前常用的算法是欧几里德距离算法。

 

        我们可以建立标准字库(为每个字生成一张图片),将欲识别图片用以上算法到字库中进行分别匹配,得到多个相似度。我们让每个相似度乘以一个权重再先加,得出总相似度,总相似度最高的就是识别结果。这里涉及到一个问题,就是权重的确定,每个算法的平均准确度是不一样的,我们让准确度高的权重大一些。但是又怎么来确定权重的具体数值呢?请阅读下篇:Logistic回归的应用,通过该模型,我们会得到一个不错的结果。

 

 在上文中提到图片相似度比对算法,得出了图片的相似度数据。接下来最重要的是通过相似度数据来得出图片是否相似,也就是对于多个数据进行计算得到相似与不相似两个结果。这就要使用分类回归——logistic回归了。由于本人对于机器学习造诣不足,本文不过多的涉及回归模型的原理讲解,只对模型的java实现与模型在本项目中的应用进行阐述。

Logistic回归的直观认识

         线性回归模型想必大家都知道,它是对于数据的拟合,通过学习多组数据,得出一条可以很好拟合所有数据的方程(模型)。再将新数据带入这个方程(模型),就可以计算得出一个数值,即预测结果。当然这个方程是多元的,因为影响结果的因素不止一个,也不一定是一次的,因为变量之间的关系可能不是一维的。选择不同的方程,得出的预测准确度是不尽一样的。

(线性回归:一元一次方程得出的是一条直线)

         Logistic分类回归可以理解为就是对线性回归得出结果的进一步计算,将结果限定在0和1之间,从而表示相似与不相似。

(logistic函数:将结果限定在0与1之间)

         当然整个重点就是在于怎么拟合训练数据,常用的算法就是梯度下降。通过梯度下降可以让我们的方程逐步的向最小误差靠近,这里逐步的步长是可以改变的,也就是学习率。学习率的选择是比较重要的,会影响整个模型的学习速度与正确率。

(梯度下降:算法逐步靠近局部最优值)

         总的来说,通过logistic模型,可以对我们输入的相似度数据计算出一个0到1的相似值,通过该值就可以确定图片是否相似,从而识别文字。

带入数据进行训练

         训练的数据是两张图片的对比相似度,如果图片是同一个字,我们将结果标记为1,否则标记为0。下面是一些数据,x为多个相似度算法得出的结果,y为结果标记,为了方便观察,对数据进行了一些处理。

         可以看到x0一直为1,这是因为在方程中第一项是常数项,即x的0次方为1,也可以理解为偏置。还有6个对比算法得出相似度数据,可以看到正样本中的相似度比负样本中的高,说明我们的对比算法是有一定说服力的。但是每种算法的准确率不尽一样比如x3的正负样本都得出了比较高的相似度,说明准确率不高。因为x3是基于重心的对比,方块字的重心都差不多,所以没什么说服力。将这些数据带进模型进行训练,可以得出如下结果。

说明: C:\Users\Administrator.PC-20160823BFLX\Desktop\re1.png

         可以看到经过5222次的迭代,结果和我们预测的差不多,x3和x6与结果呈负相关。整个代价0.074,即准确率92.6%,已经不错了,毕竟学习数据很少。

使用训练好的模型

         上文我们已经将模型训练好了,现在只需要将相似度比对数据带入模型即可得出预测结果。结果是一个0~1之间的数据,我们将待预测图片与字库图片进行一一比对后带入模型,取结果最大的为识别结果即可。

完整代码请访问我的gihubhttps://github.com/printlin/tmOcr

  1. 模型的java实现
  2. package util;
  3. import java.util.List;
  4. import java.util.Map;
  5. /**
  6.  * @author Administrator
  7.  * @Description 分类回归模型,传入训练集,学习得到sts,即可使用sts对输入的x计算y
  8.  * @date 2018716日 下午2:33:31
  9.  */
  10. public class LogisticModel {
  11.          private double[] sts=null;//参数Θ,通过学习得到
  12.          private double a=0.1;//学习速率
  13.          private List<Map<String,Object>> list=null;
  14.          /**
  15.           * @param list 训练集 Map中x为 double[]代表变量数组;y为double代表结果
  16.           */
  17.          public LogisticModel(List<Map<String,Object>> list){
  18.                    this.list=list;
  19.                    Map<String,Object> map=list.get(0);//空指针异常,未判断
  20.                    double[] x=(double[])map.get("x");//空指针异常,未判断
  21.                    sts=new double[x.length];
  22.          }
  23.          public LogisticModel(){
  24.          }
  25.          /**
  26.           * @author Administrator
  27.           * @Description 函数模型 X1*Θ1+...+Xn*Θn=y。输入x计算y
  28.           * @param xs x变量
  29.           * @return 计算结果
  30.           * @date 2018716日 下午2:25:10
  31.           */
  32.          public double function(double[] xs){
  33.                    double re=0f;
  34.                    for(int i=0;i<xs.length;i++){//X1*Θ1+...+Xn*Θn=y 全是一维,对于本次学习足以
  35.                             re+=xs[i]*sts[i];
  36.                    }
  37.                    return 1/(Math.pow(Math.E, -re)+1);//logistic函数,将结果限定在0~1
  38.          }
  39.          /**
  40.           * @author Administrator
  41.           * @Description 使用梯度下降算法进行函数参数更新(学习)
  42.           * @date 2018716日 下午2:28:09
  43.           */
  44.          private void update(){
  45.                    double[] stss=new double[sts.length];//新的模型参数,此处单独用数组来装而不是直接对该参数赋值,是为了不影响下一个参数的学习,保证每个参数对应的都是同一个函数
  46.                    int len=list.size();
  47.                    for(int i=0;i<stss.length;i++){//遍历每一个参数
  48.                             double sum=0f;
  49.                             for(Map<String,Object> map:list){
  50.                                      double[] xs=(double[])map.get("x");
  51.                                      double y=(double)map.get("y");
  52.                                      sum+=(function(xs)-y)*xs[i];
  53.                             }
  54.                             //System.out.println("js---:"+a*(1.0f/len)*sum);
  55.                             stss[i]=sts[i]-a*(1.0f/len)*sum;//更新该参数
  56.                    }
  57.                    sts=stss;//统一更新参数
  58.          }
  59.          /**
  60.           * @author Administrator
  61.           * @Description 代价函数,在训练集上计算误差
  62.           * @return 误差损失
  63.           * @date 2018716日 下午2:29:36
  64.           */
  65.          private double dj(){
  66.                    double sum=0f;
  67.                    for(Map<String,Object> map:list){
  68.                             double[] xs=(double[])map.get("x");
  69.                             double y=(double)map.get("y");
  70.                             sum+=y*Math.log(function(xs))+(1-y)*Math.log(1-function(xs));
  71.                    }
  72.                    return -(1.0f/list.size())*sum;
  73.          }
  74.          /**
  75.           * @author Administrator
  76.           * @Description 开始学习
  77.           * @date 2018716日 下午2:30:51
  78.           */
  79.          public void go(){
  80.                    int sum=0;//参数迭代次数
  81.                    int count=0;
  82.                    while(true){
  83.                             double oldDj=dj();//迭代前损失
  84.                             sum++;
  85.                             if(sum>=10000){//迭代次数不超过10000
  86.                                      break;
  87.                             }
  88.                             update();//迭代
  89.                             double newDj=dj();//迭代后损失
  90.                             if(Math.abs(newDj-oldDj)<0.00001){//两次损失差
  91.                                      count++;
  92.                                      if(count>10){//如果损失差小于0.00001连续10次,则认为已经拟合
  93.                                                break;
  94.                                      }
  95.                             }else{count=0;}
  96.                    }
  97.                    for(int j=0;j<sts.length;j++){
  98.                             System.out.print("st"+j+":"+sts[j]+"  ");//输出学习到的所有参数
  99.                    }
  100.                    System.out.println("\ndj:"+dj()+"   sum:"+sum);//输出误差已经总学习次数
  101.          }
  102.          public double[] getSts() {
  103.                    return sts;
  104.          }
  105.          public void setSts(double[] sts) {
  106.                    this.sts = sts;
  107.          }
  108.          public List<Map<String, Object>> getList() {
  109.                    return list;
  110.          }
  111.          public void setList(List<Map<String, Object>> list) {
  112.                    this.list = list;
  113.          }
  114. }

 

 

训练模型的代码

  1. package test;
  2. import java.io.File;
  3. import java.io.IOException;
  4. import java.util.ArrayList;
  5. import java.util.HashMap;
  6. import java.util.List;
  7. import java.util.Map;
  8. import imgdo.ImgData;
  9. import util.ImgUtil;
  10. import util.LogisticModel;
  11. /**
  12.  * @author Administrator
  13.  * @Description
  14.  * @date 201883日 上午11:41:54
  15.  */
  16. public class LogisticTest {
  17.          private String trueFolder="F:\\textImg\\trueFolder";
  18.          private String falseFolder="F:\\textImg\\falseFolder";
  19.          public static void main(String[] args) throws IOException {
  20.                    new LogisticTest().learn();
  21.          }
  22.          /*
  23.           * 下面这个方法只对一个字进行了学习。将同样的字作为正样本,其他字为负样本,进行分类学习
  24.           * 如果衍生到所有字:
  25.           * 将所有字放在一起,文件名为该字。学习时判断文件名是否一致,一致则结果设置为1.其他为0
  26.           * 当然集合中一致的字是相对少的,这样会导致负样本过多,可以随机取固定比例的负样本进行学习。
  27.           * */
  28.          public void learn() throws IOException{
  29.                    ImgUtil iu=new ImgUtil();
  30.                    File trueFile=new File(trueFolder);
  31.                    File[] trueFiles=trueFile.listFiles();
  32.                    File falseFile=new File(falseFolder);
  33.                    File[] falseFiles=falseFile.listFiles();
  34.                    List<Map<String,Object>> list=new ArrayList<Map<String,Object>>();
  35.                    for(int i=0,len=trueFiles.length;i<len;i++){//将正样本进行两两比较
  36.                             for(int j=i+1;j<len;j++){
  37.                                      ImgData data=iu.formatLibImg(trueFiles[i]),img=iu.formatLibImg(trueFiles[j]);//格式化图片
  38.                                      double[] re=iu.allMatch(data, img);//比对图片,得出各个比对算法的计算结果
  39.                                      Map<String,Object> map=new HashMap<String,Object>();
  40.                                      System.out.println("x=["+re[0]+"\t"+re[1]+"\t"+re[2]+"\t"+re[3]+"\t"+re[4]+"\t"+re[5]+"\t"+re[6]+"]\ty=1");
  41.                                      map.put("x", re);
  42.                                      map.put("y",1.0);//结果为相似
  43.                                      list.add(map);
  44.                             }
  45.                    }
  46.                    for(int i=0,len=trueFiles.length;i<len;i++){//将正样本与每一个负样本进行比较
  47.                             for(int j=0,jLen=falseFiles.length;j<jLen;j++){
  48.                                      ImgData data=iu.formatLibImg(trueFiles[i]),img=iu.formatLibImg(falseFiles[j]);//格式化图片
  49.                                      double[] re=iu.allMatch(data, img);//比对图片,得出各个比对算法的计算结果
  50.                                      Map<String,Object> map=new HashMap<String,Object>();
  51.                                      System.out.println("x=["+re[0]+"\t"+re[1]+"\t"+re[2]+"\t"+re[3]+"\t"+re[4]+"\t"+re[5]+"\t"+re[6]+"]\ty=0");
  52.                                      map.put("x", re);
  53.                                      map.put("y",0.0);//结果为不相似
  54.                                      list.add(map);
  55.                             }
  56.                    }
  57.                    LogisticModel lm=new LogisticModel(list);//带入模型
  58.                    lm.go();//开始学习
  59.          }
  60. }

 ​​​​​​​

文件夹中的内容


以上就是我的拙见,非常感谢您能看到这里,有什么问题可以评论指正哦。

参考文献:

https://blog.csdn.net/Print_lin/article/details/81052497

https://github.com/printlin/tmOcr

https://blog.csdn.net/Print_lin/article/details/81389392

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/950948
推荐阅读
相关标签
  

闽ICP备14008679号