Compare commits

...

6 Commits

@ -7,16 +7,21 @@
#include <ctime> #include <ctime>
namespace easypr { namespace easypr {
Mat colorMatch(const Mat &src, Mat &match, const Color r,
//用于在HSV颜色空间中匹配特定的颜色。它可以用于识别图像中的蓝色、黄色和白色区域
Mat colorMatch(const Mat &src, Mat &match, const Color r, //函数接受四个参数一个源图像src一个输出图像match一个颜色枚举r和一个布尔值adaptive_minsv
const bool adaptive_minsv) { const bool adaptive_minsv) {
// if use adaptive_minsv // if use adaptive_minsv
// min value of s and v is adaptive to h // min value of s and v is adaptive to h
//定义了一些常量包括最大的饱和度和亮度值max_sv参考的最小饱和度和亮度值minref_sv和绝对的最小饱和度和亮度值minabs_sv
const float max_sv = 255; const float max_sv = 255;
const float minref_sv = 64; const float minref_sv = 64;
const float minabs_sv = 95; //95; const float minabs_sv = 95; //95;
//定义了蓝色、黄色和白色在HSV颜色空间中的H值范围
// H range of blue // H range of blue
const int min_blue = 100; // 100 const int min_blue = 100; // 100
@ -32,18 +37,21 @@ namespace easypr {
const int min_white = 0; // 15 const int min_white = 0; // 15
const int max_white = 30; // 40 const int max_white = 30; // 40
//将源图像从BGR颜色空间转换到HSV颜色空间
Mat src_hsv; Mat src_hsv;
// convert to HSV space // convert to HSV space
cvtColor(src, src_hsv, CV_BGR2HSV); cvtColor(src, src_hsv, CV_BGR2HSV);
//将HSV图像分割成三个通道对V通道进行直方图均衡化然后再合并回去
std::vector<cv::Mat> hsvSplit; std::vector<cv::Mat> hsvSplit;
split(src_hsv, hsvSplit); split(src_hsv, hsvSplit);
equalizeHist(hsvSplit[2], hsvSplit[2]); equalizeHist(hsvSplit[2], hsvSplit[2]);
merge(hsvSplit, src_hsv); merge(hsvSplit, src_hsv);
// match to find the color // match to find the color
//根据输入的颜色枚举r设置H值的范围
int min_h = 0; int min_h = 0;
int max_h = 0; int max_h = 0;
switch (r) { switch (r) {
@ -64,9 +72,11 @@ namespace easypr {
break; break;
} }
//计算H值的差值和平均值
float diff_h = float((max_h - min_h) / 2); float diff_h = float((max_h - min_h) / 2);
float avg_h = min_h + diff_h; float avg_h = min_h + diff_h;
//获取图像的通道数、行数和列数。如果图像是连续的那么将列数乘以行数行数设置为1
int channels = src_hsv.channels(); int channels = src_hsv.channels();
int nRows = src_hsv.rows; int nRows = src_hsv.rows;
@ -77,11 +87,15 @@ namespace easypr {
nRows = 1; nRows = 1;
} }
//对图像进行遍历对每个像素进行颜色匹配。如果像素的颜色匹配成功那么将其设置为红色255否则设置为黑色0
//定义变量用于后续的计算和循环
int i, j; int i, j;
uchar* p; uchar* p;
float s_all = 0; float s_all = 0;
float v_all = 0; float v_all = 0;
float count = 0; float count = 0;
//双重循环遍历图像的每个像素。对于每个像素我们获取其在HSV颜色空间中的H色相、S饱和度和V亮度
for (i = 0; i < nRows; ++i) { for (i = 0; i < nRows; ++i) {
p = src_hsv.ptr<uchar>(i); p = src_hsv.ptr<uchar>(i);
for (j = 0; j < nCols; j += 3) { for (j = 0; j < nCols; j += 3) {
@ -89,13 +103,19 @@ namespace easypr {
int S = int(p[j + 1]); // 0-255 int S = int(p[j + 1]); // 0-255
int V = int(p[j + 2]); // 0-255 int V = int(p[j + 2]); // 0-255
//此处计算了所有像素的S和V值的总和以及像素的数量
s_all += S; s_all += S;
v_all += V; v_all += V;
count++; count++;
//以下这段代码是颜色匹配的核心部分。
//首先检查像素的H值是否在预设的范围内。
//如果在范围内计算H值与平均H值的差值然后根据这个差值和预设的阈值来计算最小的S和V值。
//如果像素的S和V值都在这个范围内认为这个像素的颜色匹配成功。
bool colorMatched = false; bool colorMatched = false;
if (H > min_h && H < max_h) { if (H > min_h && H < max_h) { ////如果在范围内计算H值与平均H值的差值
float Hdiff = 0; float Hdiff = 0;
if (H > avg_h) if (H > avg_h)
Hdiff = H - avg_h; Hdiff = H - avg_h;
@ -105,7 +125,7 @@ namespace easypr {
float Hdiff_p = float(Hdiff) / diff_h; float Hdiff_p = float(Hdiff) / diff_h;
float min_sv = 0; float min_sv = 0;
if (true == adaptive_minsv) if (true == adaptive_minsv) //然后根据这个差值和预设的阈值来计算最小的S和V值
min_sv = min_sv =
minref_sv - minref_sv -
minref_sv / 2 * minref_sv / 2 *
@ -114,10 +134,11 @@ namespace easypr {
else else
min_sv = minabs_sv; // add min_sv = minabs_sv; // add
if ((S > min_sv && S < max_sv) && (V > min_sv && V < max_sv)) if ((S > min_sv && S < max_sv) && (V > min_sv && V < max_sv)) //如果像素的S和V值都在这个范围内认为这个像素的颜色匹配成功
colorMatched = true; colorMatched = true;
} }
//如果像素的颜色匹配成功将其设置为红色在HSV颜色空间中H=0S=0V=255表示红色。否则将其设置为黑色H=0S=0V=0
if (colorMatched == true) { if (colorMatched == true) {
p[j] = 0; p[j] = 0;
p[j + 1] = 0; p[j + 1] = 0;
@ -135,7 +156,8 @@ namespace easypr {
// cout << "avg_v:" << v_all / count << endl; // cout << "avg_v:" << v_all / count << endl;
// get the final binary // get the final binary
//将处理后的HSV图像分割成三个通道取出V通道作为灰度图像并返回这个灰度图像
//在这个灰度图像中,匹配的颜色区域被标记为白色,其余区域为黑色
Mat src_grey; Mat src_grey;
std::vector<cv::Mat> hsvSplit_done; std::vector<cv::Mat> hsvSplit_done;
split(src_hsv, hsvSplit_done); split(src_hsv, hsvSplit_done);
@ -143,13 +165,16 @@ namespace easypr {
match = src_grey; match = src_grey;
return src_grey; return src_grey; //函数返回一个灰度图像,其中匹配的颜色区域被标记为白色,其余区域为黑色
} }
//该函数用于在图像中找到左右边界。它通过计算图像中的白色像素数量来确定边界
//函数接受一个Mat类型的引用bound_threshold以及两个整型引用posLeft和posRight作为参数
bool bFindLeftRightBound1(Mat &bound_threshold, int &posLeft, int &posRight) { bool bFindLeftRightBound1(Mat &bound_threshold, int &posLeft, int &posRight) {
//首先计算图像行数的20%作为一个跨度span
float span = bound_threshold.rows * 0.2f; float span = bound_threshold.rows * 0.2f;
//然后从左到右遍历图像每次跳过3列计算每个跨度内的白色像素数量
for (int i = 0; i < bound_threshold.cols - span - 1; i += 3) { for (int i = 0; i < bound_threshold.cols - span - 1; i += 3) {
int whiteCount = 0; int whiteCount = 0;
for (int k = 0; k < bound_threshold.rows; k++) { for (int k = 0; k < bound_threshold.rows; k++) {
@ -159,14 +184,15 @@ namespace easypr {
} }
} }
} }
//如果白色像素的比例超过15%,则认为找到了左边界
if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.15) { if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.15) {
posLeft = i; posLeft = i;
break; break;
} }
} }
span = bound_threshold.rows * 0.2f; span = bound_threshold.rows * 0.2f; //再次计算跨度,用于寻找右边界
// 接着从右到左遍历图像每次跳过2列计算每个跨度内的白色像素数量
for (int i = bound_threshold.cols - 1; i > span; i -= 2) { for (int i = bound_threshold.cols - 1; i > span; i -= 2) {
int whiteCount = 0; int whiteCount = 0;
for (int k = 0; k < bound_threshold.rows; k++) { for (int k = 0; k < bound_threshold.rows; k++) {
@ -177,11 +203,12 @@ namespace easypr {
} }
} }
// 如果白色像素的比例超过6%,则认为找到了右边界
if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.06) { if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.06) {
posRight = i; posRight = i;
if (posRight + 5 < bound_threshold.cols) { if (posRight + 5 < bound_threshold.cols) { // 如果右边界加5后仍在图像列数范围内则右边界加5
posRight = posRight + 5; posRight = posRight + 5;
} else { } else { // 否则右边界设为图像列数减1
posRight = bound_threshold.cols - 1; posRight = bound_threshold.cols - 1;
} }
@ -189,17 +216,22 @@ namespace easypr {
} }
} }
// 最后,如果左边界小于右边界,返回真,否则返回假
if (posLeft < posRight) { if (posLeft < posRight) {
return true; return true;
} }
return false; return false;
} }
bool bFindLeftRightBound(Mat &bound_threshold, int &posLeft, int &posRight) {
//该函数用于在图像中找到左右边界。它通过计算图像中的白色像素数量来确定边界
//函数接受三个参数一个Mat类型的引用bound_threshold以及两个整型引用posLeft和posRight
//函数返回一个布尔值,表示是否成功找到左右边界
bool bFindLeftRightBound(Mat &bound_threshold, int &posLeft, int &posRight) {
//首先计算图像行数的20%作为一个跨度span
float span = bound_threshold.rows * 0.2f; float span = bound_threshold.rows * 0.2f;
//然后从左到右遍历图像每次跳过2列计算每个跨度内的白色像素数量。如果白色像素的比例超过36%,则认为找到了左边界
for (int i = 0; i < bound_threshold.cols - span - 1; i += 2) { for (int i = 0; i < bound_threshold.cols - span - 1; i += 2) {
int whiteCount = 0; int whiteCount = 0;
for (int k = 0; k < bound_threshold.rows; k++) { for (int k = 0; k < bound_threshold.rows; k++) {
@ -214,9 +246,9 @@ namespace easypr {
break; break;
} }
} }
span = bound_threshold.rows * 0.2f; span = bound_threshold.rows * 0.2f; // 再次计算跨度,用于寻找右边界
//接着从右到左遍历图像每次跳过2列计算每个跨度内的白色像素数量。如果白色像素的比例超过26%,则认为找到了右边界
for (int i = bound_threshold.cols - 1; i > span; i -= 2) { for (int i = bound_threshold.cols - 1; i > span; i -= 2) {
int whiteCount = 0; int whiteCount = 0;
for (int k = 0; k < bound_threshold.rows; k++) { for (int k = 0; k < bound_threshold.rows; k++) {
@ -232,18 +264,22 @@ namespace easypr {
break; break;
} }
} }
//最后,如果左边界小于右边界,返回真,否则返回假
if (posLeft < posRight) { if (posLeft < posRight) {
return true; return true;
} }
return false; return false;
} }
bool bFindLeftRightBound2(Mat &bound_threshold, int &posLeft, int &posRight) {
// 该函数接受一个Mat类型的引用bound_threshold和两个整型引用posLeft和posRight作为参数
// 函数返回一个布尔值,表示是否成功找到左右边界
bool bFindLeftRightBound2(Mat &bound_threshold, int &posLeft, int &posRight) {
//计算图像行数的20%作为一个跨度span
float span = bound_threshold.rows * 0.2f; float span = bound_threshold.rows * 0.2f;
for (int i = 0; i < bound_threshold.cols - span - 1; i += 3) { for (int i = 0; i < bound_threshold.cols - span - 1; i += 3) { //从左到右遍历图像每次跳过3列
// 在每个跨度内,计算白色像素的数量
int whiteCount = 0; int whiteCount = 0;
for (int k = 0; k < bound_threshold.rows; k++) { for (int k = 0; k < bound_threshold.rows; k++) {
for (int l = i; l < i + span; l++) { for (int l = i; l < i + span; l++) {
@ -252,15 +288,17 @@ namespace easypr {
} }
} }
} }
// 如果白色像素的比例超过32%,则认为找到了左边界
if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.32) { if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.32) {
posLeft = i; posLeft = i;
break; break;
} }
} }
//再次计算跨度,用于寻找右边界
span = bound_threshold.rows * 0.2f; span = bound_threshold.rows * 0.2f;
for (int i = bound_threshold.cols - 1; i > span; i -= 3) { // 从右到左遍历图像每次跳过3列
for (int i = bound_threshold.cols - 1; i > span; i -= 3) { // 在每个跨度内,计算白色像素的数量
int whiteCount = 0; int whiteCount = 0;
for (int k = 0; k < bound_threshold.rows; k++) { for (int k = 0; k < bound_threshold.rows; k++) {
for (int l = i; l > i - span; l--) { for (int l = i; l > i - span; l--) {
@ -269,46 +307,54 @@ namespace easypr {
} }
} }
} }
// 如果白色像素的比例超过22%,则认为找到了右边界
if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.22) { if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.22) {
posRight = i; posRight = i;
break; break;
} }
} }
// 如果左边界小于右边界,返回真,否则返回假
if (posLeft < posRight) { if (posLeft < posRight) {
return true; return true;
} }
return false; return false;
} }
// 该函数接受一个Mat类型的引用src一个颜色枚举r一个布尔值adaptive_minsv和一个浮点数引用percent作为参数
// 函数返回一个布尔值,表示颜色是否匹配
bool plateColorJudge(const Mat &src, const Color r, const bool adaptive_minsv, bool plateColorJudge(const Mat &src, const Color r, const bool adaptive_minsv,
float &percent) { float &percent) {
// 定义一个常量thresh表示阈值
const float thresh = 0.45f; const float thresh = 0.45f;
// 调用colorMatch函数将源图像转换为灰度图像
Mat src_gray; Mat src_gray;
colorMatch(src, src_gray, r, adaptive_minsv); colorMatch(src, src_gray, r, adaptive_minsv);
// 计算灰度图像中非零像素的比例
percent = percent =
float(countNonZero(src_gray)) / float(src_gray.rows * src_gray.cols); float(countNonZero(src_gray)) / float(src_gray.rows * src_gray.cols);
// cout << "percent:" << percent << endl; // cout << "percent:" << percent << endl;
// 如果非零像素的比例大于阈值,返回真,否则返回假
if (percent > thresh) if (percent > thresh)
return true; return true;
else else
return false; return false;
} }
// 用于判断图像中的颜色类型(蓝色、黄色或白色)
// 接受两个参数一个Mat类型的引用src源图像和一个布尔值adaptive_minsv
// 函数返回一个布尔值,表示颜色是否匹配。
Color getPlateType(const Mat &src, const bool adaptive_minsv) { Color getPlateType(const Mat &src, const bool adaptive_minsv) {
// 定义两个变量max_percent用于存储最大的颜色匹配百分比max_color用于存储对应的颜色。初始值分别设为0和UNKNOW
float max_percent = 0; float max_percent = 0;
Color max_color = UNKNOWN; Color max_color = UNKNOWN;
// 定义三个变量用于存储蓝色、黄色和白色的匹配百分比。初始值都设为0
float blue_percent = 0; float blue_percent = 0;
float yellow_percent = 0; float yellow_percent = 0;
float white_percent = 0; float white_percent = 0;
// 调用plateColorJudge函数判断源图像中的颜色是否为蓝色、黄色或白色。如果是返回对应的颜色
if (plateColorJudge(src, BLUE, adaptive_minsv, blue_percent) == true) { if (plateColorJudge(src, BLUE, adaptive_minsv, blue_percent) == true) {
// cout << "BLUE" << endl; // cout << "BLUE" << endl;
return BLUE; return BLUE;
@ -320,7 +366,7 @@ namespace easypr {
true) { true) {
// cout << "WHITE" << endl; // cout << "WHITE" << endl;
return WHITE; return WHITE;
} else { } else { // 如果源图像中的颜色既不是蓝色、也不是黄色、也不是白色,那么默认返回蓝色
//std::cout << "OTHER" << std::endl; //std::cout << "OTHER" << std::endl;
/*max_percent = blue_percent > yellow_percent ? blue_percent : yellow_percent; /*max_percent = blue_percent > yellow_percent ? blue_percent : yellow_percent;
@ -332,37 +378,45 @@ namespace easypr {
} }
} }
// 该函数用于清除图像中的噪声,提高图像处理的准确性
// 接受一个Mat类型的引用img作为参数
// 该函数主要通过计算图像中每一行的跳变次数即像素值从0变为255或从255变为0的次数来判断是否为噪声如果跳变次数小于某个阈值那么就将这一行的所有像素值设为0即黑色
void clearLiuDingOnly(Mat &img) { void clearLiuDingOnly(Mat &img) {
const int x = 7; const int x = 7; // 设置阈值为7
Mat jump = Mat::zeros(1, img.rows, CV_32F); Mat jump = Mat::zeros(1, img.rows, CV_32F); // 创建一个大小为图像行数类型为浮点数的Mat对象用于存储每一行的跳变次数
for (int i = 0; i < img.rows; i++) { for (int i = 0; i < img.rows; i++) { // 遍历图像的每一行
int jumpCount = 0; int jumpCount = 0; // 初始化跳变次数为0
int whiteCount = 0; int whiteCount = 0; // 初始化白色像素的数量为0
for (int j = 0; j < img.cols - 1; j++) { 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<char>(i, j) != img.at<char>(i, j + 1)) jumpCount++; // 如果当前像素与下一个像素的值不同跳变次数加1
if (img.at<uchar>(i, j) == 255) { if (img.at<uchar>(i, j) == 255) { // 如果当前像素的值为255即白色白色像素的数量加1
whiteCount++; whiteCount++;
} }
} }
// 将当前行的跳变次数存储到jump中
jump.at<float>(i) = (float) jumpCount; jump.at<float>(i) = (float) jumpCount;
} }
for (int i = 0; i < img.rows; i++) { for (int i = 0; i < img.rows; i++) { // 再次遍历图像的每一行
if (jump.at<float>(i) <= x) { if (jump.at<float>(i) <= x) { // 如果当前行的跳变次数小于阈值
for (int j = 0; j < img.cols; j++) { for (int j = 0; j < img.cols; j++) { // 遍历当前行的每一个像素
img.at<char>(i, j) = 0; img.at<char>(i, j) = 0; // 将像素值设为0即黑色
} }
} }
} }
} }
//该函数用于清除图像中的噪声
//接受一个Mat类型的引用img作为参数返回一个布尔值表示是否成功清除噪声
bool clearLiuDing(Mat &img) { bool clearLiuDing(Mat &img) {
// 初始化一个浮点数向量fJump一个整数whiteCount用于计数白色像素一个常量x设为7以及一个全零的Mat对象jump大小为1行列数为图像的行数
std::vector<float> fJump; std::vector<float> fJump;
int whiteCount = 0; int whiteCount = 0;
const int x = 7; const int x = 7;
Mat jump = Mat::zeros(1, img.rows, CV_32F); Mat jump = Mat::zeros(1, img.rows, CV_32F);
// 遍历图像的每一行计算每一行的跳变次数即像素值从0变为255或从255变为0的次数和白色像素的数量将跳变次数存储到jump中
for (int i = 0; i < img.rows; i++) { for (int i = 0; i < img.rows; i++) {
int jumpCount = 0; int jumpCount = 0;
@ -377,6 +431,7 @@ namespace easypr {
jump.at<float>(i) = (float) jumpCount; jump.at<float>(i) = (float) jumpCount;
} }
// 遍历jump将其值添加到fJump中如果jump的值在16到45之间iCount加1
int iCount = 0; int iCount = 0;
for (int i = 0; i < img.rows; i++) { for (int i = 0; i < img.rows; i++) {
fJump.push_back(jump.at<float>(i)); fJump.push_back(jump.at<float>(i));
@ -388,6 +443,7 @@ namespace easypr {
} }
// if not is not plate // if not is not plate
// 如果iCount占图像行数的比例小于或等于40%或者白色像素占图像总像素的比例小于15%或大于50%,则返回假,表示未能成功清除噪声
if (iCount * 1.0 / img.rows <= 0.40) { if (iCount * 1.0 / img.rows <= 0.40) {
return false; return false;
} }
@ -397,6 +453,7 @@ namespace easypr {
return false; return false;
} }
// 遍历图像的每一行如果该行的跳变次数小于或等于x则将该行的所有像素值设为0即黑色。最后返回真表示成功清除噪声
for (int i = 0; i < img.rows; i++) { for (int i = 0; i < img.rows; i++) {
if (jump.at<float>(i) <= x) { if (jump.at<float>(i) <= x) {
for (int j = 0; j < img.cols; j++) { for (int j = 0; j < img.cols; j++) {
@ -408,13 +465,21 @@ namespace easypr {
} }
// 该函数用于清除图像的边界
// 接受一个Mat类型的图像img和一个Rect类型的裁剪矩形cropRect作为参数
void clearBorder(const Mat &img, Rect& cropRect) { void clearBorder(const Mat &img, Rect& cropRect) {
// 获取图像的行数和列数
int r = img.rows; int r = img.rows;
int c = img.cols; int c = img.cols;
// 创建一个全零的单通道矩阵boder用于存储每一行是否为边界
Mat boder = Mat::zeros(1, r, CV_8UC1); Mat boder = Mat::zeros(1, r, CV_8UC1);
// 设置一个阈值,如果一行中像素值没有变化的次数超过这个阈值,那么认为这一行是边界
const int noJunpCount_thresh = int(0.15f * c); const int noJunpCount_thresh = int(0.15f * c);
// if nojumpcount > // if nojumpcount >
// 遍历图像的每一行计算每一行中像素值没有变化的次数如果这个次数超过阈值那么认为这一行是边界将boder对应的位置设为1
for (int i = 0; i < r; i++) { for (int i = 0; i < r; i++) {
int nojumpCount = 0; int nojumpCount = 0;
int isBorder = 0; int isBorder = 0;
@ -429,29 +494,31 @@ void clearBorder(const Mat &img, Rect& cropRect) {
} }
boder.at<char>(i) = (char) isBorder; boder.at<char>(i) = (char) isBorder;
} }
// 设置上下边界的搜索范围只在图像的中间80%的区域内搜索边界
const int mintop = int(0.1f * r); const int mintop = int(0.1f * r);
const int maxtop = int(0.9f * r); const int maxtop = int(0.9f * r);
// 初始化裁剪矩形的上下边界
int minMatTop = 0; int minMatTop = 0;
int maxMatTop = r - 1; int maxMatTop = r - 1;
// 从上到下搜索上边界
for (int i = 0; i < mintop; i++) { for (int i = 0; i < mintop; i++) {
if (boder.at<char>(i) == 1) { if (boder.at<char>(i) == 1) {
minMatTop = i; minMatTop = i;
} }
} }
// 从下到上搜索下边界
for (int i = r - 1; i > maxtop; i--) { for (int i = r - 1; i > maxtop; i--) {
if (boder.at<char>(i) == 1) { if (boder.at<char>(i) == 1) {
maxMatTop = i; maxMatTop = i;
} }
} }
// 根据找到的上下边界创建裁剪矩形
cropRect = Rect(0, minMatTop, c, maxMatTop - minMatTop + 1); cropRect = Rect(0, minMatTop, c, maxMatTop - minMatTop + 1);
} }
//用于清除图像中的噪声
void clearLiuDing(Mat mask, int &top, int &bottom) { void clearLiuDing(Mat mask, int &top, int &bottom) {
const int x = 7; const int x = 7;

@ -6,43 +6,47 @@
#include "easypr/core/params.h" #include "easypr/core/params.h"
namespace easypr { //这部分代码实现了单例模式确保PlateJudge类只有一个实例 namespace easypr { //这部分代码实现了单例模式确保PlateJudge类只有一个实例
//定义了一个静态成员变量instance_它是PlateJudge类的指针初始化为nullptr。这是单例模式的关键所有的PlateJudge实例都将共享这个变量
PlateJudge* PlateJudge::instance_ = nullptr; PlateJudge* PlateJudge::instance_ = nullptr;
PlateJudge* PlateJudge::instance() {
if (!instance_) { //确保PlateJudge类只有一个实例当需要使用PlateJudge类时只需要调用PlateJudge::instance()即可获取到这个唯一的实例
instance_ = new PlateJudge; PlateJudge* PlateJudge::instance() { //定义了一个静态成员函数instance()它返回一个指向PlateJudge实例的指针
if (!instance_) { //检查instance_是否为nullptr
instance_ = new PlateJudge; //如果是那么就创建一个新的PlateJudge实例并将instance_设置为指向这个新创建的实例
} }
return instance_; return instance_; //返回instance_即指向PlateJudge实例的指针
} }
PlateJudge::PlateJudge() { //PlateJudge决定了使用哪种特征提取方法 PlateJudge::PlateJudge() { //PlateJudge决定了使用哪种特征提取方法
bool useLBP = false; bool useLBP = false; //定义一个布尔变量useLBP并初始化为false
if (useLBP) { if (useLBP) { //如果useLBP为true即使用LBP特征提取方法
LOAD_SVM_MODEL(svm_, kLBPSvmPath); LOAD_SVM_MODEL(svm_, kLBPSvmPath); //加载LBP的SVM模型
extractFeature = getLBPFeatures; extractFeature = getLBPFeatures; //设置特征提取函数为getLBPFeatures
} }
else { else { //如果useLBP为false即使用直方图特征提取方法
LOAD_SVM_MODEL(svm_, kHistSvmPath); LOAD_SVM_MODEL(svm_, kHistSvmPath); //加载直方图的SVM模型
extractFeature = getHistomPlusColoFeatures; extractFeature = getHistomPlusColoFeatures; //设置特征提取函数为getHistomPlusColoFeatures
} }
} }
void PlateJudge::LoadModel(std::string path) { //LoadModel函数用于加载SVM模型 void PlateJudge::LoadModel(std::string path) { //LoadModel函数用于加载SVM模型函数接收一个字符串参数path这个参数是SVM模型文件的路径
if (path != std::string(kDefaultSvmPath)) { if (path != std::string(kDefaultSvmPath)) { //检查输入的路径path是否与默认的SVM路径kDefaultSvmPath不同。如果不同那么就需要加载新的SVM模型
if (!svm_->empty()) if (!svm_->empty()) //检查当前的SVM模型是否为空。如果不为空那么在加载新的SVM模型之前需要先清空当前的SVM模型
svm_->clear(); svm_->clear(); //清空当前的SVM模型
LOAD_SVM_MODEL(svm_, path); LOAD_SVM_MODEL(svm_, path); //加载新的SVM模型。LOAD_SVM_MODEL是一个宏它接收两个参数一个是SVM模型的指针另一个是SVM模型文件的路径
} }
} }
// set the score of plate // set the score of plate
// 0 is plate, -1 is not. // 0 is plate, -1 is not.
int PlateJudge::plateSetScore(CPlate& plate) { //plateSetScore函数用于设置车牌的评分 int PlateJudge::plateSetScore(CPlate& plate) { //plateSetScore函数用于设置车牌的评分接收一个CPlate类型的引用作为参数
Mat features; Mat features; //定义一个Mat类型的变量features用于存储特征
extractFeature(plate.getPlateMat(), features); extractFeature(plate.getPlateMat(), features); //调用extractFeature方法提取车牌图像的特征结果存储在features中
float score = svm_->predict(features, noArray(), cv::ml::StatModel::Flags::RAW_OUTPUT); float score = svm_->predict(features, noArray(), cv::ml::StatModel::Flags::RAW_OUTPUT); //使用SVM模型对特征进行预测得到的结果存储在score中
//std::cout << "score:" << score << std::endl; //std::cout << "score:" << score << std::endl;
//这是一个调试用的代码块如果条件为真此处为0所以不会执行则显示车牌图像等待用户按键然后销毁窗口
if (0) { if (0) {
imshow("plate", plate.getPlateMat()); imshow("plate", plate.getPlateMat());
waitKey(0); waitKey(0);
@ -51,14 +55,15 @@ namespace easypr { //这部分代码实现了单例模式确保PlateJudge类
// score is the distance of marginbelow zero is plate, up is not // score is the distance of marginbelow zero is plate, up is not
// when score is below zero, the samll the value, the more possibliy to be a plate. // when score is below zero, the samll the value, the more possibliy to be a plate.
plate.setPlateScore(score); plate.setPlateScore(score);
if (score < 0.5) return 0; if (score < 0.5) return 0; //如果评分小于0.5返回0表示这是一个车牌否则返回-1表示这不是一个车牌
else return -1; else return -1;
} }
//定义了一个名为plateJudge的成员函数它属于PlateJudge类。该函数接收一个Mat类型的常量引用参数plateMat这个参数是需要进行车牌判断的图像
int PlateJudge::plateJudge(const Mat& plateMat) { //plateJudge函数用于判断输入的图像是否为车牌。 int PlateJudge::plateJudge(const Mat& plateMat) { //plateJudge函数用于判断输入的图像是否为车牌。
CPlate plate; CPlate plate; //这行创建了一个CPlate类型的对象plate。CPlate是一个类它可能包含车牌的相关信息如车牌图像、车牌位置等
plate.setPlateMat(plateMat); plate.setPlateMat(plateMat); //调用了CPlate类的setPlateMat成员函数将输入的图像plateMat设置为plate对象的车牌图像
return plateSetScore(plate); return plateSetScore(plate); //调用了PlateJudge类的plateSetScore成员函数对plate对象进行评分然后返回评分结果
} }
int PlateJudge::plateJudge(const std::vector<Mat> &inVec, //inVec是输入的图像向量resultVec是输出的结果向量。 int PlateJudge::plateJudge(const std::vector<Mat> &inVec, //inVec是输入的图像向量resultVec是输出的结果向量。
@ -107,18 +112,25 @@ namespace easypr { //这部分代码实现了单例模式确保PlateJudge类
return 0; //结束循环并返回0表示方法执行成功 return 0; //结束循环并返回0表示方法执行成功
} }
// non-maximum suppression // non-maximum suppression -->非极大值抑制
//函数接收三个参数一个CPlate对象的向量inVec输入的车牌向量一个CPlate对象的向量resultVec输出的车牌向量以及一个double类型的overlap重叠阈值
void NMS(std::vector<CPlate> &inVec, std::vector<CPlate> &resultVec, double overlap) { //NMS函数实现了非极大值抑制用于消除重叠的车牌。 void NMS(std::vector<CPlate> &inVec, std::vector<CPlate> &resultVec, double overlap) { //NMS函数实现了非极大值抑制用于消除重叠的车牌。
std::sort(inVec.begin(), inVec.end()); std::sort(inVec.begin(), inVec.end()); //首先对输入的车牌向量进行排序
//然后遍历输入的车牌向量,对每一个车牌对象,获取其位置的边界矩形
std::vector<CPlate>::iterator it = inVec.begin(); std::vector<CPlate>::iterator it = inVec.begin();
for (; it != inVec.end(); ++it) { for (; it != inVec.end(); ++it) {
CPlate plateSrc = *it; CPlate plateSrc = *it;
//std::cout << "plateScore:" << plateSrc.getPlateScore() << std::endl; //std::cout << "plateScore:" << plateSrc.getPlateScore() << std::endl;
Rect rectSrc = plateSrc.getPlatePos().boundingRect(); Rect rectSrc = plateSrc.getPlatePos().boundingRect();
//在内层循环中,对当前车牌对象之后的每一个车牌对象,也获取其位置的边界矩形
std::vector<CPlate>::iterator itc = it + 1; std::vector<CPlate>::iterator itc = it + 1;
for (; itc != inVec.end();) { for (; itc != inVec.end();) {
CPlate plateComp = *itc; CPlate plateComp = *itc;
Rect rectComp = plateComp.getPlatePos().boundingRect(); Rect rectComp = plateComp.getPlatePos().boundingRect();
//计算两个边界矩形的交并比Intersection over UnionIoU如果IoU大于设定的重叠阈值那么就从输入的车牌向量中删除当前的车牌对象否则继续处理下一个车牌对象
float iou = computeIOU(rectSrc, rectComp); float iou = computeIOU(rectSrc, rectComp);
if (iou > overlap) { if (iou > overlap) {
itc = inVec.erase(itc); itc = inVec.erase(itc);
@ -128,19 +140,23 @@ namespace easypr { //这部分代码实现了单例模式确保PlateJudge类
} }
} }
} }
resultVec = inVec; resultVec = inVec; //最后,将处理后的车牌向量赋值给输出的车牌向量
} }
// judge plate using nms // judge plate using nms --> 使用非极大值抑制进行车牌识别
// 定义了一个名为plateJudgeUsingNMS的成员函数它属于PlateJudge类。该函数使用非极大值抑制进行车牌识别接收一个CPlate对象的向量inVec输入的车牌向量、一个CPlate对象的向量resultVec输出的车牌向量和一个整数maxPlates最大车牌数量作为参数
int PlateJudge::plateJudgeUsingNMS(const std::vector<CPlate> &inVec, std::vector<CPlate> &resultVec, int maxPlates) { //plateJudgeUsingNMS函数使用非极大值抑制进行车牌识别。 int PlateJudge::plateJudgeUsingNMS(const std::vector<CPlate> &inVec, std::vector<CPlate> &resultVec, int maxPlates) { //plateJudgeUsingNMS函数使用非极大值抑制进行车牌识别。
std::vector<CPlate> plateVec; std::vector<CPlate> plateVec; // 创建一个CPlate对象的向量用于存储识别出的车牌
int num = inVec.size(); int num = inVec.size(); // 获取输入向量的大小
bool useCascadeJudge = true; bool useCascadeJudge = true; // 定义一个布尔变量,表示是否使用级联判断
// 遍历输入向量中的每一个元素
for (int j = 0; j < num; j++) { for (int j = 0; j < num; j++) {
CPlate plate = inVec[j]; CPlate plate = inVec[j]; // 获取当前的CPlate对象
Mat inMat = plate.getPlateMat(); Mat inMat = plate.getPlateMat(); // 获取当前CPlate对象的车牌图像
int result = plateSetScore(plate); int result = plateSetScore(plate); // 对当前的CPlate对象进行评分
// 如果评分结果为0表示这是一个车牌
if (0 == result) { if (0 == result) {
if (0) { if (0) {
imshow("inMat", inMat); imshow("inMat", inMat);
@ -148,17 +164,22 @@ namespace easypr { //这部分代码实现了单例模式确保PlateJudge类
destroyWindow("inMat"); destroyWindow("inMat");
} }
if (plate.getPlateLocateType() == CMSER) { if (plate.getPlateLocateType() == CMSER) { // 如果plate的定位类型为CMSER
int w = inMat.cols; int w = inMat.cols; // 获取图像的宽度
int h = inMat.rows; int h = inMat.rows; // 获取图像的高度
// 对图像进行裁剪
Mat tmpmat = inMat(Rect_<double>(w * 0.05, h * 0.1, w * 0.9, h * 0.8)); Mat tmpmat = inMat(Rect_<double>(w * 0.05, h * 0.1, w * 0.9, h * 0.8));
Mat tmpDes = inMat.clone(); Mat tmpDes = inMat.clone(); // 克隆图像
resize(tmpmat, tmpDes, Size(inMat.size())); resize(tmpmat, tmpDes, Size(inMat.size())); // 调整图像大小
plate.setPlateMat(tmpDes); plate.setPlateMat(tmpDes); // 设置plate的车牌图像为调整后的图像
// 如果使用级联判断
if (useCascadeJudge) { if (useCascadeJudge) {
int resultCascade = plateSetScore(plate); int resultCascade = plateSetScore(plate); // 对调整后的图像进行评分
// 如果plate的定位类型不是CMSER将plate的车牌图像设置为原图像
if (plate.getPlateLocateType() != CMSER) if (plate.getPlateLocateType() != CMSER)
plate.setPlateMat(inMat); plate.setPlateMat(inMat);
// 如果级联评分结果为0将plate添加到plateVec中
if (resultCascade == 0) { if (resultCascade == 0) {
if (0) { if (0) {
imshow("tmpDes", tmpDes); imshow("tmpDes", tmpDes);
@ -168,22 +189,24 @@ namespace easypr { //这部分代码实现了单例模式确保PlateJudge类
plateVec.push_back(plate); plateVec.push_back(plate);
} }
} }
else else // 如果不使用级联判断直接将plate添加到plateVec中
plateVec.push_back(plate); plateVec.push_back(plate);
} }
else else // 如果plate的定位类型不是CMSER直接将plate添加到plateVec中
plateVec.push_back(plate); plateVec.push_back(plate);
} }
} }
std::vector<CPlate> reDupPlateVec; std::vector<CPlate> reDupPlateVec; // 创建一个CPlate对象的向量用于存储非极大值抑制后的结果
double overlap = 0.5; double overlap = 0.5; // 定义重叠阈值
// double overlap = CParams::instance()->getParam1f(); // double overlap = CParams::instance()->getParam1f();
// use NMS to get the result plates // use NMS to get the result plates
// 使用非极大值抑制处理plateVec结果存储在reDupPlateVec中
NMS(plateVec, reDupPlateVec, overlap); NMS(plateVec, reDupPlateVec, overlap);
// sort the plates due to their scores // sort the plates due to their scores --> 根据评分对reDupPlateVec进行排序
std::sort(reDupPlateVec.begin(), reDupPlateVec.end()); std::sort(reDupPlateVec.begin(), reDupPlateVec.end());
// output the plate judge plates // output the plate judge plates
// 遍历reDupPlateVec将结果添加到resultVec中直到达到最大车牌数量
std::vector<CPlate>::iterator it = reDupPlateVec.begin(); std::vector<CPlate>::iterator it = reDupPlateVec.begin();
int count = 0; int count = 0;
for (; it != reDupPlateVec.end(); ++it) { for (; it != reDupPlateVec.end(); ++it) {
@ -197,6 +220,6 @@ namespace easypr { //这部分代码实现了单例模式确保PlateJudge类
if (count >= maxPlates) if (count >= maxPlates)
break; break;
} }
return 0; return 0; // 返回0表示函数执行成功
} }
} }

Loading…
Cancel
Save