// 这个文件定义了EasyPR里所有plate判断的通用函数 // 所属命名空间为easypr // 这个部分中的函数轻易不要改动 #include "easypr/core/core_func.h" #include "easypr/core/plate.hpp" #include "easypr/core/chars_identify.h" #include "easypr/config.h" using namespace cv; namespace easypr { //! 根据一幅图像与颜色模板获取对应的二值图 //! 输入RGB图像, 颜色模板(蓝色、黄色) //! 输出灰度图(只有0和255两个值,255代表匹配,0代表不匹配) Mat colorMatch(const Mat &src, Mat &match, const Color r, const bool adaptive_minsv) { // S和V的最小值由adaptive_minsv这个bool值判断 // 如果为true,则最小值取决于H值,按比例衰减 // 如果为false,则不再自适应,使用固定的最小值minabs_sv // 默认为false const float max_sv = 255; const float minref_sv = 64; const float minabs_sv = 95; // blue的H范围 const int min_blue = 100; // 100 const int max_blue = 140; // 140 // yellow的H范围 const int min_yellow = 15; // 15 const int max_yellow = 40; // 40 // white的H范围 const int min_white = 0; // 15 const int max_white = 30; // 40 Mat src_hsv; // 转到HSV空间进行处理,颜色搜索主要使用的是H分量进行蓝色与黄色的匹配工作 cvtColor(src, src_hsv, CV_BGR2HSV); std::vector hsvSplit; split(src_hsv, hsvSplit); equalizeHist(hsvSplit[2], hsvSplit[2]); merge(hsvSplit, src_hsv); //匹配模板基色,切换以查找想要的基色 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; //图像数据列需要考虑通道数的影响; 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; // S和V的最小值由adaptive_minsv这个bool值判断 // 如果为true,则最小值取决于H值,按比例衰减 // 如果为false,则不再自适应,使用固定的最小值minabs_sv 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; // 获取颜色匹配后的二值灰度图 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; } //! 判断一个车牌的颜色 //! 输入车牌mat与颜色模板 //! 返回true或fasle 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; } } } } //去除车牌上方的钮钉 //计算每行元素的阶跃数,如果小于X认为是柳丁,将此行全部填0(涂黑) // X的推荐值为,可根据实际调整 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) { //车牌字符满足一定跳变条件 iCount++; } } ////这样的不是车牌 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 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,找到上下边界 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); //把rect中的数据 考取到dstMat的中间 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; //上下 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 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中 mhist.at(j) = countOfBigValue(data, 20); } // Normalize histogram double min, max; minMaxLoc(mhist, &min, &max); //用mhist直方图中的最大值,归一化直方图 if (max > 0) mhist.convertTo(mhist, -1, 1.0f / max, 0); return mhist; } 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; 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); 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; } //! 非极大值抑制 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()); if (r > 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++) { 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; 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); if (height_diff_ratio < thresh1) { 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, 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(0.5 * 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; 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() > 0.8 && 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){ Mat mser = image(rect); Mat mser_mat; threshold(mser, mser_mat, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); Rect normalRect = adaptive_charrect_from_rect(rect, image.cols, image.rows); Mat region = image(normalRect); Mat thresh_mat; threshold(region, thresh_mat, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); // count mser diff ratio int countdiff = countNonZero(thresh_mat) - countNonZero(mser_mat); float MserDiffOstuRatio = float(countdiff) / float(rect.area()); if (MserDiffOstuRatio > 1) { /*std::cout << "MserDiffOstuRatio:" << MserDiffOstuRatio << std::endl; imshow("tmpMat", mser_mat); waitKey(0); imshow("tmpMat", thresh_mat); waitKey(0);*/ cv::rectangle(result, rect, Scalar(0, 0, 0), 2); return false; } return true; } bool judegMDOratio(const Mat& image, const Rect& rect, std::vector& contour, Mat& result){ Rect normalRect = adaptive_charrect_from_rect(rect, image.cols, image.rows); //cv::rectangle(result, normalRect, Scalar(0, 0, 255), 1); Mat mser_mat = adaptive_image_from_points(contour, normalRect, normalRect.size()); Mat region = image(normalRect); Mat thresh_mat; threshold(region, thresh_mat, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); // count mser diff ratio Mat differMat; absdiff(thresh_mat, mser_mat, differMat); int countdiff = countNonZero(differMat); float MserDiffOstuRatio = float(countdiff) / float(rect.area()); if (MserDiffOstuRatio > 1) { std::cout << "MserDiffOstuRatio:" << MserDiffOstuRatio << std::endl; imshow("tmpMat", mser_mat); waitKey(0); imshow("tmpMat", thresh_mat); waitKey(0); imshow("tmpMat", differMat); waitKey(0); cv::rectangle(result, rect, Scalar(0, 0, 0), 2); return false; } return true; } 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 Mat mserCharMatch(const Mat &src, Mat &match, std::vector& out_plateVec, Color color, int img_index, bool showDebug) { Mat image = src; std::vector> all_contours; std::vector all_boxes; Ptr mser; std::vector charVec; match = Mat::zeros(image.rows, image.cols, image.type()); Mat result = image.clone(); cvtColor(result, result, COLOR_GRAY2BGR); const int imageArea = image.rows * image.cols; const int delta = 1; const int minArea = 30; const double maxAreaRatio = 0.05; mser = MSER::create(delta, minArea, int(maxAreaRatio * imageArea)); mser->setPass2Only(true); mser->detectRegions(image, all_contours, all_boxes); size_t size = all_contours.size(); int char_index = 0; int char_size = 20; // verify char size and output to rects; for (size_t index = 0; index < size; index++) { Rect rect = all_boxes[index]; std::vector contour = all_contours[index]; 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 = threshold(image(charRect), tmpMat, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); //cv::circle(result, center, 3, Scalar(0, 0, 255), 2); // 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); } } } CharsIdentify::instance()->classify(charVec); double overlapThresh = 0.5; NMStoCharacter(charVec, overlapThresh); std::vector strongSeedVec; std::vector weakSeedVec; std::vector littleSeedVec; //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 overlapThresh = 0.3; NMStoCharacter(strongSeedVec, overlapThresh); //merge chars to group std::vector> charGroupVec; mergeCharToGroup(strongSeedVec, charGroupVec); //genenrate the line of the group std::vector plateVec; for (auto charGroup : charGroupVec) { Rect plateResult = charGroup[0].getCharacterPos(); std::vector points; 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; // remove outlier CharGroup std::vector roCharGroup; 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; Vec2i mindistVec; Vec2i avgdistVec; 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); } } 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(); float k = line[1] / line[0]; float x_1 = line[2]; float y_1 = line[3]; std::vector searchWeakSeedVec; std::vector searchRightWeakSeed; std::vector searchLeftWeakSeed; std::vector slideRightWindow; std::vector slideLeftWindow; //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 (mserCharacter.size() < 7) { double thresh1 = 0.1; 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); } // add mserCharacter size judge } float min_thresh = 0.3f; float max_thresh = 2.5f; reFoundAndCombineRect(mserCharacter, min_thresh, max_thresh, dist, maxrect, result); if (mserCharacter.size() < 7) { if (1 && showDebug) { std::cout << "search chinese:" << std::endl; std::cout << "judege the left is chinese:" << std::endl; } 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 = 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, 20); 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; } } //search for sliding window if (!leftIsChinese) { slideWindowSearch(image, slideLeftWindow, line, leftPoint, dist, ostu_level, maxrect, plateResult, CharSearchDirection::LEFT, true, result); if (1 && showDebug) { std::cout << "slideLeftWindow:" << slideLeftWindow.size() << std::endl; } for (auto seed : slideLeftWindow) { cv::rectangle(result, seed.getCharacterPos(), Scalar(0, 0, 255), 1); mserCharacter.push_back(seed); } } } if (mserCharacter.size() < 7) { // change ostu_level slideWindowSearch(image, slideRightWindow, line, rightPoint, dist, plate.getOstuLevel(), maxrect, plateResult, CharSearchDirection::RIGHT, false, result); if (1 && showDebug) { std::cout << "slideRightWindow:" << slideRightWindow.size() << std::endl; } for (auto seed : slideRightWindow) { cv::rectangle(result, seed.getCharacterPos(), Scalar(0, 0, 255), 1); mserCharacter.push_back(seed); } } float angle = atan(k) * 180 / (float)CV_PI; if (1 && showDebug) { std::cout << "k:" << k << std::endl; std::cout << "angle:" << angle << std::endl; } RotatedRect platePos(Point2f((float)plateResult.x + plateResult.width / 2.f, (float)plateResult.y + plateResult.height / 2.f), Size2f(plateResult.width * 1.15f, maxrect.height * 1.25f), angle); if (verifyRotatedPlateSizes(platePos)) { rotatedRectangle(result, platePos, Scalar(0, 0, 255), 1); plate.setPlatePos(platePos); plate.setPlateColor(color); plate.setPlateLocateType(CMSER); out_plateVec.push_back(plate); } //cv::rectangle(result, plateResult, Scalar(0, 0, 255), 1); //match(plateResult) = 255; if (1) { for (auto mserChar : mserCharacter) { Rect rect = mserChar.getCharacterPos(); match(rect) = 255; } cv::line(match, rightPoint, leftPoint, Scalar(255)); } } if (0 /*&& showDebug*/) { imshow("result", result); waitKey(0); destroyWindow("result"); } if (0) { imshow("match", match); waitKey(0); destroyWindow("match"); } if (1) { std::stringstream ss(std::stringstream::in | std::stringstream::out); ss << "resources/image/tmp/plateDetect/plate_" << img_index << "_" << color << ".jpg"; imwrite(ss.str(), result); } return match; } //! use verify size to first generate candidates Mat mserMatch(const Mat &src, Mat &match, const Color r, std::vector& out_plateRect, std::vector& out_charRect) { Mat image = src; std::vector plateRects; std::vector charRects; std::vector> all_contours; std::vector all_boxes; Ptr mser; std::vector charVec; match = Mat::zeros(image.rows, image.cols, image.type()); Mat result = image.clone(); cvtColor(result, result, COLOR_GRAY2BGR); int imageArea = image.rows * image.cols; mser = MSER::create(1, 30, int(0.05 * imageArea)); mser->detectRegions(image, all_contours, all_boxes); size_t size = all_contours.size(); int char_index = 0; for (size_t index = 0; index < size; index++) { Rect rect = all_boxes[index]; std::vector contour = all_contours[index]; RotatedRect rrect = minAreaRect(Mat(contour)); if (verifyRotatedPlateSizes(rrect)) { //cv::rectangle(result, rrect.boundingRect(), Scalar(0, 255, 0)); Point2f rect_points[4]; rrect.points(rect_points); for (int j = 0; j < 4; j++) line(result, rect_points[j], rect_points[(j + 1) % 4], Scalar(0, 255, 0), 1, 8); plateRects.push_back(rrect); } if (verifyCharSizes(rect)) { //if (0) //{ // std::stringstream ss(std::stringstream::in | std::stringstream::out); // ss << "resources/image/tmp/character" << char_index++ << ".jpg"; // imwrite(ss.str(), mserMat); //} if (1) { //match(rect) = min(max(0, int(maxVal * 255)),255); match(rect) = 255; cv::rectangle(result, rect, Scalar(255, 0, 0)); Point center(rect.tl().x + rect.width / 2, rect.tl().y + rect.height / 2); //cv::circle(result, center, 3, Scalar(0, 255, 0), 2); charRects.push_back(rect); out_charRect.push_back(rect); //CCharacter character; //character.setCharacterPos(rect); //character.setCharacterMat(binary_region); //character.setCharacterStr(label); //character.setCharacterScore(maxVal); //charVec.push_back(character); } } } for (auto prect : plateRects) { float areasum = 0.f; int count = 0; Rect boundingrect = prect.boundingRect(); for (auto crect : charRects) { Rect interRect = boundingrect & crect; if (interRect == crect) { Mat region = image(crect); Mat binary_region; threshold(region, binary_region, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); Mat charInput = preprocessChar(binary_region, 20); if(0) { std::stringstream ss(std::stringstream::in | std::stringstream::out); ss << "resources/image/tmp/character" << char_index++ << ".jpg"; imwrite(ss.str(), charInput); } std::string label = ""; float maxVal = -2.f; bool isCharacter = CharsIdentify::instance()->isCharacter(charInput, label, maxVal); if (isCharacter) { areasum += crect.area(); count++; } } } float ratio = areasum / (float)boundingrect.area(); if (ratio - 0.5f > 0 && count >= 5) { std::cout << "ratio:" << ratio << std::endl; std::cout << "count:" << count << std::endl; //cv::rectangle(result, prect.boundingRect(), Scalar(0, 255, 0)); out_plateRect.push_back(prect); } } if (1) { imshow("result", result); waitKey(0); } return match; } 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) { threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); } else if (type == YELLOW) { threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV); } else { 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) { int expendWidth = 0; if (rect.height > 3 * rect.width) { expendWidth = (rect.height / 2 - rect.width) / 2; } //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; int brx = rect.br().x + expendWidth < maxwidth ? rect.br().x + expendWidth : maxwidth; int bry = rect.br().y; 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; resize(image, result, size, 0, 0, INTER_NEAREST); return result; } // shift an image Mat translateImg(Mat img, int offsetx, int offsety){ Mat dst; Mat trans_mat = (Mat_(2, 3) << 1, 0, offsetx, 0, 1, offsety); warpAffine(img, dst, trans_mat, img.size()); return dst; } // rotate an image Mat rotateImg(Mat source, float angle){ Point2f src_center(source.cols / 2.0F, source.rows / 2.0F); Mat rot_mat = getRotationMatrix2D(src_center, angle, 1.0); Mat dst; warpAffine(source, dst, rot_mat, source.size()); return dst; } //! 计算一个安全的Rect //! 如果不存在,返回false bool calcSafeRect(const RotatedRect &roi_rect, const Mat &src, Rect_ &safeBoundRect) { Rect_ boudRect = roi_rect.boundingRect(); // boudRect的左上的x和y有可能小于0 float tl_x = boudRect.x > 0 ? boudRect.x : 0; float tl_y = boudRect.y > 0 ? boudRect.y : 0; // boudRect的右下的x和y有可能大于src的范围 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; // 新建一个mat,确保地址不越界,以防mat定位roi时抛异常 safeBoundRect = Rect_(tl_x, tl_y, roi_width, roi_height); return true; } }