From 30d539a265ec685dff66b35e9ae5089eb3e858df Mon Sep 17 00:00:00 2001 From: zoeda <2063629468@qq.com> Date: Mon, 4 Dec 2023 12:58:37 +0800 Subject: [PATCH 1/2] chars_identify.cpp --- src/src/core/chars_identify.cpp | 105 +++++++++++++++++++++++++++----- 1 file changed, 89 insertions(+), 16 deletions(-) diff --git a/src/src/core/chars_identify.cpp b/src/src/core/chars_identify.cpp index a88d727..452978f 100644 --- a/src/src/core/chars_identify.cpp +++ b/src/src/core/chars_identify.cpp @@ -18,6 +18,7 @@ CharsIdentify* CharsIdentify::instance() { return instance_; } +// 主要用于加载和管理预训练的神经网络模型,用于字符识别 CharsIdentify::CharsIdentify() { LOAD_ANN_MODEL(ann_, kDefaultAnnPath); LOAD_ANN_MODEL(annChinese_, kChineseAnnPath); @@ -58,18 +59,24 @@ void CharsIdentify::LoadChineseMapping(std::string path) { kv_->load(path); } +// 对输入的特征行进行预测,并识别出最可能的字符。 void CharsIdentify::classify(cv::Mat featureRows, std::vector& out_maxIndexs, std::vector& out_maxVals, std::vector 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++) { @@ -81,6 +88,8 @@ void CharsIdentify::classify(cv::Mat featureRows, std::vector& out_maxIndex } } } + // 如果该行是中文字符, + // 则从kCharactersNumber开始遍历后面的预测结果,找出值最大的那个,并记录其索引和值。 else { result = kCharactersNumber; for (int j = kCharactersNumber; j < kCharsTotalNumber; j++) { @@ -92,18 +101,20 @@ void CharsIdentify::classify(cv::Mat featureRows, std::vector& out_maxIndex } } } + // 将记录的最大索引和最大值分别赋值给out_maxIndexs和out_maxVals的相应位置 out_maxIndexs[output_index] = result; out_maxVals[output_index] = maxVal; } } - +// 接受一个CCharacter类型的向量(charVec),并对每个字符进行分类。 void CharsIdentify::classify(std::vector& 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(); @@ -111,9 +122,14 @@ void CharsIdentify::classify(std::vector& 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); @@ -135,6 +151,8 @@ void CharsIdentify::classify(std::vector& charVec){ } label = std::make_pair(kChars[result], kChars[result]).second; } + // 如果字符是中文字符,函数则从预测结果的后面部分开始查找最大值,并记录其索引和值。 + // 然后,函数根据这个最大值和索引确定预测的字符,并通过键值对(kv_)查找对应的省份,将字符和省份作为标签。 else { result = kCharactersNumber; for (int j = kCharactersNumber; j < kCharsTotalNumber; j++) { @@ -152,28 +170,33 @@ void CharsIdentify::classify(std::vector& charVec){ } /*std::cout << "result:" << result << std::endl; std::cout << "maxVal:" << maxVal << std::endl;*/ + + // 函数将预测的最大值和标签分别设置到对应字符对象的得分和字符串属性中。 character.setCharacterScore(maxVal); character.setCharacterStr(label); } } - +// 对输入的中文字符进行分类 void CharsIdentify::classifyChineseGray(std::vector& 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); @@ -198,6 +221,8 @@ void CharsIdentify::classifyChineseGray(std::vector& charVec){ isChinese = false; } + // 根据这个最大值和索引确定预测的字符。 + // 这是通过查找kChars数组实现的,其中kChars可能是一个预定义的字符集。 auto index = result + kCharsTotalNumber - kChineseNumber; const char* key = kChars[index]; std::string s = key; @@ -206,12 +231,15 @@ void CharsIdentify::classifyChineseGray(std::vector& 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& charVec){ size_t charVecSize = charVec.size(); @@ -219,15 +247,20 @@ void CharsIdentify::classifyChinese(std::vector& 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); @@ -252,6 +285,8 @@ void CharsIdentify::classifyChinese(std::vector& charVec){ isChinese = false; } + // 计算索引值,并使用该索引从kChars数组中获取对应的字符。 + // 同时,通过键值对(kv_)查找与该字符对应的省份。 auto index = result + kCharsTotalNumber - kChineseNumber; const char* key = kChars[index]; std::string s = key; @@ -260,18 +295,24 @@ void CharsIdentify::classifyChinese(std::vector& 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) { @@ -309,20 +350,27 @@ 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(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; @@ -338,8 +386,10 @@ bool CharsIdentify::isCharacter(cv::Mat input, std::string& label, float& maxVal else return false; } - +// 用于识别输入的图像数据是否是一个中文字符。 std::pair 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; @@ -347,6 +397,8 @@ std::pair 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(j); //std::cout << "j:" << j << "val:" << val << std::endl; @@ -357,6 +409,8 @@ std::pair CharsIdentify::identifyChinese(cv::Mat input } // no match + // 如果索引值为-1(即没有匹配的字符), + // 则将result设置为0,maxVal设置为0,并将isChinese设置为false,表示输入的字符不是中文。 if (-1 == result) { result = 0; maxVal = 0; @@ -365,7 +419,7 @@ std::pair 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; @@ -374,15 +428,18 @@ std::pair CharsIdentify::identifyChinese(cv::Mat input return std::make_pair(s, province); } - +// 从输入的图像(可能是一个灰度图像)中识别出可能的中文字符。 std::pair 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(j); //std::cout << "j:" << j << "val:" << val << std::endl; @@ -392,6 +449,8 @@ std::pair CharsIdentify::identifyChineseGray(cv::Mat i } } // no match + // 如果索引值为-1(即没有匹配的字符), + // 则将result设置为0,maxVal设置为0,并将isChinese设置为false,表示输入的字符不是中文 if (-1 == result) { result = 0; maxVal = 0; @@ -399,6 +458,7 @@ std::pair 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; @@ -407,11 +467,15 @@ std::pair CharsIdentify::identifyChineseGray(cv::Mat i return std::make_pair(s, province); } - +// 用于识别输入的图像数据是否是一个字符。 std::pair 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(classify(feature, maxVal, isChinese, isAlphabet)); + // 检查索引值index是否小于字符集大小kCharactersNumber。如果是,则返回由相同字符组成的pair; + // 否则,获取索引对应的字符作为键,并使用键值对kv_查找对应的省份。 if (index < kCharactersNumber) { return std::make_pair(kChars[index], kChars[index]); } @@ -423,10 +487,14 @@ std::pair CharsIdentify::identify(cv::Mat input, bool } } +// 用于处理一组输入的图像数据并识别出对应的字符和省份。 +// 函数参数包括输入图像数据(inputs),输出结果(outputs)以及一个布尔值向量(isChineseVec) int CharsIdentify::identify(std::vector inputs, std::vector>& outputs, std::vector 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); @@ -435,8 +503,13 @@ int CharsIdentify::identify(std::vector inputs, std::vector maxIndexs; std::vector 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) { -- 2.34.1 From 0212f4e481b182ec2ba622d6dad33fb0b8fa9ea5 Mon Sep 17 00:00:00 2001 From: zoeda <2063629468@qq.com> Date: Mon, 4 Dec 2023 13:05:19 +0800 Subject: [PATCH 2/2] =?UTF-8?q?doc/=E9=A1=B9=E7=9B=AE=E5=90=8D=E7=A7=B0+?= =?UTF-8?q?=E6=B3=9B=E8=AF=BB=E6=8A=A5=E5=91=8A.docx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/椤圭洰鍚嶇О+娉涜鎶ュ憡.docx | Bin 0 -> 16869 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/椤圭洰鍚嶇О+娉涜鎶ュ憡.docx b/doc/椤圭洰鍚嶇О+娉涜鎶ュ憡.docx index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ab6c6b749ca1a4391da3f795bf39647064a9564c 100644 GIT binary patch literal 16869 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 aKLx0)Bm|H>eybFR19Sm}EP}vqZ~q^9djJCf literal 0 HcmV?d00001 -- 2.34.1