|
|
|
@ -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,9 @@ 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) {
|
|
|
|
|
|
|
|
|
|
//用于检测输入图像 in 是否有偏转,并计算斜率 slope
|
|
|
|
|
if (0) { //用于调试
|
|
|
|
|
imshow("in", in);
|
|
|
|
|
waitKey(0);
|
|
|
|
|
destroyWindow("in");
|
|
|
|
@ -584,16 +653,16 @@ 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];
|
|
|
|
|
|
|
|
|
|
// 分别计算1/4、1/2、3/4高度处的行索引
|
|
|
|
|
comp_index[0] = nRows / 4;
|
|
|
|
|
comp_index[1] = nRows / 4 * 2;
|
|
|
|
|
comp_index[2] = nRows / 4 * 3;
|
|
|
|
|
|
|
|
|
|
const uchar* p;
|
|
|
|
|
|
|
|
|
|
// 这个循环会在每个四分位的行上找到第一个非零值的位置
|
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
|
int index = comp_index[i];
|
|
|
|
|
p = in.ptr<uchar>(index);
|
|
|
|
@ -603,6 +672,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;
|
|
|
|
@ -618,19 +688,15 @@ bool CPlateLocate::isdeflection(const Mat &in, const double angle,
|
|
|
|
|
double PI = 3.14159265;
|
|
|
|
|
|
|
|
|
|
double g = tan(angle * PI / 180.0);
|
|
|
|
|
|
|
|
|
|
//检查最长和最短长度是否有显著差异
|
|
|
|
|
if (maxlen - len[1] > nCols / 32 || len[1] - minlen > nCols / 32) {
|
|
|
|
|
|
|
|
|
|
double slope_can_1 =
|
|
|
|
|
double(len[2] - len[0]) / double(comp_index[1]);
|
|
|
|
|
double slope_can_2 = double(len[1] - len[0]) / double(comp_index[0]);
|
|
|
|
|
double slope_can_3 = double(len[2] - len[1]) / double(comp_index[0]);
|
|
|
|
|
// cout<<"angle:"<<angle<<endl;
|
|
|
|
|
// cout<<"g:"<<g<<endl;
|
|
|
|
|
// cout << "slope_can_1:" << slope_can_1 << endl;
|
|
|
|
|
// cout << "slope_can_2:" << slope_can_2 << endl;
|
|
|
|
|
// cout << "slope_can_3:" << slope_can_3 << endl;
|
|
|
|
|
// if(g>=0)
|
|
|
|
|
|
|
|
|
|
// 选择和输入角度的正切值差异最小的斜率为最终值
|
|
|
|
|
slope = abs(slope_can_1 - g) <= abs(slope_can_2 - g) ? slope_can_1
|
|
|
|
|
: slope_can_2;
|
|
|
|
|
// cout << "slope:" << slope << endl;
|
|
|
|
@ -640,21 +706,23 @@ bool CPlateLocate::isdeflection(const Mat &in, const double angle,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void CPlateLocate::affine(const Mat &in, Mat &out, const double slope) {
|
|
|
|
|
// imshow("in", in);
|
|
|
|
|
// waitKey(0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//对输入图像进行仿射变换,用于矫正车牌图像倾斜
|
|
|
|
|
Point2f dstTri[3];
|
|
|
|
|
Point2f plTri[3];
|
|
|
|
|
|
|
|
|
|
//输入图像的高度和宽度
|
|
|
|
|
float height = (float) in.rows;
|
|
|
|
|
float width = (float) in.cols;
|
|
|
|
|
float xiff = (float) abs(slope) * height;
|
|
|
|
|
|
|
|
|
|
if (slope > 0) {
|
|
|
|
|
//如果斜率 slope > 0,变换将图像向右倾斜。
|
|
|
|
|
|
|
|
|
|
// right, new position is xiff/2
|
|
|
|
|
|
|
|
|
@ -666,7 +734,7 @@ void CPlateLocate::affine(const Mat &in, Mat &out, const double slope) {
|
|
|
|
|
dstTri[1] = Point2f(width - 1 - xiff / 2, 0);
|
|
|
|
|
dstTri[2] = Point2f(xiff / 2, height - 1);
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
|
|
//如果斜率 slope < 0,变换将图像向左倾斜。
|
|
|
|
|
// left, new position is -xiff/2
|
|
|
|
|
|
|
|
|
|
plTri[0] = Point2f(0 + xiff, 0);
|
|
|
|
@ -679,15 +747,18 @@ void CPlateLocate::affine(const Mat &in, Mat &out, const double slope) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Mat warp_mat = getAffineTransform(plTri, dstTri);
|
|
|
|
|
|
|
|
|
|
//使用 OpenCV 的 getAffineTransform 函数,根据源点和目标点计算仿射变换矩阵 warp_mat。
|
|
|
|
|
Mat affine_mat;
|
|
|
|
|
affine_mat.create((int) height, (int) width, TYPE);
|
|
|
|
|
|
|
|
|
|
if (in.rows > HEIGHT || in.cols > WIDTH)
|
|
|
|
|
if (in.rows > HEIGHT || in.cols > WIDTH)//根据输入图像的大小,选择不同的插值方法:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//如果图像的大小超过预设的 HEIGHT 或 WIDTH,使用 CV_INTER_AREA 插值,这个通常用于缩小。
|
|
|
|
|
warpAffine(in, affine_mat, warp_mat, affine_mat.size(),
|
|
|
|
|
CV_INTER_AREA);
|
|
|
|
|
else
|
|
|
|
|
//否则使用 CV_INTER_CUBIC 插值,这个插值方法在放大时可以提供平滑的边界。
|
|
|
|
|
warpAffine(in, affine_mat, warp_mat, affine_mat.size(), CV_INTER_CUBIC);
|
|
|
|
|
|
|
|
|
|
out = affine_mat;
|
|
|
|
@ -695,18 +766,20 @@ void CPlateLocate::affine(const Mat &in, Mat &out, const double slope) {
|
|
|
|
|
|
|
|
|
|
int CPlateLocate::plateColorLocate(Mat src, vector<CPlate> &candPlates,
|
|
|
|
|
int index) {
|
|
|
|
|
//初始化两个 RotatedRect 类型的向量 rects_color_blue 和 rects_color_yellow,以及两个 CPlate 类型的向量 plates_blue 和 plates_yellow
|
|
|
|
|
vector<RotatedRect> rects_color_blue;
|
|
|
|
|
rects_color_blue.reserve(64);
|
|
|
|
|
vector<RotatedRect> rects_color_yellow;
|
|
|
|
|
rects_color_yellow.reserve(64);
|
|
|
|
|
|
|
|
|
|
//这些向量用于存储找到的蓝色和黄色车牌的位置和信息。
|
|
|
|
|
vector<CPlate> plates_blue;
|
|
|
|
|
plates_blue.reserve(64);
|
|
|
|
|
vector<CPlate> plates_yellow;
|
|
|
|
|
plates_yellow.reserve(64);
|
|
|
|
|
|
|
|
|
|
Mat src_clone = src.clone();
|
|
|
|
|
|
|
|
|
|
//使用 OpenMP 并行处理,分别对蓝色和黄色车牌进行搜索和倾斜矫正。
|
|
|
|
|
//这是通过调用 colorSearch 和 deskew 函数完成的。
|
|
|
|
|
Mat src_b_blue;
|
|
|
|
|
Mat src_b_yellow;
|
|
|
|
|
#pragma omp parallel sections
|
|
|
|
@ -722,6 +795,7 @@ int CPlateLocate::plateColorLocate(Mat src, vector<CPlate> &candPlates,
|
|
|
|
|
deskew(src_clone, src_b_yellow, rects_color_yellow, plates_yellow, true, YELLOW);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//将找到的蓝色和黄色车牌信息添加到 candPlates 向量中。
|
|
|
|
|
|
|
|
|
|
candPlates.insert(candPlates.end(), plates_blue.begin(), plates_blue.end());
|
|
|
|
|
candPlates.insert(candPlates.end(), plates_yellow.begin(), plates_yellow.end());
|
|
|
|
@ -817,10 +891,13 @@ int CPlateLocate::plateMserLocate(Mat src, vector<CPlate> &candPlates, int img_i
|
|
|
|
|
|
|
|
|
|
int CPlateLocate::sobelOperT(const Mat &in, Mat &out, int blurSize, int morphW,
|
|
|
|
|
int morphH) {
|
|
|
|
|
//在输入图像(src)中使用 MSER(最大稳定极值区域)方法定位车牌
|
|
|
|
|
Mat mat_blur;
|
|
|
|
|
mat_blur = in.clone();
|
|
|
|
|
GaussianBlur(in, mat_blur, Size(blurSize, blurSize), 0, 0, BORDER_DEFAULT);
|
|
|
|
|
//对输入图像进行高斯模糊,这是为了减少噪声
|
|
|
|
|
|
|
|
|
|
//将模糊后的图像转换为灰度图像。如果原图像已经是灰度图,则直接使用。
|
|
|
|
|
Mat mat_gray;
|
|
|
|
|
if (mat_blur.channels() == 3)
|
|
|
|
|
cvtColor(mat_blur, mat_gray, CV_BGR2GRAY);
|
|
|
|
@ -834,24 +911,25 @@ int CPlateLocate::sobelOperT(const Mat &in, Mat &out, int blurSize, int morphW,
|
|
|
|
|
int scale = SOBEL_SCALE;
|
|
|
|
|
int delta = SOBEL_DELTA;
|
|
|
|
|
int ddepth = SOBEL_DDEPTH;
|
|
|
|
|
|
|
|
|
|
//对灰度图像应用 Sobel 操作,得到 x 和 y 方向的梯度。
|
|
|
|
|
Mat grad_x, grad_y;
|
|
|
|
|
Mat abs_grad_x, abs_grad_y;
|
|
|
|
|
|
|
|
|
|
Sobel(mat_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT);
|
|
|
|
|
convertScaleAbs(grad_x, abs_grad_x);
|
|
|
|
|
|
|
|
|
|
//将 x 方向的梯度转换为绝对值,然后与 y 方向的梯度合并(假设 y 方向的梯度为0)。
|
|
|
|
|
Mat grad;
|
|
|
|
|
addWeighted(abs_grad_x, 1, 0, 0, 0, grad);
|
|
|
|
|
|
|
|
|
|
utils::imwrite("resources/image/tmp/graygrad.jpg", grad);
|
|
|
|
|
|
|
|
|
|
//使用 Otsu 的阈值法对得到的梯度图像进行二值化
|
|
|
|
|
Mat mat_threshold;
|
|
|
|
|
double otsu_thresh_val =
|
|
|
|
|
threshold(grad, mat_threshold, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
|
|
|
|
|
|
|
|
|
|
utils::imwrite("resources/image/tmp/grayBINARY.jpg", mat_threshold);
|
|
|
|
|
|
|
|
|
|
//对二值化的图像进行形态学闭操作,这有助于连接相邻的区域。
|
|
|
|
|
Mat element = getStructuringElement(MORPH_RECT, Size(morphW, morphH));
|
|
|
|
|
morphologyEx(mat_threshold, mat_threshold, MORPH_CLOSE, element);
|
|
|
|
|
|
|
|
|
@ -865,6 +943,7 @@ int CPlateLocate::sobelOperT(const Mat &in, Mat &out, int blurSize, int morphW,
|
|
|
|
|
int CPlateLocate::plateSobelLocate(Mat src, vector<CPlate> &candPlates,
|
|
|
|
|
int index) {
|
|
|
|
|
vector<RotatedRect> rects_sobel_all;
|
|
|
|
|
//引用传递的 CPlate 类的矢量,用于存储最后识别为候选车牌的结果。
|
|
|
|
|
rects_sobel_all.reserve(256);
|
|
|
|
|
|
|
|
|
|
vector<CPlate> plates;
|
|
|
|
@ -874,11 +953,13 @@ int CPlateLocate::plateSobelLocate(Mat src, vector<CPlate> &candPlates,
|
|
|
|
|
bound_rects.reserve(256);
|
|
|
|
|
|
|
|
|
|
sobelFrtSearch(src, bound_rects);
|
|
|
|
|
|
|
|
|
|
//使用 Sobel 算子处理输入的图像 src 并返回可能的边界矩形 bound_rects。
|
|
|
|
|
vector<Rect_<float>> bound_rects_part;
|
|
|
|
|
bound_rects_part.reserve(256);
|
|
|
|
|
|
|
|
|
|
// enlarge area
|
|
|
|
|
//通过扩大每个边界矩形的面积进行进一步处理,这通常是为了使候选区域更大,
|
|
|
|
|
//以包含整个车牌。代码通过改变矩形的 x 坐标,宽度,和 y 坐标,高度来实现此目的。
|
|
|
|
|
for (size_t i = 0; i < bound_rects.size(); i++) {
|
|
|
|
|
float fRatio = bound_rects[i].width * 1.0f / bound_rects[i].height;
|
|
|
|
|
if (fRatio < 3.0 && fRatio > 1.0 && bound_rects[i].height < 120) {
|
|
|
|
@ -901,6 +982,8 @@ int CPlateLocate::plateSobelLocate(Mat src, vector<CPlate> &candPlates,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// second processing to split one
|
|
|
|
|
//使用 OpenMP 并行处理进行第二次搜索。pragma omp parallel for 使循环并行执行,
|
|
|
|
|
//以加快处理速度。在每次循环中,对于每个边界矩形:
|
|
|
|
|
#pragma omp parallel for
|
|
|
|
|
for (int i = 0; i < (int)bound_rects_part.size(); i++) {
|
|
|
|
|
Rect_<float> bound_rect = bound_rects_part[i];
|
|
|
|
@ -916,12 +999,13 @@ int CPlateLocate::plateSobelLocate(Mat src, vector<CPlate> &candPlates,
|
|
|
|
|
|
|
|
|
|
Rect_<float> safe_bound_rect(x, y, width, height);
|
|
|
|
|
Mat bound_mat = src(safe_bound_rect);
|
|
|
|
|
|
|
|
|
|
//调用 sobelSecSearchPart 函数,它可能进一步处理提取的子图并返回可能的车牌候选区域 rects_sobel
|
|
|
|
|
vector<RotatedRect> rects_sobel;
|
|
|
|
|
rects_sobel.reserve(128);
|
|
|
|
|
sobelSecSearchPart(bound_mat, refpoint, rects_sobel);
|
|
|
|
|
|
|
|
|
|
#pragma omp critical
|
|
|
|
|
//确保当多个线程尝试将其搜索结果添加到 rects_sobel_all 集合时,不会发生冲突。
|
|
|
|
|
{
|
|
|
|
|
rects_sobel_all.insert(rects_sobel_all.end(), rects_sobel.begin(), rects_sobel.end());
|
|
|
|
|
}
|
|
|
|
@ -954,13 +1038,15 @@ int CPlateLocate::plateSobelLocate(Mat src, vector<CPlate> &candPlates,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Mat src_b;
|
|
|
|
|
//调用 sobelOper 函数来执行 Sobel 操作。
|
|
|
|
|
sobelOper(src, src_b, 3, 10, 3);
|
|
|
|
|
//每个可能的矩形区域都发送给 deskew 函数,这个函数可能旨在纠正候选车牌的偏斜。
|
|
|
|
|
|
|
|
|
|
deskew(src, src_b, rects_sobel_all, plates);
|
|
|
|
|
|
|
|
|
|
//for (size_t i = 0; i < plates.size(); i++)
|
|
|
|
|
// candPlates.push_back(plates[i]);
|
|
|
|
|
|
|
|
|
|
//得到的车牌从 plates 转移至 candPlates
|
|
|
|
|
candPlates.insert(candPlates.end(), plates.begin(), plates.end());
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
@ -968,12 +1054,15 @@ int CPlateLocate::plateSobelLocate(Mat src, vector<CPlate> &candPlates,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int CPlateLocate::plateLocate(Mat src, vector<Mat> &resultVec, int index) {
|
|
|
|
|
//对输入图像src执行车牌定位,并将定位到的车牌图像放入resultVec中
|
|
|
|
|
vector<CPlate> all_result_Plates;
|
|
|
|
|
|
|
|
|
|
//三个函数分别使用颜色定位、Sobel边缘检测和MSER算法来识别车牌
|
|
|
|
|
plateColorLocate(src, all_result_Plates, index);
|
|
|
|
|
plateSobelLocate(src, all_result_Plates, index);
|
|
|
|
|
plateMserLocate(src, all_result_Plates, index);
|
|
|
|
|
|
|
|
|
|
//循环通过all_result_Plates,对于每个CPlate对象,
|
|
|
|
|
//调用getPlateMat()获取车牌对应的图像,并将其添加到resultVec向量中。
|
|
|
|
|
for (size_t i = 0; i < all_result_Plates.size(); i++) {
|
|
|
|
|
CPlate plate = all_result_Plates[i];
|
|
|
|
|
resultVec.push_back(plate.getPlateMat());
|
|
|
|
@ -983,12 +1072,14 @@ int CPlateLocate::plateLocate(Mat src, vector<Mat> &resultVec, int index) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int CPlateLocate::plateLocate(Mat src, vector<CPlate> &resultVec, int index) {
|
|
|
|
|
//对输入图像src执行车牌定位,将定位到的车牌对象(CPlate)放入resultVec中
|
|
|
|
|
vector<CPlate> all_result_Plates;
|
|
|
|
|
|
|
|
|
|
plateColorLocate(src, all_result_Plates, index);
|
|
|
|
|
plateSobelLocate(src, all_result_Plates, index);
|
|
|
|
|
plateMserLocate(src, all_result_Plates, index);
|
|
|
|
|
|
|
|
|
|
//循环通过all_result_Plates,将每一个CPlate对象直接添加到resultVec向量中。
|
|
|
|
|
for (size_t i = 0; i < all_result_Plates.size(); i++) {
|
|
|
|
|
resultVec.push_back(all_result_Plates[i]);
|
|
|
|
|
}
|
|
|
|
|