From d450210d39fc6588f17a6664a7068d057c8e5964 Mon Sep 17 00:00:00 2001 From: WarmUpTill Date: Sun, 13 Mar 2022 15:25:38 +0100 Subject: [PATCH] Add option to visually select area to check --- data/locale/en-US.ini | 2 +- .../opencv/CMakeLists.txt | 4 +- .../opencv/macro-condition-video.cpp | 33 ++++- .../opencv/macro-condition-video.hpp | 8 +- ...eo-match-dialog.cpp => preview-dialog.cpp} | 130 ++++++++++++++---- .../opencv/preview-dialog.hpp | 61 ++++++++ .../opencv/video-match-dialog.hpp | 36 ----- 7 files changed, 203 insertions(+), 71 deletions(-) rename src/external-macro-modules/opencv/{video-match-dialog.cpp => preview-dialog.cpp} (57%) create mode 100644 src/external-macro-modules/opencv/preview-dialog.hpp delete mode 100644 src/external-macro-modules/opencv/video-match-dialog.hpp diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 65ff31cf..95a85f2c 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -161,7 +161,7 @@ AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{imagePa AdvSceneSwitcher.condition.video.entry.modelPath="Model data (haar cascade classifier): {{modelDataPath}}" AdvSceneSwitcher.condition.video.entry.minNeighbor="Minimum neighbors: {{minNeighbors}}" 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.entry.checkArea="{{checkAreaEnable}}Perform check only in area {{checkArea}} {{selectArea}}" AdvSceneSwitcher.condition.video.minSize="Minimum size:" AdvSceneSwitcher.condition.video.maxSize="Maximum size:" AdvSceneSwitcher.condition.video.selectArea="Select area" diff --git a/src/external-macro-modules/opencv/CMakeLists.txt b/src/external-macro-modules/opencv/CMakeLists.txt index 023b55a9..b3bd6b0c 100644 --- a/src/external-macro-modules/opencv/CMakeLists.txt +++ b/src/external-macro-modules/opencv/CMakeLists.txt @@ -21,8 +21,8 @@ set(module_SOURCES area-selection.hpp macro-condition-video.cpp macro-condition-video.hpp - video-match-dialog.cpp - video-match-dialog.hpp + preview-dialog.cpp + preview-dialog.hpp threshold-slider.cpp threshold-slider.hpp opencv-helpers.cpp diff --git a/src/external-macro-modules/opencv/macro-condition-video.cpp b/src/external-macro-modules/opencv/macro-condition-video.cpp index 63628617..fa41fa1b 100644 --- a/src/external-macro-modules/opencv/macro-condition-video.cpp +++ b/src/external-macro-modules/opencv/macro-condition-video.cpp @@ -312,7 +312,9 @@ MacroConditionVideoEdit::MacroConditionVideoEdit( _throttleCount(new QSpinBox()), _showMatch(new QPushButton(obs_module_text( "AdvSceneSwitcher.condition.video.showMatch"))), - _matchDialog(this, entryData.get(), &GetSwitcher()->m), + _selectArea(new QPushButton(obs_module_text( + "AdvSceneSwitcher.condition.video.selectArea"))), + _previewDialog(this, entryData.get(), &GetSwitcher()->m), _modelPathLayout(new QHBoxLayout), _neighborsControlLayout(new QHBoxLayout), _checkAreaControlLayout(new QHBoxLayout), @@ -364,6 +366,10 @@ MacroConditionVideoEdit::MacroConditionVideoEdit( SLOT(ThrottleCountChanged(int))); QWidget::connect(_showMatch, SIGNAL(clicked()), this, SLOT(ShowMatchClicked())); + QWidget::connect(&_previewDialog, SIGNAL(SelectionAreaChanged(QRect)), + this, SLOT(CheckAreaChanged(QRect))); + QWidget::connect(_selectArea, SIGNAL(clicked()), this, + SLOT(SelectAreaClicked())); populateVideoSelection(_videoSelection); populateConditionSelection(_condition); @@ -381,6 +387,7 @@ MacroConditionVideoEdit::MacroConditionVideoEdit( {"{{throttleCount}}", _throttleCount}, {"{{checkAreaEnable}}", _checkAreaEnable}, {"{{checkArea}}", _checkArea}, + {"{{selectArea}}", _selectArea}, }; placeWidgets(obs_module_text("AdvSceneSwitcher.condition.video.entry"), entryLine1Layout, widgetPlaceholders); @@ -662,6 +669,7 @@ void MacroConditionVideoEdit::CheckAreaEnableChanged(int value) std::lock_guard lock(GetSwitcher()->m); _entryData->_checkAreaEnable = value; _checkArea->setEnabled(value); + _selectArea->setEnabled(value); } void MacroConditionVideoEdit::CheckAreaChanged(advss::Area value) @@ -674,6 +682,13 @@ void MacroConditionVideoEdit::CheckAreaChanged(advss::Area value) _entryData->_checkArea = value; } +void MacroConditionVideoEdit::CheckAreaChanged(QRect rect) +{ + advss::Area area{rect.topLeft().x(), rect.y(), rect.width(), + rect.height()}; + _checkArea->SetArea(area); +} + void MacroConditionVideoEdit::ThrottleEnableChanged(int value) { if (_loading || !_entryData) { @@ -697,10 +712,18 @@ void MacroConditionVideoEdit::ThrottleCountChanged(int value) void MacroConditionVideoEdit::ShowMatchClicked() { - _matchDialog.show(); - _matchDialog.raise(); - _matchDialog.activateWindow(); - _matchDialog.ShowMatch(); + _previewDialog.show(); + _previewDialog.raise(); + _previewDialog.activateWindow(); + _previewDialog.ShowMatch(); +} + +void MacroConditionVideoEdit::SelectAreaClicked() +{ + _previewDialog.show(); + _previewDialog.raise(); + _previewDialog.activateWindow(); + _previewDialog.SelectArea(); } void MacroConditionVideoEdit::ModelPathChanged(const QString &text) diff --git a/src/external-macro-modules/opencv/macro-condition-video.hpp b/src/external-macro-modules/opencv/macro-condition-video.hpp index c52515c5..5f845b34 100644 --- a/src/external-macro-modules/opencv/macro-condition-video.hpp +++ b/src/external-macro-modules/opencv/macro-condition-video.hpp @@ -1,7 +1,7 @@ #pragma once #include "opencv-helpers.hpp" #include "threshold-slider.hpp" -#include "video-match-dialog.hpp" +#include "preview-dialog.hpp" #include "area-selection.hpp" #include @@ -14,6 +14,7 @@ #include #include #include +#include enum class VideoCondition { MATCH, @@ -119,6 +120,8 @@ private slots: void CheckAreaEnableChanged(int value); void CheckAreaChanged(advss::Area); + void CheckAreaChanged(QRect area); + void SelectAreaClicked(); void ThrottleEnableChanged(int value); void ThrottleCountChanged(int value); @@ -148,13 +151,14 @@ protected: QHBoxLayout *_checkAreaControlLayout; QCheckBox *_checkAreaEnable; AreaSelection *_checkArea; + QPushButton *_selectArea; QHBoxLayout *_throttleControlLayout; QCheckBox *_throttleEnable; QSpinBox *_throttleCount; QPushButton *_showMatch; - ShowMatchDialog _matchDialog; + PreviewDialog _previewDialog; std::shared_ptr _entryData; diff --git a/src/external-macro-modules/opencv/video-match-dialog.cpp b/src/external-macro-modules/opencv/preview-dialog.cpp similarity index 57% rename from src/external-macro-modules/opencv/video-match-dialog.cpp rename to src/external-macro-modules/opencv/preview-dialog.cpp index ce72799a..bd913270 100644 --- a/src/external-macro-modules/opencv/video-match-dialog.cpp +++ b/src/external-macro-modules/opencv/preview-dialog.cpp @@ -1,22 +1,24 @@ -#include "video-match-dialog.hpp" +#include "preview-dialog.hpp" #include "macro-condition-video.hpp" #include "opencv-helpers.hpp" #include "utility.hpp" #include -ShowMatchDialog::ShowMatchDialog(QWidget *parent, - MacroConditionVideo *conditionData, - std::mutex *mutex) +PreviewDialog::PreviewDialog(QWidget *parent, + MacroConditionVideo *conditionData, + std::mutex *mutex) : QDialog(parent), _conditionData(conditionData), - _imageLabel(new QLabel), + _imageLabel(new QLabel(this)), _scrollArea(new QScrollArea), + _rubberBand(new QRubberBand(QRubberBand::Rectangle, this)), _mtx(mutex) { setWindowTitle("Advanced Scene Switcher"); _statusLabel = new QLabel(obs_module_text( "AdvSceneSwitcher.condition.video.showMatch.loading")); + resize(640, 480); _scrollArea->setBackgroundRole(QPalette::Dark); _scrollArea->setWidget(_imageLabel); @@ -26,17 +28,53 @@ ShowMatchDialog::ShowMatchDialog(QWidget *parent, setLayout(layout); // This is a workaround to handle random segfaults triggered when using: - // QMetaObject::invokeMethod(this, "RedrawImage", -Qt::QueuedConnection, - // Q_ARG(QImage, image)); + // QMetaObject::invokeMethod(this, "RedrawImage", Qt::QueuedConnection, + // Q_ARG(QImage, image)); // from within CheckForMatchLoop(). // Even using BlockingQueuedConnection causes deadlocks _timer.setInterval(500); QWidget::connect(&_timer, &QTimer::timeout, this, - &ShowMatchDialog::Resize); + &PreviewDialog::Resize); _timer.start(); } -ShowMatchDialog::~ShowMatchDialog() +void PreviewDialog::mousePressEvent(QMouseEvent *event) +{ + _selectingArea = true; + if (_type == Type::SHOW_MATCH) { + return; + } + _origin = event->pos(); + _rubberBand->setGeometry(QRect(_origin, QSize())); + _rubberBand->show(); +} + +void PreviewDialog::mouseMoveEvent(QMouseEvent *event) +{ + _rubberBand->setGeometry(QRect(_origin, event->pos()).normalized()); +} + +void PreviewDialog::mouseReleaseEvent(QMouseEvent *) +{ + auto selectionStart = + _rubberBand->mapToGlobal(_rubberBand->rect().topLeft()); + QRect selectionArea(selectionStart, _rubberBand->size()); + + auto imageStart = + _imageLabel->mapToGlobal(_imageLabel->rect().topLeft()); + QRect imageArea(imageStart, _imageLabel->size()); + + auto intersected = imageArea.intersected(selectionArea); + QRect checksize(QPoint(intersected.topLeft() - imageStart), + intersected.size()); + if (checksize.x() >= 0 && checksize.y() >= 0 && checksize.width() > 0 && + checksize.height() > 0) { + emit SelectionAreaChanged(checksize); + } + _selectingArea = false; +} + +PreviewDialog::~PreviewDialog() { _stop = true; if (_thread.joinable()) { @@ -44,7 +82,32 @@ ShowMatchDialog::~ShowMatchDialog() } } -void ShowMatchDialog::ShowMatch() +void PreviewDialog::ShowMatch() +{ + Start(); + _rubberBand->hide(); + _type = Type::SHOW_MATCH; +} + +void PreviewDialog::SelectArea() +{ + _selectingArea = false; + Start(); + _type = Type::SELECT_AREA; + DrawFrame(); + _statusLabel->setText(obs_module_text( + "AdvSceneSwitcher.condition.video.selectArea.status")); +} + +void PreviewDialog::Resize() +{ + _imageLabel->adjustSize(); + if (_type == Type::SELECT_AREA && !_selectingArea) { + DrawFrame(); + } +} + +void PreviewDialog::Start() { if (_thread.joinable()) { return; @@ -54,15 +117,10 @@ void ShowMatchDialog::ShowMatch() "AdvSceneSwitcher.condition.video.screenshotFail")); return; } - _thread = std::thread(&ShowMatchDialog::CheckForMatchLoop, this); + _thread = std::thread(&PreviewDialog::CheckForMatchLoop, this); } -void ShowMatchDialog::Resize() -{ - _imageLabel->adjustSize(); -} - -void ShowMatchDialog::CheckForMatchLoop() +void PreviewDialog::CheckForMatchLoop() { std::condition_variable cv; while (!_stop) { @@ -75,6 +133,9 @@ void ShowMatchDialog::CheckForMatchLoop() if (_stop) { return; } + if (isHidden()) { + continue; + } if (!screenshot.done) { _statusLabel->setText(obs_module_text( "AdvSceneSwitcher.condition.video.screenshotFail")); @@ -89,14 +150,16 @@ void ShowMatchDialog::CheckForMatchLoop() continue; } - if (_conditionData->_checkAreaEnable) { - screenshot.image = screenshot.image.copy( - _conditionData->_checkArea.x, - _conditionData->_checkArea.y, - _conditionData->_checkArea.width, - _conditionData->_checkArea.height); + if (_type == Type::SHOW_MATCH) { + if (_conditionData->_checkAreaEnable) { + screenshot.image = screenshot.image.copy( + _conditionData->_checkArea.x, + _conditionData->_checkArea.y, + _conditionData->_checkArea.width, + _conditionData->_checkArea.height); + } + MarkMatch(screenshot.image); } - MarkMatch(screenshot.image); _imageLabel->setPixmap(QPixmap::fromImage(screenshot.image)); } } @@ -127,7 +190,7 @@ void markObjects(QImage &image, std::vector &objects) } } -void ShowMatchDialog::MarkMatch(QImage &screenshot) +void PreviewDialog::MarkMatch(QImage &screenshot) { if (_conditionData->_condition == VideoCondition::PATTERN) { cv::Mat result; @@ -160,3 +223,20 @@ void ShowMatchDialog::MarkMatch(QImage &screenshot) } } } + +void PreviewDialog::DrawFrame() +{ + if (!_conditionData) { + return; + } + auto imageStart = + _imageLabel->mapToGlobal(_imageLabel->rect().topLeft()); + auto windowStart = mapToGlobal(rect().topLeft()); + _rubberBand->resize(_conditionData->_checkArea.width, + _conditionData->_checkArea.height); + _rubberBand->move(_conditionData->_checkArea.x + + (imageStart.x() - windowStart.x()), + _conditionData->_checkArea.y + + (imageStart.y() - windowStart.y())); + _rubberBand->show(); +} diff --git a/src/external-macro-modules/opencv/preview-dialog.hpp b/src/external-macro-modules/opencv/preview-dialog.hpp new file mode 100644 index 00000000..a1cd65dd --- /dev/null +++ b/src/external-macro-modules/opencv/preview-dialog.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +class MacroConditionVideo; + +class PreviewDialog : public QDialog { + Q_OBJECT + +public: + PreviewDialog(QWidget *parent, MacroConditionVideo *_conditionData, + std::mutex *mutex); + virtual ~PreviewDialog(); + void ShowMatch(); + void SelectArea(); + +private slots: + void Resize(); +signals: + void SelectionAreaChanged(QRect area); + +private: + void Start(); + void CheckForMatchLoop(); + void MarkMatch(QImage &screenshot); + void DrawFrame(); + + void mousePressEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + + MacroConditionVideo *_conditionData; + QScrollArea *_scrollArea; + QLabel *_statusLabel; + QLabel *_imageLabel; + QTimer _timer; + + QPoint _origin; + QRubberBand *_rubberBand = nullptr; + std::atomic_bool _selectingArea = {false}; + + std::mutex *_mtx; + std::thread _thread; + std::atomic_bool _stop = {false}; + + enum class Type { + SHOW_MATCH, + SELECT_AREA, + }; + Type _type = Type::SHOW_MATCH; +}; diff --git a/src/external-macro-modules/opencv/video-match-dialog.hpp b/src/external-macro-modules/opencv/video-match-dialog.hpp deleted file mode 100644 index 5df396e5..00000000 --- a/src/external-macro-modules/opencv/video-match-dialog.hpp +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -class MacroConditionVideo; - -class ShowMatchDialog : public QDialog { - Q_OBJECT - -public: - ShowMatchDialog(QWidget *parent, MacroConditionVideo *_conditionData, - std::mutex *mutex); - virtual ~ShowMatchDialog(); - void ShowMatch(); - -private slots: - void Resize(); - -private: - void CheckForMatchLoop(); - void MarkMatch(QImage &screenshot); - - MacroConditionVideo *_conditionData; - QScrollArea *_scrollArea; - QLabel *_statusLabel; - QLabel *_imageLabel; - QTimer _timer; - std::mutex *_mtx; - std::thread _thread; - std::atomic_bool _stop = {false}; -};