张腾远的更新 #25

Open
ppqsjm7lb wants to merge 4 commits from zhangtengyuan_branch into develop

@ -0,0 +1 @@
123123

@ -18,7 +18,6 @@ CharsIdentify* CharsIdentify::instance() {
return instance_;
}
// 主要用于加载和管理预训练的神经网络模型,用于字符识别
CharsIdentify::CharsIdentify() {
LOAD_ANN_MODEL(ann_, kDefaultAnnPath);
LOAD_ANN_MODEL(annChinese_, kChineseAnnPath);
@ -59,24 +58,18 @@ void CharsIdentify::LoadChineseMapping(std::string path) {
kv_->load(path);
}
// 对输入的特征行进行预测,并识别出最可能的字符。
void CharsIdentify::classify(cv::Mat featureRows, std::vector<int>& out_maxIndexs,
std::vector<float>& out_maxVals, std::vector<bool> isChineseVec){
// 获取特征行的行数。
int rowNum = featureRows.rows;
// 创建一个新的矩阵output大小为特征行的行数rowNum乘以总的字符数量
cv::Mat output(rowNum, kCharsTotalNumber, CV_32FC1);
// 使用预先训练好的模型ann_对输入的特征行进行预测结果保存在output矩阵中
ann_->predict(featureRows, output);
// 循环遍历每一行输出:
// 对于每一行,首先获取该行的预测结果
for (int output_index = 0; output_index < rowNum; output_index++) {
Mat output_row = output.row(output_index);
int result = 0;
float maxVal = -2.f;
bool isChinses = isChineseVec[output_index];
// 如果该行不是中文字符由isChineseVec向量确定
// 则遍历前kCharactersNumber个预测结果找出值最大的那个并记录其索引和值。
if (!isChinses) {
result = 0;
for (int j = 0; j < kCharactersNumber; j++) {
@ -88,8 +81,6 @@ void CharsIdentify::classify(cv::Mat featureRows, std::vector<int>& out_maxIndex
}
}
}
// 如果该行是中文字符,
// 则从kCharactersNumber开始遍历后面的预测结果找出值最大的那个并记录其索引和值。
else {
result = kCharactersNumber;
for (int j = kCharactersNumber; j < kCharsTotalNumber; j++) {
@ -101,20 +92,18 @@ void CharsIdentify::classify(cv::Mat featureRows, std::vector<int>& out_maxIndex
}
}
}
// 将记录的最大索引和最大值分别赋值给out_maxIndexs和out_maxVals的相应位置
out_maxIndexs[output_index] = result;
out_maxVals[output_index] = maxVal;
}
}
// 接受一个CCharacter类型的向量charVec并对每个字符进行分类。
void CharsIdentify::classify(std::vector<CCharacter>& charVec){
size_t charVecSize = charVec.size();
if (charVecSize == 0)
return;
// 创建一个名为featureRows的Mat对象并通过循环将每个字符的特征提取出来并添加到featureRows中。
Mat featureRows;
for (size_t index = 0; index < charVecSize; index++) {
Mat charInput = charVec[index].getCharacterMat();
@ -122,14 +111,9 @@ void CharsIdentify::classify(std::vector<CCharacter>& charVec){
featureRows.push_back(feature);
}
// 创建一个输出矩阵output并使用预先训练好的模型ann_对特征进行预测。
cv::Mat output(charVecSize, kCharsTotalNumber, CV_32FC1);
ann_->predict(featureRows, output);
// 遍历每个输出,对于每个输出,首先获取对应的字符(通过索引),
// 然后获取该字符的预测结果行(通过索引)。然后,函数检查该字符是否为中文字符,
// 如果不是,它就在循环中找出值最大的预测结果,并记录其索引和值。
// 最后,函数根据这个最大值和索引确定预测的字符,并将其作为标签。
for (size_t output_index = 0; output_index < charVecSize; output_index++) {
CCharacter& character = charVec[output_index];
Mat output_row = output.row(output_index);
@ -151,8 +135,6 @@ void CharsIdentify::classify(std::vector<CCharacter>& charVec){
}
label = std::make_pair(kChars[result], kChars[result]).second;
}
// 如果字符是中文字符,函数则从预测结果的后面部分开始查找最大值,并记录其索引和值。
// 然后函数根据这个最大值和索引确定预测的字符并通过键值对kv_查找对应的省份将字符和省份作为标签。
else {
result = kCharactersNumber;
for (int j = kCharactersNumber; j < kCharsTotalNumber; j++) {
@ -170,33 +152,28 @@ void CharsIdentify::classify(std::vector<CCharacter>& charVec){
}
/*std::cout << "result:" << result << std::endl;
std::cout << "maxVal:" << maxVal << std::endl;*/
// 函数将预测的最大值和标签分别设置到对应字符对象的得分和字符串属性中。
character.setCharacterScore(maxVal);
character.setCharacterStr(label);
}
}
// 对输入的中文字符进行分类
void CharsIdentify::classifyChineseGray(std::vector<CCharacter>& charVec){
size_t charVecSize = charVec.size();
if (charVecSize == 0)
return;
Mat featureRows;
// 通过循环提取每个字符的特征并存储在featureRows中
for (size_t index = 0; index < charVecSize; index++) {
Mat charInput = charVec[index].getCharacterMat();
cv::Mat feature;
extractFeature(charInput, feature);
featureRows.push_back(feature);
}
// 创建一个输出矩阵output然后使用预先训练好的模型annGray_对特征进行预测并将结果存储在output中
cv::Mat output(charVecSize, kChineseNumber, CV_32FC1);
annGray_->predict(featureRows, output);
// 对于输出矩阵中的每一行(每个字符的预测结果),
// 如果该字符是中文字符,函数会从预测结果的后面部分开始查找最大值,并记录其索引和值。
for (size_t output_index = 0; output_index < charVecSize; output_index++) {
CCharacter& character = charVec[output_index];
Mat output_row = output.row(output_index);
@ -221,8 +198,6 @@ void CharsIdentify::classifyChineseGray(std::vector<CCharacter>& charVec){
isChinese = false;
}
// 根据这个最大值和索引确定预测的字符。
// 这是通过查找kChars数组实现的其中kChars可能是一个预定义的字符集。
auto index = result + kCharsTotalNumber - kChineseNumber;
const char* key = kChars[index];
std::string s = key;
@ -231,15 +206,12 @@ void CharsIdentify::classifyChineseGray(std::vector<CCharacter>& charVec){
/*std::cout << "result:" << result << std::endl;
std::cout << "maxVal:" << maxVal << std::endl;*/
// 将预测的最大值、预测的字符以及对应的省份作为标签,
// 分别设置到对应字符对象的得分、字符串属性和是否为中文字符属性中
character.setCharacterScore(maxVal);
character.setCharacterStr(province);
character.setIsChinese(isChinese);
}
}
// 使用OpenCV库和神经网络进行中文字符识别
void CharsIdentify::classifyChinese(std::vector<CCharacter>& charVec){
size_t charVecSize = charVec.size();
@ -247,20 +219,15 @@ void CharsIdentify::classifyChinese(std::vector<CCharacter>& charVec){
return;
Mat featureRows;
// 通过循环遍历每个字符提取其特征并将其存储在featureRows中。
// 这里charFeatures函数被用于提取每个字符的特性kChineseSize可能是一个预定义的特性大小。
for (size_t index = 0; index < charVecSize; index++) {
Mat charInput = charVec[index].getCharacterMat();
Mat feature = charFeatures(charInput, kChineseSize);
featureRows.push_back(feature);
}
// 创建一个输出矩阵output并使用预先训练好的模型annChinese_对特征进行预测。预测结果存储在output中。
cv::Mat output(charVecSize, kChineseNumber, CV_32FC1);
annChinese_->predict(featureRows, output);
// 遍历每个预测结果,并对每个结果进行处理。对于每个预测结果,函数查找最大值及其索引。
// 如果最大值小于或等于-1则将最大值设置为0并将result设置为0同时将isChinese设置为false。
for (size_t output_index = 0; output_index < charVecSize; output_index++) {
CCharacter& character = charVec[output_index];
Mat output_row = output.row(output_index);
@ -285,8 +252,6 @@ void CharsIdentify::classifyChinese(std::vector<CCharacter>& charVec){
isChinese = false;
}
// 计算索引值并使用该索引从kChars数组中获取对应的字符。
// 同时通过键值对kv_查找与该字符对应的省份。
auto index = result + kCharsTotalNumber - kChineseNumber;
const char* key = kChars[index];
std::string s = key;
@ -295,24 +260,18 @@ void CharsIdentify::classifyChinese(std::vector<CCharacter>& charVec){
/*std::cout << "result:" << result << std::endl;
std::cout << "maxVal:" << maxVal << std::endl;*/
// 将最大值、省份和isChinese作为标签分别设置到对应字符对象的得分、字符串属性和是否为中文字符属性中。
character.setCharacterScore(maxVal);
character.setCharacterStr(province);
character.setIsChinese(isChinese);
}
}
// 对输入的图像数据进行分类
int CharsIdentify::classify(cv::Mat f, float& maxVal, bool isChinses, bool isAlphabet){
int result = 0;
// 调用预先训练好的模型ann_进行预测并将预测结果存储在output变量中。
cv::Mat output(1, kCharsTotalNumber, CV_32FC1);
ann_->predict(f, output);
// 查找最大值及其索引。如果图像数据不是中文,则会检查它是否是字母。
// 如果它是字母那么函数将只查找字母范围内的值从10开始对应于'A')。
// 否则,它将查找所有字符范围内的值。如果图像数据是中文,则函数将查找中文字符范围内的值
maxVal = -2.f;
if (!isChinses) {
if (!isAlphabet) {
@ -350,27 +309,20 @@ int CharsIdentify::classify(cv::Mat f, float& maxVal, bool isChinses, bool isAlp
}
}
}
// 返回索引值result该值是预测的字符在预先定义的字符集kChars中的索引。
// 同时它也将最大值maxVal和对应的索引result设置到输入的float引用maxVal中以便调用者可以访问这些值。
//std::cout << "maxVal:" << maxVal << std::endl;
return result;
}
// 根据输入的图像数据判断它是否是一个字符(特别是中文字符)
bool CharsIdentify::isCharacter(cv::Mat input, std::string& label, float& maxVal, bool isChinese) {
// 调用charFeatures函数提取输入图像的特征并存储在feature变量中。
// 然后它调用classify函数对特征进行分类得到一个索引值index
cv::Mat feature = charFeatures(input, kPredictSize);
auto index = static_cast<int>(classify(feature, maxVal, isChinese));
if (isChinese) {
//std::cout << "maxVal:" << maxVal << std::endl;
}
float chineseMaxThresh = 0.2f;
// 检查预测的最大值maxVal是否大于等于0.9或者如果输入的字符是中文且最大值大于等于chineseMaxThresh这个阈值被设置为0.2)。
// 如果满足这些条件之一函数将检查索引index是否小于kCharactersNumber这可能是一个预定义的字符集大小
// 如果是则将索引对应的字符作为标签否则使用键值对kv_查找索引对应的省份并将该索引对应的字符和省份作为标签。
// 最后函数返回true表示输入的图像是一个字符否则返回false
if (maxVal >= 0.9 || (isChinese && maxVal >= chineseMaxThresh)) {
if (index < kCharactersNumber) {
label = std::make_pair(kChars[index], kChars[index]).second;
@ -386,10 +338,8 @@ bool CharsIdentify::isCharacter(cv::Mat input, std::string& label, float& maxVal
else
return false;
}
// 用于识别输入的图像数据是否是一个中文字符。
std::pair<std::string, std::string> CharsIdentify::identifyChinese(cv::Mat input, float& out, bool& isChinese) {
// 调用charFeatures函数提取输入图像的特征并存储在feature变量中。
// 然后它调用预先训练好的模型annChinese_进行预测并将预测结果存储在output变量中。
cv::Mat feature = charFeatures(input, kChineseSize);
float maxVal = -2;
int result = 0;
@ -397,8 +347,6 @@ std::pair<std::string, std::string> CharsIdentify::identifyChinese(cv::Mat input
cv::Mat output(1, kChineseNumber, CV_32FC1);
annChinese_->predict(feature, output);
// 遍历输出数组,找到最大的值及其索引。
// 如果最大值大于0.9则将isChinese设置为true表示输入的字符可能是中文。
for (int j = 0; j < kChineseNumber; j++) {
float val = output.at<float>(j);
//std::cout << "j:" << j << "val:" << val << std::endl;
@ -409,8 +357,6 @@ std::pair<std::string, std::string> CharsIdentify::identifyChinese(cv::Mat input
}
// no match
// 如果索引值为-1即没有匹配的字符
// 则将result设置为0maxVal设置为0并将isChinese设置为false表示输入的字符不是中文。
if (-1 == result) {
result = 0;
maxVal = 0;
@ -419,7 +365,7 @@ std::pair<std::string, std::string> CharsIdentify::identifyChinese(cv::Mat input
else if (maxVal > 0.9){
isChinese = true;
}
// 通过索引值获取字符的标签和省份并将最大值保存到out中。函数返回一个由字符标签和省份组成的pair。
auto index = result + kCharsTotalNumber - kChineseNumber;
const char* key = kChars[index];
std::string s = key;
@ -428,18 +374,15 @@ std::pair<std::string, std::string> CharsIdentify::identifyChinese(cv::Mat input
return std::make_pair(s, province);
}
// 从输入的图像(可能是一个灰度图像)中识别出可能的中文字符。
std::pair<std::string, std::string> CharsIdentify::identifyChineseGray(cv::Mat input, float& out, bool& isChinese) {
cv::Mat feature;
// 通过extractFeature函数提取输入图像的特征并将特征保存在feature变量中。
// 然后它使用预先训练好的模型annGray_进行预测并将预测结果存储在output变量中。
extractFeature(input, feature);
float maxVal = -2;
int result = 0;
cv::Mat output(1, kChineseNumber, CV_32FC1);
annGray_->predict(feature, output);
// 遍历输出数组,找到最大的值及其索引。
// 如果最大值大于0.9则将isChinese设置为true表示输入的字符可能是中文。
for (int j = 0; j < kChineseNumber; j++) {
float val = output.at<float>(j);
//std::cout << "j:" << j << "val:" << val << std::endl;
@ -449,8 +392,6 @@ std::pair<std::string, std::string> CharsIdentify::identifyChineseGray(cv::Mat i
}
}
// no match
// 如果索引值为-1即没有匹配的字符
// 则将result设置为0maxVal设置为0并将isChinese设置为false表示输入的字符不是中文
if (-1 == result) {
result = 0;
maxVal = 0;
@ -458,7 +399,6 @@ std::pair<std::string, std::string> CharsIdentify::identifyChineseGray(cv::Mat i
} else if (maxVal > 0.9){
isChinese = true;
}
// 通过索引值获取字符的标签和省份并将最大值保存到out中。函数返回一个由字符标签和省份组成的pair。
auto index = result + kCharsTotalNumber - kChineseNumber;
const char* key = kChars[index];
std::string s = key;
@ -467,15 +407,11 @@ std::pair<std::string, std::string> CharsIdentify::identifyChineseGray(cv::Mat i
return std::make_pair(s, province);
}
// 用于识别输入的图像数据是否是一个字符。
std::pair<std::string, std::string> CharsIdentify::identify(cv::Mat input, bool isChinese, bool isAlphabet) {
// 过调用charFeatures函数提取输入图像的特征并存储在feature变量中。
// 然后它调用预先训练好的模型classify进行预测并将预测结果存储在index变量中。
cv::Mat feature = charFeatures(input, kPredictSize);
float maxVal = -2;
auto index = static_cast<int>(classify(feature, maxVal, isChinese, isAlphabet));
// 检查索引值index是否小于字符集大小kCharactersNumber。如果是则返回由相同字符组成的pair
// 否则获取索引对应的字符作为键并使用键值对kv_查找对应的省份。
if (index < kCharactersNumber) {
return std::make_pair(kChars[index], kChars[index]);
}
@ -487,14 +423,10 @@ std::pair<std::string, std::string> CharsIdentify::identify(cv::Mat input, bool
}
}
// 用于处理一组输入的图像数据并识别出对应的字符和省份。
// 函数参数包括输入图像数据inputs输出结果outputs以及一个布尔值向量isChineseVec
int CharsIdentify::identify(std::vector<cv::Mat> inputs, std::vector<std::pair<std::string, std::string>>& outputs,
std::vector<bool> isChineseVec) {
// featureRows创建一个空的Mat对象。它将被用于存储所有输入图像的特征。
Mat featureRows;
size_t input_size = inputs.size();
// 每一张图像提取特征并将这些特征添加到featureRows中。
for (size_t i = 0; i < input_size; i++) {
Mat input = inputs[i];
cv::Mat feature = charFeatures(input, kPredictSize);
@ -503,13 +435,8 @@ int CharsIdentify::identify(std::vector<cv::Mat> inputs, std::vector<std::pair<s
std::vector<int> maxIndexs;
std::vector<float> maxVals;
// 调用classify函数输入特征矩阵featureRows并返回最大值的索引maxIndexs和最大值maxVals
// 同时根据这些最大值更新isChineseVec中的对应元素。
classify(featureRows, maxIndexs, maxVals, isChineseVec);
// 遍历所有的输入图像对于每一张图像根据其对应的最大值索引构造一个输出对并存储在outputs中。
// 如果索引小于字符集大小kCharactersNumber则输出对由相同字符组成
// 否则获取索引对应的字符作为键并使用键值对kv_查找对应的省份。
for (size_t row_index = 0; row_index < input_size; row_index++) {
int index = maxIndexs[row_index];
if (index < kCharactersNumber) {

@ -6,29 +6,43 @@
namespace easypr {
CCharsRecognise::CCharsRecognise() { m_charsSegment = new CCharsSegment(); }
//定义了CCharsRecognise类的构造函数创建一个新的CCharsSegment类的实例并将其地址赋值给成员变量m_charsSegment
CCharsRecognise::~CCharsRecognise() { SAFE_RELEASE(m_charsSegment); }
//定义了CCharsRecognise类的析构函数
//调用了SAFE_RELEASE宏来释放即删除并置空m_charsSegment指针指向的CCharsSegment对象
int CCharsRecognise::charsRecognise(Mat plate, std::string& plateLicense) {
//车牌字符识别函数接收两个参数一个是Mat类型的plate它是需要进行识别的车牌图像
//另一个是std::string&类型的plateLicense它是一个引用用于存储识别出来的车牌号码。
std::vector<Mat> matChars;
//matChars用于存储经过字符分割后的单个字符图像。
int result = m_charsSegment->charsSegment(plate, matChars);
//调用m_charsSegment对象的charsSegment函数进行字符分割
//将分割后的字符存储在matChars中。charsSegment函数的返回值存储在result变量中。
if (result == 0) {
//result = 0则表示字符分割成功接着进行车牌号码的识别
int num = matChars.size();
for (int j = 0; j < num; j++)
{
Mat charMat = matChars.at(j);
//循环遍历matChars中的每个字符图像
bool isChinses = false;
float maxVal = 0;
if (j == 0) {
bool judge = true;
isChinses = true;
//第一个字符,则认为是中文汉字
auto character = CharsIdentify::instance()->identifyChinese(charMat, maxVal, judge);
plateLicense.append(character.second);
//调用CharsIdentify::instance()->identifyChinese函数进行识别并将识别结果追加到plateLicense中
}
else {
isChinses = false;
//如果不是第一个字符,则认为不是中文汉字,
auto character = CharsIdentify::instance()->identify(charMat, isChinses);
plateLicense.append(character.second);
//调用CharsIdentify::instance()->identify函数进行识别并将识别结果追加到plateLicense中。
}
}
@ -36,20 +50,27 @@ int CCharsRecognise::charsRecognise(Mat plate, std::string& plateLicense) {
if (plateLicense.size() < 7) {
return -1;
}
// 检查plateLicense的长度是否小于7如果小于7则返回 - 1表示车牌号码识别失败。否则返回result表示车牌号码识别成功。
return result;
}
int CCharsRecognise::charsRecognise(CPlate& plate, std::string& plateLicense) {
//同样是用来识别车牌的函数数接收两个参数一个是CPlate&类型的plate它是需要进行识别的车牌对象
// 另一个是std::string&类型的plateLicense它是一个引用用于存储识别出来的车牌号码
std::vector<Mat> matChars;
std::vector<Mat> grayChars;
//matChars用于存储经过字符分割后的单个字符图像grayChars用于存储灰度图像。
Mat plateMat = plate.getPlateMat();
//调用plate.getPlateMat()获取车牌图像并将其存储在plateMat中。
if (0) writeTempImage(plateMat, "plateMat/plate");
Color color;
if (plate.getPlateLocateType() == CMSER) {
color = plate.getPlateColor();
}
//选择不同的方式来获取车牌颜色。如果车牌定位类型为CMSER则直接使用plate.getPlateColor()获取车牌颜色;
//否则从车牌图像中截取一部分区域并调用getPlateType()函数获取车牌类型。
else {
int w = plateMat.cols;
int h = plateMat.rows;
@ -58,35 +79,42 @@ int CCharsRecognise::charsRecognise(CPlate& plate, std::string& plateLicense) {
}
int result = m_charsSegment->charsSegmentUsingOSTU(plateMat, matChars, grayChars, color);
//调用charsSegmentUsingOSTU方法对输入的车牌图像进行字符分割
if (result == 0) {
int num = matChars.size();
for (int j = 0; j < num; j++)
{
//for循环遍历每一个字符图像
Mat charMat = matChars.at(j);
Mat grayChar = grayChars.at(j);
//通过matChars.at(j)和grayChars.at(j)获取第j个字符图像和其灰度图像
if (color != Color::BLUE)
grayChar = 255 - grayChar;
//如果车牌颜色不是蓝色,则对灰度图像进行操作
bool isChinses = false;
std::pair<std::string, std::string> character;
float maxVal;
if (0 == j) {
isChinses = true;
bool judge = true;
//设定第一个字符为中文字符
character = CharsIdentify::instance()->identifyChineseGray(grayChar, maxVal, judge);
plateLicense.append(character.second);
//使用CharsIdentify::instance()->identifyChineseGray方法识别灰度图像grayChar
// 并将结果存储在character对中。
// set plate chinese mat and str
plate.setChineseMat(grayChar);
plate.setChineseKey(character.first);
//设置车牌的中文图像和字符串。
if (0) writeTempImage(grayChar, "char_data/" + character.first + "/chars_");
}
else if (1 == j) {
isChinses = false;
bool isAbc = true;
character = CharsIdentify::instance()->identify(charMat, isChinses, isAbc);
//使用CharsIdentify::instance()->identify方法识别字符图像charMat
plateLicense.append(character.second);
//将识别的第二个字符串添加到plateLicense
}
else {
isChinses = false;
@ -96,17 +124,21 @@ int CCharsRecognise::charsRecognise(CPlate& plate, std::string& plateLicense) {
}
CCharacter charResult;
//创建CCharacter对象并设置其图像和字符串。
charResult.setCharacterMat(charMat);
charResult.setCharacterGrayMat(grayChar);
if (isChinses)
charResult.setCharacterStr(character.first);
else
charResult.setCharacterStr(character.second);
//如果isChinses为true则使用识别的第一个字符串作为字符的字符串否则使用识别的第二个字符串。
plate.addReutCharacter(charResult);
//字符结果添加到车牌对象中。
}
if (plateLicense.size() < 7) {
return -1;
//最后如果识别的字符数量少于7个函数返回 - 1否则返回result
}
}

@ -9,4 +9,5 @@ namespace easypr {
}
return instance_;
}
}/*! \namespace easypr*/
}/*! \namespace easypr*/
//这段代码是一个单例模式的实现。CParams类的instance_成员变量被初始化为nullptr然后instance()函数用来获取CParams类的唯一实例。如果instance_为空就会创建一个新的实例并返回否则直接返回现有的实例。这样可以确保在程序运行期间只有一个CParams类的实例存在。

@ -11,7 +11,7 @@ namespace easypr {
m_type = 0;
m_showDetect = false;
}
// 这段代码是CPlateDetect类的构造函数初始化了一些成员变量包括m_plateLocate指针、m_maxPlates、m_type和m_showDetect。
CPlateDetect::~CPlateDetect() { SAFE_RELEASE(m_plateLocate); }
int CPlateDetect::plateDetect(Mat src, std::vector<CPlate> &resultVec, int type,
@ -24,6 +24,11 @@ namespace easypr {
mser_Plates.reserve(16);
std::vector<CPlate> all_result_Plates;
all_result_Plates.reserve(64);
// 这部分代码定义了CPlateDetect类的析构函数和plateDetect方法。
// 在plateDetect方法中使用了OpenMP并行处理来同时进行Sobel、颜色和MSER三种车牌定位方法。
// 最后使用NMS非极大值抑制来判断车牌并返回结果。
// plateDetect方法还有一个重载版本其中showDetectArea参数为false。
// LoadSVM方法用于加载SVM模型。
#pragma omp parallel sections
{
#pragma omp section
@ -32,12 +37,16 @@ namespace easypr {
m_plateLocate->plateSobelLocate(src, sobel_Plates, img_index);
}
}
// 这部分代码使用了OpenMP并行处理来进行Sobel车牌定位方法。
// 如果type为0或者包含PR_DETECT_SOBEL标志位则调用m_plateLocate的plateSobelLocate方法。
// plateSobelLocate方法用于进行Sobel算子定位车牌。
#pragma omp section
{
if (!type || type & PR_DETECT_COLOR) {
m_plateLocate->plateColorLocate(src, color_Plates, img_index);
}
}
//这段代码是使用OpenMP并行处理来调用m_plateLocate的plateColorLocate方法用于进行颜色定位车牌。根据type参数的值判断是否需要进行颜色定位
#pragma omp section
{
if (!type || type & PR_DETECT_CMSER) {
@ -64,7 +73,11 @@ namespace easypr {
showDectectResults(src, resultVec, m_maxPlates);
return 0;
}
// 这部分代码是CPlateDetect类的plateDetect方法的一部分。
// 首先通过OpenMP并行处理调用plateMserLocate方法来进行MSER车牌定位。
// 然后将定位结果分别设置为对应的类型SOBEL、COLOR、CMSER并存入all_result_Plates中。
// 最后使用NMS非极大值抑制来判断车牌并将结果存入resultVec中。
// 代码中的if (0)条件语句似乎没有实际作用,可能需要进一步确认其意图。
int CPlateDetect::plateDetect(Mat src, std::vector<CPlate> &resultVec, int img_index) {
int result = plateDetect(src, resultVec, m_type, false, img_index);
return result;
@ -74,4 +87,6 @@ namespace easypr {
PlateJudge::instance()->LoadModel(path);
}
}
}
// 这段代码中的plateDetect方法是CPlateDetect类的成员函数用于调用另一个重载版本的plateDetect方法并返回结果。
// LoadSVM方法用于加载SVM模型其中调用了PlateJudge类的LoadModel方法。

@ -1,9 +1,11 @@
//该代码主要用于车牌识别。它定义了一个名为PlateJudge的类该类包含了一系列用于车牌识别的方法。
#include "easypr/core/plate_judge.h"
#include "easypr/config.h"
#include "easypr/core/core_func.h"
#include "easypr/core/params.h"
namespace easypr {
namespace easypr { //这部分代码实现了单例模式确保PlateJudge类只有一个实例
PlateJudge* PlateJudge::instance_ = nullptr;
@ -14,7 +16,7 @@ namespace easypr {
return instance_;
}
PlateJudge::PlateJudge() {
PlateJudge::PlateJudge() { //PlateJudge决定了使用哪种特征提取方法。
bool useLBP = false;
if (useLBP) {
LOAD_SVM_MODEL(svm_, kLBPSvmPath);
@ -26,7 +28,7 @@ namespace easypr {
}
}
void PlateJudge::LoadModel(std::string path) {
void PlateJudge::LoadModel(std::string path) { //LoadModel函数用于加载SVM模型。
if (path != std::string(kDefaultSvmPath)) {
if (!svm_->empty())
svm_->clear();
@ -36,7 +38,7 @@ namespace easypr {
// set the score of plate
// 0 is plate, -1 is not.
int PlateJudge::plateSetScore(CPlate& plate) {
int PlateJudge::plateSetScore(CPlate& plate) { //plateSetScore函数用于设置车牌的评分。
Mat features;
extractFeature(plate.getPlateMat(), features);
float score = svm_->predict(features, noArray(), cv::ml::StatModel::Flags::RAW_OUTPUT);
@ -53,53 +55,60 @@ namespace easypr {
else return -1;
}
int PlateJudge::plateJudge(const Mat& plateMat) {
int PlateJudge::plateJudge(const Mat& plateMat) { //plateJudge函数用于判断输入的图像是否为车牌。
CPlate plate;
plate.setPlateMat(plateMat);
return plateSetScore(plate);
}
int PlateJudge::plateJudge(const std::vector<Mat> &inVec,
std::vector<Mat> &resultVec) {
int num = inVec.size();
for (int j = 0; j < num; j++) {
Mat inMat = inVec[j];
int PlateJudge::plateJudge(const std::vector<Mat> &inVec, //inVec是输入的图像向量resultVec是输出的结果向量。
std::vector<Mat> &resultVec) { //对inVec中的每一张图像进行车牌判断。如果判断结果为车牌即plateJudge(inMat)的返回值为0则将该图像添加到resultVec中。
int num = inVec.size(); // 获取输入图像的数量
for (int j = 0; j < num; j++) { // 遍历每一张图像
Mat inMat = inVec[j]; // 获取当前图像
int response = -1;
response = plateJudge(inMat);
response = plateJudge(inMat); // 对当前图像进行车牌判断
if (response == 0) resultVec.push_back(inMat);
if (response == 0) resultVec.push_back(inMat); // 如果判断结果为车牌,将该图像添加到结果向量中
}
return 0;
return 0; // 返回0表示执行成功
}
int PlateJudge::plateJudge(const std::vector<CPlate> &inVec,
std::vector<CPlate> &resultVec) {
int num = inVec.size();
for (int j = 0; j < num; j++) {
CPlate inPlate = inVec[j];
//属于PlateJudge类用于判断输入的车牌向量中哪些是有效的车牌。这个方法的输入是一个CPlate对象的向量inVec输出是一个有效车牌的CPlate对象的向量resultVec。
int PlateJudge::plateJudge(const std::vector<CPlate> &inVec,
std::vector<CPlate> &resultVec) { //接收两个参数一个CPlate对象的向量inVec输入的车牌向量和一个CPlate对象的向量resultVec输出的有效车牌向量
int num = inVec.size(); // 获取输入向量的大小
for (int j = 0; j < num; j++) { //遍历输入向量中的每一个元素
//获取当前的CPlate对象和它的车牌图像
CPlate inPlate = inVec[j];
Mat inMat = inPlate.getPlateMat();
//调用plateJudge方法判断当前的车牌图像是否有效结果存储在response中
int response = -1;
response = plateJudge(inMat);
//如果当前的车牌图像有效response等于0则将当前的CPlate对象添加到结果向量中
if (response == 0)
resultVec.push_back(inPlate);
//如果当前的车牌图像无效,那么对车牌图像进行裁剪和调整大小
else {
int w = inMat.cols;
int h = inMat.rows;
Mat tmpmat = inMat(Rect_<double>(w * 0.05, h * 0.1, w * 0.9, h * 0.8));
Mat tmpDes = inMat.clone();
resize(tmpmat, tmpDes, Size(inMat.size()));
//再次调用plateJudge方法判断调整后的车牌图像是否有效如果有效则将当前的CPlate对象添加到结果向量中
response = plateJudge(tmpDes);
if (response == 0) resultVec.push_back(inPlate);
}
}
return 0;
return 0; //结束循环并返回0表示方法执行成功
}
// non-maximum suppression
void NMS(std::vector<CPlate> &inVec, std::vector<CPlate> &resultVec, double overlap) {
void NMS(std::vector<CPlate> &inVec, std::vector<CPlate> &resultVec, double overlap) { //NMS函数实现了非极大值抑制用于消除重叠的车牌。
std::sort(inVec.begin(), inVec.end());
std::vector<CPlate>::iterator it = inVec.begin();
for (; it != inVec.end(); ++it) {
@ -123,7 +132,7 @@ namespace easypr {
}
// judge plate using nms
int PlateJudge::plateJudgeUsingNMS(const std::vector<CPlate> &inVec, std::vector<CPlate> &resultVec, int maxPlates) {
int PlateJudge::plateJudgeUsingNMS(const std::vector<CPlate> &inVec, std::vector<CPlate> &resultVec, int maxPlates) { //plateJudgeUsingNMS函数使用非极大值抑制进行车牌识别。
std::vector<CPlate> plateVec;
int num = inVec.size();
bool useCascadeJudge = true;

@ -7,7 +7,10 @@ namespace easypr {
CPlateRecognize::CPlateRecognize() {
m_showResult = false;
}
// 这段代码是C++中的类定义定义了CPlateRecognize类的构造函数初始化了m_showResult成员变量为false。
// 该类包含plateRecognize方法用于车牌识别包括车牌检测和字符识别两部分。
// 还包含了一些Load方法用于加载模型文件。
// 代码中使用了OpenCV库中的Mat类和一些自定义的类和方法。
// main method, plate recognize, contain two parts
// 1. plate detect
@ -20,6 +23,9 @@ int CPlateRecognize::plateRecognize(const Mat& src, std::vector<CPlate> &plateVe
// 1. plate detect
std::vector<CPlate> plateVec;
int resultPD = plateDetect(img, plateVec, img_index);
// 这段代码是C++中的类方法CPlateRecognize::plateRecognize的一部分用于车牌识别。
// 首先对输入图像进行统一尺寸的调整然后进行车牌检测将结果存储在plateVec中。
// 返回值resultPD表示车牌检测的结果0表示成功非0表示失败。
if (resultPD == 0) {
size_t num = plateVec.size();
for (size_t j = 0; j < num; j++) {
@ -31,7 +37,12 @@ int CPlateRecognize::plateRecognize(const Mat& src, std::vector<CPlate> &plateVe
item.setPlateScale(scale);
RotatedRect rect = item.getPlatePos();
item.setPlatePos(scaleBackRRect(rect, 1.f / scale));
// 如果车牌检测成功resultPD == 0则对每个检测到的车牌进行处理
// 1. 获取车牌图像并显示
// 2. 调整车牌位置和大小
// 3. 获取车牌颜色并识别字符
// 4. 将识别结果存储,并根据识别结果设置车牌字符串
// 5. 根据显示类型展示检测结果或识别结果
// get plate color
Color color = item.getPlateColor();
if (color == UNKNOWN) {
@ -42,7 +53,10 @@ int CPlateRecognize::plateRecognize(const Mat& src, std::vector<CPlate> &plateVe
if (0) {
std::cout << "plateColor:" << plateColor << std::endl;
}
// 这段代码用于获取车牌颜色并进行处理。
// 首先获取车牌颜色如果颜色为UNKNOWN则调用getPlateType方法获取颜色并设置到item中。
// 然后将颜色转换为字符串形式并存储在plateColor中。
// 最后通过条件判断如果条件为0则输出plateColor到控制台。
// 2. chars recognize
std::string plateIdentify = "";
int resultCR = charsRecognise(item, plateIdentify);
@ -59,6 +73,11 @@ int CPlateRecognize::plateRecognize(const Mat& src, std::vector<CPlate> &plateVe
if (0) std::cout << "resultCR:" << resultCR << std::endl;
}
}
// 这段代码是C++中的类方法CPlateRecognize::plateRecognize的一部分用于字符识别。
// 首先定义了一个空字符串plateIdentify并调用charsRecognise方法进行字符识别结果存储在resultCR中。
// 如果resultCR为0则将plateColor和plateIdentify拼接成license字符串并设置到item中然后将item存入plateVecOut中。
// 否则只将plateColor设置到license字符串中然后将item存入plateVecOut中。
// 最后根据条件判断如果条件为0则输出resultCR到控制台。
if (getResultShow()) {
// param type: 0 detect, 1 recognize;
int showType = 1;
@ -70,7 +89,9 @@ int CPlateRecognize::plateRecognize(const Mat& src, std::vector<CPlate> &plateVe
}
return resultPD;
}
// 这段代码是C++中的类方法CPlateRecognize::plateRecognize的一部分用于根据getResultShow()的返回值决定是否展示检测结果或识别结果。
// 如果getResultShow()返回true则根据showType的值选择展示检测结果或识别结果。
// 最后返回resultPD表示车牌检测的结果。
void CPlateRecognize::LoadSVM(std::string path) {
PlateJudge::instance()->LoadModel(path);
}
@ -78,7 +99,12 @@ void CPlateRecognize::LoadSVM(std::string path) {
void CPlateRecognize::LoadANN(std::string path) {
CharsIdentify::instance()->LoadModel(path);
}
// 评注该代码
// 这部分代码定义了CPlateRecognize类的两个方法LoadSVM和LoadANN。
// LoadSVM方法用于加载SVM模型调用PlateJudge类的实例的LoadModel方法加载模型。
// LoadANN方法用于加载ANN模型调用CharsIdentify类的实例的LoadModel方法加载模型。
// 这些方法用于在车牌识别过程中加载相关的机器学习模型。
void CPlateRecognize::LoadChineseANN(std::string path) {
CharsIdentify::instance()->LoadChineseModel(path);
}
@ -90,7 +116,9 @@ void CPlateRecognize::LoadGrayChANN(std::string path) {
void CPlateRecognize::LoadChineseMapping(std::string path) {
CharsIdentify::instance()->LoadChineseMapping(path);
}
// 这部分代码定义了CPlateRecognize类的三个方法LoadChineseANN、LoadGrayChANN和LoadChineseMapping。
// 这些方法用于在字符识别过程中加载相关的中文字符识别模型和映射文件。
// 分别调用CharsIdentify类的实例的LoadChineseModel、LoadGrayChANN和LoadChineseMapping方法加载模型和映射文件。
// deprected
int CPlateRecognize::plateRecognize(const Mat& src, std::vector<std::string> &licenseVec) {
vector<CPlate> plates;
@ -102,4 +130,8 @@ int CPlateRecognize::plateRecognize(const Mat& src, std::vector<std::string> &li
return resultPR;
}
}
}
// 这段代码定义了CPlateRecognize类的一个新方法plateRecognize接受一个输入图像和一个字符串向量作为参数。
// 在方法内部首先创建了一个CPlate类型的向量plates并调用了原有的plateRecognize方法来进行车牌识别。
// 然后遍历plates向量中的每个车牌将其车牌字符串存入传入的licenseVec向量中。
// 最后返回了车牌识别的结果resultPR。

@ -21,6 +21,10 @@ AnnTrain::AnnTrain(const char* chars_folder, const char* xml)
kv_->load("resources/text/province_mapping");
}
// 这段代码是C++的类成员函数AnnTrain::AnnTrain的构造函数实现。
// 构造函数接受两个参数chars_folder和xml并将其分别赋值给成员变量chars_folder_和ann_xml_。
// 然后使用cv::ml::ANN_MLP::create()创建了一个神经网络对象ann_。
// type被初始化为0kv_被初始化为一个加载了"resources/text/province_mapping"的Kv对象。
void AnnTrain::train() {
int classNumber = 0;
@ -45,7 +49,9 @@ void AnnTrain::train() {
hidden_number = kNeurons;
output_number = classNumber;
}
// 这段代码是AnnTrain类的train方法根据type的值选择不同的classNumber、input_number、hidden_number和output_number。
// 当type为0时classNumber为kCharsTotalNumberinput_number为kAnnInputhidden_number为kNeuronsoutput_number为classNumber。
// 当type为1时classNumber为kChineseNumberinput_number为kAnnInputhidden_number为kNeuronsoutput_number为classNumber。
int N = input_number;
int m = output_number;
int first_hidden_neurons = int(std::sqrt((m + 2) * N) + 2 * std::sqrt(N / (m + 2)));
@ -70,7 +76,9 @@ void AnnTrain::train() {
layers.at<int>(2) = second_hidden_neurons;
layers.at<int>(3) = output_number;
}
//这段代码根据输入和输出的数量计算了两个隐藏层的神经元数量,
//并根据布尔变量useTLFN的值选择了创建三层或四层的神经网络层。如果useTLFN为false则创建三层否则创建四层。
//在创建四层时,输出了两个隐藏层的神经元数量。
ann_->setLayerSizes(layers);
ann_->setActivationFunction(cv::ml::ANN_MLP::SIGMOID_SYM, 1, 1);
ann_->setTrainMethod(cv::ml::ANN_MLP::TrainingMethods::BACKPROP);
@ -99,7 +107,11 @@ void AnnTrain::train() {
std::cout << "Your ANN Model was saved to " << ann_xml_ << std::endl;
std::cout << "Training done. Time elapse: " << (end - start) / (1000 * 60) << "minute" << std::endl;
}
//这段代码是一个C++类成员函数AnnTrain::train的实现。
//在这段代码中神经网络ann_被训练并保存到ann_xml_文件中。
//训练数据通过sdata(350)函数获取然后使用ann_->train(traindata)进行训练。
//训练完成后,会输出"Your ANN Model was saved to "以及训练所花费的时间。
//同时还会调用test()函数进行测试。如果训练文件夹中没有文件,则会输出相应的提示信息。
std::pair<std::string, std::string> AnnTrain::identifyChinese(cv::Mat input) {
cv::Mat feature = charFeatures2(input, kPredictSize);
float maxVal = -2;
@ -124,7 +136,10 @@ std::pair<std::string, std::string> AnnTrain::identifyChinese(cv::Mat input) {
return std::make_pair(s, province);
}
// 这段代码是AnnTrain类的identifyChinese方法接受一个cv::Mat类型的输入参数input。
// 首先调用charFeatures2函数提取特征然后使用神经网络ann_对特征进行预测得到输出output。
// 接着遍历output找到最大值对应的索引result并计算出最终的索引index。
// 最后根据index获取对应的字符key再通过kv_获取对应的省份province最终返回一个包含字符和省份的pair。
std::pair<std::string, std::string> AnnTrain::identify(cv::Mat input) {
cv::Mat feature = charFeatures2(input, kPredictSize);
@ -155,7 +170,10 @@ std::pair<std::string, std::string> AnnTrain::identify(cv::Mat input) {
return std::make_pair(s, province);
}
}
// 这段代码是AnnTrain类的identify方法接受一个cv::Mat类型的输入参数input。
// 首先调用charFeatures2函数提取特征然后使用神经网络ann_对特征进行预测得到输出output。
// 接着遍历output找到最大值对应的索引result并计算出最终的索引index。
// 最后根据index判断返回的字符和省份信息返回一个包含字符和省份的pair。
void AnnTrain::test() {
assert(chars_folder_);
@ -175,7 +193,10 @@ void AnnTrain::test() {
auto chars_files = utils::getFiles(sub_folder);
int corrects = 0, sum = 0;
std::vector<std::pair<std::string, std::string>> error_files;
// 这段代码是AnnTrain类的test方法用于测试字符识别的准确率。
// 首先根据type的值确定classNumber然后遍历每个字符的文件夹进行测试。
// 在测试过程中,会统计正确识别的字符数量和总测试字符数量,以及每个字符的识别准确率。
// 最后输出总的测试结果和平均准确率。
for (auto file : chars_files) {
auto img = cv::imread(file, 0); // a grayscale image
if (!img.data) {
@ -196,6 +217,11 @@ void AnnTrain::test() {
++sum;
++sum_all;
}
// 这段代码是一个循环遍历chars_files中的文件对每个文件进行处理。
// 首先使用OpenCV的imread函数读取文件为灰度图像img然后判断img是否为空。
// 如果type为0则调用identify函数对图像进行识别否则调用identifyChinese函数。
// 如果识别结果与char_key相同则将corrects和corrects_all加一否则将错误信息加入error_files。
// 最后将sum和sum_all加一。
float rate = (float)corrects / (sum == 0 ? 1 : sum);
fprintf(stdout, ">> [sum: %d, correct: %d, rate: %.2f]\n", sum, corrects, rate);
rate_list.push_back(rate);
@ -217,6 +243,10 @@ void AnnTrain::test() {
}
fprintf(stdout, ">> [\n%s\n ]\n", error_string.c_str());
}
// 这段代码计算了识别准确率,并输出了每个字符的识别结果和错误信息。
// 首先计算了识别准确率rate并将其加入rate_list中。
// 然后构建了错误信息字符串error_string遍历error_files并将错误信息添加到字符串中。
// 最后使用fprintf输出了总的识别结果和错误信息。
fprintf(stdout, ">> [sum_all: %d, correct_all: %d, rate: %.4f]\n", sum_all, corrects_all,
(float)corrects_all / (sum_all == 0 ? 1 : sum_all));
@ -226,6 +256,8 @@ void AnnTrain::test() {
fprintf(stdout, ">> [classNumber: %d, avg_rate: %.4f]\n", classNumber, rate_mean);
}
// 这段代码用于输出总的测试结果和平均准确率。
// 首先输出总的测试结果和准确率,然后计算了每个字符的识别准确率的平均值并输出。
cv::Mat getSyntheticImage(const Mat& image) {
int rand_type = rand();
Mat result = image.clone();
@ -244,7 +276,11 @@ cv::Mat getSyntheticImage(const Mat& image) {
return result;
}
// 该代码定义了一个函数getSyntheticImage接受一个cv::Mat类型的参数image。
//首先生成一个随机数rand_type然后将result初始化为image的克隆。
//如果rand_type为偶数则生成两个随机数ran_x和ran_y然后调用translateImg函数对result进行平移操作。
// 如果rand_type为奇数则生成一个随机角度angle然后调用rotateImg函数对result进行旋转操作。
// 最后返回result。
cv::Ptr<cv::ml::TrainData> AnnTrain::sdata(size_t number_for_count) {
assert(chars_folder_);
@ -256,6 +292,9 @@ cv::Ptr<cv::ml::TrainData> AnnTrain::sdata(size_t number_for_count) {
if (type == 1) classNumber = kChineseNumber;
srand((unsigned)time(0));
// 这段代码是AnnTrain类的sdata方法用于生成训练数据。
// 首先检查chars_folder_是否存在然后初始化samples和labels。
// 根据type的值确定classNumber然后使用srand函数初始化随机数种子。
for (int i = 0; i < classNumber; ++i) {
auto char_key = kChars[i + kCharsTotalNumber - classNumber];
@ -274,7 +313,11 @@ cv::Ptr<cv::ml::TrainData> AnnTrain::sdata(size_t number_for_count) {
auto img = cv::imread(file, 0); // a grayscale image
matVec.push_back(img);
}
// 这段代码是一个循环遍历每个字符文件夹中的文件并将文件读取为灰度图像后存入matVec中。
// 首先根据循环变量i计算出当前字符的关键字char_key并构建对应的子文件夹路径sub_folder。
// 然后使用utils::getFiles函数获取子文件夹中的文件列表chars_files并统计文件数量char_size。
// 接着初始化了一个存储灰度图像的向量matVec并预留了number_for_count个元素的空间。
// 遍历chars_files使用cv::imread函数读取文件为灰度图像img并将其存入matVec中。
for (int t = 0; t < (int)number_for_count - (int)char_size; t++) {
int rand_range = char_size + t;
int ran_num = rand() % rand_range;
@ -287,7 +330,10 @@ cv::Ptr<cv::ml::TrainData> AnnTrain::sdata(size_t number_for_count) {
imwrite(ss.str(), simg);
}
}
// 这段代码是一个循环,用于生成合成图像并保存到文件中。
// 首先循环变量t从0到number_for_count - char_size然后生成一个随机数ran_num。
// 接着从matVec中获取对应索引的图像img并调用getSyntheticImage函数生成合成图像simg。
// 将simg添加到matVec中并使用imwrite函数将simg保存为文件。
fprintf(stdout, ">> Characters count: %d \n", (int)matVec.size());
for (auto img : matVec) {
@ -297,7 +343,8 @@ cv::Ptr<cv::ml::TrainData> AnnTrain::sdata(size_t number_for_count) {
labels.push_back(i);
}
}
// 这段代码用于统计字符数量并将特征和标签添加到训练数据中。
// 首先使用fprintf输出字符数量然后遍历matVec中的图像对每个图像提取特征并将特征和标签添加到训练数据中。
cv::Mat samples_;
samples.convertTo(samples_, CV_32F);
cv::Mat train_classes =
@ -310,7 +357,10 @@ cv::Ptr<cv::ml::TrainData> AnnTrain::sdata(size_t number_for_count) {
return cv::ml::TrainData::create(samples_, cv::ml::SampleTypes::ROW_SAMPLE,
train_classes);
}
// 该部分代码是AnnTrain类的tdata方法用于生成训练数据。
// 首先将samples转换为CV_32F类型的samples_然后初始化train_classes为全零矩阵。
// 接着遍历train_classes的每一行将对应位置的值设为1。
// 最后使用cv::ml::TrainData::create函数创建并返回训练数据对象。
cv::Ptr<cv::ml::TrainData> AnnTrain::tdata() {
assert(chars_folder_);
@ -322,7 +372,9 @@ cv::Ptr<cv::ml::TrainData> AnnTrain::tdata() {
int classNumber = 0;
if (type == 0) classNumber = kCharsTotalNumber;
if (type == 1) classNumber = kChineseNumber;
// 这段代码是AnnTrain类的tdata方法用于生成训练数据。
// 首先检查chars_folder_是否存在然后初始化samples和labels。
// 根据type的值确定classNumber。
for (int i = 0; i < classNumber; ++i) {
auto char_key = kChars[i + kCharsTotalNumber - classNumber];
char sub_folder[512] = {0};
@ -340,7 +392,11 @@ cv::Ptr<cv::ml::TrainData> AnnTrain::tdata() {
labels.push_back(i);
}
}
// 这段代码是一个循环,遍历每个字符文件夹中的文件,并将文件读取为灰度图像后提取特征并添加到训练数据中。
// 首先根据循环变量i计算出当前字符的关键字char_key并构建对应的子文件夹路径sub_folder。
// 然后使用utils::getFiles函数获取子文件夹中的文件列表chars_files并遍历每个文件。
// 对每个文件使用cv::imread函数读取为灰度图像img然后调用charFeatures2函数提取特征fps并将其添加到samples中。
// 同时将当前字符的标签i添加到labels中。
cv::Mat samples_;
samples.convertTo(samples_, CV_32F);
cv::Mat train_classes =
@ -354,3 +410,6 @@ cv::Ptr<cv::ml::TrainData> AnnTrain::tdata() {
train_classes);
}
}
// 该部分代码是用于生成训练数据的一部分首先将samples转换为CV_32F类型的samples_
// 然后初始化train_classes为全零矩阵接着遍历train_classes的每一行将对应位置的值设为1。
// 最后使用cv::ml::TrainData::create函数创建并返回训练数据对象。

@ -7,7 +7,10 @@ namespace easypr {
assert(img.type() == CV_8UC1);
int w = img.cols;
int h = img.rows;
// 该部分代码定义了一个名为getBorderColor的函数用于获取图像边界的颜色值
// 函数首先确保输入图像是单通道灰度图像,然后获取图像的宽度和高度
// 接下来通过遍历图像边界像素的方式计算边界颜色的平均值,并返回整数类型的平均值
// 该文件还包含了一些其他图像处理函数,如平移、旋转、裁剪和生成合成图像的函数
float sum = 0;
for (int i = 0; i < h; ++i) {
sum += img.at<unsigned char>(i, 0);
@ -21,7 +24,9 @@ namespace easypr {
float avg = sum / float(w + w + h + h);
return int(avg);
}
// 该部分代码计算了图像边界像素的平均值作为边界颜色值,并返回整数类型的平均值
// 首先对图像边界像素进行遍历累加像素值到sum中
// 然后计算平均值avg并返回整数类型的平均值
// shift an image
Mat translateImg(Mat img, int offsetx, int offsety, int bk){
Mat dst;
@ -31,7 +36,12 @@ namespace easypr {
warpAffine(img, dst, trans_mat, img.size(), 1, 0, Scalar(bk));
return dst;
}
// 该部分代码定义了一个名为translateImg的函数用于对图像进行平移操作
// 函数接受输入图像img以及x和y方向的偏移量offsetx和offsety以及背景颜色bk
// 在函数内部首先创建了一个空的目标图像dst
// 然后定义了一个2x3的变换矩阵trans_mat用于表示平移操作
// 最后调用warpAffine函数对输入图像进行平移操作并将结果存储在目标图像dst中
// 最终返回平移后的目标图像dst
// rotate an image
Mat rotateImg(Mat source, float angle, int bk){
Point2f src_center(source.cols / 2.0F, source.rows / 2.0F);
@ -40,7 +50,12 @@ namespace easypr {
warpAffine(source, dst, rot_mat, source.size(), 1, 0, Scalar(bk));
return dst;
}
// 该部分代码定义了一个名为rotateImg的函数用于对图像进行旋转操作
// 函数接受输入图像source以及旋转角度angle和背景颜色bk作为参数
// 在函数内部首先计算了输入图像的中心点坐标src_center
// 然后利用getRotationMatrix2D函数计算了旋转矩阵rot_mat
// 最后调用warpAffine函数对输入图像进行旋转操作并将结果存储在目标图像dst中
// 最终返回旋转后的目标图像dst
// crop the image
Mat cropImg(Mat src, int x, int y, int shift, int bk){
int width = src.cols;
@ -61,7 +76,13 @@ namespace easypr {
resize(dst, dst, Size(width, height));
return dst;
}
// 该部分代码定义了一个名为cropImg的函数用于对图像进行裁剪操作
// 函数接受输入图像src以及裁剪起始点的x和y坐标裁剪尺寸shift和背景颜色bk作为参数
// 在函数内部,首先获取输入图像的宽度和高度
// 然后计算裁剪后的图像宽度和高度
// 接着根据裁剪起始点和裁剪尺寸计算裁剪区域的矩形rect
// 将裁剪后的图像存储在目标图像dst中并进行大小调整
// 最终返回裁剪后的目标图像dst
Mat generateSyntheticImage(const Mat& image, int use_swap) {
int rd = rand();
int bkColor = getBoderColor(image);
@ -85,4 +106,10 @@ namespace easypr {
return result;
}
}
// 该部分代码定义了一个名为generateSyntheticImage的函数用于生成合成图像
// 函数接受输入图像image以及一个整数参数use_swap
// 首先通过rand函数生成一个随机数rd
// 然后调用getBorderColor函数获取输入图像的边界颜色值并存储在bkColor中
// 接着对输入图像进行克隆存储在result中
// 之后根据随机数rd的不同位进行裁剪、平移和旋转操作并将结果存储在result中
// 最终返回合成后的目标图像result

@ -18,7 +18,9 @@ SvmTrain::SvmTrain(const char* plates_folder, const char* xml)
extractFeature = getHistomPlusColoFeatures;
}
// 这段代码是SvmTrain类的构造函数接受两个const char*类型的参数plates_folder和xml。
// 在构造函数中使用了assert函数来确保plates_folder和xml不为空。
// 同时将extractFeature设置为getHistomPlusColoFeatures函数。
void SvmTrain::train() {
svm_ = cv::ml::SVM::create();
svm_->setType(cv::ml::SVM::C_SVC);
@ -31,7 +33,10 @@ void SvmTrain::train() {
svm_->setNu(0.1);
svm_->setP(0.1);
svm_->setTermCriteria(cvTermCriteria(CV_TERMCRIT_ITER, 20000, 0.0001));
// 这段代码是SvmTrain类的train方法用于训练支持向量机模型。
// 在方法中创建了一个SVM对象并设置了其类型、核函数类型、参数等。
// 最后调用了trainAuto方法进行模型训练并保存了训练好的模型。
// 该方法还包括了一些打印输出和测试方法的调用。
this->prepare();
if (train_file_list_.size() == 0) {
@ -41,7 +46,9 @@ void SvmTrain::train() {
return;
}
auto train_data = tdata();
// 这段代码是在svm_train.cpp文件中的train方法中的一部分。
// 首先调用了prepare方法然后检查train_file_list_的大小是否为0如果是则输出提示信息并返回。
// 最后调用了tdata方法并将返回值赋给train_data。
fprintf(stdout, ">> Training SVM model, please wait...\n");
long start = utils::getTimestamp();
svm_->trainAuto(train_data, 10, SVM::getDefaultGrid(SVM::C),
@ -49,7 +56,10 @@ void SvmTrain::train() {
SVM::getDefaultGrid(SVM::NU), SVM::getDefaultGrid(SVM::COEF),
SVM::getDefaultGrid(SVM::DEGREE), true);
//svm_->train(train_data);
// 这段代码是在svm_train.cpp文件中的train方法中的一部分。
// 首先输出训练提示信息,然后获取当前时间戳作为训练开始时间。
// 接着调用svm_->trainAuto方法进行支持向量机模型的自动训练使用了默认的参数网格和并行化。
// 最后注释掉了旧的svm_->train方法的调用。
long end = utils::getTimestamp();
fprintf(stdout, ">> Training done. Time elapse: %ldms\n", end - start);
fprintf(stdout, ">> Saving model file...\n");
@ -61,7 +71,11 @@ void SvmTrain::train() {
this->test();
}
// 这段代码是SvmTrain类中的test方法用于测试支持向量机模型。
// 首先通过utils::getTimestamp()获取当前时间戳作为测试结束时间,并计算训练时间。
// 然后输出训练完成的提示信息并保存训练好的模型到指定的xml文件中。
// 接着输出模型保存的路径,并提示进行测试。
// 最后调用了SvmTrain类中的test方法进行模型测试。
void SvmTrain::test() {
// 1.4 bug fix: old 1.4 ver there is no null judge
// if (NULL == svm_)
@ -70,13 +84,16 @@ void SvmTrain::test() {
if (test_file_list_.empty()) {
this->prepare();
}
// 这段代码是SvmTrain类中的test方法。
// 首先通过LOAD_SVM_MODEL宏加载了svm模型。
// 然后检查test_file_list_是否为空如果为空则调用prepare方法。
double count_all = test_file_list_.size();
double ptrue_rtrue = 0;
double ptrue_rfalse = 0;
double pfalse_rtrue = 0;
double pfalse_rfalse = 0;
// 这段代码用于初始化四个double类型的变量分别表示总数、真正例、假反例和假正例。
// 这些变量将在后续的循环中用于统计支持向量机模型的预测结果。
for (auto item : test_file_list_) {
auto image = cv::imread(item.file);
if (!image.data) {
@ -85,7 +102,10 @@ void SvmTrain::test() {
}
cv::Mat feature;
extractFeature(image, feature);
// 这段代码是在SvmTrain类中的test方法中的循环部分。
// 首先遍历test_file_list_中的每个item然后使用OpenCV的imread函数读取图像文件。
// 如果图像数据为空,则输出"no"并继续下一次循环。
// 否则创建一个cv::Mat类型的feature变量并调用extractFeature函数提取图像特征。
auto predict = int(svm_->predict(feature));
//std::cout << "predict: " << predict << std::endl;
@ -95,13 +115,17 @@ void SvmTrain::test() {
if (predict == kInverse && real == kForward) pfalse_rtrue++;
if (predict == kInverse && real == kInverse) pfalse_rfalse++;
}
// 这段代码用于对图像特征进行预测,并统计预测结果的真正例、假反例、假正例和真反例的数量。
// 首先使用svm_->predict函数对特征进行预测然后根据预测结果和真实标签进行统计。
// 最后根据统计结果计算精确率precise、召回率recall和F分数Fsocre
std::cout << "count_all: " << count_all << std::endl;
std::cout << "ptrue_rtrue: " << ptrue_rtrue << std::endl;
std::cout << "ptrue_rfalse: " << ptrue_rfalse << std::endl;
std::cout << "pfalse_rtrue: " << pfalse_rtrue << std::endl;
std::cout << "pfalse_rfalse: " << pfalse_rfalse << std::endl;
// 评注该代码
// 这段代码用于输出count_all、ptrue_rtrue、ptrue_rfalse、pfalse_rtrue和pfalse_rfalse的值
// 这些值分别表示总数、真正例、假反例、假正例和真反例的数量
double precise = 0;
if (ptrue_rtrue + ptrue_rfalse != 0) {
precise = ptrue_rtrue / (ptrue_rtrue + ptrue_rfalse);
@ -110,7 +134,8 @@ void SvmTrain::test() {
std::cout << "precise: "
<< "NA" << std::endl;
}
// 这段代码用于计算精确率precision如果ptrue_rtrue和ptrue_rfalse之和不为0
// 则计算精确率并输出,否则输出"NA"
double recall = 0;
if (ptrue_rtrue + pfalse_rtrue != 0) {
recall = ptrue_rtrue / (ptrue_rtrue + pfalse_rtrue);
@ -119,7 +144,8 @@ void SvmTrain::test() {
std::cout << "recall: "
<< "NA" << std::endl;
}
// 这段代码计算并输出召回率recall召回率表示被正确预测为正例的样本数占所有实际正例样本数的比例。
// 如果ptrue_rtrue和pfalse_rtrue之和不为0则计算召回率并输出否则输出"NA"。
double Fsocre = 0;
if (precise + recall != 0) {
Fsocre = 2 * (precise * recall) / (precise + recall);
@ -129,7 +155,9 @@ void SvmTrain::test() {
<< "NA" << std::endl;
}
}
// 评注该代码
// 这段代码用于计算并输出F分数FsocreF分数是精确率precision和召回率recall的调和平均数。
// 首先判断精确率和召回率之和是否为0如果不为0则计算F分数并输出否则输出"NA"。
void SvmTrain::prepare() {
srand(unsigned(time(NULL)));
@ -150,7 +178,10 @@ void SvmTrain::prepare() {
sprintf(buffer, "%s/no/test", plates_folder_);
auto no_file_test_list = utils::getFiles(buffer);
std::random_shuffle(no_file_test_list.begin(), no_file_test_list.end());
// 该代码是SvmTrain类中的prepare方法用于准备训练和测试数据集。
// 首先使用srand和time函数生成随机种子。
// 然后通过sprintf和utils::getFiles函数获取训练和测试数据集的文件列表并对列表进行随机打乱。
// 最后将获取到的文件列表分别存储到has_file_train_list、has_file_test_list、no_file_train_list和no_file_test_list中。
fprintf(stdout, ">> Collecting train data...\n");
for (auto file : has_file_train_list)
@ -167,11 +198,19 @@ void SvmTrain::prepare() {
for (auto file : no_file_test_list)
test_file_list_.push_back({ file, kInverse });
}
// 该部分代码用于收集训练数据和测试数据。
// 首先输出提示信息"Collecting train data..."然后遍历has_file_train_list和no_file_train_list
// 将文件路径和标签分别添加到train_file_list_中。
// 然后输出提示信息"Collecting test data..."并遍历has_file_test_list和no_file_test_list
// 将文件路径和标签分别添加到test_file_list_中。
cv::Ptr<cv::ml::TrainData> SvmTrain::tdata() {
cv::Mat samples;
std::vector<int> responses;
// 这段代码是SvmTrain类中的tdata方法用于准备支持向量机训练数据。
// 首先定义了一个cv::Mat类型的samples和一个std::vector<int>类型的responses。
// 然后遍历train_file_list_中的每个文件使用OpenCV的imread函数读取图像文件提取图像特征并将特征转换为一行的形式。
// 将特征和标签分别添加到samples和responses中。
// 最后将samples和responses转换为CV_32FC1类型的samples_和responses_并使用cv::ml::TrainData::create创建训练数据对象并返回。
for (auto f : train_file_list_) {
auto image = cv::imread(f.file);
if (!image.data) {
@ -185,7 +224,11 @@ cv::Ptr<cv::ml::TrainData> SvmTrain::tdata() {
samples.push_back(feature);
responses.push_back(int(f.label));
}
// 这段代码是在SvmTrain类中的tdata方法中的循环部分。
// 首先遍历train_file_list_中的每个文件使用OpenCV的imread函数读取图像文件。
// 如果图像数据为空,则输出"Invalid image: 文件路径 ignore."并继续下一次循环。
// 否则创建一个cv::Mat类型的feature变量并调用extractFeature函数提取图像特征。
// 将特征转换为一行的形式然后将特征和标签分别添加到samples和responses中。
cv::Mat samples_, responses_;
samples.convertTo(samples_, CV_32FC1);
cv::Mat(responses).copyTo(responses_);
@ -194,3 +237,6 @@ cv::Ptr<cv::ml::TrainData> SvmTrain::tdata() {
}
} // namespace easypr
// 这段代码是在SvmTrain类中的tdata方法中的最后部分。
// 首先将samples转换为CV_32FC1类型的samples_然后将responses复制到responses_中。
// 最后使用cv::ml::TrainData::create创建并返回训练数据对象。

@ -6,3 +6,4 @@ ITrain::ITrain() {}
ITrain::~ITrain() {}
}
//这段代码是一个C++文件定义了一个名为ITrain的类包含了一个默认构造函数和一个析构函数。这些函数都位于easypr命名空间中。
Loading…
Cancel
Save