|
|
|
@ -11,14 +11,17 @@ const float DEFAULT_ERROR = 0.9f; // 0.6
|
|
|
|
|
const float DEFAULT_ASPECT = 3.75f; // 3.75
|
|
|
|
|
|
|
|
|
|
CPlateLocate::CPlateLocate() {
|
|
|
|
|
//CPlateLocate函数用于车牌定位
|
|
|
|
|
m_GaussianBlurSize = DEFAULT_GAUSSIANBLUR_SIZE;
|
|
|
|
|
m_MorphSizeWidth = DEFAULT_MORPH_SIZE_WIDTH;
|
|
|
|
|
m_MorphSizeHeight = DEFAULT_MORPH_SIZE_HEIGHT;
|
|
|
|
|
|
|
|
|
|
//定义了高斯模糊大小、变形宽度、变形高度
|
|
|
|
|
m_error = DEFAULT_ERROR;
|
|
|
|
|
m_aspect = DEFAULT_ASPECT;
|
|
|
|
|
//默认错误和方面
|
|
|
|
|
m_verifyMin = DEFAULT_VERIFY_MIN;
|
|
|
|
|
m_verifyMax = DEFAULT_VERIFY_MAX;
|
|
|
|
|
//验证最小值与最大值
|
|
|
|
|
|
|
|
|
|
m_angle = DEFAULT_ANGLE;
|
|
|
|
|
|
|
|
|
@ -26,6 +29,7 @@ CPlateLocate::CPlateLocate() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CPlateLocate::setLifemode(bool param) {
|
|
|
|
|
//若参数param为真,设置成员变量为特定值,为假则设为初始值
|
|
|
|
|
if (param) {
|
|
|
|
|
setGaussianBlurSize(5);
|
|
|
|
|
setMorphSizeWidth(10);
|
|
|
|
@ -46,6 +50,8 @@ void CPlateLocate::setLifemode(bool param) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CPlateLocate::verifySizes(RotatedRect mr) {
|
|
|
|
|
//验证图像中给定的车牌区域大小是否满足预设的大小限制
|
|
|
|
|
//主要是在宽度、高度、面积,满足返回true
|
|
|
|
|
float error = m_error;
|
|
|
|
|
// Spain car plate size: 52x11 aspect 4,7272
|
|
|
|
|
// China car plate size: 440mm*140mm,aspect 3.142857
|
|
|
|
@ -64,12 +70,14 @@ bool CPlateLocate::verifySizes(RotatedRect mr) {
|
|
|
|
|
float rmax = aspect + aspect * error;
|
|
|
|
|
|
|
|
|
|
float area = mr.size.height * mr.size.width;
|
|
|
|
|
//r为宽高比
|
|
|
|
|
float r = (float) mr.size.width / (float) mr.size.height;
|
|
|
|
|
if (r < 1) r = (float) mr.size.height / (float) mr.size.width;
|
|
|
|
|
|
|
|
|
|
// cout << "area:" << area << endl;
|
|
|
|
|
// cout << "r:" << r << endl;
|
|
|
|
|
|
|
|
|
|
//判断车牌面积是否满足
|
|
|
|
|
if ((area < min || area > max) || (r < rmin || r > rmax))
|
|
|
|
|
return false;
|
|
|
|
|
else
|
|
|
|
@ -78,29 +86,32 @@ bool CPlateLocate::verifySizes(RotatedRect mr) {
|
|
|
|
|
|
|
|
|
|
//! mser search method
|
|
|
|
|
int CPlateLocate::mserSearch(const Mat &src, vector<Mat> &out,
|
|
|
|
|
//在输入的图像src中搜索并找到车牌区域,
|
|
|
|
|
// 并将找到的蓝色和黄色的车牌信息及其旋转矩形框信息和匹配后的灰度图像返回。
|
|
|
|
|
vector<vector<CPlate>>& out_plateVec, bool usePlateMser, vector<vector<RotatedRect>>& out_plateRRect,
|
|
|
|
|
|
|
|
|
|
int img_index, bool showDebug) {
|
|
|
|
|
vector<Mat> match_grey;
|
|
|
|
|
|
|
|
|
|
//存储匹配后的灰度图像。
|
|
|
|
|
vector<CPlate> plateVec_blue;
|
|
|
|
|
plateVec_blue.reserve(16);
|
|
|
|
|
vector<RotatedRect> plateRRect_blue;
|
|
|
|
|
plateRRect_blue.reserve(16);
|
|
|
|
|
|
|
|
|
|
//创建两个向量来存储蓝色的车牌信息和其旋转矩形框信息。
|
|
|
|
|
vector<CPlate> plateVec_yellow;
|
|
|
|
|
plateVec_yellow.reserve(16);
|
|
|
|
|
|
|
|
|
|
//创建两个向量来存储黄色的车牌信息和其旋转矩形框信息。
|
|
|
|
|
vector<RotatedRect> plateRRect_yellow;
|
|
|
|
|
plateRRect_yellow.reserve(16);
|
|
|
|
|
|
|
|
|
|
mserCharMatch(src, match_grey, plateVec_blue, plateVec_yellow, usePlateMser, plateRRect_blue, plateRRect_yellow, img_index, showDebug);
|
|
|
|
|
|
|
|
|
|
//调用MSER算法的函数,输入源图像和各种参数,输出匹配后的灰度图像以及两种颜色的车牌信息和其旋转矩形框信息。
|
|
|
|
|
out_plateVec.push_back(plateVec_blue);
|
|
|
|
|
out_plateVec.push_back(plateVec_yellow);
|
|
|
|
|
|
|
|
|
|
//将找到的蓝色和黄色的车牌信息添加到out_plateVec向量中。
|
|
|
|
|
out_plateRRect.push_back(plateRRect_blue);
|
|
|
|
|
out_plateRRect.push_back(plateRRect_yellow);
|
|
|
|
|
|
|
|
|
|
//将找到的蓝色和黄色的车牌的旋转矩形框信息添加到out_plateRRect向量中。
|
|
|
|
|
out = match_grey;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
@ -111,21 +122,26 @@ int CPlateLocate::colorSearch(const Mat &src, const Color r, Mat &out,
|
|
|
|
|
vector<RotatedRect> &outRects) {
|
|
|
|
|
Mat match_grey;
|
|
|
|
|
|
|
|
|
|
//在输入的图像(src)中搜索特定颜色(r)的区域,
|
|
|
|
|
// 并返回找到的区域(RotatedRect)的列表
|
|
|
|
|
// width is important to the final results;
|
|
|
|
|
const int color_morph_width = 10;
|
|
|
|
|
const int color_morph_height = 2;
|
|
|
|
|
|
|
|
|
|
colorMatch(src, match_grey, r, false);
|
|
|
|
|
//将输入图像·src转换为灰度图像match_grey
|
|
|
|
|
SHOW_IMAGE(match_grey, 0);
|
|
|
|
|
|
|
|
|
|
Mat src_threshold;
|
|
|
|
|
threshold(match_grey, src_threshold, 0, 255,
|
|
|
|
|
CV_THRESH_OTSU + CV_THRESH_BINARY);
|
|
|
|
|
|
|
|
|
|
//使用阈值函数(threshold)对匹配后的图像进行处理,
|
|
|
|
|
// 将像素值从0到255进行二值化处理
|
|
|
|
|
Mat element = getStructuringElement(
|
|
|
|
|
MORPH_RECT, Size(color_morph_width, color_morph_height));
|
|
|
|
|
morphologyEx(src_threshold, src_threshold, MORPH_CLOSE, element);
|
|
|
|
|
|
|
|
|
|
//morphologyEx进行形态学闭运算,
|
|
|
|
|
// 主要用于去除噪声以及填充空洞
|
|
|
|
|
//if (m_debug) {
|
|
|
|
|
// utils::imwrite("resources/image/tmp/color.jpg", src_threshold);
|
|
|
|
|
//}
|
|
|
|
@ -139,10 +155,11 @@ int CPlateLocate::colorSearch(const Mat &src, const Color r, Mat &out,
|
|
|
|
|
contours, // a vector of contours
|
|
|
|
|
CV_RETR_EXTERNAL,
|
|
|
|
|
CV_CHAIN_APPROX_NONE); // all pixels of each contours
|
|
|
|
|
|
|
|
|
|
//indContours函数会找到所有外部轮廓,即所有的区域都会被找到。
|
|
|
|
|
vector<vector<Point>>::iterator itc = contours.begin();
|
|
|
|
|
while (itc != contours.end()) {
|
|
|
|
|
RotatedRect mr = minAreaRect(Mat(*itc));
|
|
|
|
|
//对找到的每个轮廓执行minAreaRect函数,得到该轮廓的最小外接矩形(mr)
|
|
|
|
|
|
|
|
|
|
if (!verifySizes(mr))
|
|
|
|
|
itc = contours.erase(itc);
|
|
|
|
@ -150,6 +167,8 @@ int CPlateLocate::colorSearch(const Mat &src, const Color r, Mat &out,
|
|
|
|
|
++itc;
|
|
|
|
|
outRects.push_back(mr);
|
|
|
|
|
}
|
|
|
|
|
//最后通过verifySizes函数检查得到的最小外接矩形是否满足预设的条件。
|
|
|
|
|
// 如果满足条件,则将该矩形添加到outRects列表中;如果不满足条件,则删除该轮廓
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
@ -158,11 +177,15 @@ int CPlateLocate::colorSearch(const Mat &src, const Color r, Mat &out,
|
|
|
|
|
|
|
|
|
|
int CPlateLocate::sobelFrtSearch(const Mat &src,
|
|
|
|
|
vector<Rect_<float>> &outRects) {
|
|
|
|
|
//主要功能是在输入的图像(src)中搜索边缘,
|
|
|
|
|
// 并返回找到的区域(RotatedRect)的列表。
|
|
|
|
|
Mat src_threshold;
|
|
|
|
|
|
|
|
|
|
//对输入图像进行边缘检测后的结果。
|
|
|
|
|
sobelOper(src, src_threshold, m_GaussianBlurSize, m_MorphSizeWidth,
|
|
|
|
|
m_MorphSizeHeight);
|
|
|
|
|
|
|
|
|
|
//调用sobelOper函数,对输入图像src进行边缘检测,
|
|
|
|
|
// 并将结果存储在src_threshold中
|
|
|
|
|
//该函数还使用了高斯模糊和形态学操作来增强边缘检测的效果
|
|
|
|
|
vector<vector<Point>> contours;
|
|
|
|
|
findContours(src_threshold,
|
|
|
|
|
contours, // a vector of contours
|
|
|
|
@ -170,19 +193,22 @@ int CPlateLocate::sobelFrtSearch(const Mat &src,
|
|
|
|
|
CV_CHAIN_APPROX_NONE); // all pixels of each contours
|
|
|
|
|
|
|
|
|
|
vector<vector<Point>>::iterator itc = contours.begin();
|
|
|
|
|
|
|
|
|
|
//调用findContours函数找到边缘检测后的轮廓。
|
|
|
|
|
vector<RotatedRect> first_rects;
|
|
|
|
|
|
|
|
|
|
while (itc != contours.end()) {
|
|
|
|
|
RotatedRect mr = minAreaRect(Mat(*itc));
|
|
|
|
|
|
|
|
|
|
//计算其最小外接矩形(minAreaRect函数)
|
|
|
|
|
|
|
|
|
|
if (verifySizes(mr)) {
|
|
|
|
|
//检查这个矩形是否满足预设的条件(verifySizes函数)。
|
|
|
|
|
// 如果满足条件,将这个矩形加入到first_rects列表中
|
|
|
|
|
first_rects.push_back(mr);
|
|
|
|
|
|
|
|
|
|
float area = mr.size.height * mr.size.width;
|
|
|
|
|
float r = (float) mr.size.width / (float) mr.size.height;
|
|
|
|
|
if (r < 1) r = (float) mr.size.height / (float) mr.size.width;
|
|
|
|
|
//计算其面积和宽高比(r)。如果r小于1,则重新计算宽高比。
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
++itc;
|
|
|
|
@ -193,8 +219,11 @@ int CPlateLocate::sobelFrtSearch(const Mat &src,
|
|
|
|
|
|
|
|
|
|
Rect_<float> safeBoundRect;
|
|
|
|
|
if (!calcSafeRect(roi_rect, src, safeBoundRect)) continue;
|
|
|
|
|
|
|
|
|
|
//对于每一个在first_rects中的矩形,
|
|
|
|
|
// 计算其在原图中的安全边界矩形
|
|
|
|
|
//如果计算失败,则跳过当前循环。
|
|
|
|
|
outRects.push_back(safeBoundRect);
|
|
|
|
|
//将每个安全边界矩形加入到outRects列表中。
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
@ -202,20 +231,23 @@ int CPlateLocate::sobelFrtSearch(const Mat &src,
|
|
|
|
|
|
|
|
|
|
int CPlateLocate::sobelSecSearchPart(Mat &bound, Point2f refpoint,
|
|
|
|
|
vector<RotatedRect> &outRects) {
|
|
|
|
|
//功能:在输入的图像(bound)中搜索边缘,并返回找到的区域(RotatedRect)的列表(outRects)
|
|
|
|
|
Mat bound_threshold;
|
|
|
|
|
|
|
|
|
|
sobelOperT(bound, bound_threshold, 3, 6, 2);
|
|
|
|
|
|
|
|
|
|
//使用sobelOperT函数进行边缘检测,其中3、6、2是sobel算子的大小。
|
|
|
|
|
Mat tempBoundThread = bound_threshold.clone();
|
|
|
|
|
|
|
|
|
|
clearLiuDingOnly(tempBoundThread);
|
|
|
|
|
|
|
|
|
|
int posLeft = 0, posRight = 0;
|
|
|
|
|
if (bFindLeftRightBound(tempBoundThread, posLeft, posRight)) {
|
|
|
|
|
|
|
|
|
|
//使用bFindLeftRightBound函数查找图像的左右边界,
|
|
|
|
|
// find left and right bounds to repair
|
|
|
|
|
|
|
|
|
|
if (posRight != 0 && posLeft != 0 && posLeft < posRight) {
|
|
|
|
|
// 如果左边界不为0且右边界不为0且左边界小于右边界,
|
|
|
|
|
// 则在图像中央位置的左右边界之间填充白色
|
|
|
|
|
int posY = int(bound_threshold.rows * 0.5);
|
|
|
|
|
for (int i = posLeft + (int) (bound_threshold.rows * 0.1);
|
|
|
|
|
i < posRight - 4; i++) {
|
|
|
|
@ -224,13 +256,16 @@ int CPlateLocate::sobelSecSearchPart(Mat &bound, Point2f refpoint,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
utils::imwrite("resources/image/tmp/repaireimg1.jpg", bound_threshold);
|
|
|
|
|
|
|
|
|
|
//保存处理后的图像
|
|
|
|
|
// remove the left and right boundaries
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < bound_threshold.rows; i++) {
|
|
|
|
|
for (int i = 0; i < bound_threshold.rows; i++)
|
|
|
|
|
{
|
|
|
|
|
//每个行上,将左边界和右边界的像素设置为0。
|
|
|
|
|
bound_threshold.data[i * bound_threshold.cols + posLeft] = 0;
|
|
|
|
|
bound_threshold.data[i * bound_threshold.cols + posRight] = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
utils::imwrite("resources/image/tmp/repaireimg2.jpg", bound_threshold);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -239,23 +274,28 @@ int CPlateLocate::sobelSecSearchPart(Mat &bound, Point2f refpoint,
|
|
|
|
|
contours, // a vector of contours
|
|
|
|
|
CV_RETR_EXTERNAL,
|
|
|
|
|
CV_CHAIN_APPROX_NONE); // all pixels of each contours
|
|
|
|
|
|
|
|
|
|
//调用findContours函数找到边缘检测后的轮廓。
|
|
|
|
|
vector<vector<Point>>::iterator itc = contours.begin();
|
|
|
|
|
|
|
|
|
|
vector<RotatedRect> second_rects;
|
|
|
|
|
while (itc != contours.end()) {
|
|
|
|
|
RotatedRect mr = minAreaRect(Mat(*itc));
|
|
|
|
|
second_rects.push_back(mr);
|
|
|
|
|
//对于每个轮廓,计算其最小外接矩形(minAreaRect函数)
|
|
|
|
|
//并将结果添加到second_rects列表中。
|
|
|
|
|
++itc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < second_rects.size(); i++) {
|
|
|
|
|
RotatedRect roi = second_rects[i];
|
|
|
|
|
|
|
|
|
|
if (verifySizes(roi)) {
|
|
|
|
|
//对于second_rects中的每个矩形,
|
|
|
|
|
// 如果满足条件(verifySizes函数)则计算其中心、大小和角度
|
|
|
|
|
Point2f refcenter = roi.center + refpoint;
|
|
|
|
|
Size2f size = roi.size;
|
|
|
|
|
float angle = roi.angle;
|
|
|
|
|
|
|
|
|
|
//创建一个新的RotatedRect对象,然后将其添加到outRects列表中。
|
|
|
|
|
RotatedRect refroi(refcenter, size, angle);
|
|
|
|
|
outRects.push_back(refroi);
|
|
|
|
|
}
|
|
|
|
@ -267,6 +307,8 @@ int CPlateLocate::sobelSecSearchPart(Mat &bound, Point2f refpoint,
|
|
|
|
|
|
|
|
|
|
int CPlateLocate::sobelSecSearch(Mat &bound, Point2f refpoint,
|
|
|
|
|
vector<RotatedRect> &outRects) {
|
|
|
|
|
//功能:在输入的图像(bound)中搜索边缘,并返回找到的区域(RotatedRect)的列表(outRects)
|
|
|
|
|
//函数内部的函数功能和sobelSecSearch函数差不多
|
|
|
|
|
Mat bound_threshold;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -307,19 +349,24 @@ int CPlateLocate::sobelSecSearch(Mat &bound, Point2f refpoint,
|
|
|
|
|
|
|
|
|
|
int CPlateLocate::sobelOper(const Mat &in, Mat &out, int blurSize, int morphW,
|
|
|
|
|
int morphH) {
|
|
|
|
|
//功能是对输入图像进行边缘检测,并返回检测到的边缘图像
|
|
|
|
|
Mat mat_blur;
|
|
|
|
|
mat_blur = in.clone();
|
|
|
|
|
GaussianBlur(in, mat_blur, Size(blurSize, blurSize), 0, 0, BORDER_DEFAULT);
|
|
|
|
|
//对输入图像in进行高斯模糊处理,并将结果存储在mat_blur中
|
|
|
|
|
|
|
|
|
|
Mat mat_gray;
|
|
|
|
|
if (mat_blur.channels() == 3)
|
|
|
|
|
cvtColor(mat_blur, mat_gray, CV_RGB2GRAY);
|
|
|
|
|
//如果是彩色图,则转换为灰度图像
|
|
|
|
|
//否则直接将mat_blur作为灰度图像。
|
|
|
|
|
else
|
|
|
|
|
mat_gray = mat_blur;
|
|
|
|
|
|
|
|
|
|
int scale = SOBEL_SCALE;
|
|
|
|
|
int delta = SOBEL_DELTA;
|
|
|
|
|
int ddepth = SOBEL_DDEPTH;
|
|
|
|
|
//三个变量分别表示Sobel算子中的尺度、偏差和深度。
|
|
|
|
|
|
|
|
|
|
Mat grad_x, grad_y;
|
|
|
|
|
Mat abs_grad_x, abs_grad_y;
|
|
|
|
@ -327,30 +374,31 @@ int CPlateLocate::sobelOper(const Mat &in, Mat &out, int blurSize, int morphW,
|
|
|
|
|
|
|
|
|
|
Sobel(mat_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT);
|
|
|
|
|
convertScaleAbs(grad_x, abs_grad_x);
|
|
|
|
|
|
|
|
|
|
//调用Sobel函数,对灰度图像进行Sobel算子运算,得到梯度图像grad_x。
|
|
|
|
|
Mat grad;
|
|
|
|
|
addWeighted(abs_grad_x, SOBEL_X_WEIGHT, 0, 0, 0, grad);
|
|
|
|
|
|
|
|
|
|
//将x方向的梯度图像和y方向的梯度图像加权叠加,得到最终的梯度图像grad。
|
|
|
|
|
Mat mat_threshold;
|
|
|
|
|
double otsu_thresh_val =
|
|
|
|
|
threshold(grad, mat_threshold, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
|
|
|
|
|
|
|
|
|
|
//对梯度图像grad进行阈值处理,得到二值化图像mat_threshold
|
|
|
|
|
|
|
|
|
|
Mat element = getStructuringElement(MORPH_RECT, Size(morphW, morphH));
|
|
|
|
|
morphologyEx(mat_threshold, mat_threshold, MORPH_CLOSE, element);
|
|
|
|
|
|
|
|
|
|
//对二值化图像mat_threshold进行形态学闭运算,以填充孔洞和连接断开的边缘
|
|
|
|
|
out = mat_threshold;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void deleteNotArea(Mat &inmat, Color color = UNKNOWN) {
|
|
|
|
|
//用于删除图像中的非区域部分,并保留特定数据
|
|
|
|
|
Mat input_grey;
|
|
|
|
|
cvtColor(inmat, input_grey, CV_BGR2GRAY);
|
|
|
|
|
|
|
|
|
|
//将输入图像转换为灰度图像
|
|
|
|
|
int w = inmat.cols;
|
|
|
|
|
int h = inmat.rows;
|
|
|
|
|
|
|
|
|
|
//从输入图像中截取一个子区域。
|
|
|
|
|
Mat tmpMat = inmat(Rect_<double>(w * 0.15, h * 0.1, w * 0.7, h * 0.7));
|
|
|
|
|
|
|
|
|
|
Color plateType;
|
|
|
|
@ -360,36 +408,42 @@ void deleteNotArea(Mat &inmat, Color color = UNKNOWN) {
|
|
|
|
|
else {
|
|
|
|
|
plateType = color;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//根据输入的颜色参数,确定要保留的颜色类型。
|
|
|
|
|
//如果输入颜色为未知(UNKNOWN),则通过getPlateType函数确定颜色类型。
|
|
|
|
|
Mat img_threshold;
|
|
|
|
|
|
|
|
|
|
if (BLUE == plateType) {
|
|
|
|
|
img_threshold = input_grey.clone();
|
|
|
|
|
//若车牌是蓝色,克隆灰度图像input_grey到img_threshold
|
|
|
|
|
Mat tmp = input_grey(Rect_<double>(w * 0.15, h * 0.15, w * 0.7, h * 0.7));
|
|
|
|
|
// 在input_grey中截取中心70%的部分进行阈值分割
|
|
|
|
|
int threadHoldV = ThresholdOtsu(tmp);
|
|
|
|
|
|
|
|
|
|
threshold(input_grey, img_threshold, threadHoldV, 255, CV_THRESH_BINARY);
|
|
|
|
|
// threshold(input_grey, img_threshold, 5, 255, CV_THRESH_OTSU +
|
|
|
|
|
// CV_THRESH_BINARY);
|
|
|
|
|
|
|
|
|
|
// 应用计算出的最佳阈值进行二值化,即低于阈值的像素点变为0,高于阈值的变为255
|
|
|
|
|
utils::imwrite("resources/image/tmp/inputgray2.jpg", img_threshold);
|
|
|
|
|
//保存二值化处理后的图像
|
|
|
|
|
} else if (YELLOW == plateType) {// 如果车牌类型是黄色
|
|
|
|
|
|
|
|
|
|
} else if (YELLOW == plateType) {
|
|
|
|
|
img_threshold = input_grey.clone();
|
|
|
|
|
|
|
|
|
|
Mat tmp = input_grey(Rect_<double>(w * 0.1, h * 0.1, w * 0.8, h * 0.8));
|
|
|
|
|
// 在input_grey中截取中心80%的部分进行阈值分割
|
|
|
|
|
int threadHoldV = ThresholdOtsu(tmp);
|
|
|
|
|
|
|
|
|
|
// 使用Otsu方法计算最佳阈值
|
|
|
|
|
threshold(input_grey, img_threshold, threadHoldV, 255,
|
|
|
|
|
CV_THRESH_BINARY_INV);
|
|
|
|
|
|
|
|
|
|
// 应用计算出的最佳阈值进行反向二值化,即高于阈值的像素点变为0,低于阈值的变为255
|
|
|
|
|
utils::imwrite("resources/image/tmp/inputgray2.jpg", img_threshold);
|
|
|
|
|
|
|
|
|
|
// threshold(input_grey, img_threshold, 10, 255, CV_THRESH_OTSU +
|
|
|
|
|
// CV_THRESH_BINARY_INV);
|
|
|
|
|
} else
|
|
|
|
|
} else// 如果既不是蓝色也不是黄色的车牌
|
|
|
|
|
threshold(input_grey, img_threshold, 10, 255,
|
|
|
|
|
CV_THRESH_OTSU + CV_THRESH_BINARY);
|
|
|
|
|
|
|
|
|
|
// 直接使用固定阈值10进行OTSU二值化处理
|
|
|
|
|
//img_threshold = input_grey.clone();
|
|
|
|
|
//spatial_ostu(img_threshold, 8, 2, plateType);
|
|
|
|
|
|
|
|
|
@ -399,13 +453,13 @@ void deleteNotArea(Mat &inmat, Color color = UNKNOWN) {
|
|
|
|
|
int top = 0;
|
|
|
|
|
int bottom = img_threshold.rows - 1;
|
|
|
|
|
clearLiuDing(img_threshold, top, bottom);
|
|
|
|
|
|
|
|
|
|
if (0) {
|
|
|
|
|
//清除图像中噪声
|
|
|
|
|
if (0) { //用于调试使用
|
|
|
|
|
imshow("inmat", inmat);
|
|
|
|
|
waitKey(0);
|
|
|
|
|
destroyWindow("inmat");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
if (bFindLeftRightBound1(img_threshold, posLeft, posRight)) {
|
|
|
|
|
inmat = inmat(Rect(posLeft, top, w - posLeft, bottom - top));
|
|
|
|
|
if (0) {
|
|
|
|
@ -414,6 +468,7 @@ void deleteNotArea(Mat &inmat, Color color = UNKNOWN) {
|
|
|
|
|
destroyWindow("inmat");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 如果找到了图像的左右边界,则将图像裁剪为这个边界内的部分。
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -422,20 +477,21 @@ int CPlateLocate::deskew(const Mat &src, const Mat &src_b,
|
|
|
|
|
vector<CPlate> &outPlates, bool useDeteleArea, Color color) {
|
|
|
|
|
Mat mat_debug;
|
|
|
|
|
src.copyTo(mat_debug);
|
|
|
|
|
//创建一个新的Mat对象,并将源图像复制到这个新对象。
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < inRects.size(); i++) {
|
|
|
|
|
for (size_t i = 0; i < inRects.size(); i++) { //遍历输入的旋转句型
|
|
|
|
|
RotatedRect roi_rect = inRects[i];
|
|
|
|
|
|
|
|
|
|
float r = (float) roi_rect.size.width / (float) roi_rect.size.height;
|
|
|
|
|
float roi_angle = roi_rect.angle;
|
|
|
|
|
float roi_angle = roi_rect.angle;//计算旋转矩形的宽高比和角度。
|
|
|
|
|
|
|
|
|
|
Size roi_rect_size = roi_rect.size;
|
|
|
|
|
if (r < 1) {
|
|
|
|
|
roi_angle = 90 + roi_angle;
|
|
|
|
|
swap(roi_rect_size.width, roi_rect_size.height);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_debug) {
|
|
|
|
|
//如果宽高比小于1,说明矩形是竖直的,需要调整角度和宽高。
|
|
|
|
|
if (m_debug) { //调试模式,绘制旋转矩形的边界
|
|
|
|
|
Point2f rect_points[4];
|
|
|
|
|
roi_rect.points(rect_points);
|
|
|
|
|
for (int j = 0; j < 4; j++)
|
|
|
|
@ -448,14 +504,14 @@ int CPlateLocate::deskew(const Mat &src, const Mat &src_b,
|
|
|
|
|
// rotation < m_angel;
|
|
|
|
|
|
|
|
|
|
// m_angle=60
|
|
|
|
|
if (roi_angle - m_angle < 0 && roi_angle + m_angle > 0) {
|
|
|
|
|
if (roi_angle - m_angle < 0 && roi_angle + m_angle > 0) { //旋转矩形的角度在合理范围时
|
|
|
|
|
Rect_<float> safeBoundRect;
|
|
|
|
|
bool isFormRect = calcSafeRect(roi_rect, src, safeBoundRect);
|
|
|
|
|
bool isFormRect = calcSafeRect(roi_rect, src, safeBoundRect); //计算旋转矩形的安全边界。
|
|
|
|
|
if (!isFormRect) continue;
|
|
|
|
|
|
|
|
|
|
Mat bound_mat = src(safeBoundRect);
|
|
|
|
|
Mat bound_mat_b = src_b(safeBoundRect);
|
|
|
|
|
|
|
|
|
|
//根据安全边界,从源图像中截取子图像。
|
|
|
|
|
if (0) {
|
|
|
|
|
imshow("bound_mat_b", bound_mat_b);
|
|
|
|
|
waitKey(0);
|
|
|
|
@ -468,13 +524,16 @@ int CPlateLocate::deskew(const Mat &src, const Mat &src_b,
|
|
|
|
|
if ((roi_angle - 5 < 0 && roi_angle + 5 > 0) || 90.0 == roi_angle ||
|
|
|
|
|
-90.0 == roi_angle) {
|
|
|
|
|
deskew_mat = bound_mat;
|
|
|
|
|
} else {
|
|
|
|
|
//如果矩形角度接近0度或90度,直接使用截取的子图像,
|
|
|
|
|
|
|
|
|
|
} else {//倾斜调整
|
|
|
|
|
Mat rotated_mat;
|
|
|
|
|
Mat rotated_mat_b;
|
|
|
|
|
|
|
|
|
|
if (!rotation(bound_mat, rotated_mat, roi_rect_size, roi_ref_center, roi_angle))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
//对bound_mat应用旋转,旋转后存储在rotated_mat中,
|
|
|
|
|
//若旋转失败,继续下一次循环
|
|
|
|
|
if (!rotation(bound_mat_b, rotated_mat_b, roi_rect_size, roi_ref_center, roi_angle))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
@ -483,7 +542,9 @@ int CPlateLocate::deskew(const Mat &src, const Mat &src_b,
|
|
|
|
|
// imshow("1roated_mat",rotated_mat);
|
|
|
|
|
// imshow("rotated_mat_b",rotated_mat_b);
|
|
|
|
|
if (isdeflection(rotated_mat_b, roi_angle, roi_slope)) {
|
|
|
|
|
// 检查rotated_mat_b是否需要倾斜调整
|
|
|
|
|
affine(rotated_mat, deskew_mat, roi_slope);
|
|
|
|
|
//应用仿射变换
|
|
|
|
|
} else
|
|
|
|
|
deskew_mat = rotated_mat;
|
|
|
|
|
}
|
|
|
|
@ -494,18 +555,23 @@ int CPlateLocate::deskew(const Mat &src, const Mat &src_b,
|
|
|
|
|
// haitungaga add,affect 25% to full recognition.
|
|
|
|
|
if (useDeteleArea)
|
|
|
|
|
deleteNotArea(deskew_mat, color);
|
|
|
|
|
//用于删除非区域部分
|
|
|
|
|
|
|
|
|
|
if (deskew_mat.cols * 1.0 / deskew_mat.rows > 2.3 && deskew_mat.cols * 1.0 / deskew_mat.rows < 6) {
|
|
|
|
|
//检查经过校正的图像(deskew_mat)的长宽比是否在某个范围内
|
|
|
|
|
if (deskew_mat.cols >= WIDTH || deskew_mat.rows >= HEIGHT)
|
|
|
|
|
// 如果校正后的图像的宽(cols)或高(rows)超出预设的阈值(WIDTH或HEIGHT)
|
|
|
|
|
|
|
|
|
|
resize(deskew_mat, plate_mat, plate_mat.size(), 0, 0, INTER_AREA);
|
|
|
|
|
// 使用INTER_AREA(区域插值)方法缩小图像,保持plate_mat.size()大小
|
|
|
|
|
else
|
|
|
|
|
resize(deskew_mat, plate_mat, plate_mat.size(), 0, 0, INTER_CUBIC);
|
|
|
|
|
|
|
|
|
|
CPlate plate;
|
|
|
|
|
// 如果宽或高没有超出阈值,则使用INTER_CUBIC(三次插值)方法放大图像,保持plate_mat.size()大小
|
|
|
|
|
CPlate plate; //存储车牌信息
|
|
|
|
|
plate.setPlatePos(roi_rect);
|
|
|
|
|
plate.setPlateMat(plate_mat);
|
|
|
|
|
if (color != UNKNOWN) plate.setPlateColor(color);
|
|
|
|
|
outPlates.push_back(plate);
|
|
|
|
|
if (color != UNKNOWN) plate.setPlateColor(color); // 如果车牌颜色已知,则设置车牌颜色
|
|
|
|
|
outPlates.push_back(plate); //将包含车牌信息的对象输出
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -515,7 +581,7 @@ int CPlateLocate::deskew(const Mat &src, const Mat &src_b,
|
|
|
|
|
|
|
|
|
|
bool CPlateLocate::rotation(Mat &in, Mat &out, const Size rect_size,
|
|
|
|
|
const Point2f center, const double angle) {
|
|
|
|
|
if (0) {
|
|
|
|
|
if (0) {//通过打印imshow信息调试
|
|
|
|
|
imshow("in", in);
|
|
|
|
|
waitKey(0);
|
|
|
|
|
destroyWindow("in");
|
|
|
|
@ -523,33 +589,33 @@ bool CPlateLocate::rotation(Mat &in, Mat &out, const Size rect_size,
|
|
|
|
|
|
|
|
|
|
Mat in_large;
|
|
|
|
|
in_large.create(int(in.rows * 1.5), int(in.cols * 1.5), in.type());
|
|
|
|
|
|
|
|
|
|
//创建一个1.5倍in图像的行数、1.5倍in图像的列数且同类的图像
|
|
|
|
|
float x = in_large.cols / 2 - center.x > 0 ? in_large.cols / 2 - center.x : 0;
|
|
|
|
|
float y = in_large.rows / 2 - center.y > 0 ? in_large.rows / 2 - center.y : 0;
|
|
|
|
|
|
|
|
|
|
float width = x + in.cols < in_large.cols ? in.cols : in_large.cols - x;
|
|
|
|
|
float height = y + in.rows < in_large.rows ? in.rows : in_large.rows - y;
|
|
|
|
|
|
|
|
|
|
//计算原始图像 in 在放大图像 in_large 中的位置,确保其居中且不超出 in_large 的边界。
|
|
|
|
|
/*assert(width == in.cols);
|
|
|
|
|
assert(height == in.rows);*/
|
|
|
|
|
|
|
|
|
|
if (width != in.cols || height != in.rows) return false;
|
|
|
|
|
|
|
|
|
|
Mat imageRoi = in_large(Rect_<float>(x, y, width, height));
|
|
|
|
|
//如果计算得到的宽度和高度与原图 in 不匹配,函数返回 false。
|
|
|
|
|
Mat imageRoi = in_large(Rect_<float>(x, y, width, height));//合并原图并放大
|
|
|
|
|
addWeighted(imageRoi, 0, in, 1, 0, imageRoi);
|
|
|
|
|
|
|
|
|
|
//选择 in_large 中的一个区域 imageRoi,将原图 in 覆盖到这个区域上。
|
|
|
|
|
Point2f center_diff(in.cols / 2.f, in.rows / 2.f);
|
|
|
|
|
Point2f new_center(in_large.cols / 2.f, in_large.rows / 2.f);
|
|
|
|
|
|
|
|
|
|
Mat rot_mat = getRotationMatrix2D(new_center, angle, 1);
|
|
|
|
|
|
|
|
|
|
//使用 OpenCV 函数 getRotationMatrix2D 生成旋转矩阵 rot_mat。
|
|
|
|
|
/*imshow("in_copy", in_large);
|
|
|
|
|
waitKey(0);*/
|
|
|
|
|
|
|
|
|
|
Mat mat_rotated;
|
|
|
|
|
warpAffine(in_large, mat_rotated, rot_mat, Size(in_large.cols, in_large.rows),
|
|
|
|
|
CV_INTER_CUBIC);
|
|
|
|
|
|
|
|
|
|
//使用 warpAffine 函数将旋转矩阵应用于放大后的图像 in_large,结果存储在 mat_rotated 中
|
|
|
|
|
/*imshow("mat_rotated", mat_rotated);
|
|
|
|
|
waitKey(0);*/
|
|
|
|
|
|
|
|
|
@ -558,8 +624,9 @@ bool CPlateLocate::rotation(Mat &in, Mat &out, const Size rect_size,
|
|
|
|
|
new_center, img_crop);
|
|
|
|
|
|
|
|
|
|
out = img_crop;
|
|
|
|
|
|
|
|
|
|
if (0) {
|
|
|
|
|
//使用 getRectSubPix 函数根据指定的尺寸和中心点从旋转后的图像中裁剪出区域,
|
|
|
|
|
//结果存储在 img_crop 中,然后赋值给输出参数 out。
|
|
|
|
|
if (0) { //调试代码
|
|
|
|
|
imshow("out", out);
|
|
|
|
|
waitKey(0);
|
|
|
|
|
destroyWindow("out");
|
|
|
|
@ -574,7 +641,7 @@ bool CPlateLocate::rotation(Mat &in, Mat &out, const Size rect_size,
|
|
|
|
|
bool CPlateLocate::isdeflection(const Mat &in, const double angle,
|
|
|
|
|
double &slope) { /*imshow("in",in);
|
|
|
|
|
waitKey(0);*/
|
|
|
|
|
if (0) {
|
|
|
|
|
if (0) { //用于调试
|
|
|
|
|
imshow("in", in);
|
|
|
|
|
waitKey(0);
|
|
|
|
|
destroyWindow("in");
|
|
|
|
@ -584,7 +651,7 @@ bool CPlateLocate::isdeflection(const Mat &in, const double angle,
|
|
|
|
|
int nCols = in.cols;
|
|
|
|
|
|
|
|
|
|
assert(in.channels() == 1);
|
|
|
|
|
|
|
|
|
|
//获取图像的行数 nRows 和列数 nCols,并确认图像是单通道(灰度图)。
|
|
|
|
|
int comp_index[3];
|
|
|
|
|
int len[3];
|
|
|
|
|
|
|
|
|
@ -603,6 +670,7 @@ bool CPlateLocate::isdeflection(const Mat &in, const double angle,
|
|
|
|
|
while (0 == value && j < nCols) value = int(p[j++]);
|
|
|
|
|
|
|
|
|
|
len[i] = j;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// cout << "len[0]:" << len[0] << endl;
|
|
|
|
|