mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-08-04 19:48:56 -05:00
Split macro-condition-video.cpp UI into per-widget files
This makes it easier to conditionally exclude video-edit-object.cpp from the build when OpenCV >= 5 drops CascadeClassifier support.
This commit is contained in:
parent
1030868a75
commit
107d77f1b8
3
.github/scripts/.build-deps.zsh
vendored
3
.github/scripts/.build-deps.zsh
vendored
|
|
@ -302,6 +302,7 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
|||
log_info "Configure OpenCV (x86_64) ..."
|
||||
cmake -S . -B build_x86_64 ${opencv_cmake_args_common} \
|
||||
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
|
||||
-DCMAKE_PROJECT_INCLUDE="${SCRIPT_HOME}/opencv-force-processor-x86_64.cmake" \
|
||||
-DCMAKE_INSTALL_PREFIX="${opencv_install_x86}" \
|
||||
-DWITH_IPP=OFF
|
||||
|
||||
|
|
@ -344,7 +345,7 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
|||
-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
|
||||
-DSW_BUILD=OFF
|
||||
-DOPENJPEG_SUPPORT=OFF
|
||||
-DLIBWEBP_SUPPORT=OFF
|
||||
-DENABLE_WEBP=OFF
|
||||
-DCMAKE_DISABLE_FIND_PACKAGE_GIF=TRUE
|
||||
-DCMAKE_DISABLE_FIND_PACKAGE_JPEG=TRUE
|
||||
-DCMAKE_DISABLE_FIND_PACKAGE_TIFF=TRUE
|
||||
|
|
|
|||
7
.github/scripts/opencv-force-processor-x86_64.cmake
vendored
Normal file
7
.github/scripts/opencv-force-processor-x86_64.cmake
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Injected via CMAKE_PROJECT_INCLUDE after opencv's project() call. Forces
|
||||
# CMAKE_SYSTEM_PROCESSOR into the cache so that third-party subdirectories
|
||||
# (mlas) that read the cache directly see x86_64 instead of the arm64 host
|
||||
# processor on Apple Silicon CI runners.
|
||||
set(CMAKE_SYSTEM_PROCESSOR
|
||||
x86_64
|
||||
CACHE INTERNAL "" FORCE)
|
||||
2
.github/workflows/build-project.yaml
vendored
2
.github/workflows/build-project.yaml
vendored
|
|
@ -6,7 +6,7 @@ on:
|
|||
description: "Project name detected by parsing build spec file"
|
||||
value: ${{ jobs.check-event.outputs.pluginName }}
|
||||
env:
|
||||
DEP_DIR: .deps/advss-build-dependencies-5
|
||||
DEP_DIR: .deps/advss-build-dependencies-6
|
||||
jobs:
|
||||
check-event:
|
||||
name: Check GitHub Event Data 🔎
|
||||
|
|
|
|||
|
|
@ -30,10 +30,29 @@ if(OS_LINUX AND NOT Leptonica_FOUND)
|
|||
endif()
|
||||
endif()
|
||||
|
||||
# --- Check cascade classifier availability ---
|
||||
# CascadeClassifier was moved to the xobjdetect contrib module in OpenCV 5
|
||||
if(OpenCV_VERSION_MAJOR LESS 5
|
||||
OR OpenCV_xobjdetect_FOUND
|
||||
OR "opencv_xobjdetect" IN_LIST OpenCV_LIBS)
|
||||
set(ADVSS_CASCADE_SUPPORT ON)
|
||||
else()
|
||||
message(
|
||||
WARNING
|
||||
"Cascade classifier support disabled!\n"
|
||||
"OpenCV 5+ requires the opencv_xobjdetect contrib module.\n\n"
|
||||
"Rebuild OpenCV with -DBUILD_LIST=...,xobjdetect and -DOPENCV_EXTRA_MODULES_PATH=<opencv_contrib>/modules"
|
||||
)
|
||||
endif()
|
||||
|
||||
# --- End of section ---
|
||||
|
||||
add_library(${PROJECT_NAME} MODULE)
|
||||
|
||||
if(ADVSS_CASCADE_SUPPORT)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE ADVSS_CASCADE_SUPPORT)
|
||||
endif()
|
||||
|
||||
if(Leptonica_FOUND AND Tesseract_FOUND)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE OCR_SUPPORT)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE Tesseract::libtesseract
|
||||
|
|
@ -54,8 +73,11 @@ target_sources(
|
|||
${PROJECT_NAME}
|
||||
PRIVATE area-selection.cpp
|
||||
area-selection.hpp
|
||||
cascade-classifier-detector.cpp
|
||||
cascade-classifier-detector.hpp
|
||||
macro-condition-video.cpp
|
||||
macro-condition-video.hpp
|
||||
object-detector.hpp
|
||||
opencv-helpers.cpp
|
||||
opencv-helpers.hpp
|
||||
parameter-wrappers.cpp
|
||||
|
|
@ -63,7 +85,12 @@ target_sources(
|
|||
preview-dialog.cpp
|
||||
preview-dialog.hpp
|
||||
screenshot-dialog.cpp
|
||||
screenshot-dialog.hpp)
|
||||
screenshot-dialog.hpp
|
||||
video-edit-area.cpp
|
||||
video-edit-brightness.cpp
|
||||
video-edit-color.cpp
|
||||
video-edit-object.cpp
|
||||
video-edit-ocr.cpp)
|
||||
|
||||
setup_advss_plugin(${PROJECT_NAME})
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
#include "area-selection.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <obs-module-helper.hpp>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace advss {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
#pragma once
|
||||
#include <variable-spinbox.hpp>
|
||||
#include "variable-spinbox.hpp"
|
||||
|
||||
#include <QWidget>
|
||||
#include <obs-data.h>
|
||||
#include <opencv2/opencv.hpp>
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
namespace advss {
|
||||
|
||||
struct Size {
|
||||
|
|
|
|||
109
plugins/video/cascade-classifier-detector.cpp
Normal file
109
plugins/video/cascade-classifier-detector.cpp
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
#include "cascade-classifier-detector.hpp"
|
||||
|
||||
#ifdef ADVSS_CASCADE_SUPPORT
|
||||
|
||||
#include "opencv-helpers.hpp"
|
||||
#include "log-helper.hpp"
|
||||
|
||||
#if CV_VERSION_MAJOR < 5
|
||||
#include <opencv2/objdetect.hpp>
|
||||
#else
|
||||
#include <opencv2/xobjdetect.hpp>
|
||||
#endif
|
||||
|
||||
namespace advss {
|
||||
|
||||
struct CascadeClassifierDetector::Impl {
|
||||
cv::CascadeClassifier cascade;
|
||||
};
|
||||
|
||||
CascadeClassifierDetector::CascadeClassifierDetector()
|
||||
: _impl(std::make_unique<Impl>())
|
||||
{
|
||||
}
|
||||
|
||||
CascadeClassifierDetector::~CascadeClassifierDetector() = default;
|
||||
|
||||
bool CascadeClassifierDetector::IsSupported()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CascadeClassifierDetector::Load(const std::string &modelPath)
|
||||
{
|
||||
try {
|
||||
if (!_impl->cascade.load(modelPath)) {
|
||||
blog(LOG_WARNING, "failed to load cascade model \"%s\"",
|
||||
modelPath.c_str());
|
||||
return false;
|
||||
}
|
||||
} catch (...) {
|
||||
blog(LOG_WARNING, "failed to load cascade model \"%s\"",
|
||||
modelPath.c_str());
|
||||
return false;
|
||||
}
|
||||
return !_impl->cascade.empty();
|
||||
}
|
||||
|
||||
bool CascadeClassifierDetector::IsLoaded() const
|
||||
{
|
||||
return !_impl->cascade.empty();
|
||||
}
|
||||
|
||||
std::vector<cv::Rect> CascadeClassifierDetector::Detect(QImage &img)
|
||||
{
|
||||
if (img.isNull() || _impl->cascade.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto image = QImageToMat(img);
|
||||
cv::Mat frameGray;
|
||||
cv::cvtColor(image, frameGray, cv::COLOR_RGBA2GRAY);
|
||||
cv::equalizeHist(frameGray, frameGray);
|
||||
|
||||
std::vector<cv::Rect> objects;
|
||||
try {
|
||||
_impl->cascade.detectMultiScale(frameGray, objects, scaleFactor,
|
||||
minNeighbors, 0, minSize,
|
||||
maxSize);
|
||||
} catch (const std::exception &e) {
|
||||
vblog(LOG_INFO, "detectMultiScale failed: %s", e.what());
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
|
||||
#else // ADVSS_CASCADE_SUPPORT
|
||||
|
||||
namespace advss {
|
||||
|
||||
struct CascadeClassifierDetector::Impl {};
|
||||
|
||||
CascadeClassifierDetector::CascadeClassifierDetector()
|
||||
: _impl(std::make_unique<Impl>())
|
||||
{
|
||||
}
|
||||
|
||||
CascadeClassifierDetector::~CascadeClassifierDetector() = default;
|
||||
|
||||
bool CascadeClassifierDetector::IsSupported()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool CascadeClassifierDetector::Load(const std::string &)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool CascadeClassifierDetector::IsLoaded() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::vector<cv::Rect> CascadeClassifierDetector::Detect(QImage &)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
|
||||
#endif // ADVSS_CASCADE_SUPPORT
|
||||
27
plugins/video/cascade-classifier-detector.hpp
Normal file
27
plugins/video/cascade-classifier-detector.hpp
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
#include "object-detector.hpp"
|
||||
#include <memory>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class CascadeClassifierDetector : public ObjectDetector {
|
||||
public:
|
||||
CascadeClassifierDetector();
|
||||
~CascadeClassifierDetector() override;
|
||||
|
||||
static bool IsSupported();
|
||||
bool Load(const std::string &modelPath) override;
|
||||
bool IsLoaded() const override;
|
||||
std::vector<cv::Rect> Detect(QImage &img) override;
|
||||
|
||||
double scaleFactor = 1.1;
|
||||
int minNeighbors = 3;
|
||||
cv::Size minSize{0, 0};
|
||||
cv::Size maxSize{0, 0};
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> _impl;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
|
|
@ -1,17 +1,15 @@
|
|||
#include "macro-condition-video.hpp"
|
||||
#include "cascade-classifier-detector.hpp"
|
||||
#include "screenshot-dialog.hpp"
|
||||
#include "layout-helpers.hpp"
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
#include "selection-helpers.hpp"
|
||||
#include "ui-helpers.hpp"
|
||||
|
||||
#include <layout-helpers.hpp>
|
||||
#include <macro-condition-edit.hpp>
|
||||
#include <plugin-state-helpers.hpp>
|
||||
#include <QBuffer>
|
||||
#include <QDesktopServices>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QtGlobal>
|
||||
#include <QToolTip>
|
||||
#include <ui-helpers.hpp>
|
||||
#include <selection-helpers.hpp>
|
||||
|
||||
namespace advss {
|
||||
|
||||
|
|
@ -22,30 +20,6 @@ bool MacroConditionVideo::_registered = MacroConditionFactory::Register(
|
|||
{MacroConditionVideo::Create, MacroConditionVideoEdit::Create,
|
||||
"AdvSceneSwitcher.condition.video"});
|
||||
|
||||
const static std::map<VideoCondition, std::string> conditionTypes = {
|
||||
{VideoCondition::MATCH,
|
||||
"AdvSceneSwitcher.condition.video.condition.match"},
|
||||
{VideoCondition::DIFFER,
|
||||
"AdvSceneSwitcher.condition.video.condition.differ"},
|
||||
{VideoCondition::HAS_NOT_CHANGED,
|
||||
"AdvSceneSwitcher.condition.video.condition.hasNotChanged"},
|
||||
{VideoCondition::HAS_CHANGED,
|
||||
"AdvSceneSwitcher.condition.video.condition.hasChanged"},
|
||||
{VideoCondition::NO_IMAGE,
|
||||
"AdvSceneSwitcher.condition.video.condition.noImage"},
|
||||
{VideoCondition::PATTERN,
|
||||
"AdvSceneSwitcher.condition.video.condition.pattern"},
|
||||
{VideoCondition::OBJECT,
|
||||
"AdvSceneSwitcher.condition.video.condition.object"},
|
||||
{VideoCondition::BRIGHTNESS,
|
||||
"AdvSceneSwitcher.condition.video.condition.brightness"},
|
||||
#ifdef OCR_SUPPORT
|
||||
{VideoCondition::OCR, "AdvSceneSwitcher.condition.video.condition.ocr"},
|
||||
#endif
|
||||
{VideoCondition::COLOR,
|
||||
"AdvSceneSwitcher.condition.video.condition.color"},
|
||||
};
|
||||
|
||||
const static std::map<VideoInput::Type, std::string> videoInputTypes = {
|
||||
{VideoInput::Type::OBS_MAIN_OUTPUT,
|
||||
"AdvSceneSwitcher.condition.video.type.main"},
|
||||
|
|
@ -64,27 +38,6 @@ const static std::map<cv::TemplateMatchModes, std::string> patternMatchModes = {
|
|||
"AdvSceneSwitcher.condition.video.patternMatchMode.squaredDifference"},
|
||||
};
|
||||
|
||||
const static std::map<tesseract::PageSegMode, std::string> pageSegModes = {
|
||||
{tesseract::PageSegMode::PSM_SINGLE_COLUMN,
|
||||
"AdvSceneSwitcher.condition.video.ocrMode.singleColumn"},
|
||||
{tesseract::PageSegMode::PSM_SINGLE_BLOCK_VERT_TEXT,
|
||||
"AdvSceneSwitcher.condition.video.ocrMode.singleBlockVertText"},
|
||||
{tesseract::PageSegMode::PSM_SINGLE_BLOCK,
|
||||
"AdvSceneSwitcher.condition.video.ocrMode.singleBlock"},
|
||||
{tesseract::PageSegMode::PSM_SINGLE_LINE,
|
||||
"AdvSceneSwitcher.condition.video.ocrMode.singleLine"},
|
||||
{tesseract::PageSegMode::PSM_SINGLE_WORD,
|
||||
"AdvSceneSwitcher.condition.video.ocrMode.singleWord"},
|
||||
{tesseract::PageSegMode::PSM_CIRCLE_WORD,
|
||||
"AdvSceneSwitcher.condition.video.ocrMode.circleWord"},
|
||||
{tesseract::PageSegMode::PSM_SINGLE_CHAR,
|
||||
"AdvSceneSwitcher.condition.video.ocrMode.singleChar"},
|
||||
{tesseract::PageSegMode::PSM_SPARSE_TEXT,
|
||||
"AdvSceneSwitcher.condition.video.ocrMode.sparseText"},
|
||||
{tesseract::PageSegMode::PSM_SPARSE_TEXT_OSD,
|
||||
"AdvSceneSwitcher.condition.video.ocrMode.sparseTextOSD"},
|
||||
};
|
||||
|
||||
static bool requiresFileInput(VideoCondition t)
|
||||
{
|
||||
return t == VideoCondition::MATCH || t == VideoCondition::DIFFER ||
|
||||
|
|
@ -94,7 +47,7 @@ static bool requiresFileInput(VideoCondition t)
|
|||
bool MacroConditionVideo::CheckShouldBeSkipped()
|
||||
{
|
||||
if (_condition != VideoCondition::PATTERN &&
|
||||
_condition != VideoCondition::OBJECT &&
|
||||
_condition != VideoCondition::OBJECT_CASCADE &&
|
||||
_condition != VideoCondition::HAS_CHANGED &&
|
||||
_condition != VideoCondition::HAS_NOT_CHANGED) {
|
||||
return false;
|
||||
|
|
@ -185,7 +138,7 @@ bool MacroConditionVideo::Save(obs_data_t *obj) const
|
|||
_blockUntilScreenshotDone);
|
||||
_brightnessThreshold.Save(obj, "brightnessThreshold");
|
||||
_patternMatchParameters.Save(obj);
|
||||
_objMatchParameters.Save(obj);
|
||||
_cascadeMatchParameters.Save(obj);
|
||||
_ocrParameters.Save(obj);
|
||||
_colorParameters.Save(obj);
|
||||
obs_data_set_bool(obj, "throttleEnabled", _throttleEnabled);
|
||||
|
|
@ -211,7 +164,7 @@ bool MacroConditionVideo::Load(obs_data_t *obj)
|
|||
_brightnessThreshold.Load(obj, "brightnessThreshold");
|
||||
}
|
||||
_patternMatchParameters.Load(obj);
|
||||
_objMatchParameters.Load(obj);
|
||||
_cascadeMatchParameters.Load(obj);
|
||||
_ocrParameters.Load(obj);
|
||||
_colorParameters.Load(obj);
|
||||
_throttleEnabled = obs_data_get_bool(obj, "throttleEnabled");
|
||||
|
|
@ -378,15 +331,11 @@ bool MacroConditionVideo::OutputChanged()
|
|||
|
||||
bool MacroConditionVideo::ScreenshotContainsObject()
|
||||
{
|
||||
auto model = _objMatchParameters.GetModel();
|
||||
if (!model) {
|
||||
auto *detector = _cascadeMatchParameters.GetDetector();
|
||||
if (!detector) {
|
||||
return false;
|
||||
}
|
||||
auto objects = MatchObject(_screenshotData.GetImage(), *model,
|
||||
_objMatchParameters.scaleFactor,
|
||||
_objMatchParameters.minNeighbors,
|
||||
_objMatchParameters.minSize.CV(),
|
||||
_objMatchParameters.maxSize.CV());
|
||||
auto objects = detector->Detect(_screenshotData.GetImage());
|
||||
const auto count = objects.size();
|
||||
SetTempVarValue("objectCount", std::to_string(count));
|
||||
return count > 0;
|
||||
|
|
@ -463,7 +412,7 @@ bool MacroConditionVideo::Compare()
|
|||
return _screenshotData.GetImage().isNull();
|
||||
case VideoCondition::PATTERN:
|
||||
return ScreenshotContainsPattern();
|
||||
case VideoCondition::OBJECT:
|
||||
case VideoCondition::OBJECT_CASCADE:
|
||||
return ScreenshotContainsObject();
|
||||
case VideoCondition::BRIGHTNESS:
|
||||
return CheckBrightnessThreshold();
|
||||
|
|
@ -531,7 +480,7 @@ void MacroConditionVideo::SetupTempVars()
|
|||
obs_module_text(
|
||||
"AdvSceneSwitcher.tempVar.video.matchHeight.description"));
|
||||
break;
|
||||
case VideoCondition::OBJECT:
|
||||
case VideoCondition::OBJECT_CASCADE:
|
||||
AddTempvar(
|
||||
"objectCount",
|
||||
obs_module_text(
|
||||
|
|
@ -584,18 +533,41 @@ static inline void populateVideoInputSelection(QComboBox *list)
|
|||
|
||||
static inline void populateConditionSelection(QComboBox *list)
|
||||
{
|
||||
const static std::vector<std::pair<VideoCondition, std::string>>
|
||||
conditionTypes = {
|
||||
{VideoCondition::MATCH,
|
||||
"AdvSceneSwitcher.condition.video.condition.match"},
|
||||
{VideoCondition::DIFFER,
|
||||
"AdvSceneSwitcher.condition.video.condition.differ"},
|
||||
{VideoCondition::HAS_NOT_CHANGED,
|
||||
"AdvSceneSwitcher.condition.video.condition.hasNotChanged"},
|
||||
{VideoCondition::HAS_CHANGED,
|
||||
"AdvSceneSwitcher.condition.video.condition.hasChanged"},
|
||||
{VideoCondition::NO_IMAGE,
|
||||
"AdvSceneSwitcher.condition.video.condition.noImage"},
|
||||
{VideoCondition::PATTERN,
|
||||
"AdvSceneSwitcher.condition.video.condition.pattern"},
|
||||
{VideoCondition::OBJECT_CASCADE,
|
||||
"AdvSceneSwitcher.condition.video.condition.object"},
|
||||
{VideoCondition::BRIGHTNESS,
|
||||
"AdvSceneSwitcher.condition.video.condition.brightness"},
|
||||
#ifdef OCR_SUPPORT
|
||||
{VideoCondition::OCR,
|
||||
"AdvSceneSwitcher.condition.video.condition.ocr"},
|
||||
#endif
|
||||
{VideoCondition::COLOR,
|
||||
"AdvSceneSwitcher.condition.video.condition.color"},
|
||||
};
|
||||
|
||||
for (auto &[value, name] : conditionTypes) {
|
||||
list->addItem(obs_module_text(name.c_str()),
|
||||
static_cast<int>(value));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void populatePageSegModeSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &[mode, name] : pageSegModes) {
|
||||
list->addItem(obs_module_text(name.c_str()),
|
||||
static_cast<int>(mode));
|
||||
}
|
||||
SetRowVisibleByValue(
|
||||
list,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.condition.object"),
|
||||
CascadeClassifierDetector::IsSupported());
|
||||
}
|
||||
|
||||
static inline void populatePatternMatchModeSelection(QComboBox *list)
|
||||
|
|
@ -606,628 +578,6 @@ static inline void populatePatternMatchModeSelection(QComboBox *list)
|
|||
}
|
||||
}
|
||||
|
||||
BrightnessEdit::BrightnessEdit(QWidget *parent,
|
||||
const std::shared_ptr<MacroConditionVideo> &data)
|
||||
: QWidget(parent),
|
||||
_threshold(new SliderSpinBox(
|
||||
0., 1.,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.brightnessThreshold"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.brightnessThresholdDescription"))),
|
||||
_current(new QLabel),
|
||||
_entryData(data)
|
||||
{
|
||||
auto layout = new QVBoxLayout;
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->addWidget(_threshold);
|
||||
layout->addWidget(_current);
|
||||
setLayout(layout);
|
||||
|
||||
QWidget::connect(
|
||||
_threshold,
|
||||
SIGNAL(DoubleValueChanged(const NumberVariable<double> &)),
|
||||
this,
|
||||
SLOT(BrightnessThresholdChanged(
|
||||
const NumberVariable<double> &)));
|
||||
QWidget::connect(&_timer, &QTimer::timeout, this,
|
||||
&BrightnessEdit::UpdateCurrentBrightness);
|
||||
_timer.start(1000);
|
||||
|
||||
_threshold->SetDoubleValue(_entryData->_brightnessThreshold);
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void BrightnessEdit::UpdateCurrentBrightness()
|
||||
{
|
||||
QString text = obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.currentBrightness");
|
||||
_current->setText(text.arg(_entryData->GetCurrentBrightness()));
|
||||
}
|
||||
|
||||
void BrightnessEdit::BrightnessThresholdChanged(const DoubleVariable &value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_brightnessThreshold = value;
|
||||
}
|
||||
|
||||
static void openFileInEditor(const std::string &filepath)
|
||||
{
|
||||
const auto path = QString::fromStdString(filepath);
|
||||
const QFileInfo fileInfo(path);
|
||||
if (!fileInfo.exists()) {
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
DisplayMessage(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.ocrOpenConfig.createFailed"));
|
||||
return;
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
QUrl fileUrl = QUrl::fromLocalFile(path);
|
||||
if (!QDesktopServices::openUrl(fileUrl)) {
|
||||
DisplayMessage(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.ocrOpenConfig.openFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
OCREdit::OCREdit(QWidget *parent, PreviewDialog *previewDialog,
|
||||
const std::shared_ptr<MacroConditionVideo> &data)
|
||||
: QWidget(parent),
|
||||
_matchText(new VariableTextEdit(this)),
|
||||
_regex(new RegexConfigWidget(this)),
|
||||
_colorButton(new VariableColorButton(
|
||||
this,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.selectColor"))),
|
||||
_colorThreshold(new SliderSpinBox(
|
||||
0., 1.,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.colorDeviationThreshold"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.colorDeviationThresholdDescription"),
|
||||
true)),
|
||||
_pageSegMode(new QComboBox()),
|
||||
_tesseractBaseDir(new FileSelection(FileSelection::Type::FOLDER)),
|
||||
_languageCode(new VariableLineEdit(this)),
|
||||
_useConfig(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.ocrUseConfigFile"))),
|
||||
_configFile(new FileSelection(FileSelection::Type::WRITE, this)),
|
||||
_openConfigFile(new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.ocrOpenConfigFile"))),
|
||||
_reloadConfig(new QPushButton()),
|
||||
_configLayout(new QHBoxLayout()),
|
||||
_previewDialog(previewDialog),
|
||||
_entryData(data)
|
||||
{
|
||||
populatePageSegModeSelection(_pageSegMode);
|
||||
|
||||
_reloadConfig->setMaximumWidth(22);
|
||||
SetButtonIcon(_reloadConfig, GetThemeTypeName() == "Light"
|
||||
? ":res/images/refresh.svg"
|
||||
: "theme:Dark/refresh.svg");
|
||||
_reloadConfig->setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.ocrConfigReload"));
|
||||
|
||||
QWidget::connect(_colorButton,
|
||||
SIGNAL(ColorVariableChanged(const ColorVariable &)),
|
||||
this, SLOT(ColorChanged(const ColorVariable &)));
|
||||
QWidget::connect(
|
||||
_colorThreshold,
|
||||
SIGNAL(DoubleValueChanged(const NumberVariable<double> &)),
|
||||
this,
|
||||
SLOT(ColorThresholdChanged(const NumberVariable<double> &)));
|
||||
QWidget::connect(_matchText, SIGNAL(textChanged()), this,
|
||||
SLOT(MatchTextChanged()));
|
||||
QWidget::connect(_regex,
|
||||
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
|
||||
SLOT(RegexChanged(const RegexConfig &)));
|
||||
QWidget::connect(_pageSegMode, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(PageSegModeChanged(int)));
|
||||
QWidget::connect(_tesseractBaseDir,
|
||||
SIGNAL(PathChanged(const QString &)), this,
|
||||
SLOT(TesseractBaseDirChanged(const QString &)));
|
||||
QWidget::connect(_languageCode, SIGNAL(editingFinished()), this,
|
||||
SLOT(LanguageChanged()));
|
||||
QWidget::connect(_useConfig, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(UseConfigChanged(int)));
|
||||
QWidget::connect(_configFile, SIGNAL(PathChanged(const QString &)),
|
||||
this, SLOT(ConfigFileChanged(const QString &)));
|
||||
QWidget::connect(_openConfigFile, &QPushButton::clicked, [this](bool) {
|
||||
openFileInEditor(
|
||||
_entryData->_ocrParameters.GetCustomConfigFile());
|
||||
});
|
||||
QWidget::connect(_reloadConfig, &QPushButton::clicked, [this](bool) {
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_ocrParameters.EnableCustomConfig(true);
|
||||
_previewDialog->OCRParametersChanged(
|
||||
_entryData->_ocrParameters);
|
||||
});
|
||||
|
||||
auto configFileHint = new QLabel();
|
||||
const QString path = GetThemeTypeName() == "Light"
|
||||
? ":/res/images/help.svg"
|
||||
: ":/res/images/help_light.svg";
|
||||
const QIcon icon(path);
|
||||
const QPixmap pixmap = icon.pixmap(QSize(16, 16));
|
||||
configFileHint->setPixmap(pixmap);
|
||||
configFileHint->setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.ocrConfigHint"));
|
||||
|
||||
const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{color}}", _colorButton},
|
||||
{"{{textType}}", _pageSegMode},
|
||||
{"{{tesseractBaseDir}}", _tesseractBaseDir},
|
||||
{"{{languageCode}}", _languageCode},
|
||||
{"{{configFile}}", _configFile},
|
||||
{"{{openConfigFile}}", _openConfigFile},
|
||||
{"{{reloadConfig}}", _reloadConfig},
|
||||
{"{{configFileHint}}", configFileHint},
|
||||
};
|
||||
|
||||
auto layout = new QVBoxLayout();
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
auto textLayout = new QHBoxLayout();
|
||||
textLayout->setContentsMargins(0, 0, 0, 0);
|
||||
textLayout->addWidget(_matchText);
|
||||
textLayout->addWidget(_regex);
|
||||
layout->addLayout(textLayout);
|
||||
auto pageModeSegLayout = new QHBoxLayout();
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.layout.ocrTextType"),
|
||||
pageModeSegLayout, widgetPlaceholders);
|
||||
layout->addLayout(pageModeSegLayout);
|
||||
auto baseDirLayout = new QHBoxLayout();
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.layout.ocrBaseDir"),
|
||||
baseDirLayout, widgetPlaceholders, false);
|
||||
layout->addLayout(baseDirLayout);
|
||||
auto languageLayout = new QHBoxLayout();
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.layout.ocrLanguage"),
|
||||
languageLayout, widgetPlaceholders);
|
||||
layout->addLayout(languageLayout);
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.layout.ocrConfig"),
|
||||
_configLayout, widgetPlaceholders, false);
|
||||
layout->addWidget(_useConfig);
|
||||
layout->addLayout(_configLayout);
|
||||
auto colorPickLayout = new QHBoxLayout();
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.layout.ocrColorPick"),
|
||||
colorPickLayout, widgetPlaceholders);
|
||||
layout->addLayout(colorPickLayout);
|
||||
layout->addWidget(_colorThreshold);
|
||||
setLayout(layout);
|
||||
|
||||
_matchText->setPlainText(_entryData->_ocrParameters.text);
|
||||
_regex->SetRegexConfig(_entryData->_ocrParameters.regex);
|
||||
_colorButton->SetValue(_entryData->_ocrParameters.color);
|
||||
_colorThreshold->SetDoubleValue(
|
||||
_entryData->_ocrParameters.colorThreshold);
|
||||
_pageSegMode->setCurrentIndex(_pageSegMode->findData(
|
||||
static_cast<int>(_entryData->_ocrParameters.GetPageMode())));
|
||||
_tesseractBaseDir->SetPath(
|
||||
_entryData->_ocrParameters.GetTesseractBasePath());
|
||||
_languageCode->setText(_entryData->_ocrParameters.GetLanguageCode());
|
||||
_useConfig->setChecked(
|
||||
_entryData->_ocrParameters.CustomConfigIsEnabled());
|
||||
_configFile->SetPath(_entryData->_ocrParameters.GetCustomConfigFile());
|
||||
SetLayoutVisible(_configLayout,
|
||||
_entryData->_ocrParameters.CustomConfigIsEnabled());
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void OCREdit::ColorChanged(const ColorVariable &value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_ocrParameters.color = value;
|
||||
|
||||
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
|
||||
}
|
||||
|
||||
void OCREdit::ColorThresholdChanged(const DoubleVariable &value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_ocrParameters.colorThreshold = value;
|
||||
|
||||
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
|
||||
}
|
||||
|
||||
void OCREdit::MatchTextChanged()
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_ocrParameters.text =
|
||||
_matchText->toPlainText().toUtf8().constData();
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
|
||||
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
|
||||
}
|
||||
|
||||
void OCREdit::RegexChanged(const RegexConfig &conf)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_ocrParameters.regex = conf;
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
|
||||
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
|
||||
}
|
||||
|
||||
void OCREdit::PageSegModeChanged(int idx)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->SetPageSegMode(static_cast<tesseract::PageSegMode>(
|
||||
_pageSegMode->itemData(idx).toInt()));
|
||||
|
||||
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
|
||||
}
|
||||
|
||||
void OCREdit::TesseractBaseDirChanged(const QString &path)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
if (!_entryData->SetTesseractBaseDir(path.toStdString())) {
|
||||
const QString message(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.ocrLanguageNotFound"));
|
||||
const QDir dataDir(path);
|
||||
const QString fileName(_languageCode->text() + ".traineddata");
|
||||
DisplayMessage(message.arg(fileName, dataDir.absolutePath()));
|
||||
|
||||
// Reset to previous value
|
||||
const QSignalBlocker b(this);
|
||||
_tesseractBaseDir->SetPath(
|
||||
_entryData->_ocrParameters.GetTesseractBasePath());
|
||||
return;
|
||||
}
|
||||
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
|
||||
}
|
||||
|
||||
void OCREdit::LanguageChanged()
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
if (!_entryData->SetLanguageCode(_languageCode->text().toStdString())) {
|
||||
const QString message(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.ocrLanguageNotFound"));
|
||||
const QDir dataDir(QString::fromStdString(
|
||||
_entryData->_ocrParameters.GetTesseractBasePath()));
|
||||
const QString fileName(_languageCode->text() + ".traineddata");
|
||||
DisplayMessage(message.arg(fileName, dataDir.absolutePath()));
|
||||
|
||||
// Reset to previous value
|
||||
const QSignalBlocker b(this);
|
||||
_languageCode->setText(
|
||||
_entryData->_ocrParameters.GetLanguageCode());
|
||||
return;
|
||||
}
|
||||
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
|
||||
}
|
||||
|
||||
void OCREdit::UseConfigChanged(int value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_ocrParameters.EnableCustomConfig(value);
|
||||
SetLayoutVisible(_configLayout, value);
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
|
||||
}
|
||||
|
||||
void OCREdit::ConfigFileChanged(const QString &path)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_ocrParameters.SetCustomConfigFile(path.toStdString());
|
||||
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
|
||||
}
|
||||
|
||||
ObjectDetectEdit::ObjectDetectEdit(
|
||||
QWidget *parent, PreviewDialog *previewDialog,
|
||||
const std::shared_ptr<MacroConditionVideo> &data)
|
||||
: QWidget(parent),
|
||||
_modelDataPath(new FileSelection()),
|
||||
_objectScaleThreshold(new SliderSpinBox(
|
||||
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)),
|
||||
_previewDialog(previewDialog),
|
||||
_entryData(data)
|
||||
{
|
||||
_minNeighbors->setMinimum(minMinNeighbors);
|
||||
_minNeighbors->setMaximum(maxMinNeighbors);
|
||||
|
||||
QWidget::connect(
|
||||
_objectScaleThreshold,
|
||||
SIGNAL(DoubleValueChanged(const NumberVariable<double> &)),
|
||||
this,
|
||||
SLOT(ObjectScaleThresholdChanged(
|
||||
const NumberVariable<double> &)));
|
||||
QWidget::connect(_minNeighbors, SIGNAL(valueChanged(int)), this,
|
||||
SLOT(MinNeighborsChanged(int)));
|
||||
QWidget::connect(_minSize, SIGNAL(SizeChanged(Size)), this,
|
||||
SLOT(MinSizeChanged(Size)));
|
||||
QWidget::connect(_maxSize, SIGNAL(SizeChanged(Size)), this,
|
||||
SLOT(MaxSizeChanged(Size)));
|
||||
QWidget::connect(_modelDataPath, SIGNAL(PathChanged(const QString &)),
|
||||
this, SLOT(ModelPathChanged(const QString &)));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{minNeighbors}}", _minNeighbors},
|
||||
{"{{minSize}}", _minSize},
|
||||
{"{{maxSize}}", _maxSize},
|
||||
{"{{modelDataPath}}", _modelDataPath},
|
||||
};
|
||||
|
||||
auto pathLayout = new QHBoxLayout;
|
||||
pathLayout->setContentsMargins(0, 0, 0, 0);
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.layout.modelPath"),
|
||||
pathLayout, widgetPlaceholders);
|
||||
|
||||
auto neighborsLayout = new QHBoxLayout;
|
||||
neighborsLayout->setContentsMargins(0, 0, 0, 0);
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.layout.minNeighbor"),
|
||||
neighborsLayout, widgetPlaceholders);
|
||||
|
||||
auto 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);
|
||||
auto sizeLayout = new QHBoxLayout;
|
||||
sizeLayout->setContentsMargins(0, 0, 0, 0);
|
||||
sizeLayout->addLayout(sizeGrid);
|
||||
sizeLayout->addStretch();
|
||||
|
||||
auto layout = new QVBoxLayout();
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->addLayout(pathLayout);
|
||||
layout->addLayout(neighborsLayout);
|
||||
layout->addLayout(sizeLayout);
|
||||
setLayout(layout);
|
||||
|
||||
_modelDataPath->SetPath(_entryData->_objMatchParameters.GetModelPath());
|
||||
_objectScaleThreshold->SetDoubleValue(
|
||||
_entryData->_objMatchParameters.scaleFactor);
|
||||
_minNeighbors->setValue(_entryData->_objMatchParameters.minNeighbors);
|
||||
_minSize->SetSize(_entryData->_objMatchParameters.minSize);
|
||||
_maxSize->SetSize(_entryData->_objMatchParameters.maxSize);
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void ObjectDetectEdit::ObjectScaleThresholdChanged(const DoubleVariable &value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_objMatchParameters.scaleFactor = value;
|
||||
_previewDialog->ObjDetectParametersChanged(
|
||||
_entryData->_objMatchParameters);
|
||||
}
|
||||
|
||||
void ObjectDetectEdit::MinNeighborsChanged(int value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_objMatchParameters.minNeighbors = value;
|
||||
_previewDialog->ObjDetectParametersChanged(
|
||||
_entryData->_objMatchParameters);
|
||||
}
|
||||
|
||||
void ObjectDetectEdit::MinSizeChanged(advss::Size value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_objMatchParameters.minSize = value;
|
||||
_previewDialog->ObjDetectParametersChanged(
|
||||
_entryData->_objMatchParameters);
|
||||
}
|
||||
|
||||
void ObjectDetectEdit::MaxSizeChanged(advss::Size value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_objMatchParameters.maxSize = value;
|
||||
_previewDialog->ObjDetectParametersChanged(
|
||||
_entryData->_objMatchParameters);
|
||||
}
|
||||
|
||||
void ObjectDetectEdit::ModelPathChanged(const QString &text)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool dataLoaded = false;
|
||||
{
|
||||
auto lock = LockContext();
|
||||
std::string path = text.toStdString();
|
||||
dataLoaded = _entryData->_objMatchParameters.SetModelPath(path);
|
||||
}
|
||||
if (!dataLoaded) {
|
||||
DisplayMessage(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.modelLoadFail"));
|
||||
}
|
||||
_previewDialog->ObjDetectParametersChanged(
|
||||
_entryData->_objMatchParameters);
|
||||
}
|
||||
|
||||
ColorEdit::ColorEdit(QWidget *parent,
|
||||
const std::shared_ptr<MacroConditionVideo> &data)
|
||||
: QWidget(parent),
|
||||
_matchThreshold(new SliderSpinBox(
|
||||
0., 1.,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.colorMatchThreshold"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.colorMatchThresholdDescription"),
|
||||
true)),
|
||||
_colorThreshold(new SliderSpinBox(
|
||||
0., 1.,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.colorDeviationThreshold"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.colorDeviationThresholdDescription"),
|
||||
true)),
|
||||
_colorButton(new VariableColorButton(
|
||||
this,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.selectColor"))),
|
||||
_entryData(data)
|
||||
{
|
||||
QWidget::connect(_colorButton,
|
||||
SIGNAL(ColorVariableChanged(const ColorVariable &)),
|
||||
this, SLOT(ColorChanged(const ColorVariable &)));
|
||||
QWidget::connect(
|
||||
_matchThreshold,
|
||||
SIGNAL(DoubleValueChanged(const NumberVariable<double> &)),
|
||||
this,
|
||||
SLOT(MatchThresholdChanged(const NumberVariable<double> &)));
|
||||
QWidget::connect(
|
||||
_colorThreshold,
|
||||
SIGNAL(DoubleValueChanged(const NumberVariable<double> &)),
|
||||
this,
|
||||
SLOT(ColorThresholdChanged(const NumberVariable<double> &)));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{color}}", _colorButton},
|
||||
};
|
||||
|
||||
auto colorLayout = new QHBoxLayout;
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.layout.color"),
|
||||
colorLayout, widgetPlaceholders);
|
||||
|
||||
auto layout = new QVBoxLayout;
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->addLayout(colorLayout);
|
||||
layout->addWidget(_colorThreshold);
|
||||
layout->addWidget(_matchThreshold);
|
||||
setLayout(layout);
|
||||
|
||||
_matchThreshold->SetDoubleValue(
|
||||
_entryData->_colorParameters.matchThreshold);
|
||||
_colorThreshold->SetDoubleValue(
|
||||
_entryData->_colorParameters.colorThreshold);
|
||||
_colorButton->SetValue(_entryData->_colorParameters.color);
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void ColorEdit::ColorChanged(const ColorVariable &value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_colorParameters.color = value;
|
||||
}
|
||||
|
||||
void ColorEdit::MatchThresholdChanged(const DoubleVariable &value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_colorParameters.matchThreshold = value;
|
||||
}
|
||||
|
||||
void ColorEdit::ColorThresholdChanged(const DoubleVariable &value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_colorParameters.colorThreshold = value;
|
||||
}
|
||||
|
||||
AreaEdit::AreaEdit(QWidget *parent, PreviewDialog *previewDialog,
|
||||
const std::shared_ptr<MacroConditionVideo> &data)
|
||||
: QWidget(parent),
|
||||
_checkAreaEnable(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.layout.checkAreaEnable"))),
|
||||
_checkArea(new AreaSelection(0, 99999)),
|
||||
_selectArea(new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.selectArea"))),
|
||||
_previewDialog(previewDialog),
|
||||
_entryData(data)
|
||||
{
|
||||
QWidget::connect(_checkAreaEnable, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(CheckAreaEnableChanged(int)));
|
||||
QWidget::connect(_checkArea, SIGNAL(AreaChanged(Area)), this,
|
||||
SLOT(CheckAreaChanged(Area)));
|
||||
QWidget::connect(_selectArea, SIGNAL(clicked()), this,
|
||||
SLOT(SelectAreaClicked()));
|
||||
QWidget::connect(_previewDialog, SIGNAL(SelectionAreaChanged(QRect)),
|
||||
this, SLOT(CheckAreaChanged(QRect)));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{checkAreaEnable}}", _checkAreaEnable},
|
||||
{"{{checkArea}}", _checkArea},
|
||||
{"{{selectArea}}", _selectArea},
|
||||
};
|
||||
|
||||
auto layout = new QHBoxLayout;
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.layout.checkArea"),
|
||||
layout, widgetPlaceholders);
|
||||
setLayout(layout);
|
||||
|
||||
_checkAreaEnable->setChecked(_entryData->_areaParameters.enable);
|
||||
_checkArea->SetArea(_entryData->_areaParameters.area);
|
||||
SetWidgetVisibility();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void AreaEdit::SetWidgetVisibility()
|
||||
{
|
||||
_checkArea->setVisible(_entryData->_areaParameters.enable);
|
||||
_selectArea->setVisible(_entryData->_areaParameters.enable);
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void AreaEdit::SelectAreaClicked()
|
||||
{
|
||||
_previewDialog->show();
|
||||
_previewDialog->raise();
|
||||
_previewDialog->activateWindow();
|
||||
_previewDialog->SelectArea();
|
||||
}
|
||||
|
||||
void AreaEdit::CheckAreaEnableChanged(int value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_areaParameters.enable = value;
|
||||
SetWidgetVisibility();
|
||||
_previewDialog->AreaParametersChanged(_entryData->_areaParameters);
|
||||
emit Resized();
|
||||
}
|
||||
|
||||
void AreaEdit::CheckAreaChanged(Area value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_areaParameters.area = value;
|
||||
_previewDialog->AreaParametersChanged(_entryData->_areaParameters);
|
||||
}
|
||||
|
||||
void AreaEdit::CheckAreaChanged(QRect rect)
|
||||
{
|
||||
const QSignalBlocker b(_checkArea);
|
||||
Area area{rect.topLeft().x(), rect.y(), rect.width(), rect.height()};
|
||||
_checkArea->SetArea(area);
|
||||
CheckAreaChanged(area);
|
||||
}
|
||||
|
||||
static QStringList getVideoSourcesList()
|
||||
{
|
||||
auto sources = GetVideoSourceNames();
|
||||
|
|
@ -1263,7 +613,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
|
|||
_previewDialog(this),
|
||||
_brightness(new BrightnessEdit(this, entryData)),
|
||||
_ocr(new OCREdit(this, &_previewDialog, entryData)),
|
||||
_objectDetect(new ObjectDetectEdit(this, &_previewDialog, entryData)),
|
||||
_cascadeClassifierEdit(
|
||||
new CascadeClassifierEdit(this, &_previewDialog, entryData)),
|
||||
_color(new ColorEdit(this, entryData)),
|
||||
_area(new AreaEdit(this, &_previewDialog, entryData)),
|
||||
_throttleControlLayout(new QHBoxLayout),
|
||||
|
|
@ -1292,8 +643,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
|
|||
QSizePolicy::Preferred);
|
||||
_ocr->setSizePolicy(QSizePolicy::MinimumExpanding,
|
||||
QSizePolicy::Preferred);
|
||||
_objectDetect->setSizePolicy(QSizePolicy::MinimumExpanding,
|
||||
QSizePolicy::Preferred);
|
||||
_cascadeClassifierEdit->setSizePolicy(QSizePolicy::MinimumExpanding,
|
||||
QSizePolicy::Preferred);
|
||||
_color->setSizePolicy(QSizePolicy::MinimumExpanding,
|
||||
QSizePolicy::Preferred);
|
||||
_area->setSizePolicy(QSizePolicy::MinimumExpanding,
|
||||
|
|
@ -1392,7 +743,7 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
|
|||
mainLayout->addLayout(_patternMatchModeLayout);
|
||||
mainLayout->addWidget(_brightness);
|
||||
mainLayout->addWidget(_ocr);
|
||||
mainLayout->addWidget(_objectDetect);
|
||||
mainLayout->addWidget(_cascadeClassifierEdit);
|
||||
mainLayout->addWidget(_color);
|
||||
mainLayout->addLayout(_throttleControlLayout);
|
||||
mainLayout->addWidget(_area);
|
||||
|
|
@ -1634,13 +985,14 @@ void MacroConditionVideoEdit::ShowMatchClicked()
|
|||
static bool needsShowMatch(VideoCondition cond)
|
||||
{
|
||||
return cond == VideoCondition::PATTERN ||
|
||||
cond == VideoCondition::OBJECT || cond == VideoCondition::OCR;
|
||||
cond == VideoCondition::OBJECT_CASCADE ||
|
||||
cond == VideoCondition::OCR;
|
||||
}
|
||||
|
||||
static bool needsThrottleControls(VideoCondition cond)
|
||||
{
|
||||
return cond == VideoCondition::PATTERN ||
|
||||
cond == VideoCondition::OBJECT ||
|
||||
cond == VideoCondition::OBJECT_CASCADE ||
|
||||
cond == VideoCondition::HAS_CHANGED ||
|
||||
cond == VideoCondition::HAS_NOT_CHANGED;
|
||||
}
|
||||
|
|
@ -1681,8 +1033,8 @@ void MacroConditionVideoEdit::SetWidgetVisibility()
|
|||
VideoCondition::BRIGHTNESS);
|
||||
_showMatch->setVisible(needsShowMatch(_entryData->GetCondition()));
|
||||
_ocr->setVisible(_entryData->GetCondition() == VideoCondition::OCR);
|
||||
_objectDetect->setVisible(_entryData->GetCondition() ==
|
||||
VideoCondition::OBJECT);
|
||||
_cascadeClassifierEdit->setVisible(_entryData->GetCondition() ==
|
||||
VideoCondition::OBJECT_CASCADE);
|
||||
_color->setVisible(_entryData->GetCondition() == VideoCondition::COLOR);
|
||||
SetLayoutVisible(_throttleControlLayout,
|
||||
needsThrottleControls(_entryData->GetCondition()));
|
||||
|
|
@ -1725,8 +1077,8 @@ void MacroConditionVideoEdit::SetupPreviewDialogParams()
|
|||
{
|
||||
_previewDialog.PatternMatchParametersChanged(
|
||||
_entryData->_patternMatchParameters);
|
||||
_previewDialog.ObjDetectParametersChanged(
|
||||
_entryData->_objMatchParameters);
|
||||
_previewDialog.CascadeClassifierParametersChanged(
|
||||
_entryData->_cascadeMatchParameters);
|
||||
_previewDialog.OCRParametersChanged(_entryData->_ocrParameters);
|
||||
_previewDialog.VideoSelectionChanged(_entryData->_video);
|
||||
_previewDialog.AreaParametersChanged(_entryData->_areaParameters);
|
||||
|
|
|
|||
|
|
@ -4,18 +4,21 @@
|
|||
#include "parameter-wrappers.hpp"
|
||||
#include "preview-dialog.hpp"
|
||||
|
||||
#include <help-icon.hpp>
|
||||
#include <macro-condition-edit.hpp>
|
||||
#include <file-selection.hpp>
|
||||
#include <screenshot-helper.hpp>
|
||||
#include <slider-spinbox.hpp>
|
||||
#include <source-helpers.hpp>
|
||||
#include <variable-color-button.hpp>
|
||||
#include <variable-line-edit.hpp>
|
||||
#include <variable-text-edit.hpp>
|
||||
#include "help-icon.hpp"
|
||||
#include "section.hpp"
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include "file-selection.hpp"
|
||||
#include "screenshot-helper.hpp"
|
||||
#include "slider-spinbox.hpp"
|
||||
#include "source-helpers.hpp"
|
||||
#include "variable-color-button.hpp"
|
||||
#include "variable-line-edit.hpp"
|
||||
#include "variable-text-edit.hpp"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QLineEdit>
|
||||
#include <QDateTime>
|
||||
#include <QGridLayout>
|
||||
#include <QHBoxLayout>
|
||||
|
|
@ -70,7 +73,7 @@ public:
|
|||
bool _blockUntilScreenshotDone = true;
|
||||
NumberVariable<double> _brightnessThreshold = 0.5;
|
||||
PatternMatchParameters _patternMatchParameters;
|
||||
ObjDetectParameters _objMatchParameters;
|
||||
CascadeClassifierParameters _cascadeMatchParameters;
|
||||
OCRParameters _ocrParameters;
|
||||
ColorParameters _colorParameters;
|
||||
AreaParameters _areaParameters;
|
||||
|
|
@ -174,12 +177,12 @@ private:
|
|||
bool _loading = true;
|
||||
};
|
||||
|
||||
class ObjectDetectEdit : public QWidget {
|
||||
class CascadeClassifierEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ObjectDetectEdit(QWidget *parent, PreviewDialog *,
|
||||
const std::shared_ptr<MacroConditionVideo> &);
|
||||
CascadeClassifierEdit(QWidget *parent, PreviewDialog *,
|
||||
const std::shared_ptr<MacroConditionVideo> &);
|
||||
|
||||
private slots:
|
||||
void ModelPathChanged(const QString &text);
|
||||
|
|
@ -320,7 +323,7 @@ private:
|
|||
|
||||
BrightnessEdit *_brightness;
|
||||
OCREdit *_ocr;
|
||||
ObjectDetectEdit *_objectDetect;
|
||||
CascadeClassifierEdit *_cascadeClassifierEdit;
|
||||
ColorEdit *_color;
|
||||
AreaEdit *_area;
|
||||
|
||||
|
|
|
|||
18
plugins/video/object-detector.hpp
Normal file
18
plugins/video/object-detector.hpp
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
#include <opencv2/opencv.hpp>
|
||||
|
||||
#include <QImage>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace advss {
|
||||
|
||||
struct ObjectDetector {
|
||||
virtual bool Load(const std::string &modelPath) = 0;
|
||||
virtual bool IsLoaded() const = 0;
|
||||
virtual std::vector<cv::Rect> Detect(QImage &img) = 0;
|
||||
virtual ~ObjectDetector() = default;
|
||||
};
|
||||
|
||||
} // namespace advss
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
#include "opencv-helpers.hpp"
|
||||
|
||||
#include <log-helper.hpp>
|
||||
#include "log-helper.hpp"
|
||||
|
||||
namespace advss {
|
||||
|
||||
|
|
@ -135,29 +134,6 @@ double MatchPattern(QImage &img, QImage &pattern, double threshold,
|
|||
matchColor);
|
||||
}
|
||||
|
||||
std::vector<cv::Rect> MatchObject(QImage &img, cv::CascadeClassifier &cascade,
|
||||
double scaleFactor, int minNeighbors,
|
||||
const cv::Size &minSize,
|
||||
const cv::Size &maxSize)
|
||||
{
|
||||
if (img.isNull() || cascade.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto image = QImageToMat(img);
|
||||
cv::Mat frameGray;
|
||||
cv::cvtColor(image, frameGray, cv::COLOR_RGBA2GRAY);
|
||||
cv::equalizeHist(frameGray, frameGray);
|
||||
std::vector<cv::Rect> objects;
|
||||
try {
|
||||
cascade.detectMultiScale(frameGray, objects, scaleFactor,
|
||||
minNeighbors, 0, minSize, maxSize);
|
||||
} catch (const std::exception &e) {
|
||||
vblog(LOG_INFO, "detectMultiScale failed: %s", e.what());
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
uchar GetAvgBrightness(QImage &img)
|
||||
{
|
||||
if (img.isNull()) {
|
||||
|
|
|
|||
|
|
@ -72,10 +72,6 @@ double MatchPattern(QImage &img, QImage &pattern, double threshold,
|
|||
cv::Mat &result, bool useAlphaAsMask,
|
||||
cv::TemplateMatchModes matchMode);
|
||||
int CountPatternMatches(const cv::Mat &result, const cv::Size &patternSize);
|
||||
std::vector<cv::Rect> MatchObject(QImage &img, cv::CascadeClassifier &cascade,
|
||||
double scaleFactor, int minNeighbors,
|
||||
const cv::Size &minSize,
|
||||
const cv::Size &maxSize);
|
||||
uchar GetAvgBrightness(QImage &img);
|
||||
cv::Mat PreprocessForOCR(const QImage &image, const QColor &color,
|
||||
double colorDiff);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
#include "parameter-wrappers.hpp"
|
||||
#include "cascade-classifier-detector.hpp"
|
||||
#include "log-helper.hpp"
|
||||
#include "source-helpers.hpp"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <source-helpers.hpp>
|
||||
|
||||
namespace advss {
|
||||
|
||||
|
|
@ -48,45 +49,6 @@ bool PatternMatchParameters::Load(obs_data_t *obj)
|
|||
return true;
|
||||
}
|
||||
|
||||
static std::shared_ptr<cv::CascadeClassifier>
|
||||
initObjectCascade(std::string &path)
|
||||
{
|
||||
auto cascade = std::make_shared<cv::CascadeClassifier>();
|
||||
try {
|
||||
cascade->load(path);
|
||||
} catch (...) {
|
||||
blog(LOG_WARNING, "failed to load model data \"%s\"",
|
||||
path.c_str());
|
||||
}
|
||||
return cascade;
|
||||
}
|
||||
|
||||
bool ObjDetectParameters::LoadModelData()
|
||||
{
|
||||
const auto path = QString::fromStdString(modelPath);
|
||||
if (!QFileInfo(path).exists(path)) {
|
||||
cascade.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
cascade = initObjectCascade(modelPath);
|
||||
return !cascade->empty();
|
||||
}
|
||||
|
||||
bool ObjDetectParameters::Save(obs_data_t *obj) const
|
||||
{
|
||||
auto data = obs_data_create();
|
||||
obs_data_set_string(data, "modelPath", modelPath.c_str());
|
||||
scaleFactor.Save(data, "scaleFactor");
|
||||
obs_data_set_int(data, "minNeighbors", minNeighbors);
|
||||
minSize.Save(data, "minSize");
|
||||
maxSize.Save(data, "maxSize");
|
||||
obs_data_set_obj(obj, "objectMatchData", data);
|
||||
obs_data_set_int(data, "version", 2);
|
||||
obs_data_release(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool isScaleFactorValid(double scaleFactor)
|
||||
{
|
||||
return scaleFactor > 1.;
|
||||
|
|
@ -98,11 +60,41 @@ static bool isMinNeighborsValid(int minNeighbors)
|
|||
minNeighbors <= maxMinNeighbors;
|
||||
}
|
||||
|
||||
bool ObjDetectParameters::Load(obs_data_t *obj)
|
||||
bool CascadeClassifierParameters::LoadModelData()
|
||||
{
|
||||
const auto path = QString::fromStdString(_modelPath);
|
||||
if (!QFileInfo(path).exists(path)) {
|
||||
_detector.reset();
|
||||
return false;
|
||||
}
|
||||
auto det = std::make_unique<CascadeClassifierDetector>();
|
||||
if (!det->Load(_modelPath)) {
|
||||
_detector.reset();
|
||||
return false;
|
||||
}
|
||||
_detector = std::move(det);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CascadeClassifierParameters::Save(obs_data_t *obj) const
|
||||
{
|
||||
auto data = obs_data_create();
|
||||
obs_data_set_string(data, "modelPath", _modelPath.c_str());
|
||||
scaleFactor.Save(data, "scaleFactor");
|
||||
obs_data_set_int(data, "minNeighbors", minNeighbors);
|
||||
minSize.Save(data, "minSize");
|
||||
maxSize.Save(data, "maxSize");
|
||||
obs_data_set_int(data, "version", 2);
|
||||
obs_data_set_obj(obj, "objectMatchData", data);
|
||||
obs_data_release(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CascadeClassifierParameters::Load(obs_data_t *obj)
|
||||
{
|
||||
// TODO: Remove this fallback in a future version
|
||||
if (!obs_data_has_user_value(obj, "patternMatchData")) {
|
||||
modelPath = obs_data_get_string(obj, "modelDataPath");
|
||||
_modelPath = obs_data_get_string(obj, "modelDataPath");
|
||||
scaleFactor = obs_data_get_double(obj, "scaleFactor");
|
||||
if (!isScaleFactorValid(scaleFactor)) {
|
||||
scaleFactor = 1.1;
|
||||
|
|
@ -116,7 +108,7 @@ bool ObjDetectParameters::Load(obs_data_t *obj)
|
|||
return true;
|
||||
}
|
||||
auto data = obs_data_get_obj(obj, "objectMatchData");
|
||||
modelPath = obs_data_get_string(data, "modelPath");
|
||||
_modelPath = obs_data_get_string(data, "modelPath");
|
||||
scaleFactor.Load(data, "scaleFactor");
|
||||
// TODO: Remove this fallback in a future version
|
||||
if (!obs_data_has_user_value(data, "version")) {
|
||||
|
|
@ -129,11 +121,11 @@ bool ObjDetectParameters::Load(obs_data_t *obj)
|
|||
// which invalidates previously saved default model paths.
|
||||
const std::string oldPrefix =
|
||||
"../../data/obs-plugins/advanced-scene-switcher/res/cascadeClassifiers/";
|
||||
if (modelPath.substr(0, oldPrefix.size()) == oldPrefix) {
|
||||
modelPath = std::string(obs_get_module_data_path(
|
||||
obs_current_module())) +
|
||||
"/res/cascadeClassifiers/" +
|
||||
modelPath.substr(oldPrefix.size());
|
||||
if (_modelPath.substr(0, oldPrefix.size()) == oldPrefix) {
|
||||
_modelPath = std::string(obs_get_module_data_path(
|
||||
obs_current_module())) +
|
||||
"/res/cascadeClassifiers/" +
|
||||
_modelPath.substr(oldPrefix.size());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
@ -147,27 +139,29 @@ bool ObjDetectParameters::Load(obs_data_t *obj)
|
|||
minSize.Load(data, "minSize");
|
||||
maxSize.Load(data, "maxSize");
|
||||
obs_data_release(data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ObjDetectParameters::SetModelPath(const std::string &path)
|
||||
bool CascadeClassifierParameters::SetModelPath(const std::string &path)
|
||||
{
|
||||
modelPath = path;
|
||||
_modelPath = path;
|
||||
return LoadModelData();
|
||||
}
|
||||
|
||||
std::shared_ptr<cv::CascadeClassifier> ObjDetectParameters::GetModel()
|
||||
ObjectDetector *CascadeClassifierParameters::GetDetector()
|
||||
{
|
||||
if (cascade && !cascade->empty()) {
|
||||
return cascade;
|
||||
if (!_detector || !_detector->IsLoaded()) {
|
||||
if (!LoadModelData()) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (!LoadModelData()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return cascade;
|
||||
auto *cascade =
|
||||
static_cast<CascadeClassifierDetector *>(_detector.get());
|
||||
cascade->scaleFactor = scaleFactor;
|
||||
cascade->minNeighbors = minNeighbors;
|
||||
cascade->minSize = minSize.CV();
|
||||
cascade->maxSize = maxSize.CV();
|
||||
return _detector.get();
|
||||
}
|
||||
|
||||
bool AreaParameters::Save(obs_data_t *obj) const
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
#pragma once
|
||||
#include "object-detector.hpp"
|
||||
#include "opencv-helpers.hpp"
|
||||
#include "obs-module-helper.hpp"
|
||||
#include "area-selection.hpp"
|
||||
#include "source-selection.hpp"
|
||||
#include "scene-selection.hpp"
|
||||
#include "regex-config.hpp"
|
||||
#include "variable-color.hpp"
|
||||
#include "variable-string.hpp"
|
||||
#include "variable-number.hpp"
|
||||
|
||||
#include <source-selection.hpp>
|
||||
#include <scene-selection.hpp>
|
||||
#include <regex-config.hpp>
|
||||
#include <variable-color.hpp>
|
||||
#include <variable-string.hpp>
|
||||
#include <variable-number.hpp>
|
||||
#include <obs.hpp>
|
||||
#include <obs-module.h>
|
||||
|
||||
|
|
@ -27,7 +28,7 @@ enum class VideoCondition {
|
|||
HAS_CHANGED,
|
||||
NO_IMAGE,
|
||||
PATTERN,
|
||||
OBJECT,
|
||||
OBJECT_CASCADE,
|
||||
BRIGHTNESS,
|
||||
OCR,
|
||||
COLOR,
|
||||
|
|
@ -64,14 +65,37 @@ public:
|
|||
NumberVariable<double> threshold = 0.999;
|
||||
};
|
||||
|
||||
class ObjDetectParameters {
|
||||
class CascadeClassifierParameters {
|
||||
public:
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
|
||||
CascadeClassifierParameters() = default;
|
||||
CascadeClassifierParameters(const CascadeClassifierParameters &other)
|
||||
: scaleFactor(other.scaleFactor),
|
||||
minNeighbors(other.minNeighbors),
|
||||
minSize(other.minSize),
|
||||
maxSize(other.maxSize),
|
||||
_modelPath(other._modelPath)
|
||||
{
|
||||
}
|
||||
CascadeClassifierParameters &
|
||||
operator=(const CascadeClassifierParameters &other)
|
||||
{
|
||||
if (this != &other) {
|
||||
scaleFactor = other.scaleFactor;
|
||||
minNeighbors = other.minNeighbors;
|
||||
minSize = other.minSize;
|
||||
maxSize = other.maxSize;
|
||||
_modelPath = other._modelPath;
|
||||
_detector.reset();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool SetModelPath(const std::string &path);
|
||||
const std::string &GetModelPath() const { return modelPath; }
|
||||
std::shared_ptr<cv::CascadeClassifier> GetModel();
|
||||
const std::string &GetModelPath() const { return _modelPath; }
|
||||
ObjectDetector *GetDetector();
|
||||
|
||||
NumberVariable<double> scaleFactor = defaultScaleFactor;
|
||||
int minNeighbors = minMinNeighbors;
|
||||
|
|
@ -81,8 +105,8 @@ public:
|
|||
private:
|
||||
bool LoadModelData();
|
||||
|
||||
std::shared_ptr<cv::CascadeClassifier> cascade;
|
||||
std::string modelPath =
|
||||
std::unique_ptr<ObjectDetector> _detector;
|
||||
std::string _modelPath =
|
||||
obs_get_module_data_path(obs_current_module()) +
|
||||
std::string(
|
||||
"/res/cascadeClassifiers/haarcascade_frontalface_alt.xml");
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
#include "preview-dialog.hpp"
|
||||
#include "opencv-helpers.hpp"
|
||||
#include "screenshot-helper.hpp"
|
||||
#include "ui-helpers.hpp"
|
||||
|
||||
#include <QLayout>
|
||||
#include <screenshot-helper.hpp>
|
||||
|
||||
namespace advss {
|
||||
|
||||
|
|
@ -131,10 +131,11 @@ void PreviewDialog::PatternMatchParametersChanged(
|
|||
_patternImageData = CreatePatternData(_patternMatchParams.image);
|
||||
}
|
||||
|
||||
void PreviewDialog::ObjDetectParametersChanged(const ObjDetectParameters ¶ms)
|
||||
void PreviewDialog::CascadeClassifierParametersChanged(
|
||||
const CascadeClassifierParameters ¶ms)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_mtx);
|
||||
_objDetectParams = std::make_shared<ObjDetectParameters>(params);
|
||||
_cascadeParams = std::make_shared<CascadeClassifierParameters>(params);
|
||||
}
|
||||
|
||||
void PreviewDialog::OCRParametersChanged(const OCRParameters ¶ms)
|
||||
|
|
@ -185,7 +186,7 @@ void PreviewDialog::UpdateImage(const QPixmap &image)
|
|||
DrawFrame();
|
||||
}
|
||||
emit NeedImage(_video, _type, _patternMatchParams, _patternImageData,
|
||||
_objDetectParams, _ocrParams, _areaParams, _condition);
|
||||
_cascadeParams, _ocrParams, _areaParams, _condition);
|
||||
}
|
||||
|
||||
void PreviewDialog::Start()
|
||||
|
|
@ -215,7 +216,7 @@ void PreviewDialog::Start()
|
|||
_thread.start();
|
||||
|
||||
emit NeedImage(_video, _type, _patternMatchParams, _patternImageData,
|
||||
_objDetectParams, _ocrParams, _areaParams, _condition);
|
||||
_cascadeParams, _ocrParams, _areaParams, _condition);
|
||||
}
|
||||
|
||||
void PreviewDialog::DrawFrame()
|
||||
|
|
@ -266,7 +267,7 @@ void PreviewImage::CreateImage(
|
|||
const VideoInput &video, PreviewType type,
|
||||
const PatternMatchParameters &patternMatchParams,
|
||||
const PatternImageData &patternImageData,
|
||||
std::shared_ptr<ObjDetectParameters> objDetectParams,
|
||||
std::shared_ptr<CascadeClassifierParameters> cascadeParams,
|
||||
std::shared_ptr<OCRParameters> ocrParams,
|
||||
const AreaParameters &areaParams, VideoCondition condition)
|
||||
{
|
||||
|
|
@ -299,7 +300,7 @@ void PreviewImage::CreateImage(
|
|||
std::unique_lock<std::mutex> lock(_mtx);
|
||||
// Will emit status label update
|
||||
MarkMatch(screenshot.GetImage(), patternMatchParams,
|
||||
patternImageData, objDetectParams, ocrParams,
|
||||
patternImageData, cascadeParams, ocrParams,
|
||||
condition);
|
||||
} else {
|
||||
emit StatusUpdate(obs_module_text(
|
||||
|
|
@ -311,14 +312,16 @@ void PreviewImage::CreateImage(
|
|||
void PreviewImage::MarkMatch(
|
||||
QImage &screenshot, const PatternMatchParameters &patternMatchParams,
|
||||
const PatternImageData &patternImageData,
|
||||
std::shared_ptr<ObjDetectParameters> objDetectParams,
|
||||
std::shared_ptr<CascadeClassifierParameters> cascadeParams,
|
||||
std::shared_ptr<OCRParameters> ocrParams, VideoCondition condition)
|
||||
{
|
||||
if (condition == VideoCondition::PATTERN) {
|
||||
MarkPatternMatch(screenshot, patternMatchParams,
|
||||
patternImageData);
|
||||
} else if (condition == VideoCondition::OBJECT) {
|
||||
MarkObjectMatch(screenshot, objDetectParams);
|
||||
} else if (condition == VideoCondition::OBJECT_CASCADE) {
|
||||
MarkObjectsFromDetector(
|
||||
screenshot,
|
||||
cascadeParams ? cascadeParams->GetDetector() : nullptr);
|
||||
} else if (condition == VideoCondition::OCR) {
|
||||
MarkOCRMatch(screenshot, ocrParams);
|
||||
}
|
||||
|
|
@ -347,26 +350,15 @@ void PreviewImage::MarkPatternMatch(
|
|||
}
|
||||
}
|
||||
|
||||
void PreviewImage::MarkObjectMatch(
|
||||
QImage &screenshot,
|
||||
const std::shared_ptr<ObjDetectParameters> &objDetectParams)
|
||||
void PreviewImage::MarkObjectsFromDetector(QImage &screenshot,
|
||||
ObjectDetector *detector)
|
||||
{
|
||||
if (!objDetectParams) {
|
||||
if (!detector) {
|
||||
emit StatusUpdate(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.objectMatchFail"));
|
||||
return;
|
||||
}
|
||||
auto model = objDetectParams->GetModel();
|
||||
if (!model) {
|
||||
emit StatusUpdate(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.objectMatchFail"));
|
||||
return;
|
||||
}
|
||||
auto objects = MatchObject(screenshot, *model,
|
||||
objDetectParams->scaleFactor,
|
||||
objDetectParams->minNeighbors,
|
||||
objDetectParams->minSize.CV(),
|
||||
objDetectParams->maxSize.CV());
|
||||
auto objects = detector->Detect(screenshot);
|
||||
if (objects.empty()) {
|
||||
emit StatusUpdate(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.objectMatchFail"));
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ public slots:
|
|||
void CreateImage(const VideoInput &, PreviewType,
|
||||
const PatternMatchParameters &,
|
||||
const PatternImageData &,
|
||||
std::shared_ptr<ObjDetectParameters>,
|
||||
std::shared_ptr<CascadeClassifierParameters>,
|
||||
std::shared_ptr<OCRParameters>, const AreaParameters &,
|
||||
VideoCondition);
|
||||
signals:
|
||||
|
|
@ -38,12 +38,11 @@ signals:
|
|||
private:
|
||||
void MarkMatch(QImage &screenshot, const PatternMatchParameters &,
|
||||
const PatternImageData &,
|
||||
std::shared_ptr<ObjDetectParameters>,
|
||||
std::shared_ptr<CascadeClassifierParameters>,
|
||||
std::shared_ptr<OCRParameters>, VideoCondition);
|
||||
void MarkPatternMatch(QImage &, const PatternMatchParameters &,
|
||||
const PatternImageData &);
|
||||
void MarkObjectMatch(QImage &,
|
||||
const std::shared_ptr<ObjDetectParameters> &);
|
||||
void MarkObjectsFromDetector(QImage &, ObjectDetector *);
|
||||
void MarkOCRMatch(QImage &, const std::shared_ptr<OCRParameters> &);
|
||||
|
||||
std::mutex &_mtx;
|
||||
|
|
@ -62,7 +61,8 @@ public:
|
|||
|
||||
public slots:
|
||||
void PatternMatchParametersChanged(const PatternMatchParameters &);
|
||||
void ObjDetectParametersChanged(const ObjDetectParameters &);
|
||||
void
|
||||
CascadeClassifierParametersChanged(const CascadeClassifierParameters &);
|
||||
void OCRParametersChanged(const OCRParameters &);
|
||||
void VideoSelectionChanged(const VideoInput &);
|
||||
void AreaParametersChanged(const AreaParameters &);
|
||||
|
|
@ -75,7 +75,7 @@ signals:
|
|||
void SelectionAreaChanged(QRect area);
|
||||
void NeedImage(const VideoInput &, PreviewType,
|
||||
const PatternMatchParameters &, const PatternImageData &,
|
||||
std::shared_ptr<ObjDetectParameters>,
|
||||
std::shared_ptr<CascadeClassifierParameters>,
|
||||
std::shared_ptr<OCRParameters>, const AreaParameters &,
|
||||
VideoCondition);
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ private:
|
|||
VideoInput _video;
|
||||
PatternMatchParameters _patternMatchParams;
|
||||
PatternImageData _patternImageData;
|
||||
std::shared_ptr<ObjDetectParameters> _objDetectParams;
|
||||
std::shared_ptr<CascadeClassifierParameters> _cascadeParams;
|
||||
std::shared_ptr<OCRParameters> _ocrParams;
|
||||
AreaParameters _areaParams;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
#pragma once
|
||||
#include "screenshot-helper.hpp"
|
||||
#include "parameter-wrappers.hpp"
|
||||
#include "screenshot-helper.hpp"
|
||||
|
||||
#include <optional>
|
||||
#include <obs.h>
|
||||
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QImage>
|
||||
|
|
@ -11,6 +11,8 @@
|
|||
#include <QRubberBand>
|
||||
#include <QScrollArea>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace advss {
|
||||
|
||||
class ScreenshotDialog : public QDialog {
|
||||
|
|
|
|||
89
plugins/video/video-edit-area.cpp
Normal file
89
plugins/video/video-edit-area.cpp
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
#include "macro-condition-video.hpp"
|
||||
#include "layout-helpers.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
|
||||
#include <QPushButton>
|
||||
|
||||
namespace advss {
|
||||
|
||||
AreaEdit::AreaEdit(QWidget *parent, PreviewDialog *previewDialog,
|
||||
const std::shared_ptr<MacroConditionVideo> &data)
|
||||
: QWidget(parent),
|
||||
_checkAreaEnable(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.layout.checkAreaEnable"))),
|
||||
_checkArea(new AreaSelection(0, 99999)),
|
||||
_selectArea(new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.selectArea"))),
|
||||
_previewDialog(previewDialog),
|
||||
_entryData(data)
|
||||
{
|
||||
QWidget::connect(_checkAreaEnable, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(CheckAreaEnableChanged(int)));
|
||||
QWidget::connect(_checkArea, SIGNAL(AreaChanged(Area)), this,
|
||||
SLOT(CheckAreaChanged(Area)));
|
||||
QWidget::connect(_selectArea, SIGNAL(clicked()), this,
|
||||
SLOT(SelectAreaClicked()));
|
||||
QWidget::connect(_previewDialog, SIGNAL(SelectionAreaChanged(QRect)),
|
||||
this, SLOT(CheckAreaChanged(QRect)));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{checkAreaEnable}}", _checkAreaEnable},
|
||||
{"{{checkArea}}", _checkArea},
|
||||
{"{{selectArea}}", _selectArea},
|
||||
};
|
||||
|
||||
auto layout = new QHBoxLayout;
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.layout.checkArea"),
|
||||
layout, widgetPlaceholders);
|
||||
setLayout(layout);
|
||||
|
||||
_checkAreaEnable->setChecked(_entryData->_areaParameters.enable);
|
||||
_checkArea->SetArea(_entryData->_areaParameters.area);
|
||||
SetWidgetVisibility();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void AreaEdit::SetWidgetVisibility()
|
||||
{
|
||||
_checkArea->setVisible(_entryData->_areaParameters.enable);
|
||||
_selectArea->setVisible(_entryData->_areaParameters.enable);
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void AreaEdit::SelectAreaClicked()
|
||||
{
|
||||
_previewDialog->show();
|
||||
_previewDialog->raise();
|
||||
_previewDialog->activateWindow();
|
||||
_previewDialog->SelectArea();
|
||||
}
|
||||
|
||||
void AreaEdit::CheckAreaEnableChanged(int value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_areaParameters.enable = value;
|
||||
SetWidgetVisibility();
|
||||
_previewDialog->AreaParametersChanged(_entryData->_areaParameters);
|
||||
emit Resized();
|
||||
}
|
||||
|
||||
void AreaEdit::CheckAreaChanged(Area value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_areaParameters.area = value;
|
||||
_previewDialog->AreaParametersChanged(_entryData->_areaParameters);
|
||||
}
|
||||
|
||||
void AreaEdit::CheckAreaChanged(QRect rect)
|
||||
{
|
||||
const QSignalBlocker b(_checkArea);
|
||||
Area area{rect.topLeft().x(), rect.y(), rect.width(), rect.height()};
|
||||
_checkArea->SetArea(area);
|
||||
CheckAreaChanged(area);
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
53
plugins/video/video-edit-brightness.cpp
Normal file
53
plugins/video/video-edit-brightness.cpp
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
#include "macro-condition-video.hpp"
|
||||
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace advss {
|
||||
|
||||
BrightnessEdit::BrightnessEdit(QWidget *parent,
|
||||
const std::shared_ptr<MacroConditionVideo> &data)
|
||||
: QWidget(parent),
|
||||
_threshold(new SliderSpinBox(
|
||||
0., 1.,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.brightnessThreshold"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.brightnessThresholdDescription"))),
|
||||
_current(new QLabel),
|
||||
_entryData(data)
|
||||
{
|
||||
auto layout = new QVBoxLayout;
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->addWidget(_threshold);
|
||||
layout->addWidget(_current);
|
||||
setLayout(layout);
|
||||
|
||||
QWidget::connect(
|
||||
_threshold,
|
||||
SIGNAL(DoubleValueChanged(const NumberVariable<double> &)),
|
||||
this,
|
||||
SLOT(BrightnessThresholdChanged(
|
||||
const NumberVariable<double> &)));
|
||||
QWidget::connect(&_timer, &QTimer::timeout, this,
|
||||
&BrightnessEdit::UpdateCurrentBrightness);
|
||||
_timer.start(1000);
|
||||
|
||||
_threshold->SetDoubleValue(_entryData->_brightnessThreshold);
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void BrightnessEdit::UpdateCurrentBrightness()
|
||||
{
|
||||
QString text = obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.currentBrightness");
|
||||
_current->setText(text.arg(_entryData->GetCurrentBrightness()));
|
||||
}
|
||||
|
||||
void BrightnessEdit::BrightnessThresholdChanged(const DoubleVariable &value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_brightnessThreshold = value;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
88
plugins/video/video-edit-color.cpp
Normal file
88
plugins/video/video-edit-color.cpp
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
#include "macro-condition-video.hpp"
|
||||
#include "layout-helpers.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace advss {
|
||||
|
||||
ColorEdit::ColorEdit(QWidget *parent,
|
||||
const std::shared_ptr<MacroConditionVideo> &data)
|
||||
: QWidget(parent),
|
||||
_matchThreshold(new SliderSpinBox(
|
||||
0., 1.,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.colorMatchThreshold"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.colorMatchThresholdDescription"),
|
||||
true)),
|
||||
_colorThreshold(new SliderSpinBox(
|
||||
0., 1.,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.colorDeviationThreshold"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.colorDeviationThresholdDescription"),
|
||||
true)),
|
||||
_colorButton(new VariableColorButton(
|
||||
this,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.selectColor"))),
|
||||
_entryData(data)
|
||||
{
|
||||
QWidget::connect(_colorButton,
|
||||
SIGNAL(ColorVariableChanged(const ColorVariable &)),
|
||||
this, SLOT(ColorChanged(const ColorVariable &)));
|
||||
QWidget::connect(
|
||||
_matchThreshold,
|
||||
SIGNAL(DoubleValueChanged(const NumberVariable<double> &)),
|
||||
this,
|
||||
SLOT(MatchThresholdChanged(const NumberVariable<double> &)));
|
||||
QWidget::connect(
|
||||
_colorThreshold,
|
||||
SIGNAL(DoubleValueChanged(const NumberVariable<double> &)),
|
||||
this,
|
||||
SLOT(ColorThresholdChanged(const NumberVariable<double> &)));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{color}}", _colorButton},
|
||||
};
|
||||
|
||||
auto colorLayout = new QHBoxLayout;
|
||||
PlaceWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.layout.color"),
|
||||
colorLayout, widgetPlaceholders);
|
||||
|
||||
auto layout = new QVBoxLayout;
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->addLayout(colorLayout);
|
||||
layout->addWidget(_colorThreshold);
|
||||
layout->addWidget(_matchThreshold);
|
||||
setLayout(layout);
|
||||
|
||||
_matchThreshold->SetDoubleValue(
|
||||
_entryData->_colorParameters.matchThreshold);
|
||||
_colorThreshold->SetDoubleValue(
|
||||
_entryData->_colorParameters.colorThreshold);
|
||||
_colorButton->SetValue(_entryData->_colorParameters.color);
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void ColorEdit::ColorChanged(const ColorVariable &value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_colorParameters.color = value;
|
||||
}
|
||||
|
||||
void ColorEdit::MatchThresholdChanged(const DoubleVariable &value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_colorParameters.matchThreshold = value;
|
||||
}
|
||||
|
||||
void ColorEdit::ColorThresholdChanged(const DoubleVariable &value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_colorParameters.colorThreshold = value;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
157
plugins/video/video-edit-object.cpp
Normal file
157
plugins/video/video-edit-object.cpp
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
#include "macro-condition-video.hpp"
|
||||
#include "layout-helpers.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
#include "ui-helpers.hpp"
|
||||
|
||||
#include <QSpinBox>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace advss {
|
||||
|
||||
CascadeClassifierEdit::CascadeClassifierEdit(
|
||||
QWidget *parent, PreviewDialog *previewDialog,
|
||||
const std::shared_ptr<MacroConditionVideo> &data)
|
||||
: QWidget(parent),
|
||||
_modelDataPath(new FileSelection()),
|
||||
_objectScaleThreshold(new SliderSpinBox(
|
||||
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)),
|
||||
_previewDialog(previewDialog),
|
||||
_entryData(data)
|
||||
{
|
||||
_minNeighbors->setMinimum(minMinNeighbors);
|
||||
_minNeighbors->setMaximum(maxMinNeighbors);
|
||||
|
||||
QWidget::connect(
|
||||
_objectScaleThreshold,
|
||||
SIGNAL(DoubleValueChanged(const NumberVariable<double> &)),
|
||||
this,
|
||||
SLOT(ObjectScaleThresholdChanged(
|
||||
const NumberVariable<double> &)));
|
||||
QWidget::connect(_minNeighbors, SIGNAL(valueChanged(int)), this,
|
||||
SLOT(MinNeighborsChanged(int)));
|
||||
QWidget::connect(_minSize, SIGNAL(SizeChanged(Size)), this,
|
||||
SLOT(MinSizeChanged(Size)));
|
||||
QWidget::connect(_maxSize, SIGNAL(SizeChanged(Size)), this,
|
||||
SLOT(MaxSizeChanged(Size)));
|
||||
QWidget::connect(_modelDataPath, SIGNAL(PathChanged(const QString &)),
|
||||
this, SLOT(ModelPathChanged(const QString &)));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{minNeighbors}}", _minNeighbors},
|
||||
{"{{minSize}}", _minSize},
|
||||
{"{{maxSize}}", _maxSize},
|
||||
{"{{modelDataPath}}", _modelDataPath},
|
||||
};
|
||||
|
||||
auto pathLayout = new QHBoxLayout;
|
||||
pathLayout->setContentsMargins(0, 0, 0, 0);
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.layout.modelPath"),
|
||||
pathLayout, widgetPlaceholders);
|
||||
|
||||
auto neighborsLayout = new QHBoxLayout;
|
||||
neighborsLayout->setContentsMargins(0, 0, 0, 0);
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.layout.minNeighbor"),
|
||||
neighborsLayout, widgetPlaceholders);
|
||||
|
||||
auto 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);
|
||||
auto sizeLayout = new QHBoxLayout;
|
||||
sizeLayout->setContentsMargins(0, 0, 0, 0);
|
||||
sizeLayout->addLayout(sizeGrid);
|
||||
sizeLayout->addStretch();
|
||||
|
||||
auto layout = new QVBoxLayout();
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->addLayout(pathLayout);
|
||||
layout->addLayout(neighborsLayout);
|
||||
layout->addLayout(sizeLayout);
|
||||
setLayout(layout);
|
||||
|
||||
_modelDataPath->SetPath(
|
||||
_entryData->_cascadeMatchParameters.GetModelPath());
|
||||
_objectScaleThreshold->SetDoubleValue(
|
||||
_entryData->_cascadeMatchParameters.scaleFactor);
|
||||
_minNeighbors->setValue(
|
||||
_entryData->_cascadeMatchParameters.minNeighbors);
|
||||
_minSize->SetSize(_entryData->_cascadeMatchParameters.minSize);
|
||||
_maxSize->SetSize(_entryData->_cascadeMatchParameters.maxSize);
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void CascadeClassifierEdit::ObjectScaleThresholdChanged(
|
||||
const DoubleVariable &value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_cascadeMatchParameters.scaleFactor = value;
|
||||
_previewDialog->CascadeClassifierParametersChanged(
|
||||
_entryData->_cascadeMatchParameters);
|
||||
}
|
||||
|
||||
void CascadeClassifierEdit::MinNeighborsChanged(int value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_cascadeMatchParameters.minNeighbors = value;
|
||||
_previewDialog->CascadeClassifierParametersChanged(
|
||||
_entryData->_cascadeMatchParameters);
|
||||
}
|
||||
|
||||
void CascadeClassifierEdit::MinSizeChanged(advss::Size value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_cascadeMatchParameters.minSize = value;
|
||||
_previewDialog->CascadeClassifierParametersChanged(
|
||||
_entryData->_cascadeMatchParameters);
|
||||
}
|
||||
|
||||
void CascadeClassifierEdit::MaxSizeChanged(advss::Size value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_cascadeMatchParameters.maxSize = value;
|
||||
_previewDialog->CascadeClassifierParametersChanged(
|
||||
_entryData->_cascadeMatchParameters);
|
||||
}
|
||||
|
||||
void CascadeClassifierEdit::ModelPathChanged(const QString &text)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool dataLoaded = false;
|
||||
{
|
||||
auto lock = LockContext();
|
||||
std::string path = text.toStdString();
|
||||
dataLoaded =
|
||||
_entryData->_cascadeMatchParameters.SetModelPath(path);
|
||||
}
|
||||
if (!dataLoaded) {
|
||||
DisplayMessage(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.modelLoadFail"));
|
||||
}
|
||||
_previewDialog->CascadeClassifierParametersChanged(
|
||||
_entryData->_cascadeMatchParameters);
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
320
plugins/video/video-edit-ocr.cpp
Normal file
320
plugins/video/video-edit-ocr.cpp
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
#include "macro-condition-video.hpp"
|
||||
#include "layout-helpers.hpp"
|
||||
#include "plugin-state-helpers.hpp"
|
||||
#include "ui-helpers.hpp"
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QUrl>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace advss {
|
||||
|
||||
const static std::map<tesseract::PageSegMode, std::string> pageSegModes = {
|
||||
{tesseract::PageSegMode::PSM_SINGLE_COLUMN,
|
||||
"AdvSceneSwitcher.condition.video.ocrMode.singleColumn"},
|
||||
{tesseract::PageSegMode::PSM_SINGLE_BLOCK_VERT_TEXT,
|
||||
"AdvSceneSwitcher.condition.video.ocrMode.singleBlockVertText"},
|
||||
{tesseract::PageSegMode::PSM_SINGLE_BLOCK,
|
||||
"AdvSceneSwitcher.condition.video.ocrMode.singleBlock"},
|
||||
{tesseract::PageSegMode::PSM_SINGLE_LINE,
|
||||
"AdvSceneSwitcher.condition.video.ocrMode.singleLine"},
|
||||
{tesseract::PageSegMode::PSM_SINGLE_WORD,
|
||||
"AdvSceneSwitcher.condition.video.ocrMode.singleWord"},
|
||||
{tesseract::PageSegMode::PSM_CIRCLE_WORD,
|
||||
"AdvSceneSwitcher.condition.video.ocrMode.circleWord"},
|
||||
{tesseract::PageSegMode::PSM_SINGLE_CHAR,
|
||||
"AdvSceneSwitcher.condition.video.ocrMode.singleChar"},
|
||||
{tesseract::PageSegMode::PSM_SPARSE_TEXT,
|
||||
"AdvSceneSwitcher.condition.video.ocrMode.sparseText"},
|
||||
{tesseract::PageSegMode::PSM_SPARSE_TEXT_OSD,
|
||||
"AdvSceneSwitcher.condition.video.ocrMode.sparseTextOSD"},
|
||||
};
|
||||
|
||||
static inline void populatePageSegModeSelection(QComboBox *list)
|
||||
{
|
||||
for (const auto &[mode, name] : pageSegModes) {
|
||||
list->addItem(obs_module_text(name.c_str()),
|
||||
static_cast<int>(mode));
|
||||
}
|
||||
}
|
||||
|
||||
static void openFileInEditor(const std::string &filepath)
|
||||
{
|
||||
const auto path = QString::fromStdString(filepath);
|
||||
const QFileInfo fileInfo(path);
|
||||
if (!fileInfo.exists()) {
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
DisplayMessage(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.ocrOpenConfig.createFailed"));
|
||||
return;
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
QUrl fileUrl = QUrl::fromLocalFile(path);
|
||||
if (!QDesktopServices::openUrl(fileUrl)) {
|
||||
DisplayMessage(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.ocrOpenConfig.openFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
OCREdit::OCREdit(QWidget *parent, PreviewDialog *previewDialog,
|
||||
const std::shared_ptr<MacroConditionVideo> &data)
|
||||
: QWidget(parent),
|
||||
_matchText(new VariableTextEdit(this)),
|
||||
_regex(new RegexConfigWidget(this)),
|
||||
_colorButton(new VariableColorButton(
|
||||
this,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.selectColor"))),
|
||||
_colorThreshold(new SliderSpinBox(
|
||||
0., 1.,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.colorDeviationThreshold"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.colorDeviationThresholdDescription"),
|
||||
true)),
|
||||
_pageSegMode(new QComboBox()),
|
||||
_tesseractBaseDir(new FileSelection(FileSelection::Type::FOLDER)),
|
||||
_languageCode(new VariableLineEdit(this)),
|
||||
_useConfig(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.ocrUseConfigFile"))),
|
||||
_configFile(new FileSelection(FileSelection::Type::WRITE, this)),
|
||||
_openConfigFile(new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.ocrOpenConfigFile"))),
|
||||
_reloadConfig(new QPushButton()),
|
||||
_configLayout(new QHBoxLayout()),
|
||||
_previewDialog(previewDialog),
|
||||
_entryData(data)
|
||||
{
|
||||
populatePageSegModeSelection(_pageSegMode);
|
||||
|
||||
_reloadConfig->setMaximumWidth(22);
|
||||
SetButtonIcon(_reloadConfig, GetThemeTypeName() == "Light"
|
||||
? ":res/images/refresh.svg"
|
||||
: "theme:Dark/refresh.svg");
|
||||
_reloadConfig->setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.ocrConfigReload"));
|
||||
|
||||
QWidget::connect(_colorButton,
|
||||
SIGNAL(ColorVariableChanged(const ColorVariable &)),
|
||||
this, SLOT(ColorChanged(const ColorVariable &)));
|
||||
QWidget::connect(
|
||||
_colorThreshold,
|
||||
SIGNAL(DoubleValueChanged(const NumberVariable<double> &)),
|
||||
this,
|
||||
SLOT(ColorThresholdChanged(const NumberVariable<double> &)));
|
||||
QWidget::connect(_matchText, SIGNAL(textChanged()), this,
|
||||
SLOT(MatchTextChanged()));
|
||||
QWidget::connect(_regex,
|
||||
SIGNAL(RegexConfigChanged(const RegexConfig &)), this,
|
||||
SLOT(RegexChanged(const RegexConfig &)));
|
||||
QWidget::connect(_pageSegMode, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(PageSegModeChanged(int)));
|
||||
QWidget::connect(_tesseractBaseDir,
|
||||
SIGNAL(PathChanged(const QString &)), this,
|
||||
SLOT(TesseractBaseDirChanged(const QString &)));
|
||||
QWidget::connect(_languageCode, SIGNAL(editingFinished()), this,
|
||||
SLOT(LanguageChanged()));
|
||||
QWidget::connect(_useConfig, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(UseConfigChanged(int)));
|
||||
QWidget::connect(_configFile, SIGNAL(PathChanged(const QString &)),
|
||||
this, SLOT(ConfigFileChanged(const QString &)));
|
||||
QWidget::connect(_openConfigFile, &QPushButton::clicked, [this](bool) {
|
||||
openFileInEditor(
|
||||
_entryData->_ocrParameters.GetCustomConfigFile());
|
||||
});
|
||||
QWidget::connect(_reloadConfig, &QPushButton::clicked, [this](bool) {
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_ocrParameters.EnableCustomConfig(true);
|
||||
_previewDialog->OCRParametersChanged(
|
||||
_entryData->_ocrParameters);
|
||||
});
|
||||
|
||||
auto configFileHint = new QLabel();
|
||||
const QString path = GetThemeTypeName() == "Light"
|
||||
? ":/res/images/help.svg"
|
||||
: ":/res/images/help_light.svg";
|
||||
const QIcon icon(path);
|
||||
const QPixmap pixmap = icon.pixmap(QSize(16, 16));
|
||||
configFileHint->setPixmap(pixmap);
|
||||
configFileHint->setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.ocrConfigHint"));
|
||||
|
||||
const std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{color}}", _colorButton},
|
||||
{"{{textType}}", _pageSegMode},
|
||||
{"{{tesseractBaseDir}}", _tesseractBaseDir},
|
||||
{"{{languageCode}}", _languageCode},
|
||||
{"{{configFile}}", _configFile},
|
||||
{"{{openConfigFile}}", _openConfigFile},
|
||||
{"{{reloadConfig}}", _reloadConfig},
|
||||
{"{{configFileHint}}", configFileHint},
|
||||
};
|
||||
|
||||
auto layout = new QVBoxLayout();
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
auto textLayout = new QHBoxLayout();
|
||||
textLayout->setContentsMargins(0, 0, 0, 0);
|
||||
textLayout->addWidget(_matchText);
|
||||
textLayout->addWidget(_regex);
|
||||
layout->addLayout(textLayout);
|
||||
auto pageModeSegLayout = new QHBoxLayout();
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.layout.ocrTextType"),
|
||||
pageModeSegLayout, widgetPlaceholders);
|
||||
layout->addLayout(pageModeSegLayout);
|
||||
auto baseDirLayout = new QHBoxLayout();
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.layout.ocrBaseDir"),
|
||||
baseDirLayout, widgetPlaceholders, false);
|
||||
layout->addLayout(baseDirLayout);
|
||||
auto languageLayout = new QHBoxLayout();
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.layout.ocrLanguage"),
|
||||
languageLayout, widgetPlaceholders);
|
||||
layout->addLayout(languageLayout);
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.layout.ocrConfig"),
|
||||
_configLayout, widgetPlaceholders, false);
|
||||
layout->addWidget(_useConfig);
|
||||
layout->addLayout(_configLayout);
|
||||
auto colorPickLayout = new QHBoxLayout();
|
||||
PlaceWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.layout.ocrColorPick"),
|
||||
colorPickLayout, widgetPlaceholders);
|
||||
layout->addLayout(colorPickLayout);
|
||||
layout->addWidget(_colorThreshold);
|
||||
setLayout(layout);
|
||||
|
||||
_matchText->setPlainText(_entryData->_ocrParameters.text);
|
||||
_regex->SetRegexConfig(_entryData->_ocrParameters.regex);
|
||||
_colorButton->SetValue(_entryData->_ocrParameters.color);
|
||||
_colorThreshold->SetDoubleValue(
|
||||
_entryData->_ocrParameters.colorThreshold);
|
||||
_pageSegMode->setCurrentIndex(_pageSegMode->findData(
|
||||
static_cast<int>(_entryData->_ocrParameters.GetPageMode())));
|
||||
_tesseractBaseDir->SetPath(
|
||||
_entryData->_ocrParameters.GetTesseractBasePath());
|
||||
_languageCode->setText(_entryData->_ocrParameters.GetLanguageCode());
|
||||
_useConfig->setChecked(
|
||||
_entryData->_ocrParameters.CustomConfigIsEnabled());
|
||||
_configFile->SetPath(_entryData->_ocrParameters.GetCustomConfigFile());
|
||||
SetLayoutVisible(_configLayout,
|
||||
_entryData->_ocrParameters.CustomConfigIsEnabled());
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void OCREdit::ColorChanged(const ColorVariable &value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_ocrParameters.color = value;
|
||||
|
||||
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
|
||||
}
|
||||
|
||||
void OCREdit::ColorThresholdChanged(const DoubleVariable &value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_ocrParameters.colorThreshold = value;
|
||||
|
||||
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
|
||||
}
|
||||
|
||||
void OCREdit::MatchTextChanged()
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_ocrParameters.text =
|
||||
_matchText->toPlainText().toUtf8().constData();
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
|
||||
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
|
||||
}
|
||||
|
||||
void OCREdit::RegexChanged(const RegexConfig &conf)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_ocrParameters.regex = conf;
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
|
||||
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
|
||||
}
|
||||
|
||||
void OCREdit::PageSegModeChanged(int idx)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->SetPageSegMode(static_cast<tesseract::PageSegMode>(
|
||||
_pageSegMode->itemData(idx).toInt()));
|
||||
|
||||
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
|
||||
}
|
||||
|
||||
void OCREdit::TesseractBaseDirChanged(const QString &path)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
if (!_entryData->SetTesseractBaseDir(path.toStdString())) {
|
||||
const QString message(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.ocrLanguageNotFound"));
|
||||
const QDir dataDir(path);
|
||||
const QString fileName(_languageCode->text() + ".traineddata");
|
||||
DisplayMessage(message.arg(fileName, dataDir.absolutePath()));
|
||||
|
||||
// Reset to previous value
|
||||
const QSignalBlocker b(this);
|
||||
_tesseractBaseDir->SetPath(
|
||||
_entryData->_ocrParameters.GetTesseractBasePath());
|
||||
return;
|
||||
}
|
||||
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
|
||||
}
|
||||
|
||||
void OCREdit::LanguageChanged()
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
if (!_entryData->SetLanguageCode(_languageCode->text().toStdString())) {
|
||||
const QString message(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.ocrLanguageNotFound"));
|
||||
const QDir dataDir(QString::fromStdString(
|
||||
_entryData->_ocrParameters.GetTesseractBasePath()));
|
||||
const QString fileName(_languageCode->text() + ".traineddata");
|
||||
DisplayMessage(message.arg(fileName, dataDir.absolutePath()));
|
||||
|
||||
// Reset to previous value
|
||||
const QSignalBlocker b(this);
|
||||
_languageCode->setText(
|
||||
_entryData->_ocrParameters.GetLanguageCode());
|
||||
return;
|
||||
}
|
||||
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
|
||||
}
|
||||
|
||||
void OCREdit::UseConfigChanged(int value)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_ocrParameters.EnableCustomConfig(value);
|
||||
SetLayoutVisible(_configLayout, value);
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
|
||||
}
|
||||
|
||||
void OCREdit::ConfigFileChanged(const QString &path)
|
||||
{
|
||||
GUARD_LOADING_AND_LOCK();
|
||||
_entryData->_ocrParameters.SetCustomConfigFile(path.toStdString());
|
||||
_previewDialog->OCRParametersChanged(_entryData->_ocrParameters);
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
Loading…
Reference in New Issue
Block a user