diff --git a/src/macro-external/opencv/macro-condition-video.cpp b/src/macro-external/opencv/macro-condition-video.cpp index fc4fe83e..97576a84 100644 --- a/src/macro-external/opencv/macro-condition-video.cpp +++ b/src/macro-external/opencv/macro-condition-video.cpp @@ -342,7 +342,7 @@ MacroConditionVideoEdit::MacroConditionVideoEdit( _throttleCount(new QSpinBox()), _showMatch(new QPushButton(obs_module_text( "AdvSceneSwitcher.condition.video.showMatch"))), - _previewDialog(this, GetSwitcher()->interval) + _previewDialog(this) { _reduceLatency->setToolTip(obs_module_text( "AdvSceneSwitcher.condition.video.reduceLatency.tooltip")); diff --git a/src/macro-external/opencv/opencv-helpers.cpp b/src/macro-external/opencv/opencv-helpers.cpp index dd8dcf76..bd50e4da 100644 --- a/src/macro-external/opencv/opencv-helpers.cpp +++ b/src/macro-external/opencv/opencv-helpers.cpp @@ -18,8 +18,8 @@ PatternImageData createPatternData(QImage &pattern) return data; } -void matchPattern(QImage &img, PatternImageData &patternData, double threshold, - cv::Mat &result, bool useAlphaAsMask) +void matchPattern(QImage &img, const PatternImageData &patternData, + double threshold, cv::Mat &result, bool useAlphaAsMask) { if (img.isNull() || patternData.rgbaPattern.empty()) { return; diff --git a/src/macro-external/opencv/opencv-helpers.hpp b/src/macro-external/opencv/opencv-helpers.hpp index 9335bada..e96b5d39 100644 --- a/src/macro-external/opencv/opencv-helpers.hpp +++ b/src/macro-external/opencv/opencv-helpers.hpp @@ -14,8 +14,9 @@ struct PatternImageData { }; PatternImageData createPatternData(QImage &pattern); -void matchPattern(QImage &img, PatternImageData &patternData, double threshold, - cv::Mat &result, bool useAlphaAsMask = true); +void matchPattern(QImage &img, const PatternImageData &patternData, + double threshold, cv::Mat &result, + bool useAlphaAsMask = true); void matchPattern(QImage &img, QImage &pattern, double threshold, cv::Mat &result, bool useAlphaAsMask); std::vector matchObject(QImage &img, cv::CascadeClassifier &cascade, diff --git a/src/macro-external/opencv/preview-dialog.cpp b/src/macro-external/opencv/preview-dialog.cpp index 5c32d123..a64ea57d 100644 --- a/src/macro-external/opencv/preview-dialog.cpp +++ b/src/macro-external/opencv/preview-dialog.cpp @@ -5,12 +5,11 @@ #include -PreviewDialog::PreviewDialog(QWidget *parent, int delay) +PreviewDialog::PreviewDialog(QWidget *parent) : QDialog(parent), _scrollArea(new QScrollArea), _imageLabel(new QLabel(this)), - _rubberBand(new QRubberBand(QRubberBand::Rectangle, this)), - _delay(delay) + _rubberBand(new QRubberBand(QRubberBand::Rectangle, this)) { setWindowTitle("Advanced Scene Switcher"); setWindowFlags(windowFlags() | Qt::WindowMaximizeButtonHint | @@ -39,22 +38,12 @@ PreviewDialog::PreviewDialog(QWidget *parent, int delay) layout->addWidget(_statusLabel); layout->addWidget(_scrollArea); setLayout(layout); - - // This is a workaround to handle random segfaults triggered when using: - // QMetaObject::invokeMethod(this, "RedrawImage", Qt::QueuedConnection, - // Q_ARG(QImage, image)); - // from within CheckForMatchLoop(). - // Even using BlockingQueuedConnection causes deadlocks - _timer.setInterval(300); - QWidget::connect(&_timer, &QTimer::timeout, this, - &PreviewDialog::ResizeImageLabel); - _timer.start(); } void PreviewDialog::mousePressEvent(QMouseEvent *event) { _selectingArea = true; - if (_type == Type::SHOW_MATCH) { + if (_type == PreviewType::SHOW_MATCH) { return; } _origin = event->pos(); @@ -64,7 +53,7 @@ void PreviewDialog::mousePressEvent(QMouseEvent *event) void PreviewDialog::mouseMoveEvent(QMouseEvent *event) { - if (_type == Type::SHOW_MATCH) { + if (_type == PreviewType::SHOW_MATCH) { return; } _rubberBand->setGeometry(QRect(_origin, event->pos()).normalized()); @@ -72,7 +61,7 @@ void PreviewDialog::mouseMoveEvent(QMouseEvent *event) void PreviewDialog::mouseReleaseEvent(QMouseEvent *) { - if (_type == Type::SHOW_MATCH) { + if (_type == PreviewType::SHOW_MATCH) { return; } auto selectionStart = @@ -102,14 +91,14 @@ void PreviewDialog::ShowMatch() { Start(); _rubberBand->hide(); - _type = Type::SHOW_MATCH; + _type = PreviewType::SHOW_MATCH; } void PreviewDialog::SelectArea() { _selectingArea = false; Start(); - _type = Type::SELECT_AREA; + _type = PreviewType::SELECT_AREA; DrawFrame(); _statusLabel->setText(obs_module_text( "AdvSceneSwitcher.condition.video.selectArea.status")); @@ -117,11 +106,8 @@ void PreviewDialog::SelectArea() void PreviewDialog::Stop() { - _stop = true; - _cv.notify_all(); - if (_thread.joinable()) { - _thread.join(); - } + _thread.quit(); + _thread.wait(); } void PreviewDialog::closeEvent(QCloseEvent *event) @@ -164,79 +150,66 @@ void PreviewDialog::ConditionChanged(int cond) _condition = static_cast(cond); } -void PreviewDialog::ResizeImageLabel() +void PreviewDialog::UpdateStatus(const QString &status) { + _statusLabel->setText(status); +} + +void PreviewDialog::UpdateImage(const QPixmap &image) +{ + _imageLabel->setPixmap(image); _imageLabel->adjustSize(); - if (_type == Type::SELECT_AREA && !_selectingArea) { + if (_type == PreviewType::SELECT_AREA && !_selectingArea) { DrawFrame(); } + emit NeedImage(_video, _type, _patternMatchParams, _patternImageData, + _objDetectParams, _areaParams, _condition); } void PreviewDialog::Start() { - if (_thread.joinable()) { - return; - } if (!_video.ValidSelection()) { DisplayMessage(obs_module_text( "AdvSceneSwitcher.condition.video.screenshotFail")); close(); return; } - _stop = false; - _thread = std::thread(&PreviewDialog::CheckForMatchLoop, this); -} -void PreviewDialog::CheckForMatchLoop() -{ - while (!_stop) { - std::unique_lock lock(_mtx); - auto source = obs_weak_source_get_source(_video.GetVideo()); - ScreenshotHelper screenshot(source); - obs_source_release(source); - _cv.wait_for(lock, std::chrono::milliseconds(_delay)); - if (_stop || isHidden()) { - return; - } - if (!_video.ValidSelection()) { - _statusLabel->setText(obs_module_text( - "AdvSceneSwitcher.condition.video.screenshotFail")); - _imageLabel->setPixmap(QPixmap()); - continue; - } - if (!screenshot.done) { - _statusLabel->setText(obs_module_text( - "AdvSceneSwitcher.condition.video.screenshotFail")); - _imageLabel->setPixmap(QPixmap()); - if (_delay < 1000) { - _delay += 50; - } - continue; - } - if (screenshot.image.width() == 0 || - screenshot.image.height() == 0) { - _statusLabel->setText(obs_module_text( - "AdvSceneSwitcher.condition.video.screenshotEmpty")); - _imageLabel->setPixmap(QPixmap()); - continue; - } - - if (_type == Type::SHOW_MATCH) { - if (_areaParams.enable) { - screenshot.image = screenshot.image.copy( - _areaParams.area.x, _areaParams.area.y, - _areaParams.area.width, - _areaParams.area.height); - } - MarkMatch(screenshot.image); - } else { - _statusLabel->setText(""); - } - _imageLabel->setPixmap(QPixmap::fromImage(screenshot.image)); + if (_thread.isRunning()) { + return; } + + PreviewImage *worker = new PreviewImage(); + worker->moveToThread(&_thread); + connect(&_thread, &QThread::finished, worker, &QObject::deleteLater); + connect(worker, &PreviewImage::ImageReady, this, + &PreviewDialog::UpdateImage); + connect(worker, &PreviewImage::StatusUpdate, this, + &PreviewDialog::UpdateStatus); + connect(this, &PreviewDialog::NeedImage, worker, + &PreviewImage::CreateImage); + _thread.start(); + + emit NeedImage(_video, _type, _patternMatchParams, _patternImageData, + _objDetectParams, _areaParams, _condition); } -void markPatterns(cv::Mat &matchResult, QImage &image, cv::Mat &pattern) +void PreviewDialog::DrawFrame() +{ + if (!_video.ValidSelection()) { + return; + } + auto imageStart = + _imageLabel->mapToGlobal(_imageLabel->rect().topLeft()); + auto windowStart = mapToGlobal(rect().topLeft()); + _rubberBand->resize(_areaParams.area.width, _areaParams.area.height); + _rubberBand->move( + _areaParams.area.x + (imageStart.x() - windowStart.x()), + _areaParams.area.y + (imageStart.y() - windowStart.y())); + _rubberBand->show(); +} + +void markPatterns(cv::Mat &matchResult, QImage &image, const cv::Mat &pattern) { auto matchImg = QImageToMat(image); for (int row = 0; row < matchResult.rows - 1; row++) { @@ -262,50 +235,79 @@ void markObjects(QImage &image, std::vector &objects) } } -void PreviewDialog::MarkMatch(QImage &screenshot) +void PreviewImage::CreateImage(const VideoInput &video, PreviewType type, + const PatternMatchParameters &patternMatchParams, + const PatternImageData &patternImageData, + ObjDetectParamerts objDetectParams, + const AreaParamters &areaParams, + VideoCondition condition) { - if (_condition == VideoCondition::PATTERN) { + auto source = obs_weak_source_get_source(video.GetVideo()); + ScreenshotHelper screenshot(source, true); + obs_source_release(source); + + if (!video.ValidSelection() || !screenshot.done) { + emit StatusUpdate(obs_module_text( + "AdvSceneSwitcher.condition.video.screenshotFail")); + emit ImageReady(QPixmap()); + return; + } + + if (screenshot.image.width() == 0 || screenshot.image.height() == 0) { + emit StatusUpdate(obs_module_text( + "AdvSceneSwitcher.condition.video.screenshotEmpty")); + emit ImageReady(QPixmap()); + return; + } + + if (type == PreviewType::SHOW_MATCH) { + if (areaParams.enable) { + screenshot.image = screenshot.image.copy( + areaParams.area.x, areaParams.area.y, + areaParams.area.width, areaParams.area.height); + } + // Will emit status label update + MarkMatch(screenshot.image, patternMatchParams, + patternImageData, objDetectParams, condition); + } else { + emit StatusUpdate(""); + } + emit ImageReady(QPixmap::fromImage(screenshot.image)); +} + +void PreviewImage::MarkMatch(QImage &screenshot, + const PatternMatchParameters &patternMatchParams, + const PatternImageData &patternImageData, + ObjDetectParamerts &objDetectParams, + VideoCondition condition) +{ + if (condition == VideoCondition::PATTERN) { cv::Mat result; - matchPattern(screenshot, _patternImageData, - _patternMatchParams.threshold, result, - _patternMatchParams.useAlphaAsMask); + matchPattern(screenshot, patternImageData, + patternMatchParams.threshold, result, + patternMatchParams.useAlphaAsMask); if (countNonZero(result) == 0) { - _statusLabel->setText(obs_module_text( + emit StatusUpdate(obs_module_text( "AdvSceneSwitcher.condition.video.patternMatchFail")); } else { - _statusLabel->setText(obs_module_text( + emit StatusUpdate(obs_module_text( "AdvSceneSwitcher.condition.video.patternMatchSuccess")); markPatterns(result, screenshot, - _patternImageData.rgbaPattern); + patternImageData.rgbaPattern); } - } else if (_condition == VideoCondition::OBJECT) { - auto objects = matchObject(screenshot, _objDetectParams.cascade, - _objDetectParams.scaleFactor, - _objDetectParams.minNeighbors, - _objDetectParams.minSize.CV(), - _objDetectParams.maxSize.CV()); + } else if (condition == VideoCondition::OBJECT) { + auto objects = matchObject(screenshot, objDetectParams.cascade, + objDetectParams.scaleFactor, + objDetectParams.minNeighbors, + objDetectParams.minSize.CV(), + objDetectParams.maxSize.CV()); if (objects.empty()) { - _statusLabel->setText(obs_module_text( + emit StatusUpdate(obs_module_text( "AdvSceneSwitcher.condition.video.objectMatchFail")); } else { - _statusLabel->setText(obs_module_text( + emit StatusUpdate(obs_module_text( "AdvSceneSwitcher.condition.video.objectMatchSuccess")); markObjects(screenshot, objects); } } } - -void PreviewDialog::DrawFrame() -{ - if (!_video.ValidSelection()) { - return; - } - auto imageStart = - _imageLabel->mapToGlobal(_imageLabel->rect().topLeft()); - auto windowStart = mapToGlobal(rect().topLeft()); - _rubberBand->resize(_areaParams.area.width, _areaParams.area.height); - _rubberBand->move( - _areaParams.area.x + (imageStart.x() - windowStart.x()), - _areaParams.area.y + (imageStart.y() - windowStart.y())); - _rubberBand->show(); -} diff --git a/src/macro-external/opencv/preview-dialog.hpp b/src/macro-external/opencv/preview-dialog.hpp index 1b01f5e2..f8d43ee4 100644 --- a/src/macro-external/opencv/preview-dialog.hpp +++ b/src/macro-external/opencv/preview-dialog.hpp @@ -4,21 +4,40 @@ #include #include #include -#include - +#include #include #include #include - -#include #include -#include + +enum class PreviewType { + SHOW_MATCH, + SELECT_AREA, +}; + +class PreviewImage : public QObject { + Q_OBJECT + +public slots: + void CreateImage(const VideoInput &, PreviewType, + const PatternMatchParameters &, + const PatternImageData &, ObjDetectParamerts, + const AreaParamters &, VideoCondition); +signals: + void ImageReady(const QPixmap &); + void StatusUpdate(const QString &); + +private: + void MarkMatch(QImage &screenshot, const PatternMatchParameters &, + const PatternImageData &, ObjDetectParamerts &, + VideoCondition); +}; class PreviewDialog : public QDialog { Q_OBJECT public: - PreviewDialog(QWidget *parent, int delay); + PreviewDialog(QWidget *parent); virtual ~PreviewDialog(); void ShowMatch(); void SelectArea(); @@ -32,14 +51,17 @@ public slots: void AreaParamtersChanged(const AreaParamters &); void ConditionChanged(int cond); private slots: - void ResizeImageLabel(); + void UpdateImage(const QPixmap &); + void UpdateStatus(const QString &); signals: void SelectionAreaChanged(QRect area); + void NeedImage(const VideoInput &, PreviewType, + const PatternMatchParameters &, const PatternImageData &, + ObjDetectParamerts, const AreaParamters &, + VideoCondition); private: void Start(); - void CheckForMatchLoop(); - void MarkMatch(QImage &screenshot); void DrawFrame(); void mousePressEvent(QMouseEvent *event); @@ -56,21 +78,13 @@ private: QScrollArea *_scrollArea; QLabel *_statusLabel; QLabel *_imageLabel; - QTimer _timer; QPoint _origin; QRubberBand *_rubberBand = nullptr; std::atomic_bool _selectingArea = {false}; - std::mutex _mtx; - std::condition_variable _cv; - std::thread _thread; - std::atomic_bool _stop = {true}; + PreviewType _type = PreviewType::SHOW_MATCH; - enum class Type { - SHOW_MATCH, - SELECT_AREA, - }; - Type _type = Type::SHOW_MATCH; - int _delay = 300; + std::mutex _mtx; + QThread _thread; };