diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 5e0d909e..4bcbb612 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -121,6 +121,7 @@ AdvSceneSwitcher.condition.video.askFileAction.file="Use existing file" AdvSceneSwitcher.condition.video.askFileAction.screenshot="Create screenshot" 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." AdvSceneSwitcher.condition.video.objectScaleThreshold="Scale factor: " AdvSceneSwitcher.condition.video.objectScaleThresholdDescription="A lower scale factor will lead to more matches but higher CPU load." AdvSceneSwitcher.condition.video.minNeighborDescription="A higher minimum neighbors value will result in fewer but higher quality matches." diff --git a/src/headers/macro-condition-video.hpp b/src/headers/macro-condition-video.hpp index 78cce213..125b39bc 100644 --- a/src/headers/macro-condition-video.hpp +++ b/src/headers/macro-condition-video.hpp @@ -44,6 +44,7 @@ public: OBSWeakSource _videoSource; VideoCondition _condition = VideoCondition::MATCH; std::string _file = obs_module_text("AdvSceneSwitcher.enterPath"); + bool _useAlphaAsMask = false; double _patternThreshold = 0.8; cv::CascadeClassifier _objectCascade; double _scaleFactor = 1.1; @@ -120,6 +121,7 @@ private slots: void ImagePathChanged(const QString &text); void ImageBrowseButtonClicked(); void PatternThresholdChanged(double); + void UseAlphaAsMaskChanged(int value); void ModelPathChanged(const QString &text); void ObjectScaleThresholdChanged(double); @@ -141,6 +143,7 @@ protected: FileSelection *_imagePath; ThresholdSlider *_patternThreshold; + QCheckBox *_useAlphaAsMask; FileSelection *_modelDataPath; QHBoxLayout *_modelPathLayout; diff --git a/src/macro-condition-video.cpp b/src/macro-condition-video.cpp index e0b506ac..0fc09b1b 100644 --- a/src/macro-condition-video.cpp +++ b/src/macro-condition-video.cpp @@ -100,6 +100,7 @@ bool MacroConditionVideo::Save(obs_data_t *obj) obs_data_set_int(obj, "condition", static_cast(_condition)); obs_data_set_string(obj, "filePath", _file.c_str()); obs_data_set_double(obj, "threshold", _patternThreshold); + obs_data_set_bool(obj, "useAlphaAsMask", _useAlphaAsMask); obs_data_set_string(obj, "modelDataPath", _modelDataPath.c_str()); obs_data_set_double(obj, "scaleFactor", _scaleFactor); obs_data_set_int(obj, "minNeighbors", _minNeighbors); @@ -132,6 +133,7 @@ bool MacroConditionVideo::Load(obs_data_t *obj) static_cast(obs_data_get_int(obj, "condition")); _file = obs_data_get_string(obj, "filePath"); _patternThreshold = obs_data_get_double(obj, "threshold"); + _useAlphaAsMask = obs_data_get_bool(obj, "useAlphaAsMask"); _modelDataPath = obs_data_get_string(obj, "modelDataPath"); _scaleFactor = obs_data_get_double(obj, "scaleFactor"); if (!isScaleFactorValid(_scaleFactor)) { @@ -181,8 +183,15 @@ bool MacroConditionVideo::LoadImageFromFile() _file.c_str()); return false; } - _matchImage = - _matchImage.convertToFormat(QImage::Format::Format_RGBX8888); + + if (_condition == VideoCondition::PATTERN && _useAlphaAsMask) { + _matchImage = _matchImage.convertToFormat( + QImage::Format::Format_RGBA8888); + } else { + _matchImage = _matchImage.convertToFormat( + QImage::Format::Format_RGBX8888); + } + return true; } @@ -193,7 +202,7 @@ bool MacroConditionVideo::LoadModelData(std::string &path) return !_objectCascade.empty(); } -// Assumption is that QImage uses Format_RGBX8888. +// Assumption is that QImage uses Format_RGBA8888. // Conversion from: https://github.com/dbzhang800/QtOpenCV cv::Mat QImageToMat(const QImage &img) { @@ -210,11 +219,11 @@ QImage MatToQImage(const cv::Mat &mat) return QImage(); } return QImage(mat.data, mat.cols, mat.rows, mat.step, - QImage::Format::Format_RGBX8888); + QImage::Format::Format_RGBA8888); } void matchPattern(QImage &img, QImage &pattern, double threshold, - cv::Mat &result) + cv::Mat &result, bool useAlphaAsMask = true) { if (img.isNull() || pattern.isNull()) { return; @@ -226,8 +235,36 @@ void matchPattern(QImage &img, QImage &pattern, double threshold, auto i = QImageToMat(img); auto p = QImageToMat(pattern); - cv::matchTemplate(i, p, result, cv::TM_CCOEFF_NORMED); - cv::threshold(result, result, threshold, 0, cv::THRESH_TOZERO); + if (useAlphaAsMask) { + std::vector rgbaChannelsPattern; + cv::split(p, rgbaChannelsPattern); + std::vector rgbChanlesPattern( + rgbaChannelsPattern.begin(), + rgbaChannelsPattern.begin() + 3); + + cv::Mat3b rgbPattern; + cv::merge(rgbChanlesPattern, rgbPattern); + cv::Mat1b mask; + cv::threshold(rgbaChannelsPattern[3], mask, 0, 255, + cv::THRESH_BINARY); + + std::vector rgbaChannelsImage; + cv::split(i, rgbaChannelsImage); + std::vector rgbChanlesImage( + rgbaChannelsImage.begin(), + rgbaChannelsImage.begin() + 3); + + cv::Mat3b rgbImage; + cv::merge(rgbChanlesImage, rgbImage); + + cv::matchTemplate(rgbImage, rgbPattern, result, + cv::TM_CCORR_NORMED, mask); + cv::threshold(result, result, threshold, 0, cv::THRESH_TOZERO); + + } else { + cv::matchTemplate(i, p, result, cv::TM_CCOEFF_NORMED); + cv::threshold(result, result, threshold, 0, cv::THRESH_TOZERO); + } } bool MacroConditionVideo::ScreenshotContainsPattern() @@ -358,6 +395,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit( "AdvSceneSwitcher.condition.video.patternThreshold"), obs_module_text( "AdvSceneSwitcher.condition.video.patternThresholdDescription")); + _useAlphaAsMask = new QCheckBox(obs_module_text( + "AdvSceneSwitcher.condition.video.patternThresholdUseAlphaAsMask")); _modelDataPath = new FileSelection(); _objectScaleThreshold = new ThresholdSlider( @@ -399,6 +438,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit( SLOT(ImageBrowseButtonClicked())); QWidget::connect(_patternThreshold, SIGNAL(DoubleValueChanged(double)), this, SLOT(PatternThresholdChanged(double))); + QWidget::connect(_useAlphaAsMask, SIGNAL(stateChanged(int)), this, + SLOT(UseAlphaAsMaskChanged(int))); QWidget::connect(_objectScaleThreshold, SIGNAL(DoubleValueChanged(double)), this, SLOT(ObjectScaleThresholdChanged(double))); @@ -474,6 +515,7 @@ MacroConditionVideoEdit::MacroConditionVideoEdit( QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(entryLine1Layout); mainLayout->addWidget(_patternThreshold); + mainLayout->addWidget(_useAlphaAsMask); mainLayout->addLayout(_modelPathLayout); mainLayout->addWidget(_objectScaleThreshold); mainLayout->addLayout(_neighborsControlLayout); @@ -528,11 +570,6 @@ void MacroConditionVideoEdit::SourceChanged(const QString &text) QString::fromStdString(_entryData->GetShortDesc())); } -bool needsPatternThreshold(VideoCondition cond) -{ - return cond == VideoCondition::PATTERN; -} - void MacroConditionVideoEdit::ConditionChanged(int cond) { if (_loading || !_entryData) { @@ -647,6 +684,17 @@ void MacroConditionVideoEdit::PatternThresholdChanged(double value) _entryData->_patternThreshold = value; } +void MacroConditionVideoEdit::UseAlphaAsMaskChanged(int value) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_useAlphaAsMask = value; + _entryData->LoadImageFromFile(); +} + void MacroConditionVideoEdit::ObjectScaleThresholdChanged(double value) { if (_loading || !_entryData) { @@ -737,7 +785,7 @@ QImage markPatterns(cv::Mat &matchResult, QImage &image, QImage &pattern) rectangle(matchImg, {col, row}, cv::Point(col + pattern.width(), row + pattern.height()), - cv::Scalar(255, 0, 0, 0), 2, 8, 0); + cv::Scalar(255, 0, 0, 255), 2, 8, 0); } } } @@ -751,7 +799,7 @@ QImage markObjects(QImage &image, std::vector &objects) rectangle(frame, cv::Point(objects[i].x, objects[i].y), cv::Point(objects[i].x + objects[i].width, objects[i].y + objects[i].height), - cv::Scalar(255, 0, 0, 0), 2, 8, 0); + cv::Scalar(255, 0, 0, 255), 2, 8, 0); } return MatToQImage(frame); } @@ -854,8 +902,10 @@ void setLayoutVisible(QLayout *layout, bool visible) void MacroConditionVideoEdit::SetWidgetVisibility() { _imagePath->setVisible(requiresFileInput(_entryData->_condition)); - _patternThreshold->setVisible( - needsPatternThreshold(_entryData->_condition)); + _patternThreshold->setVisible(_entryData->_condition == + VideoCondition::PATTERN); + _useAlphaAsMask->setVisible(_entryData->_condition == + VideoCondition::PATTERN); _showMatch->setVisible(needsShowMatch(_entryData->_condition)); _objectScaleThreshold->setVisible( needsObjectControls(_entryData->_condition)); @@ -885,6 +935,7 @@ void MacroConditionVideoEdit::UpdateEntryData() _condition->setCurrentIndex(static_cast(_entryData->_condition)); _imagePath->SetPath(QString::fromStdString(_entryData->_file)); _patternThreshold->SetDoubleValue(_entryData->_patternThreshold); + _useAlphaAsMask->setChecked(_entryData->_useAlphaAsMask); _modelDataPath->SetPath(_entryData->GetModelDataPath().c_str()); _objectScaleThreshold->SetDoubleValue(_entryData->_scaleFactor); _minNeighbors->setValue(_entryData->_minNeighbors);