#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 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 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(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 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(i, j) != img.at(i, j + 1)) jumpCount++; if (img.at(i, j) == 255) { whiteCount++; } } jump.at(i) = (float) jumpCount; } for (int i = 0; i < img.rows; i++) { if (jump.at(i) <= x) { for (int j = 0; j < img.cols; j++) { img.at(i, j) = 0; } } } } bool clearLiuDing(Mat &img) { std::vector 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(i, j) != img.at(i, j + 1)) jumpCount++; if (img.at(i, j) == 255) { whiteCount++; } } jump.at(i) = (float) jumpCount; } int iCount = 0; for (int i = 0; i < img.rows; i++) { fJump.push_back(jump.at(i)); if (jump.at(i) >= 16 && jump.at(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(i) <= x) { for (int j = 0; j < img.cols; j++) { img.at(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(i, j) == img.at(i, j + 1)) nojumpCount++; if (nojumpCount > noJunpCount_thresh) { nojumpCount = 0; isBorder = 1; break; } } boder.at(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(i) == 1) { minMatTop = i; } } for (int i = r - 1; i > maxtop; i--) { if (boder.at(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(i, j) != mask.at(i, j + 1)) jumpCount++; if ((int) mask.at(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(i, j) != mask.at(i, j + 1)) jumpCount++; if (mask.at(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 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(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(i)); for (int j = height - 1; j >= 0; j--) { if (height - j <= len) show.at(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(0, 2) = float(m / 2 - w / 2); transformMat.at(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 &inVec, double overlap) { std::sort(inVec.begin(), inVec.end()); std::vector::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::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 vecRect, std::vector> &charGroupVec) { std::vector 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 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 &charVec, std::vector &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 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 &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 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 &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_ safe_rect1; calcSafeRect(rrect1, width, height, safe_rect1); Rect_ 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_ safe_rect1; calcSafeRect(rrect1, width, height, safe_rect1); Rect_ 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 &charGroup, std::vector &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 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 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 &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 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 &charGroup, double thresh, Mat result) { std::vector 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::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 &match, std::vector &out_plateVec_blue, std::vector &out_plateVec_yellow, bool usePlateMser, std::vector &out_plateRRect_blue, std::vector &out_plateRRect_yellow, int img_index, bool showDebug) { Mat image = src; std::vector>> all_contours; std::vector> 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 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 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 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 &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 strongSeedVec; strongSeedVec.reserve(64); std::vector weakSeedVec; weakSeedVec.reserve(64); std::vector 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 searchCandidate = charVec; // nms to srong seed, only leave the strongest one overlapThresh = 0.3; NMStoCharacter(strongSeedVec, overlapThresh); // merge chars to group std::vector> 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 plateVec; plateVec.reserve(16); for (auto charGroup : charGroupVec) { Rect plateResult = charGroup[0].getCharacterPos(); std::vector 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 mserCharVec; mserCharVec.reserve(32); // remove outlier CharGroup std::vector 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 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 mserCharacter = plate.getCopyOfMserCharacters(); mserCharacter.reserve(16); float k = line[1] / line[0]; float x_1 = line[2]; float y_1 = line[3]; std::vector searchWeakSeedVec; searchWeakSeedVec.reserve(16); std::vector searchRightWeakSeed; searchRightWeakSeed.reserve(8); std::vector searchLeftWeakSeed; searchLeftWeakSeed.reserve(8); std::vector slideRightWindow; slideRightWindow.reserve(8); std::vector 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 static void mat_set_invoke(Mat &mat, int row, int col, const Scalar &value) { if (1 == mat.channels()) { mat.at(row, col) = (T) value.val[0]; } else if (3 == mat.channels()) { T *ptr_src = mat.ptr(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(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(mat, row, col, value); } else if (CV_8S == mat.depth()) { mat_set_invoke(mat, row, col, value); } else if (CV_16U == mat.depth()) { mat_set_invoke(mat, row, col, value); } else if (CV_16S == mat.depth()) { mat_set_invoke(mat, row, col, value); } else if (CV_32S == mat.depth()) { mat_set_invoke(mat, row, col, value); } else if (CV_32F == mat.depth()) { mat_set_invoke(mat, row, col, value); } else if (CV_64F == mat.depth()) { mat_set_invoke(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 &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_ &safeBoundRect) { Rect_ 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_(tl_x, tl_y, roi_width, roi_height); return true; } bool calcSafeRect(const RotatedRect &roi_rect, const int width, const int height, Rect_ &safeBoundRect) { Rect_ 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_(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 &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_ 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_(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_ 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_(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); } }