|
|
|
@ -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<char>(i, j) == img.at<char>(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<char>(i, j) == img.at<char>(i, j + 1))
|
|
|
|
|
nojumpCount++;
|
|
|
|
|
if (nojumpCount > noJunpCount_thresh) {
|
|
|
|
|
nojumpCount = 0;
|
|
|
|
|
isBorder = 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
boder.at<char>(i) = (char) isBorder;
|
|
|
|
|
}
|
|
|
|
|
boder.at<char>(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<char>(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<char>(i) == 1) {
|
|
|
|
|
minMatTop = i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 从下到上搜索下边界
|
|
|
|
|
for (int i = r - 1; i > maxtop; i--) {
|
|
|
|
|
if (boder.at<char>(i) == 1) {
|
|
|
|
|
maxMatTop = i;
|
|
|
|
|
// 从下到上搜索下边界
|
|
|
|
|
for (int i = r - 1; i > maxtop; i--) {
|
|
|
|
|
if (boder.at<char>(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<cv::Mat> 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<float>(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<float>(i));
|
|
|
|
|
for (int j = height - 1; j >= 0; j--) {
|
|
|
|
|
// 将直方图的每一列的长度以像素的形式显示出来
|
|
|
|
|
if (height - j <= len)
|
|
|
|
|
show.at<char>(j, i) = (char) 255;
|
|
|
|
|
}
|
|
|
|
@ -787,130 +809,138 @@ void clearBorder(const Mat &img, Rect& cropRect) {
|
|
|
|
|
return show;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 对输入图像进行预处理
|
|
|
|
|
// 预处理包括创建一个单位矩阵,计算图像的最大维度,对输入图像进行仿射变换,然后将变换后的图像缩放到指定的字符大小
|
|
|
|
|
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<float>(0, 2) = float(m / 2 - w / 2);
|
|
|
|
|
Mat transformMat = Mat::eye(2, 3, CV_32F); // 创建一个2x3的单位矩阵
|
|
|
|
|
int m = max(w, h); // 获取图像的最大维度
|
|
|
|
|
transformMat.at<float>(0, 2) = float(m / 2 - w / 2); // 计算并设置变换矩阵的平移部分
|
|
|
|
|
transformMat.at<float>(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; // 返回处理后的图像
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 接受一个特定的矩形,然后计算新的宽度(为原宽度的110%)
|
|
|
|
|
// 新的x坐标(为原x坐标减去新宽度的110%),如果新的x坐标小于0,将其设置为0
|
|
|
|
|
// 然后创建一个新的矩形,并返回
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 计算图像宽度和最大宽度的比例,图像高度和最大高度的比例,获取宽度和高度比例中的最大值
|
|
|
|
|
// 计算新的宽度和新的高度,将图像缩放到新的大小,设置缩放比例
|
|
|
|
|
// 如果图像的大小已经小于或等于最大大小,直接返回原图像,设置缩放比例为1.0
|
|
|
|
|
Mat scaleImage(const Mat &image, const Size &maxSize, double &scale_ratio) {
|
|
|
|
|
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; // 返回处理后的图像
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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 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;
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
|
@ -924,6 +954,7 @@ void clearBorder(const Mat &img, Rect& cropRect) {
|
|
|
|
|
//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;
|
|
|
|
|
|
|
|
|
@ -932,27 +963,28 @@ void clearBorder(const Mat &img, Rect& cropRect) {
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
@ -985,27 +1017,30 @@ void clearBorder(const Mat &img, Rect& cropRect) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//! non-maximum suppression
|
|
|
|
|
// 该函数实现了非极大值抑制(Non-Maximum Suppression,NMS)的功能
|
|
|
|
|
// 非极大值抑制是一种常用于目标检测中的技术,用于消除多余的重叠区域
|
|
|
|
|
// 该函数主要删除inVec中重叠度过高的CCharacter对象,只保留最具代表性的对象
|
|
|
|
|
void NMStoCharacter(std::vector<CCharacter> &inVec, double overlap) {
|
|
|
|
|
|
|
|
|
|
std::sort(inVec.begin(), inVec.end());
|
|
|
|
|
|
|
|
|
|
// 函数接受两个参数,一个是CCharacter对象的向量inVec,另一个是重叠阈值overlap
|
|
|
|
|
std::sort(inVec.begin(), inVec.end()); // 对inVec进行排序
|
|
|
|
|
// 遍历inVec中的每一个CCharacter对象
|
|
|
|
|
std::vector<CCharacter>::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<CCharacter>::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 {
|
|
|
|
|