From 0490c97ad950f632ad9ed797e640e862895d55e8 Mon Sep 17 00:00:00 2001 From: WarmUpTill Date: Sun, 31 Oct 2021 13:21:40 +0100 Subject: [PATCH] Add option to use pattern matching for "output changed" condition This will allow you to ignore minor changes in the video output, for example in noisy video streams. --- data/locale/en-US.ini | 2 + src/headers/macro-condition-video.hpp | 4 ++ src/macro-condition-video.cpp | 68 +++++++++++++++++++++++++-- 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 4b12cd20..f0642e94 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -120,6 +120,8 @@ AdvSceneSwitcher.condition.video.condition.object="contains object" AdvSceneSwitcher.condition.video.askFileAction="Do you want to use an existing file or create a screenshot of the currently selected source?" AdvSceneSwitcher.condition.video.askFileAction.file="Use existing file" AdvSceneSwitcher.condition.video.askFileAction.screenshot="Create screenshot" +AdvSceneSwitcher.condition.video.usePatternForChangedCheck="Use pattern matching" +AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip="This will allow you to control how much the image has to change for the condition to be true." AdvSceneSwitcher.condition.video.patternThreshold="Threshold: " AdvSceneSwitcher.condition.video.patternThresholdDescription="A higher threshold value means that the pattern needs to match the video source more closely." AdvSceneSwitcher.condition.video.patternThresholdUseAlphaAsMask="Use alpha channel as mask for pattern." diff --git a/src/headers/macro-condition-video.hpp b/src/headers/macro-condition-video.hpp index 2e5ac5b7..c85d2ed5 100644 --- a/src/headers/macro-condition-video.hpp +++ b/src/headers/macro-condition-video.hpp @@ -51,6 +51,7 @@ public: VideoCondition _condition = VideoCondition::MATCH; std::string _file = obs_module_text("AdvSceneSwitcher.enterPath"); bool _useAlphaAsMask = false; + bool _usePatternForChangedCheck = false; PatternMatchData _patternData; double _patternThreshold = 0.8; cv::CascadeClassifier _objectCascade; @@ -65,6 +66,7 @@ public: int _throttleCount = 3; private: + bool OutputChanged(); bool ScreenshotContainsPattern(); bool ScreenshotContainsObject(); bool Compare(); @@ -127,6 +129,7 @@ private slots: void ConditionChanged(int cond); void ImagePathChanged(const QString &text); void ImageBrowseButtonClicked(); + void UsePatternForChangedCheckChanged(int value); void PatternThresholdChanged(double); void UseAlphaAsMaskChanged(int value); @@ -148,6 +151,7 @@ protected: QComboBox *_videoSelection; QComboBox *_condition; + QCheckBox *_usePatternForChangedCheck; FileSelection *_imagePath; ThresholdSlider *_patternThreshold; QCheckBox *_useAlphaAsMask; diff --git a/src/macro-condition-video.cpp b/src/macro-condition-video.cpp index 8c2a2ac3..d885e48e 100644 --- a/src/macro-condition-video.cpp +++ b/src/macro-condition-video.cpp @@ -99,6 +99,8 @@ bool MacroConditionVideo::Save(obs_data_t *obj) GetWeakSourceName(_videoSource).c_str()); obs_data_set_int(obj, "condition", static_cast(_condition)); obs_data_set_string(obj, "filePath", _file.c_str()); + obs_data_set_bool(obj, "usePatternForChangedCheck", + _usePatternForChangedCheck); obs_data_set_double(obj, "threshold", _patternThreshold); obs_data_set_bool(obj, "useAlphaAsMask", _useAlphaAsMask); obs_data_set_string(obj, "modelDataPath", _modelDataPath.c_str()); @@ -132,6 +134,8 @@ bool MacroConditionVideo::Load(obs_data_t *obj) _condition = static_cast(obs_data_get_int(obj, "condition")); _file = obs_data_get_string(obj, "filePath"); + _usePatternForChangedCheck = + obs_data_get_bool(obj, "usePatternForChangedCheck"); _patternThreshold = obs_data_get_double(obj, "threshold"); _useAlphaAsMask = obs_data_get_bool(obj, "useAlphaAsMask"); _modelDataPath = obs_data_get_string(obj, "modelDataPath"); @@ -284,6 +288,18 @@ bool MacroConditionVideo::ScreenshotContainsPattern() return countNonZero(result) > 0; } +bool MacroConditionVideo::OutputChanged() +{ + if (_usePatternForChangedCheck) { + cv::Mat result; + _patternData = createPatternData(_matchImage); + matchPattern(_screenshotData->image, _patternData, + _patternThreshold, result, _useAlphaAsMask); + return countNonZero(result) == 0; + } + return _screenshotData->image != _matchImage; +} + std::vector matchObject(QImage &img, cv::CascadeClassifier &cascade, double scaleFactor, int minNeighbors, cv::Size minSize, cv::Size maxSize) @@ -319,9 +335,9 @@ bool MacroConditionVideo::Compare() case VideoCondition::DIFFER: return _screenshotData->image != _matchImage; case VideoCondition::HAS_CHANGED: - return _screenshotData->image != _matchImage; + return OutputChanged(); case VideoCondition::HAS_NOT_CHANGED: - return _screenshotData->image == _matchImage; + return !OutputChanged(); case VideoCondition::NO_IMAGE: return _screenshotData->image.isNull(); case VideoCondition::PATTERN: @@ -398,6 +414,10 @@ MacroConditionVideoEdit::MacroConditionVideoEdit( _imagePath = new FileSelection(); _imagePath->Button()->disconnect(); + _usePatternForChangedCheck = new QCheckBox(obs_module_text( + "AdvSceneSwitcher.condition.video.usePatternForChangedCheck")); + _usePatternForChangedCheck->setToolTip(obs_module_text( + "AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip")); _patternThreshold = new ThresholdSlider( 0., 1., obs_module_text( @@ -445,6 +465,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit( SLOT(ImagePathChanged(const QString &))); QWidget::connect(_imagePath->Button(), SIGNAL(clicked()), this, SLOT(ImageBrowseButtonClicked())); + QWidget::connect(_usePatternForChangedCheck, SIGNAL(stateChanged(int)), + this, SLOT(UsePatternForChangedCheckChanged(int))); QWidget::connect(_patternThreshold, SIGNAL(DoubleValueChanged(double)), this, SLOT(PatternThresholdChanged(double))); QWidget::connect(_useAlphaAsMask, SIGNAL(stateChanged(int)), this, @@ -523,6 +545,7 @@ MacroConditionVideoEdit::MacroConditionVideoEdit( showMatchLayout->addStretch(); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(entryLine1Layout); + mainLayout->addWidget(_usePatternForChangedCheck); mainLayout->addWidget(_patternThreshold); mainLayout->addWidget(_useAlphaAsMask); mainLayout->addLayout(_modelPathLayout); @@ -683,6 +706,18 @@ void MacroConditionVideoEdit::ImageBrowseButtonClicked() ImagePathChanged(path); } +void MacroConditionVideoEdit::UsePatternForChangedCheckChanged(int value) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_usePatternForChangedCheck = value; + _patternThreshold->setVisible(value); + adjustSize(); +} + void MacroConditionVideoEdit::PatternThresholdChanged(double value) { if (_loading || !_entryData) { @@ -828,9 +863,9 @@ void MacroConditionVideoEdit::ShowMatchClicked() } QImage markedIamge; - QImage pattern = _entryData->GetMatchImage(); if (_entryData->_condition == VideoCondition::PATTERN) { cv::Mat result; + QImage pattern = _entryData->GetMatchImage(); matchPattern(screenshot->image, pattern, _entryData->_patternThreshold, result, _entryData->_useAlphaAsMask); @@ -899,6 +934,19 @@ bool needsThrottleControls(VideoCondition cond) cond == VideoCondition::OBJECT; } +bool needsThreshold(VideoCondition cond) +{ + return cond == VideoCondition::PATTERN || + cond == VideoCondition::HAS_CHANGED || + cond == VideoCondition::HAS_NOT_CHANGED; +} + +bool patternControlIsOptional(VideoCondition cond) +{ + return cond == VideoCondition::HAS_CHANGED || + cond == VideoCondition::HAS_NOT_CHANGED; +} + void setLayoutVisible(QLayout *layout, bool visible) { for (int i = 0; i < layout->count(); ++i) { @@ -912,8 +960,9 @@ void setLayoutVisible(QLayout *layout, bool visible) void MacroConditionVideoEdit::SetWidgetVisibility() { _imagePath->setVisible(requiresFileInput(_entryData->_condition)); - _patternThreshold->setVisible(_entryData->_condition == - VideoCondition::PATTERN); + _usePatternForChangedCheck->setVisible( + patternControlIsOptional(_entryData->_condition)); + _patternThreshold->setVisible(needsThreshold(_entryData->_condition)); _useAlphaAsMask->setVisible(_entryData->_condition == VideoCondition::PATTERN); _showMatch->setVisible(needsShowMatch(_entryData->_condition)); @@ -931,6 +980,13 @@ void MacroConditionVideoEdit::SetWidgetVisibility() needsObjectControls(_entryData->_condition)); setLayoutVisible(_throttleControlLayout, needsThrottleControls(_entryData->_condition)); + + if (_entryData->_condition == VideoCondition::HAS_CHANGED || + _entryData->_condition == VideoCondition::HAS_NOT_CHANGED) { + _patternThreshold->setVisible( + _entryData->_usePatternForChangedCheck); + } + adjustSize(); } @@ -944,6 +1000,8 @@ void MacroConditionVideoEdit::UpdateEntryData() GetWeakSourceName(_entryData->_videoSource).c_str()); _condition->setCurrentIndex(static_cast(_entryData->_condition)); _imagePath->SetPath(QString::fromStdString(_entryData->_file)); + _usePatternForChangedCheck->setChecked( + _entryData->_usePatternForChangedCheck); _patternThreshold->SetDoubleValue(_entryData->_patternThreshold); _useAlphaAsMask->setChecked(_entryData->_useAlphaAsMask); _modelDataPath->SetPath(_entryData->GetModelDataPath().c_str());