From d25d52f4696e0cf9de8e1da889b277ee5105b24d Mon Sep 17 00:00:00 2001 From: www-369 <2776296049@qq.com> Date: Tue, 26 Dec 2023 01:13:15 +0800 Subject: [PATCH 1/2] WXP2.0 --- src/src/core/core_func.cpp | 388 ++++++++++++++++++++----------------- 1 file changed, 205 insertions(+), 183 deletions(-) diff --git a/src/src/core/core_func.cpp b/src/src/core/core_func.cpp index 58fb4f5..a2d2895 100644 --- a/src/src/core/core_func.cpp +++ b/src/src/core/core_func.cpp @@ -467,61 +467,62 @@ 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; - for (int j = 0; j < c - 1; j++) { - if (img.at(i, j) == img.at(i, j + 1)) - nojumpCount++; - if (nojumpCount > noJunpCount_thresh) { - nojumpCount = 0; - isBorder = 1; - break; + 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; + for (int j = 0; j < c - 1; j++) { + if (img.at(i, j) == img.at(i, j + 1)) + nojumpCount++; + if (nojumpCount > noJunpCount_thresh) { + nojumpCount = 0; + isBorder = 1; + break; + } } + boder.at(i) = (char) isBorder; } - 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; + // 设置上下边界的搜索范围,只在图像的中间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; + // 从下到上搜索下边界 + for (int i = r - 1; i > maxtop; i--) { + if (boder.at(i) == 1) { + maxMatTop = i; + } } - } -// 根据找到的上下边界创建裁剪矩形 - cropRect = Rect(0, minMatTop, c, maxMatTop - minMatTop + 1); + // 根据找到的上下边界创建裁剪矩形 + cropRect = Rect(0, minMatTop, c, maxMatTop - minMatTop + 1); -} + } -//用于清除图像中的噪声 +//用于清除图像中的噪声—————函数的目的是找到图像中的有效区域,即去除上下边缘的噪声 +//函数接受一个Mat类型的图像mask和两个整数引用top和bottom作为参数 void clearLiuDing(Mat mask, int &top, int &bottom) { - const int x = 7; - + const int x = 7; //定义一个常量x,值为7 +//遍历图像的上半部分,计算每一行的跳变次数(即像素值从0变为255,或从255变为0的次数)和白色像素的数量 for (int i = 0; i < mask.rows / 2; i++) { int whiteCount = 0; int jumpCount = 0; @@ -532,18 +533,20 @@ void clearBorder(const Mat &img, Rect& cropRect) { whiteCount++; } } + //如果跳变次数小于x且白色像素占该行的比例大于15%,或者白色像素的数量小于4,那么将top设为当前行数 if ((jumpCount < x && whiteCount * 1.0 / mask.cols > 0.15) || whiteCount < 4) { top = i; } } + //将top减1,如果top小于0,那么将top设为0 top -= 1; if (top < 0) { top = 0; } // ok, find top and bottom boudnadry - +//遍历图像的下半部分,计算每一行的跳变次数和白色像素的数量 for (int i = mask.rows - 1; i >= mask.rows / 2; i--) { int jumpCount = 0; int whiteCount = 0; @@ -553,92 +556,101 @@ void clearBorder(const Mat &img, Rect& cropRect) { whiteCount++; } } + //如果跳变次数小于x且白色像素占该行的比例大于15%,或者白色像素的数量小于4,那么将bottom设为当前行数 if ((jumpCount < x && whiteCount * 1.0 / mask.cols > 0.15) || whiteCount < 4) { bottom = i; } } + //将bottom加1,如果bottom大于等于图像的行数,那么将bottom设为图像的行数减1 bottom += 1; if (bottom >= mask.rows) { bottom = mask.rows - 1; } - +//如果top大于等于bottom,那么将top设为0,bottom设为图像的行数减1 if (top >= bottom) { top = 0; bottom = mask.rows - 1; } } - int ThresholdOtsu(Mat mat) { - int height = mat.rows; - int width = mat.cols; + int ThresholdOtsu(Mat mat) { // 定义函数ThresholdOtsu,参数为OpenCV的Mat类型,返回值为int类型 + int height = mat.rows; // 获取图像的行数 + int width = mat.cols; // 获取图像的列数 // histogram - float histogram[256] = {0}; - for (int i = 0; i < height; i++) { - for (int j = 0; j < width; j++) { - unsigned char p = (unsigned char) ((mat.data[i * mat.step[0] + j])); - histogram[p]++; + float histogram[256] = {0}; // 初始化一个长度为256的浮点数数组,用于存储图像的灰度直方图 + for (int i = 0; i < height; i++) { // 遍历图像的每一行 + for (int j = 0; j < width; j++) { // 遍历图像的每一列 + unsigned char p = (unsigned char) ((mat.data[i * mat.step[0] + j])); // 获取当前像素的灰度值 + histogram[p]++; // 对应的灰度直方图的值加1 } } // normalize histogram - int size = height * width; - for (int i = 0; i < 256; i++) { - histogram[i] = histogram[i] / size; + int size = height * width; // 计算图像的总像素数 + for (int i = 0; i < 256; i++) { // 遍历灰度直方图 + histogram[i] = histogram[i] / size; // 将灰度直方图的每一个值除以总像素数,得到每个灰度值的概率 } // average pixel value - float avgValue = 0; - for (int i = 0; i < 256; i++) { - avgValue += i * histogram[i]; + float avgValue = 0; // 初始化平均灰度值为0 + for (int i = 0; i < 256; i++) { // 遍历灰度直方图 + avgValue += i * histogram[i]; // 计算平均灰度值 } - int thresholdV; - float maxVariance = 0; - float w = 0, u = 0; - for (int i = 0; i < 256; i++) { - w += histogram[i]; - u += i * histogram[i]; + int thresholdV; // 初始化阈值 + float maxVariance = 0; // 初始化最大方差为0 + float w = 0, u = 0; // 初始化两个变量w和u,分别用于存储阈值以下和阈值以上的像素数的累积和 + for (int i = 0; i < 256; i++) { // 遍历灰度直方图 + w += histogram[i]; // 计算阈值以下的像素数的累积和 + u += i * histogram[i]; // 计算阈值以上的像素数的累积和 - float t = avgValue * w - u; - float variance = t * t / (w * (1 - w)); - if (variance > maxVariance) { - maxVariance = variance; - thresholdV = i; + float t = avgValue * w - u; // 计算类间方差 + float variance = t * t / (w * (1 - w)); // 计算类间方差 + if (variance > maxVariance) { // 如果当前的类间方差大于最大方差 + maxVariance = variance; // 更新最大方差 + thresholdV = i; // 更新阈值 } } - return thresholdV; + return thresholdV; // 返回阈值 } - +//该函数接受一个Mat类型的输入图像,并返回一个经过直方图均衡化处理的图像 Mat histeq(Mat in) { - Mat out(in.size(), in.type()); - if (in.channels() == 3) { + Mat out(in.size(), in.type()); // 创建一个与输入图像同样大小和类型的Mat对象out,用于存储处理后的图像 + if (in.channels() == 3) { //判断输入图像的通道数,如果为3,即为彩色图像,执行以下操作 + //创建一个Mat对象hsv和一个Mat向量hsvSplit,然后使用cvtColor函数将输入图像从BGR色彩空间转换为HSV色彩空间,结果存储在hsv中 Mat hsv; std::vector hsvSplit; cvtColor(in, hsv, CV_BGR2HSV); + //使用split函数将hsv图像的三个通道分离到hsvSplit向量中,然后对V通道(亮度)进行直方图均衡化 split(hsv, hsvSplit); equalizeHist(hsvSplit[2], hsvSplit[2]); + //使用merge函数将处理后的三个通道合并回hsv,然后使用cvtColor函数将hsv图像从HSV色彩空间转换回BGR色彩空间,结果存储在out中 merge(hsvSplit, hsv); cvtColor(hsv, out, CV_HSV2BGR); - } else if (in.channels() == 1) { + } else if (in.channels() == 1) { //如果输入图像的通道数为1,即为灰度图像,执行以下操作 + //直接对输入图像进行直方图均衡化,结果存储在out中 equalizeHist(in, out); } - return out; + return out; // 返回处理后的图像 } #define HORIZONTAL 1 #define VERTICAL 0 - Mat CutTheRect(Mat &in, Rect &rect) { +//从输入图像中裁剪出一个指定的矩形区域,并将其放置在一个新的正方形图像的中心位置 + Mat CutTheRect(Mat &in, Rect &rect) { //接受一个Mat类型的引用in和一个Rect类型的引用rect作为参数 int size = in.cols; // (rect.width>rect.height)?rect.width:rect.height; +//创建了一个新的Mat对象dstMat,其大小为sizexsize,类型为CV_8UC1(8位无符号单通道),并将其所有元素设置为0 Mat dstMat(size, size, CV_8UC1); dstMat.setTo(Scalar(0, 0, 0)); - +//计算矩形rect在新图像中的起始位置(x, y) int x = (int) floor((float) (size - rect.width) / 2.0f); int y = (int) floor((float) (size - rect.height) / 2.0f); +//遍历矩形rect中的每一个像素,并将其复制到新图像dstMat的相应位置 for (int i = 0; i < rect.height; ++i) { for (int j = 0; j < rect.width; ++j) { @@ -647,123 +659,129 @@ void clearBorder(const Mat &img, Rect& cropRect) { } } - // + //返回处理后的新图像dstMat return dstMat; } +//其目的是在输入图像中找到一个矩形区域,该区域包含了图像中所有像素值大于20的部分 +//函数接受一个 Mat 类型的引用 in 作为参数,返回一个 Rect 类型的对象 Rect GetCenterRect(Mat &in) { - Rect _rect; - - int top = 0; - int bottom = in.rows - 1; + Rect _rect; // 定义一个矩形对象 - // find the center rect + int top = 0; // 初始化矩形的上边界 + int bottom = in.rows - 1; // 初始化矩形的下边界为图像的行数减1 - for (int i = 0; i < in.rows; ++i) { - bool bFind = false; - for (int j = 0; j < in.cols; ++j) { - if (in.data[i * in.step[0] + j] > 20) { - top = i; - bFind = true; + // 从上到下遍历图像的每一行,找到第一个包含像素值大于20的行,将其行数设为矩形的上边界 + for (int i = 0; i < in.rows; ++i) { + bool bFind = false; + for (int j = 0; j < in.cols; ++j) { + if (in.data[i * in.step[0] + j] > 20) { + top = i; + bFind = true; + break; + } + } + if (bFind) { break; } } - if (bFind) { - break; - } - } - for (int i = in.rows - 1; - i >= 0; - --i) { - bool bFind = false; - for (int j = 0; j < in.cols; ++j) { - if (in.data[i * in.step[0] + j] > 20) { - bottom = i; - bFind = true; + // 从下到上遍历图像的每一行,找到第一个包含像素值大于20的行,将其行数设为矩形的下边界 + for (int i = in.rows - 1; i >= 0; --i) { + bool bFind = false; + for (int j = 0; j < in.cols; ++j) { + if (in.data[i * in.step[0] + j] > 20) { + bottom = i; + bFind = true; + break; + } + } + if (bFind) { break; } } - if (bFind) { - break; - } - - } + int left = 0; // 初始化矩形的左边界 + int right = in.cols - 1; // 初始化矩形的右边界为图像的列数减1 - int left = 0; - int right = in.cols - 1; - for (int j = 0; j < in.cols; ++j) { - bool bFind = false; - for (int i = 0; i < in.rows; ++i) { - if (in.data[i * in.step[0] + j] > 20) { - left = j; - bFind = true; + // 从左到右遍历图像的每一列,找到第一个包含像素值大于20的列,将其列数设为矩形的左边界 + for (int j = 0; j < in.cols; ++j) { + bool bFind = false; + for (int i = 0; i < in.rows; ++i) { + if (in.data[i * in.step[0] + j] > 20) { + left = j; + bFind = true; + break; + } + } + if (bFind) { break; } } - if (bFind) { - break; - } - - } - for (int j = in.cols - 1; - j >= 0; - --j) { - bool bFind = false; - for (int i = 0; i < in.rows; ++i) { - if (in.data[i * in.step[0] + j] > 20) { - right = j; - bFind = true; + // 从右到左遍历图像的每一列,找到第一个包含像素值大于20的列,将其列数设为矩形的右边界 + for (int j = in.cols - 1; j >= 0; --j) { + bool bFind = false; + for (int i = 0; i < in.rows; ++i) { + if (in.data[i * in.step[0] + j] > 20) { + right = j; + bFind = true; + break; + } + } + if (bFind) { break; } } - if (bFind) { - break; - } - } - _rect.x = left; - _rect.y = top; - _rect.width = right - left + 1; - _rect.height = bottom - top + 1; + // 设置矩形的位置和大小 + _rect.x = left; + _rect.y = top; + _rect.width = right - left + 1; + _rect.height = bottom - top + 1; - return _rect; + return _rect; // 返回矩形对象 } + //计算图像中大于给定阈值的像素数量 float countOfBigValue(Mat &mat, int iValue) { float iCount = 0.0; + // 如果图像的行数大于1,遍历每一行 if (mat.rows > 1) { for (int i = 0; i < mat.rows; ++i) { + // 如果当前像素的值大于阈值,计数器加1 if (mat.data[i * mat.step[0]] > iValue) { iCount += 1.0; } } return iCount; - } else { + // 如果图像的行数不大于1,遍历每一列 for (int i = 0; i < mat.cols; ++i) { + // 如果当前像素的值大于阈值,计数器加1 if (mat.data[i] > iValue) { iCount += 1.0; } } - return iCount; } } + //计算图像的投影直方图 Mat ProjectedHistogram(Mat img, int t, int threshold) { + // 根据参数t的值,确定直方图的长度 int sz = (t) ? img.rows : img.cols; + // 创建一个长度为sz的零矩阵,用于存储直方图 Mat mhist = Mat::zeros(1, sz, CV_32F); for (int j = 0; j < sz; j++) { + // 根据参数t的值,确定是对图像的行还是列进行操作 Mat data = (t) ? img.row(j) : img.col(j); - + // 计算大于阈值的像素数量,并存储在直方图中 mhist.at(j) = countOfBigValue(data, threshold); } - // Normalize histogram + // 归一化直方图 double min, max; minMaxLoc(mhist, &min, &max); @@ -773,13 +791,17 @@ void clearBorder(const Mat &img, Rect& cropRect) { return mhist; } + //显示直方图 Mat showHistogram(const Mat &hist) { int height = 32; int width = hist.cols; + // 创建一个高度为32,宽度为直方图长度的零矩阵,用于显示直方图 Mat show = Mat::zeros(height, width, CV_8UC1); for (int i = 0; i < width; i++) { + // 计算直方图的每一列的长度 int len = int((float) height * hist.at(i)); for (int j = height - 1; j >= 0; j--) { + // 将直方图的每一列的长度以像素的形式显示出来 if (height - j <= len) show.at(j, i) = (char) 255; } @@ -789,59 +811,59 @@ void clearBorder(const Mat &img, Rect& cropRect) { Mat preprocessChar(Mat in, int char_size) { // Remap image - int h = in.rows; - int w = in.cols; + int h = in.rows; // 获取输入图像的行数 + int w = in.cols; // 获取输入图像的列数 - int charSize = char_size; + int charSize = char_size; // 获取字符大小 - Mat transformMat = Mat::eye(2, 3, CV_32F); - int m = max(w, h); - transformMat.at(0, 2) = float(m / 2 - w / 2); + Mat transformMat = Mat::eye(2, 3, CV_32F); // 创建一个2x3的单位矩阵 + int m = max(w, h); // 获取图像的最大维度 + transformMat.at(0, 2) = float(m / 2 - w / 2); // 计算并设置变换矩阵的平移部分 transformMat.at(1, 2) = float(m / 2 - h / 2); - Mat warpImage(m, m, in.type()); + Mat warpImage(m, m, in.type()); // 创建一个新的图像,大小为m*m,类型与输入图像相同 warpAffine(in, warpImage, transformMat, warpImage.size(), INTER_LINEAR, - BORDER_CONSTANT, Scalar(0)); + BORDER_CONSTANT, Scalar(0)); // 对输入图像进行仿射变换 Mat out; - cv::resize(warpImage, out, Size(charSize, charSize)); + cv::resize(warpImage, out, Size(charSize, charSize)); // 将变换后的图像缩放到指定的字符大小 - return out; + return out; // 返回处理后的图像 } Rect GetChineseRect(const Rect rectSpe) { - int height = rectSpe.height; - float newwidth = rectSpe.width * 1.10f; - int x = rectSpe.x; - int y = rectSpe.y; + int height = rectSpe.height; // 获取特定矩形的高度 + float newwidth = rectSpe.width * 1.10f; // 计算新的宽度,为原宽度的110% + int x = rectSpe.x; // 获取特定矩形的x坐标 + int y = rectSpe.y; // 获取特定矩形的y坐标 - int newx = x - int(newwidth * 1.10f); - newx = newx > 0 ? newx : 0; + int newx = x - int(newwidth * 1.10f); // 计算新的x坐标,为原x坐标减去新宽度的110% + newx = newx > 0 ? newx : 0; // 如果新的x坐标小于0,将其设置为0 - Rect a(newx, y, int(newwidth), height); + Rect a(newx, y, int(newwidth), height); // 创建一个新的矩形 - return a; + return a; // 返回新的矩形 } bool verifyCharSizes(Rect r) { // Char sizes 45x90 - float aspect = 45.0f / 90.0f; - float charAspect = (float) r.width / (float) r.height; - float error = 0.35f; - float minHeight = 25.f; - float maxHeight = 50.f; + float aspect = 45.0f / 90.0f; // 计算字符的宽高比 + float charAspect = (float) r.width / (float) r.height; // 计算矩形的宽高比 + float error = 0.35f; // 设置误差范围 + float minHeight = 25.f; // 设置最小高度 + float maxHeight = 50.f; // 设置最大高度 // We have a different aspect ratio for number 1, and it can be ~0.2 - float minAspect = 0.05f; - float maxAspect = aspect + aspect * error; + float minAspect = 0.05f; // 设置最小宽高比 + float maxAspect = aspect + aspect * error; // 设置最大宽高比 // bb area - int bbArea = r.width * r.height; + int bbArea = r.width * r.height; // 计算矩形的面积 if (charAspect > minAspect && charAspect < maxAspect /*&& r.rows >= minHeight && r.rows < maxHeight*/) - return true; + return true; // 如果矩形的宽高比在最小和最大宽高比之间,返回true else - return false; + return false; // 否则返回false } @@ -849,21 +871,21 @@ void clearBorder(const Mat &img, Rect& cropRect) { Mat ret; if (image.cols > maxSize.width || image.rows > maxSize.height) { - double widthRatio = image.cols / (double) maxSize.width; - double heightRatio = image.rows / (double) maxSize.height; - double m_real_to_scaled_ratio = max(widthRatio, heightRatio); + double widthRatio = image.cols / (double) maxSize.width; // 计算图像宽度和最大宽度的比例 + double heightRatio = image.rows / (double) maxSize.height; // 计算图像高度和最大高度的比例 + double m_real_to_scaled_ratio = max(widthRatio, heightRatio); // 获取宽度和高度比例中的最大值 - int newWidth = int(image.cols / m_real_to_scaled_ratio); - int newHeight = int(image.rows / m_real_to_scaled_ratio); + int newWidth = int(image.cols / m_real_to_scaled_ratio); // 计算新的宽度 + int newHeight = int(image.rows / m_real_to_scaled_ratio); // 计算新的高度 - cv::resize(image, ret, Size(newWidth, newHeight), 0, 0); - scale_ratio = m_real_to_scaled_ratio; + cv::resize(image, ret, Size(newWidth, newHeight), 0, 0); // 将图像缩放到新的大小 + scale_ratio = m_real_to_scaled_ratio; // 设置缩放比例 } else { - ret = image; - scale_ratio = 1.0; + ret = image; // 如果图像的大小已经小于或等于最大大小,直接返回原图像 + scale_ratio = 1.0; // 设置缩放比例为1.0 } - return ret; + return ret; // 返回处理后的图像 } From 1809748e2fa0e1c62ea7aeb78e1b24f69f66d32f Mon Sep 17 00:00:00 2001 From: www-369 <2776296049@qq.com> Date: Tue, 26 Dec 2023 01:31:15 +0800 Subject: [PATCH 2/2] WXP2.1 --- src/src/core/core_func.cpp | 85 ++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/src/src/core/core_func.cpp b/src/src/core/core_func.cpp index a2d2895..fd98d57 100644 --- a/src/src/core/core_func.cpp +++ b/src/src/core/core_func.cpp @@ -809,6 +809,8 @@ namespace easypr { return show; } +// 对输入图像进行预处理 +// 预处理包括创建一个单位矩阵,计算图像的最大维度,对输入图像进行仿射变换,然后将变换后的图像缩放到指定的字符大小 Mat preprocessChar(Mat in, int char_size) { // Remap image int h = in.rows; // 获取输入图像的行数 @@ -831,6 +833,9 @@ namespace easypr { return out; // 返回处理后的图像 } +// 接受一个特定的矩形,然后计算新的宽度(为原宽度的110%) +// 新的x坐标(为原x坐标减去新宽度的110%),如果新的x坐标小于0,将其设置为0 +// 然后创建一个新的矩形,并返回 Rect GetChineseRect(const Rect rectSpe) { int height = rectSpe.height; // 获取特定矩形的高度 float newwidth = rectSpe.width * 1.10f; // 计算新的宽度,为原宽度的110% @@ -845,6 +850,7 @@ namespace easypr { return a; // 返回新的矩形 } +// 接受一个矩形,然后计算字符的宽高比,矩形的宽高比…… bool verifyCharSizes(Rect r) { // Char sizes 45x90 float aspect = 45.0f / 90.0f; // 计算字符的宽高比 @@ -866,7 +872,9 @@ namespace easypr { return false; // 否则返回false } - +// 计算图像宽度和最大宽度的比例,图像高度和最大高度的比例,获取宽度和高度比例中的最大值 +// 计算新的宽度和新的高度,将图像缩放到新的大小,设置缩放比例 +// 如果图像的大小已经小于或等于最大大小,直接返回原图像,设置缩放比例为1.0 Mat scaleImage(const Mat &image, const Size &maxSize, double &scale_ratio) { Mat ret; @@ -890,49 +898,49 @@ namespace easypr { // Scale back RotatedRect +// 将一个旋转矩形(RotatedRect)按照给定的缩放比例进行反向缩放,返回一个新的旋转矩形 RotatedRect scaleBackRRect(const RotatedRect &rr, const float scale_ratio) { - float width = rr.size.width * scale_ratio; - float height = rr.size.height * scale_ratio; - float x = rr.center.x * scale_ratio; - float y = rr.center.y * scale_ratio; - RotatedRect mserRect(Point2f(x, y), Size2f(width, height), rr.angle); + float width = rr.size.width * scale_ratio; // 根据缩放比例计算原始宽度 + float height = rr.size.height * scale_ratio; // 根据缩放比例计算原始高度 + float x = rr.center.x * scale_ratio; // 根据缩放比例计算原始中心点的x坐标 + float y = rr.center.y * scale_ratio; // 根据缩放比例计算原始中心点的y坐标 + RotatedRect mserRect(Point2f(x, y), Size2f(width, height), rr.angle); // 创建一个新的RotatedRect对象,使用原始的尺寸和角度 - return mserRect; + return mserRect; // 返回新的RotatedRect对象 } +// 验证一个矩形是否符合预设的车牌尺寸,包括面积和宽高比,如果符合则返回true,否则返回false bool verifyPlateSize(Rect mr) { - float error = 0.6f; + float error = 0.6f; // 设置误差范围 // Spain car plate size: 52x11 aspect 4,7272 // China car plate size: 440mm*140mm,aspect 3.142857 // Real car plate size: 136 * 32, aspect 4 - float aspect = 3.75; + float aspect = 3.75; // 设置期望的宽高比 // Set a min and max area. All other patchs are discarded - // int min= 1*aspect*1; // minimum area - // int max= 2000*aspect*2000; // maximum area - int min = 34 * 8 * 1; // minimum area - int max = 34 * 8 * 200; // maximum area + int min = 34 * 8 * 1; // 设置最小面积 + int max = 34 * 8 * 200; // 设置最大面积 // Get only patchs that match to a respect ratio. - float rmin = aspect - aspect * error; - float rmax = aspect + aspect * error; - - float area = float(mr.height * mr.width); - float r = (float) mr.width / (float) mr.height; - if (r < 1) r = (float) mr.height / (float) mr.width; + float rmin = aspect - aspect * error; // 计算最小宽高比 + float rmax = aspect + aspect * error; // 计算最大宽高比 - // cout << "area:" << area << endl; - // cout << "r:" << r << endl; + float area = float(mr.height * mr.width); // 计算矩形的面积 + float r = (float) mr.width / (float) mr.height; // 计算矩形的宽高比 + if (r < 1) r = (float) mr.height / (float) mr.width; // 如果宽高比小于1,取其倒数 + // 判断矩形的面积和宽高比是否在指定的范围内 if ((area < min || area > max) || (r < rmin || r > rmax)) - return false; + return false; // 如果不在指定范围内,返回false else - return true; + return true; // 如果在指定范围内,返回true } +// 接收一个RotatedRect对象和一个布尔值showDebug作为参数 +// 该函数的主要目的是验证一个旋转矩形(通常是车牌)是否符合预设的尺寸和比例 bool verifyRotatedPlateSizes(RotatedRect mr, bool showDebug) { - float error = 0.65f; + float error = 0.65f; // 定义误差值error,以及车牌的宽高比aspect // Spain car plate size: 52x11 aspect 4,7272 // China car plate size: 440mm*140mm,aspect 3.142857 @@ -946,6 +954,7 @@ namespace easypr { //int max = 34 * 8 * 200; // maximum area // Get only patchs that match to a respect ratio. + // 计算最小和最大的面积min和max,以及最小和最大的宽高比aspect_min和aspect_max float aspect_min = aspect - aspect * error; float aspect_max = aspect + aspect * error; @@ -954,27 +963,28 @@ namespace easypr { float min = float(width_min * width_min / aspect_max); // minimum area float max = float(width_max * width_max / aspect_min); // maximum area - +// 获取旋转矩形的宽度、高度、面积、宽高比和角度 float width = mr.size.width; float height = mr.size.height; float area = width * height; float ratio = width / height; float angle = mr.angle; - if (ratio < 1) { + if (ratio < 1) { // 如果宽高比小于1,交换宽度和高度的值,并调整角度 swap(width, height); ratio = width / height; angle = 90.f + angle; //std::cout << "angle:" << angle << std::endl; } - +// 定义角度的最小和最大值 float angle_min = -60.f; float angle_max = 60.f; //std::cout << "aspect_min:" << aspect_min << std::endl; //std::cout << "aspect_max:" << aspect_max << std::endl; - +// 通过一系列的条件判断,验证旋转矩形是否符合预设的尺寸和比例 +// 如果不符合,返回false;如果符合,返回true if (area < min || area > max) { if (0 && showDebug) { std::cout << "area < min || area > max: " << area << std::endl; @@ -1007,27 +1017,30 @@ namespace easypr { } //! non-maximum suppression +// 该函数实现了非极大值抑制(Non-Maximum Suppression,NMS)的功能 +// 非极大值抑制是一种常用于目标检测中的技术,用于消除多余的重叠区域 +// 该函数主要删除inVec中重叠度过高的CCharacter对象,只保留最具代表性的对象 void NMStoCharacter(std::vector &inVec, double overlap) { - - std::sort(inVec.begin(), inVec.end()); - +// 函数接受两个参数,一个是CCharacter对象的向量inVec,另一个是重叠阈值overlap + std::sort(inVec.begin(), inVec.end()); // 对inVec进行排序 +// 遍历inVec中的每一个CCharacter对象 std::vector::iterator it = inVec.begin(); for (; it != inVec.end(); ++it) { - CCharacter charSrc = *it; + CCharacter charSrc = *it; // 对于每一个CCharacter对象,获取其位置信息rectSrc //std::cout << "plateScore:" << plateSrc.getPlateScore() << std::endl; Rect rectSrc = charSrc.getCharacterPos(); - +// 遍历当前CCharacter对象之后的所有CCharacter对象 std::vector::iterator itc = it + 1; for (; itc != inVec.end();) { - CCharacter charComp = *itc; + CCharacter charComp = *itc; // 对于每一个后续的CCharacter对象,也获取其位置信息rectComp Rect rectComp = charComp.getCharacterPos(); //Rect rectInter = rectSrc & rectComp; //Rect rectUnion = rectSrc | rectComp; //double r = double(rectInter.area()) / double(rectUnion.area()); - +// 计算当前CCharacter对象与后续CCharacter对象的交并比(Intersection over Union,IoU) float iou = computeIOU(rectSrc, rectComp); - +// 如果IoU大于设定的阈值overlap,则删除后续的CCharacter对象。否则,继续检查下一个CCharacter对象 if (iou > overlap) { itc = inVec.erase(itc); } else {