# Conflicts: # src/src/core/plate_judge.cpppull/14/head
						commit
						0d03268573
					
				
											
												Binary file not shown.
											
										
									
								@ -0,0 +1,454 @@
 | 
				
			||||
#include "easypr/core/chars_identify.h"
 | 
				
			||||
#include "easypr/core/character.hpp"
 | 
				
			||||
#include "easypr/core/core_func.h"
 | 
				
			||||
#include "easypr/core/feature.h"
 | 
				
			||||
#include "easypr/core/params.h"
 | 
				
			||||
#include "easypr/config.h"
 | 
				
			||||
 | 
				
			||||
using namespace cv;
 | 
				
			||||
 | 
				
			||||
namespace easypr {
 | 
				
			||||
 | 
				
			||||
CharsIdentify* CharsIdentify::instance_ = nullptr;
 | 
				
			||||
 | 
				
			||||
CharsIdentify* CharsIdentify::instance() {
 | 
				
			||||
  if (!instance_) {
 | 
				
			||||
    instance_ = new CharsIdentify;
 | 
				
			||||
  }
 | 
				
			||||
  return instance_;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
CharsIdentify::CharsIdentify() {
 | 
				
			||||
  LOAD_ANN_MODEL(ann_, kDefaultAnnPath);
 | 
				
			||||
  LOAD_ANN_MODEL(annChinese_, kChineseAnnPath);
 | 
				
			||||
  LOAD_ANN_MODEL(annGray_, kGrayAnnPath);
 | 
				
			||||
 | 
				
			||||
  kv_ = std::shared_ptr<Kv>(new Kv);
 | 
				
			||||
  kv_->load(kChineseMappingPath);
 | 
				
			||||
 | 
				
			||||
  extractFeature = getGrayPlusProject;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void CharsIdentify::LoadModel(std::string path) {
 | 
				
			||||
  if (path != std::string(kDefaultAnnPath)) {
 | 
				
			||||
    if (!ann_->empty())
 | 
				
			||||
      ann_->clear();
 | 
				
			||||
    LOAD_ANN_MODEL(ann_, path);
 | 
				
			||||
  }
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void CharsIdentify::LoadChineseModel(std::string path) {
 | 
				
			||||
  if (path != std::string(kChineseAnnPath)) {
 | 
				
			||||
    if (!annChinese_->empty())
 | 
				
			||||
      annChinese_->clear();
 | 
				
			||||
    LOAD_ANN_MODEL(annChinese_, path);
 | 
				
			||||
  }
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void CharsIdentify::LoadGrayChANN(std::string path) {
 | 
				
			||||
  if (path != std::string(kGrayAnnPath)) {
 | 
				
			||||
    if (!annGray_->empty())
 | 
				
			||||
      annGray_->clear();
 | 
				
			||||
    LOAD_ANN_MODEL(annGray_, path);
 | 
				
			||||
  }
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void CharsIdentify::LoadChineseMapping(std::string path) {
 | 
				
			||||
  kv_->clear();
 | 
				
			||||
  kv_->load(path);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void CharsIdentify::classify(cv::Mat featureRows, std::vector<int>& out_maxIndexs,
 | 
				
			||||
                             std::vector<float>& out_maxVals, std::vector<bool> isChineseVec){
 | 
				
			||||
  int rowNum = featureRows.rows;
 | 
				
			||||
 | 
				
			||||
  cv::Mat output(rowNum, kCharsTotalNumber, CV_32FC1);
 | 
				
			||||
  ann_->predict(featureRows, output);
 | 
				
			||||
 | 
				
			||||
  for (int output_index = 0; output_index < rowNum; output_index++) {
 | 
				
			||||
    Mat output_row = output.row(output_index);
 | 
				
			||||
    int result = 0;
 | 
				
			||||
    float maxVal = -2.f;
 | 
				
			||||
    bool isChinses = isChineseVec[output_index];
 | 
				
			||||
    if (!isChinses) {
 | 
				
			||||
      result = 0;
 | 
				
			||||
      for (int j = 0; j < kCharactersNumber; j++) {
 | 
				
			||||
        float val = output_row.at<float>(j);
 | 
				
			||||
        // std::cout << "j:" << j << "val:" << val << std::endl;
 | 
				
			||||
        if (val > maxVal) {
 | 
				
			||||
          maxVal = val;
 | 
				
			||||
          result = j;
 | 
				
			||||
        }
 | 
				
			||||
      }
 | 
				
			||||
    }
 | 
				
			||||
    else {
 | 
				
			||||
      result = kCharactersNumber;
 | 
				
			||||
      for (int j = kCharactersNumber; j < kCharsTotalNumber; j++) {
 | 
				
			||||
        float val = output_row.at<float>(j);
 | 
				
			||||
        //std::cout << "j:" << j << "val:" << val << std::endl;
 | 
				
			||||
        if (val > maxVal) {
 | 
				
			||||
          maxVal = val;
 | 
				
			||||
          result = j;
 | 
				
			||||
        }
 | 
				
			||||
      }
 | 
				
			||||
    }
 | 
				
			||||
    out_maxIndexs[output_index] = result;
 | 
				
			||||
    out_maxVals[output_index] = maxVal;
 | 
				
			||||
  }
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
void CharsIdentify::classify(std::vector<CCharacter>& charVec){
 | 
				
			||||
  size_t charVecSize = charVec.size();
 | 
				
			||||
 | 
				
			||||
  if (charVecSize == 0)
 | 
				
			||||
    return;
 | 
				
			||||
 | 
				
			||||
  Mat featureRows;
 | 
				
			||||
  for (size_t index = 0; index < charVecSize; index++) {
 | 
				
			||||
    Mat charInput = charVec[index].getCharacterMat();
 | 
				
			||||
    Mat feature = charFeatures(charInput, kPredictSize);
 | 
				
			||||
    featureRows.push_back(feature);
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  cv::Mat output(charVecSize, kCharsTotalNumber, CV_32FC1);
 | 
				
			||||
  ann_->predict(featureRows, output);
 | 
				
			||||
 | 
				
			||||
  for (size_t output_index = 0; output_index < charVecSize; output_index++) {
 | 
				
			||||
    CCharacter& character = charVec[output_index];
 | 
				
			||||
    Mat output_row = output.row(output_index);
 | 
				
			||||
 | 
				
			||||
    int result = 0;
 | 
				
			||||
    float maxVal = -2.f;
 | 
				
			||||
    std::string label = "";
 | 
				
			||||
 | 
				
			||||
    bool isChinses = character.getIsChinese();
 | 
				
			||||
    if (!isChinses) {
 | 
				
			||||
      result = 0;
 | 
				
			||||
      for (int j = 0; j < kCharactersNumber; j++) {
 | 
				
			||||
        float val = output_row.at<float>(j);
 | 
				
			||||
        //std::cout << "j:" << j << "val:" << val << std::endl;
 | 
				
			||||
        if (val > maxVal) {
 | 
				
			||||
          maxVal = val;
 | 
				
			||||
          result = j;
 | 
				
			||||
        }
 | 
				
			||||
      }
 | 
				
			||||
      label = std::make_pair(kChars[result], kChars[result]).second;
 | 
				
			||||
    }
 | 
				
			||||
    else {
 | 
				
			||||
      result = kCharactersNumber;
 | 
				
			||||
      for (int j = kCharactersNumber; j < kCharsTotalNumber; j++) {
 | 
				
			||||
        float val = output_row.at<float>(j);
 | 
				
			||||
        //std::cout << "j:" << j << "val:" << val << std::endl;
 | 
				
			||||
        if (val > maxVal) {
 | 
				
			||||
          maxVal = val;
 | 
				
			||||
          result = j;
 | 
				
			||||
        }
 | 
				
			||||
      }
 | 
				
			||||
      const char* key = kChars[result];
 | 
				
			||||
      std::string s = key;
 | 
				
			||||
      std::string province = kv_->get(s);
 | 
				
			||||
      label = std::make_pair(s, province).second;
 | 
				
			||||
    }
 | 
				
			||||
    /*std::cout << "result:" << result << std::endl;
 | 
				
			||||
    std::cout << "maxVal:" << maxVal << std::endl;*/
 | 
				
			||||
    character.setCharacterScore(maxVal);
 | 
				
			||||
    character.setCharacterStr(label);
 | 
				
			||||
  }
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
void CharsIdentify::classifyChineseGray(std::vector<CCharacter>& charVec){
 | 
				
			||||
  size_t charVecSize = charVec.size();
 | 
				
			||||
  if (charVecSize == 0)
 | 
				
			||||
    return;
 | 
				
			||||
 | 
				
			||||
  Mat featureRows;
 | 
				
			||||
  for (size_t index = 0; index < charVecSize; index++) {
 | 
				
			||||
    Mat charInput = charVec[index].getCharacterMat();
 | 
				
			||||
    cv::Mat feature;
 | 
				
			||||
    extractFeature(charInput, feature);
 | 
				
			||||
    featureRows.push_back(feature);
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  cv::Mat output(charVecSize, kChineseNumber, CV_32FC1);
 | 
				
			||||
  annGray_->predict(featureRows, output);
 | 
				
			||||
 | 
				
			||||
  for (size_t output_index = 0; output_index < charVecSize; output_index++) {
 | 
				
			||||
    CCharacter& character = charVec[output_index];
 | 
				
			||||
    Mat output_row = output.row(output_index);
 | 
				
			||||
    bool isChinese = true;
 | 
				
			||||
 | 
				
			||||
    float maxVal = -2;
 | 
				
			||||
    int result = 0;
 | 
				
			||||
 | 
				
			||||
    for (int j = 0; j < kChineseNumber; j++) {
 | 
				
			||||
      float val = output_row.at<float>(j);
 | 
				
			||||
      //std::cout << "j:" << j << "val:" << val << std::endl;
 | 
				
			||||
      if (val > maxVal) {
 | 
				
			||||
        maxVal = val;
 | 
				
			||||
        result = j;
 | 
				
			||||
      }
 | 
				
			||||
    }
 | 
				
			||||
 | 
				
			||||
    // no match
 | 
				
			||||
    if (-1 == result) {
 | 
				
			||||
      result = 0;
 | 
				
			||||
      maxVal = 0;
 | 
				
			||||
      isChinese = false;
 | 
				
			||||
    }
 | 
				
			||||
 | 
				
			||||
    auto index = result + kCharsTotalNumber - kChineseNumber;
 | 
				
			||||
    const char* key = kChars[index];
 | 
				
			||||
    std::string s = key;
 | 
				
			||||
    std::string province = kv_->get(s);
 | 
				
			||||
 | 
				
			||||
    /*std::cout << "result:" << result << std::endl;
 | 
				
			||||
    std::cout << "maxVal:" << maxVal << std::endl;*/
 | 
				
			||||
 | 
				
			||||
    character.setCharacterScore(maxVal);
 | 
				
			||||
    character.setCharacterStr(province);
 | 
				
			||||
    character.setIsChinese(isChinese);
 | 
				
			||||
  }
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void CharsIdentify::classifyChinese(std::vector<CCharacter>& charVec){
 | 
				
			||||
  size_t charVecSize = charVec.size();
 | 
				
			||||
 | 
				
			||||
  if (charVecSize == 0)
 | 
				
			||||
    return;
 | 
				
			||||
 | 
				
			||||
  Mat featureRows;
 | 
				
			||||
  for (size_t index = 0; index < charVecSize; index++) {
 | 
				
			||||
    Mat charInput = charVec[index].getCharacterMat();
 | 
				
			||||
    Mat feature = charFeatures(charInput, kChineseSize);
 | 
				
			||||
    featureRows.push_back(feature);
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  cv::Mat output(charVecSize, kChineseNumber, CV_32FC1);
 | 
				
			||||
  annChinese_->predict(featureRows, output);
 | 
				
			||||
 | 
				
			||||
  for (size_t output_index = 0; output_index < charVecSize; output_index++) {
 | 
				
			||||
    CCharacter& character = charVec[output_index];
 | 
				
			||||
    Mat output_row = output.row(output_index);
 | 
				
			||||
    bool isChinese = true;
 | 
				
			||||
 | 
				
			||||
    float maxVal = -2;
 | 
				
			||||
    int result = 0;
 | 
				
			||||
 | 
				
			||||
    for (int j = 0; j < kChineseNumber; j++) {
 | 
				
			||||
      float val = output_row.at<float>(j);
 | 
				
			||||
      //std::cout << "j:" << j << "val:" << val << std::endl;
 | 
				
			||||
      if (val > maxVal) {
 | 
				
			||||
        maxVal = val;
 | 
				
			||||
        result = j;
 | 
				
			||||
      }
 | 
				
			||||
    }
 | 
				
			||||
 | 
				
			||||
    // no match
 | 
				
			||||
    if (-1 == result) {
 | 
				
			||||
      result = 0;
 | 
				
			||||
      maxVal = 0;
 | 
				
			||||
      isChinese = false;
 | 
				
			||||
    }
 | 
				
			||||
 | 
				
			||||
    auto index = result + kCharsTotalNumber - kChineseNumber;
 | 
				
			||||
    const char* key = kChars[index];
 | 
				
			||||
    std::string s = key;
 | 
				
			||||
    std::string province = kv_->get(s);
 | 
				
			||||
 | 
				
			||||
    /*std::cout << "result:" << result << std::endl;
 | 
				
			||||
    std::cout << "maxVal:" << maxVal << std::endl;*/
 | 
				
			||||
 | 
				
			||||
    character.setCharacterScore(maxVal);
 | 
				
			||||
    character.setCharacterStr(province);
 | 
				
			||||
    character.setIsChinese(isChinese);
 | 
				
			||||
  }
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
int CharsIdentify::classify(cv::Mat f, float& maxVal, bool isChinses, bool isAlphabet){
 | 
				
			||||
  int result = 0;
 | 
				
			||||
 | 
				
			||||
  cv::Mat output(1, kCharsTotalNumber, CV_32FC1);
 | 
				
			||||
  ann_->predict(f, output);
 | 
				
			||||
 | 
				
			||||
  maxVal = -2.f;
 | 
				
			||||
  if (!isChinses) {
 | 
				
			||||
    if (!isAlphabet) {
 | 
				
			||||
      result = 0;
 | 
				
			||||
      for (int j = 0; j < kCharactersNumber; j++) {
 | 
				
			||||
        float val = output.at<float>(j);
 | 
				
			||||
        // std::cout << "j:" << j << "val:" << val << std::endl;
 | 
				
			||||
        if (val > maxVal) {
 | 
				
			||||
          maxVal = val;
 | 
				
			||||
          result = j;
 | 
				
			||||
        }
 | 
				
			||||
      }
 | 
				
			||||
    }
 | 
				
			||||
    else {
 | 
				
			||||
      result = 0;
 | 
				
			||||
      // begin with 11th char, which is 'A'
 | 
				
			||||
      for (int j = 10; j < kCharactersNumber; j++) {
 | 
				
			||||
        float val = output.at<float>(j);
 | 
				
			||||
        // std::cout << "j:" << j << "val:" << val << std::endl;
 | 
				
			||||
        if (val > maxVal) {
 | 
				
			||||
          maxVal = val;
 | 
				
			||||
          result = j;
 | 
				
			||||
        }
 | 
				
			||||
      }
 | 
				
			||||
    }
 | 
				
			||||
  }
 | 
				
			||||
  else {
 | 
				
			||||
    result = kCharactersNumber;
 | 
				
			||||
    for (int j = kCharactersNumber; j < kCharsTotalNumber; j++) {
 | 
				
			||||
      float val = output.at<float>(j);
 | 
				
			||||
      //std::cout << "j:" << j << "val:" << val << std::endl;
 | 
				
			||||
      if (val > maxVal) {
 | 
				
			||||
        maxVal = val;
 | 
				
			||||
        result = j;
 | 
				
			||||
      }
 | 
				
			||||
    }
 | 
				
			||||
  }
 | 
				
			||||
  //std::cout << "maxVal:" << maxVal << std::endl;
 | 
				
			||||
  return result;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
bool CharsIdentify::isCharacter(cv::Mat input, std::string& label, float& maxVal, bool isChinese) {
 | 
				
			||||
  cv::Mat feature = charFeatures(input, kPredictSize);
 | 
				
			||||
  auto index = static_cast<int>(classify(feature, maxVal, isChinese));
 | 
				
			||||
 | 
				
			||||
  if (isChinese) {
 | 
				
			||||
    //std::cout << "maxVal:" << maxVal << std::endl;
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  float chineseMaxThresh = 0.2f;
 | 
				
			||||
 | 
				
			||||
  if (maxVal >= 0.9 || (isChinese && maxVal >= chineseMaxThresh)) {
 | 
				
			||||
    if (index < kCharactersNumber) {
 | 
				
			||||
      label = std::make_pair(kChars[index], kChars[index]).second;
 | 
				
			||||
    }
 | 
				
			||||
    else {
 | 
				
			||||
      const char* key = kChars[index];
 | 
				
			||||
      std::string s = key;
 | 
				
			||||
      std::string province = kv_->get(s);
 | 
				
			||||
      label = std::make_pair(s, province).second;
 | 
				
			||||
    }
 | 
				
			||||
    return true;
 | 
				
			||||
  }
 | 
				
			||||
  else
 | 
				
			||||
    return false;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
std::pair<std::string, std::string> CharsIdentify::identifyChinese(cv::Mat input, float& out, bool& isChinese) {
 | 
				
			||||
  cv::Mat feature = charFeatures(input, kChineseSize);
 | 
				
			||||
  float maxVal = -2;
 | 
				
			||||
  int result = 0;
 | 
				
			||||
 | 
				
			||||
  cv::Mat output(1, kChineseNumber, CV_32FC1);
 | 
				
			||||
  annChinese_->predict(feature, output);
 | 
				
			||||
 | 
				
			||||
  for (int j = 0; j < kChineseNumber; j++) {
 | 
				
			||||
    float val = output.at<float>(j);
 | 
				
			||||
    //std::cout << "j:" << j << "val:" << val << std::endl;
 | 
				
			||||
    if (val > maxVal) {
 | 
				
			||||
      maxVal = val;
 | 
				
			||||
      result = j;
 | 
				
			||||
    }
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  // no match
 | 
				
			||||
  if (-1 == result) {
 | 
				
			||||
    result = 0;
 | 
				
			||||
    maxVal = 0;
 | 
				
			||||
    isChinese = false;
 | 
				
			||||
  }
 | 
				
			||||
  else if (maxVal > 0.9){
 | 
				
			||||
    isChinese = true;
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  auto index = result + kCharsTotalNumber - kChineseNumber;
 | 
				
			||||
  const char* key = kChars[index];
 | 
				
			||||
  std::string s = key;
 | 
				
			||||
  std::string province = kv_->get(s);
 | 
				
			||||
  out = maxVal;
 | 
				
			||||
 | 
				
			||||
  return std::make_pair(s, province);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
std::pair<std::string, std::string> CharsIdentify::identifyChineseGray(cv::Mat input, float& out, bool& isChinese) {
 | 
				
			||||
  cv::Mat feature;
 | 
				
			||||
  extractFeature(input, feature);
 | 
				
			||||
  float maxVal = -2;
 | 
				
			||||
  int result = 0;
 | 
				
			||||
  cv::Mat output(1, kChineseNumber, CV_32FC1);
 | 
				
			||||
  annGray_->predict(feature, output);
 | 
				
			||||
 | 
				
			||||
  for (int j = 0; j < kChineseNumber; j++) {
 | 
				
			||||
    float val = output.at<float>(j);
 | 
				
			||||
    //std::cout << "j:" << j << "val:" << val << std::endl;
 | 
				
			||||
    if (val > maxVal) {
 | 
				
			||||
      maxVal = val;
 | 
				
			||||
      result = j;
 | 
				
			||||
    }
 | 
				
			||||
  }
 | 
				
			||||
  // no match
 | 
				
			||||
  if (-1 == result) {
 | 
				
			||||
    result = 0;
 | 
				
			||||
    maxVal = 0;
 | 
				
			||||
    isChinese = false;
 | 
				
			||||
  } else if (maxVal > 0.9){
 | 
				
			||||
    isChinese = true;
 | 
				
			||||
  }
 | 
				
			||||
  auto index = result + kCharsTotalNumber - kChineseNumber;
 | 
				
			||||
  const char* key = kChars[index];
 | 
				
			||||
  std::string s = key;
 | 
				
			||||
  std::string province = kv_->get(s);
 | 
				
			||||
  out = maxVal;
 | 
				
			||||
  return std::make_pair(s, province);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
std::pair<std::string, std::string> CharsIdentify::identify(cv::Mat input, bool isChinese, bool isAlphabet) {
 | 
				
			||||
  cv::Mat feature = charFeatures(input, kPredictSize);
 | 
				
			||||
  float maxVal = -2;
 | 
				
			||||
  auto index = static_cast<int>(classify(feature, maxVal, isChinese, isAlphabet));
 | 
				
			||||
  if (index < kCharactersNumber) {
 | 
				
			||||
    return std::make_pair(kChars[index], kChars[index]);
 | 
				
			||||
  }
 | 
				
			||||
  else {
 | 
				
			||||
    const char* key = kChars[index];
 | 
				
			||||
    std::string s = key;
 | 
				
			||||
    std::string province = kv_->get(s);
 | 
				
			||||
    return std::make_pair(s, province);
 | 
				
			||||
  }
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
int CharsIdentify::identify(std::vector<cv::Mat> inputs, std::vector<std::pair<std::string, std::string>>& outputs,
 | 
				
			||||
                            std::vector<bool> isChineseVec) {
 | 
				
			||||
  Mat featureRows;
 | 
				
			||||
  size_t input_size = inputs.size();
 | 
				
			||||
  for (size_t i = 0; i < input_size; i++) {
 | 
				
			||||
    Mat input = inputs[i];
 | 
				
			||||
    cv::Mat feature = charFeatures(input, kPredictSize);
 | 
				
			||||
    featureRows.push_back(feature);
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  std::vector<int> maxIndexs;
 | 
				
			||||
  std::vector<float> maxVals;
 | 
				
			||||
  classify(featureRows, maxIndexs, maxVals, isChineseVec);
 | 
				
			||||
 | 
				
			||||
  for (size_t row_index = 0; row_index < input_size; row_index++) {
 | 
				
			||||
    int index = maxIndexs[row_index];
 | 
				
			||||
    if (index < kCharactersNumber) {
 | 
				
			||||
      outputs[row_index] = std::make_pair(kChars[index], kChars[index]);
 | 
				
			||||
    }
 | 
				
			||||
    else {
 | 
				
			||||
      const char* key = kChars[index];
 | 
				
			||||
      std::string s = key;
 | 
				
			||||
      std::string province = kv_->get(s);
 | 
				
			||||
      outputs[row_index] = std::make_pair(s, province);
 | 
				
			||||
    }
 | 
				
			||||
  }
 | 
				
			||||
  return 0;
 | 
				
			||||
}
 | 
				
			||||
}
 | 
				
			||||
@ -0,0 +1,117 @@
 | 
				
			||||
#include "easypr/core/chars_recognise.h"
 | 
				
			||||
#include "easypr/core/character.hpp"
 | 
				
			||||
#include "easypr/util/util.h"
 | 
				
			||||
#include <ctime>
 | 
				
			||||
 | 
				
			||||
namespace easypr {
 | 
				
			||||
 | 
				
			||||
CCharsRecognise::CCharsRecognise() { m_charsSegment = new CCharsSegment(); }
 | 
				
			||||
 | 
				
			||||
CCharsRecognise::~CCharsRecognise() { SAFE_RELEASE(m_charsSegment); }
 | 
				
			||||
 | 
				
			||||
int CCharsRecognise::charsRecognise(Mat plate, std::string& plateLicense) {
 | 
				
			||||
  std::vector<Mat> matChars;
 | 
				
			||||
  int result = m_charsSegment->charsSegment(plate, matChars);
 | 
				
			||||
  if (result == 0) {
 | 
				
			||||
    int num = matChars.size();
 | 
				
			||||
    for (int j = 0; j < num; j++)
 | 
				
			||||
    {
 | 
				
			||||
      Mat charMat = matChars.at(j);
 | 
				
			||||
      bool isChinses = false;
 | 
				
			||||
      float maxVal = 0;
 | 
				
			||||
      if (j == 0) {
 | 
				
			||||
        bool judge = true;
 | 
				
			||||
        isChinses = true;
 | 
				
			||||
        auto character = CharsIdentify::instance()->identifyChinese(charMat, maxVal, judge);
 | 
				
			||||
        plateLicense.append(character.second);
 | 
				
			||||
      }
 | 
				
			||||
      else {
 | 
				
			||||
        isChinses = false;
 | 
				
			||||
        auto character = CharsIdentify::instance()->identify(charMat, isChinses);
 | 
				
			||||
        plateLicense.append(character.second);
 | 
				
			||||
      }
 | 
				
			||||
    }
 | 
				
			||||
 | 
				
			||||
  }
 | 
				
			||||
  if (plateLicense.size() < 7) {
 | 
				
			||||
    return -1;
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  return result;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
int CCharsRecognise::charsRecognise(CPlate& plate, std::string& plateLicense) {
 | 
				
			||||
  std::vector<Mat> matChars;
 | 
				
			||||
  std::vector<Mat> grayChars;
 | 
				
			||||
  Mat plateMat = plate.getPlateMat();
 | 
				
			||||
  if (0) writeTempImage(plateMat, "plateMat/plate");
 | 
				
			||||
  Color color;
 | 
				
			||||
  if (plate.getPlateLocateType() == CMSER) {
 | 
				
			||||
    color = plate.getPlateColor();
 | 
				
			||||
  }
 | 
				
			||||
  else {
 | 
				
			||||
    int w = plateMat.cols;
 | 
				
			||||
    int h = plateMat.rows;
 | 
				
			||||
    Mat tmpMat = plateMat(Rect_<double>(w * 0.1, h * 0.1, w * 0.8, h * 0.8));
 | 
				
			||||
    color = getPlateType(tmpMat, true);
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  int result = m_charsSegment->charsSegmentUsingOSTU(plateMat, matChars, grayChars, color);
 | 
				
			||||
 | 
				
			||||
  if (result == 0) {
 | 
				
			||||
    int num = matChars.size();
 | 
				
			||||
    for (int j = 0; j < num; j++)
 | 
				
			||||
    {
 | 
				
			||||
      Mat charMat = matChars.at(j);
 | 
				
			||||
      Mat grayChar = grayChars.at(j);
 | 
				
			||||
      if (color != Color::BLUE)
 | 
				
			||||
        grayChar = 255 - grayChar;
 | 
				
			||||
 | 
				
			||||
      bool isChinses = false;
 | 
				
			||||
      std::pair<std::string, std::string> character;
 | 
				
			||||
      float maxVal;
 | 
				
			||||
      if (0 == j) {
 | 
				
			||||
        isChinses = true;
 | 
				
			||||
        bool judge = true;
 | 
				
			||||
        character = CharsIdentify::instance()->identifyChineseGray(grayChar, maxVal, judge);
 | 
				
			||||
        plateLicense.append(character.second);
 | 
				
			||||
 | 
				
			||||
        // set plate chinese mat and str
 | 
				
			||||
        plate.setChineseMat(grayChar);
 | 
				
			||||
        plate.setChineseKey(character.first);
 | 
				
			||||
        if (0) writeTempImage(grayChar, "char_data/" + character.first + "/chars_");
 | 
				
			||||
      }
 | 
				
			||||
      else if (1 == j) {
 | 
				
			||||
        isChinses = false;
 | 
				
			||||
        bool isAbc = true;
 | 
				
			||||
        character = CharsIdentify::instance()->identify(charMat, isChinses, isAbc);
 | 
				
			||||
        plateLicense.append(character.second);
 | 
				
			||||
      }
 | 
				
			||||
      else {
 | 
				
			||||
        isChinses = false;
 | 
				
			||||
        SHOW_IMAGE(charMat, 0);
 | 
				
			||||
        character = CharsIdentify::instance()->identify(charMat, isChinses);
 | 
				
			||||
        plateLicense.append(character.second);
 | 
				
			||||
      }
 | 
				
			||||
 | 
				
			||||
      CCharacter charResult;
 | 
				
			||||
      charResult.setCharacterMat(charMat);
 | 
				
			||||
      charResult.setCharacterGrayMat(grayChar);
 | 
				
			||||
      if (isChinses)
 | 
				
			||||
        charResult.setCharacterStr(character.first);
 | 
				
			||||
      else
 | 
				
			||||
        charResult.setCharacterStr(character.second);
 | 
				
			||||
 | 
				
			||||
      plate.addReutCharacter(charResult);
 | 
				
			||||
    }
 | 
				
			||||
    if (plateLicense.size() < 7) {
 | 
				
			||||
      return -1;
 | 
				
			||||
    }
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  return result;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
}
 | 
				
			||||
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								@ -0,0 +1,466 @@
 | 
				
			||||
#include "easypr/core/feature.h"
 | 
				
			||||
#include "easypr/core/core_func.h"
 | 
				
			||||
#include "thirdparty/LBP/lbp.hpp"
 | 
				
			||||
 | 
				
			||||
namespace easypr {
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
Mat getHistogram(Mat in) {
 | 
				
			||||
  const int VERTICAL = 0;
 | 
				
			||||
  const int HORIZONTAL = 1;
 | 
				
			||||
 | 
				
			||||
  // Histogram features
 | 
				
			||||
  Mat vhist = ProjectedHistogram(in, VERTICAL);
 | 
				
			||||
  Mat hhist = ProjectedHistogram(in, HORIZONTAL);
 | 
				
			||||
 | 
				
			||||
  // Last 10 is the number of moments components
 | 
				
			||||
  int numCols = vhist.cols + hhist.cols;
 | 
				
			||||
 | 
				
			||||
  Mat out = Mat::zeros(1, numCols, CV_32F);
 | 
				
			||||
 | 
				
			||||
  int j = 0;
 | 
				
			||||
  for (int i = 0; i < vhist.cols; i++) {
 | 
				
			||||
    out.at<float>(j) = vhist.at<float>(i);
 | 
				
			||||
    j++;
 | 
				
			||||
  }
 | 
				
			||||
  for (int i = 0; i < hhist.cols; i++) {
 | 
				
			||||
    out.at<float>(j) = hhist.at<float>(i);
 | 
				
			||||
    j++;
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  return out;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void getHistogramFeatures(const Mat& image, Mat& features) {
 | 
				
			||||
  Mat grayImage;
 | 
				
			||||
  cvtColor(image, grayImage, CV_RGB2GRAY);
 | 
				
			||||
 | 
				
			||||
  //grayImage = histeq(grayImage);
 | 
				
			||||
 | 
				
			||||
  Mat img_threshold;
 | 
				
			||||
  threshold(grayImage, img_threshold, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
 | 
				
			||||
  //Mat img_threshold = grayImage.clone();
 | 
				
			||||
  //spatial_ostu(img_threshold, 8, 2, getPlateType(image, false));
 | 
				
			||||
 | 
				
			||||
  features = getHistogram(img_threshold);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
// compute color histom
 | 
				
			||||
void getColorFeatures(const Mat& src, Mat& features) {
 | 
				
			||||
  Mat src_hsv;
 | 
				
			||||
 | 
				
			||||
  //grayImage = histeq(grayImage);
 | 
				
			||||
  cvtColor(src, src_hsv, CV_BGR2HSV);
 | 
				
			||||
  int channels = src_hsv.channels();
 | 
				
			||||
  int nRows = src_hsv.rows;
 | 
				
			||||
 | 
				
			||||
  // consider multi channel image
 | 
				
			||||
  int nCols = src_hsv.cols * channels;
 | 
				
			||||
  if (src_hsv.isContinuous()) {
 | 
				
			||||
    nCols *= nRows;
 | 
				
			||||
    nRows = 1;
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  const int sz = 180;
 | 
				
			||||
  int h[sz] = { 0 };
 | 
				
			||||
 | 
				
			||||
  uchar* p;
 | 
				
			||||
  for (int i = 0; i < nRows; ++i) {
 | 
				
			||||
    p = src_hsv.ptr<uchar>(i);
 | 
				
			||||
    for (int j = 0; j < nCols; j += 3) {
 | 
				
			||||
      int H = int(p[j]);      // 0-180
 | 
				
			||||
      if (H > sz - 1) H = sz - 1;
 | 
				
			||||
      if (H < 0) H = 0;
 | 
				
			||||
      h[H]++;
 | 
				
			||||
    }
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  Mat mhist = Mat::zeros(1, sz, CV_32F);
 | 
				
			||||
  for (int j = 0; j < sz; j++) {
 | 
				
			||||
    mhist.at<float>(j) = (float)h[j];
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  // Normalize histogram
 | 
				
			||||
  double min, max;
 | 
				
			||||
  minMaxLoc(mhist, &min, &max);
 | 
				
			||||
 | 
				
			||||
  if (max > 0)
 | 
				
			||||
    mhist.convertTo(mhist, -1, 1.0f / max, 0);
 | 
				
			||||
 | 
				
			||||
  features = mhist;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
void getHistomPlusColoFeatures(const Mat& image, Mat& features) {
 | 
				
			||||
  // TODO
 | 
				
			||||
  Mat feature1, feature2;
 | 
				
			||||
  getHistogramFeatures(image, feature1);
 | 
				
			||||
  getColorFeatures(image, feature2);
 | 
				
			||||
  hconcat(feature1.reshape(1, 1), feature2.reshape(1, 1), features);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
void getSIFTFeatures(const Mat& image, Mat& features) {
 | 
				
			||||
  // TODO
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
//HOG Features
 | 
				
			||||
void getHOGFeatures(const Mat& image, Mat& features) {
 | 
				
			||||
  //HOG descripter
 | 
				
			||||
  HOGDescriptor hog(cvSize(128, 64), cvSize(16, 16), cvSize(8, 8), cvSize(8, 8), 3); //these parameters work well
 | 
				
			||||
	std::vector<float> descriptor;
 | 
				
			||||
 | 
				
			||||
  // resize input image to (128,64) for compute
 | 
				
			||||
	Size dsize = Size(128,64);
 | 
				
			||||
	Mat trainImg = Mat(dsize, CV_32S);
 | 
				
			||||
	resize(image, trainImg, dsize);
 | 
				
			||||
 | 
				
			||||
  // compute descripter
 | 
				
			||||
	hog.compute(trainImg, descriptor, Size(8, 8));
 | 
				
			||||
 | 
				
			||||
  // copy the result
 | 
				
			||||
	Mat mat_featrue(descriptor);
 | 
				
			||||
	mat_featrue.copyTo(features);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
void getHSVHistFeatures(const Mat& image, Mat& features) {
 | 
				
			||||
  // TODO
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
//! LBP feature
 | 
				
			||||
void getLBPFeatures(const Mat& image, Mat& features) {
 | 
				
			||||
 | 
				
			||||
  Mat grayImage;
 | 
				
			||||
  cvtColor(image, grayImage, CV_RGB2GRAY);
 | 
				
			||||
 | 
				
			||||
  Mat lbpimage;
 | 
				
			||||
  lbpimage = libfacerec::olbp(grayImage);
 | 
				
			||||
  Mat lbp_hist = libfacerec::spatial_histogram(lbpimage, 32, 4, 4);
 | 
				
			||||
 | 
				
			||||
  features = lbp_hist;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
Mat charFeatures(Mat in, int sizeData) {
 | 
				
			||||
  const int VERTICAL = 0;
 | 
				
			||||
  const int HORIZONTAL = 1;
 | 
				
			||||
 | 
				
			||||
  // cut the cetner, will afect 5% perices.
 | 
				
			||||
  Rect _rect = GetCenterRect(in);
 | 
				
			||||
  Mat tmpIn = CutTheRect(in, _rect);
 | 
				
			||||
  //Mat tmpIn = in.clone();
 | 
				
			||||
 | 
				
			||||
  // Low data feature
 | 
				
			||||
  Mat lowData;
 | 
				
			||||
  resize(tmpIn, lowData, Size(sizeData, sizeData));
 | 
				
			||||
 | 
				
			||||
  // Histogram features
 | 
				
			||||
  Mat vhist = ProjectedHistogram(lowData, VERTICAL);
 | 
				
			||||
  Mat hhist = ProjectedHistogram(lowData, HORIZONTAL);
 | 
				
			||||
 | 
				
			||||
  // Last 10 is the number of moments components
 | 
				
			||||
  int numCols = vhist.cols + hhist.cols + lowData.cols * lowData.cols;
 | 
				
			||||
 | 
				
			||||
  Mat out = Mat::zeros(1, numCols, CV_32F);
 | 
				
			||||
  // Asign values to
 | 
				
			||||
 | 
				
			||||
  int j = 0;
 | 
				
			||||
  for (int i = 0; i < vhist.cols; i++) {
 | 
				
			||||
    out.at<float>(j) = vhist.at<float>(i);
 | 
				
			||||
    j++;
 | 
				
			||||
  }
 | 
				
			||||
  for (int i = 0; i < hhist.cols; i++) {
 | 
				
			||||
    out.at<float>(j) = hhist.at<float>(i);
 | 
				
			||||
    j++;
 | 
				
			||||
  }
 | 
				
			||||
  for (int x = 0; x < lowData.cols; x++) {
 | 
				
			||||
    for (int y = 0; y < lowData.rows; y++) {
 | 
				
			||||
      out.at<float>(j) += (float)lowData.at <unsigned char>(x, y);
 | 
				
			||||
      j++;
 | 
				
			||||
    }
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  //std::cout << out << std::endl;
 | 
				
			||||
 | 
				
			||||
  return out;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
Mat charFeatures2(Mat in, int sizeData) {
 | 
				
			||||
  const int VERTICAL = 0;
 | 
				
			||||
  const int HORIZONTAL = 1;
 | 
				
			||||
 | 
				
			||||
  // cut the cetner, will afect 5% perices.
 | 
				
			||||
  Rect _rect = GetCenterRect(in);
 | 
				
			||||
  Mat tmpIn = CutTheRect(in, _rect);
 | 
				
			||||
  //Mat tmpIn = in.clone();
 | 
				
			||||
 | 
				
			||||
  // Low data feature
 | 
				
			||||
  Mat lowData;
 | 
				
			||||
  resize(tmpIn, lowData, Size(sizeData, sizeData));
 | 
				
			||||
 | 
				
			||||
  // Histogram features
 | 
				
			||||
  Mat vhist = ProjectedHistogram(lowData, VERTICAL);
 | 
				
			||||
  Mat hhist = ProjectedHistogram(lowData, HORIZONTAL);
 | 
				
			||||
 | 
				
			||||
  // Last 10 is the number of moments components
 | 
				
			||||
  int numCols = vhist.cols + hhist.cols + lowData.cols * lowData.cols;
 | 
				
			||||
 | 
				
			||||
  Mat out = Mat::zeros(1, numCols, CV_32F);
 | 
				
			||||
 | 
				
			||||
  int j = 0;
 | 
				
			||||
  for (int i = 0; i < vhist.cols; i++) {
 | 
				
			||||
    out.at<float>(j) = vhist.at<float>(i);
 | 
				
			||||
    j++;
 | 
				
			||||
  }
 | 
				
			||||
  for (int i = 0; i < hhist.cols; i++) {
 | 
				
			||||
    out.at<float>(j) = hhist.at<float>(i);
 | 
				
			||||
    j++;
 | 
				
			||||
  }
 | 
				
			||||
  for (int x = 0; x < lowData.cols; x++) {
 | 
				
			||||
    for (int y = 0; y < lowData.rows; y++) {
 | 
				
			||||
      out.at<float>(j) += (float)lowData.at <unsigned char>(x, y);
 | 
				
			||||
      j++;
 | 
				
			||||
    }
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  //std::cout << out << std::endl;
 | 
				
			||||
 | 
				
			||||
  return out;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
Mat charProjectFeatures(const Mat& in, int sizeData) {
 | 
				
			||||
  const int VERTICAL = 0;
 | 
				
			||||
  const int HORIZONTAL = 1;
 | 
				
			||||
 | 
				
			||||
  SHOW_IMAGE(in, 0);
 | 
				
			||||
  // cut the cetner, will afect 5% perices.
 | 
				
			||||
 | 
				
			||||
  Mat lowData;
 | 
				
			||||
  resize(in, lowData, Size(sizeData, sizeData));
 | 
				
			||||
 | 
				
			||||
  SHOW_IMAGE(lowData, 0);
 | 
				
			||||
  // Histogram features
 | 
				
			||||
  Mat vhist = ProjectedHistogram(lowData, VERTICAL);
 | 
				
			||||
  Mat hhist = ProjectedHistogram(lowData, HORIZONTAL);
 | 
				
			||||
 | 
				
			||||
  // Last 10 is the number of moments components
 | 
				
			||||
  int numCols = vhist.cols + hhist.cols;
 | 
				
			||||
 | 
				
			||||
  Mat out = Mat::zeros(1, numCols, CV_32F);
 | 
				
			||||
 | 
				
			||||
  int j = 0;
 | 
				
			||||
  for (int i = 0; i < vhist.cols; i++) {
 | 
				
			||||
    out.at<float>(j) = vhist.at<float>(i);
 | 
				
			||||
    j++;
 | 
				
			||||
  }
 | 
				
			||||
  for (int i = 0; i < hhist.cols; i++) {
 | 
				
			||||
    out.at<float>(j) = hhist.at<float>(i);
 | 
				
			||||
    j++;
 | 
				
			||||
  }
 | 
				
			||||
  //std::cout << out << std::endl;
 | 
				
			||||
 | 
				
			||||
  return out;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void getGrayCharFeatures(const Mat& grayChar, Mat& features) {
 | 
				
			||||
  // TODO: check channnels == 1
 | 
				
			||||
  SHOW_IMAGE(grayChar, 0);
 | 
				
			||||
  SHOW_IMAGE(255 - grayChar, 0);
 | 
				
			||||
 | 
				
			||||
  // resize to uniform size, like 20x32
 | 
				
			||||
  bool useResize = false;
 | 
				
			||||
  bool useConvert = true;
 | 
				
			||||
  bool useMean = true;
 | 
				
			||||
  bool useLBP = false;
 | 
				
			||||
 | 
				
			||||
  Mat char_mat;
 | 
				
			||||
  if (useResize) {
 | 
				
			||||
    char_mat.create(kGrayCharHeight, kGrayCharWidth, CV_8UC1);
 | 
				
			||||
    resize(grayChar, char_mat, char_mat.size(), 0, 0, INTER_LINEAR);
 | 
				
			||||
  } else {
 | 
				
			||||
    char_mat = grayChar;
 | 
				
			||||
  }
 | 
				
			||||
  SHOW_IMAGE(char_mat, 0);
 | 
				
			||||
 | 
				
			||||
  // convert to float
 | 
				
			||||
  Mat float_img;
 | 
				
			||||
  if (useConvert) {
 | 
				
			||||
    float scale = 1.f / 255;
 | 
				
			||||
    char_mat.convertTo(float_img, CV_32FC1, scale, 0);
 | 
				
			||||
  } else {
 | 
				
			||||
    float_img = char_mat;
 | 
				
			||||
  }
 | 
				
			||||
  SHOW_IMAGE(float_img, 0);
 | 
				
			||||
 | 
				
			||||
  // cut from mean, it can be optional
 | 
				
			||||
 | 
				
			||||
  Mat mean_img;
 | 
				
			||||
  if (useMean) {
 | 
				
			||||
    float_img -= mean(float_img);
 | 
				
			||||
    mean_img = float_img;
 | 
				
			||||
  } else {
 | 
				
			||||
    mean_img = float_img;
 | 
				
			||||
  }
 | 
				
			||||
  SHOW_IMAGE(mean_img, 0);
 | 
				
			||||
 | 
				
			||||
  // use lbp to get features, it can be changed to other
 | 
				
			||||
  Mat feautreImg;
 | 
				
			||||
  if (useLBP) {
 | 
				
			||||
    Mat lbpimage = libfacerec::olbp(char_mat);
 | 
				
			||||
    SHOW_IMAGE(lbpimage, 0);
 | 
				
			||||
    feautreImg = libfacerec::spatial_histogram(lbpimage, kCharLBPPatterns, kCharLBPGridX, kCharLBPGridY);
 | 
				
			||||
  } else {
 | 
				
			||||
    feautreImg = mean_img.reshape(1, 1);
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  // return back
 | 
				
			||||
  features = feautreImg;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
void getGrayPlusProject(const Mat& grayChar, Mat& features)
 | 
				
			||||
{
 | 
				
			||||
  // TODO: check channnels == 1
 | 
				
			||||
  SHOW_IMAGE(grayChar, 0);
 | 
				
			||||
  SHOW_IMAGE(255 - grayChar, 0);
 | 
				
			||||
 | 
				
			||||
  // resize to uniform size, like 20x32
 | 
				
			||||
  bool useResize = false;
 | 
				
			||||
  bool useConvert = true;
 | 
				
			||||
  bool useMean = true;
 | 
				
			||||
  bool useLBP = false;
 | 
				
			||||
 | 
				
			||||
  Mat char_mat;
 | 
				
			||||
  if (useResize) {
 | 
				
			||||
    char_mat.create(kGrayCharHeight, kGrayCharWidth, CV_8UC1);
 | 
				
			||||
    resize(grayChar, char_mat, char_mat.size(), 0, 0, INTER_LINEAR);
 | 
				
			||||
  }
 | 
				
			||||
  else {
 | 
				
			||||
    char_mat = grayChar;
 | 
				
			||||
  }
 | 
				
			||||
  SHOW_IMAGE(char_mat, 0);
 | 
				
			||||
 | 
				
			||||
  // convert to float
 | 
				
			||||
  Mat float_img;
 | 
				
			||||
  if (useConvert) {
 | 
				
			||||
    float scale = 1.f / 255;
 | 
				
			||||
    char_mat.convertTo(float_img, CV_32FC1, scale, 0);
 | 
				
			||||
  }
 | 
				
			||||
  else {
 | 
				
			||||
    float_img = char_mat;
 | 
				
			||||
  }
 | 
				
			||||
  SHOW_IMAGE(float_img, 0);
 | 
				
			||||
 | 
				
			||||
  // cut from mean, it can be optional
 | 
				
			||||
 | 
				
			||||
  Mat mean_img;
 | 
				
			||||
  if (useMean) {
 | 
				
			||||
    float_img -= mean(float_img);
 | 
				
			||||
    mean_img = float_img;
 | 
				
			||||
  }
 | 
				
			||||
  else {
 | 
				
			||||
    mean_img = float_img;
 | 
				
			||||
  }
 | 
				
			||||
  SHOW_IMAGE(mean_img, 0);
 | 
				
			||||
 | 
				
			||||
  // use lbp to get features, it can be changed to other
 | 
				
			||||
  Mat feautreImg;
 | 
				
			||||
  if (useLBP) {
 | 
				
			||||
    Mat lbpimage = libfacerec::olbp(char_mat);
 | 
				
			||||
    SHOW_IMAGE(lbpimage, 0);
 | 
				
			||||
    feautreImg = libfacerec::spatial_histogram(lbpimage, kCharLBPPatterns, kCharLBPGridX, kCharLBPGridY);
 | 
				
			||||
  }
 | 
				
			||||
  else {
 | 
				
			||||
    feautreImg = mean_img.reshape(1, 1);
 | 
				
			||||
  }
 | 
				
			||||
  SHOW_IMAGE(grayChar, 0);
 | 
				
			||||
  Mat binaryChar;
 | 
				
			||||
  threshold(grayChar, binaryChar, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
 | 
				
			||||
  SHOW_IMAGE(binaryChar, 0);
 | 
				
			||||
  Mat projectFeature = charProjectFeatures(binaryChar, 32);
 | 
				
			||||
 | 
				
			||||
  hconcat(feautreImg.reshape(1, 1), projectFeature.reshape(1, 1), features);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
void getGrayPlusLBP(const Mat& grayChar, Mat& features)
 | 
				
			||||
{
 | 
				
			||||
  // TODO: check channnels == 1
 | 
				
			||||
  SHOW_IMAGE(grayChar, 0);
 | 
				
			||||
  SHOW_IMAGE(255 - grayChar, 0);
 | 
				
			||||
 | 
				
			||||
  // resize to uniform size, like 20x32
 | 
				
			||||
  bool useResize = false;
 | 
				
			||||
  bool useConvert = true;
 | 
				
			||||
  bool useMean = true;
 | 
				
			||||
  bool useLBP = true;
 | 
				
			||||
 | 
				
			||||
  Mat char_mat;
 | 
				
			||||
  if (useResize) {
 | 
				
			||||
    char_mat.create(kGrayCharHeight, kGrayCharWidth, CV_8UC1);
 | 
				
			||||
    resize(grayChar, char_mat, char_mat.size(), 0, 0, INTER_LINEAR);
 | 
				
			||||
  }
 | 
				
			||||
  else {
 | 
				
			||||
    char_mat = grayChar;
 | 
				
			||||
  }
 | 
				
			||||
  SHOW_IMAGE(char_mat, 0);
 | 
				
			||||
 | 
				
			||||
  // convert to float
 | 
				
			||||
  Mat float_img;
 | 
				
			||||
  if (useConvert) {
 | 
				
			||||
    float scale = 1.f / 255;
 | 
				
			||||
    char_mat.convertTo(float_img, CV_32FC1, scale, 0);
 | 
				
			||||
  }
 | 
				
			||||
  else {
 | 
				
			||||
    float_img = char_mat;
 | 
				
			||||
  }
 | 
				
			||||
  SHOW_IMAGE(float_img, 0);
 | 
				
			||||
 | 
				
			||||
  // cut from mean, it can be optional
 | 
				
			||||
 | 
				
			||||
  Mat mean_img;
 | 
				
			||||
  if (useMean) {
 | 
				
			||||
    float_img -= mean(float_img);
 | 
				
			||||
    mean_img = float_img;
 | 
				
			||||
  }
 | 
				
			||||
  else {
 | 
				
			||||
    mean_img = float_img;
 | 
				
			||||
  }
 | 
				
			||||
  SHOW_IMAGE(mean_img, 0);
 | 
				
			||||
 | 
				
			||||
  // use lbp to get features, it can be changed to other
 | 
				
			||||
  Mat originImage = mean_img.clone();
 | 
				
			||||
  Mat lbpimage = libfacerec::olbp(mean_img);
 | 
				
			||||
  SHOW_IMAGE(lbpimage, 0);
 | 
				
			||||
  lbpimage = libfacerec::spatial_histogram(lbpimage, kCharLBPPatterns, kCharLBPGridX, kCharLBPGridY);
 | 
				
			||||
 | 
				
			||||
  // 32x20 + 16x16
 | 
				
			||||
  hconcat(mean_img.reshape(1, 1), lbpimage.reshape(1, 1), features);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void getLBPplusHistFeatures(const Mat& image, Mat& features) {
 | 
				
			||||
  Mat grayImage;
 | 
				
			||||
  cvtColor(image, grayImage, CV_RGB2GRAY);
 | 
				
			||||
 | 
				
			||||
  Mat lbpimage;
 | 
				
			||||
  lbpimage = libfacerec::olbp(grayImage);
 | 
				
			||||
  Mat lbp_hist = libfacerec::spatial_histogram(lbpimage, 64, 8, 4);
 | 
				
			||||
  //features = lbp_hist.reshape(1, 1);
 | 
				
			||||
 | 
				
			||||
  Mat greyImage;
 | 
				
			||||
  cvtColor(image, greyImage, CV_RGB2GRAY);
 | 
				
			||||
 | 
				
			||||
  //grayImage = histeq(grayImage);
 | 
				
			||||
  Mat img_threshold;
 | 
				
			||||
  threshold(greyImage, img_threshold, 0, 255,
 | 
				
			||||
    CV_THRESH_OTSU + CV_THRESH_BINARY);
 | 
				
			||||
  Mat histomFeatures = getHistogram(img_threshold);
 | 
				
			||||
 | 
				
			||||
  hconcat(lbp_hist.reshape(1, 1), histomFeatures.reshape(1, 1), features);
 | 
				
			||||
  //std::cout << features << std::endl;
 | 
				
			||||
  //features = histomFeatures;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
}
 | 
				
			||||
@ -0,0 +1,12 @@
 | 
				
			||||
#include "easypr/core/params.h"
 | 
				
			||||
 | 
				
			||||
namespace easypr {
 | 
				
			||||
  CParams* CParams::instance_ = nullptr;
 | 
				
			||||
 | 
				
			||||
  CParams* CParams::instance() {
 | 
				
			||||
    if (!instance_) {
 | 
				
			||||
      instance_ = new CParams;
 | 
				
			||||
    }
 | 
				
			||||
    return instance_;
 | 
				
			||||
  }
 | 
				
			||||
}/*! \namespace easypr*/
 | 
				
			||||
@ -0,0 +1,77 @@
 | 
				
			||||
#include "easypr/core/plate_detect.h"
 | 
				
			||||
#include "easypr/util/util.h"
 | 
				
			||||
#include "easypr/core/core_func.h"
 | 
				
			||||
#include "easypr/config.h"
 | 
				
			||||
 | 
				
			||||
namespace easypr {
 | 
				
			||||
 | 
				
			||||
  CPlateDetect::CPlateDetect() {
 | 
				
			||||
    m_plateLocate = new CPlateLocate();
 | 
				
			||||
    m_maxPlates = 3;
 | 
				
			||||
    m_type = 0;
 | 
				
			||||
    m_showDetect = false;
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  CPlateDetect::~CPlateDetect() { SAFE_RELEASE(m_plateLocate); }
 | 
				
			||||
 | 
				
			||||
  int CPlateDetect::plateDetect(Mat src, std::vector<CPlate> &resultVec, int type,
 | 
				
			||||
    bool showDetectArea, int img_index) {
 | 
				
			||||
    std::vector<CPlate> sobel_Plates;
 | 
				
			||||
    sobel_Plates.reserve(16);
 | 
				
			||||
    std::vector<CPlate> color_Plates;
 | 
				
			||||
    color_Plates.reserve(16);
 | 
				
			||||
    std::vector<CPlate> mser_Plates;
 | 
				
			||||
    mser_Plates.reserve(16);
 | 
				
			||||
    std::vector<CPlate> all_result_Plates;
 | 
				
			||||
    all_result_Plates.reserve(64);
 | 
				
			||||
#pragma omp parallel sections
 | 
				
			||||
    {
 | 
				
			||||
#pragma omp section
 | 
				
			||||
      {
 | 
				
			||||
        if (!type || type & PR_DETECT_SOBEL) {
 | 
				
			||||
          m_plateLocate->plateSobelLocate(src, sobel_Plates, img_index);
 | 
				
			||||
        }
 | 
				
			||||
      }
 | 
				
			||||
#pragma omp section
 | 
				
			||||
      {
 | 
				
			||||
        if (!type || type & PR_DETECT_COLOR) {
 | 
				
			||||
          m_plateLocate->plateColorLocate(src, color_Plates, img_index);
 | 
				
			||||
        }
 | 
				
			||||
      }
 | 
				
			||||
#pragma omp section
 | 
				
			||||
      {
 | 
				
			||||
        if (!type || type & PR_DETECT_CMSER) {
 | 
				
			||||
          m_plateLocate->plateMserLocate(src, mser_Plates, img_index);
 | 
				
			||||
        }
 | 
				
			||||
      }
 | 
				
			||||
    }
 | 
				
			||||
    for (auto plate : sobel_Plates) {
 | 
				
			||||
      plate.setPlateLocateType(SOBEL);
 | 
				
			||||
      all_result_Plates.push_back(plate);
 | 
				
			||||
    }
 | 
				
			||||
    for (auto plate : color_Plates) {
 | 
				
			||||
      plate.setPlateLocateType(COLOR);
 | 
				
			||||
      all_result_Plates.push_back(plate);
 | 
				
			||||
    }
 | 
				
			||||
    for (auto plate : mser_Plates) {
 | 
				
			||||
      plate.setPlateLocateType(CMSER);
 | 
				
			||||
      all_result_Plates.push_back(plate);
 | 
				
			||||
    }
 | 
				
			||||
    // use nms to judge plate
 | 
				
			||||
    PlateJudge::instance()->plateJudgeUsingNMS(all_result_Plates, resultVec, m_maxPlates);
 | 
				
			||||
 | 
				
			||||
    if (0)
 | 
				
			||||
      showDectectResults(src, resultVec, m_maxPlates);
 | 
				
			||||
    return 0;
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  int CPlateDetect::plateDetect(Mat src, std::vector<CPlate> &resultVec, int img_index) {
 | 
				
			||||
    int result = plateDetect(src, resultVec, m_type, false, img_index);
 | 
				
			||||
    return result;
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  void CPlateDetect::LoadSVM(std::string path) {
 | 
				
			||||
    PlateJudge::instance()->LoadModel(path);
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
}
 | 
				
			||||
@ -0,0 +1,105 @@
 | 
				
			||||
#include "easypr/core/plate_recognize.h"
 | 
				
			||||
#include "easypr/config.h"
 | 
				
			||||
#include "thirdparty/textDetect/erfilter.hpp"
 | 
				
			||||
 | 
				
			||||
namespace easypr {
 | 
				
			||||
 | 
				
			||||
CPlateRecognize::CPlateRecognize() { 
 | 
				
			||||
  m_showResult = false;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
// main method, plate recognize, contain two parts
 | 
				
			||||
// 1. plate detect
 | 
				
			||||
// 2. chars recognize
 | 
				
			||||
int CPlateRecognize::plateRecognize(const Mat& src, std::vector<CPlate> &plateVecOut, int img_index) {
 | 
				
			||||
  // resize to uniform sizes
 | 
				
			||||
  float scale = 1.f;
 | 
				
			||||
  Mat img = uniformResize(src, scale);
 | 
				
			||||
 | 
				
			||||
  // 1. plate detect
 | 
				
			||||
  std::vector<CPlate> plateVec;
 | 
				
			||||
  int resultPD = plateDetect(img, plateVec, img_index);
 | 
				
			||||
  if (resultPD == 0) {
 | 
				
			||||
    size_t num = plateVec.size();
 | 
				
			||||
    for (size_t j = 0; j < num; j++) {
 | 
				
			||||
      CPlate& item = plateVec.at(j);
 | 
				
			||||
      Mat plateMat = item.getPlateMat();
 | 
				
			||||
      SHOW_IMAGE(plateMat, 0);
 | 
				
			||||
 | 
				
			||||
      // scale the rect to src;
 | 
				
			||||
      item.setPlateScale(scale);
 | 
				
			||||
      RotatedRect rect = item.getPlatePos();
 | 
				
			||||
      item.setPlatePos(scaleBackRRect(rect, 1.f / scale));
 | 
				
			||||
 | 
				
			||||
      // get plate color
 | 
				
			||||
      Color color = item.getPlateColor();
 | 
				
			||||
      if (color == UNKNOWN) {
 | 
				
			||||
        color = getPlateType(plateMat, true);
 | 
				
			||||
        item.setPlateColor(color);
 | 
				
			||||
      }
 | 
				
			||||
      std::string plateColor = getPlateColor(color);
 | 
				
			||||
      if (0) {
 | 
				
			||||
        std::cout << "plateColor:" << plateColor << std::endl;
 | 
				
			||||
      }
 | 
				
			||||
 | 
				
			||||
      // 2. chars recognize
 | 
				
			||||
      std::string plateIdentify = "";
 | 
				
			||||
      int resultCR = charsRecognise(item, plateIdentify);
 | 
				
			||||
      if (resultCR == 0) {
 | 
				
			||||
        std::string license = plateColor + ":" + plateIdentify;
 | 
				
			||||
        item.setPlateStr(license);
 | 
				
			||||
        plateVecOut.push_back(item);
 | 
				
			||||
        if (0) std::cout << "resultCR:" << resultCR << std::endl;
 | 
				
			||||
      }
 | 
				
			||||
      else {
 | 
				
			||||
        std::string license = plateColor;
 | 
				
			||||
        item.setPlateStr(license);
 | 
				
			||||
        plateVecOut.push_back(item);
 | 
				
			||||
        if (0) std::cout << "resultCR:" << resultCR << std::endl;
 | 
				
			||||
      }
 | 
				
			||||
    }
 | 
				
			||||
    if (getResultShow()) {
 | 
				
			||||
      // param type: 0 detect, 1 recognize;
 | 
				
			||||
      int showType = 1;
 | 
				
			||||
      if (0 == showType)
 | 
				
			||||
        showDectectResults(img, plateVec, num);
 | 
				
			||||
      else
 | 
				
			||||
        showDectectResults(img, plateVecOut, num);
 | 
				
			||||
    }
 | 
				
			||||
  }
 | 
				
			||||
  return resultPD;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void CPlateRecognize::LoadSVM(std::string path) {
 | 
				
			||||
  PlateJudge::instance()->LoadModel(path);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void CPlateRecognize::LoadANN(std::string path) {
 | 
				
			||||
  CharsIdentify::instance()->LoadModel(path);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void CPlateRecognize::LoadChineseANN(std::string path) {
 | 
				
			||||
  CharsIdentify::instance()->LoadChineseModel(path);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void CPlateRecognize::LoadGrayChANN(std::string path) {
 | 
				
			||||
  CharsIdentify::instance()->LoadGrayChANN(path);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void CPlateRecognize::LoadChineseMapping(std::string path) {
 | 
				
			||||
  CharsIdentify::instance()->LoadChineseMapping(path);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
// deprected
 | 
				
			||||
int CPlateRecognize::plateRecognize(const Mat& src, std::vector<std::string> &licenseVec) {
 | 
				
			||||
  vector<CPlate> plates;
 | 
				
			||||
  int resultPR = plateRecognize(src, plates, 0);
 | 
				
			||||
 | 
				
			||||
  for (auto plate : plates) {
 | 
				
			||||
    licenseVec.push_back(plate.getPlateStr());
 | 
				
			||||
  }
 | 
				
			||||
  return resultPR;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
}
 | 
				
			||||
@ -0,0 +1,277 @@
 | 
				
			||||
#include <numeric>
 | 
				
			||||
#include <ctime>
 | 
				
			||||
 | 
				
			||||
#include "easypr/train/annCh_train.h"
 | 
				
			||||
#include "easypr/config.h"
 | 
				
			||||
#include "easypr/core/chars_identify.h"
 | 
				
			||||
#include "easypr/core/feature.h"
 | 
				
			||||
#include "easypr/core/core_func.h"
 | 
				
			||||
#include "easypr/util/util.h"
 | 
				
			||||
#include "easypr/train/create_data.h"
 | 
				
			||||
 | 
				
			||||
namespace easypr {
 | 
				
			||||
 | 
				
			||||
  AnnChTrain::AnnChTrain(const char* chars_folder, const char* xml)
 | 
				
			||||
    : chars_folder_(chars_folder), ann_xml_(xml)
 | 
				
			||||
  {
 | 
				
			||||
  ann_ = cv::ml::ANN_MLP::create();
 | 
				
			||||
  type = 1;
 | 
				
			||||
  kv_ = std::shared_ptr<Kv>(new Kv);
 | 
				
			||||
  kv_->load("resources/text/province_mapping");
 | 
				
			||||
  extractFeature = getGrayPlusProject;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
  void AnnChTrain::train()
 | 
				
			||||
  {
 | 
				
			||||
    int classNumber = 0;
 | 
				
			||||
    int input_number = 0;
 | 
				
			||||
    int hidden_number = 0;
 | 
				
			||||
    int output_number = 0;
 | 
				
			||||
 | 
				
			||||
    bool useLBP = false;
 | 
				
			||||
    if (useLBP)
 | 
				
			||||
      input_number = kCharLBPPatterns * kCharLBPGridX * kCharLBPGridY;
 | 
				
			||||
    else
 | 
				
			||||
      input_number = kGrayCharHeight * kGrayCharWidth;
 | 
				
			||||
 | 
				
			||||
    input_number += 64;
 | 
				
			||||
 | 
				
			||||
  classNumber = kChineseNumber;
 | 
				
			||||
  hidden_number = kCharHiddenNeurans;
 | 
				
			||||
  output_number = classNumber;
 | 
				
			||||
  cv::Mat layers;
 | 
				
			||||
 | 
				
			||||
    int first_hidden_neurons = 48;
 | 
				
			||||
    int second_hidden_neurons = 32;
 | 
				
			||||
 | 
				
			||||
    int N = input_number;
 | 
				
			||||
    int m = output_number;
 | 
				
			||||
    //int first_hidden_neurons = int(std::sqrt((m + 2) * N) + 2 * std::sqrt(N / (m + 2)));
 | 
				
			||||
    //int second_hidden_neurons = int(m * std::sqrt(N / (m + 2)));
 | 
				
			||||
 | 
				
			||||
    bool useTLFN = false;
 | 
				
			||||
    if (!useTLFN) {
 | 
				
			||||
      layers.create(1, 3, CV_32SC1);
 | 
				
			||||
      layers.at<int>(0) = input_number;
 | 
				
			||||
      layers.at<int>(1) = hidden_number;
 | 
				
			||||
      layers.at<int>(2) = output_number;
 | 
				
			||||
    }
 | 
				
			||||
    else {
 | 
				
			||||
      fprintf(stdout, ">> Use two-layers neural networks,\n");
 | 
				
			||||
      fprintf(stdout, ">> First_hidden_neurons: %d \n", first_hidden_neurons);
 | 
				
			||||
      fprintf(stdout, ">> Second_hidden_neurons: %d \n", second_hidden_neurons);
 | 
				
			||||
 | 
				
			||||
      layers.create(1, 4, CV_32SC1);
 | 
				
			||||
      layers.at<int>(0) = input_number;
 | 
				
			||||
      layers.at<int>(1) = first_hidden_neurons;
 | 
				
			||||
      layers.at<int>(2) = second_hidden_neurons;
 | 
				
			||||
      layers.at<int>(3) = output_number;
 | 
				
			||||
    }
 | 
				
			||||
 | 
				
			||||
  ann_->setLayerSizes(layers);
 | 
				
			||||
  ann_->setActivationFunction(cv::ml::ANN_MLP::SIGMOID_SYM, 1, 1);
 | 
				
			||||
  ann_->setTrainMethod(cv::ml::ANN_MLP::TrainingMethods::BACKPROP);
 | 
				
			||||
  ann_->setTermCriteria(cvTermCriteria(CV_TERMCRIT_ITER, 30000, 0.0001));
 | 
				
			||||
  ann_->setBackpropWeightScale(0.1);
 | 
				
			||||
  ann_->setBackpropMomentumScale(0.1);
 | 
				
			||||
 | 
				
			||||
  auto files = Utils::getFiles(chars_folder_);
 | 
				
			||||
  if (files.size() == 0) {
 | 
				
			||||
    fprintf(stdout, "No file found in the train folder!\n");
 | 
				
			||||
    fprintf(stdout, "You should create a folder named \"tmp\" in EasyPR main folder.\n");
 | 
				
			||||
    fprintf(stdout, "Copy train data folder(like \"annCh\") under \"tmp\". \n");
 | 
				
			||||
    return;
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  // using raw data or raw + synthic data.
 | 
				
			||||
  trainVal(m_number_for_count);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
std::pair<std::string, std::string> AnnChTrain::identifyGrayChinese(cv::Mat input) {
 | 
				
			||||
  Mat feature;
 | 
				
			||||
  extractFeature(input, feature);
 | 
				
			||||
  float maxVal = -2;
 | 
				
			||||
  int result = 0;
 | 
				
			||||
 | 
				
			||||
  cv::Mat output(1, kChineseNumber, CV_32FC1);
 | 
				
			||||
  ann_->predict(feature, output);
 | 
				
			||||
 | 
				
			||||
  for (int j = 0; j < kChineseNumber; j++) {
 | 
				
			||||
    float val = output.at<float>(j);
 | 
				
			||||
    //std::cout << "j:" << j << "val:" << val << std::endl;
 | 
				
			||||
    if (val > maxVal) {
 | 
				
			||||
      maxVal = val;
 | 
				
			||||
      result = j;
 | 
				
			||||
    }
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  auto index = result + kCharsTotalNumber - kChineseNumber;
 | 
				
			||||
  const char* key = kChars[index];
 | 
				
			||||
  std::string s = key;
 | 
				
			||||
  std::string province = kv_->get(s);
 | 
				
			||||
 | 
				
			||||
  return std::make_pair(s, province);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
  void AnnChTrain::test() {
 | 
				
			||||
    //TODO
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void AnnChTrain::trainVal(size_t number_for_count) {
 | 
				
			||||
  assert(chars_folder_);
 | 
				
			||||
  cv::Mat train_samples;
 | 
				
			||||
  std::vector<cv::Mat>  train_images, val_images;
 | 
				
			||||
  std::vector<int> train_label, val_labels;
 | 
				
			||||
  float percentage = 0.7f;
 | 
				
			||||
  int classNumber = kChineseNumber;
 | 
				
			||||
 | 
				
			||||
  for (int i = 0; i < classNumber; ++i) { 
 | 
				
			||||
    auto char_key = kChars[i + kCharsTotalNumber - classNumber];
 | 
				
			||||
    char sub_folder[512] = { 0 };
 | 
				
			||||
    sprintf(sub_folder, "%s/%s", chars_folder_, char_key);
 | 
				
			||||
    std::string test_char(char_key);
 | 
				
			||||
    // if (test_char != "zh_yun") continue;
 | 
				
			||||
 | 
				
			||||
    fprintf(stdout, ">> Testing characters %s in %s \n", char_key, sub_folder);
 | 
				
			||||
    auto chars_files = utils::getFiles(sub_folder);
 | 
				
			||||
    size_t char_size = chars_files.size();
 | 
				
			||||
    fprintf(stdout, ">> Characters count: %d \n", (int)char_size);
 | 
				
			||||
 | 
				
			||||
    std::vector<cv::Mat> matVec;
 | 
				
			||||
    matVec.reserve(number_for_count);
 | 
				
			||||
    for (auto file : chars_files) {
 | 
				
			||||
      std::cout << file << std::endl;
 | 
				
			||||
      auto img = cv::imread(file, IMREAD_GRAYSCALE);  // a grayscale image 
 | 
				
			||||
      Mat img_resize;
 | 
				
			||||
      img_resize.create(kGrayCharHeight, kGrayCharWidth, CV_8UC1);
 | 
				
			||||
      resize(img, img_resize, img_resize.size(), 0, 0, INTER_LINEAR);
 | 
				
			||||
      matVec.push_back(img_resize);
 | 
				
			||||
    }
 | 
				
			||||
    // genrate the synthetic images
 | 
				
			||||
    for (int t = 0; t < (int)number_for_count - (int)char_size; t++) {
 | 
				
			||||
      int rand_range = char_size + t;
 | 
				
			||||
      int ran_num = rand() % rand_range;
 | 
				
			||||
      auto img = matVec.at(ran_num);
 | 
				
			||||
      SHOW_IMAGE(img, 0);
 | 
				
			||||
      auto simg = generateSyntheticImage(img);
 | 
				
			||||
      SHOW_IMAGE(simg, 0);
 | 
				
			||||
      matVec.push_back(simg);
 | 
				
			||||
    }
 | 
				
			||||
    fprintf(stdout, ">> Characters count: %d \n", (int)matVec.size());
 | 
				
			||||
 | 
				
			||||
    // random sort the mat;
 | 
				
			||||
    srand(unsigned(time(NULL)));
 | 
				
			||||
    random_shuffle(matVec.begin(), matVec.end());
 | 
				
			||||
 | 
				
			||||
    int mat_size = (int)matVec.size();
 | 
				
			||||
    int split_index = int((float)mat_size * percentage);
 | 
				
			||||
    for (int j = mat_size - 1; j >= 0; j--) {
 | 
				
			||||
      Mat img = matVec.at(j);
 | 
				
			||||
      if (1) {
 | 
				
			||||
        Mat feature;
 | 
				
			||||
        extractFeature(img, feature);
 | 
				
			||||
        if (j <= split_index) {
 | 
				
			||||
          train_samples.push_back(feature);
 | 
				
			||||
          train_images.push_back(img);
 | 
				
			||||
          train_label.push_back(i);
 | 
				
			||||
        }
 | 
				
			||||
        else {
 | 
				
			||||
          val_images.push_back(img);
 | 
				
			||||
          val_labels.push_back(i);
 | 
				
			||||
        }
 | 
				
			||||
      }
 | 
				
			||||
    }
 | 
				
			||||
  } 
 | 
				
			||||
  // generate train data
 | 
				
			||||
  train_samples.convertTo(train_samples, CV_32F);
 | 
				
			||||
  cv::Mat train_classes = cv::Mat::zeros((int)train_label.size(), classNumber, CV_32F);
 | 
				
			||||
  for (int i = 0; i < train_classes.rows; ++i)
 | 
				
			||||
    train_classes.at<float>(i, train_label[i]) = 1.f;
 | 
				
			||||
  auto train_data = cv::ml::TrainData::create(train_samples, cv::ml::SampleTypes::ROW_SAMPLE, train_classes);
 | 
				
			||||
 | 
				
			||||
  // train the data, calculate the cost time
 | 
				
			||||
  std::cout << "Training ANN chinese model, please wait..." << std::endl;
 | 
				
			||||
  long start = utils::getTimestamp();
 | 
				
			||||
  ann_->train(train_data);
 | 
				
			||||
  long end = utils::getTimestamp();
 | 
				
			||||
  ann_->save(ann_xml_);
 | 
				
			||||
  std::cout << "Your ANN Model was saved to " << ann_xml_ << std::endl;
 | 
				
			||||
  std::cout << "Training done. Time elapse: " << (end - start) / (1000 * 60) << "minute" << std::endl;
 | 
				
			||||
 | 
				
			||||
  // test the accuracy_rate in train
 | 
				
			||||
  if (1) {
 | 
				
			||||
    int corrects_all = 0, sum_all = train_images.size();
 | 
				
			||||
    std::cout << "train_images size: " << sum_all << std::endl;
 | 
				
			||||
    for (size_t i = 0; i < train_images.size(); ++i) {
 | 
				
			||||
      cv::Mat img = train_images.at(i);
 | 
				
			||||
      int label = train_label.at(i);
 | 
				
			||||
      auto char_key = kChars[label + kCharsTotalNumber - classNumber];
 | 
				
			||||
      std::pair<std::string, std::string> ch = identifyGrayChinese(img);
 | 
				
			||||
      if (ch.first == char_key)
 | 
				
			||||
        corrects_all++;
 | 
				
			||||
    }
 | 
				
			||||
    float accuracy_rate = (float)corrects_all / (float)sum_all;
 | 
				
			||||
    std::cout << "Train error_rate: " << (1.f - accuracy_rate) * 100.f << "% "<< std::endl;
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  // test the accuracy_rate in val
 | 
				
			||||
  if (1) {
 | 
				
			||||
    int corrects_all = 0, sum_all = val_images.size();
 | 
				
			||||
    std::cout << "val_images: " << sum_all << std::endl;
 | 
				
			||||
    for (size_t i = 0; i < val_images.size(); ++i) {
 | 
				
			||||
      cv::Mat img = val_images.at(i);
 | 
				
			||||
      int label = val_labels.at(i);
 | 
				
			||||
      auto char_key = kChars[label + kCharsTotalNumber - classNumber];
 | 
				
			||||
      std::pair<std::string, std::string> ch = identifyGrayChinese(img);
 | 
				
			||||
      if (ch.first == char_key)
 | 
				
			||||
        corrects_all++;
 | 
				
			||||
    }
 | 
				
			||||
    float accuracy_rate = (float)corrects_all / (float)sum_all;
 | 
				
			||||
    std::cout << "Test error_rate: " << (1.f - accuracy_rate) * 100.f << "% "<< std::endl;
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
cv::Ptr<cv::ml::TrainData> AnnChTrain::tdata() {
 | 
				
			||||
  assert(chars_folder_);
 | 
				
			||||
 | 
				
			||||
  cv::Mat samples;
 | 
				
			||||
  std::vector<int> labels;
 | 
				
			||||
 | 
				
			||||
  std::cout << "Collecting chars in " << chars_folder_ << std::endl;
 | 
				
			||||
 | 
				
			||||
  int classNumber = 0;
 | 
				
			||||
  if (type == 0) classNumber = kCharsTotalNumber;
 | 
				
			||||
  if (type == 1) classNumber = kChineseNumber;
 | 
				
			||||
 | 
				
			||||
  for (int i = 0; i < classNumber; ++i) {
 | 
				
			||||
    auto char_key = kChars[i + kCharsTotalNumber - classNumber];
 | 
				
			||||
    char sub_folder[512] = {0};
 | 
				
			||||
 | 
				
			||||
    sprintf(sub_folder, "%s/%s", chars_folder_, char_key);
 | 
				
			||||
    std::cout << "  >> Featuring characters " << char_key << " in "
 | 
				
			||||
              << sub_folder << std::endl;
 | 
				
			||||
 | 
				
			||||
    auto chars_files = utils::getFiles(sub_folder);
 | 
				
			||||
    for (auto file : chars_files) {
 | 
				
			||||
      auto img = cv::imread(file, 0);  // a grayscale image
 | 
				
			||||
      auto fps = charFeatures2(img, kPredictSize);
 | 
				
			||||
 | 
				
			||||
      samples.push_back(fps);
 | 
				
			||||
      labels.push_back(i);
 | 
				
			||||
    }
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  cv::Mat samples_;
 | 
				
			||||
  samples.convertTo(samples_, CV_32F);
 | 
				
			||||
  cv::Mat train_classes =
 | 
				
			||||
    cv::Mat::zeros((int)labels.size(), classNumber, CV_32F);
 | 
				
			||||
 | 
				
			||||
  for (int i = 0; i < train_classes.rows; ++i) {
 | 
				
			||||
    train_classes.at<float>(i, labels[i]) = 1.f;
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  return cv::ml::TrainData::create(samples_, cv::ml::SampleTypes::ROW_SAMPLE,
 | 
				
			||||
                                   train_classes);
 | 
				
			||||
}
 | 
				
			||||
}
 | 
				
			||||
@ -0,0 +1,356 @@
 | 
				
			||||
#include <numeric>
 | 
				
			||||
#include <ctime>
 | 
				
			||||
 | 
				
			||||
#include "easypr/train/ann_train.h"
 | 
				
			||||
#include "easypr/config.h"
 | 
				
			||||
#include "easypr/core/chars_identify.h"
 | 
				
			||||
#include "easypr/core/feature.h"
 | 
				
			||||
#include "easypr/core/core_func.h"
 | 
				
			||||
#include "easypr/train/create_data.h"
 | 
				
			||||
#include "easypr/util/util.h"
 | 
				
			||||
 | 
				
			||||
namespace easypr {
 | 
				
			||||
 | 
				
			||||
AnnTrain::AnnTrain(const char* chars_folder, const char* xml)
 | 
				
			||||
    : chars_folder_(chars_folder), ann_xml_(xml) {
 | 
				
			||||
  ann_ = cv::ml::ANN_MLP::create();
 | 
				
			||||
  // type=0, all characters
 | 
				
			||||
  // type=1, only chinese
 | 
				
			||||
  type = 0;
 | 
				
			||||
  kv_ = std::shared_ptr<Kv>(new Kv);
 | 
				
			||||
  kv_->load("resources/text/province_mapping");
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void AnnTrain::train() {
 | 
				
			||||
  
 | 
				
			||||
  int classNumber = 0;
 | 
				
			||||
 | 
				
			||||
  cv::Mat layers;
 | 
				
			||||
 | 
				
			||||
  int input_number = 0;
 | 
				
			||||
  int hidden_number = 0;
 | 
				
			||||
  int output_number = 0;
 | 
				
			||||
 | 
				
			||||
  if (type == 0) {
 | 
				
			||||
    classNumber = kCharsTotalNumber;
 | 
				
			||||
 | 
				
			||||
    input_number = kAnnInput;
 | 
				
			||||
    hidden_number = kNeurons;
 | 
				
			||||
    output_number = classNumber;
 | 
				
			||||
  }
 | 
				
			||||
  else if (type == 1) {
 | 
				
			||||
    classNumber = kChineseNumber;
 | 
				
			||||
 | 
				
			||||
    input_number = kAnnInput;
 | 
				
			||||
    hidden_number = kNeurons;
 | 
				
			||||
    output_number = classNumber;
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  int N = input_number;
 | 
				
			||||
  int m = output_number;
 | 
				
			||||
  int first_hidden_neurons = int(std::sqrt((m + 2) * N) + 2 * std::sqrt(N / (m + 2)));
 | 
				
			||||
  int second_hidden_neurons = int(m * std::sqrt(N / (m + 2)));
 | 
				
			||||
 | 
				
			||||
  bool useTLFN = false;
 | 
				
			||||
  if (!useTLFN) {
 | 
				
			||||
    layers.create(1, 3, CV_32SC1);
 | 
				
			||||
    layers.at<int>(0) = input_number;
 | 
				
			||||
    layers.at<int>(1) = hidden_number;
 | 
				
			||||
    layers.at<int>(2) = output_number;
 | 
				
			||||
  }
 | 
				
			||||
  else {
 | 
				
			||||
    // Two-layers neural networks is hard to train, So do not try it
 | 
				
			||||
    fprintf(stdout, ">> Use two-layers neural networks,\n");
 | 
				
			||||
    fprintf(stdout, ">> First_hidden_neurons: %d \n", first_hidden_neurons);
 | 
				
			||||
    fprintf(stdout, ">> Second_hidden_neurons: %d \n", second_hidden_neurons);
 | 
				
			||||
 | 
				
			||||
    layers.create(1, 4, CV_32SC1);
 | 
				
			||||
    layers.at<int>(0) = input_number;
 | 
				
			||||
    layers.at<int>(1) = first_hidden_neurons;
 | 
				
			||||
    layers.at<int>(2) = second_hidden_neurons;
 | 
				
			||||
    layers.at<int>(3) = output_number;
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  ann_->setLayerSizes(layers);
 | 
				
			||||
  ann_->setActivationFunction(cv::ml::ANN_MLP::SIGMOID_SYM, 1, 1);
 | 
				
			||||
  ann_->setTrainMethod(cv::ml::ANN_MLP::TrainingMethods::BACKPROP);
 | 
				
			||||
  ann_->setTermCriteria(cvTermCriteria(CV_TERMCRIT_ITER, 30000, 0.0001));
 | 
				
			||||
  ann_->setBackpropWeightScale(0.1);
 | 
				
			||||
  ann_->setBackpropMomentumScale(0.1);
 | 
				
			||||
 | 
				
			||||
  auto files = Utils::getFiles(chars_folder_);
 | 
				
			||||
  if (files.size() == 0) {
 | 
				
			||||
    fprintf(stdout, "No file found in the train folder!\n");
 | 
				
			||||
    fprintf(stdout, "You should create a folder named \"tmp\" in EasyPR main folder.\n");
 | 
				
			||||
    fprintf(stdout, "Copy train data folder(like \"ann\") under \"tmp\". \n");
 | 
				
			||||
    return;
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  //using raw data or raw + synthic data.
 | 
				
			||||
  auto traindata = sdata(350);
 | 
				
			||||
 | 
				
			||||
  std::cout << "Training ANN model, please wait..." << std::endl;
 | 
				
			||||
  long start = utils::getTimestamp();
 | 
				
			||||
  ann_->train(traindata);
 | 
				
			||||
  long end = utils::getTimestamp();
 | 
				
			||||
  ann_->save(ann_xml_);
 | 
				
			||||
  
 | 
				
			||||
  test();
 | 
				
			||||
  std::cout << "Your ANN Model was saved to " << ann_xml_ << std::endl;
 | 
				
			||||
  std::cout << "Training done. Time elapse: " << (end - start) / (1000 * 60) << "minute" << std::endl;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
std::pair<std::string, std::string> AnnTrain::identifyChinese(cv::Mat input) {
 | 
				
			||||
  cv::Mat feature = charFeatures2(input, kPredictSize);
 | 
				
			||||
  float maxVal = -2;
 | 
				
			||||
  int result = 0;
 | 
				
			||||
 | 
				
			||||
  cv::Mat output(1, kChineseNumber, CV_32FC1);
 | 
				
			||||
  ann_->predict(feature, output);
 | 
				
			||||
 | 
				
			||||
  for (int j = 0; j < kChineseNumber; j++) {
 | 
				
			||||
    float val = output.at<float>(j);
 | 
				
			||||
    // std::cout << "j:" << j << "val:" << val << std::endl;
 | 
				
			||||
    if (val > maxVal) {
 | 
				
			||||
      maxVal = val;
 | 
				
			||||
      result = j;
 | 
				
			||||
    }
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  auto index = result + kCharsTotalNumber - kChineseNumber;
 | 
				
			||||
  const char* key = kChars[index];
 | 
				
			||||
  std::string s = key;
 | 
				
			||||
  std::string province = kv_->get(s);
 | 
				
			||||
 | 
				
			||||
  return std::make_pair(s, province);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
std::pair<std::string, std::string> AnnTrain::identify(cv::Mat input) {
 | 
				
			||||
  cv::Mat feature = charFeatures2(input, kPredictSize);
 | 
				
			||||
  float maxVal = -2;
 | 
				
			||||
  int result = 0;
 | 
				
			||||
 | 
				
			||||
  //std::cout << feature << std::endl;
 | 
				
			||||
  cv::Mat output(1, kCharsTotalNumber, CV_32FC1);
 | 
				
			||||
  ann_->predict(feature, output);
 | 
				
			||||
  //std::cout << output << std::endl;
 | 
				
			||||
  for (int j = 0; j < kCharsTotalNumber; j++) {
 | 
				
			||||
    float val = output.at<float>(j);
 | 
				
			||||
    //std::cout << "j:" << j << "val:" << val << std::endl;
 | 
				
			||||
    if (val > maxVal) {
 | 
				
			||||
      maxVal = val;
 | 
				
			||||
      result = j;
 | 
				
			||||
    }
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  auto index = result;
 | 
				
			||||
  if (index < kCharactersNumber) {
 | 
				
			||||
    return std::make_pair(kChars[index], kChars[index]);
 | 
				
			||||
  }
 | 
				
			||||
  else {
 | 
				
			||||
    const char* key = kChars[index];
 | 
				
			||||
    std::string s = key;
 | 
				
			||||
    std::string province = kv_->get(s);
 | 
				
			||||
    return std::make_pair(s, province);
 | 
				
			||||
  }
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void AnnTrain::test() {
 | 
				
			||||
  assert(chars_folder_);
 | 
				
			||||
 | 
				
			||||
  int classNumber = 0;
 | 
				
			||||
  if (type == 0) classNumber = kCharsTotalNumber;
 | 
				
			||||
  if (type == 1) classNumber = kChineseNumber;
 | 
				
			||||
 | 
				
			||||
  int corrects_all = 0, sum_all = 0;
 | 
				
			||||
  std::vector<float> rate_list;
 | 
				
			||||
  for (int i = 0; i < classNumber; ++i) {
 | 
				
			||||
    auto char_key = kChars[i + kCharsTotalNumber - classNumber];
 | 
				
			||||
    char sub_folder[512] = { 0 };
 | 
				
			||||
 | 
				
			||||
    sprintf(sub_folder, "%s/%s", chars_folder_, char_key);
 | 
				
			||||
    fprintf(stdout, ">> Testing characters %s in %s \n", char_key, sub_folder);
 | 
				
			||||
 | 
				
			||||
    auto chars_files = utils::getFiles(sub_folder);
 | 
				
			||||
    int corrects = 0, sum = 0;
 | 
				
			||||
    std::vector<std::pair<std::string, std::string>> error_files;
 | 
				
			||||
 | 
				
			||||
    for (auto file : chars_files) {
 | 
				
			||||
      auto img = cv::imread(file, 0);  // a grayscale image
 | 
				
			||||
      if (!img.data) {
 | 
				
			||||
        //cout << "Null pointer!" << endl;
 | 
				
			||||
        continue;
 | 
				
			||||
      }
 | 
				
			||||
      std::pair<std::string, std::string> ch;
 | 
				
			||||
 | 
				
			||||
      if (type == 0) ch = identify(img);
 | 
				
			||||
      if (type == 1) ch = identifyChinese(img);
 | 
				
			||||
 | 
				
			||||
      if (ch.first == char_key) {
 | 
				
			||||
        ++corrects;
 | 
				
			||||
        ++corrects_all;
 | 
				
			||||
      } else {
 | 
				
			||||
        error_files.push_back(std::make_pair(utils::getFileName(file), ch.second));
 | 
				
			||||
      }
 | 
				
			||||
      ++sum;
 | 
				
			||||
      ++sum_all;
 | 
				
			||||
    }
 | 
				
			||||
    float rate = (float)corrects / (sum == 0 ? 1 : sum);
 | 
				
			||||
    fprintf(stdout, ">>   [sum: %d, correct: %d, rate: %.2f]\n", sum, corrects, rate);
 | 
				
			||||
    rate_list.push_back(rate);
 | 
				
			||||
 | 
				
			||||
    std::string error_string;
 | 
				
			||||
    auto end = error_files.end();
 | 
				
			||||
    if (error_files.size() >= 10) {
 | 
				
			||||
      end -= static_cast<size_t>(error_files.size() * (1 - 0.1));
 | 
				
			||||
    }
 | 
				
			||||
    for (auto k = error_files.begin(); k != end; ++k) {
 | 
				
			||||
      auto kv = *k;
 | 
				
			||||
      error_string.append("       ").append(kv.first).append(": ").append(
 | 
				
			||||
          kv.second);
 | 
				
			||||
      if (k != end - 1) {
 | 
				
			||||
        error_string.append(",\n");
 | 
				
			||||
      } else {
 | 
				
			||||
        error_string.append("\n       ...");
 | 
				
			||||
      }
 | 
				
			||||
    }
 | 
				
			||||
    fprintf(stdout, ">>   [\n%s\n     ]\n", error_string.c_str());
 | 
				
			||||
  }
 | 
				
			||||
  fprintf(stdout, ">>   [sum_all: %d, correct_all: %d, rate: %.4f]\n", sum_all, corrects_all,
 | 
				
			||||
    (float)corrects_all / (sum_all == 0 ? 1 : sum_all));
 | 
				
			||||
 | 
				
			||||
  double rate_sum = std::accumulate(rate_list.begin(), rate_list.end(), 0.0);
 | 
				
			||||
  double rate_mean = rate_sum / (rate_list.size() == 0 ? 1 : rate_list.size());
 | 
				
			||||
 | 
				
			||||
  fprintf(stdout, ">>   [classNumber: %d, avg_rate: %.4f]\n", classNumber, rate_mean);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
cv::Mat getSyntheticImage(const Mat& image) {
 | 
				
			||||
  int rand_type = rand();
 | 
				
			||||
  Mat result = image.clone();
 | 
				
			||||
 | 
				
			||||
  if (rand_type % 2 == 0) {
 | 
				
			||||
    int ran_x = rand() % 5 - 2;
 | 
				
			||||
    int ran_y = rand() % 5 - 2;
 | 
				
			||||
 | 
				
			||||
    result = translateImg(result, ran_x, ran_y);
 | 
				
			||||
  }
 | 
				
			||||
  else if (rand_type % 2 != 0) {
 | 
				
			||||
    float angle = float(rand() % 15 - 7);
 | 
				
			||||
 | 
				
			||||
    result = rotateImg(result, angle);
 | 
				
			||||
  }
 | 
				
			||||
  
 | 
				
			||||
  return result;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
cv::Ptr<cv::ml::TrainData> AnnTrain::sdata(size_t number_for_count) {
 | 
				
			||||
  assert(chars_folder_);
 | 
				
			||||
 | 
				
			||||
  cv::Mat samples;
 | 
				
			||||
  std::vector<int> labels;
 | 
				
			||||
 | 
				
			||||
  int classNumber = 0;
 | 
				
			||||
  if (type == 0) classNumber = kCharsTotalNumber;
 | 
				
			||||
  if (type == 1) classNumber = kChineseNumber;
 | 
				
			||||
  
 | 
				
			||||
  srand((unsigned)time(0));
 | 
				
			||||
  for (int i = 0; i < classNumber; ++i) {
 | 
				
			||||
   
 | 
				
			||||
    auto char_key = kChars[i + kCharsTotalNumber - classNumber];
 | 
				
			||||
    char sub_folder[512] = { 0 };
 | 
				
			||||
 | 
				
			||||
    sprintf(sub_folder, "%s/%s", chars_folder_, char_key);
 | 
				
			||||
    fprintf(stdout, ">> Testing characters %s in %s \n", char_key, sub_folder);
 | 
				
			||||
 | 
				
			||||
    auto chars_files = utils::getFiles(sub_folder);
 | 
				
			||||
    size_t char_size = chars_files.size();
 | 
				
			||||
    fprintf(stdout, ">> Characters count: %d \n", int(char_size));
 | 
				
			||||
 | 
				
			||||
    std::vector<cv::Mat> matVec;
 | 
				
			||||
    matVec.reserve(number_for_count);
 | 
				
			||||
    for (auto file : chars_files) {
 | 
				
			||||
      auto img = cv::imread(file, 0);  // a grayscale image
 | 
				
			||||
      matVec.push_back(img);
 | 
				
			||||
    }
 | 
				
			||||
 | 
				
			||||
    for (int t = 0; t < (int)number_for_count - (int)char_size; t++) {
 | 
				
			||||
      int rand_range = char_size + t;
 | 
				
			||||
      int ran_num = rand() % rand_range;
 | 
				
			||||
      auto img = matVec.at(ran_num);
 | 
				
			||||
      auto simg = getSyntheticImage(img);
 | 
				
			||||
      matVec.push_back(simg);
 | 
				
			||||
      if (1) {
 | 
				
			||||
        std::stringstream ss(std::stringstream::in | std::stringstream::out);
 | 
				
			||||
        ss << sub_folder << "/" << i << "_" << t << "_" << ran_num << ".jpg";
 | 
				
			||||
        imwrite(ss.str(), simg);
 | 
				
			||||
      }
 | 
				
			||||
    }
 | 
				
			||||
 | 
				
			||||
    fprintf(stdout, ">> Characters count: %d \n", (int)matVec.size());
 | 
				
			||||
 | 
				
			||||
    for (auto img : matVec) {
 | 
				
			||||
      auto fps = charFeatures2(img, kPredictSize);
 | 
				
			||||
 | 
				
			||||
      samples.push_back(fps);
 | 
				
			||||
      labels.push_back(i);
 | 
				
			||||
    }
 | 
				
			||||
  } 
 | 
				
			||||
 | 
				
			||||
  cv::Mat samples_;
 | 
				
			||||
  samples.convertTo(samples_, CV_32F);
 | 
				
			||||
  cv::Mat train_classes =
 | 
				
			||||
    cv::Mat::zeros((int)labels.size(), classNumber, CV_32F);
 | 
				
			||||
 | 
				
			||||
  for (int i = 0; i < train_classes.rows; ++i) {
 | 
				
			||||
    train_classes.at<float>(i, labels[i]) = 1.f;
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  return cv::ml::TrainData::create(samples_, cv::ml::SampleTypes::ROW_SAMPLE,
 | 
				
			||||
    train_classes);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
cv::Ptr<cv::ml::TrainData> AnnTrain::tdata() {
 | 
				
			||||
  assert(chars_folder_);
 | 
				
			||||
 | 
				
			||||
  cv::Mat samples;
 | 
				
			||||
  std::vector<int> labels;
 | 
				
			||||
 | 
				
			||||
  std::cout << "Collecting chars in " << chars_folder_ << std::endl;
 | 
				
			||||
 | 
				
			||||
  int classNumber = 0;
 | 
				
			||||
  if (type == 0) classNumber = kCharsTotalNumber;
 | 
				
			||||
  if (type == 1) classNumber = kChineseNumber;
 | 
				
			||||
 | 
				
			||||
  for (int i = 0; i < classNumber; ++i) {
 | 
				
			||||
    auto char_key = kChars[i + kCharsTotalNumber - classNumber];
 | 
				
			||||
    char sub_folder[512] = {0};
 | 
				
			||||
 | 
				
			||||
    sprintf(sub_folder, "%s/%s", chars_folder_, char_key);
 | 
				
			||||
    std::cout << "  >> Featuring characters " << char_key << " in "
 | 
				
			||||
              << sub_folder << std::endl;
 | 
				
			||||
 | 
				
			||||
    auto chars_files = utils::getFiles(sub_folder);
 | 
				
			||||
    for (auto file : chars_files) {
 | 
				
			||||
      auto img = cv::imread(file, 0);  // a grayscale image
 | 
				
			||||
      auto fps = charFeatures2(img, kPredictSize);
 | 
				
			||||
 | 
				
			||||
      samples.push_back(fps);
 | 
				
			||||
      labels.push_back(i);
 | 
				
			||||
    }
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  cv::Mat samples_;
 | 
				
			||||
  samples.convertTo(samples_, CV_32F);
 | 
				
			||||
  cv::Mat train_classes =
 | 
				
			||||
    cv::Mat::zeros((int)labels.size(), classNumber, CV_32F);
 | 
				
			||||
 | 
				
			||||
  for (int i = 0; i < train_classes.rows; ++i) {
 | 
				
			||||
    train_classes.at<float>(i, labels[i]) = 1.f;
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  return cv::ml::TrainData::create(samples_, cv::ml::SampleTypes::ROW_SAMPLE,
 | 
				
			||||
                                   train_classes);
 | 
				
			||||
}
 | 
				
			||||
}
 | 
				
			||||
@ -0,0 +1,88 @@
 | 
				
			||||
#include "easypr/train/create_data.h"
 | 
				
			||||
 | 
				
			||||
namespace easypr {
 | 
				
			||||
 | 
				
			||||
  int getBoderColor(Mat img) {
 | 
				
			||||
    assert(img.channels() == 1);
 | 
				
			||||
    assert(img.type() == CV_8UC1);
 | 
				
			||||
    int w = img.cols;
 | 
				
			||||
    int h = img.rows;
 | 
				
			||||
 | 
				
			||||
    float sum = 0;
 | 
				
			||||
    for (int i = 0; i < h; ++i) {
 | 
				
			||||
      sum += img.at<unsigned char>(i, 0);
 | 
				
			||||
      sum += img.at<unsigned char>(i, w-1);
 | 
				
			||||
    }
 | 
				
			||||
    for (int j = 0; j < w; ++j) {
 | 
				
			||||
      sum += img.at<unsigned char>(0, j);
 | 
				
			||||
      sum += img.at<unsigned char>(h-1, j);
 | 
				
			||||
    }
 | 
				
			||||
 | 
				
			||||
    float avg = sum / float(w + w + h + h);
 | 
				
			||||
    return int(avg);
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  // shift an image
 | 
				
			||||
  Mat translateImg(Mat img, int offsetx, int offsety, int bk){
 | 
				
			||||
    Mat dst;
 | 
				
			||||
    //cout << test << endl;
 | 
				
			||||
    Mat trans_mat = (Mat_<double>(2, 3) << 1, 0, offsetx, 0, 1, offsety);
 | 
				
			||||
    //cout << trans_mat << endl;
 | 
				
			||||
    warpAffine(img, dst, trans_mat, img.size(), 1, 0, Scalar(bk));
 | 
				
			||||
    return dst;
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  // rotate an image
 | 
				
			||||
  Mat rotateImg(Mat source, float angle, int bk){
 | 
				
			||||
    Point2f src_center(source.cols / 2.0F, source.rows / 2.0F);
 | 
				
			||||
    Mat rot_mat = getRotationMatrix2D(src_center, angle, 1.0);
 | 
				
			||||
    Mat dst;
 | 
				
			||||
    warpAffine(source, dst, rot_mat, source.size(), 1, 0, Scalar(bk));
 | 
				
			||||
    return dst;
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  // crop the image
 | 
				
			||||
  Mat cropImg(Mat src, int x, int y, int shift, int bk){
 | 
				
			||||
    int width = src.cols;
 | 
				
			||||
    int height = src.rows;
 | 
				
			||||
 | 
				
			||||
    int crop_width = width - shift;
 | 
				
			||||
    int crop_height = height - shift;
 | 
				
			||||
 | 
				
			||||
    int x_shift = shift;
 | 
				
			||||
    int y_shift = shift;
 | 
				
			||||
 | 
				
			||||
    x = x < x_shift ? x : x_shift;
 | 
				
			||||
    y = y < y_shift ? y : y_shift;
 | 
				
			||||
 | 
				
			||||
    Rect rect = Rect(x, y, crop_width, crop_height);
 | 
				
			||||
 | 
				
			||||
    Mat dst = src(rect);
 | 
				
			||||
    resize(dst, dst, Size(width, height));
 | 
				
			||||
    return dst;
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  Mat generateSyntheticImage(const Mat& image, int use_swap) {
 | 
				
			||||
    int rd = rand();
 | 
				
			||||
    int bkColor = getBoderColor(image);
 | 
				
			||||
    Mat result = image.clone();
 | 
				
			||||
    if (0 && (rd >> 6 & 1)) {
 | 
				
			||||
      int shift = 2;
 | 
				
			||||
      int ran_x = rand() % shift;
 | 
				
			||||
      int ran_y = rand() % shift;
 | 
				
			||||
      result = cropImg(result, ran_x, ran_y, shift, bkColor);
 | 
				
			||||
    }
 | 
				
			||||
    if (0 && (rd >> 4 & 1)) {
 | 
				
			||||
      int ran_x = rand() % 2 - 1;
 | 
				
			||||
      int ran_y = rand() % 2 - 1;
 | 
				
			||||
      result = translateImg(result, ran_x, ran_y, bkColor);
 | 
				
			||||
    }
 | 
				
			||||
    if (1 && (rd >> 2 & 1)) {
 | 
				
			||||
      float angle = float(rand() % 100) * 0.1f - 5.f;
 | 
				
			||||
      result = rotateImg(result, angle, bkColor);
 | 
				
			||||
    }
 | 
				
			||||
 | 
				
			||||
    return result;
 | 
				
			||||
  }
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
											
												Binary file not shown.
											
										
									
								@ -0,0 +1,196 @@
 | 
				
			||||
#include "easypr/train/svm_train.h"
 | 
				
			||||
#include "easypr/util/util.h"
 | 
				
			||||
#include "easypr/config.h"
 | 
				
			||||
 | 
				
			||||
#ifdef OS_WINDOWS
 | 
				
			||||
#include <ctime>
 | 
				
			||||
#endif
 | 
				
			||||
 | 
				
			||||
using namespace cv;
 | 
				
			||||
using namespace cv::ml;
 | 
				
			||||
 | 
				
			||||
namespace easypr {
 | 
				
			||||
 | 
				
			||||
SvmTrain::SvmTrain(const char* plates_folder, const char* xml)
 | 
				
			||||
    : plates_folder_(plates_folder), svm_xml_(xml) {
 | 
				
			||||
  assert(plates_folder);
 | 
				
			||||
  assert(xml);
 | 
				
			||||
 | 
				
			||||
  extractFeature = getHistomPlusColoFeatures;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void SvmTrain::train() {
 | 
				
			||||
  svm_ = cv::ml::SVM::create();
 | 
				
			||||
  svm_->setType(cv::ml::SVM::C_SVC);
 | 
				
			||||
  svm_->setKernel(cv::ml::SVM::RBF);
 | 
				
			||||
  svm_->setDegree(0.1);
 | 
				
			||||
  // 1.4 bug fix: old 1.4 ver gamma is 1
 | 
				
			||||
  svm_->setGamma(0.1); 
 | 
				
			||||
  svm_->setCoef0(0.1);
 | 
				
			||||
  svm_->setC(1);
 | 
				
			||||
  svm_->setNu(0.1);
 | 
				
			||||
  svm_->setP(0.1);
 | 
				
			||||
  svm_->setTermCriteria(cvTermCriteria(CV_TERMCRIT_ITER, 20000, 0.0001));
 | 
				
			||||
 | 
				
			||||
  this->prepare();
 | 
				
			||||
 | 
				
			||||
  if (train_file_list_.size() == 0) {
 | 
				
			||||
    fprintf(stdout, "No file found in the train folder!\n");
 | 
				
			||||
    fprintf(stdout, "You should create a folder named \"tmp\" in EasyPR main folder.\n");
 | 
				
			||||
    fprintf(stdout, "Copy train data folder(like \"SVM\") under \"tmp\". \n");
 | 
				
			||||
    return;
 | 
				
			||||
  }
 | 
				
			||||
  auto train_data = tdata();
 | 
				
			||||
 | 
				
			||||
  fprintf(stdout, ">> Training SVM model, please wait...\n");
 | 
				
			||||
  long start = utils::getTimestamp();
 | 
				
			||||
  svm_->trainAuto(train_data, 10, SVM::getDefaultGrid(SVM::C),
 | 
				
			||||
                  SVM::getDefaultGrid(SVM::GAMMA), SVM::getDefaultGrid(SVM::P),
 | 
				
			||||
                  SVM::getDefaultGrid(SVM::NU), SVM::getDefaultGrid(SVM::COEF),
 | 
				
			||||
                  SVM::getDefaultGrid(SVM::DEGREE), true);
 | 
				
			||||
  //svm_->train(train_data);
 | 
				
			||||
 | 
				
			||||
  long end = utils::getTimestamp();
 | 
				
			||||
  fprintf(stdout, ">> Training done. Time elapse: %ldms\n", end - start);
 | 
				
			||||
  fprintf(stdout, ">> Saving model file...\n");
 | 
				
			||||
  svm_->save(svm_xml_);
 | 
				
			||||
 | 
				
			||||
  fprintf(stdout, ">> Your SVM Model was saved to %s\n", svm_xml_);
 | 
				
			||||
  fprintf(stdout, ">> Testing...\n");
 | 
				
			||||
 | 
				
			||||
  this->test();
 | 
				
			||||
  
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void SvmTrain::test() {
 | 
				
			||||
  // 1.4 bug fix: old 1.4 ver there is no null judge
 | 
				
			||||
  // if (NULL == svm_)
 | 
				
			||||
  LOAD_SVM_MODEL(svm_, svm_xml_);
 | 
				
			||||
 | 
				
			||||
  if (test_file_list_.empty()) {
 | 
				
			||||
    this->prepare();
 | 
				
			||||
  }
 | 
				
			||||
 
 | 
				
			||||
  double count_all = test_file_list_.size();
 | 
				
			||||
  double ptrue_rtrue = 0;
 | 
				
			||||
  double ptrue_rfalse = 0;
 | 
				
			||||
  double pfalse_rtrue = 0;
 | 
				
			||||
  double pfalse_rfalse = 0;
 | 
				
			||||
 | 
				
			||||
  for (auto item : test_file_list_) {
 | 
				
			||||
    auto image = cv::imread(item.file);
 | 
				
			||||
    if (!image.data) {
 | 
				
			||||
      std::cout << "no" << std::endl;
 | 
				
			||||
      continue;
 | 
				
			||||
    }
 | 
				
			||||
    cv::Mat feature;
 | 
				
			||||
    extractFeature(image, feature);
 | 
				
			||||
 | 
				
			||||
    auto predict = int(svm_->predict(feature));
 | 
				
			||||
    //std::cout << "predict: " << predict << std::endl;
 | 
				
			||||
 | 
				
			||||
    auto real = item.label;
 | 
				
			||||
    if (predict == kForward && real == kForward) ptrue_rtrue++;
 | 
				
			||||
    if (predict == kForward && real == kInverse) ptrue_rfalse++;
 | 
				
			||||
    if (predict == kInverse && real == kForward) pfalse_rtrue++;
 | 
				
			||||
    if (predict == kInverse && real == kInverse) pfalse_rfalse++;
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  std::cout << "count_all: " << count_all << std::endl;
 | 
				
			||||
  std::cout << "ptrue_rtrue: " << ptrue_rtrue << std::endl;
 | 
				
			||||
  std::cout << "ptrue_rfalse: " << ptrue_rfalse << std::endl;
 | 
				
			||||
  std::cout << "pfalse_rtrue: " << pfalse_rtrue << std::endl;
 | 
				
			||||
  std::cout << "pfalse_rfalse: " << pfalse_rfalse << std::endl;
 | 
				
			||||
 | 
				
			||||
  double precise = 0;
 | 
				
			||||
  if (ptrue_rtrue + ptrue_rfalse != 0) {
 | 
				
			||||
    precise = ptrue_rtrue / (ptrue_rtrue + ptrue_rfalse);
 | 
				
			||||
    std::cout << "precise: " << precise << std::endl;
 | 
				
			||||
  } else {
 | 
				
			||||
    std::cout << "precise: "
 | 
				
			||||
              << "NA" << std::endl;
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  double recall = 0;
 | 
				
			||||
  if (ptrue_rtrue + pfalse_rtrue != 0) {
 | 
				
			||||
    recall = ptrue_rtrue / (ptrue_rtrue + pfalse_rtrue);
 | 
				
			||||
    std::cout << "recall: " << recall << std::endl;
 | 
				
			||||
  } else {
 | 
				
			||||
    std::cout << "recall: "
 | 
				
			||||
              << "NA" << std::endl;
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  double Fsocre = 0;
 | 
				
			||||
  if (precise + recall != 0) {
 | 
				
			||||
    Fsocre = 2 * (precise * recall) / (precise + recall);
 | 
				
			||||
    std::cout << "Fsocre: " << Fsocre << std::endl;
 | 
				
			||||
  } else {
 | 
				
			||||
    std::cout << "Fsocre: "
 | 
				
			||||
              << "NA" << std::endl;
 | 
				
			||||
  }
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void SvmTrain::prepare() {
 | 
				
			||||
  srand(unsigned(time(NULL)));
 | 
				
			||||
 | 
				
			||||
  char buffer[260] = {0};
 | 
				
			||||
 | 
				
			||||
  sprintf(buffer, "%s/has/train", plates_folder_);
 | 
				
			||||
  auto has_file_train_list = utils::getFiles(buffer);
 | 
				
			||||
  std::random_shuffle(has_file_train_list.begin(), has_file_train_list.end());
 | 
				
			||||
 | 
				
			||||
  sprintf(buffer, "%s/has/test", plates_folder_);
 | 
				
			||||
  auto has_file_test_list = utils::getFiles(buffer);
 | 
				
			||||
  std::random_shuffle(has_file_test_list.begin(), has_file_test_list.end());
 | 
				
			||||
 | 
				
			||||
  sprintf(buffer, "%s/no/train", plates_folder_);
 | 
				
			||||
  auto no_file_train_list = utils::getFiles(buffer);
 | 
				
			||||
  std::random_shuffle(no_file_train_list.begin(), no_file_train_list.end());
 | 
				
			||||
 | 
				
			||||
  sprintf(buffer, "%s/no/test", plates_folder_);
 | 
				
			||||
  auto no_file_test_list = utils::getFiles(buffer);
 | 
				
			||||
  std::random_shuffle(no_file_test_list.begin(), no_file_test_list.end());
 | 
				
			||||
 | 
				
			||||
  fprintf(stdout, ">> Collecting train data...\n");
 | 
				
			||||
 | 
				
			||||
  for (auto file : has_file_train_list)
 | 
				
			||||
    train_file_list_.push_back({ file, kForward });
 | 
				
			||||
 | 
				
			||||
  for (auto file : no_file_train_list)
 | 
				
			||||
    train_file_list_.push_back({ file, kInverse });
 | 
				
			||||
 | 
				
			||||
  fprintf(stdout, ">> Collecting test data...\n");
 | 
				
			||||
 | 
				
			||||
  for (auto file : has_file_test_list)
 | 
				
			||||
    test_file_list_.push_back({ file, kForward });
 | 
				
			||||
 | 
				
			||||
  for (auto file : no_file_test_list)
 | 
				
			||||
    test_file_list_.push_back({ file, kInverse });
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
cv::Ptr<cv::ml::TrainData> SvmTrain::tdata() {
 | 
				
			||||
  cv::Mat samples;
 | 
				
			||||
  std::vector<int> responses;
 | 
				
			||||
 | 
				
			||||
  for (auto f : train_file_list_) {
 | 
				
			||||
    auto image = cv::imread(f.file);
 | 
				
			||||
    if (!image.data) {
 | 
				
			||||
      fprintf(stdout, ">> Invalid image: %s  ignore.\n", f.file.c_str());
 | 
				
			||||
      continue;
 | 
				
			||||
    }
 | 
				
			||||
    cv::Mat feature;
 | 
				
			||||
    extractFeature(image, feature);
 | 
				
			||||
    feature = feature.reshape(1, 1);
 | 
				
			||||
 | 
				
			||||
    samples.push_back(feature);
 | 
				
			||||
    responses.push_back(int(f.label));
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  cv::Mat samples_, responses_;
 | 
				
			||||
  samples.convertTo(samples_, CV_32FC1);
 | 
				
			||||
  cv::Mat(responses).copyTo(responses_);
 | 
				
			||||
 | 
				
			||||
  return cv::ml::TrainData::create(samples_, cv::ml::SampleTypes::ROW_SAMPLE, responses_);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
}  // namespace easypr
 | 
				
			||||
@ -0,0 +1,8 @@
 | 
				
			||||
#include "easypr/train/train.h"
 | 
				
			||||
 | 
				
			||||
namespace easypr {
 | 
				
			||||
 | 
				
			||||
ITrain::ITrain() {}
 | 
				
			||||
 | 
				
			||||
ITrain::~ITrain() {}
 | 
				
			||||
}
 | 
				
			||||
@ -0,0 +1,81 @@
 | 
				
			||||
#include "easypr/util/kv.h"
 | 
				
			||||
#include "easypr/util/util.h"
 | 
				
			||||
 | 
				
			||||
namespace easypr {
 | 
				
			||||
 | 
				
			||||
Kv::Kv() { }
 | 
				
			||||
 | 
				
			||||
void Kv::load(const std::string &file) {
 | 
				
			||||
  this->clear();
 | 
				
			||||
  std::ifstream reader(file);
 | 
				
			||||
  assert(reader);
 | 
				
			||||
 | 
				
			||||
  if (reader.is_open()) {
 | 
				
			||||
    while (!reader.eof()) {
 | 
				
			||||
      std::string line;
 | 
				
			||||
      std::getline(reader, line);
 | 
				
			||||
      if (line.empty()) continue;
 | 
				
			||||
 | 
				
			||||
      const auto parse = [](const std::string &str) {
 | 
				
			||||
        std::string tmp, key, value;
 | 
				
			||||
        for (size_t i = 0, len = str.length(); i < len; ++i) {
 | 
				
			||||
          const char ch = str[i];
 | 
				
			||||
          if (ch == ' ') {
 | 
				
			||||
            if (i > 0 && str[i - 1] != ' ' && key.empty()) {
 | 
				
			||||
              key = tmp;
 | 
				
			||||
              tmp.clear();
 | 
				
			||||
            }
 | 
				
			||||
          }
 | 
				
			||||
          else {
 | 
				
			||||
            tmp.push_back(ch);
 | 
				
			||||
          }
 | 
				
			||||
          if (i == len - 1) {
 | 
				
			||||
            value = tmp;
 | 
				
			||||
          }
 | 
				
			||||
        }
 | 
				
			||||
        return std::make_pair(key, value);
 | 
				
			||||
      };
 | 
				
			||||
 | 
				
			||||
      auto kv = parse(line);
 | 
				
			||||
      this->add(kv.first, kv.second);
 | 
				
			||||
    }
 | 
				
			||||
    reader.close();
 | 
				
			||||
  }
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
std::string Kv::get(const std::string &key) {
 | 
				
			||||
  if (data_.find(key) == data_.end()) {
 | 
				
			||||
    std::cerr << "[Kv] cannot find " << key << std::endl;
 | 
				
			||||
    return "";
 | 
				
			||||
  }
 | 
				
			||||
  return data_.at(key);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void Kv::add(const std::string &key, const std::string &value) {
 | 
				
			||||
  if (data_.find(key) != data_.end()) {
 | 
				
			||||
    fprintf(stderr,
 | 
				
			||||
            "[Kv] find duplicate: %s = %s , ignore\n",
 | 
				
			||||
            key.c_str(),
 | 
				
			||||
            value.c_str());
 | 
				
			||||
  } else {
 | 
				
			||||
    std::string v(value);
 | 
				
			||||
#ifdef OS_WINDOWS
 | 
				
			||||
    v = utils::utf8_to_gbk(value.c_str());
 | 
				
			||||
#endif
 | 
				
			||||
    data_[key] = v;
 | 
				
			||||
  }
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void Kv::remove(const std::string &key) {
 | 
				
			||||
  if (data_.find(key) == data_.end()) {
 | 
				
			||||
    std::cerr << "[Kv] cannot find " << key << std::endl;
 | 
				
			||||
    return;
 | 
				
			||||
  }
 | 
				
			||||
  data_.erase(key);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void Kv::clear() {
 | 
				
			||||
  data_.clear();
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
}
 | 
				
			||||
@ -0,0 +1,540 @@
 | 
				
			||||
#include "easypr/util/program_options.h"
 | 
				
			||||
 | 
				
			||||
namespace program_options {
 | 
				
			||||
 | 
				
			||||
// class ParseError
 | 
				
			||||
 | 
				
			||||
ParseError::ParseError(const std::string& msg) : _msg(msg) {}
 | 
				
			||||
 | 
				
			||||
const char* ParseError::what() const throw() {
 | 
				
			||||
  std::string msg;
 | 
				
			||||
  msg.append("Command line parse error: ").append(_msg).push_back('.');
 | 
				
			||||
  return msg.c_str();
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
ParseError::~ParseError() throw() {}
 | 
				
			||||
 | 
				
			||||
// class Generator
 | 
				
			||||
 | 
				
			||||
Generator::Generator() : parser_(nullptr) {
 | 
				
			||||
  current_subroutine_ = Subroutine::get_default_name();
 | 
				
			||||
  add_subroutine(current_subroutine_.c_str());
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
Generator::~Generator() {
 | 
				
			||||
  if (parser_) {
 | 
				
			||||
    delete parser_;
 | 
				
			||||
    parser_ = nullptr;
 | 
				
			||||
  }
 | 
				
			||||
  for (auto it = subroutines_.begin(); it != subroutines_.end(); ++it) {
 | 
				
			||||
    if (it->second) {
 | 
				
			||||
      delete it->second;
 | 
				
			||||
      it->second = nullptr;
 | 
				
			||||
    }
 | 
				
			||||
  }
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
Generator& Generator::make_usage(const char* first_line) {
 | 
				
			||||
  get_subroutine()->set_first_line(first_line);
 | 
				
			||||
  return *this;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
Parser* Generator::make_parser() {
 | 
				
			||||
  if (parser_) delete parser_;
 | 
				
			||||
  parser_ = new Parser;
 | 
				
			||||
  parser_->set_usage_subroutines(&subroutines_);
 | 
				
			||||
  return parser_;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
Generator& Generator::add_subroutine(const char* name) {
 | 
				
			||||
  add_subroutine(name, "");
 | 
				
			||||
  return *this;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
Generator& Generator::add_subroutine(const char* name,
 | 
				
			||||
                                     const char* description) {
 | 
				
			||||
  if (subroutines_.find(name) == subroutines_.end()) {
 | 
				
			||||
    // a new subroutine
 | 
				
			||||
    current_subroutine_ = name;
 | 
				
			||||
    Subroutine* routine = new Subroutine(name, description);
 | 
				
			||||
    subroutines_.insert({current_subroutine_, routine});
 | 
				
			||||
  }
 | 
				
			||||
  return *this;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
std::map<std::string, std::string> Generator::get_subroutine_list() {
 | 
				
			||||
  std::map<std::string, std::string> kv;
 | 
				
			||||
  for (auto pr : subroutines_) {
 | 
				
			||||
    Subroutine* subroutine = pr.second;
 | 
				
			||||
    if (subroutine->get_name() != Subroutine::get_default_name())
 | 
				
			||||
      kv[subroutine->get_name()] = subroutine->get_description();
 | 
				
			||||
  }
 | 
				
			||||
  return std::move(kv);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
bool Generator::add_usage_line(const char* option, const char* default_value,
 | 
				
			||||
                               const char* description) {
 | 
				
			||||
  std::string option_str(option);
 | 
				
			||||
  auto delimiter_pos = option_str.find(kDelimiter);
 | 
				
			||||
 | 
				
			||||
  std::string option_short;
 | 
				
			||||
  std::string option_long;
 | 
				
			||||
 | 
				
			||||
  if (delimiter_pos != std::string::npos) {
 | 
				
			||||
    option_short.assign(std::move(option_str.substr(0, delimiter_pos)));
 | 
				
			||||
    option_long.assign(std::move(option_str.substr(delimiter_pos + 1)));
 | 
				
			||||
 | 
				
			||||
    Row row;
 | 
				
			||||
    row.oshort(option_short);
 | 
				
			||||
    row.olong(option_long);
 | 
				
			||||
    row.value(default_value);
 | 
				
			||||
    row.desc(description);
 | 
				
			||||
 | 
				
			||||
    get_subroutine()->add_usage_line(row);
 | 
				
			||||
    return true;
 | 
				
			||||
  }
 | 
				
			||||
  return false;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
std::ostream& operator<<(std::ostream& out, Generator& generator) {
 | 
				
			||||
  for (auto pr : generator.subroutines_) {
 | 
				
			||||
    Subroutine* subroutine = pr.second;
 | 
				
			||||
    if (subroutine->get_name() != Subroutine::get_default_name()) {
 | 
				
			||||
      out << subroutine->get_name() << "\t";
 | 
				
			||||
    }
 | 
				
			||||
    out << subroutine->get_description();
 | 
				
			||||
    if (!subroutine->get_usage().empty()) {
 | 
				
			||||
      out << std::endl;
 | 
				
			||||
    }
 | 
				
			||||
    out << *subroutine;
 | 
				
			||||
  }
 | 
				
			||||
  return out;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
// class ParseItem
 | 
				
			||||
 | 
				
			||||
ParseItem::ParseItem(const std::string& value) : value_(value) {}
 | 
				
			||||
 | 
				
			||||
// class Parser
 | 
				
			||||
 | 
				
			||||
ParseItem* Parser::get(const std::string& key) {
 | 
				
			||||
  if (pr_->find(key) != pr_->end()) {
 | 
				
			||||
    return (*pr_)[key];
 | 
				
			||||
  }
 | 
				
			||||
  return nullptr;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
Parser::Parser() : subroutines_(nullptr), pr_(nullptr) {}
 | 
				
			||||
 | 
				
			||||
Parser::~Parser() { this->cleanup(); }
 | 
				
			||||
 | 
				
			||||
Parser::ParseResult* Parser::parse(const int argc, const char** argv) {
 | 
				
			||||
  if (!this->init(argc, argv)) {
 | 
				
			||||
    return nullptr;
 | 
				
			||||
  }
 | 
				
			||||
  auto ibegin = args_.begin() + 1;  // ignore the first cmd name
 | 
				
			||||
  auto iend = args_.end();
 | 
				
			||||
  auto it = ibegin;
 | 
				
			||||
 | 
				
			||||
  if (argc >= 2 && args_[1][0] != '-') {
 | 
				
			||||
    // the second block may be a subroutine name
 | 
				
			||||
    // e.g., ./exec pull --option
 | 
				
			||||
    if (subroutines_ && (subroutines_->find(args_[1]) != subroutines_->end())) {
 | 
				
			||||
      subroutine_name_ = args_[1];
 | 
				
			||||
      it++;  // ignore the subroutine name
 | 
				
			||||
    } else {
 | 
				
			||||
      subroutine_name_ = args_[1];
 | 
				
			||||
    }
 | 
				
			||||
  } else {
 | 
				
			||||
    // there is no options as well as subroutine name
 | 
				
			||||
    // e.g., ./exec
 | 
				
			||||
    subroutine_name_ = Subroutine::get_default_name();
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  std::string block;
 | 
				
			||||
  std::string previous(*ibegin);
 | 
				
			||||
 | 
				
			||||
  for (; it != iend; ++it) {
 | 
				
			||||
    block.assign(*it);
 | 
				
			||||
 | 
				
			||||
    switch (block.size()) {
 | 
				
			||||
      case 1:
 | 
				
			||||
        if (block == "-") {
 | 
				
			||||
          throw ParseError("single '-' is not allowed");
 | 
				
			||||
        }
 | 
				
			||||
        break;
 | 
				
			||||
      case 2:
 | 
				
			||||
        if (block[0] == '-') {
 | 
				
			||||
          if (block[1] == '-') {
 | 
				
			||||
            throw ParseError("option '--' is incomplete");
 | 
				
			||||
          } else if (block[1] == '=') {
 | 
				
			||||
            throw ParseError("option '-=' is invalid");
 | 
				
			||||
          } else {
 | 
				
			||||
            // single option
 | 
				
			||||
            // e.g., ./exec -s
 | 
				
			||||
            (*pr_)[block.substr(1)] = nullptr;
 | 
				
			||||
          }
 | 
				
			||||
        }
 | 
				
			||||
        break;
 | 
				
			||||
      default:  // >=3
 | 
				
			||||
        if (block[0] == '-') {
 | 
				
			||||
          if (block[1] == '-') {
 | 
				
			||||
            size_t pos_equal = block.find('=');
 | 
				
			||||
            if (pos_equal == std::string::npos) {
 | 
				
			||||
              // a long format option
 | 
				
			||||
              // e.g., ./exec --option
 | 
				
			||||
              (*pr_)[block.substr(2)] = nullptr;
 | 
				
			||||
            } else {
 | 
				
			||||
              if (pos_equal > 3) {
 | 
				
			||||
                // e.g, ./exec --op[..=]value
 | 
				
			||||
                std::string key(block.substr(2, pos_equal - 2));
 | 
				
			||||
                if (block.size() > 5)
 | 
				
			||||
                  // e.g, ./exec --op=v
 | 
				
			||||
                  (*pr_)[key] = new ParseItem(block.substr(pos_equal + 1));
 | 
				
			||||
                else
 | 
				
			||||
                  (*pr_)[key] = nullptr;
 | 
				
			||||
              } else {
 | 
				
			||||
                // a long format option but = is illegal
 | 
				
			||||
                // e.g., ./exec --o=[...]
 | 
				
			||||
                (*pr_)[block.substr(2)] = nullptr;
 | 
				
			||||
              }
 | 
				
			||||
            }
 | 
				
			||||
          } else if (block[2] == '=') {
 | 
				
			||||
            // a single option with =
 | 
				
			||||
            // e.g., ./exec -o=[...]
 | 
				
			||||
            std::string key;
 | 
				
			||||
            key.push_back(block[1]);
 | 
				
			||||
            if (block.size() > 3)
 | 
				
			||||
              (*pr_)[key] = new ParseItem(block.substr(3));
 | 
				
			||||
            else
 | 
				
			||||
              (*pr_)[key] = nullptr;
 | 
				
			||||
          } else {
 | 
				
			||||
            // a combination options
 | 
				
			||||
            // e.g., ./exec -ab[...]
 | 
				
			||||
            auto tbegin = block.begin() + 1;  // ignore the first '-'
 | 
				
			||||
            auto tend = block.end();
 | 
				
			||||
            auto t = tbegin;
 | 
				
			||||
 | 
				
			||||
            for (; t != tend; ++t) {
 | 
				
			||||
              std::string key;
 | 
				
			||||
              key.push_back(*t);
 | 
				
			||||
              (*pr_)[key] = nullptr;
 | 
				
			||||
            }
 | 
				
			||||
          }
 | 
				
			||||
        }
 | 
				
			||||
        break;
 | 
				
			||||
    }  // switch
 | 
				
			||||
 | 
				
			||||
    if (block[0] != '-' && previous != block  // not the first option
 | 
				
			||||
        ) {
 | 
				
			||||
      if (previous[0] != '-') {
 | 
				
			||||
        // previous is not an option, error occur
 | 
				
			||||
        // e.g., ./exec abc def
 | 
				
			||||
        throw ParseError("'" + block + "' is not allowed here");
 | 
				
			||||
      }
 | 
				
			||||
 | 
				
			||||
      std::string key;
 | 
				
			||||
 | 
				
			||||
      if (previous[0] == '-' && previous[1] == '-') {
 | 
				
			||||
        // previous is a long format option.
 | 
				
			||||
        // e.g., ./exec --option value
 | 
				
			||||
        key = previous.substr(2);
 | 
				
			||||
      } else {
 | 
				
			||||
        // it's the value of previous option.
 | 
				
			||||
        // e.g., ./exec -o   [...]
 | 
				
			||||
        // e.g., ./exec -opq [...]
 | 
				
			||||
        key.push_back(*(previous.end() - 1));
 | 
				
			||||
      }
 | 
				
			||||
 | 
				
			||||
      if (pr_->find(key) != pr_->end()) {
 | 
				
			||||
        (*pr_)[key] = new ParseItem(block);
 | 
				
			||||
      }
 | 
				
			||||
    }
 | 
				
			||||
 | 
				
			||||
    previous = block;
 | 
				
			||||
  }  // for
 | 
				
			||||
 | 
				
			||||
  if (subroutines_) {
 | 
				
			||||
    this->set_addition();
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  return pr_;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
Parser::ParseResult* Parser::parse(const char* command_line) {
 | 
				
			||||
  int i = 0;
 | 
				
			||||
  std::string block;
 | 
				
			||||
  std::vector<std::string> blocks;
 | 
				
			||||
  char c;
 | 
				
			||||
  while ((c = command_line[i++]) != '\0') {
 | 
				
			||||
    if (c != ' ') {
 | 
				
			||||
      block.push_back(c);
 | 
				
			||||
    } else {
 | 
				
			||||
      if (!block.empty()) {
 | 
				
			||||
        blocks.push_back(block);
 | 
				
			||||
      }
 | 
				
			||||
      block.clear();
 | 
				
			||||
    }
 | 
				
			||||
  }
 | 
				
			||||
  if (!block.empty()) {
 | 
				
			||||
    blocks.push_back(block);
 | 
				
			||||
  }
 | 
				
			||||
  size_t size = blocks.size();  // argc
 | 
				
			||||
  char** argv = new char*[size];
 | 
				
			||||
  i = 0;
 | 
				
			||||
  std::for_each(blocks.begin(), blocks.end(), [argv, &i](const std::string& b) {
 | 
				
			||||
    argv[i++] = const_cast<char*>(b.c_str());
 | 
				
			||||
  });
 | 
				
			||||
  auto pr =
 | 
				
			||||
      this->parse(static_cast<const int>(size), const_cast<const char**>(argv));
 | 
				
			||||
 | 
				
			||||
  delete[] argv;
 | 
				
			||||
  argv = nullptr;
 | 
				
			||||
 | 
				
			||||
  return pr;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
bool Parser::has(const char* key) {
 | 
				
			||||
  std::string skey(key);
 | 
				
			||||
 | 
				
			||||
  if (pr_ && !pr_->empty() && !skey.empty()) {
 | 
				
			||||
    if (skey[0] == '-') {
 | 
				
			||||
      // check combination options, e.g., Parser::has("-xyz")
 | 
				
			||||
      for (size_t i = 1; i < skey.size(); ++i) {
 | 
				
			||||
        std::string tkey;
 | 
				
			||||
        tkey.push_back(skey[i]);
 | 
				
			||||
        if (pr_->find(tkey) == pr_->end()) {
 | 
				
			||||
          return false;
 | 
				
			||||
        }
 | 
				
			||||
      }
 | 
				
			||||
      return true;
 | 
				
			||||
    } else {
 | 
				
			||||
      // check single option, e.g., Parser::has("x")
 | 
				
			||||
      return pr_->find(skey) != pr_->end();
 | 
				
			||||
    }
 | 
				
			||||
  }
 | 
				
			||||
  return false;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
bool Parser::has_or(std::initializer_list<const char*> options) {
 | 
				
			||||
  if (options.size() == 0) {
 | 
				
			||||
    return false;
 | 
				
			||||
  }
 | 
				
			||||
  for (auto key : options) {
 | 
				
			||||
    if (this->has(key)) return true;
 | 
				
			||||
  }
 | 
				
			||||
  return false;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
bool Parser::has_and(std::initializer_list<const char*> options) {
 | 
				
			||||
  if (options.size() == 0) {
 | 
				
			||||
    return false;
 | 
				
			||||
  }
 | 
				
			||||
  for (auto key : options) {
 | 
				
			||||
    if (!this->has(key)) return false;
 | 
				
			||||
  }
 | 
				
			||||
  return true;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
bool Parser::init(const int argc, const char** argv) {
 | 
				
			||||
  argc_ = argc;
 | 
				
			||||
  // argv_ = argv;
 | 
				
			||||
  // don't save it, point to a local var in parse(const char* command_line).
 | 
				
			||||
  // use member var args_ instead.
 | 
				
			||||
  if (argc > 0) {
 | 
				
			||||
    this->cleanup();
 | 
				
			||||
 | 
				
			||||
    args_.reserve(static_cast<size_t>(argc_));
 | 
				
			||||
 | 
				
			||||
    for (int i = 0; i < argc_; ++i) {
 | 
				
			||||
      args_.push_back(argv[i]);
 | 
				
			||||
    }
 | 
				
			||||
 | 
				
			||||
    pr_ = new Parser::ParseResult;
 | 
				
			||||
    return true;
 | 
				
			||||
  }
 | 
				
			||||
  return false;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void Parser::cleanup() {
 | 
				
			||||
  args_.clear();
 | 
				
			||||
  if (pr_) {
 | 
				
			||||
    auto ibegin = pr_->begin();
 | 
				
			||||
    auto iend = pr_->end();
 | 
				
			||||
    auto it = ibegin;
 | 
				
			||||
    for (; it != iend; ++it) {
 | 
				
			||||
      ParseItem* item = it->second;
 | 
				
			||||
      if (item) delete item;
 | 
				
			||||
    }
 | 
				
			||||
    delete pr_;
 | 
				
			||||
    pr_ = nullptr;
 | 
				
			||||
  }
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void Parser::set_addition() {
 | 
				
			||||
  if (subroutines_->find(subroutine_name_) != subroutines_->end()) {
 | 
				
			||||
    for (const Row& row : *(subroutines_->at(subroutine_name_))) {
 | 
				
			||||
      // assume both -o and --option are allowed,
 | 
				
			||||
      // but only provide -o,
 | 
				
			||||
      // then set the another --option.
 | 
				
			||||
      // vice versa.
 | 
				
			||||
      const std::string& def = row.value();
 | 
				
			||||
      const std::string& ops = row.oshort();
 | 
				
			||||
      const std::string& opl = row.olong();
 | 
				
			||||
      ParseResult& pr = *pr_;
 | 
				
			||||
 | 
				
			||||
      bool has_short = this->has(ops.c_str());
 | 
				
			||||
      bool has_long = this->has(opl.c_str());
 | 
				
			||||
 | 
				
			||||
      // assume -o [ --option ] arg = 1
 | 
				
			||||
      // but not provide option value,
 | 
				
			||||
      // then set to default 1.
 | 
				
			||||
      // otherwise, both set to user defined value
 | 
				
			||||
 | 
				
			||||
      if (!ops.empty()) {
 | 
				
			||||
        if (has_short) {
 | 
				
			||||
          if (pr[ops] != nullptr && !opl.empty()) {
 | 
				
			||||
            pr[opl] = new ParseItem(std::move(pr[ops]->val()));
 | 
				
			||||
          } else if (pr[ops] == nullptr && !def.empty()) {
 | 
				
			||||
            pr[ops] = new ParseItem(std::move(def));
 | 
				
			||||
            if (!opl.empty()) pr[opl] = new ParseItem(std::move(def));
 | 
				
			||||
          } else {
 | 
				
			||||
            pr[opl] = nullptr;
 | 
				
			||||
          }
 | 
				
			||||
        }
 | 
				
			||||
      }
 | 
				
			||||
 | 
				
			||||
      if (!opl.empty()) {
 | 
				
			||||
        if (has_long) {
 | 
				
			||||
          if (pr[opl] != nullptr && !ops.empty()) {
 | 
				
			||||
            pr[ops] = new ParseItem(std::move(pr[opl]->val()));
 | 
				
			||||
          } else if (pr[opl] == nullptr && !def.empty()) {
 | 
				
			||||
            if (!ops.empty()) pr[ops] = new ParseItem(std::move(def));
 | 
				
			||||
            pr[opl] = new ParseItem(std::move(def));
 | 
				
			||||
          } else {
 | 
				
			||||
            pr[ops] = nullptr;
 | 
				
			||||
          }
 | 
				
			||||
        }
 | 
				
			||||
      }
 | 
				
			||||
 | 
				
			||||
      if (!has_long && !has_short && !def.empty()) {
 | 
				
			||||
        if (!opl.empty()) pr[opl] = new ParseItem(std::move(def));
 | 
				
			||||
        if (!ops.empty()) pr[ops] = new ParseItem(std::move(def));
 | 
				
			||||
      }
 | 
				
			||||
    }  // for
 | 
				
			||||
  }    // if
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
// class Row
 | 
				
			||||
 | 
				
			||||
Row::Row() : require_value(true) {}
 | 
				
			||||
 | 
				
			||||
// class Subroutine
 | 
				
			||||
 | 
				
			||||
Subroutine::Subroutine() : first_line_("") {}
 | 
				
			||||
 | 
				
			||||
Subroutine::Subroutine(const char* name, const char* description)
 | 
				
			||||
    : first_line_(""), description_(description), name_(name) {
 | 
				
			||||
  usages_.reserve(5);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void Subroutine::print_with_row(std::ostream& out) {
 | 
				
			||||
  // print the subroutine name and its description
 | 
				
			||||
  if (strcmp(get_first_line(), "") != 0) {
 | 
				
			||||
    // print the first line
 | 
				
			||||
    out << get_first_line();
 | 
				
			||||
    if (!usages_.empty()) {
 | 
				
			||||
      out << std::endl;
 | 
				
			||||
    }
 | 
				
			||||
  }
 | 
				
			||||
  auto begin = usages_.begin();
 | 
				
			||||
  auto end = usages_.end();
 | 
				
			||||
 | 
				
			||||
  std::vector<std::string> row_list;
 | 
				
			||||
  row_list.reserve(usages_.size());
 | 
				
			||||
 | 
				
			||||
  // build usage rows without description field,
 | 
				
			||||
  // find the max-len row at the same time.
 | 
				
			||||
  size_t max_len = 0;
 | 
				
			||||
  std::for_each(begin, end, [&max_len, &row_list](const Row& row) {
 | 
				
			||||
    std::stringstream ss;
 | 
				
			||||
    ss << "  ";
 | 
				
			||||
    if (!row.oshort().empty()) {
 | 
				
			||||
      ss << "-" << row.oshort() << " ";
 | 
				
			||||
    }
 | 
				
			||||
    if (!row.olong().empty()) {
 | 
				
			||||
      if (!row.oshort().empty())
 | 
				
			||||
        ss << "[ --" << row.olong() << " ] ";
 | 
				
			||||
      else
 | 
				
			||||
        ss << "--" << row.olong() << " ";
 | 
				
			||||
    }
 | 
				
			||||
 | 
				
			||||
    if (row.required()) {
 | 
				
			||||
      ss << "arg ";
 | 
				
			||||
      if (!row.value().empty()) {
 | 
				
			||||
        ss << "= " << row.value() << " ";
 | 
				
			||||
      }
 | 
				
			||||
    }
 | 
				
			||||
 | 
				
			||||
    max_len = std::max(max_len, ss.str().size());
 | 
				
			||||
    row_list.push_back(std::move(ss.str()));
 | 
				
			||||
  });
 | 
				
			||||
 | 
				
			||||
  // show all rows and align description field
 | 
				
			||||
  size_t row_count = usages_.size();
 | 
				
			||||
  for (size_t i = 0; i < row_count; ++i) {
 | 
				
			||||
    std::string str_row(std::move(row_list[i]));
 | 
				
			||||
    // print row without description
 | 
				
			||||
    out << str_row;
 | 
				
			||||
    // print spaces
 | 
				
			||||
    size_t spaces = 0;
 | 
				
			||||
    size_t len = str_row.size();
 | 
				
			||||
    if (max_len > len) spaces = max_len - len;
 | 
				
			||||
 | 
				
			||||
    while (spaces--) {
 | 
				
			||||
      out << " ";
 | 
				
			||||
    }
 | 
				
			||||
    // print description
 | 
				
			||||
    out << usages_.at(i).desc() << std::endl;
 | 
				
			||||
  }
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
void Subroutine::print_with_template(std::ostream& out) {
 | 
				
			||||
  for (auto usage : usages_) {
 | 
				
			||||
    size_t i = 0;
 | 
				
			||||
    for (auto t = template_str_.begin(); t != template_str_.end(); ++t) {
 | 
				
			||||
      if (*t == '%') {
 | 
				
			||||
        switch (*(order_.begin() + i)) {
 | 
				
			||||
          case Row::kShort:
 | 
				
			||||
            out << usage.oshort();
 | 
				
			||||
            break;
 | 
				
			||||
          case Row::kLong:
 | 
				
			||||
            out << usage.olong();
 | 
				
			||||
            break;
 | 
				
			||||
          case Row::kDefault:
 | 
				
			||||
            out << usage.value();
 | 
				
			||||
            break;
 | 
				
			||||
          case Row::kDescription:
 | 
				
			||||
            out << usage.desc();
 | 
				
			||||
            break;
 | 
				
			||||
          default:
 | 
				
			||||
            break;
 | 
				
			||||
        }
 | 
				
			||||
        ++i;
 | 
				
			||||
      } else {
 | 
				
			||||
        out << *t;
 | 
				
			||||
      }  // if %
 | 
				
			||||
    }    // for template_str_
 | 
				
			||||
    out << std::endl;
 | 
				
			||||
  }  // for usages_
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
std::ostream& operator<<(std::ostream& out, Subroutine& subroutine) {
 | 
				
			||||
  if (subroutine.template_str_.empty()) {
 | 
				
			||||
    subroutine.print_with_row(out);
 | 
				
			||||
  } else {
 | 
				
			||||
    subroutine.print_with_template(out);
 | 
				
			||||
  }
 | 
				
			||||
  return out;
 | 
				
			||||
}
 | 
				
			||||
}
 | 
				
			||||
@ -0,0 +1,293 @@
 | 
				
			||||
#include "easypr/util/util.h"
 | 
				
			||||
#include <string>
 | 
				
			||||
 | 
				
			||||
#ifdef OS_WINDOWS
 | 
				
			||||
#include <windows.h>
 | 
				
			||||
#include <direct.h>
 | 
				
			||||
#include <io.h>
 | 
				
			||||
#define PATH_DELIMITER '\\'
 | 
				
			||||
#ifdef min
 | 
				
			||||
#undef min
 | 
				
			||||
#endif
 | 
				
			||||
 | 
				
			||||
#ifdef max
 | 
				
			||||
#undef max
 | 
				
			||||
#endif
 | 
				
			||||
#elif defined(OS_LINUX) || defined(OS_UNIX)
 | 
				
			||||
 | 
				
			||||
#include <cstring>
 | 
				
			||||
#include <dirent.h>
 | 
				
			||||
#include <sys/stat.h>
 | 
				
			||||
#include <unistd.h>
 | 
				
			||||
 | 
				
			||||
#define PATH_DELIMITER '/'
 | 
				
			||||
#endif
 | 
				
			||||
 | 
				
			||||
#ifdef OS_UNIX
 | 
				
			||||
 | 
				
			||||
#include <sys/timeb.h>
 | 
				
			||||
 | 
				
			||||
#endif
 | 
				
			||||
 | 
				
			||||
#include <list>
 | 
				
			||||
#include <opencv2/highgui/highgui.hpp>
 | 
				
			||||
 | 
				
			||||
namespace easypr {
 | 
				
			||||
 | 
				
			||||
long Utils::getTimestamp() {
 | 
				
			||||
#ifdef OS_WINDOWS
 | 
				
			||||
  return static_cast<long>(cv::getTickCount());
 | 
				
			||||
#endif
 | 
				
			||||
 | 
				
			||||
#ifdef OS_LINUX
 | 
				
			||||
  struct timespec ts;
 | 
				
			||||
 | 
				
			||||
  clock_gettime(CLOCK_MONOTONIC, &ts);
 | 
				
			||||
 | 
				
			||||
  return (ts.tv_sec * 1e3 + ts.tv_nsec / 1e6);
 | 
				
			||||
#endif
 | 
				
			||||
 | 
				
			||||
#ifdef OS_UNIX
 | 
				
			||||
  // there is no function provided by osx to get system tick count.
 | 
				
			||||
  // but considering the purpose by using this function,
 | 
				
			||||
  // we can simply return a millisecond since 1970/1/1 to calc the time elapse.
 | 
				
			||||
  struct timeb tb;
 | 
				
			||||
  ftime(&tb);
 | 
				
			||||
  return long(tb.time * 1e3 + tb.millitm);
 | 
				
			||||
#endif
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
std::string Utils::getFileName(const std::string &path,
 | 
				
			||||
                               const bool postfix /* = false */) {
 | 
				
			||||
  if (!path.empty()) {
 | 
				
			||||
    size_t last_slash = utils::get_last_slash(path);
 | 
				
			||||
    size_t last_dot = path.find_last_of('.');
 | 
				
			||||
 | 
				
			||||
    if (last_dot < last_slash || last_dot == std::string::npos) {
 | 
				
			||||
      // not found the right dot of the postfix,
 | 
				
			||||
      // return the file name directly
 | 
				
			||||
      return path.substr(last_slash + 1);
 | 
				
			||||
    } else {
 | 
				
			||||
      // the path has a postfix
 | 
				
			||||
      if (postfix) {
 | 
				
			||||
        // return the file name including postfix
 | 
				
			||||
        return path.substr(last_slash + 1);
 | 
				
			||||
      }
 | 
				
			||||
      // without postfix
 | 
				
			||||
      return path.substr(last_slash + 1, last_dot - last_slash - 1);
 | 
				
			||||
    }
 | 
				
			||||
  }
 | 
				
			||||
  return "";
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
std::vector<std::string> Utils::splitString(const std::string &str,
 | 
				
			||||
                                            const char delimiter) {
 | 
				
			||||
  std::vector<std::string> splited;
 | 
				
			||||
  std::string s(str);
 | 
				
			||||
  size_t pos;
 | 
				
			||||
 | 
				
			||||
  while ((pos = s.find(delimiter)) != std::string::npos) {
 | 
				
			||||
    std::string sec = s.substr(0, pos);
 | 
				
			||||
 | 
				
			||||
    if (!sec.empty()) {
 | 
				
			||||
      splited.push_back(s.substr(0, pos));
 | 
				
			||||
    }
 | 
				
			||||
 | 
				
			||||
    s = s.substr(pos + 1);
 | 
				
			||||
  }
 | 
				
			||||
 | 
				
			||||
  splited.push_back(s);
 | 
				
			||||
 | 
				
			||||
  return splited;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
std::vector<std::string> Utils::getFiles(const std::string &folder,
 | 
				
			||||
                                         const bool all /* = true */) {
 | 
				
			||||
  std::vector<std::string> files;
 | 
				
			||||
  std::list<std::string> subfolders;
 | 
				
			||||
  subfolders.push_back(folder);
 | 
				
			||||
#ifdef OS_WINDOWS
 | 
				
			||||
  while (!subfolders.empty()) {
 | 
				
			||||
    std::string current_folder(subfolders.back());
 | 
				
			||||
 | 
				
			||||
    if (*(current_folder.end() - 1) != '/') {
 | 
				
			||||
      current_folder.append("/*");
 | 
				
			||||
    } else {
 | 
				
			||||
      current_folder.append("*");
 | 
				
			||||
    }
 | 
				
			||||
 | 
				
			||||
    subfolders.pop_back();
 | 
				
			||||
 | 
				
			||||
    struct _finddata_t file_info;
 | 
				
			||||
    auto file_handler = _findfirst(current_folder.c_str(), &file_info);
 | 
				
			||||
 | 
				
			||||
    while (file_handler != -1) {
 | 
				
			||||
      if (all &&
 | 
				
			||||
          (!strcmp(file_info.name, ".") || !strcmp(file_info.name, ".."))) {
 | 
				
			||||
        if (_findnext(file_handler, &file_info) != 0) break;
 | 
				
			||||
        continue;
 | 
				
			||||
      }
 | 
				
			||||
 | 
				
			||||
      if (file_info.attrib & _A_SUBDIR) {
 | 
				
			||||
        // it's a sub folder
 | 
				
			||||
        if (all) {
 | 
				
			||||
          // will search sub folder
 | 
				
			||||
          std::string folder(current_folder);
 | 
				
			||||
          folder.pop_back();
 | 
				
			||||
          folder.append(file_info.name);
 | 
				
			||||
 | 
				
			||||
          subfolders.push_back(folder.c_str());
 | 
				
			||||
        }
 | 
				
			||||
      } else {
 | 
				
			||||
        // it's a file
 | 
				
			||||
        std::string file_path;
 | 
				
			||||
        // current_folder.pop_back();
 | 
				
			||||
        file_path.assign(current_folder.c_str()).pop_back();
 | 
				
			||||
        file_path.append(file_info.name);
 | 
				
			||||
 | 
				
			||||
        files.push_back(file_path);
 | 
				
			||||
      }
 | 
				
			||||
 | 
				
			||||
      if (_findnext(file_handler, &file_info) != 0) break;
 | 
				
			||||
    }  // while
 | 
				
			||||
    _findclose(file_handler);
 | 
				
			||||
  }
 | 
				
			||||
#elif defined(OS_LINUX) || defined(OS_UNIX)
 | 
				
			||||
  while (!subfolders.empty()) {
 | 
				
			||||
    std::string current_folder(subfolders.back());
 | 
				
			||||
 | 
				
			||||
    if (*(current_folder.end() - 1) != '/') {
 | 
				
			||||
      current_folder.push_back('/');
 | 
				
			||||
    }
 | 
				
			||||
 | 
				
			||||
    DIR* pdir = opendir(current_folder.c_str());
 | 
				
			||||
 | 
				
			||||
    subfolders.pop_back();
 | 
				
			||||
 | 
				
			||||
    if (!pdir) {
 | 
				
			||||
      continue;
 | 
				
			||||
    }
 | 
				
			||||
 | 
				
			||||
    dirent* dir = NULL;
 | 
				
			||||
 | 
				
			||||
    while ((dir = readdir(pdir)) != NULL) {
 | 
				
			||||
      // iterates the current folder, search file & sub folder
 | 
				
			||||
      struct stat st;
 | 
				
			||||
 | 
				
			||||
      if (all && (!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, ".."))) {
 | 
				
			||||
        // must ignore . & ..
 | 
				
			||||
        continue;
 | 
				
			||||
      }
 | 
				
			||||
 | 
				
			||||
      if (!strcmp(dir->d_name, ".DS_Store")) {
 | 
				
			||||
        // in OSX, 'finder' will create .DS_Store
 | 
				
			||||
        continue;
 | 
				
			||||
      }
 | 
				
			||||
 | 
				
			||||
      std::string file_path;
 | 
				
			||||
 | 
				
			||||
      file_path.append(current_folder.c_str());
 | 
				
			||||
      file_path.append(dir->d_name);
 | 
				
			||||
 | 
				
			||||
      if (lstat(file_path.c_str(), &st) < 0) {
 | 
				
			||||
        // perror("lstat");
 | 
				
			||||
        continue;
 | 
				
			||||
      }
 | 
				
			||||
 | 
				
			||||
      if (S_ISDIR(st.st_mode)) {
 | 
				
			||||
        // it's a sub folder
 | 
				
			||||
        if (all) {
 | 
				
			||||
          // will search sub folder
 | 
				
			||||
          std::string subfolder(current_folder);
 | 
				
			||||
          subfolder.append(dir->d_name);
 | 
				
			||||
 | 
				
			||||
          subfolders.push_back(subfolder.c_str());
 | 
				
			||||
        }
 | 
				
			||||
      } else {
 | 
				
			||||
        // it's a file
 | 
				
			||||
        files.push_back(file_path);
 | 
				
			||||
      }
 | 
				
			||||
    }  // while
 | 
				
			||||
    closedir(pdir);
 | 
				
			||||
  }
 | 
				
			||||
#endif
 | 
				
			||||
  return files;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
bool Utils::mkdir(const std::string folder) {
 | 
				
			||||
  std::string folder_builder;
 | 
				
			||||
  std::string sub;
 | 
				
			||||
  sub.reserve(folder.size());
 | 
				
			||||
  for (auto it = folder.begin(); it != folder.end(); ++it) {
 | 
				
			||||
    const char c = *it;
 | 
				
			||||
    sub.push_back(c);
 | 
				
			||||
    if (c == PATH_DELIMITER || it == folder.end() - 1) {
 | 
				
			||||
      folder_builder.append(sub);
 | 
				
			||||
#ifdef OS_WINDOWS
 | 
				
			||||
      if (0 != ::_access(folder_builder.c_str(), 0)) {
 | 
				
			||||
#else
 | 
				
			||||
      if (0 != ::access(folder_builder.c_str(), 0)) {
 | 
				
			||||
#endif
 | 
				
			||||
        // this folder not exist
 | 
				
			||||
#ifdef OS_WINDOWS
 | 
				
			||||
        if (0 != ::_mkdir(folder_builder.c_str())) {
 | 
				
			||||
#else
 | 
				
			||||
        if (0 != ::mkdir(folder_builder.c_str(), S_IRWXU)) {
 | 
				
			||||
#endif
 | 
				
			||||
          // create failed
 | 
				
			||||
          return false;
 | 
				
			||||
        }
 | 
				
			||||
      }
 | 
				
			||||
      sub.clear();
 | 
				
			||||
    }
 | 
				
			||||
  }
 | 
				
			||||
  return true;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
bool Utils::imwrite(const std::string &file, const cv::Mat &image) {
 | 
				
			||||
  auto folder = file.substr(0, utils::get_last_slash(file));
 | 
				
			||||
  Utils::mkdir(folder);
 | 
				
			||||
  return cv::imwrite(file, image);
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
#ifdef OS_WINDOWS
 | 
				
			||||
std::string Utils::utf8_to_gbk(const char* utf8) {
 | 
				
			||||
  int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
 | 
				
			||||
  wchar_t* wszGBK = new wchar_t[len + 1];
 | 
				
			||||
  memset(wszGBK, 0, len * 2 + 2);
 | 
				
			||||
  MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wszGBK, len);
 | 
				
			||||
  len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL);
 | 
				
			||||
  char* szGBK = new char[len + 1];
 | 
				
			||||
  memset(szGBK, 0, len + 1);
 | 
				
			||||
  WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL);
 | 
				
			||||
  std::string strTemp(szGBK);
 | 
				
			||||
  if (wszGBK)
 | 
				
			||||
    delete[] wszGBK;
 | 
				
			||||
  if (szGBK)
 | 
				
			||||
    delete[] szGBK;
 | 
				
			||||
  return strTemp;
 | 
				
			||||
}
 | 
				
			||||
#endif
 | 
				
			||||
 | 
				
			||||
std::size_t Utils::get_last_slash(const std::string &path) {
 | 
				
			||||
#ifdef OS_WINDOWS
 | 
				
			||||
  size_t last_slash_1 = path.find_last_of("\\");
 | 
				
			||||
  size_t last_slash_2 = path.find_last_of("/");
 | 
				
			||||
  size_t last_slash;
 | 
				
			||||
 | 
				
			||||
  if (last_slash_1 != std::string::npos && last_slash_2 != std::string::npos) {
 | 
				
			||||
    // C:/path\\to/file.postfix
 | 
				
			||||
    last_slash = std::max(last_slash_1, last_slash_2);
 | 
				
			||||
  } else {
 | 
				
			||||
    // C:\\path\\to\\file.postfix
 | 
				
			||||
    // C:/path/to/file.postfix
 | 
				
			||||
    last_slash =
 | 
				
			||||
        (last_slash_1 == std::string::npos) ? last_slash_2 : last_slash_1;
 | 
				
			||||
  }
 | 
				
			||||
#else
 | 
				
			||||
  size_t last_slash = path.find_last_of('/');
 | 
				
			||||
#endif
 | 
				
			||||
  return last_slash;
 | 
				
			||||
}
 | 
				
			||||
 | 
				
			||||
}  // namespace easypr
 | 
				
			||||
					Loading…
					
					
				
		Reference in new issue