Merge branch 'develop' of https://bdgit.educoder.net/pmuy8zkev/Galaxy into wuxiuping_branch

huangbingcheng-branch
www-369 1 year ago
commit 5e82037ce5

@ -7,8 +7,8 @@
namespace easypr {
const float DEFAULT_BLUEPERCEMT = 0.3f;//定义蓝色百分比
const float DEFAULT_WHITEPERCEMT = 0.1f;//定义白色百分比
const float DEFAULT_BLUEPERCEMT = 0.3f;//定义蓝色百分比
const float DEFAULT_WHITEPERCEMT = 0.1f;//定义白色百分比
CCharsSegment::CCharsSegment() {
m_LiuDingSize = DEFAULT_LIUDING_SIZE;
@ -19,75 +19,75 @@ CCharsSegment::CCharsSegment() {
m_WhitePercent = DEFAULT_WHITEPERCEMT;
m_debug = DEFAULT_DEBUG;
}//设置几个类的成员变量,进行初始化
}//设置几个类的成员变量,进行初始化
bool CCharsSegment::verifyCharSizes(Mat r) {
// Char sizes 45x90
//接收一个OpenCV的Mat对象作为参数将其赋值给r
float aspect = 45.0f / 90.0f;//预期的字符宽高比
//接收一个OpenCV的Mat对象作为参数将其赋值给r
float aspect = 45.0f / 90.0f;//预期的字符宽高比
float charAspect = (float)r.cols / (float)r.rows;
//输入图像的宽高比,通过计算输入图像的列数和行数得出。
float error = 0.7f;//允许的宽高比误差
float minHeight = 10.f;//字符最小高度
float maxHeight = 35.f;//字符最大高度
//输入图像的宽高比,通过计算输入图像的列数和行数得出。
float error = 0.7f;//允许的宽高比误差
float minHeight = 10.f;//字符最小高度
float maxHeight = 35.f;//字符最大高度
// We have a different aspect ratio for number 1, and it can be ~0.2
float minAspect = 0.05f//最小允许宽高比
float minAspect = 0.05f//最小允许宽高比
float maxAspect = aspect + aspect * error
//最大允许宽高比,由预期的宽高比加上其误差得出
//最大允许宽高比,由预期的宽高比加上其误差得出
// area of pixels
int area = cv::countNonZero(r);
// 输入图像的非零像素数
// 输入图像的非零像素数
int bbArea = r.cols * r.rows;
//% of pixel in area
//输入图像的总面积,通过乘以其列数和行数得出
//输入图像的总面积,通过乘以其列数和行数得出
int percPixels = area / bbArea;
//非零像素数在总面积中的比例
//非零像素数在总面积中的比例
if (percPixels <= 1 && charAspect > minAspect && charAspect < maxAspect &&
r.rows >= minHeight && r.rows < maxHeight)
return true;
else
return false;
}//判断输入图像是否可以被视为一个字符,满足特定的字符尺寸和形状要求
}//判断输入图像是否可以被视为一个字符,满足特定的字符尺寸和形状要求
Mat CCharsSegment::preprocessChar(Mat in) {
// Remap image
//接收一个Mat对象作为输入它对输入的图像进行一些预处理操作。
//接收一个Mat对象作为输入它对输入的图像进行一些预处理操作。
int h = in.rows;
int w = in.cols;
//获取输入图像的高度和宽度
//获取输入图像的高度和宽度
int charSize = CHAR_SIZE;
Mat transformMat = Mat::eye(2, 3, CV_32F);
//定义一个2x3的单位矩阵作为变换矩阵这个矩阵用于图像的几何变换。
//定义一个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());
warpAffine(in, warpImage, transformMat, warpImage.size(), INTER_LINEAR,
BORDER_CONSTANT, Scalar(0));
Mat out;
//warpAffine函数应用仿射变换生成新的变换后的图像。
//warpAffine函数应用仿射变换生成新的变换后的图像。
resize(warpImage, out, Size(charSize, charSize));
//resize函数调整新图像的尺寸至预设的字符大小。
//resize函数调整新图像的尺寸至预设的字符大小。
return out;
}
//! choose the bese threshold method for chinese
void CCharsSegment::judgeChinese(Mat in, Mat& out, Color plateType)
{ //接收一个Mat对象作为输入并对其进行阈值处理以识别中文字符
{ //接收一个Mat对象作为输入并对其进行阈值处理以识别中文字符
Mat auxRoi = in;
//表示输入图像的一个子区域,其中可能包含要识别的中文字符
//表示输入图像的一个子区域,其中可能包含要识别的中文字符
float valOstu = -1.f, valAdap = -1.f;
Mat roiOstu, roiAdap;
//为Otsu阈值法和自适应阈值法分别定义了一个变量valOstu和valAdap
// 用于存储阈值
//为Otsu阈值法和自适应阈值法分别定义了一个变量valOstu和valAdap
// 用于存储阈值
bool isChinese = true;
if (1) {
if (BLUE == plateType) {
@ -102,23 +102,23 @@ void CCharsSegment::judgeChinese(Mat in, Mat& out, Color plateType)
else {
threshold(auxRoi, roiOstu, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
}
//根据输入的颜色类型使用不同的阈值处理方法对auxRoi进行阈值处理
// 生成二值化的图像roiOstu
//根据输入的颜色类型使用不同的阈值处理方法对auxRoi进行阈值处理
// 生成二值化的图像roiOstu
roiOstu = preprocessChar(roiOstu);
//对二值化的图像进行字符识别预处理
//调用preprocessChar(roiOstu)方法对图像进行进一步处理
//对二值化的图像进行字符识别预处理
//调用preprocessChar(roiOstu)方法对图像进行进一步处理
if (0) {
imshow("roiOstu", roiOstu);
waitKey(0);
destroyWindow("roiOstu");
}
auto character = CharsIdentify::instance()->identifyChinese(roiOstu, valOstu, isChinese);
//对预处理后的图像进行字符识别,并返回识别的结果和阈值。
//对预处理后的图像进行字符识别,并返回识别的结果和阈值。
}
if (1) {
if (BLUE == plateType) {
adaptiveThreshold(auxRoi, roiAdap, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, 0);
//调用图像处理函数对图像进行自适应阈值处理。
//调用图像处理函数对图像进行自适应阈值处理。
}
else if (YELLOW == plateType) {
adaptiveThreshold(auxRoi, roiAdap, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV, 3, 0);
@ -130,7 +130,7 @@ void CCharsSegment::judgeChinese(Mat in, Mat& out, Color plateType)
adaptiveThreshold(auxRoi, roiAdap, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, 0);
}
roiAdap = preprocessChar(roiAdap);
//对二值化后的图像进行预处理字符操作。
//对二值化后的图像进行预处理字符操作。
auto character = CharsIdentify::instance()->identifyChinese(roiAdap, valAdap, isChinese);
}
@ -146,20 +146,20 @@ void CCharsSegment::judgeChinese(Mat in, Mat& out, Color plateType)
}
void CCharsSegment::judgeChineseGray(Mat in, Mat& out, Color plateType) {
out = in;//复制输入图像
out = in;//复制输入图像
}
bool slideChineseWindow(Mat& image, Rect mr, Mat& newRoi, Color plateType, float slideLengthRatio, bool useAdapThreshold) {
std::vector<CCharacter> charCandidateVec;
//用于存储候选的中文字符。
//用于存储候选的中文字符。
Rect maxrect = mr;
Point tlPoint = mr.tl();
//获取mr的左上角点并赋值给tlPoint
bool isChinese = true;//标记图像中是否含有中文字符
//获取mr的左上角点并赋值给tlPoint
bool isChinese = true;//标记图像中是否含有中文字符
int slideLength = int(slideLengthRatio * maxrect.width);
int slideStep = 1;//控制滑动窗口的步长
int fromX = 0;//指定从哪个位置开始滑动窗口
fromX = tlPoint.x;//实际的起始位置是左上角的x坐标
int slideStep = 1;//控制滑动窗口的步长
int fromX = 0;//指定从哪个位置开始滑动窗口
fromX = tlPoint.x;//实际的起始位置是左上角的x坐标
for (int slideX = -slideLength; slideX < slideLength; slideX += slideStep) {
float x_slide = 0;
@ -167,21 +167,21 @@ bool slideChineseWindow(Mat& image, Rect mr, Mat& newRoi, Color plateType, float
x_slide = float(fromX + slideX);
float y_slide = (float)tlPoint.y;
//用来计算滑动窗口的位置。
//用来计算滑动窗口的位置。
Point2f p_slide(x_slide, y_slide);
//cv::circle(image, p_slide, 2, Scalar(255), 1);
int chineseWidth = int(maxrect.width);
int chineseHeight = int(maxrect.height);
//宽度和高度
//宽度和高度
Rect rect(Point2f(x_slide, y_slide), Size(chineseWidth, chineseHeight));
if (rect.tl().x < 0 || rect.tl().y < 0 || rect.br().x >= image.cols || rect.br().y >= image.rows)
continue;
//检查新定义的矩形区域是否在图像的边界内。如果超出了边界,就跳过这次循环。
//检查新定义的矩形区域是否在图像的边界内。如果超出了边界,就跳过这次循环。
Mat auxRoi = image(rect);
//原始图像中提取出这个矩形区域的子图像auxRoi
//原始图像中提取出这个矩形区域的子图像auxRoi
Mat roiOstu, roiAdap;
if (1) {
if (BLUE == plateType) {
@ -196,15 +196,15 @@ bool slideChineseWindow(Mat& image, Rect mr, Mat& newRoi, Color plateType, float
else {
threshold(auxRoi, roiOstu, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
}
//根据预设的颜色,使用不同的阈值处理方法对提取出的子图像进行阈值处理。
// 处理后的图像分别保存到roiOstu和roiAdap。
//根据预设的颜色,使用不同的阈值处理方法对提取出的子图像进行阈值处理。
// 处理后的图像分别保存到roiOstu和roiAdap。
roiOstu = preprocessChar(roiOstu, kChineseSize);
CCharacter charCandidateOstu;
charCandidateOstu.setCharacterPos(rect); // 设置字符的位置信息
charCandidateOstu.setCharacterMat(roiOstu);// 设置字符的图像信息
charCandidateOstu.setIsChinese(isChinese);// 设置字符是否为中文字符的信息
charCandidateVec.push_back(charCandidateOstu);// 将字符信息添加到字符候选向量中以备后续处理使用。
charCandidateOstu.setCharacterPos(rect); // 设置字符的位置信息
charCandidateOstu.setCharacterMat(roiOstu);// 设置字符的图像信息
charCandidateOstu.setIsChinese(isChinese);// 设置字符是否为中文字符的信息
charCandidateVec.push_back(charCandidateOstu);// 将字符信息添加到字符候选向量中以备后续处理使用。
}
if (useAdapThreshold) {
if (BLUE == plateType) {
@ -220,7 +220,7 @@ bool slideChineseWindow(Mat& image, Rect mr, Mat& newRoi, Color plateType, float
adaptiveThreshold(auxRoi, roiAdap, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, 0);
}
roiAdap = preprocessChar(roiAdap, kChineseSize);
//对图像进行阈值处理以获得二值化的图像。处理后的图像保存在roiAdap中。
//对图像进行阈值处理以获得二值化的图像。处理后的图像保存在roiAdap中。
CCharacter charCandidateAdap;
charCandidateAdap.setCharacterPos(rect);
charCandidateAdap.setCharacterMat(roiAdap);
@ -234,8 +234,8 @@ bool slideChineseWindow(Mat& image, Rect mr, Mat& newRoi, Color plateType, float
double overlapThresh = 0.1;
NMStoCharacter(charCandidateVec, overlapThresh);
//使用了非极大值抑制NMS算法它通过计算每个字符候选区域与其它所有字符候选区域的交并比
// 将交并比低于某个阈值的字符候选区域去除
//使用了非极大值抑制NMS算法它通过计算每个字符候选区域与其它所有字符候选区域的交并比
// 将交并比低于某个阈值的字符候选区域去除
if (charCandidateVec.size() >= 1) {
std::sort(charCandidateVec.begin(), charCandidateVec.end(),
[](const CCharacter& r1, const CCharacter& r2) {
@ -245,60 +245,60 @@ bool slideChineseWindow(Mat& image, Rect mr, Mat& newRoi, Color plateType, float
newRoi = charCandidateVec.at(0).getCharacterMat();
return true;
}
//如果生成的字符候选向量中至少有一个元素,
// 则将得分最高的字符候选区域的图像提取出来并返回true否则返回false。
//如果生成的字符候选向量中至少有一个元素,
// 则将得分最高的字符候选区域的图像提取出来并返回true否则返回false。
return false;
}
bool slideChineseGrayWindow(const Mat& image, Rect& mr, Mat& newRoi, Color plateType, float slideLengthRatio) {
std::vector<CCharacter> charCandidateVec;
// 定义一个向量来保存字符候选
// 定义一个向量来保存字符候选
Rect maxrect = mr;
Point tlPoint = mr.tl();
// 获取给定的最大矩形区域的左上角点和宽度
// 获取给定的最大矩形区域的左上角点和宽度
bool isChinese = true;
// 默认假设它是中文字符
// 默认假设它是中文字符
int slideLength = int(slideLengthRatio * maxrect.width);
int slideStep = 1;
int fromX = 0;
fromX = tlPoint.x;
// 根据最大矩形的宽度计算滑动窗口的长度和步长
// 根据最大矩形的宽度计算滑动窗口的长度和步长
for (int slideX = -slideLength; slideX < slideLength; slideX += slideStep) {
// 在指定的范围内进行滑动窗口操作
// 在指定的范围内进行滑动窗口操作
float x_slide = 0;
x_slide = float(fromX + slideX);
float y_slide = (float)tlPoint.y;
// 计算当前滑动的x和y坐标
// 计算当前滑动的x和y坐标
int chineseWidth = int(maxrect.width);
int chineseHeight = int(maxrect.height);
// 获取中国字符的宽度和高度
// 获取中国字符的宽度和高度
Rect rect(Point2f(x_slide, y_slide), Size(chineseWidth, chineseHeight));
// 根据当前滑动的坐标和中国字符的尺寸创建一个矩形区域
// 根据当前滑动的坐标和中国字符的尺寸创建一个矩形区域
if (rect.tl().x < 0 || rect.tl().y < 0 || rect.br().x >= image.cols || rect.br().y >= image.rows)
continue;
// 检查这个矩形是否在图像内,如果不在,则跳过当前的循环迭代
// 检查这个矩形是否在图像内,如果不在,则跳过当前的循环迭代
Mat auxRoi = image(rect);
Mat grayChinese;
grayChinese.create(kGrayCharHeight, kGrayCharWidth, CV_8UC1);
// 创建一个灰度图像,尺寸为预设的中文字符的尺寸和通道数
// 创建一个灰度图像,尺寸为预设的中文字符的尺寸和通道数
resize(auxRoi, grayChinese, grayChinese.size(), 0, 0, INTER_LINEAR);
// 将提取的子图像调整为预设的中文字符的尺寸,并将其保存到灰度图像中
// 从图像中提取当前矩形区域的子图像
// 将提取的子图像调整为预设的中文字符的尺寸,并将其保存到灰度图像中
// 从图像中提取当前矩形区域的子图像
CCharacter charCandidateOstu;
charCandidateOstu.setCharacterPos(rect);
charCandidateOstu.setCharacterMat(grayChinese);
charCandidateOstu.setIsChinese(isChinese);
charCandidateVec.push_back(charCandidateOstu);// 将字符候选对象添加到向量中以备后续处理使用
// 创建一个字符候选对象,并设置其位置、图像和其他属性(这里指定为中文字符)
charCandidateVec.push_back(charCandidateOstu);// 将字符候选对象添加到向量中以备后续处理使用
// 创建一个字符候选对象,并设置其位置、图像和其他属性(这里指定为中文字符)
}
CharsIdentify::instance()->classifyChineseGray(charCandidateVec);
// 对所有的字符候选进行分类(这里假设是中文字符分类)
// 对所有的字符候选进行分类(这里假设是中文字符分类)
double overlapThresh = 0.1;
NMStoCharacter(charCandidateVec, overlapThresh);
// 对所有的字符候选进行非极大值抑制NMS以消除多余的字符候选区域
// 这里的阈值设置为0.1(根据实际情况可能需要进行调整)
// 对所有的字符候选进行非极大值抑制NMS以消除多余的字符候选区域
// 这里的阈值设置为0.1(根据实际情况可能需要进行调整)
if (charCandidateVec.size() >= 1) {
std::sort(charCandidateVec.begin(), charCandidateVec.end(),
[](const CCharacter& r1, const CCharacter& r2) {
@ -309,23 +309,23 @@ bool slideChineseGrayWindow(const Mat& image, Rect& mr, Mat& newRoi, Color plate
mr = charCandidateVec.at(0).getCharacterPos();
return true;
}
// 如果字符候选向量中至少有一个元素,则对它们进行排序,
// 并提取出得分最高的字符候选区域将其图像保存到newRoi中
// 并更新mr为最高得分字符候选的位置然后返回true否则返回false。
// 注意这里使用了一个lambda表达式作为排序函数
// 根据字符候选的得分进行降序排序。
// 如果字符候选向量中至少有一个元素,则对它们进行排序,
// 并提取出得分最高的字符候选区域将其图像保存到newRoi中
// 并更新mr为最高得分字符候选的位置然后返回true否则返回false。
// 注意这里使用了一个lambda表达式作为排序函数
// 根据字符候选的得分进行降序排序。
return false;
}
int CCharsSegment::charsSegment(Mat input, vector<Mat>& resultVec, Color color) {
if (!input.data) return 0x01;
//检查输入图像是否有数据
Color plateType = color;
Mat input_grey;
Mat input_grey; //存储灰度图像。
cvtColor(input, input_grey, CV_BGR2GRAY);
//将输入图像转换为灰度图像并保存到input_grey中。
if (0) {
imshow("plate", input_grey);
waitKey(0);
@ -336,6 +336,7 @@ int CCharsSegment::charsSegment(Mat input, vector<Mat>& resultVec, Color color)
img_threshold = input_grey.clone();
spatial_ostu(img_threshold, 8, 2, plateType);
//对图像进行Otsu阈值分割
if (0) {
imshow("plate", img_threshold);
@ -346,23 +347,28 @@ int CCharsSegment::charsSegment(Mat input, vector<Mat>& resultVec, Color color)
// remove liuding and hor lines
// also judge weather is plate use jump count
if (!clearLiuDing(img_threshold)) return 0x02;
//清除图像中的一些无效区域
Mat img_contours;
img_threshold.copyTo(img_contours);
//将img_threshold复制到img_contours中。
vector<vector<Point> > contours;
//定义一个二维向量contours用于保存轮廓信息。
findContours(img_contours,
contours, // a vector of contours
CV_RETR_EXTERNAL, // retrieve the external contours
CV_CHAIN_APPROX_NONE); // all pixels of each contours
//使用OpenCV的findContours函数查找图像中的轮廓。
// 这里指定了检索模式为外部轮廓,并且指定了轮廓的近似方法。
vector<vector<Point> >::iterator itc = contours.begin();
//定义一个迭代器itc并初始化为轮廓向量的起始位置
vector<Rect> vecRect;
while (itc != contours.end()) {
//定义一个向量vecRect用于保存每个轮廓的边界框。
while (itc != contours.end()) {//遍历轮廓向量
Rect mr = boundingRect(Mat(*itc));
//对于当前轮廓使用OpenCV的boundingRect函数计算其边界框
// 并保存到变量mr中。
Mat auxRoi(img_threshold, mr);
//根据当前轮廓的边界框,从图像中提取一个子图像。
if (verifyCharSizes(auxRoi)) vecRect.push_back(mr);
++itc;
}
@ -371,16 +377,18 @@ int CCharsSegment::charsSegment(Mat input, vector<Mat>& resultVec, Color color)
if (vecRect.size() == 0) return 0x03;
vector<Rect> sortedRect(vecRect);
//创建一个新的向量sortedRect并将vecRect的内容复制到这个新的向量中。
std::sort(sortedRect.begin(), sortedRect.end(),
[](const Rect& r1, const Rect& r2) { return r1.x < r2.x; });
//使用lambda函数将sortedRect按照x坐标从小到大进行排序。
size_t specIndex = 0;
specIndex = GetSpecificRect(sortedRect);
//获取特定的矩形
Rect chineseRect;
if (specIndex < sortedRect.size())
chineseRect = GetChineseRect(sortedRect[specIndex]);
//获取特定索引的矩形并存储到chineseRect
else
return 0x04;
@ -390,11 +398,13 @@ int CCharsSegment::charsSegment(Mat input, vector<Mat>& resultVec, Color color)
waitKey(0);
destroyWindow("plate");
}
//绘制矩形chineseRect在图像上并显示出来
//这部分代码永远不会被执行
vector<Rect> newSortedRect;
newSortedRect.push_back(chineseRect);
RebuildRect(sortedRect, newSortedRect, specIndex);
//对图像中的矩形区域进行重新构建或处理的。
if (newSortedRect.size() == 0) return 0x05;
bool useSlideWindow = true;
@ -402,12 +412,14 @@ int CCharsSegment::charsSegment(Mat input, vector<Mat>& resultVec, Color color)
//bool useAdapThreshold = CParams::instance()->getParam1b();
for (size_t i = 0; i < newSortedRect.size(); i++) {
//循环遍历新的排序矩形向量中的每一个矩形。
Rect mr = newSortedRect[i];
//获取当前矩形的坐标信息
// Mat auxRoi(img_threshold, mr);
Mat auxRoi(input_grey, mr);
//根据给定的图像和矩形区域创建ROI
Mat newRoi;
//第二个是用来存储处理后的图像的。
if (i == 0) {
if (useSlideWindow) {
float slideLengthRatio = 0.1f;
@ -431,15 +443,24 @@ int CCharsSegment::charsSegment(Mat input, vector<Mat>& resultVec, Color color)
else {
threshold(auxRoi, newRoi, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
}
//根据不同的颜色来执行不同的阈值处理操作,
// 然后对处理后的图像进行预处
newRoi = preprocessChar(newRoi);
}
if (0) {
if (i == 0) {
imshow("input_grey", input_grey);
// 使用imshow函数显示名为"input_grey"的窗口,并在其中显示图像"input_grey"。
// 用于创建和显示窗口。
waitKey(0);
//这是调用OpenCV的waitKey函数等待用户按键。
// 该函数的参数是0这意味着它将无限期地等待用户按键。
// 一旦用户按下键该函数将返回按下的键的ASCII值。
destroyWindow("input_grey");
//这个函数会销毁窗口并释放其内存。
}
if (i == 0) {
imshow("newRoi", newRoi);
@ -449,6 +470,9 @@ int CCharsSegment::charsSegment(Mat input, vector<Mat>& resultVec, Color color)
}
resultVec.push_back(newRoi);
// 将新的Roi添加到名为resultVec的向量中。
// 存储一系列元素。这里将新的Roi图像添加到该向量中。
}
return 0;
@ -457,44 +481,53 @@ int CCharsSegment::charsSegment(Mat input, vector<Mat>& resultVec, Color color)
int CCharsSegment::projectSegment(const Mat& input, Color color, vector<int>& out_indexs) {
if (!input.data) return 0x01;
Color plateType = color;
Mat input_grey;
cvtColor(input, input_grey, CV_BGR2GRAY);
SHOW_IMAGE(input_grey, 0);
Color plateType = color; // 将输入的颜色赋值给plateType
Mat input_grey; // 定义一个Mat对象用于存储灰度图像
cvtColor(input, input_grey, CV_BGR2GRAY); // 将输入图像转换为灰度图像
SHOW_IMAGE(input_grey, 0); // 显示灰度图像
Mat img_threshold;
img_threshold = input_grey.clone();
spatial_ostu(img_threshold, 8, 2, plateType);
SHOW_IMAGE(img_threshold, 0);
Mat img_threshold; // 定义一个Mat对象用于存储阈值化的图像
img_threshold = input_grey.clone(); // 将灰度图像复制到img_threshold中
spatial_ostu(img_threshold, 8, 2, plateType); // 对图像进行空间自适应阈值化处理,
SHOW_IMAGE(img_threshold, 0); // 显示阈值化后的图像
// remove liuding and hor lines
// also judge weather is plate use jump count
if (!clearLiuDing(img_threshold)) return 0x02;
SHOW_IMAGE(img_threshold, 0);
Mat vhist = ProjectedHistogram(img_threshold, VERTICAL, 0);
Mat showHist = showHistogram(vhist);
SHOW_IMAGE(showHist, 1);
vector<float> values;
vector<int> indexs;
int size = vhist.cols;
for (int i = 0; i < size; i++) {
float val = vhist.at<float>(i);
values.push_back(1.f - val);
// 进行一些形状的清理和判断是否为车牌的跳跃计数
if (!clearLiuDing(img_threshold)) return 0x02;
// 使用clearLiuDing函数清理图像中的多余部分
SHOW_IMAGE(img_threshold, 0);
// 显示清理后的图像
Mat vhist = ProjectedHistogram(img_threshold, VERTICAL, 0);
// 对图像进行垂直直方图投影结果保存在vhist中
Mat showHist = showHistogram(vhist);
// 显示直方图,但是此行代码在后续的代码中并没有被使用
SHOW_IMAGE(showHist, 1); // 显示直方图
vector<float> values; // 定义一个向量用于存储每个像素到直方图最大值的距离
vector<int> indexs; // 定义一个向量用于存储非最大值的索引
int size = vhist.cols; // 获取直方图列的数量,作为后续循环的次数
for (int i = 0; i < size; i++) { // 遍历每个像素在直方图中的位置
float val = vhist.at<float>(i); // 获取当前像素在直方图中的值
values.push_back(1.f - val);
// 计算当前像素到直方图最大值的距离并添加到values向量中
}
Mat img_test = img_threshold.clone();
NMSfor1D<float>(values, indexs);
out_indexs.resize(size);
for (int j = 0; j < size; j++)
out_indexs.at(j) = 0;
for (int i = 0; i < size; i++) {
float val = vhist.at<float>(i);
if (indexs.at(i) && val < 0.1f) {
out_indexs.at(i) = 1;
Mat img_test = img_threshold.clone(); // 复制阈值化后的图像到一个新的Mat对象中
NMSfor1D<float>(values, indexs); // 对values向量进行非极大值抑制NMS
out_indexs.resize(size); // 重新调整out_indexs向量的尺寸为后续的赋值做准备
for (int j = 0; j < size; j++)
out_indexs.at(j) = 0; // 将out_indexs向量中的所有值初始化为0
for (int i = 0; i < size; i++) { // 遍历每个像素在直方图中的位置
float val = vhist.at<float>(i); // 获取当前像素在直方图中的值
if (indexs.at(i) && val < 0.1f) { // 如果当前像素是非极大值且其值小于0.1f
out_indexs.at(i) = 1; // 则将out_indexs向量中的相应位置设置为1
for (int j = 0; j < img_test.rows; j++) {
img_test.at<char>(j, i) = (char)255;
// 将该位置的像素值设置为255白色
}
}
}
@ -506,72 +539,106 @@ int CCharsSegment::projectSegment(const Mat& input, Color color, vector<int>& ou
bool verifyCharRectSizes(Rect r) {
// Char sizes 45x90
float aspect = 45.0f / 90.0f;
//定义一个变量aspect其值为0.5,表示字符的预期宽高比。
float charAspect = (float)r.width / (float)r.height;
//计算输入矩形的宽高比并将其存储在变量charAspect中。
float error = 0.5f;
//表示允许的宽高比误差。
float minHeight = kPlateResizeHeight * 0.5f;
//表示矩形的最小高度
float maxHeight = kPlateResizeHeight * 1.f;
//表示矩形的最大高度
// We have a different aspect ratio for number 1, and it can be ~0.2
float minAspect = 0.10f; //0.2f;
//表示允许的最小宽高比
float maxAspect = 0.85f; // aspect + aspect * error; //0.8f;
//表示允许的最大宽高比
int ch = r.tl().y + r.height / 2;
//计算矩形的中心点纵坐标
int min_ch = int(kPlateResizeHeight * 0.3f);
//表示字符中心点的最小纵坐标
int max_ch = int(kPlateResizeHeight * 0.7f);
//表示字符中心点的最大纵坐标
if (ch > max_ch || ch < min_ch)
return false;
//如果字符中心点的纵坐标超出允许的范围则返回false。
float h = (float)r.height;
//将输入矩形的height转换为浮点数
if (h > maxHeight || h < minHeight)
return false;
//矩形的height超出允许的范围则返回false。
if (charAspect < minAspect || charAspect > maxAspect)
return false;
//如果矩形的宽高比超出允许的范围则返回false。
return true;
}
Mat preprocessCharMat(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; // 将传入的字符大小参数保存为charSize
Mat transformMat = Mat::eye(2, 3, CV_32F);
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);
// 创建一个2x3的单位矩阵作为图像变换的转换矩阵
Mat transformMat = Mat::eye(2, 3, CV_32F);
Mat warpImage(m, m, in.type());
warpAffine(in, warpImage, transformMat, warpImage.size(), INTER_LINEAR,
BORDER_CONSTANT, Scalar(0));
// 找出输入图像的最大尺寸(高或宽)
int m = max(w, h);
Mat out;
cv::resize(warpImage, out, Size(charSize, charSize));
// 根据最大尺寸,设置转换矩阵的最后两行(平移参数)
// 这样做的目的是将输入图像的中心移动到变换后的图像的中心
transformMat.at<float>(0, 2) = float(m / 2 - w / 2);
transformMat.at<float>(1, 2) = float(m / 2 - h / 2);
return out;
// 创建一个与输入图像相同类型和大小的输出图像
Mat warpImage(m, m, in.type());
// 使用上面定义的转换矩阵对输入图像执行仿射变换warpAffine
warpAffine(in, warpImage, transformMat, warpImage.size(), INTER_LINEAR,
BORDER_CONSTANT, Scalar(0));
// 根据指定的字符大小,调整变换后的图像大小
Mat out;
cv::resize(warpImage, out, Size(charSize, charSize));
// 返回处理后的图像
return out;
}
Mat clearLiuDingAndBorder(const Mat& grayImage, Color color) {
SHOW_IMAGE(grayImage, 0);
//显示输入的灰度图像
Mat img_threshold;
//定义一个Mat对象img_threshold用于存放阈值化后的图像。
img_threshold = grayImage.clone();
//将输入的灰度图像复制到img_threshold中。
spatial_ostu(img_threshold, 1, 1, color);
//对图像进行空间自适应阈值化spatial OSTU
// 这里的参数1和1可能表示的是高斯核的大小而color则表示颜色空间。
clearLiuDing(img_threshold);
//为了去除刘定噪声
Rect cropRect;
//存放裁剪区域的坐标。
clearBorder(img_threshold, cropRect);
//为了清除图像的边框
Mat cropedGrayImage;
//存放裁剪后的灰度图像
resize(grayImage(cropRect), cropedGrayImage, Size(kPlateResizeWidth, kPlateResizeHeight));
//对裁剪后的灰度图像进行大小调整。
SHOW_IMAGE(cropedGrayImage, 0);
//显示调整大小后的灰度图像。
return cropedGrayImage;
//返回处理后的灰度图像。
}
void NMStoCharacterByRatio(std::vector<CCharacter> &inVec, double overlap, const Rect groundRect) {
// rechange the score
for (auto& character : inVec) {
double score = character.getCharacterScore();
for (auto& character : inVec) {//对inVec中的每个字符进行操作
double score = character.getCharacterScore();//获取当前字符的得分
//cout << "score:" << score << endl;
Rect rect = character.getCharacterPos();
Rect rect = character.getCharacterPos();//获取当前字符的位置信息
int w = rect.width;
int h = rect.height;
int gw = groundRect.width;
@ -581,13 +648,14 @@ void NMStoCharacterByRatio(std::vector<CCharacter> &inVec, double overlap, const
int w_diff = abs(w - gw);
int h_diff = abs(h - gh);
//计算当前字符框与地面真实框的IOU交并比
//float w_ratio = (float)w / (float)gw;
//float h_ratio = (float)h / (float)gh;
float w_ratio = 1 - (float)w_diff / (float)gw;
float h_ratio = 1 - (float)h_diff / (float)gh;
//分别表示字符框的宽度和高度与地面真实框的宽高比。
// 这两个比例会影响最终的权重得分。
float a = 0.5f;
float b = 0.5f;
//cout << "str:" << character.getCharacterStr() << endl;
@ -595,17 +663,20 @@ void NMStoCharacterByRatio(std::vector<CCharacter> &inVec, double overlap, const
if ("1" == character.getCharacterStr()) {
a = 0.3f; //0.2f;
b = 0.7f; //0.8f;
//如果字符是'1'那么会对其IOU进行一个调整。
}
float c = 0.1f;
//float weighted_score = a * (float)score + b * w_ratio + c * h_ratio;
float weighted_score = a * (float)score + b * w_ratio + c * h_ratio;
//计算权重得分
SHOW_IMAGE(character.getCharacterMat(), 0);
character.setCharacterScore((double)weighted_score);
//设置新的权重得分。
//cout << "weighted_score:" << character.getCharacterScore() << endl;
}
std::sort(inVec.begin(), inVec.end());
//对vector进行排序以便后续的NMS操作。
std::vector<CCharacter>::iterator it = inVec.begin();
for (; it != inVec.end(); ++it) {
CCharacter charSrc = *it;
@ -625,20 +696,26 @@ void NMStoCharacterByRatio(std::vector<CCharacter> &inVec, double overlap, const
++itc;
}
}
}
}//如果它与后面的字符的IOU大于预设的重叠阈值那么就将后面的字符从vector中删除。
//这样可以保证最终vector中每一个字符都不与其它字符有大的重叠
}
int getNearestIndex(Point center, const vector<Point>& groundCenters) {
int gc_size = int(groundCenters.size());
//获取 groundCenters 向量的大小,并将其转换为整数类型并存储在变量 gc_size 中
int index = 0;
//用于存储最接近 center 的地心坐标的索引
int min_length = INT_MAX;
for (int p = 0; p < gc_size; p++) {
Point gc_point = groundCenters.at(p);
//用于存储与 center 距离最短的地心坐标的距离的平方
for (int p = 0; p < gc_size; p++) {//遍历所有地心坐标
Point gc_point = groundCenters.at(p);//获取索引为 p 的地心坐标,并将其存储在变量 gc_point 中。
int length_square = (gc_point.x - center.x) * (gc_point.x - center.x) +
(gc_point.y - center.y) * (gc_point.y - center.y);
//计算当前地心坐标 gc_point 与 center 之间的距离的平方。
//int length_square = abs(gc_point.x - center.x);
if (length_square < min_length) {
min_length = length_square;
//当前地心坐标与 center 的距离的平方设置为新的最小距离。
index = p;
}
}

@ -3,11 +3,11 @@
namespace easypr {
Kv::Kv() { }
Kv::Kv() { } //Kv类的构造函数
void Kv::load(const std::string &file) {
this->clear();
std::ifstream reader(file);
void Kv::load(const std::string &file) { //一个成员函数,用于从指定的文件中加载键值对
this->clear();//清空当前的键值对
std::ifstream reader(file);//打开文件
assert(reader);
if (reader.is_open()) {
@ -15,17 +15,17 @@ void Kv::load(const std::string &file) {
std::string line;
std::getline(reader, line);
if (line.empty()) continue;
const auto parse = [](const std::string &str) {
//打开文件并读取每一行
const auto parse = [](const std::string &str) {//解析键和值
std::string tmp, key, value;
for (size_t i = 0, len = str.length(); i < len; ++i) {
const char ch = str[i];
if (ch == ' ') {
if (ch == ' ') {//遇到空格时
if (i > 0 && str[i - 1] != ' ' && key.empty()) {
key = tmp;
tmp.clear();
}
}
}//将之前的字符串作为键,空格后的字符串作为值
else {
tmp.push_back(ch);
}
@ -37,45 +37,45 @@ void Kv::load(const std::string &file) {
};
auto kv = parse(line);
this->add(kv.first, kv.second);
this->add(kv.first, kv.second);//解析出的键值对添加到存储中
}
reader.close();
reader.close();//关闭文件
}
}
std::string Kv::get(const std::string &key) {
std::string Kv::get(const std::string &key) {//是一个成员函数,用于获取给定键的值
if (data_.find(key) == data_.end()) {
std::cerr << "[Kv] cannot find " << key << std::endl;
return "";
std::cerr << "[Kv] cannot find " << key << std::endl;//如果键不存在,它会打印一个错误消息
return "";//返回一个空字符串。
}
return data_.at(key);
return data_.at(key);//获取给定键的值
}
void Kv::add(const std::string &key, const std::string &value) {
if (data_.find(key) != data_.end()) {
void Kv::add(const std::string &key, const std::string &value) {//是一个成员函数,用于添加一个键值对
if (data_.find(key) != data_.end()) {//如果键已经存在
fprintf(stderr,
"[Kv] find duplicate: %s = %s , ignore\n",
key.c_str(),
value.c_str());
value.c_str());//会打印一个错误消息并忽略这个添加操作
} else {
std::string v(value);
#ifdef OS_WINDOWS
v = utils::utf8_to_gbk(value.c_str());
#ifdef OS_WINDOWS//如果在Windows操作系统上
v = utils::utf8_to_gbk(value.c_str()); //将值从UTF-8编码转换为GBK编码。
#endif
data_[key] = v;
data_[key] = v;//添加一个键值对
}
}
void Kv::remove(const std::string &key) {
if (data_.find(key) == data_.end()) {
std::cerr << "[Kv] cannot find " << key << std::endl;
void Kv::remove(const std::string &key) {//是一个成员函数,用于删除一个键值对。
if (data_.find(key) == data_.end()) {//如果键不存在
std::cerr << "[Kv] cannot find " << key << std::endl;//打印一个错误消息。
return;
}
data_.erase(key);
data_.erase(key);//删除一个键值对。
}
void Kv::clear() {
data_.clear();
void Kv::clear() {//是一个成员函数,用于清空所有的键值对。
data_.clear();//用于清空所有的键值对。
}
}

Loading…
Cancel
Save