You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
smokeboxidentification/src/CategoryMatcher.cpp

474 lines
14 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#pragma execution_character_set("utf-8")
#include "CategoryMatcher.h"
CategoryMatcher::CategoryMatcher(QObject *parent)
: QObject(parent)
{
m_ilmatch = ILMatchPtr(__uuidof(LMatch));
m_ilmatchF = ILMatchPtr(__uuidof(LMatch));
m_display = ILDisplayPtr(__uuidof(LDisplay));
initILMatchParam(QCoreApplication::applicationDirPath() + MATCHER_CONFIG);
setILMatch(m_matchParam);
qDebug() << "init CategoryMatcher successed";
}
CategoryMatcher::~CategoryMatcher()
{
}
void CategoryMatcher::initILMatchParam(const QString& path)
{
QString fileMyself = path;
QFile file(fileMyself);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
{
qWarning() << "打开文件失败!";
return;
}
QByteArray arr = file.readAll();
file.close();
if (arr.isEmpty())
{
qWarning() << "内容为空";
return;
}
QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(arr, &err);
if (doc.isEmpty())
{
qWarning() << err.errorString(); //打印失败信息
return;
}
QJsonObject jsMyself = doc.object();
QJsonObject MatchParamObj = jsMyself.value("MatchParam").toObject();
if (!MatchParamObj.isEmpty()) {
m_matchParam.MaxCount = MatchParamObj.value("MaxCount").toInt();
m_matchParam.ScaleTolerance = MatchParamObj.value("ScaleTolerance").toInt();
m_matchParam.AngleTolerance = MatchParamObj.value("AngleTolerance").toInt();
m_matchParam.AcceptScore = MatchParamObj.value("ScoreThresh").toInt();
m_matchParam.StrictScore = MatchParamObj.value("StrictScore").toBool();
m_matchParam.UseCache = MatchParamObj.value("UseCache").toBool();
}
}
void CategoryMatcher::setILMatch(const MatchParam& matchParam)
{
m_ilmatch->MaxCount = m_matchParam.MaxCount;
m_ilmatch->ScaleTolerance = m_matchParam.ScaleTolerance;
m_ilmatch->AngleTolerance = m_matchParam.AngleTolerance;
m_ilmatch->StrictScore = m_matchParam.StrictScore;
m_ilmatch->UseCache = m_matchParam.UseCache;
m_ilmatch->AcceptScore = m_matchParam.AcceptScore;
m_ilmatchF->MaxCount = m_matchParam.MaxCount;
m_ilmatchF->ScaleTolerance = m_matchParam.ScaleTolerance;
m_ilmatchF->AngleTolerance = m_matchParam.AngleTolerance;
m_ilmatchF->StrictScore = m_matchParam.StrictScore;
m_ilmatchF->UseCache = m_matchParam.UseCache;
m_ilmatchF->AcceptScore = m_matchParam.AcceptScore;
}
bool CategoryMatcher::smokeMatch(const QString& typeNo, ILImagePtr image)
{
std::pair<int, QVector<ILMatchResultPtr>> templateLevelAndvecMatchRltPtr;
bool bRlt = smokeMatch(typeNo, image, templateLevelAndvecMatchRltPtr);
drawImage(image, templateLevelAndvecMatchRltPtr);
return bRlt;
}
bool CategoryMatcher::smokeMatch(const QString& typeNo, const cv::Mat& inputMat, cv::Mat& rltMat)
{
if (inputMat.empty())
{
qWarning() << "inputMat is empty";
return false;
}
ILImagePtr ilImgGray(__uuidof(LImage));
cv::Mat imgMatGray;
ensureGrayImg(inputMat, imgMatGray);
qDebug() << "ensureGrayImg OK!";
ilImgGray->SetImageData(imgMatGray.cols, imgMatGray.rows, (void*)imgMatGray.data, imgMatGray.step, 0);
qDebug() << "LPV SetImageData OK!";
std::pair<int, QVector<ILMatchResultPtr>> templateLevelAndvecMatchRltPtr;
bool bRlt = smokeMatch(typeNo, ilImgGray, templateLevelAndvecMatchRltPtr);
drawImage(imgMatGray, rltMat, templateLevelAndvecMatchRltPtr);
return bRlt;
}
bool CategoryMatcher::smokeMatch(const QString& typeNo, const ILImagePtr ilImgPtr,
std::pair<int, QVector<ILMatchResultPtr>>& templateLevelAndvecMatchRltPtr)
{
TemplateObject templateObj;
QString templateImgFolderPath = TYPE_TEMPLATE_PATH;
//可能存在问题:之前的模板设计。后缀带了别的数据。需验证
// 模板文件格式:烟箱编码/烟箱编码-模板编码
// 模板编码:模板类型+模板编号
// 模板类型1为大模板模板和匹配图均放缩50%2为小模板
// 例如1类模板的第1个模板11
// 中华硬310102的2类模板的第1个模板310102-21
QString folderPath = QString("%1\\%2").arg(templateImgFolderPath).arg(typeNo);
if (getTemplateImgFileInfo(folderPath, templateObj))
{
qDebug() << "Get TemplateImg OK!";
QVector<double> vecMatchSroreMax;
QVector<ILMatchResultPtr> vecMatchRltsMax;
QList<int> keys = templateObj.uniqueKeys();
int templateLevel = keys.length();
qDebug() << "templateLevel = " << templateLevel;
templateLevelAndvecMatchRltPtr.first = templateLevel;
// 遍历不同模板级别
for each (int level in keys)
{
qInfo() << "level = " << level;
QVector<double> vecMatchSrore;
QVector<ILMatchResultPtr> vecMatchRlts;
QMap<QString, int> strScoreMap = templateObj.value(level);
for (QMap<QString, int>::iterator it = strScoreMap.begin(); it != strScoreMap.end(); ++it)
{
QString templateImgPath = it.key();
qInfo() << "templateImgPath = "<< templateImgPath;
ILImagePtr ilTemplatePtr(__uuidof(LImage));
ilTemplatePtr->Load(templateImgPath.toStdString().c_str());
if (ilTemplatePtr->Void())
{
QString outStr = templateImgPath + " is empty!";
qDebug() << outStr;
continue;
}
m_ilmatch->Learn(ilTemplatePtr, nullptr);
ILImagePtr img(__uuidof(LImage));
ILImagePtr tempImage(__uuidof(LImage));
ILImageArithmPtr imgArithmF(__uuidof(LImageArithm));
imgArithmF->Flip(ilTemplatePtr, LPVFlipType::LPVFlipH, tempImage);
imgArithmF->Flip(tempImage, LPVFlipType::LPVFlipV, tempImage);
m_ilmatchF->Learn(tempImage, nullptr);
ILMatchResultsPtr matchResults, matchResultsF;
if (level == 1) // 大模板
{
qDebug() << "Level == 1";
ILImageArithmPtr imgArithm(__uuidof(LImageArithm));
imgArithm->Resize(ilImgPtr, 0.5, 0.5, LPVInterNearest, img);
}
LPVErrorCode err = m_ilmatch->Match((level == 1 ? img : ilImgPtr), nullptr, &matchResults);
LPVErrorCode errF = m_ilmatchF->Match((level == 1 ? img : ilImgPtr), nullptr, &matchResultsF);
if (err < LPVErrorCode::LPVNoError || errF < LPVErrorCode::LPVNoError) {
continue; // match failed
}
int objCount = matchResults->Count() + matchResultsF->Count();
if (objCount > 0)
{
double valueMaxScore = -1;
ILMatchResultPtr matchRltPtr;
int indexMaxScore = 0;
QVector<double> matchSrore;
// 模板方向的匹配结果——旋转180度匹配结果组成一个完整的匹配结果值
for (int i = 0; i < matchResults->Count(); i++)
{
matchSrore.push_back(matchResults->Item(i)->Score);
}
for (int i = 0; i < matchResultsF->Count(); i++)
{
matchSrore.push_back(matchResultsF->Item(i)->Score);
}
//选择评分最高的匹配结果
auto maxPos = std::max_element(matchSrore.begin(), matchSrore.end());
valueMaxScore = *maxPos;
indexMaxScore = maxPos - matchSrore.begin();
vecMatchSrore.push_back(valueMaxScore);
if (indexMaxScore < matchResults->Count())
{
vecMatchRlts.push_back(matchResults->Item(indexMaxScore));
}
else
{
vecMatchRlts.push_back(matchResultsF->Item(indexMaxScore - matchResults->Count()));
}
}
}
if (vecMatchSrore.isEmpty())
{
break;
}
else
{
// 选择该模板等级下评分最高的匹配结果
auto maxMatchScorePos = std::max_element(vecMatchSrore.begin(), vecMatchSrore.end());
auto maxIndex = maxMatchScorePos - vecMatchSrore.begin();
vecMatchSroreMax.push_back(*maxMatchScorePos);
vecMatchRltsMax.push_back(vecMatchRlts.at(maxIndex));
}
}
if (vecMatchSroreMax.isEmpty() || (vecMatchSroreMax.length() != templateLevel))
{
QString outStr = QString("Level: %1 match failed!")
.arg(vecMatchSroreMax.length() + 1);
qDebug() << outStr;
templateLevelAndvecMatchRltPtr.second = vecMatchRltsMax;
return false;
}
else
{
templateLevelAndvecMatchRltPtr.second = vecMatchRltsMax;
}
}
else
{
QString outStr = QString("%1 find templateImage failed!").arg(folderPath);
qDebug() << outStr;
return false;
}
return true;
}
bool CategoryMatcher::getTemplateImgFileInfo(const QString& folderPath, TemplateObject& templateObject)
{
templateObject.clear();
QDir fileDir(folderPath);
if (!fileDir.exists())
{
qWarning() << folderPath << " is not exist";
return false;
}
for each(QFileInfo imgFileInfo in fileDir.entryInfoList(QDir::Filter::Files | QDir::NoDotAndDotDot))
{
QString fileName = imgFileInfo.fileName();
if (fileName.endsWith(".png") || fileName.endsWith(".PNG")
|| fileName.endsWith(".BMP") || fileName.endsWith(".bmp")
|| fileName.endsWith(".jpeg") || fileName.endsWith(".JPEG")
|| fileName.endsWith(".jpg") || fileName.endsWith(".JPG"))
{
QStringList strList = imgFileInfo.baseName().split("-");
QString templateImgPath = imgFileInfo.filePath(); //相对路径,避免路径存在中文时读取失败
int level = strList.last().mid(0, 1).toInt();
templateObject[level].insert(templateImgPath, 70);
////之前的模板格式例如templateImg/51520312/template_0101_65
//if (strList.first() == "template" && strList.length() == 3)
//{
// int scoreThresh = strList.last().toInt();
// QString templateImgPath = imgFileInfo.filePath(); //相对路径,避免路径存在中文时读取失败
// std::pair<QString, int> imgScorePair(templateImgPath, scoreThresh);
// QString serialNum = strList[1];
// if (serialNum.length() == 4)
// {
// int level = serialNum.mid(0, 2).toInt();
// templateObject[level].insert(templateImgPath, scoreThresh);
// }
//}
}
}
if (templateObject.isEmpty())
{
return false;
}
return true;
}
void CategoryMatcher::drawImage(ILImagePtr image, std::pair<int, QVector<ILMatchResultPtr>> templateLevelAndvecMatchRltPtr)
{
if (image->Height == 0 || image->Width == 0)
{
return;
}
int level = 1;
int templateLevel = templateLevelAndvecMatchRltPtr.first;
QVector<ILMatchResultPtr> vecMatchRlts = templateLevelAndvecMatchRltPtr.second;
QString rltStr;
cv::Scalar textColor(0, 255, 0);
if (!vecMatchRlts.isEmpty() && (vecMatchRlts.length() == templateLevel))
{
rltStr = QString("Match successful! Total level: %1").arg(templateLevel);
}
else
{
textColor = cv::Scalar(0, 0, 255);
rltStr = QString("Match failed! Total level: %1").arg(templateLevel);
}
//在Display上绘制图像+结果
m_display->SetImage(image);
//m_display->AddObject(rltStr,0);
rltStr.clear();
for each(ILMatchResultPtr matchRltPtr in vecMatchRlts)
{
// 存在问题如果不存在1级模板直接是2级模板
double score = matchRltPtr->GetScore();
rltStr.append(QString(" Level %1 score: %2").arg(level++).arg(score));
//存在的问题——大模板的时候匹配的得到的结果是放缩后的结果,需要对位置进行扩大
m_display->AddObject(matchRltPtr, LPVPatDrawFlags::LPVPatDrawBoundingRect | LPVPatDrawFlags::LPVPatDrawCenter);
}
if (!rltStr.isEmpty())
{
//m_display->AddObject(rltStr, 0);
}
}
void CategoryMatcher::saveResultsImage(const QString& filePath)
{
std::wstring str = filePath.toStdWString();
m_display->SaveSceneToFile(str.c_str(), 1, 1);
}
void CategoryMatcher::drawImage(const cv::Mat& imgMatSrc, cv::Mat& rltCvImg,
std::pair<int, QVector<ILMatchResultPtr>> templateLevelAndvecMatchRltPtr)
{
if (imgMatSrc.empty()) {
return;
}
ensureColorBGRImg(imgMatSrc, rltCvImg);
int level = 1;
int templateLevel = templateLevelAndvecMatchRltPtr.first;
QVector<ILMatchResultPtr> vecMatchRltPtr = templateLevelAndvecMatchRltPtr.second;
QString rltStr;
cv::Scalar textColor(0, 255, 0);
if (!vecMatchRltPtr.isEmpty() && (vecMatchRltPtr.length() == templateLevel))
{
rltStr = QString("Match successful! Total level: %1").arg(templateLevel);
}
else
{
textColor = cv::Scalar(0, 0, 255);
rltStr = QString("Match failed! Total level: %1").arg(templateLevel);
}
int fontFace = cv::FONT_HERSHEY_DUPLEX;
double fontScale = 2.5;
int textThickness = 2;
int baseLine = 0;
int startX = 50;
int startY = 100;
cv::Size textSize = cv::getTextSize(rltStr.toStdString(), fontFace, fontScale, textThickness, &baseLine);
cv::putText(rltCvImg, rltStr.toStdString(), cv::Point(startX, startY),
fontFace, fontScale, textColor, textThickness);
rltStr.clear();
for each(ILMatchResultPtr matchRltPtr in vecMatchRltPtr)
{
double score = matchRltPtr->GetScore();
rltStr.append(QString(" Level %1 score: %2").arg(level++).arg(score));
ILRectPtr ilRectPtr = matchRltPtr->GetRect();
ILPointsPtr ilPointsPtr = ilRectPtr->GetPoints();
cv::Point2f vertices[4];
int count = ilPointsPtr->Count();
if (count != 4)
{
continue;
}
for (int i = 0; i < count; ++i)
{
ILPointPtr ilPointPtr = ilPointsPtr->Item(i);
vertices[i] = cv::Point2f(ilPointPtr->GetX(), ilPointPtr->GetY());
}
for (int i = 0; i < 4; ++i)
{
cv::line(rltCvImg, vertices[i], vertices[(i + 1) % 4], cv::Scalar(255, 0, 0), 8);
}
}
if (!rltStr.isEmpty())
{
cv::putText(rltCvImg, rltStr.toStdString(),
cv::Point(startX, startY + textSize.height + 20),
fontFace, fontScale, textColor, textThickness);
}
}
bool ensureGrayImg(const cv::Mat& img, cv::Mat& gray)
{
if (img.channels() == 3)
{
if (img.depth() == CV_8U) {
cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
}
else {
cv::Mat channels[3];
split(img, channels);
gray = channels[2] * 0.299 + channels[1] * 0.587 + channels[0] * 0.114;
}
return true;
}
else if (img.channels() == 4)
{
if (img.depth() == CV_8U) {
cvtColor(img, gray, cv::COLOR_BGRA2GRAY);
}
else {
cv::Mat channels[4];
cv::split(img, channels);
gray = channels[2] * 0.299 + channels[1] * 0.587 + channels[0] * 0.114;
}
return true;
}
else if (img.channels() == 1)
{
gray = img;
return false;
}
else
{
_ASSERTE(false && "unsupported image conversion.");
return false;
}
}
bool ensureColorBGRImg(const cv::Mat& img, cv::Mat& color)
{
if (img.channels() == 3) {
color = img;
return false;
}
else if (img.channels() == 4) {
if (img.depth() == CV_8U) {
cvtColor(img, color, cv::COLOR_BGRA2BGR);
}
else {
static int from4_to3[] = { 0,0,1,1,2,2 };
if (color.cols != img.cols || color.rows != img.rows || color.type() != CV_MAKE_TYPE(img.depth(), 3))
color = cv::Mat(img.rows, img.cols, CV_MAKE_TYPE(img.depth(), 3));
mixChannels(img, color, from4_to3, 3);
}
return true;
}
else if (img.channels() == 1) {
if (img.depth() == CV_8U) {
cvtColor(img, color, cv::COLOR_GRAY2BGR);
}
else {
static int from1_to3[] = { 0,0,0,1,0,2 };
if (color.cols != img.cols || color.rows != img.rows || color.type() != CV_MAKE_TYPE(img.depth(), 3))
color = cv::Mat(img.rows, img.cols, CV_MAKE_TYPE(img.depth(), 3));
mixChannels(img, color, from1_to3, 3);
}
return true;
}
else {
_ASSERTE(false && "unsupported image conversion.");
return false;
}
}