Add support for TM_SQDIFF_NORMED and TM_CCOEFF_NORMED for pattern match

This commit is contained in:
WarmUpTill
2023-02-15 22:07:19 +01:00
committed by WarmUpTill
parent ac68fc016d
commit a0550ca141
8 changed files with 118 additions and 26 deletions

View File

@@ -50,6 +50,15 @@ const static std::map<VideoInput::Type, std::string> videoInputTypes = {
"AdvSceneSwitcher.condition.video.type.scene"},
};
const static std::map<cv::TemplateMatchModes, std::string> patternMatchModes = {
{cv::TemplateMatchModes::TM_CCOEFF_NORMED,
"AdvSceneSwitcher.condition.video.patternMatchMode.correlationCoefficient"},
{cv::TemplateMatchModes::TM_CCORR_NORMED,
"AdvSceneSwitcher.condition.video.patternMatchMode.crossCorrelation"},
{cv::TemplateMatchModes::TM_SQDIFF_NORMED,
"AdvSceneSwitcher.condition.video.patternMatchMode.squaredDifference"},
};
const static std::map<tesseract::PageSegMode, std::string> pageSegModes = {
{tesseract::PageSegMode::PSM_SINGLE_COLUMN,
"AdvSceneSwitcher.condition.video.ocrMode.singleColumn"},
@@ -240,7 +249,8 @@ bool MacroConditionVideo::ScreenshotContainsPattern()
cv::Mat result;
matchPattern(_screenshotData.image, _patternImageData,
_patternMatchParameters.threshold, result,
_patternMatchParameters.useAlphaAsMask);
_patternMatchParameters.useAlphaAsMask,
_patternMatchParameters.matchMode);
return countNonZero(result) > 0;
}
@@ -251,7 +261,8 @@ bool MacroConditionVideo::OutputChanged()
_patternImageData = createPatternData(_matchImage);
matchPattern(_screenshotData.image, _patternImageData,
_patternMatchParameters.threshold, result,
_patternMatchParameters.useAlphaAsMask);
_patternMatchParameters.useAlphaAsMask,
_patternMatchParameters.matchMode);
return countNonZero(result) == 0;
}
return _screenshotData.image != _matchImage;
@@ -357,6 +368,14 @@ static inline void populatePageSegModeSelection(QComboBox *list)
}
}
static inline void populatePatternMatchModeSelection(QComboBox *list)
{
for (const auto &[mode, name] : patternMatchModes) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(mode));
}
}
MacroConditionVideoEdit::MacroConditionVideoEdit(
QWidget *parent, std::shared_ptr<MacroConditionVideo> entryData)
: QWidget(parent),
@@ -378,6 +397,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
"AdvSceneSwitcher.condition.video.patternThresholdDescription"))),
_useAlphaAsMask(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.patternThresholdUseAlphaAsMask"))),
_patternMatchModeLayout(new QHBoxLayout()),
_patternMatchMode(new QComboBox()),
_brightnessThreshold(new SliderSpinBox(
0., 1.,
obs_module_text(
@@ -425,6 +446,9 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
_imagePath->Button()->disconnect();
_usePatternForChangedCheck->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip"));
_patternMatchMode->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchMode.tip"));
populatePatternMatchModeSelection(_patternMatchMode);
_minNeighbors->setMinimum(minMinNeighbors);
_minNeighbors->setMaximum(maxMinNeighbors);
populatePageSegModeSelection(_pageSegMode);
@@ -457,6 +481,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
this, SLOT(PatternThresholdChanged(double)));
QWidget::connect(_useAlphaAsMask, SIGNAL(stateChanged(int)), this,
SLOT(UseAlphaAsMaskChanged(int)));
QWidget::connect(_patternMatchMode, SIGNAL(currentIndexChanged(int)),
this, SLOT(PatternMatchModeChanged(int)));
QWidget::connect(_brightnessThreshold,
SIGNAL(DoubleValueChanged(double)), this,
SLOT(BrightnessThresholdChanged(double)));
@@ -523,9 +549,14 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
{"{{textColor}}", _textColor},
{"{{selectColor}}", _selectColor},
{"{{textType}}", _pageSegMode},
{"{{patternMatchingModes}}", _patternMatchMode},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.condition.video.entry"),
entryLine1Layout, widgetPlaceholders);
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchMode"),
_patternMatchModeLayout, widgetPlaceholders);
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.video.entry.modelPath"),
@@ -583,6 +614,7 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
mainLayout->addWidget(_usePatternForChangedCheck);
mainLayout->addWidget(_patternThreshold);
mainLayout->addWidget(_useAlphaAsMask);
mainLayout->addLayout(_patternMatchModeLayout);
mainLayout->addWidget(_brightnessThreshold);
mainLayout->addWidget(_currentBrightness);
mainLayout->addLayout(_ocrLayout);
@@ -812,8 +844,7 @@ void MacroConditionVideoEdit::UsePatternForChangedCheckChanged(int value)
std::lock_guard<std::mutex> lock(GetSwitcher()->m);
_entryData->_patternMatchParameters.useForChangedCheck = value;
_patternThreshold->setVisible(value);
adjustSize();
SetWidgetVisibility();
}
void MacroConditionVideoEdit::PatternThresholdChanged(double value)
@@ -851,6 +882,20 @@ void MacroConditionVideoEdit::UseAlphaAsMaskChanged(int value)
_entryData->_patternMatchParameters);
}
void MacroConditionVideoEdit::PatternMatchModeChanged(int idx)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(GetSwitcher()->m);
_entryData->_patternMatchParameters.matchMode =
static_cast<cv::TemplateMatchModes>(
_patternMatchMode->itemData(idx).toInt());
_previewDialog.PatternMatchParamtersChanged(
_entryData->_patternMatchParameters);
}
void MacroConditionVideoEdit::BrightnessThresholdChanged(double value)
{
if (_loading || !_entryData) {
@@ -1132,6 +1177,8 @@ void MacroConditionVideoEdit::SetWidgetVisibility()
_patternThreshold->setVisible(needsThreshold(_entryData->_condition));
_useAlphaAsMask->setVisible(_entryData->_condition ==
VideoCondition::PATTERN);
setLayoutVisible(_patternMatchModeLayout,
_entryData->_condition == VideoCondition::PATTERN);
_brightnessThreshold->setVisible(_entryData->_condition ==
VideoCondition::BRIGHTNESS);
_currentBrightness->setVisible(_entryData->_condition ==
@@ -1160,6 +1207,9 @@ void MacroConditionVideoEdit::SetWidgetVisibility()
_entryData->_condition == VideoCondition::HAS_NOT_CHANGED) {
_patternThreshold->setVisible(
_entryData->_patternMatchParameters.useForChangedCheck);
setLayoutVisible(
_patternMatchModeLayout,
_entryData->_patternMatchParameters.useForChangedCheck);
}
adjustSize();
@@ -1197,6 +1247,8 @@ void MacroConditionVideoEdit::UpdateEntryData()
_entryData->_patternMatchParameters.threshold);
_useAlphaAsMask->setChecked(
_entryData->_patternMatchParameters.useAlphaAsMask);
_patternMatchMode->setCurrentIndex(_patternMatchMode->findData(
_entryData->_patternMatchParameters.matchMode));
_brightnessThreshold->SetDoubleValue(_entryData->_brightnessThreshold);
_modelDataPath->SetPath(_entryData->GetModelDataPath().c_str());
_objectScaleThreshold->SetDoubleValue(

View File

@@ -113,6 +113,7 @@ private slots:
void UsePatternForChangedCheckChanged(int value);
void PatternThresholdChanged(double);
void UseAlphaAsMaskChanged(int value);
void PatternMatchModeChanged(int value);
void BrightnessThresholdChanged(double);
@@ -159,6 +160,8 @@ private:
SliderSpinBox *_patternThreshold;
QCheckBox *_useAlphaAsMask;
QHBoxLayout *_patternMatchModeLayout;
QComboBox *_patternMatchMode;
SliderSpinBox *_brightnessThreshold;
QLabel *_currentBrightness;

View File

@@ -18,8 +18,19 @@ PatternImageData createPatternData(QImage &pattern)
return data;
}
static void invertPatternMatchResult(cv::Mat &mat)
{
for (int r = 0; r < mat.rows; r++) {
for (int c = 0; c < mat.cols; c++) {
float value = mat.at<float>(r, c) =
1.0 - mat.at<float>(r, c);
}
}
}
void matchPattern(QImage &img, const PatternImageData &patternData,
double threshold, cv::Mat &result, bool useAlphaAsMask)
double threshold, cv::Mat &result, bool useAlphaAsMask,
cv::TemplateMatchModes matchMode)
{
if (img.isNull() || patternData.rgbaPattern.empty()) {
return;
@@ -29,34 +40,44 @@ void matchPattern(QImage &img, const PatternImageData &patternData,
return;
}
auto i = QImageToMat(img);
auto input = QImageToMat(img);
if (useAlphaAsMask) {
std::vector<cv::Mat1b> rgbaChannelsImage;
cv::split(i, rgbaChannelsImage);
// Remove alpha channel of input image as the alpha channel
// information is used as a stencil for the pattern instead and
// thus should not be used while matching the pattern as well
//
// Input format is Format_RGBA8888 so discard the 4th channel
std::vector<cv::Mat1b> inputChannels;
cv::split(input, inputChannels);
std::vector<cv::Mat1b> rgbChanlesImage(
rgbaChannelsImage.begin(),
rgbaChannelsImage.begin() + 3);
cv::Mat3b rgbImage;
cv::merge(rgbChanlesImage, rgbImage);
cv::matchTemplate(rgbImage, patternData.rgbPattern, result,
cv::TM_CCORR_NORMED, patternData.mask);
cv::threshold(result, result, threshold, 0, cv::THRESH_TOZERO);
inputChannels.begin(), inputChannels.begin() + 3);
cv::Mat3b rgbInput;
cv::merge(rgbChanlesImage, rgbInput);
cv::matchTemplate(rgbInput, patternData.rgbPattern, result,
matchMode, patternData.mask);
} else {
cv::matchTemplate(i, patternData.rgbaPattern, result,
cv::TM_CCOEFF_NORMED);
cv::threshold(result, result, threshold, 0, cv::THRESH_TOZERO);
cv::matchTemplate(input, patternData.rgbaPattern, result,
matchMode);
}
// A perfect match is represented as "0" for TM_SQDIFF_NORMED
//
// For TM_CCOEFF_NORMED and TM_CCORR_NORMED a perfect match is
// represented as "1"
if (matchMode == cv::TM_SQDIFF_NORMED) {
invertPatternMatchResult(result);
}
cv::threshold(result, result, threshold, 0.0, cv::THRESH_TOZERO);
}
void matchPattern(QImage &img, QImage &pattern, double threshold,
cv::Mat &result, bool useAlphaAsMask)
cv::Mat &result, bool useAlphaAsMask,
cv::TemplateMatchModes matchColor)
{
auto data = createPatternData(pattern);
matchPattern(img, data, threshold, result, useAlphaAsMask);
matchPattern(img, data, threshold, result, useAlphaAsMask, matchColor);
}
std::vector<cv::Rect> matchObject(QImage &img, cv::CascadeClassifier &cascade,

View File

@@ -47,10 +47,11 @@ struct PatternImageData {
PatternImageData createPatternData(QImage &pattern);
void matchPattern(QImage &img, const PatternImageData &patternData,
double threshold, cv::Mat &result,
bool useAlphaAsMask = true);
double threshold, cv::Mat &result, bool useAlphaAsMask,
cv::TemplateMatchModes matchMode);
void matchPattern(QImage &img, QImage &pattern, double threshold,
cv::Mat &result, bool useAlphaAsMask);
cv::Mat &result, bool useAlphaAsMask,
cv::TemplateMatchModes matchMode);
std::vector<cv::Rect> matchObject(QImage &img, cv::CascadeClassifier &cascade,
double scaleFactor, int minNeighbors,
cv::Size minSize, cv::Size maxSize);

View File

@@ -6,6 +6,7 @@ bool PatternMatchParameters::Save(obs_data_t *obj) const
obs_data_set_bool(data, "useForChangedCheck", useForChangedCheck);
obs_data_set_double(data, "threshold", threshold);
obs_data_set_bool(data, "useAlphaAsMask", useAlphaAsMask);
obs_data_set_int(data, "matchMode", matchMode);
obs_data_set_obj(obj, "patternMatchData", data);
obs_data_release(data);
return true;
@@ -25,6 +26,13 @@ bool PatternMatchParameters::Load(obs_data_t *obj)
useForChangedCheck = obs_data_get_bool(data, "useForChangedCheck");
threshold = obs_data_get_double(data, "threshold");
useAlphaAsMask = obs_data_get_bool(data, "useAlphaAsMask");
// TODO: Remove this fallback in a future version
if (!obs_data_has_user_value(obj, "matchMode")) {
matchMode = cv::TM_CCORR_NORMED;
} else {
matchMode = static_cast<cv::TemplateMatchModes>(
obs_data_get_int(data, "matchMode"));
}
obs_data_release(data);
return true;
}

View File

@@ -54,6 +54,7 @@ public:
QImage image;
bool useForChangedCheck = false;
bool useAlphaAsMask = false;
cv::TemplateMatchModes matchMode = cv::TM_CCORR_NORMED;
double threshold = 0.8;
};

View File

@@ -294,7 +294,8 @@ void PreviewImage::MarkMatch(QImage &screenshot,
cv::Mat result;
matchPattern(screenshot, patternImageData,
patternMatchParams.threshold, result,
patternMatchParams.useAlphaAsMask);
patternMatchParams.useAlphaAsMask,
patternMatchParams.matchMode);
if (countNonZero(result) == 0) {
emit StatusUpdate(obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchFail"));