#include "easypr/core/core_func.h"
#include "easypr/core/plate.hpp"
#include "easypr/core/chars_identify.h"
#include "easypr/config.h"
#include "easypr/core/params.h"
#include "thirdparty/mser/mser2.hpp"
#include <ctime>

namespace easypr {
  Mat colorMatch(const Mat &src, Mat &match, const Color r,
    const bool adaptive_minsv) {

    // if use adaptive_minsv
    // min value of s and v is adaptive to h
    const float max_sv = 255;
    const float minref_sv = 64;

    const float minabs_sv = 95; //95;

    // H range of blue 

    const int min_blue = 100;  // 100
    const int max_blue = 140;  // 140

    // H range of yellow

    const int min_yellow = 15;  // 15
    const int max_yellow = 40;  // 40

    // H range of white

    const int min_white = 0;   // 15
    const int max_white = 30;  // 40

    Mat src_hsv;

    // convert to HSV space
    cvtColor(src, src_hsv, CV_BGR2HSV);

    std::vector<cv::Mat> hsvSplit;
    split(src_hsv, hsvSplit);
    equalizeHist(hsvSplit[2], hsvSplit[2]);
    merge(hsvSplit, src_hsv);

    // match to find the color

    int min_h = 0;
    int max_h = 0;
    switch (r) {
    case BLUE:
      min_h = min_blue;
      max_h = max_blue;
      break;
    case YELLOW:
      min_h = min_yellow;
      max_h = max_yellow;
      break;
    case WHITE:
      min_h = min_white;
      max_h = max_white;
      break;
    default:
      // Color::UNKNOWN
      break;
    }

    float diff_h = float((max_h - min_h) / 2);
    float avg_h = min_h + diff_h;

    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;
    }

    int i, j;
    uchar* p;
    float s_all = 0;
    float v_all = 0;
    float count = 0;
    for (i = 0; i < nRows; ++i) {
      p = src_hsv.ptr<uchar>(i);
      for (j = 0; j < nCols; j += 3) {
        int H = int(p[j]);      // 0-180
        int S = int(p[j + 1]);  // 0-255
        int V = int(p[j + 2]);  // 0-255

        s_all += S;
        v_all += V;
        count++;

        bool colorMatched = false;

        if (H > min_h && H < max_h) {
          float Hdiff = 0;
          if (H > avg_h)
            Hdiff = H - avg_h;
          else
            Hdiff = avg_h - H;

          float Hdiff_p = float(Hdiff) / diff_h;

          float min_sv = 0;
          if (true == adaptive_minsv)
            min_sv =
            minref_sv -
            minref_sv / 2 *
            (1
            - Hdiff_p);  // inref_sv - minref_sv / 2 * (1 - Hdiff_p)
          else
            min_sv = minabs_sv;  // add

          if ((S > min_sv && S < max_sv) && (V > min_sv && V < max_sv))
            colorMatched = true;
        }

        if (colorMatched == true) {
          p[j] = 0;
          p[j + 1] = 0;
          p[j + 2] = 255;
        }
        else {
          p[j] = 0;
          p[j + 1] = 0;
          p[j + 2] = 0;
        }
      }
    }

    // cout << "avg_s:" << s_all / count << endl;
    // cout << "avg_v:" << v_all / count << endl;

    // get the final binary

    Mat src_grey;
    std::vector<cv::Mat> hsvSplit_done;
    split(src_hsv, hsvSplit_done);
    src_grey = hsvSplit_done[2];

    match = src_grey;

    return src_grey;
  }

  bool bFindLeftRightBound1(Mat &bound_threshold, int &posLeft, int &posRight) {

    float span = bound_threshold.rows * 0.2f;

    for (int i = 0; i < bound_threshold.cols - span - 1; i += 3) {
      int whiteCount = 0;
      for (int k = 0; k < bound_threshold.rows; k++) {
        for (int l = i; l < i + span; l++) {
          if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) {
            whiteCount++;
          }
        }
      }
      if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.15) {
        posLeft = i;
        break;
      }
    }
    span = bound_threshold.rows * 0.2f;


    for (int i = bound_threshold.cols - 1; i > span; i -= 2) {
      int whiteCount = 0;
      for (int k = 0; k < bound_threshold.rows; k++) {
        for (int l = i; l > i - span; l--) {
          if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) {
            whiteCount++;
          }
        }
      }

      if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.06) {
        posRight = i;
        if (posRight + 5 < bound_threshold.cols) {
          posRight = posRight + 5;
        } else {
          posRight = bound_threshold.cols - 1;
        }

        break;
      }
    }

    if (posLeft < posRight) {
      return true;
    }
    return false;
  }

  bool bFindLeftRightBound(Mat &bound_threshold, int &posLeft, int &posRight) {


    float span = bound_threshold.rows * 0.2f;

    for (int i = 0; i < bound_threshold.cols - span - 1; i += 2) {
      int whiteCount = 0;
      for (int k = 0; k < bound_threshold.rows; k++) {
        for (int l = i; l < i + span; l++) {
          if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) {
            whiteCount++;
          }
        }
      }
      if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.36) {
        posLeft = i;
        break;
      }
    }
    span = bound_threshold.rows * 0.2f;


    for (int i = bound_threshold.cols - 1; i > span; i -= 2) {
      int whiteCount = 0;
      for (int k = 0; k < bound_threshold.rows; k++) {
        for (int l = i; l > i - span; l--) {
          if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) {
            whiteCount++;
          }
        }
      }

      if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.26) {
        posRight = i;
        break;
      }
    }

    if (posLeft < posRight) {
      return true;
    }
    return false;
  }

  bool bFindLeftRightBound2(Mat &bound_threshold, int &posLeft, int &posRight) {

    float span = bound_threshold.rows * 0.2f;

    for (int i = 0; i < bound_threshold.cols - span - 1; i += 3) {
      int whiteCount = 0;
      for (int k = 0; k < bound_threshold.rows; k++) {
        for (int l = i; l < i + span; l++) {
          if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) {
            whiteCount++;
          }
        }
      }
      if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.32) {
        posLeft = i;
        break;
      }
    }
    span = bound_threshold.rows * 0.2f;


    for (int i = bound_threshold.cols - 1; i > span; i -= 3) {
      int whiteCount = 0;
      for (int k = 0; k < bound_threshold.rows; k++) {
        for (int l = i; l > i - span; l--) {
          if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) {
            whiteCount++;
          }
        }
      }

      if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.22) {
        posRight = i;
        break;
      }
    }

    if (posLeft < posRight) {
      return true;
    }
    return false;
  }


  bool plateColorJudge(const Mat &src, const Color r, const bool adaptive_minsv,
                       float &percent) {

    const float thresh = 0.45f;

    Mat src_gray;
    colorMatch(src, src_gray, r, adaptive_minsv);

    percent =
        float(countNonZero(src_gray)) / float(src_gray.rows * src_gray.cols);
    // cout << "percent:" << percent << endl;

    if (percent > thresh)
      return true;
    else
      return false;
  }

  Color getPlateType(const Mat &src, const bool adaptive_minsv) {
    float max_percent = 0;
    Color max_color = UNKNOWN;

    float blue_percent = 0;
    float yellow_percent = 0;
    float white_percent = 0;

    if (plateColorJudge(src, BLUE, adaptive_minsv, blue_percent) == true) {
      // cout << "BLUE" << endl;
      return BLUE;
    } else if (plateColorJudge(src, YELLOW, adaptive_minsv, yellow_percent) ==
               true) {
      // cout << "YELLOW" << endl;
      return YELLOW;
    } else if (plateColorJudge(src, WHITE, adaptive_minsv, white_percent) ==
               true) {
      // cout << "WHITE" << endl;
      return WHITE;
    } else {
      //std::cout << "OTHER" << std::endl;

      /*max_percent = blue_percent > yellow_percent ? blue_percent : yellow_percent;
      max_color = blue_percent > yellow_percent ? BLUE : YELLOW;
      max_color = max_percent > white_percent ? max_color : WHITE;*/

      // always return blue
      return BLUE;
    }
  }

  void clearLiuDingOnly(Mat &img) {
    const int x = 7;
    Mat jump = Mat::zeros(1, img.rows, CV_32F);
    for (int i = 0; i < img.rows; i++) {
      int jumpCount = 0;
      int whiteCount = 0;
      for (int j = 0; j < img.cols - 1; j++) {
        if (img.at<char>(i, j) != img.at<char>(i, j + 1)) jumpCount++;

        if (img.at<uchar>(i, j) == 255) {
          whiteCount++;
        }
      }

      jump.at<float>(i) = (float) jumpCount;
    }

    for (int i = 0; i < img.rows; i++) {
      if (jump.at<float>(i) <= x) {
        for (int j = 0; j < img.cols; j++) {
          img.at<char>(i, j) = 0;
        }
      }
    }
  }

  bool clearLiuDing(Mat &img) {
    std::vector<float> fJump;
    int whiteCount = 0;
    const int x = 7;
    Mat jump = Mat::zeros(1, img.rows, CV_32F);
    for (int i = 0; i < img.rows; i++) {
      int jumpCount = 0;

      for (int j = 0; j < img.cols - 1; j++) {
        if (img.at<char>(i, j) != img.at<char>(i, j + 1)) jumpCount++;

        if (img.at<uchar>(i, j) == 255) {
          whiteCount++;
        }
      }

      jump.at<float>(i) = (float) jumpCount;
    }

    int iCount = 0;
    for (int i = 0; i < img.rows; i++) {
      fJump.push_back(jump.at<float>(i));
      if (jump.at<float>(i) >= 16 && jump.at<float>(i) <= 45) {

        // jump condition
        iCount++;
      }
    }

    // if not is not plate
    if (iCount * 1.0 / img.rows <= 0.40) {
      return false;
    }

    if (whiteCount * 1.0 / (img.rows * img.cols) < 0.15 ||
        whiteCount * 1.0 / (img.rows * img.cols) > 0.50) {
      return false;
    }

    for (int i = 0; i < img.rows; i++) {
      if (jump.at<float>(i) <= x) {
        for (int j = 0; j < img.cols; j++) {
          img.at<char>(i, j) = 0;
        }
      }
    }
    return true;
  }


void clearBorder(const Mat &img, Rect& cropRect) {
  int r = img.rows;
  int c = img.cols;
  Mat boder = Mat::zeros(1, r, CV_8UC1);
  const int noJunpCount_thresh = int(0.15f * c);

  // if nojumpcount >
  for (int i = 0; i < r; i++) {
    int nojumpCount = 0;
    int isBorder = 0;
    for (int j = 0; j < c - 1; j++) {
      if (img.at<char>(i, j) == img.at<char>(i, j + 1))
        nojumpCount++;
      if (nojumpCount > noJunpCount_thresh) {
        nojumpCount = 0;
        isBorder = 1;
        break;
      }
    }
    boder.at<char>(i) = (char) isBorder;
  }

  const int mintop = int(0.1f * r);
  const int maxtop = int(0.9f * r);

  int minMatTop = 0;
  int maxMatTop = r - 1;

  for (int i = 0; i < mintop; i++) {
    if (boder.at<char>(i) == 1) {
      minMatTop = i;
    }
  }

  for (int i = r - 1; i > maxtop; i--) {
    if (boder.at<char>(i) == 1) {
      maxMatTop = i;
    }
  }

  cropRect = Rect(0, minMatTop, c, maxMatTop - minMatTop + 1);

}

  void clearLiuDing(Mat mask, int &top, int &bottom) {
    const int x = 7;

    for (int i = 0; i < mask.rows / 2; i++) {
      int whiteCount = 0;
      int jumpCount = 0;
      for (int j = 0; j < mask.cols - 1; j++) {
        if (mask.at<char>(i, j) != mask.at<char>(i, j + 1)) jumpCount++;

        if ((int) mask.at<uchar>(i, j) == 255) {
          whiteCount++;
        }
      }
      if ((jumpCount < x && whiteCount * 1.0 / mask.cols > 0.15) ||
          whiteCount < 4) {
        top = i;
      }
    }
    top -= 1;
    if (top < 0) {
      top = 0;
    }

    // ok, find top and bottom boudnadry

    for (int i = mask.rows - 1; i >= mask.rows / 2; i--) {
      int jumpCount = 0;
      int whiteCount = 0;
      for (int j = 0; j < mask.cols - 1; j++) {
        if (mask.at<char>(i, j) != mask.at<char>(i, j + 1)) jumpCount++;
        if (mask.at<uchar>(i, j) == 255) {
          whiteCount++;
        }
      }
      if ((jumpCount < x && whiteCount * 1.0 / mask.cols > 0.15) ||
          whiteCount < 4) {
        bottom = i;
      }
    }
    bottom += 1;
    if (bottom >= mask.rows) {
      bottom = mask.rows - 1;
    }

    if (top >= bottom) {
      top = 0;
      bottom = mask.rows - 1;
    }
  }

  int ThresholdOtsu(Mat mat) {
    int height = mat.rows;
    int width = mat.cols;

    // histogram
    float histogram[256] = {0};
    for (int i = 0; i < height; i++) {
      for (int j = 0; j < width; j++) {
        unsigned char p = (unsigned char) ((mat.data[i * mat.step[0] + j]));
        histogram[p]++;
      }
    }
    // normalize histogram
    int size = height * width;
    for (int i = 0; i < 256; i++) {
      histogram[i] = histogram[i] / size;
    }

    // average pixel value
    float avgValue = 0;
    for (int i = 0; i < 256; i++) {
      avgValue += i * histogram[i];
    }

    int thresholdV;
    float maxVariance = 0;
    float w = 0, u = 0;
    for (int i = 0; i < 256; i++) {
      w += histogram[i];
      u += i * histogram[i];

      float t = avgValue * w - u;
      float variance = t * t / (w * (1 - w));
      if (variance > maxVariance) {
        maxVariance = variance;
        thresholdV = i;
      }
    }

    return thresholdV;
  }


  Mat histeq(Mat in) {
    Mat out(in.size(), in.type());
    if (in.channels() == 3) {
      Mat hsv;
      std::vector<cv::Mat> hsvSplit;
      cvtColor(in, hsv, CV_BGR2HSV);
      split(hsv, hsvSplit);
      equalizeHist(hsvSplit[2], hsvSplit[2]);
      merge(hsvSplit, hsv);
      cvtColor(hsv, out, CV_HSV2BGR);
    } else if (in.channels() == 1) {
      equalizeHist(in, out);
    }
    return out;
  }

#define HORIZONTAL 1
#define VERTICAL 0

  Mat CutTheRect(Mat &in, Rect &rect) {
    int size = in.cols;  // (rect.width>rect.height)?rect.width:rect.height;
    Mat dstMat(size, size, CV_8UC1);
    dstMat.setTo(Scalar(0, 0, 0));

    int x = (int) floor((float) (size - rect.width) / 2.0f);
    int y = (int) floor((float) (size - rect.height) / 2.0f);

    for (int i = 0; i < rect.height; ++i) {

      for (int j = 0; j < rect.width; ++j) {
        dstMat.data[dstMat.step[0] * (i + y) + j + x] =
            in.data[in.step[0] * (i + rect.y) + j + rect.x];
      }
    }

    //
    return dstMat;
  }

  Rect GetCenterRect(Mat &in) {
    Rect _rect;

    int top = 0;
    int bottom = in.rows - 1;

    // find the center rect

    for (int i = 0; i < in.rows; ++i) {
      bool bFind = false;
      for (int j = 0; j < in.cols; ++j) {
        if (in.data[i * in.step[0] + j] > 20) {
          top = i;
          bFind = true;
          break;
        }
      }
      if (bFind) {
        break;
      }

    }
    for (int i = in.rows - 1;
         i >= 0;
         --i) {
      bool bFind = false;
      for (int j = 0; j < in.cols; ++j) {
        if (in.data[i * in.step[0] + j] > 20) {
          bottom = i;
          bFind = true;
          break;
        }
      }
      if (bFind) {
        break;
      }

    }


    int left = 0;
    int right = in.cols - 1;
    for (int j = 0; j < in.cols; ++j) {
      bool bFind = false;
      for (int i = 0; i < in.rows; ++i) {
        if (in.data[i * in.step[0] + j] > 20) {
          left = j;
          bFind = true;
          break;
        }
      }
      if (bFind) {
        break;
      }

    }
    for (int j = in.cols - 1;
         j >= 0;
         --j) {
      bool bFind = false;
      for (int i = 0; i < in.rows; ++i) {
        if (in.data[i * in.step[0] + j] > 20) {
          right = j;
          bFind = true;

          break;
        }
      }
      if (bFind) {
        break;
      }
    }

    _rect.x = left;
    _rect.y = top;
    _rect.width = right - left + 1;
    _rect.height = bottom - top + 1;

    return _rect;
  }

  float countOfBigValue(Mat &mat, int iValue) {
    float iCount = 0.0;
    if (mat.rows > 1) {
      for (int i = 0; i < mat.rows; ++i) {
        if (mat.data[i * mat.step[0]] > iValue) {
          iCount += 1.0;
        }
      }
      return iCount;

    } else {
      for (int i = 0; i < mat.cols; ++i) {
        if (mat.data[i] > iValue) {
          iCount += 1.0;
        }
      }

      return iCount;
    }
  }

  Mat ProjectedHistogram(Mat img, int t, int threshold) {
    int sz = (t) ? img.rows : img.cols;
    Mat mhist = Mat::zeros(1, sz, CV_32F);

    for (int j = 0; j < sz; j++) {
      Mat data = (t) ? img.row(j) : img.col(j);

      mhist.at<float>(j) = countOfBigValue(data, threshold);
    }

    // Normalize histogram
    double min, max;
    minMaxLoc(mhist, &min, &max);

    if (max > 0)
      mhist.convertTo(mhist, -1, 1.0f / max, 0);

    return mhist;
  }

  Mat showHistogram(const Mat &hist) {
    int height = 32;
    int width = hist.cols;
    Mat show = Mat::zeros(height, width, CV_8UC1);
    for (int i = 0; i < width; i++) {
      int len = int((float) height * hist.at<float>(i));
      for (int j = height - 1; j >= 0; j--) {
        if (height - j <= len)
          show.at<char>(j, i) = (char) 255;
      }
    }
    return show;
  }

  Mat preprocessChar(Mat in, int char_size) {
    // Remap image
    int h = in.rows;
    int w = in.cols;

    int charSize = char_size;

    Mat transformMat = Mat::eye(2, 3, CV_32F);
    int m = max(w, h);
    transformMat.at<float>(0, 2) = float(m / 2 - w / 2);
    transformMat.at<float>(1, 2) = float(m / 2 - h / 2);

    Mat warpImage(m, m, in.type());
    warpAffine(in, warpImage, transformMat, warpImage.size(), INTER_LINEAR,
               BORDER_CONSTANT, Scalar(0));

    Mat out;
    cv::resize(warpImage, out, Size(charSize, charSize));

    return out;
  }

  Rect GetChineseRect(const Rect rectSpe) {
    int height = rectSpe.height;
    float newwidth = rectSpe.width * 1.10f;
    int x = rectSpe.x;
    int y = rectSpe.y;

    int newx = x - int(newwidth * 1.10f);
    newx = newx > 0 ? newx : 0;

    Rect a(newx, y, int(newwidth), height);

    return a;
  }

  bool verifyCharSizes(Rect r) {
    // Char sizes 45x90
    float aspect = 45.0f / 90.0f;
    float charAspect = (float) r.width / (float) r.height;
    float error = 0.35f;
    float minHeight = 25.f;
    float maxHeight = 50.f;
    // We have a different aspect ratio for number 1, and it can be ~0.2
    float minAspect = 0.05f;
    float maxAspect = aspect + aspect * error;

    // bb area
    int bbArea = r.width * r.height;

    if (charAspect > minAspect && charAspect < maxAspect /*&&
                                                       r.rows >= minHeight && r.rows < maxHeight*/)
      return true;
    else
      return false;
  }


  Mat scaleImage(const Mat &image, const Size &maxSize, double &scale_ratio) {
    Mat ret;

    if (image.cols > maxSize.width || image.rows > maxSize.height) {
      double widthRatio = image.cols / (double) maxSize.width;
      double heightRatio = image.rows / (double) maxSize.height;
      double m_real_to_scaled_ratio = max(widthRatio, heightRatio);

      int newWidth = int(image.cols / m_real_to_scaled_ratio);
      int newHeight = int(image.rows / m_real_to_scaled_ratio);

      cv::resize(image, ret, Size(newWidth, newHeight), 0, 0);
      scale_ratio = m_real_to_scaled_ratio;
    } else {
      ret = image;
      scale_ratio = 1.0;
    }

    return ret;
  }


// Scale back RotatedRect
  RotatedRect scaleBackRRect(const RotatedRect &rr, const float scale_ratio) {
    float width = rr.size.width * scale_ratio;
    float height = rr.size.height * scale_ratio;
    float x = rr.center.x * scale_ratio;
    float y = rr.center.y * scale_ratio;
    RotatedRect mserRect(Point2f(x, y), Size2f(width, height), rr.angle);

    return mserRect;
  }

  bool verifyPlateSize(Rect mr) {
    float error = 0.6f;
    // Spain car plate size: 52x11 aspect 4,7272
    // China car plate size: 440mm*140mm，aspect 3.142857

    // Real car plate size: 136 * 32, aspect 4
    float aspect = 3.75;

    // Set a min and max area. All other patchs are discarded
    // int min= 1*aspect*1; // minimum area
    // int max= 2000*aspect*2000; // maximum area
    int min = 34 * 8 * 1;  // minimum area
    int max = 34 * 8 * 200;  // maximum area

    // Get only patchs that match to a respect ratio.
    float rmin = aspect - aspect * error;
    float rmax = aspect + aspect * error;

    float area = float(mr.height * mr.width);
    float r = (float) mr.width / (float) mr.height;
    if (r < 1) r = (float) mr.height / (float) mr.width;

    // cout << "area:" << area << endl;
    // cout << "r:" << r << endl;

    if ((area < min || area > max) || (r < rmin || r > rmax))
      return false;
    else
      return true;
  }

  bool verifyRotatedPlateSizes(RotatedRect mr, bool showDebug) {
    float error = 0.65f;
    // Spain car plate size: 52x11 aspect 4,7272
    // China car plate size: 440mm*140mm，aspect 3.142857

    // Real car plate size: 136 * 32, aspect 4
    float aspect = 3.75f;

    // Set a min and max area. All other patchs are discarded
    // int min= 1*aspect*1; // minimum area
    // int max= 2000*aspect*2000; // maximum area
    //int min = 34 * 8 * 1;  // minimum area
    //int max = 34 * 8 * 200;  // maximum area

    // Get only patchs that match to a respect ratio.
    float aspect_min = aspect - aspect * error;
    float aspect_max = aspect + aspect * error;

    float width_max = 600.f;
    float width_min = 30.f;

    float min = float(width_min * width_min / aspect_max);  // minimum area
    float max = float(width_max * width_max / aspect_min);  // maximum area

    float width = mr.size.width;
    float height = mr.size.height;
    float area = width * height;

    float ratio = width / height;
    float angle = mr.angle;
    if (ratio < 1) {
      swap(width, height);
      ratio = width / height;

      angle = 90.f + angle;
      //std::cout << "angle:" << angle << std::endl;
    }

    float angle_min = -60.f;
    float angle_max = 60.f;

    //std::cout << "aspect_min:" << aspect_min << std::endl;
    //std::cout << "aspect_max:" << aspect_max << std::endl;

    if (area < min || area > max) {
      if (0 && showDebug) {
        std::cout << "area < min || area > max: " << area << std::endl;
      }

      return false;
    } else if (ratio < aspect_min || ratio > aspect_max) {
      if (0 && showDebug) {
        std::cout << "ratio < aspect_min || ratio > aspect_max: " << ratio << std::endl;
      }

      return false;
    } else if (angle < angle_min || angle > angle_max) {
      if (0 && showDebug) {
        std::cout << "angle < angle_min || angle > angle_max: " << angle << std::endl;
      }

      return false;
    } else if (width < width_min || width > width_max) {
      if (0 && showDebug) {
        std::cout << "width < width_min || width > width_max: " << width << std::endl;
      }

      return false;
    } else {
      return true;
    }

    return true;
  }

//! non-maximum suppression
  void NMStoCharacter(std::vector<CCharacter> &inVec, double overlap) {

    std::sort(inVec.begin(), inVec.end());

    std::vector<CCharacter>::iterator it = inVec.begin();
    for (; it != inVec.end(); ++it) {
      CCharacter charSrc = *it;
      //std::cout << "plateScore:" << plateSrc.getPlateScore() << std::endl;
      Rect rectSrc = charSrc.getCharacterPos();

      std::vector<CCharacter>::iterator itc = it + 1;

      for (; itc != inVec.end();) {
        CCharacter charComp = *itc;
        Rect rectComp = charComp.getCharacterPos();
        //Rect rectInter = rectSrc & rectComp;
        //Rect rectUnion = rectSrc | rectComp;
        //double r = double(rectInter.area()) / double(rectUnion.area());

        float iou = computeIOU(rectSrc, rectComp);

        if (iou > overlap) {
          itc = inVec.erase(itc);
        } else {
          ++itc;
        }
      }
    }
  }

// judge weather two CCharacter are nearly the same;
  bool compareCharRect(const CCharacter &character1, const CCharacter &character2) {
    Rect rect1 = character1.getCharacterPos();
    Rect rect2 = character2.getCharacterPos();

    // the character in plate are similar height
    float width_1 = float(rect1.width);
    float height_1 = float(rect1.height);

    float width_2 = float(rect2.width);
    float height_2 = float(rect2.height);

    float height_diff = abs(height_1 - height_2);
    double height_diff_ratio = height_diff / min(height_1, height_2);

    if (height_diff_ratio > 0.25)
      return false;

    // the character in plate are similar in the y-axis
    float y_1 = float(rect1.tl().y);
    float y_2 = float(rect2.tl().y);

    float y_diff = abs(y_1 - y_2);
    double y_diff_ratio = y_diff / min(height_1, height_2);

    if (y_diff_ratio > 0.5)
      return false;

    // the character center in plate are not to near in the x-axis
    float x_1 = float(rect1.tl().x + rect1.width / 2);
    float x_2 = float(rect2.tl().x + rect2.width / 2);

    float x_diff = abs(x_1 - x_2);
    double x_diff_ratio = x_diff / min(height_1, height_2);

    if (x_diff_ratio < 0.25)
      return false;

    // the character in plate are near in the x-axis but not very near
    float x_margin_left = float(min(rect1.br().x, rect2.br().x));
    float x_margin_right = float(max(rect1.tl().x, rect2.tl().x));

    float x_margin_diff = abs(x_margin_left - x_margin_right);
    double x_margin_diff_ratio = x_margin_diff / min(height_1, height_2);

    if (x_margin_diff_ratio > 1.0)
      return false;

    return true;
  }

//! merge chars to group, using the similarity
  void mergeCharToGroup(std::vector<CCharacter> vecRect,
                        std::vector<std::vector<CCharacter>> &charGroupVec) {

    std::vector<int> labels;

    int numbers = 0;
    if (vecRect.size() > 0)
      numbers = partition(vecRect, labels, &compareCharRect);

    for (size_t j = 0; j < size_t(numbers); j++) {
      std::vector<CCharacter> charGroup;

      for (size_t t = 0; t < vecRect.size(); t++) {
        int label = labels[t];

        if (label == j)
          charGroup.push_back(vecRect[t]);
      }

      if (charGroup.size() < 2)
        continue;

      charGroupVec.push_back(charGroup);
    }
  }


  void rotatedRectangle(InputOutputArray image, RotatedRect rrect, const Scalar &color, int thickness, int lineType,
                        int shift) {
    Point2f rect_points[4];
    rrect.points(rect_points);
    for (int j = 0; j < 4; j++) {
      cv::line(image, rect_points[j], rect_points[(j + 1) % 4], color, thickness, lineType, shift);
    }
  }


  void searchWeakSeed(const std::vector<CCharacter> &charVec, std::vector<CCharacter> &mserCharacter, double thresh1,
                      double thresh2,
                      const Vec4f &line, Point &boundaryPoint, const Rect &maxrect, Rect &plateResult, Mat result,
                      CharSearchDirection searchDirection) {

    float k = line[1] / line[0];
    float x_1 = line[2];
    float y_1 = line[3];

    std::vector<CCharacter> searchWeakSeedVec;
    searchWeakSeedVec.reserve(8);

    for (auto weakSeed : charVec) {
      Rect weakRect = weakSeed.getCharacterPos();

      //cv::rectangle(result, weakRect, Scalar(255, 0, 255));

      Point weakCenter(weakRect.tl().x + weakRect.width / 2, weakRect.tl().y + weakRect.height / 2);
      float x_2 = (float) weakCenter.x;

      if (searchDirection == CharSearchDirection::LEFT) {
        if (weakCenter.x + weakRect.width / 2 > boundaryPoint.x) {
          continue;
        }
      } else if (searchDirection == CharSearchDirection::RIGHT) {
        if (weakCenter.x - weakRect.width / 2 < boundaryPoint.x) {
          continue;
        }
      }

      float y_2l = k * (x_2 - x_1) + y_1;
      float y_2 = (float) weakCenter.y;

      float y_diff_ratio = abs(y_2l - y_2) / maxrect.height;

      if (y_diff_ratio < thresh1) {
        float width_1 = float(maxrect.width);
        float height_1 = float(maxrect.height);

        float width_2 = float(weakRect.width);
        float height_2 = float(weakRect.height);

        float height_diff = abs(height_1 - height_2);
        double height_diff_ratio = height_diff / min(height_1, height_2);

        float width_diff = abs(width_1 - width_2);
        double width_diff_ratio = width_diff / maxrect.width;

        if (height_diff_ratio < thresh1 && width_diff_ratio < 0.5) {
          //std::cout << "h" << height_diff_ratio << std::endl;
          //std::cout << "w" << width_diff_ratio << std::endl;
          searchWeakSeedVec.push_back(weakSeed);
        } else {

        }
      }
    }

    // form right to left to split
    if (searchWeakSeedVec.size() != 0) {
      if (searchDirection == CharSearchDirection::LEFT) {
        std::sort(searchWeakSeedVec.begin(), searchWeakSeedVec.end(),
                  [](const CCharacter &r1, const CCharacter &r2) {
                    return r1.getCharacterPos().tl().x > r2.getCharacterPos().tl().x;
                  });
      } else if (searchDirection == CharSearchDirection::RIGHT) {
        std::sort(searchWeakSeedVec.begin(), searchWeakSeedVec.end(),
                  [](const CCharacter &r1, const CCharacter &r2) {
                    return r1.getCharacterPos().tl().x < r2.getCharacterPos().tl().x;
                  });
      }

      CCharacter firstWeakSeed = searchWeakSeedVec.at(0);
      Rect firstWeakRect = firstWeakSeed.getCharacterPos();
      Point firstWeakCenter(firstWeakRect.tl().x + firstWeakRect.width / 2,
                            firstWeakRect.tl().y + firstWeakRect.height / 2);

      float ratio = (float) abs(firstWeakCenter.x - boundaryPoint.x) / (float) maxrect.height;
      if (ratio > thresh2) {
        if (0) {
          std::cout << "search seed ratio:" << ratio << std::endl;
        }
        return;
      }

      mserCharacter.push_back(firstWeakSeed);
      plateResult |= firstWeakRect;
      boundaryPoint = firstWeakCenter;

      for (size_t weakSeedIndex = 0; weakSeedIndex + 1 < searchWeakSeedVec.size(); weakSeedIndex++) {
        CCharacter weakSeed = searchWeakSeedVec[weakSeedIndex];
        CCharacter weakSeedCompare = searchWeakSeedVec[weakSeedIndex + 1];

        Rect rect1 = weakSeed.getCharacterPos();
        Rect rect2 = weakSeedCompare.getCharacterPos();

        Rect weakRect = rect2;
        Point weakCenter(weakRect.tl().x + weakRect.width / 2, weakRect.tl().y + weakRect.height / 2);

        // the character in plate are similar height
        float width_1 = float(rect1.width);
        float height_1 = float(rect1.height);

        float width_2 = float(rect2.width);
        float height_2 = float(rect2.height);

        // the character in plate are near in the x-axis but not very near
        float x_margin_left = float(min(rect1.br().x, rect2.br().x));
        float x_margin_right = float(max(rect1.tl().x, rect2.tl().x));

        float x_margin_diff = abs(x_margin_left - x_margin_right);
        double x_margin_diff_ratio = x_margin_diff / min(height_1, height_2);

        if (x_margin_diff_ratio > thresh2) {
          if (0) {
            std::cout << "search seed x_margin_diff_ratio:" << x_margin_diff_ratio << std::endl;
          }
          break;
        } else {
          //::rectangle(result, weakRect, Scalar(255, 0, 0), 1);
          mserCharacter.push_back(weakSeedCompare);
          plateResult |= weakRect;
          if (searchDirection == CharSearchDirection::LEFT) {
            if (weakCenter.x < boundaryPoint.x) {
              boundaryPoint = weakCenter;
            }
          } else if (searchDirection == CharSearchDirection::RIGHT) {
            if (weakCenter.x > boundaryPoint.x) {
              boundaryPoint = weakCenter;
            }
          }
        }
      }
    }
  }

  void slideWindowSearch(const Mat &image, std::vector<CCharacter> &slideCharacter, const Vec4f &line,
                         Point &fromPoint, const Vec2i &dist, double ostu_level, float ratioWindow,
                         float threshIsCharacter, const Rect &maxrect, Rect &plateResult,
                         CharSearchDirection searchDirection, bool isChinese, Mat &result) {
    float k = line[1] / line[0];
    float x_1 = line[2];
    float y_1 = line[3];

    int slideLength = int(ratioWindow * maxrect.width);
    int slideStep = 1;
    int fromX = 0;
    if (searchDirection == CharSearchDirection::LEFT) {
      fromX = fromPoint.x - dist[0];
    } else if (searchDirection == CharSearchDirection::RIGHT) {
      fromX = fromPoint.x + dist[0];
    }

    std::vector<CCharacter> charCandidateVec;
    for (int slideX = -slideLength; slideX < slideLength; slideX += slideStep) {
      float x_slide = 0;

      if (searchDirection == CharSearchDirection::LEFT) {
        x_slide = float(fromX - slideX);
      } else if (searchDirection == CharSearchDirection::RIGHT) {
        x_slide = float(fromX + slideX);
      }

      float y_slide = k * (x_slide - x_1) + y_1;
      Point2f p_slide(x_slide, y_slide);
      cv::circle(result, p_slide, 2, Scalar(255, 255, 255), 1);

      int chineseWidth = int(maxrect.width * 1.05);
      int chineseHeight = int(maxrect.height * 1.05);

      Rect rect(Point2f(x_slide - chineseWidth / 2, y_slide - chineseHeight / 2), Size(chineseWidth, chineseHeight));

      if (rect.tl().x < 0 || rect.tl().y < 0 || rect.br().x >= image.cols || rect.br().y >= image.rows)
        continue;

      Mat region = image(rect);
      Mat binary_region;

      cv::threshold(region, binary_region, ostu_level, 255, CV_THRESH_BINARY);
      //double ostu_level = threshold(region, binary_region, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
      //std::cout << "ostu_level:" << ostu_level << std::endl;*/

      Mat charInput = preprocessChar(binary_region, 20);

      if (0) {
        imshow("charInput", charInput);
        waitKey(0);
        destroyWindow("charInput");
      }

      CCharacter charCandidate;
      charCandidate.setCharacterPos(rect);
      charCandidate.setCharacterMat(charInput);
      charCandidate.setIsChinese(isChinese);
      charCandidateVec.push_back(charCandidate);
    }

    if (isChinese) {
      CharsIdentify::instance()->classifyChinese(charCandidateVec);
    } else {
      CharsIdentify::instance()->classify(charCandidateVec);
    }

    double overlapThresh = 0.1;
    NMStoCharacter(charCandidateVec, overlapThresh);

    for (auto character : charCandidateVec) {
      Rect rect = character.getCharacterPos();
      Point center(rect.tl().x + rect.width / 2, rect.tl().y + rect.height / 2);

      if (character.getCharacterScore() > threshIsCharacter && character.getCharacterStr() != "1") {
        //cv::rectangle(result, rect, Scalar(255, 255, 255), 1);
        plateResult |= rect;
        slideCharacter.push_back(character);
        fromPoint = center;

        if (0) {
          std::cout << "label:" << character.getCharacterStr();
          std::cout << "__score:" << character.getCharacterScore() << std::endl;
        }
      }
    }
  }


  bool judegMDOratio2(const Mat &image, const Rect &rect, std::vector<Point> &contour, Mat &result, const float thresh,
    bool useExtendHeight) {

    Mat mser = image(rect);
    Mat mser_mat;
    cv::threshold(mser, mser_mat, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);

    Rect normalRect = adaptive_charrect_from_rect(rect, image.cols, image.rows, useExtendHeight);
    Mat region = image(normalRect);
    Mat thresh_mat;
    cv::threshold(region, thresh_mat, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);

    // count mser diff ratio
    int countdiff = abs(countNonZero(thresh_mat) - countNonZero(mser_mat));

    float MserDiffOstuRatio = float(countdiff) / float(rect.area());

    if (MserDiffOstuRatio > thresh) {
      //std::cout << "MserDiffOstuRatio:" << MserDiffOstuRatio << std::endl;
      /*imshow("tmpMat", mser_mat);
      waitKey(0);
      imshow("tmpMat", thresh_mat);
      waitKey(0);*/

      cv::rectangle(result, normalRect, Scalar(0, 0, 0), 2);
      return false;
    }

    return true;
  }

  Rect interRect(const Rect &a, const Rect &b) {
    Rect c;
    int x1 = a.x > b.x ? a.x : b.x;
    int y1 = a.y > b.y ? a.y : b.y;
    c.width = (a.x + a.width < b.x + b.width ? a.x + a.width : b.x + b.width) - x1;
    c.height = (a.y + a.height < b.y + b.height ? a.y + a.height : b.y + b.height) - y1;
    c.x = x1;
    c.y = y1;
    if (c.width <= 0 || c.height <= 0)
      c = Rect();
    return c;
  }

  Rect mergeRect(const Rect &a, const Rect &b) {
    Rect c;
    int x1 = a.x < b.x ? a.x : b.x;
    int y1 = a.y < b.y ? a.y : b.y;
    c.width = (a.x + a.width > b.x + b.width ? a.x + a.width : b.x + b.width) - x1;
    c.height = (a.y + a.height > b.y + b.height ? a.y + a.height : b.y + b.height) - y1;
    c.x = x1;
    c.y = y1;
    return c;
  }

  bool computeIOU(const RotatedRect &rrect1, const RotatedRect &rrect2, const int width, const int height,
                  const float thresh, float &result) {
    Rect_<float> safe_rect1;
    calcSafeRect(rrect1, width, height, safe_rect1);

    Rect_<float> safe_rect2;
    calcSafeRect(rrect2, width, height, safe_rect2);

    Rect inter = interRect(safe_rect1, safe_rect2);
    Rect urect = mergeRect(safe_rect1, safe_rect2);

    float iou = (float) inter.area() / (float) urect.area();

    result = iou;

    if (iou > thresh) {
      return true;
    }

    return false;
  }

  float computeIOU(const RotatedRect &rrect1, const RotatedRect &rrect2, const int width, const int height) {
    Rect_<float> safe_rect1;
    calcSafeRect(rrect1, width, height, safe_rect1);

    Rect_<float> safe_rect2;
    calcSafeRect(rrect2, width, height, safe_rect2);

    Rect inter = interRect(safe_rect1, safe_rect2);
    Rect urect = mergeRect(safe_rect1, safe_rect2);

    float iou = (float) inter.area() / (float) urect.area();
    //std::cout << "iou" << iou << std::endl;

    return iou;
  }

  bool computeIOU(const Rect &rect1, const Rect &rect2, const float thresh, float &result) {

    Rect inter = interRect(rect1, rect2);
    Rect urect = mergeRect(rect1, rect2);

    float iou = (float) inter.area() / (float) urect.area();
    result = iou;

    if (iou > thresh) {
      return true;
    }

    return false;
  }

  float computeIOU(const Rect &rect1, const Rect &rect2) {

    Rect inter = interRect(rect1, rect2);
    Rect urect = mergeRect(rect1, rect2);

    float iou = (float) inter.area() / (float) urect.area();

    return iou;
  }


// the slope are nealy the same along the line
// if one slope is much different others, it should be outliers
// this function to remove it
  void removeRightOutliers(std::vector<CCharacter> &charGroup, std::vector<CCharacter> &out_charGroup, double thresh1,
                           double thresh2, Mat result) {
    std::sort(charGroup.begin(), charGroup.end(),
              [](const CCharacter &r1, const CCharacter &r2) {
                return r1.getCenterPoint().x < r2.getCenterPoint().x;
              });

    std::vector<float> slopeVec;
    float slope_last = 0;
    for (size_t charGroup_i = 0; charGroup_i + 1 < charGroup.size(); charGroup_i++) {
      // line_between_two_points
      Vec4f line_btp;
      CCharacter leftChar = charGroup.at(charGroup_i);
      CCharacter rightChar = charGroup.at(charGroup_i + 1);
      std::vector<Point> two_points;
      two_points.push_back(leftChar.getCenterPoint());
      two_points.push_back(rightChar.getCenterPoint());
      fitLine(Mat(two_points), line_btp, CV_DIST_L2, 0, 0.01, 0.01);
      float slope = line_btp[1] / line_btp[0];
      slopeVec.push_back(slope);

      if (0) {
        cv::line(result, leftChar.getCenterPoint(), rightChar.getCenterPoint(), Scalar(0, 0, 255));
      }
    }

    int uniformity_count = 0;
    int outlier_index = -1;
    for (size_t slopeVec_i = 0; slopeVec_i + 1 < slopeVec.size(); slopeVec_i++) {
      float slope_1 = slopeVec.at(slopeVec_i);
      float slope_2 = slopeVec.at(slopeVec_i + 1);
      float slope_diff = abs(slope_1 - slope_2);
      if (0) {
        std::cout << "slope_diff:" << slope_diff << std::endl;
      }
      if (slope_diff <= thresh1) {
        uniformity_count++;
      }
      if (0) {
        std::cout << "slope_1:" << slope_1 << std::endl;
        std::cout << "slope_2:" << slope_2 << std::endl;
      }
      if (1/*(slope_1 <= 0 && slope_2 >= 0) || (slope_1 >= 0 && slope_2 <= 0)*/) {
        if (uniformity_count >= 2 && slope_diff >= thresh2) {
          outlier_index = slopeVec_i + 2;
          break;
        }
      }
    }
    if (0) {
      std::cout << "uniformity_count:" << uniformity_count << std::endl;
      std::cout << "outlier_index:" << outlier_index << std::endl;
    }

    for (int charGroup_i = 0; charGroup_i < (int) charGroup.size(); charGroup_i++) {
      if (charGroup_i != outlier_index) {
        CCharacter theChar = charGroup.at(charGroup_i);
        out_charGroup.push_back(theChar);
      }
    }

    if (0) {
      std::cout << "end:" << std::endl;
    }
  }

  Rect getSafeRect(Point2f center, float width, float height, Mat image) {
    int rows = image.rows;
    int cols = image.cols;

    float x = center.x;
    float y = center.y;

    float x_tl = (x - width / 2.f);
    float y_tl = (y - height / 2.f);

    float x_br = (x + width / 2.f);
    float y_br = (y + height / 2.f);

    x_tl = x_tl > 0.f ? x_tl : 0.f;
    y_tl = y_tl > 0.f ? y_tl : 0.f;
    x_br = x_br < (float) image.cols ? x_br : (float) image.cols;
    y_br = y_br < (float) image.rows ? y_br : (float) image.rows;

    Rect rect(Point((int) x_tl, int(y_tl)), Point((int) x_br, int(y_br)));
    return rect;
  }

// based on the assumptions: distance beween two nearby characters in plate are the same.
// add not found rect and combine two small and near rect.
  void reFoundAndCombineRect(std::vector<CCharacter> &mserCharacter, float min_thresh, float max_thresh,
                             Vec2i dist, Rect maxrect, Mat result) {
    if (mserCharacter.size() == 0) {
      return;
    }

    std::sort(mserCharacter.begin(), mserCharacter.end(),
              [](const CCharacter &r1, const CCharacter &r2) {
                return r1.getCenterPoint().x < r2.getCenterPoint().x;
              });

    int comparDist = dist[0] * dist[0] + dist[1] * dist[1];
    if (0) {
      std::cout << "comparDist:" << comparDist << std::endl;
    }

    std::vector<CCharacter> reCharacters;

    size_t mserCharacter_i = 0;
    for (; mserCharacter_i + 1 < mserCharacter.size(); mserCharacter_i++) {
      CCharacter leftChar = mserCharacter.at(mserCharacter_i);
      CCharacter rightChar = mserCharacter.at(mserCharacter_i + 1);

      Point leftCenter = leftChar.getCenterPoint();
      Point rightCenter = rightChar.getCenterPoint();

      int x_diff = leftCenter.x - rightCenter.x;
      int y_diff = leftCenter.y - rightCenter.y;

      // distance between two centers
      int distance2 = x_diff * x_diff + y_diff * y_diff;

      if (0) {
        std::cout << "distance2:" << distance2 << std::endl;
      }

      float ratio = (float) distance2 / (float) comparDist;
      if (ratio > max_thresh) {
        float x_add = (float) (leftCenter.x + rightCenter.x) / 2.f;
        float y_add = (float) (leftCenter.y + rightCenter.y) / 2.f;

        float width = (float) maxrect.width;
        float height = (float) maxrect.height;

        float x_tl = (x_add - width / 2.f);
        float y_tl = (y_add - height / 2.f);

        //Rect rect_add((int)x_tl, (int)y_tl, (int)width, (int)height);
        Rect rect_add = getSafeRect(Point2f(x_add, y_add), width, height, result);

        reCharacters.push_back(leftChar);

        CCharacter charAdd;
        charAdd.setCenterPoint(Point((int) x_add, (int) y_add));
        charAdd.setCharacterPos(rect_add);
        reCharacters.push_back(charAdd);

        if (1) {
          cv::rectangle(result, rect_add, Scalar(0, 128, 255));
        }
      } else if (ratio < min_thresh) {
        Rect rect_union = leftChar.getCharacterPos() | rightChar.getCharacterPos();
        /*float x_add = (float)(leftCenter.x + rightCenter.x) / 2.f;
        float y_add = (float)(leftCenter.y + rightCenter.y) / 2.f;*/
        int x_add = rect_union.tl().x + rect_union.width / 2;
        int y_add = rect_union.tl().y + rect_union.height / 2;

        CCharacter charAdd;
        charAdd.setCenterPoint(Point(x_add, y_add));
        charAdd.setCharacterPos(rect_union);
        reCharacters.push_back(charAdd);
        if (1) {
          cv::rectangle(result, rect_union, Scalar(0, 128, 255));
        }

        mserCharacter_i++;
      } else {
        reCharacters.push_back(leftChar);
      }
    }

    if (mserCharacter_i + 1 == mserCharacter.size()) {
      reCharacters.push_back(mserCharacter.at(mserCharacter_i));
    }

    mserCharacter = reCharacters;
  }


  void removeOutliers(std::vector<CCharacter> &charGroup, double thresh, Mat result) {
    std::vector<Point> points;
    Vec4f line;
    for (auto character : charGroup) {
      points.push_back(character.getCenterPoint());
    }

    fitLine(Mat(points), line, CV_DIST_L2, 0, 0.01, 0.01);

    float k = line[1] / line[0];
    float x_1 = line[2];
    float y_1 = line[3];
    float step = 100;
    cv::line(result, Point2f(x_1 - step, y_1 - k * step), Point2f(x_1 + step, k * step + y_1), Scalar(0, 0, 255));

    float a = k;
    float b = -1;
    float c = y_1 - k * x_1;
    float sumdistance = 0;
    for (auto character : charGroup) {
      Point center = character.getCenterPoint();
      float distance = (a * center.x + b * center.y + c) / std::sqrt(a * a + b * b);
      std::cout << "distance:" << distance << std::endl;
      sumdistance += distance;
    }
    float avgdistance = sumdistance / (float) charGroup.size();

    std::vector<CCharacter>::iterator it = charGroup.begin();
    for (; it != charGroup.end();) {
      Point center = it->getCenterPoint();
      float distance = a * center.x + b * center.y + c;
      float ratio = distance / avgdistance;
      std::cout << "ratio:" << ratio << std::endl;
      if (ratio > (float) thresh) {
        it = charGroup.erase(it);
      } else {
        ++it;
      }
    }
  }

//! use verify size to first generate char candidates
  void mserCharMatch(const Mat &src, std::vector<Mat> &match, std::vector<CPlate> &out_plateVec_blue,
                     std::vector<CPlate> &out_plateVec_yellow,
                     bool usePlateMser, std::vector<RotatedRect> &out_plateRRect_blue,
                     std::vector<RotatedRect> &out_plateRRect_yellow, int img_index,
                     bool showDebug) {
    Mat image = src;

    std::vector<std::vector<std::vector<Point>>> all_contours;
    std::vector<std::vector<Rect>> all_boxes;
    all_contours.resize(2);
    all_contours.at(0).reserve(1024);
    all_contours.at(1).reserve(1024);
    all_boxes.resize(2);
    all_boxes.at(0).reserve(1024);
    all_boxes.at(1).reserve(1024);

    match.resize(2);

    std::vector<Color> flags;
    flags.push_back(BLUE);
    flags.push_back(YELLOW);

    const int imageArea = image.rows * image.cols;
    const int delta = 1;
    //const int delta = CParams::instance()->getParam2i();;
    const int minArea = 30;
    const double maxAreaRatio = 0.05;

    Ptr<MSER2> mser;
    mser = MSER2::create(delta, minArea, int(maxAreaRatio * imageArea));
    mser->detectRegions(image, all_contours.at(0), all_boxes.at(0), all_contours.at(1), all_boxes.at(1));

    // mser detect
    // color_index = 0 : mser-, detect white characters, which is in blue plate.
    // color_index = 1 : mser+, detect dark characters, which is in yellow plate.

#pragma omp parallel for
    for (int color_index = 0; color_index < 2; color_index++) {
      Color the_color = flags.at(color_index);

      std::vector<CCharacter> charVec;
      charVec.reserve(128);

      match.at(color_index) = Mat::zeros(image.rows, image.cols, image.type());

      Mat result = image.clone();
      cvtColor(result, result, COLOR_GRAY2BGR);

      size_t size = all_contours.at(color_index).size();

      int char_index = 0;
      int char_size = 20;

      // Chinese plate has max 7 characters.
      const int char_max_count = 7;

      // verify char size and output to rects;
      for (size_t index = 0; index < size; index++) {
        Rect rect = all_boxes.at(color_index)[index];
        std::vector<Point> &contour = all_contours.at(color_index)[index];

        // sometimes a plate could be a mser rect, so we could
        // also use mser algorithm to find plate
        if (usePlateMser) {
          RotatedRect rrect = minAreaRect(Mat(contour));
          if (verifyRotatedPlateSizes(rrect)) {
            //rotatedRectangle(result, rrect, Scalar(255, 0, 0), 2);
            if (the_color == BLUE) out_plateRRect_blue.push_back(rrect);
            if (the_color == YELLOW) out_plateRRect_yellow.push_back(rrect);
          }
        }

        // find character
        if (verifyCharSizes(rect)) {
          Mat mserMat = adaptive_image_from_points(contour, rect, Size(char_size, char_size));
          Mat charInput = preprocessChar(mserMat, char_size);
          Rect charRect = rect;

          Point center(charRect.tl().x + charRect.width / 2, charRect.tl().y + charRect.height / 2);
          Mat tmpMat;
          double ostu_level = cv::threshold(image(charRect), tmpMat, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);

          //cv::circle(result, center, 3, Scalar(0, 0, 255), 2);

          // use judegMDOratio2 function to
          // remove the small lines in character like "zh-cuan"
          if (judegMDOratio2(image, rect, contour, result)) {
            CCharacter charCandidate;
            charCandidate.setCharacterPos(charRect);
            charCandidate.setCharacterMat(charInput);
            charCandidate.setOstuLevel(ostu_level);
            charCandidate.setCenterPoint(center);
            charCandidate.setIsChinese(false);
            charVec.push_back(charCandidate);
          }
        }
      }

      // improtant, use matrix multiplication to acclerate the
      // classification of many samples. use the character
      // score, we can use non-maximum superssion (nms) to
      // reduce the characters which are not likely to be true
      // charaters, and use the score to select the strong seed
      // of which the score is larger than 0.9
      CharsIdentify::instance()->classify(charVec);

      // use nms to remove the character are not likely to be true.
      double overlapThresh = 0.6;
      //double overlapThresh = CParams::instance()->getParam1f();
      NMStoCharacter(charVec, overlapThresh);
      charVec.shrink_to_fit();

      std::vector<CCharacter> strongSeedVec;
      strongSeedVec.reserve(64);
      std::vector<CCharacter> weakSeedVec;
      weakSeedVec.reserve(64);
      std::vector<CCharacter> littleSeedVec;
      littleSeedVec.reserve(64);

      //size_t charCan_size = charVec.size();
      for (auto charCandidate : charVec) {
        //CCharacter& charCandidate = charVec[char_index];
        Rect rect = charCandidate.getCharacterPos();
        double score = charCandidate.getCharacterScore();
        if (charCandidate.getIsStrong()) {
          strongSeedVec.push_back(charCandidate);
        } else if (charCandidate.getIsWeak()) {
          weakSeedVec.push_back(charCandidate);
          //cv::rectangle(result, rect, Scalar(255, 0, 255));
        } else if (charCandidate.getIsLittle()) {
          littleSeedVec.push_back(charCandidate);
          //cv::rectangle(result, rect, Scalar(255, 0, 255));
        }
      }

      std::vector<CCharacter> searchCandidate = charVec;

      // nms to srong seed, only leave the strongest one
      overlapThresh = 0.3;
      NMStoCharacter(strongSeedVec, overlapThresh);

      // merge chars to group
      std::vector<std::vector<CCharacter>> charGroupVec;
      charGroupVec.reserve(64);
      mergeCharToGroup(strongSeedVec, charGroupVec);

      // genenrate the line of the group
      // based on the assumptions , the mser rects which are
      // given high socre by character classifier could be no doubtly
      // be the characters in one plate, and we can use these characeters
      // to fit a line which is the middle line of the plate.
      std::vector<CPlate> plateVec;
      plateVec.reserve(16);
      for (auto charGroup : charGroupVec) {
        Rect plateResult = charGroup[0].getCharacterPos();
        std::vector<Point> points;
        points.reserve(32);

        Vec4f line;
        int maxarea = 0;
        Rect maxrect;
        double ostu_level_sum = 0;

        int leftx = image.cols;
        Point leftPoint(leftx, 0);
        int rightx = 0;
        Point rightPoint(rightx, 0);

        std::vector<CCharacter> mserCharVec;
        mserCharVec.reserve(32);

        // remove outlier CharGroup
        std::vector<CCharacter> roCharGroup;
        roCharGroup.reserve(32);

        removeRightOutliers(charGroup, roCharGroup, 0.2, 0.5, result);
        //roCharGroup = charGroup;

        for (auto character : roCharGroup) {
          Rect charRect = character.getCharacterPos();
          cv::rectangle(result, charRect, Scalar(0, 255, 0), 1);
          plateResult |= charRect;

          Point center(charRect.tl().x + charRect.width / 2, charRect.tl().y + charRect.height / 2);
          points.push_back(center);
          mserCharVec.push_back(character);
          //cv::circle(result, center, 3, Scalar(0, 255, 0), 2);

          ostu_level_sum += character.getOstuLevel();

          if (charRect.area() > maxarea) {
            maxrect = charRect;
            maxarea = charRect.area();
          }
          if (center.x < leftPoint.x) {
            leftPoint = center;
          }
          if (center.x > rightPoint.x) {
            rightPoint = center;
          }
        }

        double ostu_level_avg = ostu_level_sum / (double) roCharGroup.size();
        if (1 && showDebug) {
          std::cout << "ostu_level_avg:" << ostu_level_avg << std::endl;
        }
        float ratio_maxrect = (float) maxrect.width / (float) maxrect.height;

        if (points.size() >= 2 && ratio_maxrect >= 0.3) {
          fitLine(Mat(points), line, CV_DIST_L2, 0, 0.01, 0.01);

          float k = line[1] / line[0];
          //float angle = atan(k) * 180 / (float)CV_PI;
          //std::cout << "k:" << k << std::endl;
          //std::cout << "angle:" << angle << std::endl;
          //std::cout << "cos:" << 0.3 * cos(k) << std::endl;
          //std::cout << "ratio_maxrect:" << ratio_maxrect << std::endl;

          std::sort(mserCharVec.begin(), mserCharVec.end(),
                    [](const CCharacter &r1, const CCharacter &r2) {
                      return r1.getCharacterPos().tl().x < r2.getCharacterPos().tl().x;
                    });

          CCharacter midChar = mserCharVec.at(int(mserCharVec.size() / 2.f));
          Rect midRect = midChar.getCharacterPos();
          Point midCenter(midRect.tl().x + midRect.width / 2, midRect.tl().y + midRect.height / 2);

          int mindist = 7 * maxrect.width;
          std::vector<Vec2i> distVecVec;
          distVecVec.reserve(32);

          Vec2i mindistVec;
          Vec2i avgdistVec;

          // computer the dist which is the distacne between
          // two near characters in the plate, use dist we can
          // judege how to computer the max search range, and choose the
          // best location of the sliding window in the next steps.
          for (size_t mser_i = 0; mser_i + 1 < mserCharVec.size(); mser_i++) {
            Rect charRect = mserCharVec.at(mser_i).getCharacterPos();
            Point center(charRect.tl().x + charRect.width / 2, charRect.tl().y + charRect.height / 2);

            Rect charRectCompare = mserCharVec.at(mser_i + 1).getCharacterPos();
            Point centerCompare(charRectCompare.tl().x + charRectCompare.width / 2,
                                charRectCompare.tl().y + charRectCompare.height / 2);

            int dist = charRectCompare.x - charRect.x;
            Vec2i distVec(charRectCompare.x - charRect.x, charRectCompare.y - charRect.y);
            distVecVec.push_back(distVec);

            //if (dist < mindist) {
            //  mindist = dist;
            //  mindistVec = distVec;
            //}
          }

          std::sort(distVecVec.begin(), distVecVec.end(),
                    [](const Vec2i &r1, const Vec2i &r2) {
                      return r1[0] < r2[0];
                    });

          avgdistVec = distVecVec.at(int((distVecVec.size() - 1) / 2.f));

          //float step = 10.f * (float)maxrect.width;
          //float step = (float)mindistVec[0];
          float step = (float) avgdistVec[0];

          //cv::line(result, Point2f(line[2] - step, line[3] - k*step), Point2f(line[2] + step, k*step + line[3]), Scalar(255, 255, 255));
          cv::line(result, Point2f(midCenter.x - step, midCenter.y - k * step),
                   Point2f(midCenter.x + step, k * step + midCenter.y), Scalar(255, 255, 255));
          //cv::circle(result, leftPoint, 3, Scalar(0, 0, 255), 2);

          CPlate plate;
          plate.setPlateLeftPoint(leftPoint);
          plate.setPlateRightPoint(rightPoint);

          plate.setPlateLine(line);
          plate.setPlatDistVec(avgdistVec);
          plate.setOstuLevel(ostu_level_avg);

          plate.setPlateMergeCharRect(plateResult);
          plate.setPlateMaxCharRect(maxrect);
          plate.setMserCharacter(mserCharVec);
          plateVec.push_back(plate);
        }
      }

      // use strong seed to construct the first shape of the plate,
      // then we need to find characters which are the weak seed.
      // because we use strong seed to build the middle lines of the plate,
      // we can simply use this to consider weak seeds only lie in the
      // near place of the middle line
      for (auto plate : plateVec) {
        Vec4f line = plate.getPlateLine();
        Point leftPoint = plate.getPlateLeftPoint();
        Point rightPoint = plate.getPlateRightPoint();

        Rect plateResult = plate.getPlateMergeCharRect();
        Rect maxrect = plate.getPlateMaxCharRect();
        Vec2i dist = plate.getPlateDistVec();
        double ostu_level = plate.getOstuLevel();

        std::vector<CCharacter> mserCharacter = plate.getCopyOfMserCharacters();
        mserCharacter.reserve(16);

        float k = line[1] / line[0];
        float x_1 = line[2];
        float y_1 = line[3];

        std::vector<CCharacter> searchWeakSeedVec;
        searchWeakSeedVec.reserve(16);

        std::vector<CCharacter> searchRightWeakSeed;
        searchRightWeakSeed.reserve(8);
        std::vector<CCharacter> searchLeftWeakSeed;
        searchLeftWeakSeed.reserve(8);

        std::vector<CCharacter> slideRightWindow;
        slideRightWindow.reserve(8);
        std::vector<CCharacter> slideLeftWindow;
        slideLeftWindow.reserve(8);

        // draw weak seed and little seed from line;
        // search for mser rect
        if (1 && showDebug) {
          std::cout << "search for mser rect:" << std::endl;
        }

        if (0 && showDebug) {
          std::stringstream ss(std::stringstream::in | std::stringstream::out);
          ss << "resources/image/tmp/" << img_index << "_1_" << "searcgMserRect.jpg";
          imwrite(ss.str(), result);
        }
        if (1 && showDebug) {
          std::cout << "mserCharacter:" << mserCharacter.size() << std::endl;
        }

        // if the count of strong seed is larger than max count, we dont need
        // all the next steps, if not, we first need to search the weak seed in
        // the same line as the strong seed. The judge condition contains the distance
        // between strong seed and weak seed , and the rect simily of each other to improve
        // the roubustnedd of the seed growing algorithm.
        if (mserCharacter.size() < char_max_count) {
          double thresh1 = 0.15;
          double thresh2 = 2.0;
          searchWeakSeed(searchCandidate, searchRightWeakSeed, thresh1, thresh2, line, rightPoint,
                         maxrect, plateResult, result, CharSearchDirection::RIGHT);
          if (1 && showDebug) {
            std::cout << "searchRightWeakSeed:" << searchRightWeakSeed.size() << std::endl;
          }
          for (auto seed : searchRightWeakSeed) {
            cv::rectangle(result, seed.getCharacterPos(), Scalar(255, 0, 0), 1);
            mserCharacter.push_back(seed);
          }

          searchWeakSeed(searchCandidate, searchLeftWeakSeed, thresh1, thresh2, line, leftPoint,
                         maxrect, plateResult, result, CharSearchDirection::LEFT);
          if (1 && showDebug) {
            std::cout << "searchLeftWeakSeed:" << searchLeftWeakSeed.size() << std::endl;
          }
          for (auto seed : searchLeftWeakSeed) {
            cv::rectangle(result, seed.getCharacterPos(), Scalar(255, 0, 0), 1);
            mserCharacter.push_back(seed);
          }
        }

        // sometimes the weak seed is in the middle of the strong seed.
        // and sometimes two strong seed are actually the two parts of one character.
        // because we only consider the weak seed in the left and right direction of strong seed.
        // now we examine all the strong seed and weak seed. not only to find the seed in the middle,
        // but also to combine two seed which are parts of one character to one seed.
        // only by this process, we could use the seed count as the condition to judge if or not to use slide window.
        float min_thresh = 0.3f;
        float max_thresh = 2.5f;
        reFoundAndCombineRect(mserCharacter, min_thresh, max_thresh, dist, maxrect, result);

        // if the characters count is less than max count
        // this means the mser rect in the lines are not enough.
        // sometimes there are still some characters could not be captured by mser algorithm,
        // such as blur, low light ,and some chinese characters like zh-cuan.
        // to handle this ,we use a simple slide window method to find them.
        if (mserCharacter.size() < char_max_count) {
          if (1 && showDebug) {
            std::cout << "search chinese:" << std::endl;
            std::cout << "judege the left is chinese:" << std::endl;
          }

          // if the left most character is chinese, this means
          // that must be the first character in chinese plate,
          // and we need not to do a slide window to left. So,
          // the first thing is to judge the left charcater is
          // or not the chinese.
          bool leftIsChinese = false;
          if (1) {
            std::sort(mserCharacter.begin(), mserCharacter.end(),
                      [](const CCharacter &r1, const CCharacter &r2) {
                        return r1.getCharacterPos().tl().x < r2.getCharacterPos().tl().x;
                      });

            CCharacter leftChar = mserCharacter[0];

            //Rect theRect = adaptive_charrect_from_rect(leftChar.getCharacterPos(), image.cols, image.rows);
            Rect theRect = leftChar.getCharacterPos();
            //cv::rectangle(result, theRect, Scalar(255, 0, 0), 1);

            Mat region = image(theRect);
            Mat binary_region;

            ostu_level = cv::threshold(region, binary_region, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
            if (1 && showDebug) {
              std::cout << "left : ostu_level:" << ostu_level << std::endl;
            }
            //plate.setOstuLevel(ostu_level);

            Mat charInput = preprocessChar(binary_region, char_size);
            if (0 /*&& showDebug*/) {
              imshow("charInput", charInput);
              waitKey(0);
              destroyWindow("charInput");
            }

            std::string label = "";
            float maxVal = -2.f;
            leftIsChinese = CharsIdentify::instance()->isCharacter(charInput, label, maxVal, true);
            //auto character = CharsIdentify::instance()->identifyChinese(charInput, maxVal, leftIsChinese);
            //label = character.second;
            if (0 /* && showDebug*/) {
              std::cout << "isChinese:" << leftIsChinese << std::endl;
              std::cout << "chinese:" << label;
              std::cout << "__score:" << maxVal << std::endl;
            }
          }

          // if the left most character is not a chinese,
          // this means we meed to slide a window to find the missed mser rect.
          // search for sliding window
          float ratioWindow = 0.4f;
          //float ratioWindow = CParams::instance()->getParam3f();
          float threshIsCharacter = 0.8f;
          //float threshIsCharacter = CParams::instance()->getParam3f();
          if (!leftIsChinese) {
            slideWindowSearch(image, slideLeftWindow, line, leftPoint, dist, ostu_level, ratioWindow, threshIsCharacter,
                              maxrect, plateResult, CharSearchDirection::LEFT, true, result);
            if (1 && showDebug) {
              std::cout << "slideLeftWindow:" << slideLeftWindow.size() << std::endl;
            }
            for (auto window : slideLeftWindow) {
              cv::rectangle(result, window.getCharacterPos(), Scalar(0, 0, 255), 1);
              mserCharacter.push_back(window);
            }
          }
        }

        // if we still have less than max count characters,
        // we need to slide a window to right to search for the missed mser rect.
        if (mserCharacter.size() < char_max_count) {
          // change ostu_level
          float ratioWindow = 0.4f;
          //float ratioWindow = CParams::instance()->getParam3f();
          float threshIsCharacter = 0.8f;
          //float threshIsCharacter = CParams::instance()->getParam3f();
          slideWindowSearch(image, slideRightWindow, line, rightPoint, dist, plate.getOstuLevel(), ratioWindow,
                            threshIsCharacter,
                            maxrect, plateResult, CharSearchDirection::RIGHT, false, result);
          if (1 && showDebug) {
            std::cout << "slideRightWindow:" << slideRightWindow.size() << std::endl;
          }
          for (auto window : slideRightWindow) {
            cv::rectangle(result, window.getCharacterPos(), Scalar(0, 0, 255), 1);
            mserCharacter.push_back(window);
          }
        }

        // computer the plate angle
        float angle = atan(k) * 180 / (float) CV_PI;
        if (1 && showDebug) {
          std::cout << "k:" << k << std::endl;
          std::cout << "angle:" << angle << std::endl;
        }

        // the plateResult rect need to be enlarge to contains all the plate,
        // not only the character area.
        float widthEnlargeRatio = 1.15f; //1.15f;
        float heightEnlargeRatio = 1.25f; //1.25f;
        RotatedRect platePos(
            Point2f((float) plateResult.x + plateResult.width / 2.f, (float) plateResult.y + plateResult.height / 2.f),
            Size2f(plateResult.width * widthEnlargeRatio, maxrect.height * heightEnlargeRatio), angle);

        // justify the size is likely to be a plate size.
        if (verifyRotatedPlateSizes(platePos)) {
          rotatedRectangle(result, platePos, Scalar(0, 0, 255), 1);

          plate.setPlatePos(platePos);
          plate.setPlateColor(the_color);
          plate.setPlateLocateType(CMSER);

          if (the_color == BLUE) out_plateVec_blue.push_back(plate);
          if (the_color == YELLOW) out_plateVec_yellow.push_back(plate);
        }

        // use deskew to rotate the image, so we need the binary image.
        if (1) {
          for (auto mserChar : mserCharacter) {
            Rect rect = mserChar.getCharacterPos();
            match.at(color_index)(rect) = 255;
          }
          cv::line(match.at(color_index), rightPoint, leftPoint, Scalar(255));
        }
      }

      if (0 /*&& showDebug*/) {
        imshow("result", result);
        waitKey(0);
        destroyWindow("result");
      }

      if (0) {
        imshow("match", match.at(color_index));
        waitKey(0);
        destroyWindow("match");
      }

      if (1) {
        std::stringstream ss(std::stringstream::in | std::stringstream::out);
        ss << "resources/image/tmp/plateDetect/plate_" << img_index << "_" << the_color << ".jpg";
        imwrite(ss.str(), result);
      }
    }


  }

// this spatial_ostu algorithm are robust to 
// the plate which has the same light shine, which is that
// the light in the left of the plate is strong than the right.
  void spatial_ostu(InputArray _src, int grid_x, int grid_y, Color type) {
    Mat src = _src.getMat();

    int width = src.cols / grid_x;
    int height = src.rows / grid_y;

    // iterate through grid
    for (int i = 0; i < grid_y; i++) {
      for (int j = 0; j < grid_x; j++) {
        Mat src_cell = Mat(src, Range(i * height, (i + 1) * height), Range(j * width, (j + 1) * width));
        if (type == BLUE) {
          cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
        } else if (type == YELLOW) {
          cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV);
        } else if (type == WHITE) {
          cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV);
        } else {
          cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
        }
      }
    }
  }


  bool mat_valid_position(const Mat &mat, int row, int col) {
    return row >= 0 && col >= 0 && row < mat.rows && col < mat.cols;
  }


  template<class T>
  static void mat_set_invoke(Mat &mat, int row, int col, const Scalar &value) {
    if (1 == mat.channels()) {
      mat.at<T>(row, col) = (T) value.val[0];
    } else if (3 == mat.channels()) {
      T *ptr_src = mat.ptr<T>(row, col);
      *ptr_src++ = (T) value.val[0];
      *ptr_src++ = (T) value.val[1];
      *ptr_src = (T) value.val[2];
    } else if (4 == mat.channels()) {
      T *ptr_src = mat.ptr<T>(row, col);
      *ptr_src++ = (T) value.val[0];
      *ptr_src++ = (T) value.val[1];
      *ptr_src++ = (T) value.val[2];
      *ptr_src = (T) value.val[3];
    }
  }

  void setPoint(Mat &mat, int row, int col, const Scalar &value) {
    if (CV_8U == mat.depth()) {
      mat_set_invoke<uchar>(mat, row, col, value);
    } else if (CV_8S == mat.depth()) {
      mat_set_invoke<char>(mat, row, col, value);
    } else if (CV_16U == mat.depth()) {
      mat_set_invoke<ushort>(mat, row, col, value);
    } else if (CV_16S == mat.depth()) {
      mat_set_invoke<short>(mat, row, col, value);
    } else if (CV_32S == mat.depth()) {
      mat_set_invoke<int>(mat, row, col, value);
    } else if (CV_32F == mat.depth()) {
      mat_set_invoke<float>(mat, row, col, value);
    } else if (CV_64F == mat.depth()) {
      mat_set_invoke<double>(mat, row, col, value);
    }
  }

  Rect adaptive_charrect_from_rect(const Rect &rect, int maxwidth, int maxheight, bool useExtendHeight) {
    int expendWidth = 0;
    int extendHeight = 0;

    if (rect.height > 3 * rect.width) {
      expendWidth = int((int(rect.height * 0.5f) - rect.width) * 0.5f);
      if (useExtendHeight) {
        extendHeight = int(rect.height * 0.3f);
      }
    }

    //Rect resultRect(rect.tl().x - expendWidth, rect.tl().y,
    //  rect.width + expendWidth * 2, rect.height);

    int tlx = rect.tl().x - expendWidth > 0 ? rect.tl().x - expendWidth : 0;
    int tly = rect.tl().y - extendHeight > 0 ? rect.tl().y - extendHeight : 0;

    int brx = rect.br().x + expendWidth < maxwidth ? rect.br().x + expendWidth : maxwidth;
    int bry = rect.br().y + extendHeight < maxheight ? rect.br().y + extendHeight : maxheight;

    Rect resultRect(tlx, tly, brx - tlx, bry - tly);
    return resultRect;
  }


  Mat adaptive_image_from_points(const std::vector<Point> &points,
                                 const Rect &rect, const Size &size,
                                 const Scalar &backgroundColor /* = ml_color_white */,
                                 const Scalar &forgroundColor /* = ml_color_black */, bool gray /* = true */) {
    int expendHeight = 0;
    int expendWidth = 0;

    if (rect.width > rect.height) {
      expendHeight = (rect.width - rect.height) / 2;
    } else if (rect.height > rect.width) {
      expendWidth = (rect.height - rect.width) / 2;
    }

    Mat image(rect.height + expendHeight * 2, rect.width + expendWidth * 2, gray ? CV_8UC1 : CV_8UC3, backgroundColor);

    for (int i = 0; i < (int) points.size(); ++i) {
      Point point = points[i];
      Point currentPt(point.x - rect.tl().x + expendWidth, point.y - rect.tl().y + expendHeight);
      if (mat_valid_position(image, currentPt.y, currentPt.x)) {
        setPoint(image, currentPt.y, currentPt.x, forgroundColor);
      }
    }

    Mat result;
    cv::resize(image, result, size, 0, 0, INTER_NEAREST);

    return result;
  }

//  calc safe Rect
//  if not exit, return false

  bool calcSafeRect(const RotatedRect &roi_rect, const Mat &src,
                    Rect_<float> &safeBoundRect) {
    Rect_<float> boudRect = roi_rect.boundingRect();

    float tl_x = boudRect.x > 0 ? boudRect.x : 0;
    float tl_y = boudRect.y > 0 ? boudRect.y : 0;

    float br_x = boudRect.x + boudRect.width < src.cols
                 ? boudRect.x + boudRect.width - 1
                 : src.cols - 1;
    float br_y = boudRect.y + boudRect.height < src.rows
                 ? boudRect.y + boudRect.height - 1
                 : src.rows - 1;

    float roi_width = br_x - tl_x;
    float roi_height = br_y - tl_y;

    if (roi_width <= 0 || roi_height <= 0) return false;

    //  a new rect not out the range of mat

    safeBoundRect = Rect_<float>(tl_x, tl_y, roi_width, roi_height);

    return true;
  }

  bool calcSafeRect(const RotatedRect &roi_rect, const int width, const int height,
                    Rect_<float> &safeBoundRect) {
    Rect_<float> boudRect = roi_rect.boundingRect();

    float tl_x = boudRect.x > 0 ? boudRect.x : 0;
    float tl_y = boudRect.y > 0 ? boudRect.y : 0;

    float br_x = boudRect.x + boudRect.width < width
                 ? boudRect.x + boudRect.width - 1
                 : width - 1;
    float br_y = boudRect.y + boudRect.height < height
                 ? boudRect.y + boudRect.height - 1
                 : height - 1;

    float roi_width = br_x - tl_x;
    float roi_height = br_y - tl_y;

    if (roi_width <= 0 || roi_height <= 0) return false;

    //  a new rect not out the range of mat

    safeBoundRect = Rect_<float>(tl_x, tl_y, roi_width, roi_height);

    return true;
  }


  Mat uniformResize(const Mat &result, float &scale) {
    const int RESULTWIDTH = kShowWindowWidth;   // 640 930
    const int RESULTHEIGHT = kShowWindowHeight;  // 540 710

    Mat img_window;
    img_window.create(RESULTHEIGHT, RESULTWIDTH, CV_8UC3);

    int nRows = result.rows;
    int nCols = result.cols;

    Mat result_resize;
    if (nCols <= img_window.cols && nRows <= img_window.rows) {
      result_resize = result;
    } else if (nCols > img_window.cols && nRows <= img_window.rows) {
      scale = float(img_window.cols) / float(nCols);
      resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA);

    } else if (nCols <= img_window.cols && nRows > img_window.rows) {
      scale = float(img_window.rows) / float(nRows);
      resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA);

    } else if (nCols > img_window.cols && nRows > img_window.rows) {
      float scale1 = float(img_window.cols) / float(nCols);
      float scale2 = float(img_window.rows) / float(nRows);
      scale = scale1 < scale2 ? scale1 : scale2;
      resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA);
    } else {
      result_resize = result;
    }
    return result_resize;
  }

  Mat uniformResizePlates (const Mat &result, float &scale) {
    const int RESULTWIDTH = kPlateResizeWidth;   // 640 930
    const int RESULTHEIGHT = kPlateResizeHeight;  // 540 710

    Mat img_window;
    img_window.create(RESULTHEIGHT, RESULTWIDTH, CV_8UC3);

    int nRows = result.rows;
    int nCols = result.cols;

    Mat result_resize;
    if (nCols <= img_window.cols && nRows <= img_window.rows) {
      result_resize = result;
    }
    else if (nCols > img_window.cols && nRows <= img_window.rows) {
      scale = float(img_window.cols) / float(nCols);
      resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA);

    }
    else if (nCols <= img_window.cols && nRows > img_window.rows) {
      scale = float(img_window.rows) / float(nRows);
      resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA);

    }
    else if (nCols > img_window.cols && nRows > img_window.rows) {
      float scale1 = float(img_window.cols) / float(nCols);
      float scale2 = float(img_window.rows) / float(nRows);
      scale = scale1 < scale2 ? scale1 : scale2;
      resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA);
    }
    else {
      result_resize = result;
    }
    return result_resize;
  }



  void showDectectResults(const Mat &img, const vector<CPlate> &plateVec, size_t num) {
    int index = 0;
    if (1) {
      Mat result;
      img.copyTo(result);
      for (size_t j = 0; j < plateVec.size(); j++) {
        // add plates to left corner
        const CPlate& item = plateVec.at(j);
        Mat plateMat = item.getPlateMat();

        int height = 36;
        int width = 136;
        if (height * index + height < result.rows) {
          Mat imageRoi = result(Rect(0, 0 + height * index, width, height));
          addWeighted(imageRoi, 0, plateMat, 1, 0, imageRoi);
        }
        index++;

        // draw the bouding box
        RotatedRect theRect = item.getPlatePos();
        float scale = item.getPlateScale();
        RotatedRect minRect = scaleBackRRect(theRect, scale);

        Point2f rect_points[4];
        minRect.points(rect_points);
        Scalar lineColor = Scalar(255, 255, 255);
        if (item.getPlateLocateType() == SOBEL) lineColor = Scalar(255, 0, 0);
        if (item.getPlateLocateType() == COLOR) lineColor = Scalar(0, 255, 0);
        if (item.getPlateLocateType() == CMSER) lineColor = Scalar(0, 0, 255);

        for (int j = 0; j < 4; j++)
          line(result, rect_points[j], rect_points[(j + 1) % 4], lineColor, 2, 8);
      }
      showResult(result);
    }
  }

  Mat showResult(const Mat &result, int img_index) {
    namedWindow("EasyPR", CV_WINDOW_AUTOSIZE);

    const int RESULTWIDTH = kShowWindowWidth;   // 640 930
    const int RESULTHEIGHT = kShowWindowHeight;  // 540 710

    Mat img_window;
    img_window.create(RESULTHEIGHT, RESULTWIDTH, CV_8UC3);

    int nRows = result.rows;
    int nCols = result.cols;

    Mat result_resize;
    if (nCols <= img_window.cols && nRows <= img_window.rows) {
      result_resize = result;

    } else if (nCols > img_window.cols && nRows <= img_window.rows) {
      float scale = float(img_window.cols) / float(nCols);
      resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA);

    } else if (nCols <= img_window.cols && nRows > img_window.rows) {
      float scale = float(img_window.rows) / float(nRows);
      resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA);

    } else if (nCols > img_window.cols && nRows > img_window.rows) {
      float scale1 = float(img_window.cols) / float(nCols);
      float scale2 = float(img_window.rows) / float(nRows);
      float scale = scale1 < scale2 ? scale1 : scale2;
      resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA);
    }
    else {
      result_resize = result;
    }

    Mat imageRoi = img_window(Rect((RESULTWIDTH - result_resize.cols) / 2,
                                   (RESULTHEIGHT - result_resize.rows) / 2,
                                   result_resize.cols, result_resize.rows));
    addWeighted(imageRoi, 0, result_resize, 1, 0, imageRoi);

    if (1) {
      imshow("EasyPR", img_window);
      waitKey(0);
      destroyWindow("EasyPR");
    }

    if (1) {
      std::stringstream ss(std::stringstream::in | std::stringstream::out);
      ss << "resources/image/tmp/Result/plate_" << img_index << ".jpg";
      imwrite(ss.str(), img_window);
    }

    return img_window;
  }

  Rect rectEnlarge(const Rect &src, const int mat_width, const int mat_height) {
    float w = (float) src.width;
    float h = (float) src.height;
    // enlarge the rect,
    // width to 120%
    // height to 105%
    float new_w = w * 1.2f;
    float new_h = h * 1.05f;

    Rect_<float> boudRect;
    boudRect.x = (float) src.x - w * 0.1f;
    boudRect.y = (float) src.y - h * 0.025f;
    boudRect.width = new_w;
    boudRect.height = new_h;

    float tl_x = boudRect.x > 0 ? boudRect.x : 0;
    float tl_y = boudRect.y > 0 ? boudRect.y : 0;

    float br_x = boudRect.x + boudRect.width - 1 <= mat_width - 1
                 ? boudRect.x + boudRect.width - 1
                 : mat_width - 1;
    float br_y = boudRect.y + boudRect.height - 1 < mat_height - 1
                 ? boudRect.y + boudRect.height - 1
                 : mat_height - 1;

    float roi_width = br_x - tl_x + 1;
    float roi_height = br_y - tl_y + 1;

    Rect dst(0, 0, 0, 0);
    if (roi_width <= 0 || roi_height <= 0)
      return src;

    //! a new rect not out the range of mat
    dst = Rect_<float>(tl_x, tl_y, roi_width, roi_height);
    return dst;

  }

  Rect rectFit(const Rect &src, const int mat_width, const int mat_height) {
    float w = (float)src.width;
    float h = (float)src.height;
    float new_w = h * 0.5f;
    float new_h = h * 1.05f;

    if (new_w <= w || new_h <= h) {
      return src;
    }

    float ex_w = (new_w - w) * 0.5f;
    float ex_h = (new_h - h) * 0.5f;

    Rect_<float> boudRect;
    boudRect.x = (float)src.x - ex_w;
    boudRect.y = (float)src.y - ex_h;
    boudRect.width = new_w;
    boudRect.height = new_h;

    float tl_x = boudRect.x > 0 ? boudRect.x : 0;
    float tl_y = boudRect.y > 0 ? boudRect.y : 0;

    float br_x = boudRect.x + boudRect.width - 1 <= mat_width - 1
      ? boudRect.x + boudRect.width - 1
      : mat_width - 1;
    float br_y = boudRect.y + boudRect.height - 1 < mat_height - 1
      ? boudRect.y + boudRect.height - 1
      : mat_height - 1;

    float roi_width = br_x - tl_x + 1;
    float roi_height = br_y - tl_y + 1;

    Rect dst(0, 0, 0, 0);
    if (roi_width <= 2 || roi_height <= 2)
      return src;

    //! a new rect not out the range of mat
    dst = Rect_<float>(tl_x, tl_y, roi_width - 1, roi_height - 1);
    return dst;

  }


  void writeTempImage(const Mat &outImg, const string path, int index) {
    std::stringstream ss(std::stringstream::in | std::stringstream::out);
    time_t t = time(0);  // get time now
    struct tm *now = localtime(&t);
    char buf[80];
    strftime(buf, sizeof(buf), "%Y-%m-%d %H_%M_%S", now);
    ss << "resources/image/tmp/" << path << "_" << std::string(buf) << "_" << index << ".jpg";
    imwrite(ss.str(), outImg);
  }
}