diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 66cf8396..65ff31cf 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -160,9 +160,14 @@ AdvSceneSwitcher.condition.video.modelLoadFail="Model data could not be loaded!" AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{imagePath}}" AdvSceneSwitcher.condition.video.entry.modelPath="Model data (haar cascade classifier): {{modelDataPath}}" AdvSceneSwitcher.condition.video.entry.minNeighbor="Minimum neighbors: {{minNeighbors}}" -AdvSceneSwitcher.condition.video.entry.minSize="Minimum size: {{minSizeX}} x {{minSizeY}}" -AdvSceneSwitcher.condition.video.entry.maxSize="Maximum size: {{maxSizeX}} x {{maxSizeY}}" AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduce CPU load by performing check only every {{throttleCount}} milliseconds" +AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}Perform check only in area {{checkArea}}" +AdvSceneSwitcher.condition.video.minSize="Minimum size:" +AdvSceneSwitcher.condition.video.maxSize="Maximum size:" +AdvSceneSwitcher.condition.video.selectArea="Select area" +AdvSceneSwitcher.condition.video.selectArea.status="Only highlighted area will be checked" +AdvSceneSwitcher.condition.video.width="Width" +AdvSceneSwitcher.condition.video.height="Height" AdvSceneSwitcher.condition.stream="Streaming" AdvSceneSwitcher.condition.stream.state.start="Stream running" AdvSceneSwitcher.condition.stream.state.stop="Stream stopped" diff --git a/data/locale/tr-TR.ini b/data/locale/tr-TR.ini index c0ffd2aa..90faa727 100644 --- a/data/locale/tr-TR.ini +++ b/data/locale/tr-TR.ini @@ -146,8 +146,6 @@ AdvSceneSwitcher.condition.video.modelLoadFail="Model verileri yüklenemedi!" AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{imagePath}}" AdvSceneSwitcher.condition.video.entry.modelPath="Model verileri (haar kademeli sınıflandırıcı):{{modelDataPath}}" AdvSceneSwitcher.condition.video.entry.minNeighbor="Minimum komşular: {{minNeighbors}}" -AdvSceneSwitcher.condition.video.entry.minSize="Minimum boyut: {{minSizeX}} x {{minSizeY}}" -AdvSceneSwitcher.condition.video.entry.maxSize="Maximum boyut: {{maxSizeX}} x {{maxSizeY}}" AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}} Yalnızca her seferinde kontrol gerçekleştirerek CPU yükünü azaltın {{throttleCount}} millisaniyeler" AdvSceneSwitcher.condition.stream="Yayınlama" AdvSceneSwitcher.condition.stream.state.start="Yayın çalışıyor" @@ -275,7 +273,6 @@ AdvSceneSwitcher.action.audio.type.mute="Sessiz" AdvSceneSwitcher.action.audio.type.unmute="Ses açmak" AdvSceneSwitcher.action.audio.type.sourceVolume="Kaynak ses seviyesini ayarla" AdvSceneSwitcher.action.audio.type.masterVolume="Ana ses seviyesini ayarla" -AdvSceneSwitcher.action.audio.fade="{{fade}}karartmak {{duration}} saniye" AdvSceneSwitcher.action.audio.entry="{{actions}} {{audioSources}} {{volume}}" AdvSceneSwitcher.action.recording="Kayıt" AdvSceneSwitcher.action.recording.type.stop="Kayıt Durdur" diff --git a/data/locale/zh-CN.ini b/data/locale/zh-CN.ini index 8d1e5eac..e7a02c1c 100644 --- a/data/locale/zh-CN.ini +++ b/data/locale/zh-CN.ini @@ -31,7 +31,6 @@ AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="保存窗口位置和 AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications="显示系统托盘通知" AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="禁用UI提示" AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs="隐藏可通过宏操作的分组栏" -AdvSceneSwitcher.generalTab.generalBehavior.highlightExecutedMacros="突出显示最近执行的宏" AdvSceneSwitcher.generalTab.priority="优先级" AdvSceneSwitcher.generalTab.priority.description="切换场景优先级 (最上方的项优先级最高)" AdvSceneSwitcher.generalTab.priority.threadPriority="使用线程优先级" @@ -155,8 +154,6 @@ AdvSceneSwitcher.condition.video.modelLoadFail="无法加载模型数据!" AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{imagePath}}" AdvSceneSwitcher.condition.video.entry.modelPath="模型数据 (haar级联分类器): {{modelDataPath}}" AdvSceneSwitcher.condition.video.entry.minNeighbor="最小区域: {{minNeighbors}}" -AdvSceneSwitcher.condition.video.entry.minSize="最小尺寸: {{minSizeX}} x {{minSizeY}}" -AdvSceneSwitcher.condition.video.entry.maxSize="最大尺寸: {{maxSizeX}} x {{maxSizeY}}" AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}通过只执行每一次检查来减少CPU负载 {{throttleCount}} 毫秒" AdvSceneSwitcher.condition.stream="推流" AdvSceneSwitcher.condition.stream.state.start="推流启动" @@ -320,7 +317,6 @@ AdvSceneSwitcher.action.audio.type.mute="静音" AdvSceneSwitcher.action.audio.type.unmute="取消静音" AdvSceneSwitcher.action.audio.type.sourceVolume="来源音量" AdvSceneSwitcher.action.audio.type.masterVolume="主音量" -AdvSceneSwitcher.action.audio.fade="{{fade}}淡出 {{duration}} 秒" AdvSceneSwitcher.action.audio.entry="{{actions}} {{audioSources}} {{volume}}" AdvSceneSwitcher.action.recording="录制" AdvSceneSwitcher.action.recording.type.stop="停止录制" diff --git a/src/external-macro-modules/opencv/CMakeLists.txt b/src/external-macro-modules/opencv/CMakeLists.txt index 72e82bc7..023b55a9 100644 --- a/src/external-macro-modules/opencv/CMakeLists.txt +++ b/src/external-macro-modules/opencv/CMakeLists.txt @@ -17,6 +17,8 @@ endif() include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../headers") set(module_SOURCES + area-selection.cpp + area-selection.hpp macro-condition-video.cpp macro-condition-video.hpp video-match-dialog.cpp diff --git a/src/external-macro-modules/opencv/area-selection.cpp b/src/external-macro-modules/opencv/area-selection.cpp new file mode 100644 index 00000000..4d3f512f --- /dev/null +++ b/src/external-macro-modules/opencv/area-selection.cpp @@ -0,0 +1,130 @@ +#include "area-selection.hpp" +#include +#include +#include +#include + +void advss::Size::Save(obs_data_t *obj, const char *name) +{ + auto data = obs_data_create(); + obs_data_set_int(data, "width", width); + obs_data_set_int(data, "height", height); + obs_data_set_obj(obj, name, data); + obs_data_release(data); +} + +void advss::Size::Load(obs_data_t *obj, const char *name) +{ + auto data = obs_data_get_obj(obj, name); + width = obs_data_get_int(data, "width"); + height = obs_data_get_int(data, "height"); + obs_data_release(data); +} + +cv::Size advss::Size::CV() +{ + return {width, height}; +} + +void advss::Area::Save(obs_data_t *obj, const char *name) +{ + auto data = obs_data_create(); + obs_data_set_int(data, "x", x); + obs_data_set_int(data, "y", y); + obs_data_set_int(data, "width", width); + obs_data_set_int(data, "height", height); + obs_data_set_obj(obj, name, data); + obs_data_release(data); +} + +void advss::Area::Load(obs_data_t *obj, const char *name) +{ + auto data = obs_data_get_obj(obj, name); + x = obs_data_get_int(data, "x"); + y = obs_data_get_int(data, "y"); + width = obs_data_get_int(data, "width"); + height = obs_data_get_int(data, "height"); + obs_data_release(data); +} + +SizeSelection::SizeSelection(int min, int max, QWidget *parent) + : _x(new QSpinBox), _y(new QSpinBox), QWidget(parent) +{ + _x->setMinimum(min); + _y->setMinimum(min); + _x->setMaximum(max); + _y->setMaximum(max); + + connect(_x, SIGNAL(valueChanged(int)), this, SLOT(XChanged(int))); + connect(_y, SIGNAL(valueChanged(int)), this, SLOT(YChanged(int))); + + auto layout = new QHBoxLayout(); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(_x); + layout->addWidget(new QLabel(" x ")); + layout->addWidget(_y); + setLayout(layout); +} + +void SizeSelection::SetSize(const advss::Size &s) +{ + _x->setValue(s.width); + _y->setValue(s.height); +} + +advss::Size SizeSelection::Size() +{ + return advss::Size{_x->value(), _y->value()}; +} + +void SizeSelection::XChanged(int value) +{ + emit SizeChanged(advss::Size{value, _y->value()}); +} + +void SizeSelection::YChanged(int value) +{ + emit SizeChanged(advss::Size{_x->value(), value}); +} + +AreaSelection::AreaSelection(int min, int max, QWidget *parent) + : _x(new SizeSelection(min, max)), + _y(new SizeSelection(min, max)), + QWidget(parent) +{ + _x->_x->setToolTip("X"); + _x->_y->setToolTip("Y"); + _y->_x->setToolTip( + obs_module_text("AdvSceneSwitcher.condition.video.width")); + _y->_y->setToolTip( + obs_module_text("AdvSceneSwitcher.condition.video.height")); + + connect(_x, SIGNAL(SizeChanged(advss::Size)), this, + SLOT(XSizeChanged(advss::Size))); + connect(_y, SIGNAL(SizeChanged(advss::Size)), this, + SLOT(YSizeChanged(advss::Size))); + + auto layout = new QVBoxLayout(); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(_x); + layout->addWidget(_y); + setLayout(layout); +} + +void AreaSelection::SetArea(const advss::Area &value) +{ + _x->SetSize({value.x, value.y}); + _y->SetSize({value.width, value.height}); +} + +void AreaSelection::XSizeChanged(advss::Size value) +{ + emit AreaChanged(advss::Area{value.width, value.height, + _y->Size().width, _y->Size().height}); +} + +void AreaSelection::YSizeChanged(advss::Size value) +{ + emit AreaChanged(advss::Area{_x->Size().width, _x->Size().height, + value.width, value.height}); +} diff --git a/src/external-macro-modules/opencv/area-selection.hpp b/src/external-macro-modules/opencv/area-selection.hpp new file mode 100644 index 00000000..29ef39d3 --- /dev/null +++ b/src/external-macro-modules/opencv/area-selection.hpp @@ -0,0 +1,67 @@ +#pragma once +#include +#include +#include +#include + +namespace advss { + +struct Size { + void Save(obs_data_t *obj, const char *name); + void Load(obs_data_t *obj, const char *name); + cv::Size CV(); + + int width; + int height; +}; + +struct Area { + void Save(obs_data_t *obj, const char *name); + void Load(obs_data_t *obj, const char *name); + + int x; + int y; + int width; + int height; +}; + +} + +class SizeSelection : public QWidget { + Q_OBJECT + +public: + SizeSelection(int min, int max, QWidget *parent = 0); + void SetSize(const advss::Size &); + advss::Size Size(); + +private slots: + void XChanged(int); + void YChanged(int); +signals: + void SizeChanged(advss::Size value); + +private: + QSpinBox *_x; + QSpinBox *_y; + + friend class AreaSelection; +}; + +class AreaSelection : public QWidget { + Q_OBJECT + +public: + AreaSelection(int min, int max, QWidget *parent = 0); + void SetArea(const advss::Area &); + +private slots: + void XSizeChanged(advss::Size value); + void YSizeChanged(advss::Size value); +signals: + void AreaChanged(advss::Area value); + +private: + SizeSelection *_x; + SizeSelection *_y; +}; diff --git a/src/external-macro-modules/opencv/macro-condition-video.cpp b/src/external-macro-modules/opencv/macro-condition-video.cpp index 602647e0..63628617 100644 --- a/src/external-macro-modules/opencv/macro-condition-video.cpp +++ b/src/external-macro-modules/opencv/macro-condition-video.cpp @@ -110,12 +110,12 @@ bool MacroConditionVideo::Save(obs_data_t *obj) obs_data_set_string(obj, "modelDataPath", _modelDataPath.c_str()); obs_data_set_double(obj, "scaleFactor", _scaleFactor); obs_data_set_int(obj, "minNeighbors", _minNeighbors); - obs_data_set_int(obj, "minSizeX", _minSizeX); - obs_data_set_int(obj, "minSizeY", _minSizeY); - obs_data_set_int(obj, "maxSizeX", _maxSizeX); - obs_data_set_int(obj, "maxSizeY", _maxSizeY); + _minSize.Save(obj, "minSize"); + _maxSize.Save(obj, "maxSize"); obs_data_set_bool(obj, "throttleEnabled", _throttleEnabled); obs_data_set_int(obj, "throttleCount", _throttleCount); + obs_data_set_bool(obj, "checkAreaEnabled", _checkAreaEnable); + _checkArea.Save(obj, "checkArea"); return true; } @@ -151,13 +151,20 @@ bool MacroConditionVideo::Load(obs_data_t *obj) if (!isMinNeighborsValid(_minNeighbors)) { _minNeighbors = minMinNeighbors; } - _minSizeX = obs_data_get_int(obj, "minSizeX"); - _minSizeY = obs_data_get_int(obj, "minSizeY"); - _maxSizeX = obs_data_get_int(obj, "maxSizeX"); - _maxSizeY = obs_data_get_int(obj, "maxSizeY"); + // TODO: Remove this fallback in future version + if (obs_data_has_user_value(obj, "minSizeX")) { + _minSize.width = obs_data_get_int(obj, "minSizeX"); + _minSize.height = obs_data_get_int(obj, "minSizeY"); + _maxSize.width = obs_data_get_int(obj, "maxSizeX"); + _maxSize.height = obs_data_get_int(obj, "maxSizeY"); + } else { + _minSize.Load(obj, "minSize"); + _maxSize.Load(obj, "maxSize"); + } _throttleEnabled = obs_data_get_bool(obj, "throttleEnabled"); _throttleCount = obs_data_get_int(obj, "throttleCount"); - + _checkAreaEnable = obs_data_get_bool(obj, "checkAreaEnabled"); + _checkArea.Load(obj, "checkArea"); if (requiresFileInput(_condition)) { (void)LoadImageFromFile(); } @@ -230,14 +237,19 @@ bool MacroConditionVideo::OutputChanged() bool MacroConditionVideo::ScreenshotContainsObject() { auto objects = matchObject(_screenshotData.image, _objectCascade, - _scaleFactor, _minNeighbors, - {_minSizeX, _minSizeY}, - {_maxSizeX, _maxSizeY}); + _scaleFactor, _minNeighbors, _minSize.CV(), + _maxSize.CV()); return objects.size() > 0; } bool MacroConditionVideo::Compare() { + if (_checkAreaEnable && _condition != VideoCondition::NO_IMAGE) { + _screenshotData.image = _screenshotData.image.copy( + _checkArea.x, _checkArea.y, _checkArea.width, + _checkArea.height); + } + switch (_condition) { case VideoCondition::MATCH: return _screenshotData.image == _matchImage; @@ -269,54 +281,52 @@ static inline void populateConditionSelection(QComboBox *list) MacroConditionVideoEdit::MacroConditionVideoEdit( QWidget *parent, std::shared_ptr entryData) : QWidget(parent), - _matchDialog(this, entryData.get(), &GetSwitcher()->m) + _videoSelection(new QComboBox()), + _condition(new QComboBox()), + _imagePath(new FileSelection()), + _usePatternForChangedCheck(new QCheckBox(obs_module_text( + "AdvSceneSwitcher.condition.video.usePatternForChangedCheck"))), + _patternThreshold(new ThresholdSlider( + 0., 1., + obs_module_text( + "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( + 1.1, 5., + obs_module_text( + "AdvSceneSwitcher.condition.video.objectScaleThreshold"), + obs_module_text( + "AdvSceneSwitcher.condition.video.objectScaleThresholdDescription"))), + _minNeighbors(new QSpinBox()), + _minNeighborsDescription(new QLabel(obs_module_text( + "AdvSceneSwitcher.condition.video.minNeighborDescription"))), + _minSize(new SizeSelection(0, 1024)), + _maxSize(new SizeSelection(0, 4096)), + _checkAreaEnable(new QCheckBox()), + _checkArea(new AreaSelection(0, 99999)), + _throttleEnable(new QCheckBox()), + _throttleCount(new QSpinBox()), + _showMatch(new QPushButton(obs_module_text( + "AdvSceneSwitcher.condition.video.showMatch"))), + _matchDialog(this, entryData.get(), &GetSwitcher()->m), + _modelPathLayout(new QHBoxLayout), + _neighborsControlLayout(new QHBoxLayout), + _checkAreaControlLayout(new QHBoxLayout), + _throttleControlLayout(new QHBoxLayout), + _sizeLayout(new QHBoxLayout()) { - _videoSelection = new QComboBox(); - _condition = new QComboBox(); - - _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( - "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( - 1.1, 5., - obs_module_text( - "AdvSceneSwitcher.condition.video.objectScaleThreshold"), - obs_module_text( - "AdvSceneSwitcher.condition.video.objectScaleThresholdDescription")); - _minNeighbors = new QSpinBox(); _minNeighbors->setMinimum(minMinNeighbors); _minNeighbors->setMaximum(maxMinNeighbors); - _minNeighborsDescription = new QLabel(obs_module_text( - "AdvSceneSwitcher.condition.video.minNeighborDescription")); - _minSizeX = new QSpinBox(); - _minSizeY = new QSpinBox(); - _minSizeX->setMaximum(1024); - _minSizeY->setMaximum(1024); - _maxSizeX = new QSpinBox(); - _maxSizeY = new QSpinBox(); - _maxSizeX->setMaximum(4096); - _maxSizeY->setMaximum(4096); - - _throttleEnable = new QCheckBox(); - _throttleCount = new QSpinBox(); _throttleCount->setMinimum(1 * GetSwitcher()->interval); _throttleCount->setMaximum(10 * GetSwitcher()->interval); _throttleCount->setSingleStep(GetSwitcher()->interval); - _showMatch = new QPushButton( - obs_module_text("AdvSceneSwitcher.condition.video.showMatch")); QWidget::connect(_videoSelection, SIGNAL(currentTextChanged(const QString &)), this, @@ -338,14 +348,14 @@ MacroConditionVideoEdit::MacroConditionVideoEdit( SLOT(ObjectScaleThresholdChanged(double))); QWidget::connect(_minNeighbors, SIGNAL(valueChanged(int)), this, SLOT(MinNeighborsChanged(int))); - QWidget::connect(_minSizeX, SIGNAL(valueChanged(int)), this, - SLOT(MinSizeXChanged(int))); - QWidget::connect(_minSizeY, SIGNAL(valueChanged(int)), this, - SLOT(MinSizeYChanged(int))); - QWidget::connect(_maxSizeX, SIGNAL(valueChanged(int)), this, - SLOT(MaxSizeXChanged(int))); - QWidget::connect(_maxSizeY, SIGNAL(valueChanged(int)), this, - SLOT(MaxSizeYChanged(int))); + QWidget::connect(_minSize, SIGNAL(SizeChanged(advss::Size)), this, + SLOT(MinSizeChanged(advss::Size))); + QWidget::connect(_maxSize, SIGNAL(SizeChanged(advss::Size)), this, + SLOT(MaxSizeChanged(advss::Size))); + QWidget::connect(_checkAreaEnable, SIGNAL(stateChanged(int)), this, + SLOT(CheckAreaEnableChanged(int))); + QWidget::connect(_checkArea, SIGNAL(AreaChanged(advss::Area)), this, + SLOT(CheckAreaChanged(advss::Area))); QWidget::connect(_modelDataPath, SIGNAL(PathChanged(const QString &)), this, SLOT(ModelPathChanged(const QString &))); QWidget::connect(_throttleEnable, SIGNAL(stateChanged(int)), this, @@ -364,43 +374,46 @@ MacroConditionVideoEdit::MacroConditionVideoEdit( {"{{condition}}", _condition}, {"{{imagePath}}", _imagePath}, {"{{minNeighbors}}", _minNeighbors}, - {"{{minSizeX}}", _minSizeX}, - {"{{minSizeY}}", _minSizeY}, - {"{{maxSizeX}}", _maxSizeX}, - {"{{maxSizeY}}", _maxSizeY}, + {"{{minSize}}", _minSize}, + {"{{maxSize}}", _maxSize}, {"{{modelDataPath}}", _modelDataPath}, {"{{throttleEnable}}", _throttleEnable}, {"{{throttleCount}}", _throttleCount}, + {"{{checkAreaEnable}}", _checkAreaEnable}, + {"{{checkArea}}", _checkArea}, }; placeWidgets(obs_module_text("AdvSceneSwitcher.condition.video.entry"), entryLine1Layout, widgetPlaceholders); - - _modelPathLayout = new QHBoxLayout; placeWidgets( obs_module_text( "AdvSceneSwitcher.condition.video.entry.modelPath"), _modelPathLayout, widgetPlaceholders); - - _neighborsControlLayout = new QHBoxLayout; placeWidgets( obs_module_text( "AdvSceneSwitcher.condition.video.entry.minNeighbor"), _neighborsControlLayout, widgetPlaceholders); - - _minSizeControlLayout = new QHBoxLayout; - placeWidgets(obs_module_text( - "AdvSceneSwitcher.condition.video.entry.minSize"), - _minSizeControlLayout, widgetPlaceholders); - - _maxSizeControlLayout = new QHBoxLayout; - placeWidgets(obs_module_text( - "AdvSceneSwitcher.condition.video.entry.maxSize"), - _maxSizeControlLayout, widgetPlaceholders); - - _throttleControlLayout = new QHBoxLayout; placeWidgets(obs_module_text( "AdvSceneSwitcher.condition.video.entry.throttle"), _throttleControlLayout, widgetPlaceholders); + placeWidgets( + obs_module_text( + "AdvSceneSwitcher.condition.video.entry.checkArea"), + _checkAreaControlLayout, widgetPlaceholders); + + QGridLayout *sizeGrid = new QGridLayout; + sizeGrid->addWidget( + new QLabel(obs_module_text( + "AdvSceneSwitcher.condition.video.minSize")), + 0, 0); + sizeGrid->addWidget(_minSize, 0, 1); + sizeGrid->addWidget( + new QLabel(obs_module_text( + "AdvSceneSwitcher.condition.video.maxSize")), + 1, 0); + sizeGrid->addWidget(_maxSize, 1, 1); + _sizeLayout->addLayout(sizeGrid); + _sizeLayout->addStretch(); + _sizeLayout->setContentsMargins(0, 0, 0, 0); QHBoxLayout *showMatchLayout = new QHBoxLayout; showMatchLayout->addWidget(_showMatch); @@ -414,10 +427,10 @@ MacroConditionVideoEdit::MacroConditionVideoEdit( mainLayout->addWidget(_objectScaleThreshold); mainLayout->addLayout(_neighborsControlLayout); mainLayout->addWidget(_minNeighborsDescription); - mainLayout->addLayout(_minSizeControlLayout); - mainLayout->addLayout(_maxSizeControlLayout); + mainLayout->addLayout(_sizeLayout); mainLayout->addLayout(showMatchLayout); mainLayout->addLayout(_throttleControlLayout); + mainLayout->addLayout(_checkAreaControlLayout); setLayout(mainLayout); _entryData = entryData; @@ -620,44 +633,45 @@ void MacroConditionVideoEdit::MinNeighborsChanged(int value) _entryData->_minNeighbors = value; } -void MacroConditionVideoEdit::MinSizeXChanged(int value) +void MacroConditionVideoEdit::MinSizeChanged(advss::Size value) { if (_loading || !_entryData) { return; } std::lock_guard lock(GetSwitcher()->m); - _entryData->_minSizeX = value; + _entryData->_minSize = value; } -void MacroConditionVideoEdit::MinSizeYChanged(int value) +void MacroConditionVideoEdit::MaxSizeChanged(advss::Size value) { if (_loading || !_entryData) { return; } std::lock_guard lock(GetSwitcher()->m); - _entryData->_minSizeY = value; + _entryData->_maxSize = value; } -void MacroConditionVideoEdit::MaxSizeXChanged(int value) +void MacroConditionVideoEdit::CheckAreaEnableChanged(int value) { if (_loading || !_entryData) { return; } std::lock_guard lock(GetSwitcher()->m); - _entryData->_maxSizeX = value; + _entryData->_checkAreaEnable = value; + _checkArea->setEnabled(value); } -void MacroConditionVideoEdit::MaxSizeYChanged(int value) +void MacroConditionVideoEdit::CheckAreaChanged(advss::Area value) { if (_loading || !_entryData) { return; } std::lock_guard lock(GetSwitcher()->m); - _entryData->_maxSizeY = value; + _entryData->_checkArea = value; } void MacroConditionVideoEdit::ThrottleEnableChanged(int value) @@ -737,6 +751,11 @@ bool patternControlIsOptional(VideoCondition cond) cond == VideoCondition::HAS_NOT_CHANGED; } +bool needsAreaControls(VideoCondition cond) +{ + return cond != VideoCondition::NO_IMAGE; +} + void MacroConditionVideoEdit::SetWidgetVisibility() { _imagePath->setVisible(requiresFileInput(_entryData->_condition)); @@ -752,14 +771,14 @@ void MacroConditionVideoEdit::SetWidgetVisibility() needsObjectControls(_entryData->_condition)); _minNeighborsDescription->setVisible( needsObjectControls(_entryData->_condition)); - setLayoutVisible(_minSizeControlLayout, - needsObjectControls(_entryData->_condition)); - setLayoutVisible(_maxSizeControlLayout, + setLayoutVisible(_sizeLayout, needsObjectControls(_entryData->_condition)); setLayoutVisible(_modelPathLayout, needsObjectControls(_entryData->_condition)); setLayoutVisible(_throttleControlLayout, needsThrottleControls(_entryData->_condition)); + setLayoutVisible(_checkAreaControlLayout, + needsAreaControls(_entryData->_condition)); if (_entryData->_condition == VideoCondition::HAS_CHANGED || _entryData->_condition == VideoCondition::HAS_NOT_CHANGED) { @@ -787,12 +806,13 @@ void MacroConditionVideoEdit::UpdateEntryData() _modelDataPath->SetPath(_entryData->GetModelDataPath().c_str()); _objectScaleThreshold->SetDoubleValue(_entryData->_scaleFactor); _minNeighbors->setValue(_entryData->_minNeighbors); - _minSizeX->setValue(_entryData->_minSizeX); - _minSizeY->setValue(_entryData->_minSizeY); - _maxSizeX->setValue(_entryData->_maxSizeX); - _maxSizeY->setValue(_entryData->_maxSizeY); + _minSize->SetSize(_entryData->_minSize); + _maxSize->SetSize(_entryData->_maxSize); _throttleEnable->setChecked(_entryData->_throttleEnabled); _throttleCount->setValue(_entryData->_throttleCount * GetSwitcher()->interval); + _checkAreaEnable->setChecked(_entryData->_checkAreaEnable); + _checkArea->SetArea(_entryData->_checkArea); + UpdatePreviewTooltip(); SetWidgetVisibility(); } diff --git a/src/external-macro-modules/opencv/macro-condition-video.hpp b/src/external-macro-modules/opencv/macro-condition-video.hpp index 1e1a34ff..c52515c5 100644 --- a/src/external-macro-modules/opencv/macro-condition-video.hpp +++ b/src/external-macro-modules/opencv/macro-condition-video.hpp @@ -1,15 +1,18 @@ #pragma once +#include "opencv-helpers.hpp" +#include "threshold-slider.hpp" +#include "video-match-dialog.hpp" +#include "area-selection.hpp" + #include -#include -#include #include -#include -#include +#include #include #include #include #include +#include #include enum class VideoCondition { @@ -51,10 +54,11 @@ public: cv::CascadeClassifier _objectCascade; double _scaleFactor = 1.1; int _minNeighbors = minMinNeighbors; - int _minSizeX = 0; - int _minSizeY = 0; - int _maxSizeX = 0; - int _maxSizeY = 0; + advss::Size _minSize{0, 0}; + advss::Size _maxSize{0, 0}; + + bool _checkAreaEnable = false; + advss::Area _checkArea{0, 0, 0, 0}; bool _throttleEnabled = false; int _throttleCount = 3; @@ -110,10 +114,11 @@ private slots: void ModelPathChanged(const QString &text); void ObjectScaleThresholdChanged(double); void MinNeighborsChanged(int value); - void MinSizeXChanged(int value); - void MinSizeYChanged(int value); - void MaxSizeXChanged(int value); - void MaxSizeYChanged(int value); + void MinSizeChanged(advss::Size value); + void MaxSizeChanged(advss::Size value); + + void CheckAreaEnableChanged(int value); + void CheckAreaChanged(advss::Area); void ThrottleEnableChanged(int value); void ThrottleCountChanged(int value); @@ -136,16 +141,18 @@ protected: QHBoxLayout *_neighborsControlLayout; QSpinBox *_minNeighbors; QLabel *_minNeighborsDescription; - QHBoxLayout *_minSizeControlLayout; - QSpinBox *_minSizeX; - QSpinBox *_minSizeY; - QHBoxLayout *_maxSizeControlLayout; - QSpinBox *_maxSizeX; - QSpinBox *_maxSizeY; + QHBoxLayout *_sizeLayout; + SizeSelection *_minSize; + SizeSelection *_maxSize; + + QHBoxLayout *_checkAreaControlLayout; + QCheckBox *_checkAreaEnable; + AreaSelection *_checkArea; QHBoxLayout *_throttleControlLayout; QCheckBox *_throttleEnable; QSpinBox *_throttleCount; + QPushButton *_showMatch; ShowMatchDialog _matchDialog; diff --git a/src/external-macro-modules/opencv/video-match-dialog.cpp b/src/external-macro-modules/opencv/video-match-dialog.cpp index d85bc0dc..ce72799a 100644 --- a/src/external-macro-modules/opencv/video-match-dialog.cpp +++ b/src/external-macro-modules/opencv/video-match-dialog.cpp @@ -88,12 +88,20 @@ void ShowMatchDialog::CheckForMatchLoop() _imageLabel->setPixmap(QPixmap()); continue; } - auto image = MarkMatch(screenshot.image); + + if (_conditionData->_checkAreaEnable) { + screenshot.image = screenshot.image.copy( + _conditionData->_checkArea.x, + _conditionData->_checkArea.y, + _conditionData->_checkArea.width, + _conditionData->_checkArea.height); + } + MarkMatch(screenshot.image); _imageLabel->setPixmap(QPixmap::fromImage(screenshot.image)); } } -QImage markPatterns(cv::Mat &matchResult, QImage &image, QImage &pattern) +void markPatterns(cv::Mat &matchResult, QImage &image, QImage &pattern) { auto matchImg = QImageToMat(image); for (int row = 0; row < matchResult.rows - 1; row++) { @@ -106,10 +114,9 @@ QImage markPatterns(cv::Mat &matchResult, QImage &image, QImage &pattern) } } } - return MatToQImage(matchImg); } -QImage markObjects(QImage &image, std::vector &objects) +void markObjects(QImage &image, std::vector &objects) { auto frame = QImageToMat(image); for (size_t i = 0; i < objects.size(); i++) { @@ -118,12 +125,10 @@ QImage markObjects(QImage &image, std::vector &objects) objects[i].y + objects[i].height), cv::Scalar(255, 0, 0, 255), 2, 8, 0); } - return MatToQImage(frame); } -QImage ShowMatchDialog::MarkMatch(QImage &screenshot) +void ShowMatchDialog::MarkMatch(QImage &screenshot) { - QImage resultIamge; if (_conditionData->_condition == VideoCondition::PATTERN) { cv::Mat result; QImage pattern = _conditionData->GetMatchImage(); @@ -131,30 +136,27 @@ QImage ShowMatchDialog::MarkMatch(QImage &screenshot) _conditionData->_patternThreshold, result, _conditionData->_useAlphaAsMask); if (countNonZero(result) == 0) { - resultIamge = screenshot; _statusLabel->setText(obs_module_text( "AdvSceneSwitcher.condition.video.patternMatchFail")); } else { _statusLabel->setText(obs_module_text( "AdvSceneSwitcher.condition.video.patternMatchSuccess")); - resultIamge = markPatterns(result, screenshot, pattern); + markPatterns(result, screenshot, pattern); } } else if (_conditionData->_condition == VideoCondition::OBJECT) { - auto objects = matchObject( - screenshot, _conditionData->_objectCascade, - _conditionData->_scaleFactor, - _conditionData->_minNeighbors, - {_conditionData->_minSizeX, _conditionData->_minSizeY}, - {_conditionData->_maxSizeX, _conditionData->_maxSizeY}); + auto objects = matchObject(screenshot, + _conditionData->_objectCascade, + _conditionData->_scaleFactor, + _conditionData->_minNeighbors, + _conditionData->_minSize.CV(), + _conditionData->_maxSize.CV()); if (objects.empty()) { - resultIamge = screenshot; _statusLabel->setText(obs_module_text( "AdvSceneSwitcher.condition.video.objectMatchFail")); } else { _statusLabel->setText(obs_module_text( "AdvSceneSwitcher.condition.video.objectMatchSuccess")); - resultIamge = markObjects(screenshot, objects); + markObjects(screenshot, objects); } } - return resultIamge; } diff --git a/src/external-macro-modules/opencv/video-match-dialog.hpp b/src/external-macro-modules/opencv/video-match-dialog.hpp index 0031cee2..5df396e5 100644 --- a/src/external-macro-modules/opencv/video-match-dialog.hpp +++ b/src/external-macro-modules/opencv/video-match-dialog.hpp @@ -23,7 +23,7 @@ private slots: private: void CheckForMatchLoop(); - QImage MarkMatch(QImage &screenshot); + void MarkMatch(QImage &screenshot); MacroConditionVideo *_conditionData; QScrollArea *_scrollArea;