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