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