zoukankan      html  css  js  c++  java
  • EasyPR源码剖析(9):字符识别

    在上一篇文章的介绍中,我们已经通过相应的字符分割方法,将车牌区域进行分割,得到7个分割字符图块,接下来要做的就是将字符图块放入训练好的神经网络模型,通过模型来预测每个图块所表示的具体字符。神经网络的介绍和训练过程我们将在下一节中具体介绍,本节主要介绍字符特征的提取,和如何通过训练好的神经网络模型来进行字符的识别。

    字符识别主要是通过 类CharsIdentify 来进行,对于中文字符和非中文字符,分别采取了不同的策略,训练得到的ANN模型也不一样,中文字符的识别主要使用 identifyChinese 来处理,非中文字符的识别主要采用 identify 来处理。另外,类CharsIdentify采用了单例模式,具体的初始化代码和构造函数如下:

     1   CharsIdentify* CharsIdentify::instance_ = nullptr;
     2 
     3   CharsIdentify* CharsIdentify::instance() {
     4     if (!instance_) {
     5       instance_ = new CharsIdentify;
     6     }
     7     return instance_;
     8   }
     9 
    10   CharsIdentify::CharsIdentify() {
    11     ann_ = ml::ANN_MLP::load<ml::ANN_MLP>(kDefaultAnnPath);
    12     annChinese_ = ml::ANN_MLP::load<ml::ANN_MLP>(kChineseAnnPath);
    13     kv_ = std::shared_ptr<Kv>(new Kv);
    14     kv_->load("etc/province_mapping");
    15   }
    16 
    17   void CharsIdentify::LoadModel(std::string path) {
    18     if (path != std::string(kDefaultAnnPath)) {
    19 
    20       if (!ann_->empty())
    21         ann_->clear();
    22 
    23       ann_ = ml::ANN_MLP::load<ml::ANN_MLP>(path);
    24     }
    25   }
    26 
    27   void CharsIdentify::LoadChineseModel(std::string path) {
    28     if (path != std::string(kChineseAnnPath)) {
    29 
    30       if (!annChinese_->empty())
    31         annChinese_->clear();
    32 
    33       annChinese_ = ml::ANN_MLP::load<ml::ANN_MLP>(path);
    34     }
    35   }
    View Code

    这边单例模式只考虑了单线程情况,对于多线程的话,需要加入双重锁定。此处处理中文字符和非中文字符,分别加载了不同的ANN模型文件,ANN模型通过opencv 中机器学习中自带的神经网络模型 ml::ANN_MLP 来实现。

    字符特征获取

    通过神经网络对字符图块进行判别,首先需要获取字符图块的特征,字符特征的获取,主要通过 charFeatures 函数来实现。具体的函数代码如下所示:

     1 Mat charFeatures(Mat in, int sizeData) {
     2   const int VERTICAL = 0;
     3   const int HORIZONTAL = 1;
     4 
     5   // cut the cetner, will afect 5% perices.
     6   Rect _rect = GetCenterRect(in);
     7   Mat tmpIn = CutTheRect(in, _rect);
     8   //Mat tmpIn = in.clone();
     9 
    10   // Low data feature
    11   Mat lowData;
    12   resize(tmpIn, lowData, Size(sizeData, sizeData));
    13 
    14   // Histogram features
    15   Mat vhist = ProjectedHistogram(lowData, VERTICAL);
    16   Mat hhist = ProjectedHistogram(lowData, HORIZONTAL);
    17 
    18   // Last 10 is the number of moments components
    19   int numCols = vhist.cols + hhist.cols + lowData.cols * lowData.cols;
    20 
    21   Mat out = Mat::zeros(1, numCols, CV_32F);
    22   // Asign values to
    23 
    24   int j = 0;
    25   for (int i = 0; i < vhist.cols; i++) {
    26     out.at<float>(j) = vhist.at<float>(i);
    27     j++;
    28   }
    29   for (int i = 0; i < hhist.cols; i++) {
    30     out.at<float>(j) = hhist.at<float>(i);
    31     j++;
    32   }
    33   for (int x = 0; x < lowData.cols; x++) {
    34     for (int y = 0; y < lowData.rows; y++) {
    35       out.at<float>(j) += (float)lowData.at <unsigned char>(x, y);
    36       j++;
    37     }
    38   }
    39 
    40   //std::cout << out << std::endl;
    41 
    42   return out;
    43 }
    View Code

    对于中文字符和英文字符,默认的图块大小是不一样的,中文字符默认是 20*20,非中文默认是10*10。

    • GetCenterRect 函数主要用于获取字符的边框,分别查找从四个角落查找字符的位置;
    • CutTheRect 函数裁剪原图,即将字符移动到图像的中间位置,通过这一步的操作,可将字符识别的准确率提高5%左右;
    • ProjectedHistogram 函数用于获取归一化序列,归一化到0-1区间范围内;

    GetCenterRect 函数具体代码如下:

     1 Rect GetCenterRect(Mat &in) {
     2   Rect _rect;
     3 
     4   int top = 0;
     5   int bottom = in.rows - 1;
     6 
     7   // find the center rect
     8 
     9   for (int i = 0; i < in.rows; ++i) {
    10     bool bFind = false;
    11     for (int j = 0; j < in.cols; ++j) {
    12       if (in.data[i * in.step[0] + j] > 20) {
    13         top = i;
    14         bFind = true;
    15         break;
    16       }
    17     }
    18     if (bFind) {
    19       break;
    20     }
    21 
    22   }
    23   for (int i = in.rows - 1;
    24   i >= 0;
    25   --i) {
    26     bool bFind = false;
    27     for (int j = 0; j < in.cols; ++j) {
    28       if (in.data[i * in.step[0] + j] > 20) {
    29         bottom = i;
    30         bFind = true;
    31         break;
    32       }
    33     }
    34     if (bFind) {
    35       break;
    36     }
    37 
    38   }
    39 
    40 
    41   int left = 0;
    42   int right = in.cols - 1;
    43   for (int j = 0; j < in.cols; ++j) {
    44     bool bFind = false;
    45     for (int i = 0; i < in.rows; ++i) {
    46       if (in.data[i * in.step[0] + j] > 20) {
    47         left = j;
    48         bFind = true;
    49         break;
    50       }
    51     }
    52     if (bFind) {
    53       break;
    54     }
    55 
    56   }
    57   for (int j = in.cols - 1;
    58   j >= 0;
    59   --j) {
    60     bool bFind = false;
    61     for (int i = 0; i < in.rows; ++i) {
    62       if (in.data[i * in.step[0] + j] > 20) {
    63         right = j;
    64         bFind = true;
    65 
    66         break;
    67       }
    68     }
    69     if (bFind) {
    70       break;
    71     }
    72   }
    73 
    74   _rect.x = left;
    75   _rect.y = top;
    76   _rect.width = right - left + 1;
    77   _rect.height = bottom - top + 1;
    78 
    79   return _rect;
    80 }
    View Code

    CutTheRect 函数具体代码如下:

     1 Mat CutTheRect(Mat &in, Rect &rect) {
     2   int size = in.cols;  // (rect.width>rect.height)?rect.rect.height;
     3   Mat dstMat(size, size, CV_8UC1);
     4   dstMat.setTo(Scalar(0, 0, 0));
     5 
     6   int x = (int) floor((float) (size - rect.width) / 2.0f);
     7   int y = (int) floor((float) (size - rect.height) / 2.0f);
     8 
     9   for (int i = 0; i < rect.height; ++i) {
    10 
    11     for (int j = 0; j < rect.width; ++j) {
    12       dstMat.data[dstMat.step[0] * (i + y) + j + x] =
    13           in.data[in.step[0] * (i + rect.y) + j + rect.x];
    14     }
    15   }
    16 
    17   //
    18   return dstMat;
    19 }
    View Code

    ProjectedHistogram 函数代码如下:

     1 float countOfBigValue(Mat &mat, int iValue) {
     2   float iCount = 0.0;
     3   if (mat.rows > 1) {
     4     for (int i = 0; i < mat.rows; ++i) {
     5       if (mat.data[i * mat.step[0]] > iValue) {
     6         iCount += 1.0;
     7       }
     8     }
     9     return iCount;
    10 
    11   } else {
    12     for (int i = 0; i < mat.cols; ++i) {
    13       if (mat.data[i] > iValue) {
    14         iCount += 1.0;
    15       }
    16     }
    17 
    18     return iCount;
    19   }
    20 }
    21 
    22 Mat ProjectedHistogram(Mat img, int t) {
    23   int sz = (t) ? img.rows : img.cols;
    24   Mat mhist = Mat::zeros(1, sz, CV_32F);
    25 
    26   for (int j = 0; j < sz; j++) {
    27     Mat data = (t) ? img.row(j) : img.col(j);
    28 
    29     mhist.at<float>(j) = countOfBigValue(data, 20);
    30   }
    31 
    32   // Normalize histogram
    33   double min, max;
    34   minMaxLoc(mhist, &min, &max);
    35 
    36   if (max > 0)
    37     mhist.convertTo(mhist, -1, 1.0f / max, 0);   //归一化 0-1
    38 
    39   return mhist;
    40 }
    View Code

     通过上述代码可知,非中文字符和中文字符获得的字符特征个数是不同的,非中文字符features个数为 10+10+10*10=120,中文字符features个数为  20+20+20*20=440。

    字符识别

    通过上述函数获取字符特征之后,可以通过神经网络模型对车牌字符进行识别,具体的识别函数如下所示:

     1   int CharsIdentify::classify(cv::Mat f, float& maxVal, bool isChinses){
     2     int result = -1;
     3 
     4     cv::Mat output(1, kCharsTotalNumber, CV_32FC1);
     5     ann_->predict(f, output);
     6 
     7     maxVal = -2.f;
     8     if (!isChinses) {
     9       result = 0;
    10       for (int j = 0; j < kCharactersNumber; j++) {
    11         float val = output.at<float>(j);
    12         // std::cout << "j:" << j << "val:" << val << std::endl;
    13         if (val > maxVal) {
    14           maxVal = val;
    15           result = j;
    16         }
    17       }
    18     }
    19     else {
    20       result = kCharactersNumber;
    21       for (int j = kCharactersNumber; j < kCharsTotalNumber; j++) {
    22         float val = output.at<float>(j);
    23         //std::cout << "j:" << j << "val:" << val << std::endl;
    24         if (val > maxVal) {
    25           maxVal = val;
    26           result = j;
    27         }
    28       }
    29     }
    30     //std::cout << "maxVal:" << maxVal << std::endl;
    31     return result;
    32   }
    View Code

    ann_为之前加载得到的神经网路模型,直接调用其 predict() 函数,即可得到输出矩阵 output,输出矩阵中最大的值即为识别的车牌字符,其中,数值分别为0-64的65个数字,对应的值如下所示:

    static const char *kChars[] = {
      "0", "1", "2",
      "3", "4", "5",
      "6", "7", "8",
      "9",
      /*  10  */
      "A", "B", "C",
      "D", "E", "F",
      "G", "H", /* {"I", "I"} */
      "J", "K", "L",
      "M", "N", /* {"O", "O"} */
      "P", "Q", "R",
      "S", "T", "U",
      "V", "W", "X",
      "Y", "Z",
      /*  24  */
      "zh_cuan" , "zh_e"    , "zh_gan"  ,
      "zh_gan1" , "zh_gui"  , "zh_gui1" ,
      "zh_hei"  , "zh_hu"   , "zh_ji"   ,
      "zh_jin"  , "zh_jing" , "zh_jl"   ,
      "zh_liao" , "zh_lu"   , "zh_meng" ,
      "zh_min"  , "zh_ning" , "zh_qing" ,
      "zh_qiong", "zh_shan" , "zh_su"   ,
      "zh_sx"   , "zh_wan"  , "zh_xiang",
      "zh_xin"  , "zh_yu"   , "zh_yu1"  ,
      "zh_yue"  , "zh_yun"  , "zh_zang" ,
      "zh_zhe"
      /*  31  */
    };

    其中26个英文字母中,因为I 和 O容易和数字的 1和0 混淆,因此被去除了,后面31个中文字符分别对应中国的31个行政区域(港澳台暂不考虑)。将识别的各个字符整体输出,就得到了最终的结果。

  • 相关阅读:
    牛客练习赛27 水图(思维+暴搜)
    差分
    矩阵化一维
    倍增法 求公共祖先
    vector 牛逼 +lower_bound+ upper_bound
    cmp和sort
    字符串的比较
    s 贪心
    太鼓达人 (有位运算的作用,但可能理解错了哈哈)
    kao shi di er ti(还没有订正)
  • 原文地址:https://www.cnblogs.com/freedomker/p/7298369.html
Copyright © 2011-2022 走看看