You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Galaxy/src/core/core_func.cpp

2569 lines
81 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "easypr/core/core_func.h"
#include "easypr/core/plate.hpp"
#include "easypr/core/chars_identify.h"
#include "easypr/config.h"
#include "easypr/core/params.h"
#include "thirdparty/mser/mser2.hpp"
#include <ctime>
namespace easypr {
Mat colorMatch(const Mat &src, Mat &match, const Color r,
const bool adaptive_minsv) {
// if use adaptive_minsv
// min value of s and v is adaptive to h
const float max_sv = 255;
const float minref_sv = 64;
const float minabs_sv = 95; //95;
// H range of blue
const int min_blue = 100; // 100
const int max_blue = 140; // 140
// H range of yellow
const int min_yellow = 15; // 15
const int max_yellow = 40; // 40
// H range of white
const int min_white = 0; // 15
const int max_white = 30; // 40
Mat src_hsv;
// convert to HSV space
cvtColor(src, src_hsv, CV_BGR2HSV);
std::vector<cv::Mat> hsvSplit;
split(src_hsv, hsvSplit);
equalizeHist(hsvSplit[2], hsvSplit[2]);
merge(hsvSplit, src_hsv);
// match to find the color
int min_h = 0;
int max_h = 0;
switch (r) {
case BLUE:
min_h = min_blue;
max_h = max_blue;
break;
case YELLOW:
min_h = min_yellow;
max_h = max_yellow;
break;
case WHITE:
min_h = min_white;
max_h = max_white;
break;
default:
// Color::UNKNOWN
break;
}
float diff_h = float((max_h - min_h) / 2);
float avg_h = min_h + diff_h;
int channels = src_hsv.channels();
int nRows = src_hsv.rows;
// consider multi channel image
int nCols = src_hsv.cols * channels;
if (src_hsv.isContinuous()) {
nCols *= nRows;
nRows = 1;
}
int i, j;
uchar* p;
float s_all = 0;
float v_all = 0;
float count = 0;
for (i = 0; i < nRows; ++i) {
p = src_hsv.ptr<uchar>(i);
for (j = 0; j < nCols; j += 3) {
int H = int(p[j]); // 0-180
int S = int(p[j + 1]); // 0-255
int V = int(p[j + 2]); // 0-255
s_all += S;
v_all += V;
count++;
bool colorMatched = false;
if (H > min_h && H < max_h) {
float Hdiff = 0;
if (H > avg_h)
Hdiff = H - avg_h;
else
Hdiff = avg_h - H;
float Hdiff_p = float(Hdiff) / diff_h;
float min_sv = 0;
if (true == adaptive_minsv)
min_sv =
minref_sv -
minref_sv / 2 *
(1
- Hdiff_p); // inref_sv - minref_sv / 2 * (1 - Hdiff_p)
else
min_sv = minabs_sv; // add
if ((S > min_sv && S < max_sv) && (V > min_sv && V < max_sv))
colorMatched = true;
}
if (colorMatched == true) {
p[j] = 0;
p[j + 1] = 0;
p[j + 2] = 255;
}
else {
p[j] = 0;
p[j + 1] = 0;
p[j + 2] = 0;
}
}
}
// cout << "avg_s:" << s_all / count << endl;
// cout << "avg_v:" << v_all / count << endl;
// get the final binary
Mat src_grey;
std::vector<cv::Mat> hsvSplit_done;
split(src_hsv, hsvSplit_done);
src_grey = hsvSplit_done[2];
match = src_grey;
return src_grey;
}
bool bFindLeftRightBound1(Mat &bound_threshold, int &posLeft, int &posRight) {
float span = bound_threshold.rows * 0.2f;
for (int i = 0; i < bound_threshold.cols - span - 1; i += 3) {
int whiteCount = 0;
for (int k = 0; k < bound_threshold.rows; k++) {
for (int l = i; l < i + span; l++) {
if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) {
whiteCount++;
}
}
}
if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.15) {
posLeft = i;
break;
}
}
span = bound_threshold.rows * 0.2f;
for (int i = bound_threshold.cols - 1; i > span; i -= 2) {
int whiteCount = 0;
for (int k = 0; k < bound_threshold.rows; k++) {
for (int l = i; l > i - span; l--) {
if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) {
whiteCount++;
}
}
}
if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.06) {
posRight = i;
if (posRight + 5 < bound_threshold.cols) {
posRight = posRight + 5;
} else {
posRight = bound_threshold.cols - 1;
}
break;
}
}
if (posLeft < posRight) {
return true;
}
return false;
}
bool bFindLeftRightBound(Mat &bound_threshold, int &posLeft, int &posRight) {
float span = bound_threshold.rows * 0.2f;
for (int i = 0; i < bound_threshold.cols - span - 1; i += 2) {
int whiteCount = 0;
for (int k = 0; k < bound_threshold.rows; k++) {
for (int l = i; l < i + span; l++) {
if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) {
whiteCount++;
}
}
}
if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.36) {
posLeft = i;
break;
}
}
span = bound_threshold.rows * 0.2f;
for (int i = bound_threshold.cols - 1; i > span; i -= 2) {
int whiteCount = 0;
for (int k = 0; k < bound_threshold.rows; k++) {
for (int l = i; l > i - span; l--) {
if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) {
whiteCount++;
}
}
}
if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.26) {
posRight = i;
break;
}
}
if (posLeft < posRight) {
return true;
}
return false;
}
bool bFindLeftRightBound2(Mat &bound_threshold, int &posLeft, int &posRight) {
float span = bound_threshold.rows * 0.2f;
for (int i = 0; i < bound_threshold.cols - span - 1; i += 3) {
int whiteCount = 0;
for (int k = 0; k < bound_threshold.rows; k++) {
for (int l = i; l < i + span; l++) {
if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) {
whiteCount++;
}
}
}
if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.32) {
posLeft = i;
break;
}
}
span = bound_threshold.rows * 0.2f;
for (int i = bound_threshold.cols - 1; i > span; i -= 3) {
int whiteCount = 0;
for (int k = 0; k < bound_threshold.rows; k++) {
for (int l = i; l > i - span; l--) {
if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) {
whiteCount++;
}
}
}
if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.22) {
posRight = i;
break;
}
}
if (posLeft < posRight) {
return true;
}
return false;
}
bool plateColorJudge(const Mat &src, const Color r, const bool adaptive_minsv,
float &percent) {
const float thresh = 0.45f;
Mat src_gray;
colorMatch(src, src_gray, r, adaptive_minsv);
percent =
float(countNonZero(src_gray)) / float(src_gray.rows * src_gray.cols);
// cout << "percent:" << percent << endl;
if (percent > thresh)
return true;
else
return false;
}
Color getPlateType(const Mat &src, const bool adaptive_minsv) {
float max_percent = 0;
Color max_color = UNKNOWN;
float blue_percent = 0;
float yellow_percent = 0;
float white_percent = 0;
if (plateColorJudge(src, BLUE, adaptive_minsv, blue_percent) == true) {
// cout << "BLUE" << endl;
return BLUE;
} else if (plateColorJudge(src, YELLOW, adaptive_minsv, yellow_percent) ==
true) {
// cout << "YELLOW" << endl;
return YELLOW;
} else if (plateColorJudge(src, WHITE, adaptive_minsv, white_percent) ==
true) {
// cout << "WHITE" << endl;
return WHITE;
} else {
//std::cout << "OTHER" << std::endl;
/*max_percent = blue_percent > yellow_percent ? blue_percent : yellow_percent;
max_color = blue_percent > yellow_percent ? BLUE : YELLOW;
max_color = max_percent > white_percent ? max_color : WHITE;*/
// always return blue
return BLUE;
}
}
void clearLiuDingOnly(Mat &img) {
const int x = 7;
Mat jump = Mat::zeros(1, img.rows, CV_32F);
for (int i = 0; i < img.rows; i++) {
int jumpCount = 0;
int whiteCount = 0;
for (int j = 0; j < img.cols - 1; j++) {
if (img.at<char>(i, j) != img.at<char>(i, j + 1)) jumpCount++;
if (img.at<uchar>(i, j) == 255) {
whiteCount++;
}
}
jump.at<float>(i) = (float) jumpCount;
}
for (int i = 0; i < img.rows; i++) {
if (jump.at<float>(i) <= x) {
for (int j = 0; j < img.cols; j++) {
img.at<char>(i, j) = 0;
}
}
}
}
bool clearLiuDing(Mat &img) {
std::vector<float> fJump;
int whiteCount = 0;
const int x = 7;
Mat jump = Mat::zeros(1, img.rows, CV_32F);
for (int i = 0; i < img.rows; i++) {
int jumpCount = 0;
for (int j = 0; j < img.cols - 1; j++) {
if (img.at<char>(i, j) != img.at<char>(i, j + 1)) jumpCount++;
if (img.at<uchar>(i, j) == 255) {
whiteCount++;
}
}
jump.at<float>(i) = (float) jumpCount;
}
int iCount = 0;
for (int i = 0; i < img.rows; i++) {
fJump.push_back(jump.at<float>(i));
if (jump.at<float>(i) >= 16 && jump.at<float>(i) <= 45) {
// jump condition
iCount++;
}
}
// if not is not plate
if (iCount * 1.0 / img.rows <= 0.40) {
return false;
}
if (whiteCount * 1.0 / (img.rows * img.cols) < 0.15 ||
whiteCount * 1.0 / (img.rows * img.cols) > 0.50) {
return false;
}
for (int i = 0; i < img.rows; i++) {
if (jump.at<float>(i) <= x) {
for (int j = 0; j < img.cols; j++) {
img.at<char>(i, j) = 0;
}
}
}
return true;
}
void clearBorder(const Mat &img, Rect& cropRect) {
int r = img.rows;
int c = img.cols;
Mat boder = Mat::zeros(1, r, CV_8UC1);
const int noJunpCount_thresh = int(0.15f * c);
// if nojumpcount >
for (int i = 0; i < r; i++) {
int nojumpCount = 0;
int isBorder = 0;
for (int j = 0; j < c - 1; j++) {
if (img.at<char>(i, j) == img.at<char>(i, j + 1))
nojumpCount++;
if (nojumpCount > noJunpCount_thresh) {
nojumpCount = 0;
isBorder = 1;
break;
}
}
boder.at<char>(i) = (char) isBorder;
}
const int mintop = int(0.1f * r);
const int maxtop = int(0.9f * r);
int minMatTop = 0;
int maxMatTop = r - 1;
for (int i = 0; i < mintop; i++) {
if (boder.at<char>(i) == 1) {
minMatTop = i;
}
}
for (int i = r - 1; i > maxtop; i--) {
if (boder.at<char>(i) == 1) {
maxMatTop = i;
}
}
cropRect = Rect(0, minMatTop, c, maxMatTop - minMatTop + 1);
}
void clearLiuDing(Mat mask, int &top, int &bottom) {
const int x = 7;
for (int i = 0; i < mask.rows / 2; i++) {
int whiteCount = 0;
int jumpCount = 0;
for (int j = 0; j < mask.cols - 1; j++) {
if (mask.at<char>(i, j) != mask.at<char>(i, j + 1)) jumpCount++;
if ((int) mask.at<uchar>(i, j) == 255) {
whiteCount++;
}
}
if ((jumpCount < x && whiteCount * 1.0 / mask.cols > 0.15) ||
whiteCount < 4) {
top = i;
}
}
top -= 1;
if (top < 0) {
top = 0;
}
// ok, find top and bottom boudnadry
for (int i = mask.rows - 1; i >= mask.rows / 2; i--) {
int jumpCount = 0;
int whiteCount = 0;
for (int j = 0; j < mask.cols - 1; j++) {
if (mask.at<char>(i, j) != mask.at<char>(i, j + 1)) jumpCount++;
if (mask.at<uchar>(i, j) == 255) {
whiteCount++;
}
}
if ((jumpCount < x && whiteCount * 1.0 / mask.cols > 0.15) ||
whiteCount < 4) {
bottom = i;
}
}
bottom += 1;
if (bottom >= mask.rows) {
bottom = mask.rows - 1;
}
if (top >= bottom) {
top = 0;
bottom = mask.rows - 1;
}
}
int ThresholdOtsu(Mat mat) {
int height = mat.rows;
int width = mat.cols;
// histogram
float histogram[256] = {0};
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
unsigned char p = (unsigned char) ((mat.data[i * mat.step[0] + j]));
histogram[p]++;
}
}
// normalize histogram
int size = height * width;
for (int i = 0; i < 256; i++) {
histogram[i] = histogram[i] / size;
}
// average pixel value
float avgValue = 0;
for (int i = 0; i < 256; i++) {
avgValue += i * histogram[i];
}
int thresholdV;
float maxVariance = 0;
float w = 0, u = 0;
for (int i = 0; i < 256; i++) {
w += histogram[i];
u += i * histogram[i];
float t = avgValue * w - u;
float variance = t * t / (w * (1 - w));
if (variance > maxVariance) {
maxVariance = variance;
thresholdV = i;
}
}
return thresholdV;
}
Mat histeq(Mat in) {
Mat out(in.size(), in.type());
if (in.channels() == 3) {
Mat hsv;
std::vector<cv::Mat> hsvSplit;
cvtColor(in, hsv, CV_BGR2HSV);
split(hsv, hsvSplit);
equalizeHist(hsvSplit[2], hsvSplit[2]);
merge(hsvSplit, hsv);
cvtColor(hsv, out, CV_HSV2BGR);
} else if (in.channels() == 1) {
equalizeHist(in, out);
}
return out;
}
#define HORIZONTAL 1
#define VERTICAL 0
Mat CutTheRect(Mat &in, Rect &rect) {
int size = in.cols; // (rect.width>rect.height)?rect.width:rect.height;
Mat dstMat(size, size, CV_8UC1);
dstMat.setTo(Scalar(0, 0, 0));
int x = (int) floor((float) (size - rect.width) / 2.0f);
int y = (int) floor((float) (size - rect.height) / 2.0f);
for (int i = 0; i < rect.height; ++i) {
for (int j = 0; j < rect.width; ++j) {
dstMat.data[dstMat.step[0] * (i + y) + j + x] =
in.data[in.step[0] * (i + rect.y) + j + rect.x];
}
}
//
return dstMat;
}
Rect GetCenterRect(Mat &in) {
Rect _rect;
int top = 0;
int bottom = in.rows - 1;
// find the center rect
for (int i = 0; i < in.rows; ++i) {
bool bFind = false;
for (int j = 0; j < in.cols; ++j) {
if (in.data[i * in.step[0] + j] > 20) {
top = i;
bFind = true;
break;
}
}
if (bFind) {
break;
}
}
for (int i = in.rows - 1;
i >= 0;
--i) {
bool bFind = false;
for (int j = 0; j < in.cols; ++j) {
if (in.data[i * in.step[0] + j] > 20) {
bottom = i;
bFind = true;
break;
}
}
if (bFind) {
break;
}
}
int left = 0;
int right = in.cols - 1;
for (int j = 0; j < in.cols; ++j) {
bool bFind = false;
for (int i = 0; i < in.rows; ++i) {
if (in.data[i * in.step[0] + j] > 20) {
left = j;
bFind = true;
break;
}
}
if (bFind) {
break;
}
}
for (int j = in.cols - 1;
j >= 0;
--j) {
bool bFind = false;
for (int i = 0; i < in.rows; ++i) {
if (in.data[i * in.step[0] + j] > 20) {
right = j;
bFind = true;
break;
}
}
if (bFind) {
break;
}
}
_rect.x = left;
_rect.y = top;
_rect.width = right - left + 1;
_rect.height = bottom - top + 1;
return _rect;
}
float countOfBigValue(Mat &mat, int iValue) {
float iCount = 0.0;
if (mat.rows > 1) {
for (int i = 0; i < mat.rows; ++i) {
if (mat.data[i * mat.step[0]] > iValue) {
iCount += 1.0;
}
}
return iCount;
} else {
for (int i = 0; i < mat.cols; ++i) {
if (mat.data[i] > iValue) {
iCount += 1.0;
}
}
return iCount;
}
}
Mat ProjectedHistogram(Mat img, int t, int threshold) {
int sz = (t) ? img.rows : img.cols;
Mat mhist = Mat::zeros(1, sz, CV_32F);
for (int j = 0; j < sz; j++) {
Mat data = (t) ? img.row(j) : img.col(j);
mhist.at<float>(j) = countOfBigValue(data, threshold);
}
// Normalize histogram
double min, max;
minMaxLoc(mhist, &min, &max);
if (max > 0)
mhist.convertTo(mhist, -1, 1.0f / max, 0);
return mhist;
}
Mat showHistogram(const Mat &hist) {
int height = 32;
int width = hist.cols;
Mat show = Mat::zeros(height, width, CV_8UC1);
for (int i = 0; i < width; i++) {
int len = int((float) height * hist.at<float>(i));
for (int j = height - 1; j >= 0; j--) {
if (height - j <= len)
show.at<char>(j, i) = (char) 255;
}
}
return show;
}
Mat preprocessChar(Mat in, int char_size) {
// Remap image
int h = in.rows;
int w = in.cols;
int charSize = char_size;
Mat transformMat = Mat::eye(2, 3, CV_32F);
int m = max(w, h);
transformMat.at<float>(0, 2) = float(m / 2 - w / 2);
transformMat.at<float>(1, 2) = float(m / 2 - h / 2);
Mat warpImage(m, m, in.type());
warpAffine(in, warpImage, transformMat, warpImage.size(), INTER_LINEAR,
BORDER_CONSTANT, Scalar(0));
Mat out;
cv::resize(warpImage, out, Size(charSize, charSize));
return out;
}
Rect GetChineseRect(const Rect rectSpe) {
int height = rectSpe.height;
float newwidth = rectSpe.width * 1.10f;
int x = rectSpe.x;
int y = rectSpe.y;
int newx = x - int(newwidth * 1.10f);
newx = newx > 0 ? newx : 0;
Rect a(newx, y, int(newwidth), height);
return a;
}
bool verifyCharSizes(Rect r) {
// Char sizes 45x90
float aspect = 45.0f / 90.0f;
float charAspect = (float) r.width / (float) r.height;
float error = 0.35f;
float minHeight = 25.f;
float maxHeight = 50.f;
// We have a different aspect ratio for number 1, and it can be ~0.2
float minAspect = 0.05f;
float maxAspect = aspect + aspect * error;
// bb area
int bbArea = r.width * r.height;
if (charAspect > minAspect && charAspect < maxAspect /*&&
r.rows >= minHeight && r.rows < maxHeight*/)
return true;
else
return false;
}
Mat scaleImage(const Mat &image, const Size &maxSize, double &scale_ratio) {
Mat ret;
if (image.cols > maxSize.width || image.rows > maxSize.height) {
double widthRatio = image.cols / (double) maxSize.width;
double heightRatio = image.rows / (double) maxSize.height;
double m_real_to_scaled_ratio = max(widthRatio, heightRatio);
int newWidth = int(image.cols / m_real_to_scaled_ratio);
int newHeight = int(image.rows / m_real_to_scaled_ratio);
cv::resize(image, ret, Size(newWidth, newHeight), 0, 0);
scale_ratio = m_real_to_scaled_ratio;
} else {
ret = image;
scale_ratio = 1.0;
}
return ret;
}
// Scale back RotatedRect
RotatedRect scaleBackRRect(const RotatedRect &rr, const float scale_ratio) {
float width = rr.size.width * scale_ratio;
float height = rr.size.height * scale_ratio;
float x = rr.center.x * scale_ratio;
float y = rr.center.y * scale_ratio;
RotatedRect mserRect(Point2f(x, y), Size2f(width, height), rr.angle);
return mserRect;
}
bool verifyPlateSize(Rect mr) {
float error = 0.6f;
// Spain car plate size: 52x11 aspect 4,7272
// China car plate size: 440mm*140mmaspect 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*140mmaspect 3.142857
// Real car plate size: 136 * 32, aspect 4
float aspect = 3.75f;
// Set a min and max area. All other patchs are discarded
// int min= 1*aspect*1; // minimum area
// int max= 2000*aspect*2000; // maximum area
//int min = 34 * 8 * 1; // minimum area
//int max = 34 * 8 * 200; // maximum area
// Get only patchs that match to a respect ratio.
float aspect_min = aspect - aspect * error;
float aspect_max = aspect + aspect * error;
float width_max = 600.f;
float width_min = 30.f;
float min = float(width_min * width_min / aspect_max); // minimum area
float max = float(width_max * width_max / aspect_min); // maximum area
float width = mr.size.width;
float height = mr.size.height;
float area = width * height;
float ratio = width / height;
float angle = mr.angle;
if (ratio < 1) {
swap(width, height);
ratio = width / height;
angle = 90.f + angle;
//std::cout << "angle:" << angle << std::endl;
}
float angle_min = -60.f;
float angle_max = 60.f;
//std::cout << "aspect_min:" << aspect_min << std::endl;
//std::cout << "aspect_max:" << aspect_max << std::endl;
if (area < min || area > max) {
if (0 && showDebug) {
std::cout << "area < min || area > max: " << area << std::endl;
}
return false;
} else if (ratio < aspect_min || ratio > aspect_max) {
if (0 && showDebug) {
std::cout << "ratio < aspect_min || ratio > aspect_max: " << ratio << std::endl;
}
return false;
} else if (angle < angle_min || angle > angle_max) {
if (0 && showDebug) {
std::cout << "angle < angle_min || angle > angle_max: " << angle << std::endl;
}
return false;
} else if (width < width_min || width > width_max) {
if (0 && showDebug) {
std::cout << "width < width_min || width > width_max: " << width << std::endl;
}
return false;
} else {
return true;
}
return true;
}
//! non-maximum suppression
void NMStoCharacter(std::vector<CCharacter> &inVec, double overlap) {
std::sort(inVec.begin(), inVec.end());
std::vector<CCharacter>::iterator it = inVec.begin();
for (; it != inVec.end(); ++it) {
CCharacter charSrc = *it;
//std::cout << "plateScore:" << plateSrc.getPlateScore() << std::endl;
Rect rectSrc = charSrc.getCharacterPos();
std::vector<CCharacter>::iterator itc = it + 1;
for (; itc != inVec.end();) {
CCharacter charComp = *itc;
Rect rectComp = charComp.getCharacterPos();
//Rect rectInter = rectSrc & rectComp;
//Rect rectUnion = rectSrc | rectComp;
//double r = double(rectInter.area()) / double(rectUnion.area());
float iou = computeIOU(rectSrc, rectComp);
if (iou > overlap) {
itc = inVec.erase(itc);
} else {
++itc;
}
}
}
}
// judge weather two CCharacter are nearly the same;
bool compareCharRect(const CCharacter &character1, const CCharacter &character2) {
Rect rect1 = character1.getCharacterPos();
Rect rect2 = character2.getCharacterPos();
// the character in plate are similar height
float width_1 = float(rect1.width);
float height_1 = float(rect1.height);
float width_2 = float(rect2.width);
float height_2 = float(rect2.height);
float height_diff = abs(height_1 - height_2);
double height_diff_ratio = height_diff / min(height_1, height_2);
if (height_diff_ratio > 0.25)
return false;
// the character in plate are similar in the y-axis
float y_1 = float(rect1.tl().y);
float y_2 = float(rect2.tl().y);
float y_diff = abs(y_1 - y_2);
double y_diff_ratio = y_diff / min(height_1, height_2);
if (y_diff_ratio > 0.5)
return false;
// the character center in plate are not to near in the x-axis
float x_1 = float(rect1.tl().x + rect1.width / 2);
float x_2 = float(rect2.tl().x + rect2.width / 2);
float x_diff = abs(x_1 - x_2);
double x_diff_ratio = x_diff / min(height_1, height_2);
if (x_diff_ratio < 0.25)
return false;
// the character in plate are near in the x-axis but not very near
float x_margin_left = float(min(rect1.br().x, rect2.br().x));
float x_margin_right = float(max(rect1.tl().x, rect2.tl().x));
float x_margin_diff = abs(x_margin_left - x_margin_right);
double x_margin_diff_ratio = x_margin_diff / min(height_1, height_2);
if (x_margin_diff_ratio > 1.0)
return false;
return true;
}
//! merge chars to group, using the similarity
void mergeCharToGroup(std::vector<CCharacter> vecRect,
std::vector<std::vector<CCharacter>> &charGroupVec) {
std::vector<int> labels;
int numbers = 0;
if (vecRect.size() > 0)
numbers = partition(vecRect, labels, &compareCharRect);
for (size_t j = 0; j < size_t(numbers); j++) {
std::vector<CCharacter> charGroup;
for (size_t t = 0; t < vecRect.size(); t++) {
int label = labels[t];
if (label == j)
charGroup.push_back(vecRect[t]);
}
if (charGroup.size() < 2)
continue;
charGroupVec.push_back(charGroup);
}
}
void rotatedRectangle(InputOutputArray image, RotatedRect rrect, const Scalar &color, int thickness, int lineType,
int shift) {
Point2f rect_points[4];
rrect.points(rect_points);
for (int j = 0; j < 4; j++) {
cv::line(image, rect_points[j], rect_points[(j + 1) % 4], color, thickness, lineType, shift);
}
}
void searchWeakSeed(const std::vector<CCharacter> &charVec, std::vector<CCharacter> &mserCharacter, double thresh1,
double thresh2,
const Vec4f &line, Point &boundaryPoint, const Rect &maxrect, Rect &plateResult, Mat result,
CharSearchDirection searchDirection) {
float k = line[1] / line[0];
float x_1 = line[2];
float y_1 = line[3];
std::vector<CCharacter> searchWeakSeedVec;
searchWeakSeedVec.reserve(8);
for (auto weakSeed : charVec) {
Rect weakRect = weakSeed.getCharacterPos();
//cv::rectangle(result, weakRect, Scalar(255, 0, 255));
Point weakCenter(weakRect.tl().x + weakRect.width / 2, weakRect.tl().y + weakRect.height / 2);
float x_2 = (float) weakCenter.x;
if (searchDirection == CharSearchDirection::LEFT) {
if (weakCenter.x + weakRect.width / 2 > boundaryPoint.x) {
continue;
}
} else if (searchDirection == CharSearchDirection::RIGHT) {
if (weakCenter.x - weakRect.width / 2 < boundaryPoint.x) {
continue;
}
}
float y_2l = k * (x_2 - x_1) + y_1;
float y_2 = (float) weakCenter.y;
float y_diff_ratio = abs(y_2l - y_2) / maxrect.height;
if (y_diff_ratio < thresh1) {
float width_1 = float(maxrect.width);
float height_1 = float(maxrect.height);
float width_2 = float(weakRect.width);
float height_2 = float(weakRect.height);
float height_diff = abs(height_1 - height_2);
double height_diff_ratio = height_diff / min(height_1, height_2);
float width_diff = abs(width_1 - width_2);
double width_diff_ratio = width_diff / maxrect.width;
if (height_diff_ratio < thresh1 && width_diff_ratio < 0.5) {
//std::cout << "h" << height_diff_ratio << std::endl;
//std::cout << "w" << width_diff_ratio << std::endl;
searchWeakSeedVec.push_back(weakSeed);
} else {
}
}
}
// form right to left to split
if (searchWeakSeedVec.size() != 0) {
if (searchDirection == CharSearchDirection::LEFT) {
std::sort(searchWeakSeedVec.begin(), searchWeakSeedVec.end(),
[](const CCharacter &r1, const CCharacter &r2) {
return r1.getCharacterPos().tl().x > r2.getCharacterPos().tl().x;
});
} else if (searchDirection == CharSearchDirection::RIGHT) {
std::sort(searchWeakSeedVec.begin(), searchWeakSeedVec.end(),
[](const CCharacter &r1, const CCharacter &r2) {
return r1.getCharacterPos().tl().x < r2.getCharacterPos().tl().x;
});
}
CCharacter firstWeakSeed = searchWeakSeedVec.at(0);
Rect firstWeakRect = firstWeakSeed.getCharacterPos();
Point firstWeakCenter(firstWeakRect.tl().x + firstWeakRect.width / 2,
firstWeakRect.tl().y + firstWeakRect.height / 2);
float ratio = (float) abs(firstWeakCenter.x - boundaryPoint.x) / (float) maxrect.height;
if (ratio > thresh2) {
if (0) {
std::cout << "search seed ratio:" << ratio << std::endl;
}
return;
}
mserCharacter.push_back(firstWeakSeed);
plateResult |= firstWeakRect;
boundaryPoint = firstWeakCenter;
for (size_t weakSeedIndex = 0; weakSeedIndex + 1 < searchWeakSeedVec.size(); weakSeedIndex++) {
CCharacter weakSeed = searchWeakSeedVec[weakSeedIndex];
CCharacter weakSeedCompare = searchWeakSeedVec[weakSeedIndex + 1];
Rect rect1 = weakSeed.getCharacterPos();
Rect rect2 = weakSeedCompare.getCharacterPos();
Rect weakRect = rect2;
Point weakCenter(weakRect.tl().x + weakRect.width / 2, weakRect.tl().y + weakRect.height / 2);
// the character in plate are similar height
float width_1 = float(rect1.width);
float height_1 = float(rect1.height);
float width_2 = float(rect2.width);
float height_2 = float(rect2.height);
// the character in plate are near in the x-axis but not very near
float x_margin_left = float(min(rect1.br().x, rect2.br().x));
float x_margin_right = float(max(rect1.tl().x, rect2.tl().x));
float x_margin_diff = abs(x_margin_left - x_margin_right);
double x_margin_diff_ratio = x_margin_diff / min(height_1, height_2);
if (x_margin_diff_ratio > thresh2) {
if (0) {
std::cout << "search seed x_margin_diff_ratio:" << x_margin_diff_ratio << std::endl;
}
break;
} else {
//::rectangle(result, weakRect, Scalar(255, 0, 0), 1);
mserCharacter.push_back(weakSeedCompare);
plateResult |= weakRect;
if (searchDirection == CharSearchDirection::LEFT) {
if (weakCenter.x < boundaryPoint.x) {
boundaryPoint = weakCenter;
}
} else if (searchDirection == CharSearchDirection::RIGHT) {
if (weakCenter.x > boundaryPoint.x) {
boundaryPoint = weakCenter;
}
}
}
}
}
}
void slideWindowSearch(const Mat &image, std::vector<CCharacter> &slideCharacter, const Vec4f &line,
Point &fromPoint, const Vec2i &dist, double ostu_level, float ratioWindow,
float threshIsCharacter, const Rect &maxrect, Rect &plateResult,
CharSearchDirection searchDirection, bool isChinese, Mat &result) {
float k = line[1] / line[0];
float x_1 = line[2];
float y_1 = line[3];
int slideLength = int(ratioWindow * maxrect.width);
int slideStep = 1;
int fromX = 0;
if (searchDirection == CharSearchDirection::LEFT) {
fromX = fromPoint.x - dist[0];
} else if (searchDirection == CharSearchDirection::RIGHT) {
fromX = fromPoint.x + dist[0];
}
std::vector<CCharacter> charCandidateVec;
for (int slideX = -slideLength; slideX < slideLength; slideX += slideStep) {
float x_slide = 0;
if (searchDirection == CharSearchDirection::LEFT) {
x_slide = float(fromX - slideX);
} else if (searchDirection == CharSearchDirection::RIGHT) {
x_slide = float(fromX + slideX);
}
float y_slide = k * (x_slide - x_1) + y_1;
Point2f p_slide(x_slide, y_slide);
cv::circle(result, p_slide, 2, Scalar(255, 255, 255), 1);
int chineseWidth = int(maxrect.width * 1.05);
int chineseHeight = int(maxrect.height * 1.05);
Rect rect(Point2f(x_slide - chineseWidth / 2, y_slide - chineseHeight / 2), Size(chineseWidth, chineseHeight));
if (rect.tl().x < 0 || rect.tl().y < 0 || rect.br().x >= image.cols || rect.br().y >= image.rows)
continue;
Mat region = image(rect);
Mat binary_region;
cv::threshold(region, binary_region, ostu_level, 255, CV_THRESH_BINARY);
//double ostu_level = threshold(region, binary_region, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
//std::cout << "ostu_level:" << ostu_level << std::endl;*/
Mat charInput = preprocessChar(binary_region, 20);
if (0) {
imshow("charInput", charInput);
waitKey(0);
destroyWindow("charInput");
}
CCharacter charCandidate;
charCandidate.setCharacterPos(rect);
charCandidate.setCharacterMat(charInput);
charCandidate.setIsChinese(isChinese);
charCandidateVec.push_back(charCandidate);
}
if (isChinese) {
CharsIdentify::instance()->classifyChinese(charCandidateVec);
} else {
CharsIdentify::instance()->classify(charCandidateVec);
}
double overlapThresh = 0.1;
NMStoCharacter(charCandidateVec, overlapThresh);
for (auto character : charCandidateVec) {
Rect rect = character.getCharacterPos();
Point center(rect.tl().x + rect.width / 2, rect.tl().y + rect.height / 2);
if (character.getCharacterScore() > threshIsCharacter && character.getCharacterStr() != "1") {
//cv::rectangle(result, rect, Scalar(255, 255, 255), 1);
plateResult |= rect;
slideCharacter.push_back(character);
fromPoint = center;
if (0) {
std::cout << "label:" << character.getCharacterStr();
std::cout << "__score:" << character.getCharacterScore() << std::endl;
}
}
}
}
bool judegMDOratio2(const Mat &image, const Rect &rect, std::vector<Point> &contour, Mat &result, const float thresh,
bool useExtendHeight) {
Mat mser = image(rect);
Mat mser_mat;
cv::threshold(mser, mser_mat, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
Rect normalRect = adaptive_charrect_from_rect(rect, image.cols, image.rows, useExtendHeight);
Mat region = image(normalRect);
Mat thresh_mat;
cv::threshold(region, thresh_mat, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
// count mser diff ratio
int countdiff = abs(countNonZero(thresh_mat) - countNonZero(mser_mat));
float MserDiffOstuRatio = float(countdiff) / float(rect.area());
if (MserDiffOstuRatio > thresh) {
//std::cout << "MserDiffOstuRatio:" << MserDiffOstuRatio << std::endl;
/*imshow("tmpMat", mser_mat);
waitKey(0);
imshow("tmpMat", thresh_mat);
waitKey(0);*/
cv::rectangle(result, normalRect, Scalar(0, 0, 0), 2);
return false;
}
return true;
}
Rect interRect(const Rect &a, const Rect &b) {
Rect c;
int x1 = a.x > b.x ? a.x : b.x;
int y1 = a.y > b.y ? a.y : b.y;
c.width = (a.x + a.width < b.x + b.width ? a.x + a.width : b.x + b.width) - x1;
c.height = (a.y + a.height < b.y + b.height ? a.y + a.height : b.y + b.height) - y1;
c.x = x1;
c.y = y1;
if (c.width <= 0 || c.height <= 0)
c = Rect();
return c;
}
Rect mergeRect(const Rect &a, const Rect &b) {
Rect c;
int x1 = a.x < b.x ? a.x : b.x;
int y1 = a.y < b.y ? a.y : b.y;
c.width = (a.x + a.width > b.x + b.width ? a.x + a.width : b.x + b.width) - x1;
c.height = (a.y + a.height > b.y + b.height ? a.y + a.height : b.y + b.height) - y1;
c.x = x1;
c.y = y1;
return c;
}
bool computeIOU(const RotatedRect &rrect1, const RotatedRect &rrect2, const int width, const int height,
const float thresh, float &result) {
Rect_<float> safe_rect1;
calcSafeRect(rrect1, width, height, safe_rect1);
Rect_<float> safe_rect2;
calcSafeRect(rrect2, width, height, safe_rect2);
Rect inter = interRect(safe_rect1, safe_rect2);
Rect urect = mergeRect(safe_rect1, safe_rect2);
float iou = (float) inter.area() / (float) urect.area();
result = iou;
if (iou > thresh) {
return true;
}
return false;
}
float computeIOU(const RotatedRect &rrect1, const RotatedRect &rrect2, const int width, const int height) {
Rect_<float> safe_rect1;
calcSafeRect(rrect1, width, height, safe_rect1);
Rect_<float> safe_rect2;
calcSafeRect(rrect2, width, height, safe_rect2);
Rect inter = interRect(safe_rect1, safe_rect2);
Rect urect = mergeRect(safe_rect1, safe_rect2);
float iou = (float) inter.area() / (float) urect.area();
//std::cout << "iou" << iou << std::endl;
return iou;
}
bool computeIOU(const Rect &rect1, const Rect &rect2, const float thresh, float &result) {
Rect inter = interRect(rect1, rect2);
Rect urect = mergeRect(rect1, rect2);
float iou = (float) inter.area() / (float) urect.area();
result = iou;
if (iou > thresh) {
return true;
}
return false;
}
float computeIOU(const Rect &rect1, const Rect &rect2) {
Rect inter = interRect(rect1, rect2);
Rect urect = mergeRect(rect1, rect2);
float iou = (float) inter.area() / (float) urect.area();
return iou;
}
// the slope are nealy the same along the line
// if one slope is much different others, it should be outliers
// this function to remove it
void removeRightOutliers(std::vector<CCharacter> &charGroup, std::vector<CCharacter> &out_charGroup, double thresh1,
double thresh2, Mat result) {
std::sort(charGroup.begin(), charGroup.end(),
[](const CCharacter &r1, const CCharacter &r2) {
return r1.getCenterPoint().x < r2.getCenterPoint().x;
});
std::vector<float> slopeVec;
float slope_last = 0;
for (size_t charGroup_i = 0; charGroup_i + 1 < charGroup.size(); charGroup_i++) {
// line_between_two_points
Vec4f line_btp;
CCharacter leftChar = charGroup.at(charGroup_i);
CCharacter rightChar = charGroup.at(charGroup_i + 1);
std::vector<Point> two_points;
two_points.push_back(leftChar.getCenterPoint());
two_points.push_back(rightChar.getCenterPoint());
fitLine(Mat(two_points), line_btp, CV_DIST_L2, 0, 0.01, 0.01);
float slope = line_btp[1] / line_btp[0];
slopeVec.push_back(slope);
if (0) {
cv::line(result, leftChar.getCenterPoint(), rightChar.getCenterPoint(), Scalar(0, 0, 255));
}
}
int uniformity_count = 0;
int outlier_index = -1;
for (size_t slopeVec_i = 0; slopeVec_i + 1 < slopeVec.size(); slopeVec_i++) {
float slope_1 = slopeVec.at(slopeVec_i);
float slope_2 = slopeVec.at(slopeVec_i + 1);
float slope_diff = abs(slope_1 - slope_2);
if (0) {
std::cout << "slope_diff:" << slope_diff << std::endl;
}
if (slope_diff <= thresh1) {
uniformity_count++;
}
if (0) {
std::cout << "slope_1:" << slope_1 << std::endl;
std::cout << "slope_2:" << slope_2 << std::endl;
}
if (1/*(slope_1 <= 0 && slope_2 >= 0) || (slope_1 >= 0 && slope_2 <= 0)*/) {
if (uniformity_count >= 2 && slope_diff >= thresh2) {
outlier_index = slopeVec_i + 2;
break;
}
}
}
if (0) {
std::cout << "uniformity_count:" << uniformity_count << std::endl;
std::cout << "outlier_index:" << outlier_index << std::endl;
}
for (int charGroup_i = 0; charGroup_i < (int) charGroup.size(); charGroup_i++) {
if (charGroup_i != outlier_index) {
CCharacter theChar = charGroup.at(charGroup_i);
out_charGroup.push_back(theChar);
}
}
if (0) {
std::cout << "end:" << std::endl;
}
}
Rect getSafeRect(Point2f center, float width, float height, Mat image) {
int rows = image.rows;
int cols = image.cols;
float x = center.x;
float y = center.y;
float x_tl = (x - width / 2.f);
float y_tl = (y - height / 2.f);
float x_br = (x + width / 2.f);
float y_br = (y + height / 2.f);
x_tl = x_tl > 0.f ? x_tl : 0.f;
y_tl = y_tl > 0.f ? y_tl : 0.f;
x_br = x_br < (float) image.cols ? x_br : (float) image.cols;
y_br = y_br < (float) image.rows ? y_br : (float) image.rows;
Rect rect(Point((int) x_tl, int(y_tl)), Point((int) x_br, int(y_br)));
return rect;
}
// based on the assumptions: distance beween two nearby characters in plate are the same.
// add not found rect and combine two small and near rect.
void reFoundAndCombineRect(std::vector<CCharacter> &mserCharacter, float min_thresh, float max_thresh,
Vec2i dist, Rect maxrect, Mat result) {
if (mserCharacter.size() == 0) {
return;
}
std::sort(mserCharacter.begin(), mserCharacter.end(),
[](const CCharacter &r1, const CCharacter &r2) {
return r1.getCenterPoint().x < r2.getCenterPoint().x;
});
int comparDist = dist[0] * dist[0] + dist[1] * dist[1];
if (0) {
std::cout << "comparDist:" << comparDist << std::endl;
}
std::vector<CCharacter> reCharacters;
size_t mserCharacter_i = 0;
for (; mserCharacter_i + 1 < mserCharacter.size(); mserCharacter_i++) {
CCharacter leftChar = mserCharacter.at(mserCharacter_i);
CCharacter rightChar = mserCharacter.at(mserCharacter_i + 1);
Point leftCenter = leftChar.getCenterPoint();
Point rightCenter = rightChar.getCenterPoint();
int x_diff = leftCenter.x - rightCenter.x;
int y_diff = leftCenter.y - rightCenter.y;
// distance between two centers
int distance2 = x_diff * x_diff + y_diff * y_diff;
if (0) {
std::cout << "distance2:" << distance2 << std::endl;
}
float ratio = (float) distance2 / (float) comparDist;
if (ratio > max_thresh) {
float x_add = (float) (leftCenter.x + rightCenter.x) / 2.f;
float y_add = (float) (leftCenter.y + rightCenter.y) / 2.f;
float width = (float) maxrect.width;
float height = (float) maxrect.height;
float x_tl = (x_add - width / 2.f);
float y_tl = (y_add - height / 2.f);
//Rect rect_add((int)x_tl, (int)y_tl, (int)width, (int)height);
Rect rect_add = getSafeRect(Point2f(x_add, y_add), width, height, result);
reCharacters.push_back(leftChar);
CCharacter charAdd;
charAdd.setCenterPoint(Point((int) x_add, (int) y_add));
charAdd.setCharacterPos(rect_add);
reCharacters.push_back(charAdd);
if (1) {
cv::rectangle(result, rect_add, Scalar(0, 128, 255));
}
} else if (ratio < min_thresh) {
Rect rect_union = leftChar.getCharacterPos() | rightChar.getCharacterPos();
/*float x_add = (float)(leftCenter.x + rightCenter.x) / 2.f;
float y_add = (float)(leftCenter.y + rightCenter.y) / 2.f;*/
int x_add = rect_union.tl().x + rect_union.width / 2;
int y_add = rect_union.tl().y + rect_union.height / 2;
CCharacter charAdd;
charAdd.setCenterPoint(Point(x_add, y_add));
charAdd.setCharacterPos(rect_union);
reCharacters.push_back(charAdd);
if (1) {
cv::rectangle(result, rect_union, Scalar(0, 128, 255));
}
mserCharacter_i++;
} else {
reCharacters.push_back(leftChar);
}
}
if (mserCharacter_i + 1 == mserCharacter.size()) {
reCharacters.push_back(mserCharacter.at(mserCharacter_i));
}
mserCharacter = reCharacters;
}
void removeOutliers(std::vector<CCharacter> &charGroup, double thresh, Mat result) {
std::vector<Point> points;
Vec4f line;
for (auto character : charGroup) {
points.push_back(character.getCenterPoint());
}
fitLine(Mat(points), line, CV_DIST_L2, 0, 0.01, 0.01);
float k = line[1] / line[0];
float x_1 = line[2];
float y_1 = line[3];
float step = 100;
cv::line(result, Point2f(x_1 - step, y_1 - k * step), Point2f(x_1 + step, k * step + y_1), Scalar(0, 0, 255));
float a = k;
float b = -1;
float c = y_1 - k * x_1;
float sumdistance = 0;
for (auto character : charGroup) {
Point center = character.getCenterPoint();
float distance = (a * center.x + b * center.y + c) / std::sqrt(a * a + b * b);
std::cout << "distance:" << distance << std::endl;
sumdistance += distance;
}
float avgdistance = sumdistance / (float) charGroup.size();
std::vector<CCharacter>::iterator it = charGroup.begin();
for (; it != charGroup.end();) {
Point center = it->getCenterPoint();
float distance = a * center.x + b * center.y + c;
float ratio = distance / avgdistance;
std::cout << "ratio:" << ratio << std::endl;
if (ratio > (float) thresh) {
it = charGroup.erase(it);
} else {
++it;
}
}
}
//! use verify size to first generate char candidates
void mserCharMatch(const Mat &src, std::vector<Mat> &match, std::vector<CPlate> &out_plateVec_blue,
std::vector<CPlate> &out_plateVec_yellow,
bool usePlateMser, std::vector<RotatedRect> &out_plateRRect_blue,
std::vector<RotatedRect> &out_plateRRect_yellow, int img_index,
bool showDebug) {
Mat image = src;
std::vector<std::vector<std::vector<Point>>> all_contours;
std::vector<std::vector<Rect>> all_boxes;
all_contours.resize(2);
all_contours.at(0).reserve(1024);
all_contours.at(1).reserve(1024);
all_boxes.resize(2);
all_boxes.at(0).reserve(1024);
all_boxes.at(1).reserve(1024);
match.resize(2);
std::vector<Color> flags;
flags.push_back(BLUE);
flags.push_back(YELLOW);
const int imageArea = image.rows * image.cols;
const int delta = 1;
//const int delta = CParams::instance()->getParam2i();;
const int minArea = 30;
const double maxAreaRatio = 0.05;
Ptr<MSER2> mser;
mser = MSER2::create(delta, minArea, int(maxAreaRatio * imageArea));
mser->detectRegions(image, all_contours.at(0), all_boxes.at(0), all_contours.at(1), all_boxes.at(1));
// mser detect
// color_index = 0 : mser-, detect white characters, which is in blue plate.
// color_index = 1 : mser+, detect dark characters, which is in yellow plate.
#pragma omp parallel for
for (int color_index = 0; color_index < 2; color_index++) {
Color the_color = flags.at(color_index);
std::vector<CCharacter> charVec;
charVec.reserve(128);
match.at(color_index) = Mat::zeros(image.rows, image.cols, image.type());
Mat result = image.clone();
cvtColor(result, result, COLOR_GRAY2BGR);
size_t size = all_contours.at(color_index).size();
int char_index = 0;
int char_size = 20;
// Chinese plate has max 7 characters.
const int char_max_count = 7;
// verify char size and output to rects;
for (size_t index = 0; index < size; index++) {
Rect rect = all_boxes.at(color_index)[index];
std::vector<Point> &contour = all_contours.at(color_index)[index];
// sometimes a plate could be a mser rect, so we could
// also use mser algorithm to find plate
if (usePlateMser) {
RotatedRect rrect = minAreaRect(Mat(contour));
if (verifyRotatedPlateSizes(rrect)) {
//rotatedRectangle(result, rrect, Scalar(255, 0, 0), 2);
if (the_color == BLUE) out_plateRRect_blue.push_back(rrect);
if (the_color == YELLOW) out_plateRRect_yellow.push_back(rrect);
}
}
// find character
if (verifyCharSizes(rect)) {
Mat mserMat = adaptive_image_from_points(contour, rect, Size(char_size, char_size));
Mat charInput = preprocessChar(mserMat, char_size);
Rect charRect = rect;
Point center(charRect.tl().x + charRect.width / 2, charRect.tl().y + charRect.height / 2);
Mat tmpMat;
double ostu_level = cv::threshold(image(charRect), tmpMat, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
//cv::circle(result, center, 3, Scalar(0, 0, 255), 2);
// use judegMDOratio2 function to
// remove the small lines in character like "zh-cuan"
if (judegMDOratio2(image, rect, contour, result)) {
CCharacter charCandidate;
charCandidate.setCharacterPos(charRect);
charCandidate.setCharacterMat(charInput);
charCandidate.setOstuLevel(ostu_level);
charCandidate.setCenterPoint(center);
charCandidate.setIsChinese(false);
charVec.push_back(charCandidate);
}
}
}
// improtant, use matrix multiplication to acclerate the
// classification of many samples. use the character
// score, we can use non-maximum superssion (nms) to
// reduce the characters which are not likely to be true
// charaters, and use the score to select the strong seed
// of which the score is larger than 0.9
CharsIdentify::instance()->classify(charVec);
// use nms to remove the character are not likely to be true.
double overlapThresh = 0.6;
//double overlapThresh = CParams::instance()->getParam1f();
NMStoCharacter(charVec, overlapThresh);
charVec.shrink_to_fit();
std::vector<CCharacter> strongSeedVec;
strongSeedVec.reserve(64);
std::vector<CCharacter> weakSeedVec;
weakSeedVec.reserve(64);
std::vector<CCharacter> littleSeedVec;
littleSeedVec.reserve(64);
//size_t charCan_size = charVec.size();
for (auto charCandidate : charVec) {
//CCharacter& charCandidate = charVec[char_index];
Rect rect = charCandidate.getCharacterPos();
double score = charCandidate.getCharacterScore();
if (charCandidate.getIsStrong()) {
strongSeedVec.push_back(charCandidate);
} else if (charCandidate.getIsWeak()) {
weakSeedVec.push_back(charCandidate);
//cv::rectangle(result, rect, Scalar(255, 0, 255));
} else if (charCandidate.getIsLittle()) {
littleSeedVec.push_back(charCandidate);
//cv::rectangle(result, rect, Scalar(255, 0, 255));
}
}
std::vector<CCharacter> searchCandidate = charVec;
// nms to srong seed, only leave the strongest one
overlapThresh = 0.3;
NMStoCharacter(strongSeedVec, overlapThresh);
// merge chars to group
std::vector<std::vector<CCharacter>> charGroupVec;
charGroupVec.reserve(64);
mergeCharToGroup(strongSeedVec, charGroupVec);
// genenrate the line of the group
// based on the assumptions , the mser rects which are
// given high socre by character classifier could be no doubtly
// be the characters in one plate, and we can use these characeters
// to fit a line which is the middle line of the plate.
std::vector<CPlate> plateVec;
plateVec.reserve(16);
for (auto charGroup : charGroupVec) {
Rect plateResult = charGroup[0].getCharacterPos();
std::vector<Point> points;
points.reserve(32);
Vec4f line;
int maxarea = 0;
Rect maxrect;
double ostu_level_sum = 0;
int leftx = image.cols;
Point leftPoint(leftx, 0);
int rightx = 0;
Point rightPoint(rightx, 0);
std::vector<CCharacter> mserCharVec;
mserCharVec.reserve(32);
// remove outlier CharGroup
std::vector<CCharacter> roCharGroup;
roCharGroup.reserve(32);
removeRightOutliers(charGroup, roCharGroup, 0.2, 0.5, result);
//roCharGroup = charGroup;
for (auto character : roCharGroup) {
Rect charRect = character.getCharacterPos();
cv::rectangle(result, charRect, Scalar(0, 255, 0), 1);
plateResult |= charRect;
Point center(charRect.tl().x + charRect.width / 2, charRect.tl().y + charRect.height / 2);
points.push_back(center);
mserCharVec.push_back(character);
//cv::circle(result, center, 3, Scalar(0, 255, 0), 2);
ostu_level_sum += character.getOstuLevel();
if (charRect.area() > maxarea) {
maxrect = charRect;
maxarea = charRect.area();
}
if (center.x < leftPoint.x) {
leftPoint = center;
}
if (center.x > rightPoint.x) {
rightPoint = center;
}
}
double ostu_level_avg = ostu_level_sum / (double) roCharGroup.size();
if (1 && showDebug) {
std::cout << "ostu_level_avg:" << ostu_level_avg << std::endl;
}
float ratio_maxrect = (float) maxrect.width / (float) maxrect.height;
if (points.size() >= 2 && ratio_maxrect >= 0.3) {
fitLine(Mat(points), line, CV_DIST_L2, 0, 0.01, 0.01);
float k = line[1] / line[0];
//float angle = atan(k) * 180 / (float)CV_PI;
//std::cout << "k:" << k << std::endl;
//std::cout << "angle:" << angle << std::endl;
//std::cout << "cos:" << 0.3 * cos(k) << std::endl;
//std::cout << "ratio_maxrect:" << ratio_maxrect << std::endl;
std::sort(mserCharVec.begin(), mserCharVec.end(),
[](const CCharacter &r1, const CCharacter &r2) {
return r1.getCharacterPos().tl().x < r2.getCharacterPos().tl().x;
});
CCharacter midChar = mserCharVec.at(int(mserCharVec.size() / 2.f));
Rect midRect = midChar.getCharacterPos();
Point midCenter(midRect.tl().x + midRect.width / 2, midRect.tl().y + midRect.height / 2);
int mindist = 7 * maxrect.width;
std::vector<Vec2i> distVecVec;
distVecVec.reserve(32);
Vec2i mindistVec;
Vec2i avgdistVec;
// computer the dist which is the distacne between
// two near characters in the plate, use dist we can
// judege how to computer the max search range, and choose the
// best location of the sliding window in the next steps.
for (size_t mser_i = 0; mser_i + 1 < mserCharVec.size(); mser_i++) {
Rect charRect = mserCharVec.at(mser_i).getCharacterPos();
Point center(charRect.tl().x + charRect.width / 2, charRect.tl().y + charRect.height / 2);
Rect charRectCompare = mserCharVec.at(mser_i + 1).getCharacterPos();
Point centerCompare(charRectCompare.tl().x + charRectCompare.width / 2,
charRectCompare.tl().y + charRectCompare.height / 2);
int dist = charRectCompare.x - charRect.x;
Vec2i distVec(charRectCompare.x - charRect.x, charRectCompare.y - charRect.y);
distVecVec.push_back(distVec);
//if (dist < mindist) {
// mindist = dist;
// mindistVec = distVec;
//}
}
std::sort(distVecVec.begin(), distVecVec.end(),
[](const Vec2i &r1, const Vec2i &r2) {
return r1[0] < r2[0];
});
avgdistVec = distVecVec.at(int((distVecVec.size() - 1) / 2.f));
//float step = 10.f * (float)maxrect.width;
//float step = (float)mindistVec[0];
float step = (float) avgdistVec[0];
//cv::line(result, Point2f(line[2] - step, line[3] - k*step), Point2f(line[2] + step, k*step + line[3]), Scalar(255, 255, 255));
cv::line(result, Point2f(midCenter.x - step, midCenter.y - k * step),
Point2f(midCenter.x + step, k * step + midCenter.y), Scalar(255, 255, 255));
//cv::circle(result, leftPoint, 3, Scalar(0, 0, 255), 2);
CPlate plate;
plate.setPlateLeftPoint(leftPoint);
plate.setPlateRightPoint(rightPoint);
plate.setPlateLine(line);
plate.setPlatDistVec(avgdistVec);
plate.setOstuLevel(ostu_level_avg);
plate.setPlateMergeCharRect(plateResult);
plate.setPlateMaxCharRect(maxrect);
plate.setMserCharacter(mserCharVec);
plateVec.push_back(plate);
}
}
// use strong seed to construct the first shape of the plate,
// then we need to find characters which are the weak seed.
// because we use strong seed to build the middle lines of the plate,
// we can simply use this to consider weak seeds only lie in the
// near place of the middle line
for (auto plate : plateVec) {
Vec4f line = plate.getPlateLine();
Point leftPoint = plate.getPlateLeftPoint();
Point rightPoint = plate.getPlateRightPoint();
Rect plateResult = plate.getPlateMergeCharRect();
Rect maxrect = plate.getPlateMaxCharRect();
Vec2i dist = plate.getPlateDistVec();
double ostu_level = plate.getOstuLevel();
std::vector<CCharacter> mserCharacter = plate.getCopyOfMserCharacters();
mserCharacter.reserve(16);
float k = line[1] / line[0];
float x_1 = line[2];
float y_1 = line[3];
std::vector<CCharacter> searchWeakSeedVec;
searchWeakSeedVec.reserve(16);
std::vector<CCharacter> searchRightWeakSeed;
searchRightWeakSeed.reserve(8);
std::vector<CCharacter> searchLeftWeakSeed;
searchLeftWeakSeed.reserve(8);
std::vector<CCharacter> slideRightWindow;
slideRightWindow.reserve(8);
std::vector<CCharacter> slideLeftWindow;
slideLeftWindow.reserve(8);
// draw weak seed and little seed from line;
// search for mser rect
if (1 && showDebug) {
std::cout << "search for mser rect:" << std::endl;
}
if (0 && showDebug) {
std::stringstream ss(std::stringstream::in | std::stringstream::out);
ss << "resources/image/tmp/" << img_index << "_1_" << "searcgMserRect.jpg";
imwrite(ss.str(), result);
}
if (1 && showDebug) {
std::cout << "mserCharacter:" << mserCharacter.size() << std::endl;
}
// if the count of strong seed is larger than max count, we dont need
// all the next steps, if not, we first need to search the weak seed in
// the same line as the strong seed. The judge condition contains the distance
// between strong seed and weak seed , and the rect simily of each other to improve
// the roubustnedd of the seed growing algorithm.
if (mserCharacter.size() < char_max_count) {
double thresh1 = 0.15;
double thresh2 = 2.0;
searchWeakSeed(searchCandidate, searchRightWeakSeed, thresh1, thresh2, line, rightPoint,
maxrect, plateResult, result, CharSearchDirection::RIGHT);
if (1 && showDebug) {
std::cout << "searchRightWeakSeed:" << searchRightWeakSeed.size() << std::endl;
}
for (auto seed : searchRightWeakSeed) {
cv::rectangle(result, seed.getCharacterPos(), Scalar(255, 0, 0), 1);
mserCharacter.push_back(seed);
}
searchWeakSeed(searchCandidate, searchLeftWeakSeed, thresh1, thresh2, line, leftPoint,
maxrect, plateResult, result, CharSearchDirection::LEFT);
if (1 && showDebug) {
std::cout << "searchLeftWeakSeed:" << searchLeftWeakSeed.size() << std::endl;
}
for (auto seed : searchLeftWeakSeed) {
cv::rectangle(result, seed.getCharacterPos(), Scalar(255, 0, 0), 1);
mserCharacter.push_back(seed);
}
}
// sometimes the weak seed is in the middle of the strong seed.
// and sometimes two strong seed are actually the two parts of one character.
// because we only consider the weak seed in the left and right direction of strong seed.
// now we examine all the strong seed and weak seed. not only to find the seed in the middle,
// but also to combine two seed which are parts of one character to one seed.
// only by this process, we could use the seed count as the condition to judge if or not to use slide window.
float min_thresh = 0.3f;
float max_thresh = 2.5f;
reFoundAndCombineRect(mserCharacter, min_thresh, max_thresh, dist, maxrect, result);
// if the characters count is less than max count
// this means the mser rect in the lines are not enough.
// sometimes there are still some characters could not be captured by mser algorithm,
// such as blur, low light ,and some chinese characters like zh-cuan.
// to handle this ,we use a simple slide window method to find them.
if (mserCharacter.size() < char_max_count) {
if (1 && showDebug) {
std::cout << "search chinese:" << std::endl;
std::cout << "judege the left is chinese:" << std::endl;
}
// if the left most character is chinese, this means
// that must be the first character in chinese plate,
// and we need not to do a slide window to left. So,
// the first thing is to judge the left charcater is
// or not the chinese.
bool leftIsChinese = false;
if (1) {
std::sort(mserCharacter.begin(), mserCharacter.end(),
[](const CCharacter &r1, const CCharacter &r2) {
return r1.getCharacterPos().tl().x < r2.getCharacterPos().tl().x;
});
CCharacter leftChar = mserCharacter[0];
//Rect theRect = adaptive_charrect_from_rect(leftChar.getCharacterPos(), image.cols, image.rows);
Rect theRect = leftChar.getCharacterPos();
//cv::rectangle(result, theRect, Scalar(255, 0, 0), 1);
Mat region = image(theRect);
Mat binary_region;
ostu_level = cv::threshold(region, binary_region, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
if (1 && showDebug) {
std::cout << "left : ostu_level:" << ostu_level << std::endl;
}
//plate.setOstuLevel(ostu_level);
Mat charInput = preprocessChar(binary_region, char_size);
if (0 /*&& showDebug*/) {
imshow("charInput", charInput);
waitKey(0);
destroyWindow("charInput");
}
std::string label = "";
float maxVal = -2.f;
leftIsChinese = CharsIdentify::instance()->isCharacter(charInput, label, maxVal, true);
//auto character = CharsIdentify::instance()->identifyChinese(charInput, maxVal, leftIsChinese);
//label = character.second;
if (0 /* && showDebug*/) {
std::cout << "isChinese:" << leftIsChinese << std::endl;
std::cout << "chinese:" << label;
std::cout << "__score:" << maxVal << std::endl;
}
}
// if the left most character is not a chinese,
// this means we meed to slide a window to find the missed mser rect.
// search for sliding window
float ratioWindow = 0.4f;
//float ratioWindow = CParams::instance()->getParam3f();
float threshIsCharacter = 0.8f;
//float threshIsCharacter = CParams::instance()->getParam3f();
if (!leftIsChinese) {
slideWindowSearch(image, slideLeftWindow, line, leftPoint, dist, ostu_level, ratioWindow, threshIsCharacter,
maxrect, plateResult, CharSearchDirection::LEFT, true, result);
if (1 && showDebug) {
std::cout << "slideLeftWindow:" << slideLeftWindow.size() << std::endl;
}
for (auto window : slideLeftWindow) {
cv::rectangle(result, window.getCharacterPos(), Scalar(0, 0, 255), 1);
mserCharacter.push_back(window);
}
}
}
// if we still have less than max count characters,
// we need to slide a window to right to search for the missed mser rect.
if (mserCharacter.size() < char_max_count) {
// change ostu_level
float ratioWindow = 0.4f;
//float ratioWindow = CParams::instance()->getParam3f();
float threshIsCharacter = 0.8f;
//float threshIsCharacter = CParams::instance()->getParam3f();
slideWindowSearch(image, slideRightWindow, line, rightPoint, dist, plate.getOstuLevel(), ratioWindow,
threshIsCharacter,
maxrect, plateResult, CharSearchDirection::RIGHT, false, result);
if (1 && showDebug) {
std::cout << "slideRightWindow:" << slideRightWindow.size() << std::endl;
}
for (auto window : slideRightWindow) {
cv::rectangle(result, window.getCharacterPos(), Scalar(0, 0, 255), 1);
mserCharacter.push_back(window);
}
}
// computer the plate angle
float angle = atan(k) * 180 / (float) CV_PI;
if (1 && showDebug) {
std::cout << "k:" << k << std::endl;
std::cout << "angle:" << angle << std::endl;
}
// the plateResult rect need to be enlarge to contains all the plate,
// not only the character area.
float widthEnlargeRatio = 1.15f; //1.15f;
float heightEnlargeRatio = 1.25f; //1.25f;
RotatedRect platePos(
Point2f((float) plateResult.x + plateResult.width / 2.f, (float) plateResult.y + plateResult.height / 2.f),
Size2f(plateResult.width * widthEnlargeRatio, maxrect.height * heightEnlargeRatio), angle);
// justify the size is likely to be a plate size.
if (verifyRotatedPlateSizes(platePos)) {
rotatedRectangle(result, platePos, Scalar(0, 0, 255), 1);
plate.setPlatePos(platePos);
plate.setPlateColor(the_color);
plate.setPlateLocateType(CMSER);
if (the_color == BLUE) out_plateVec_blue.push_back(plate);
if (the_color == YELLOW) out_plateVec_yellow.push_back(plate);
}
// use deskew to rotate the image, so we need the binary image.
if (1) {
for (auto mserChar : mserCharacter) {
Rect rect = mserChar.getCharacterPos();
match.at(color_index)(rect) = 255;
}
cv::line(match.at(color_index), rightPoint, leftPoint, Scalar(255));
}
}
if (0 /*&& showDebug*/) {
imshow("result", result);
waitKey(0);
destroyWindow("result");
}
if (0) {
imshow("match", match.at(color_index));
waitKey(0);
destroyWindow("match");
}
if (1) {
std::stringstream ss(std::stringstream::in | std::stringstream::out);
ss << "resources/image/tmp/plateDetect/plate_" << img_index << "_" << the_color << ".jpg";
imwrite(ss.str(), result);
}
}
}
// this spatial_ostu algorithm are robust to
// the plate which has the same light shine, which is that
// the light in the left of the plate is strong than the right.
void spatial_ostu(InputArray _src, int grid_x, int grid_y, Color type) {
Mat src = _src.getMat();
int width = src.cols / grid_x;
int height = src.rows / grid_y;
// iterate through grid
for (int i = 0; i < grid_y; i++) {
for (int j = 0; j < grid_x; j++) {
Mat src_cell = Mat(src, Range(i * height, (i + 1) * height), Range(j * width, (j + 1) * width));
if (type == BLUE) {
cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
} else if (type == YELLOW) {
cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV);
} else if (type == WHITE) {
cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV);
} else {
cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
}
}
}
}
bool mat_valid_position(const Mat &mat, int row, int col) {
return row >= 0 && col >= 0 && row < mat.rows && col < mat.cols;
}
template<class T>
static void mat_set_invoke(Mat &mat, int row, int col, const Scalar &value) {
if (1 == mat.channels()) {
mat.at<T>(row, col) = (T) value.val[0];
} else if (3 == mat.channels()) {
T *ptr_src = mat.ptr<T>(row, col);
*ptr_src++ = (T) value.val[0];
*ptr_src++ = (T) value.val[1];
*ptr_src = (T) value.val[2];
} else if (4 == mat.channels()) {
T *ptr_src = mat.ptr<T>(row, col);
*ptr_src++ = (T) value.val[0];
*ptr_src++ = (T) value.val[1];
*ptr_src++ = (T) value.val[2];
*ptr_src = (T) value.val[3];
}
}
void setPoint(Mat &mat, int row, int col, const Scalar &value) {
if (CV_8U == mat.depth()) {
mat_set_invoke<uchar>(mat, row, col, value);
} else if (CV_8S == mat.depth()) {
mat_set_invoke<char>(mat, row, col, value);
} else if (CV_16U == mat.depth()) {
mat_set_invoke<ushort>(mat, row, col, value);
} else if (CV_16S == mat.depth()) {
mat_set_invoke<short>(mat, row, col, value);
} else if (CV_32S == mat.depth()) {
mat_set_invoke<int>(mat, row, col, value);
} else if (CV_32F == mat.depth()) {
mat_set_invoke<float>(mat, row, col, value);
} else if (CV_64F == mat.depth()) {
mat_set_invoke<double>(mat, row, col, value);
}
}
Rect adaptive_charrect_from_rect(const Rect &rect, int maxwidth, int maxheight, bool useExtendHeight) {
int expendWidth = 0;
int extendHeight = 0;
if (rect.height > 3 * rect.width) {
expendWidth = int((int(rect.height * 0.5f) - rect.width) * 0.5f);
if (useExtendHeight) {
extendHeight = int(rect.height * 0.3f);
}
}
//Rect resultRect(rect.tl().x - expendWidth, rect.tl().y,
// rect.width + expendWidth * 2, rect.height);
int tlx = rect.tl().x - expendWidth > 0 ? rect.tl().x - expendWidth : 0;
int tly = rect.tl().y - extendHeight > 0 ? rect.tl().y - extendHeight : 0;
int brx = rect.br().x + expendWidth < maxwidth ? rect.br().x + expendWidth : maxwidth;
int bry = rect.br().y + extendHeight < maxheight ? rect.br().y + extendHeight : maxheight;
Rect resultRect(tlx, tly, brx - tlx, bry - tly);
return resultRect;
}
Mat adaptive_image_from_points(const std::vector<Point> &points,
const Rect &rect, const Size &size,
const Scalar &backgroundColor /* = ml_color_white */,
const Scalar &forgroundColor /* = ml_color_black */, bool gray /* = true */) {
int expendHeight = 0;
int expendWidth = 0;
if (rect.width > rect.height) {
expendHeight = (rect.width - rect.height) / 2;
} else if (rect.height > rect.width) {
expendWidth = (rect.height - rect.width) / 2;
}
Mat image(rect.height + expendHeight * 2, rect.width + expendWidth * 2, gray ? CV_8UC1 : CV_8UC3, backgroundColor);
for (int i = 0; i < (int) points.size(); ++i) {
Point point = points[i];
Point currentPt(point.x - rect.tl().x + expendWidth, point.y - rect.tl().y + expendHeight);
if (mat_valid_position(image, currentPt.y, currentPt.x)) {
setPoint(image, currentPt.y, currentPt.x, forgroundColor);
}
}
Mat result;
cv::resize(image, result, size, 0, 0, INTER_NEAREST);
return result;
}
// calc safe Rect
// if not exit, return false
bool calcSafeRect(const RotatedRect &roi_rect, const Mat &src,
Rect_<float> &safeBoundRect) {
Rect_<float> boudRect = roi_rect.boundingRect();
float tl_x = boudRect.x > 0 ? boudRect.x : 0;
float tl_y = boudRect.y > 0 ? boudRect.y : 0;
float br_x = boudRect.x + boudRect.width < src.cols
? boudRect.x + boudRect.width - 1
: src.cols - 1;
float br_y = boudRect.y + boudRect.height < src.rows
? boudRect.y + boudRect.height - 1
: src.rows - 1;
float roi_width = br_x - tl_x;
float roi_height = br_y - tl_y;
if (roi_width <= 0 || roi_height <= 0) return false;
// a new rect not out the range of mat
safeBoundRect = Rect_<float>(tl_x, tl_y, roi_width, roi_height);
return true;
}
bool calcSafeRect(const RotatedRect &roi_rect, const int width, const int height,
Rect_<float> &safeBoundRect) {
Rect_<float> boudRect = roi_rect.boundingRect();
float tl_x = boudRect.x > 0 ? boudRect.x : 0;
float tl_y = boudRect.y > 0 ? boudRect.y : 0;
float br_x = boudRect.x + boudRect.width < width
? boudRect.x + boudRect.width - 1
: width - 1;
float br_y = boudRect.y + boudRect.height < height
? boudRect.y + boudRect.height - 1
: height - 1;
float roi_width = br_x - tl_x;
float roi_height = br_y - tl_y;
if (roi_width <= 0 || roi_height <= 0) return false;
// a new rect not out the range of mat
safeBoundRect = Rect_<float>(tl_x, tl_y, roi_width, roi_height);
return true;
}
Mat uniformResize(const Mat &result, float &scale) {
const int RESULTWIDTH = kShowWindowWidth; // 640 930
const int RESULTHEIGHT = kShowWindowHeight; // 540 710
Mat img_window;
img_window.create(RESULTHEIGHT, RESULTWIDTH, CV_8UC3);
int nRows = result.rows;
int nCols = result.cols;
Mat result_resize;
if (nCols <= img_window.cols && nRows <= img_window.rows) {
result_resize = result;
} else if (nCols > img_window.cols && nRows <= img_window.rows) {
scale = float(img_window.cols) / float(nCols);
resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA);
} else if (nCols <= img_window.cols && nRows > img_window.rows) {
scale = float(img_window.rows) / float(nRows);
resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA);
} else if (nCols > img_window.cols && nRows > img_window.rows) {
float scale1 = float(img_window.cols) / float(nCols);
float scale2 = float(img_window.rows) / float(nRows);
scale = scale1 < scale2 ? scale1 : scale2;
resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA);
} else {
result_resize = result;
}
return result_resize;
}
Mat uniformResizePlates (const Mat &result, float &scale) {
const int RESULTWIDTH = kPlateResizeWidth; // 640 930
const int RESULTHEIGHT = kPlateResizeHeight; // 540 710
Mat img_window;
img_window.create(RESULTHEIGHT, RESULTWIDTH, CV_8UC3);
int nRows = result.rows;
int nCols = result.cols;
Mat result_resize;
if (nCols <= img_window.cols && nRows <= img_window.rows) {
result_resize = result;
}
else if (nCols > img_window.cols && nRows <= img_window.rows) {
scale = float(img_window.cols) / float(nCols);
resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA);
}
else if (nCols <= img_window.cols && nRows > img_window.rows) {
scale = float(img_window.rows) / float(nRows);
resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA);
}
else if (nCols > img_window.cols && nRows > img_window.rows) {
float scale1 = float(img_window.cols) / float(nCols);
float scale2 = float(img_window.rows) / float(nRows);
scale = scale1 < scale2 ? scale1 : scale2;
resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA);
}
else {
result_resize = result;
}
return result_resize;
}
void showDectectResults(const Mat &img, const vector<CPlate> &plateVec, size_t num) {
int index = 0;
if (1) {
Mat result;
img.copyTo(result);
for (size_t j = 0; j < plateVec.size(); j++) {
// add plates to left corner
const CPlate& item = plateVec.at(j);
Mat plateMat = item.getPlateMat();
int height = 36;
int width = 136;
if (height * index + height < result.rows) {
Mat imageRoi = result(Rect(0, 0 + height * index, width, height));
addWeighted(imageRoi, 0, plateMat, 1, 0, imageRoi);
}
index++;
// draw the bouding box
RotatedRect theRect = item.getPlatePos();
float scale = item.getPlateScale();
RotatedRect minRect = scaleBackRRect(theRect, scale);
Point2f rect_points[4];
minRect.points(rect_points);
Scalar lineColor = Scalar(255, 255, 255);
if (item.getPlateLocateType() == SOBEL) lineColor = Scalar(255, 0, 0);
if (item.getPlateLocateType() == COLOR) lineColor = Scalar(0, 255, 0);
if (item.getPlateLocateType() == CMSER) lineColor = Scalar(0, 0, 255);
for (int j = 0; j < 4; j++)
line(result, rect_points[j], rect_points[(j + 1) % 4], lineColor, 2, 8);
}
showResult(result);
}
}
Mat showResult(const Mat &result, int img_index) {
namedWindow("EasyPR", CV_WINDOW_AUTOSIZE);
const int RESULTWIDTH = kShowWindowWidth; // 640 930
const int RESULTHEIGHT = kShowWindowHeight; // 540 710
Mat img_window;
img_window.create(RESULTHEIGHT, RESULTWIDTH, CV_8UC3);
int nRows = result.rows;
int nCols = result.cols;
Mat result_resize;
if (nCols <= img_window.cols && nRows <= img_window.rows) {
result_resize = result;
} else if (nCols > img_window.cols && nRows <= img_window.rows) {
float scale = float(img_window.cols) / float(nCols);
resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA);
} else if (nCols <= img_window.cols && nRows > img_window.rows) {
float scale = float(img_window.rows) / float(nRows);
resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA);
} else if (nCols > img_window.cols && nRows > img_window.rows) {
float scale1 = float(img_window.cols) / float(nCols);
float scale2 = float(img_window.rows) / float(nRows);
float scale = scale1 < scale2 ? scale1 : scale2;
resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA);
}
else {
result_resize = result;
}
Mat imageRoi = img_window(Rect((RESULTWIDTH - result_resize.cols) / 2,
(RESULTHEIGHT - result_resize.rows) / 2,
result_resize.cols, result_resize.rows));
addWeighted(imageRoi, 0, result_resize, 1, 0, imageRoi);
if (1) {
imshow("EasyPR", img_window);
waitKey(0);
destroyWindow("EasyPR");
}
if (1) {
std::stringstream ss(std::stringstream::in | std::stringstream::out);
ss << "resources/image/tmp/Result/plate_" << img_index << ".jpg";
imwrite(ss.str(), img_window);
}
return img_window;
}
Rect rectEnlarge(const Rect &src, const int mat_width, const int mat_height) {
float w = (float) src.width;
float h = (float) src.height;
// enlarge the rect,
// width to 120%
// height to 105%
float new_w = w * 1.2f;
float new_h = h * 1.05f;
Rect_<float> boudRect;
boudRect.x = (float) src.x - w * 0.1f;
boudRect.y = (float) src.y - h * 0.025f;
boudRect.width = new_w;
boudRect.height = new_h;
float tl_x = boudRect.x > 0 ? boudRect.x : 0;
float tl_y = boudRect.y > 0 ? boudRect.y : 0;
float br_x = boudRect.x + boudRect.width - 1 <= mat_width - 1
? boudRect.x + boudRect.width - 1
: mat_width - 1;
float br_y = boudRect.y + boudRect.height - 1 < mat_height - 1
? boudRect.y + boudRect.height - 1
: mat_height - 1;
float roi_width = br_x - tl_x + 1;
float roi_height = br_y - tl_y + 1;
Rect dst(0, 0, 0, 0);
if (roi_width <= 0 || roi_height <= 0)
return src;
//! a new rect not out the range of mat
dst = Rect_<float>(tl_x, tl_y, roi_width, roi_height);
return dst;
}
Rect rectFit(const Rect &src, const int mat_width, const int mat_height) {
float w = (float)src.width;
float h = (float)src.height;
float new_w = h * 0.5f;
float new_h = h * 1.05f;
if (new_w <= w || new_h <= h) {
return src;
}
float ex_w = (new_w - w) * 0.5f;
float ex_h = (new_h - h) * 0.5f;
Rect_<float> boudRect;
boudRect.x = (float)src.x - ex_w;
boudRect.y = (float)src.y - ex_h;
boudRect.width = new_w;
boudRect.height = new_h;
float tl_x = boudRect.x > 0 ? boudRect.x : 0;
float tl_y = boudRect.y > 0 ? boudRect.y : 0;
float br_x = boudRect.x + boudRect.width - 1 <= mat_width - 1
? boudRect.x + boudRect.width - 1
: mat_width - 1;
float br_y = boudRect.y + boudRect.height - 1 < mat_height - 1
? boudRect.y + boudRect.height - 1
: mat_height - 1;
float roi_width = br_x - tl_x + 1;
float roi_height = br_y - tl_y + 1;
Rect dst(0, 0, 0, 0);
if (roi_width <= 2 || roi_height <= 2)
return src;
//! a new rect not out the range of mat
dst = Rect_<float>(tl_x, tl_y, roi_width - 1, roi_height - 1);
return dst;
}
void writeTempImage(const Mat &outImg, const string path, int index) {
std::stringstream ss(std::stringstream::in | std::stringstream::out);
time_t t = time(0); // get time now
struct tm *now = localtime(&t);
char buf[80];
strftime(buf, sizeof(buf), "%Y-%m-%d %H_%M_%S", now);
ss << "resources/image/tmp/" << path << "_" << std::string(buf) << "_" << index << ".jpg";
imwrite(ss.str(), outImg);
}
}