Compare commits

...

18 Commits
1.20.5 ... osx

Author SHA1 Message Date
WarmUpTill
b74e9d2e2a Enable executing AppleScripts on MacOS 2023-03-04 18:14:16 +01:00
WarmUpTill
7cb95cb3f6 CI: Cache dependency builds for macOS and Windows 2023-02-19 09:48:03 -08:00
WarmUpTill
160d19d6c1 Clean up UI 2023-02-19 09:48:03 -08:00
WarmUpTill
a0550ca141 Add support for TM_SQDIFF_NORMED and TM_CCOEFF_NORMED for pattern match 2023-02-19 09:48:03 -08:00
WarmUpTill
ac68fc016d Adjust area selection layout
Prefix description instead of using tooltip and remove the "x"
2023-02-19 09:48:03 -08:00
WarmUpTill
5170307f72 Add leptonica and tesseract build 2023-02-19 09:48:03 -08:00
WarmUpTill
3af0e51bd2 Add submodules leptonica and tesseract (OCR) 2023-02-19 09:48:03 -08:00
WarmUpTill
f34d1fe081 Enable variable support 2023-02-19 09:48:03 -08:00
WarmUpTill
83c72ebba5 Add OCR support to video condition using tesseract 2023-02-19 09:48:03 -08:00
WarmUpTill
6ca8e6b3ea Fix preview not showing matches
This can happen if a new video condition was created and the condition
type was cahnged to one which supports displaying matches without
changing any parameters.
2023-02-19 09:48:03 -08:00
WarmUpTill
ba24fc2fab Use correct conversion method for object detection 2023-02-19 09:48:03 -08:00
WarmUpTill
7f28b22599 Add macro list context menu options for add, rename, remove 2023-02-19 09:47:46 -08:00
WarmUpTill
4345db0d2e Don't ask for confirmation when deleting empty groups 2023-02-19 09:47:46 -08:00
WarmUpTill
eddc10c3e2 Enable deleting multiple macros at once 2023-02-19 09:47:46 -08:00
WarmUpTill
46eaada0c4 Enable dragging macro segment by header 2023-02-19 09:47:46 -08:00
WarmUpTill
f8b2dea270 Clean up MacroSegmentList 2023-02-19 09:47:46 -08:00
WarmUpTill
90ebf0e0f8 Set default logic type of new non-root conditions to "or" 2023-02-19 09:47:46 -08:00
WarmUpTill
1a819c807e Create new groups at the top most selection index 2023-02-19 09:47:46 -08:00
35 changed files with 1223 additions and 328 deletions

View File

@@ -173,23 +173,29 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
read -r product_name product_version <<< \
"$(jq -r '. | {name, version} | join(" ")' ${buildspec_file})"
local opencv_dir="${project_root}/deps/opencv"
local opencv_build_dir="${opencv_dir}/build_${target##*-}"
case ${host_os} {
macos)
sed -i '' \
"s/project(\(.*\) VERSION \(.*\))/project(${product_name} VERSION ${product_version})/" \
"${project_root}/CMakeLists.txt"
# Note: IPP must be set to OFF to avoid build failures on ARM architectures
local opencv_dir="${project_root}/deps/opencv"
local opencv_build_dir="${opencv_dir}/build_${target##*-}"
local -a opencv_cmake_args=(
-DCMAKE_BUILD_TYPE=Release
-DBUILD_LIST=core,imgproc,objdetect
-DCMAKE_OSX_ARCHITECTURES=${${target##*-}//universal/x86_64;arm64}
-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
-DWITH_IPP=OFF
)
if [ "${target}" != "macos-x86_64" ]; then
opencv_cmake_args+=(-DWITH_IPP=OFF)
fi
pushd ${opencv_dir}
log_info "Configure OpenCV ..."
cmake -S . -B ${opencv_build_dir} -G ${generator} ${opencv_cmake_args}
@@ -198,9 +204,73 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
cmake --build ${opencv_build_dir} --config Release
log_info "Installing OpenCV..."
cmake --install ${opencv_build_dir} --config Release --prefix ${opencv_build_dir}
cmake --install ${opencv_build_dir} --config Release || true
popd
local leptonica_dir="${project_root}/deps/leptonica"
local leptonica_build_dir="${leptonica_dir}/build_${target##*-}"
local -a leptonica_cmake_args=(
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_OSX_ARCHITECTURES=${${target##*-}//universal/x86_64;arm64}
-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
-DSW_BUILD=OFF
-DOPENJPEG_SUPPORT=OFF
-DLIBWEBP_SUPPORT=OFF
-DCMAKE_DISABLE_FIND_PACKAGE_GIF=TRUE
-DCMAKE_DISABLE_FIND_PACKAGE_JPEG=TRUE
-DCMAKE_DISABLE_FIND_PACKAGE_TIFF=TRUE
-DCMAKE_DISABLE_FIND_PACKAGE_PNG=TRUE
)
pushd ${leptonica_dir}
log_info "Configure Leptonica ..."
cmake -S . -B ${leptonica_build_dir} -G ${generator} ${leptonica_cmake_args}
log_info "Building Leptonica ..."
cmake --build ${leptonica_build_dir} --config Release
log_info "Installing Leptonica..."
# Workaround for "unknown file attribute: H" errors when running install
cmake --install ${leptonica_build_dir} --config Release || :
popd
local tesseract_dir="${project_root}/deps/tesseract"
local tesseract_build_dir="${tesseract_dir}/build_${target##*-}"
local -a tesseract_cmake_args=(
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_OSX_ARCHITECTURES=${${target##*-}//universal/x86_64;arm64}
-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
-DSW_BUILD=OFF
-DBUILD_TRAINING_TOOLS=OFF
)
if [ "${target}" != "macos-x86_64" ]; then
tesseract_cmake_args+=(
-DCMAKE_SYSTEM_PROCESSOR=aarch64
-DHAVE_AVX=FALSE
-DHAVE_AVX2=FALSE
-DHAVE_AVX512F=FALSE
-DHAVE_FMA=FALSE
-DHAVE_SSE4_1=FALSE
-DHAVE_NEON=TRUE
)
sed -i'.original' 's/HAVE_NEON FALSE/HAVE_NEON TRUE/g' "${tesseract_dir}/CMakeLists.txt"
fi
pushd ${tesseract_dir}
log_info "Configure Tesseract ..."
cmake -S . -B ${tesseract_build_dir} -G ${generator} ${tesseract_cmake_args}
log_info "Building Tesseract ..."
#cmake --build ${tesseract_build_dir} --config Release --target libtesseract
cmake --build ${tesseract_build_dir} --config Release
log_info "Installing Tesseract..."
#cmake --install ${tesseract_build_dir} --config Release --component libtesseract
cmake --install ${tesseract_build_dir} --config Release
popd
;;
linux)
sed -i'' \
@@ -242,7 +312,6 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOYMENT_TARGET:-10.15}
-DOBS_CODESIGN_LINKER=ON
-DOBS_BUNDLE_CODESIGN_IDENTITY="${CODESIGN_IDENT:--}"
-DOpenCV_DIR="${opencv_build_dir}"
)
;;

View File

@@ -37,7 +37,7 @@ function Build {
$UtilityFunctions = Get-ChildItem -Path $PSScriptRoot/utils.pwsh/*.ps1 -Recurse
foreach($Utility in $UtilityFunctions) {
foreach ($Utility in $UtilityFunctions) {
Write-Debug "Loading $($Utility.FullName)"
. $Utility.FullName
}
@@ -57,18 +57,22 @@ function Build {
$CmakeGenerator = $script:VisualStudioVersion
}
$DepsPath = "plugin-deps-${script:DepsVersion}-qt${script:QtVersion}-${script:Target}"
$DepInstallPath = "$(Resolve-Path -Path ${ProjectRoot}/../obs-build-dependencies/${DepsPath})"
$OpenCVPath = "${ProjectRoot}/deps/opencv"
$OpenCVBuildPath = "${OpenCVPath}/build"
Push-Location -Stack BuildOpenCVTemp
if ( ! ( ( $SkipAll ) -or ( $SkipBuild ) ) ) {
Ensure-Location $ProjectRoot
$OpenCVPath = "${ProjectRoot}/deps/opencv"
$OpenCVBuildPath = "${OpenCVPath}/build"
$OpenCVCmakeArgs = @(
'-G', $CmakeGenerator
"-DCMAKE_SYSTEM_VERSION=${script:PlatformSDK}"
"-DCMAKE_GENERATOR_PLATFORM=$(if (${script:Target} -eq "x86") { "Win32" } else { "x64" })"
"-DCMAKE_BUILD_TYPE=Release"
"-DCMAKE_PREFIX_PATH:PATH=$(Resolve-Path -Path ${ProjectRoot}/../obs-build-dependencies/${DepsPath})"
"-DCMAKE_PREFIX_PATH:PATH=${DepInstallPath}"
"-DBUILD_LIST=core,imgproc,objdetect"
)
@@ -80,18 +84,92 @@ function Build {
)
if ( $VerbosePreference -eq 'Continue' ) {
$OpenCVCmakeArgs+=('--verbose')
$OpenCVCmakeArgs += ('--verbose')
}
Log-Information "Building OpenCV..."
Invoke-External cmake --build "${OpenCVBuildPath}" @OpenCVCmakeArgs
}
Log-Information "Install OpenCV}..."
Invoke-External cmake --install "${OpenCVBuildPath}" --prefix "${OpenCVBuildPath}" @OpenCVCmakeArgs
Invoke-External cmake --install "${OpenCVBuildPath}" --prefix "${DepInstallPath}" @OpenCVCmakeArgs
$LeptonicaPath = "${ProjectRoot}/deps/leptonica"
$LeptonicaBuildPath = "${LeptonicaPath}/build"
Push-Location -Stack BuildLeptonicaTemp
if ( ! ( ( $SkipAll ) -or ( $SkipBuild ) ) ) {
Ensure-Location $ProjectRoot
$LeptonicaCmakeArgs = @(
'-G', $CmakeGenerator
"-DCMAKE_SYSTEM_VERSION=${script:PlatformSDK}"
"-DCMAKE_GENERATOR_PLATFORM=$(if (${script:Target} -eq "x86") { "Win32" } else { "x64" })"
"-DCMAKE_BUILD_TYPE=Release"
"-DCMAKE_PREFIX_PATH:PATH=${DepInstallPath}"
"-DCMAKE_INSTALL_PREFIX:PATH=${DepInstallPath}"
"-DSW_BUILD=OFF"
"-DLIBWEBP_SUPPORT=OFF"
)
Log-Information "Configuring leptonica..."
Invoke-External cmake -S ${LeptonicaPath} -B ${LeptonicaBuildPath} @LeptonicaCmakeArgs
$LeptonicaCmakeArgs = @(
'--config', "Release"
)
if ( $VerbosePreference -eq 'Continue' ) {
$LeptonicaCmakeArgs += ('--verbose')
}
Log-Information "Building leptonica..."
Invoke-External cmake --build "${LeptonicaBuildPath}" @LeptonicaCmakeArgs
}
Log-Information "Install leptonica..."
Invoke-External cmake --install "${LeptonicaBuildPath}" --prefix "${DepInstallPath}" @LeptonicaCmakeArgs
Push-Location -Stack BuildTesseractTemp
if ( ! ( ( $SkipAll ) -or ( $SkipBuild ) ) ) {
Ensure-Location $ProjectRoot
$TesseractPath = "${ProjectRoot}/deps/tesseract"
$TesseractBuildPath = "${TesseractPath}/build"
# Explicitly disable PkgConfig and tiff as it will lead build errors
$TesseractCmakeArgs = @(
'-G', $CmakeGenerator
"-DCMAKE_SYSTEM_VERSION=${script:PlatformSDK}"
"-DCMAKE_GENERATOR_PLATFORM=$(if (${script:Target} -eq "x86") { "Win32" } else { "x64" })"
"-DCMAKE_BUILD_TYPE=Release"
"-DCMAKE_PREFIX_PATH:PATH=${DepInstallPath}"
"-DCMAKE_INSTALL_PREFIX:PATH=${DepInstallPath}"
"-DSW_BUILD=OFF"
"-DDISABLE_CURL=ON"
"-DBUILD_TRAINING_TOOLS=OFF"
"-DCMAKE_DISABLE_FIND_PACKAGE_TIFF=TRUE"
"-DCMAKE_DISABLE_FIND_PACKAGE_PkgConfig=TRUE"
)
Log-Information "Configuring tesseract..."
Invoke-External cmake -S ${TesseractPath} -B ${TesseractBuildPath} @TesseractCmakeArgs
$TesseractCmakeArgs = @(
'--config', "Release"
)
if ( $VerbosePreference -eq 'Continue' ) {
$TesseractCmakeArgs += ('--verbose')
}
Log-Information "Building tesseract..."
Invoke-External cmake --build "${TesseractBuildPath}" @TesseractCmakeArgs
}
Log-Information "Install tesseract..."
Invoke-External cmake --install "${TesseractBuildPath}" --prefix "${DepInstallPath}" @TesseractCmakeArgs
(Get-Content -Path ${ProjectRoot}/CMakeLists.txt -Raw) `
-replace "project\((.*) VERSION (.*)\)", "project(${ProductName} VERSION ${ProductVersion})" `
| Out-File -Path ${ProjectRoot}/CMakeLists.txt
| Out-File -Path ${ProjectRoot}/CMakeLists.txt
Setup-Obs
@@ -99,18 +177,13 @@ function Build {
if ( ! ( ( $SkipAll ) -or ( $SkipBuild ) ) ) {
Ensure-Location $ProjectRoot
$OpenCVPath = "${ProjectRoot}/deps/opencv"
$OpenCVBuildPath = "${OpenCVPath}/build"
$DepsPath = "plugin-deps-${script:DepsVersion}-qt${script:QtVersion}-${script:Target}"
$CmakeArgs = @(
'-G', $CmakeGenerator
"-DCMAKE_SYSTEM_VERSION=${script:PlatformSDK}"
"-DCMAKE_GENERATOR_PLATFORM=$(if (${script:Target} -eq "x86") { "Win32" } else { "x64" })"
"-DCMAKE_BUILD_TYPE=${Configuration}"
"-DCMAKE_PREFIX_PATH:PATH=$(Resolve-Path -Path "${ProjectRoot}/../obs-build-dependencies/${DepsPath}")"
"-DCMAKE_PREFIX_PATH:PATH=${DepInstallPath}"
"-DQT_VERSION=${script:QtVersion}"
"-DOpenCV_DIR=${OpenCVBuildPath}"
)
Log-Debug "Attempting to configure OBS with CMake arguments: $($CmakeArgs | Out-String)"
@@ -122,7 +195,7 @@ function Build {
)
if ( $VerbosePreference -eq 'Continue' ) {
$CmakeArgs+=('--verbose')
$CmakeArgs += ('--verbose')
}
Log-Information "Building ${ProductName}..."

View File

@@ -38,6 +38,7 @@ if (( ! (${skips[(Ie)all]} + ${skips[(Ie)deps]}) )) {
libxtst-dev \
libxss-dev \
libopencv-dev \
libtesseract-dev \
libprocps-dev
local -a _qt_packages=()

View File

@@ -113,7 +113,11 @@ jobs:
id: ccache-cache
uses: actions/cache@v3
with:
path: ${{ github.workspace }}/.ccache
path: |
${{ github.workspace }}/.ccache
${{ github.workspace }}/plugin/deps/opencv/build_*
${{ github.workspace }}/plugin/deps/leptonica/build_*
${{ github.workspace }}/plugin/deps/tesseract/build*
key: macos-${{ matrix.arch }}-ccache-plugin-${{ steps.setup.outputs.ccacheDate }}
restore-keys: |
macos-${{ matrix.arch }}-ccache-plugin-
@@ -221,7 +225,8 @@ jobs:
id: ccache-cache
uses: actions/cache@v3
with:
path: ${{ github.workspace }}/.ccache
path: |
${{ github.workspace }}/.ccache
key: linux-${{ matrix.arch }}-ccache-plugin-${{ steps.setup.outputs.ccacheDate }}
restore-keys: |
linux-${{ matrix.arch }}-ccache-plugin-
@@ -301,9 +306,24 @@ jobs:
id: setup
run: |
## SETUP ENVIRONMENT SCRIPT
$CcacheDate = (Get-Date -Format "yyyy-M-d")
"ccacheDate=${CcacheDate}" >> $env:GITHUB_OUTPUT
$CommitHash = (git rev-parse HEAD)[0..8] -join ''
"commitHash=${CommitHash}" >> $env:GITHUB_OUTPUT
- name: Restore Compilation Cache
id: ccache-cache
uses: actions/cache@v3
with:
path: |
${{ github.workspace }}/.ccache
${{ github.workspace }}/plugin/deps/opencv/build_*
${{ github.workspace }}/plugin/deps/leptonica/build_*
${{ github.workspace }}/plugin/deps/tesseract/build*
key: windows-${{ matrix.arch }}-ccache-plugin-${{ steps.setup.outputs.ccacheDate }}
restore-keys: |
windows-${{ matrix.arch }}-ccache-plugin-
- name: Check for GitHub Labels
id: seekingTesters
working-directory: ${{ github.workspace }}/plugin

6
.gitmodules vendored
View File

@@ -10,3 +10,9 @@
[submodule "deps/openvr"]
path = deps/openvr
url = https://github.com/ValveSoftware/openvr.git
[submodule "deps/leptonica"]
path = deps/leptonica
url = https://github.com/DanBloomberg/leptonica.git
[submodule "deps/tesseract"]
path = deps/tesseract
url = https://github.com/tesseract-ocr/tesseract.git

View File

@@ -22,5 +22,9 @@
</array>
<key>LSMinimumSystemVersion</key>
<string>10.13</string>
<key>com.apple.security.automation.apple-events</key>
<true/>
<key>NSAppleEventsUsageDescription</key>
<string>Advanced scene switcher needs additional permissions to be able to run AppleScript applications</string>
</dict>
</plist>

View File

@@ -82,9 +82,13 @@ AdvSceneSwitcher.macroTab.defaultname="Macro %1"
AdvSceneSwitcher.macroTab.defaultGroupName="Group %1"
AdvSceneSwitcher.macroTab.exists="Macro name exists already"
AdvSceneSwitcher.macroTab.groupDeleteConfirm="Are you sure you want to delete \"%1\" and all its elements?"
AdvSceneSwitcher.macroTab.copy="Create copy of current macro"
AdvSceneSwitcher.macroTab.group="Group selected items"
AdvSceneSwitcher.macroTab.ungroup="Ungroup selected groups"
AdvSceneSwitcher.macroTab.deleteMultipleMacrosConfirmation="Are you sure you want to delete %1 macros?"
AdvSceneSwitcher.macroTab.contextMenuAdd="Add"
AdvSceneSwitcher.macroTab.copy="Duplicate Macro"
AdvSceneSwitcher.macroTab.group="Group Selected Macros"
AdvSceneSwitcher.macroTab.ungroup="Ungroup Selected Groups"
AdvSceneSwitcher.macroTab.rename="Rename"
AdvSceneSwitcher.macroTab.remove="Remove"
AdvSceneSwitcher.macroTab.expandAll="Expand all"
AdvSceneSwitcher.macroTab.collapseAll="Collapse all"
AdvSceneSwitcher.macroTab.maximize="Maximize"
@@ -174,6 +178,7 @@ AdvSceneSwitcher.condition.video.condition.noImage="has no output"
AdvSceneSwitcher.condition.video.condition.pattern="matches pattern"
AdvSceneSwitcher.condition.video.condition.object="contains object"
AdvSceneSwitcher.condition.video.condition.brightness="brightness"
AdvSceneSwitcher.condition.video.condition.ocr="contains text"
AdvSceneSwitcher.condition.video.askFileAction="Do you want to use an existing file or create a screenshot of the currently selected source?"
AdvSceneSwitcher.condition.video.askFileAction.file="Use existing file"
AdvSceneSwitcher.condition.video.askFileAction.screenshot="Create screenshot"
@@ -184,6 +189,11 @@ AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip="This will al
AdvSceneSwitcher.condition.video.patternThreshold="Threshold: "
AdvSceneSwitcher.condition.video.patternThresholdDescription="A higher threshold value means that the pattern needs to match the video source more closely."
AdvSceneSwitcher.condition.video.patternThresholdUseAlphaAsMask="Use alpha channel as mask for pattern."
AdvSceneSwitcher.condition.video.patternMatchMode="Use pattern matching mode {{patternMatchingModes}}"
AdvSceneSwitcher.condition.video.patternMatchMode.crossCorrelation="Cross correlation"
AdvSceneSwitcher.condition.video.patternMatchMode.correlationCoefficient="Correlation coefficient"
AdvSceneSwitcher.condition.video.patternMatchMode.squaredDifference="Squared difference"
AdvSceneSwitcher.condition.video.patternMatchMode.tip="The best method to use depends on the specific problem and the characteristics of the images involved.\nIn practice, it's often necessary to experiment with different methods and parameters to find the best match for a particular use case."
AdvSceneSwitcher.condition.video.brightnessThreshold="Average brightness is above:"
AdvSceneSwitcher.condition.video.brightnessThresholdDescription="A high value is indicating a bright image and a low one a darker one."
AdvSceneSwitcher.condition.video.currentBrightness="Current average brightness: %1"
@@ -198,7 +208,18 @@ AdvSceneSwitcher.condition.video.patternMatchFail="Pattern was not found!"
AdvSceneSwitcher.condition.video.patternMatchSuccess="Pattern is highlighted in red"
AdvSceneSwitcher.condition.video.objectMatchFail="Object was not found!"
AdvSceneSwitcher.condition.video.objectMatchSuccess="Object is highlighted in red"
AdvSceneSwitcher.condition.video.ocrMatchSuccess="Detected text:\n\n%1"
AdvSceneSwitcher.condition.video.modelLoadFail="Model data could not be loaded!"
AdvSceneSwitcher.condition.video.selectColor="Select Color"
AdvSceneSwitcher.condition.video.ocrMode.singleColumn="Single column of text of variable sizes"
AdvSceneSwitcher.condition.video.ocrMode.singleBlockVertText="Single uniform block of vertically aligned text"
AdvSceneSwitcher.condition.video.ocrMode.singleBlock="Single uniform block of text"
AdvSceneSwitcher.condition.video.ocrMode.singleLine="Single text line"
AdvSceneSwitcher.condition.video.ocrMode.singleWord="Single word"
AdvSceneSwitcher.condition.video.ocrMode.circleWord="Single word in a circle"
AdvSceneSwitcher.condition.video.ocrMode.singleChar="Single character"
AdvSceneSwitcher.condition.video.ocrMode.sparseText="Find text in no particular order"
AdvSceneSwitcher.condition.video.ocrMode.sparseTextOSD="Find text in no particular order (OSD)"
AdvSceneSwitcher.condition.video.type.main="OBS's main output"
AdvSceneSwitcher.condition.video.type.source="Source"
AdvSceneSwitcher.condition.video.type.scene="Scene"
@@ -208,6 +229,8 @@ AdvSceneSwitcher.condition.video.entry.minNeighbor="Minimum neighbors: {{minNeig
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduce CPU load by performing check only every {{throttleCount}} milliseconds"
AdvSceneSwitcher.condition.video.entry.checkAreaEnable="Perform check only in area"
AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}"
AdvSceneSwitcher.condition.video.entry.orcColorPick="Check for text color:{{textColor}}{{selectColor}}"
AdvSceneSwitcher.condition.video.entry.orcTextType="Check for text type:{{textType}}"
AdvSceneSwitcher.condition.video.minSize="Minimum size:"
AdvSceneSwitcher.condition.video.maxSize="Maximum size:"
AdvSceneSwitcher.condition.video.selectArea="Select area"

Binary file not shown.

1
deps/leptonica vendored Submodule

Submodule deps/leptonica added at b667978e86

1
deps/tesseract vendored Submodule

Submodule deps/tesseract added at 080da83cc5

View File

@@ -46,7 +46,10 @@ public:
bool addNewMacro(std::shared_ptr<Macro> &res, std::string &name,
std::string format = "");
void RemoveMacro(std::shared_ptr<Macro> &);
void RenameMacro(std::shared_ptr<Macro> &, const QString &name);
std::shared_ptr<Macro> getSelectedMacro();
std::vector<std::shared_ptr<Macro>> getSelectedMacros();
void SetEditMacro(Macro &m);
void SetMacroEditAreaDisabled(bool);
void HighlightAction(int idx, QColor color = QColor(Qt::green));
@@ -145,6 +148,7 @@ public slots:
void ShowMacroActionsContextMenu(const QPoint &);
void ShowMacroConditionsContextMenu(const QPoint &);
void CopyMacro();
void RenameCurrentMacro();
void ExpandAllActions();
void ExpandAllConditions();
void CollapseAllActions();

View File

@@ -375,7 +375,7 @@ void AdvSceneSwitcher::AddMacroCondition(int idx)
if (idx >= 1) {
id = macro->Conditions().at(idx - 1)->GetId();
if (idx == 1) {
logic = LogicType::NONE;
logic = LogicType::OR;
} else {
logic = macro->Conditions().at(idx - 1)->GetLogicType();
}

View File

@@ -75,7 +75,7 @@ void MacroSegmentList::SetHelpMsgVisible(bool visible)
_helpMsg->setVisible(visible);
}
void MacroSegmentList::Insert(int idx, QWidget *widget)
void MacroSegmentList::Insert(int idx, MacroSegmentEdit *widget)
{
widget->installEventFilter(this);
_contentLayout->insertWidget(idx, widget);
@@ -135,8 +135,6 @@ void MacroSegmentList::SetSelection(int idx)
bool MacroSegmentList::eventFilter(QObject *object, QEvent *event)
{
if (event->type() == QEvent::MouseButtonPress) {
}
switch (event->type()) {
case QEvent::MouseButtonPress:
mousePressEvent(static_cast<QMouseEvent *>(event));

View File

@@ -16,7 +16,7 @@ public:
void SetHelpMsg(const QString &msg);
void SetHelpMsgVisible(bool visible);
MacroSegmentEdit *WidgetAt(int idx);
void Insert(int idx, QWidget *widget);
void Insert(int idx, MacroSegmentEdit *widget);
void Add(QWidget *widget);
void Remove(int idx);
void Clear(int idx = 0); // Clear all elements >= idx

View File

@@ -7,6 +7,7 @@
#include <QMouseEvent>
#include <QLabel>
#include <QScrollBar>
#include <QApplication>
MacroSegment::MacroSegment(Macro *m, bool supportsVariableValue)
: _macro(m), _supportsVariableValue(supportsVariableValue)
@@ -182,11 +183,37 @@ MacroSegmentEdit::MacroSegmentEdit(bool highlight, QWidget *parent)
SetSelected(false);
ShowDropLine(DropLineState::NONE);
// Enable dragging while clicking on the header text
_headerInfo->installEventFilter(this);
_timer.setInterval(1500);
connect(&_timer, SIGNAL(timeout()), this, SLOT(Highlight()));
_timer.start();
}
bool MacroSegmentEdit::eventFilter(QObject *obj, QEvent *ev)
{
if (obj == _headerInfo && ev->type() == QEvent::MouseMove) {
if (!parentWidget()) {
return QWidget::eventFilter(obj, ev);
}
// Generate a mouse move event for the MacroSegmentList
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(ev);
QMouseEvent *newEvent = new QMouseEvent(
mouseEvent->type(),
_headerInfo->mapTo(this, mouseEvent->pos()),
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
mouseEvent->globalPos(),
#else
mouseEvent->globalPosition(),
#endif
mouseEvent->button(), mouseEvent->buttons(),
mouseEvent->modifiers());
QApplication::sendEvent(parentWidget(), newEvent);
}
return QWidget::eventFilter(obj, ev);
}
void MacroSegmentEdit::HeaderInfoChanged(const QString &text)
{
_headerInfo->setVisible(!text.isEmpty());

View File

@@ -75,6 +75,8 @@ signals:
void SceneGroupRenamed(const QString &oldName, const QString newName);
protected:
bool eventFilter(QObject *obj, QEvent *ev) override;
Section *_section;
QLabel *_headerInfo;
QWidget *_frame;

View File

@@ -83,14 +83,14 @@ void AdvSceneSwitcher::on_macroAdd_clicked()
emit MacroAdded(QString::fromStdString(name));
}
void AdvSceneSwitcher::on_macroRemove_clicked()
void AdvSceneSwitcher::RemoveMacro(std::shared_ptr<Macro> &macro)
{
auto macro = getSelectedMacro();
if (!macro) {
return;
}
auto name = QString::fromStdString(macro->Name());
if (macro->IsGroup()) {
if (macro->IsGroup() && macro->GroupSize() > 0) {
QString deleteWarning = obs_module_text(
"AdvSceneSwitcher.macroTab.groupDeleteConfirm");
if (!DisplayMessage(deleteWarning.arg(name), true)) {
@@ -106,6 +106,40 @@ void AdvSceneSwitcher::on_macroRemove_clicked()
emit MacroRemoved(name);
}
void AdvSceneSwitcher::RenameMacro(std::shared_ptr<Macro> &macro,
const QString &name)
{
auto oldName = QString::fromStdString(macro->Name());
{
std::lock_guard<std::mutex> lock(switcher->m);
macro->SetName(name.toStdString());
}
emit MacroRenamed(oldName, name);
}
void AdvSceneSwitcher::on_macroRemove_clicked()
{
auto macros = getSelectedMacros();
if (macros.empty()) {
return;
}
if (macros.size() == 1) {
RemoveMacro(macros.at(0));
return;
}
QString deleteWarning = obs_module_text(
"AdvSceneSwitcher.macroTab.deleteMultipleMacrosConfirmation");
if (!DisplayMessage(deleteWarning.arg(macros.size()), true)) {
return;
}
for (auto &macro : macros) {
RemoveMacro(macro);
}
}
void AdvSceneSwitcher::on_macroUp_clicked()
{
std::lock_guard<std::mutex> lock(switcher->m);
@@ -126,10 +160,37 @@ void AdvSceneSwitcher::on_macroDown_clicked()
ui->macros->Down(macro);
}
static bool newMacroNameValid(const std::string &name)
{
if (!macroNameExists(name)) {
return true;
}
DisplayMessage(obs_module_text("AdvSceneSwitcher.macroTab.exists"));
return false;
}
void AdvSceneSwitcher::RenameCurrentMacro()
{
auto macro = getSelectedMacro();
if (!macro) {
return;
}
std::string oldName = macro->Name();
std::string name;
if (!AdvSSNameDialog::AskForName(
this, obs_module_text("AdvSceneSwitcher.windowTitle"),
obs_module_text("AdvSceneSwitcher.item.newName"), name,
QString::fromStdString(oldName))) {
return;
}
if (name.empty() || name == oldName || !newMacroNameValid(name)) {
return;
}
RenameMacro(macro, QString::fromStdString(name));
}
void AdvSceneSwitcher::on_macroName_editingFinished()
{
bool nameValid = true;
auto macro = getSelectedMacro();
if (!macro) {
return;
@@ -138,33 +199,12 @@ void AdvSceneSwitcher::on_macroName_editingFinished()
QString newName = ui->macroName->text();
QString oldName = QString::fromStdString(macro->Name());
if (newName.isEmpty() || newName == oldName) {
nameValid = false;
}
if (nameValid && macroNameExists(newName.toUtf8().constData())) {
DisplayMessage(
obs_module_text("AdvSceneSwitcher.macroTab.exists"));
nameValid = false;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
if (nameValid) {
macro->SetName(newName.toUtf8().constData());
auto macro = getSelectedMacro();
if (!macro) {
return;
}
macro->SetName(newName.toStdString());
} else {
ui->macroName->setText(oldName);
}
}
if (nameValid) {
emit MacroRenamed(oldName, newName);
if (newName.isEmpty() || newName == oldName ||
!newMacroNameValid(newName.toStdString())) {
ui->macroName->setText(oldName);
return;
}
RenameMacro(macro, newName);
}
void AdvSceneSwitcher::on_runMacro_clicked()
@@ -314,6 +354,11 @@ std::shared_ptr<Macro> AdvSceneSwitcher::getSelectedMacro()
return ui->macros->GetCurrentMacro();
}
std::vector<std::shared_ptr<Macro>> AdvSceneSwitcher::getSelectedMacros()
{
return ui->macros->GetCurrentMacros();
}
void AdvSceneSwitcher::MacroSelectionChanged(const QItemSelection &,
const QItemSelection &)
{
@@ -467,15 +512,20 @@ void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos)
{
QPoint globalPos = ui->macros->mapToGlobal(pos);
QMenu menu;
auto add = menu.addAction(
obs_module_text("AdvSceneSwitcher.macroTab.contextMenuAdd"),
this, &AdvSceneSwitcher::on_macroAdd_clicked);
auto copy = menu.addAction(
obs_module_text("AdvSceneSwitcher.macroTab.copy"), this,
&AdvSceneSwitcher::CopyMacro);
copy->setDisabled(ui->macros->GroupsSelected());
copy->setEnabled(ui->macros->SingleItemSelected());
menu.addSeparator();
auto group = menu.addAction(
obs_module_text("AdvSceneSwitcher.macroTab.group"), ui->macros,
&MacroTree::GroupSelectedItems);
// Nested groups are not supported
group->setDisabled(ui->macros->GroupedItemsSelected() ||
ui->macros->GroupsSelected() ||
ui->macros->SelectionEmpty());
@@ -484,6 +534,17 @@ void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos)
obs_module_text("AdvSceneSwitcher.macroTab.ungroup"),
ui->macros, &MacroTree::UngroupSelectedGroups);
ungroup->setEnabled(ui->macros->GroupsSelected());
menu.addSeparator();
auto rename = menu.addAction(
obs_module_text("AdvSceneSwitcher.macroTab.rename"), this,
&AdvSceneSwitcher::RenameCurrentMacro);
rename->setEnabled(ui->macros->SingleItemSelected());
auto remove = menu.addAction(
obs_module_text("AdvSceneSwitcher.macroTab.remove"), this,
&AdvSceneSwitcher::on_macroRemove_clicked);
remove->setDisabled(ui->macros->SelectionEmpty());
menu.exec(globalPos);
}

View File

@@ -390,6 +390,8 @@ void MacroTreeModel::Remove(std::shared_ptr<Macro> item)
if (!item->IsCollapsed()) {
uiEndIdx += item->GroupSize();
}
} else if (item->IsSubitem()) {
Macro::PrepareMoveToGroup(nullptr, item);
}
beginRemoveRows(QModelIndex(), uiStartIdx, uiEndIdx);
@@ -472,6 +474,21 @@ std::shared_ptr<Macro> MacroTreeModel::GetCurrentMacro() const
return _macros[ModelIndexToMacroIndex(idx, _macros)];
}
std::vector<std::shared_ptr<Macro>>
MacroTreeModel::GetCurrentMacros(const QModelIndexList &selection) const
{
std::vector<std::shared_ptr<Macro>> result;
result.reserve(selection.size());
for (const auto &sel : selection) {
try {
result.emplace_back(_macros.at(
ModelIndexToMacroIndex(sel.row(), _macros)));
} catch (const std::out_of_range &) {
}
}
return result;
}
MacroTreeModel::MacroTreeModel(MacroTree *st_,
std::deque<std::shared_ptr<Macro>> &macros)
: QAbstractListModel(st_), _mt(st_), _macros(macros)
@@ -566,37 +583,42 @@ void MacroTreeModel::GroupSelectedItems(QModelIndexList &indices)
std::lock_guard<std::mutex> lock(switcher->m);
QString name = GetNewGroupName();
std::vector<std::shared_ptr<Macro>> items;
items.reserve(indices.size());
std::vector<std::shared_ptr<Macro>> itemsToGroup;
itemsToGroup.reserve(indices.size());
int insertGroupAt = indices[0].row();
for (int i = 0; i < indices.count(); i++) {
std::shared_ptr<Macro> item = _macros[ModelIndexToMacroIndex(
indices[i].row(), _macros)];
items.emplace_back(item);
int selectedIdx = indices[i].row();
if (selectedIdx < insertGroupAt) {
insertGroupAt = selectedIdx;
}
std::shared_ptr<Macro> item =
_macros[ModelIndexToMacroIndex(selectedIdx, _macros)];
itemsToGroup.emplace_back(item);
}
std::shared_ptr<Macro> item =
Macro::CreateGroup(name.toStdString(), items);
Macro::CreateGroup(name.toStdString(), itemsToGroup);
if (!item) {
return;
}
// A new list entry for group
Add(item);
_macros.insert(_macros.begin() + insertGroupAt, item);
// Move all selected items after new group entry
for (const auto &item : items) {
int offset = 1;
for (const auto &item : itemsToGroup) {
auto it = _macros.begin() + GetItemMacroIndex(item);
std::rotate(it, std::next(it, 1), _macros.end());
_macros.erase(it);
_macros.insert(_macros.begin() + insertGroupAt + offset, item);
offset++;
}
_hasGroups = true;
_mt->selectionModel()->clear();
for (auto &m : _macros) {
m->ResolveMacroRef();
}
Reset(_macros);
for (auto &m : _macros) {
m->ResolveMacroRef();
}
@@ -703,6 +725,12 @@ std::shared_ptr<Macro> MacroTree::GetCurrentMacro() const
return GetModel()->GetCurrentMacro();
}
std::vector<std::shared_ptr<Macro>> MacroTree::GetCurrentMacros() const
{
QModelIndexList indices = selectedIndexes();
return GetModel()->GetCurrentMacros(indices);
}
MacroTree::MacroTree(QWidget *parent_) : QListView(parent_)
{
setStyleSheet(QString(
@@ -1029,14 +1057,12 @@ void MacroTree::dropEvent(QDropEvent *event)
bool MacroTree::GroupsSelected() const
{
MacroTreeModel *mtm = GetModel();
QModelIndexList selectedIndices = selectedIndexes();
if (SelectionEmpty()) {
return false;
}
for (auto &idx : selectedIndices) {
MacroTreeModel *mtm = GetModel();
for (auto &idx : selectedIndexes()) {
std::shared_ptr<Macro> item =
mtm->_macros[ModelIndexToMacroIndex(idx.row(),
mtm->_macros)];
@@ -1049,14 +1075,12 @@ bool MacroTree::GroupsSelected() const
bool MacroTree::GroupedItemsSelected() const
{
MacroTreeModel *mtm = GetModel();
QModelIndexList selectedIndices = selectedIndexes();
if (SelectionEmpty()) {
return false;
}
for (auto &idx : selectedIndices) {
auto *mtm = GetModel();
for (auto &idx : selectedIndexes()) {
std::shared_ptr<Macro> item =
mtm->_macros[ModelIndexToMacroIndex(idx.row(),
mtm->_macros)];
@@ -1068,6 +1092,11 @@ bool MacroTree::GroupedItemsSelected() const
return false;
}
bool MacroTree::SingleItemSelected() const
{
return selectedIndexes().size() == 1;
}
bool MacroTree::SelectionEmpty() const
{
return selectedIndexes().empty();

View File

@@ -92,6 +92,8 @@ private:
std::shared_ptr<Macro> FindEndOfGroup(const std::shared_ptr<Macro> &m,
bool above) const;
std::shared_ptr<Macro> GetCurrentMacro() const;
std::vector<std::shared_ptr<Macro>>
GetCurrentMacros(const QModelIndexList &) const;
QString GetNewGroupName();
void GroupSelectedItems(QModelIndexList &indices);
void UngroupSelectedGroups(QModelIndexList &indices);
@@ -121,8 +123,10 @@ public:
void Up(std::shared_ptr<Macro> item) const;
void Down(std::shared_ptr<Macro> item) const;
std::shared_ptr<Macro> GetCurrentMacro() const;
std::vector<std::shared_ptr<Macro>> GetCurrentMacros() const;
bool GroupsSelected() const;
bool GroupedItemsSelected() const;
bool SingleItemSelected() const;
bool SelectionEmpty() const;
public slots:

View File

@@ -17,5 +17,5 @@ install_advss_plugin_dependency(...)
... to install the plugin and its dependencies.
#]]
add_subdirectory(opencv)
add_subdirectory(openvr)
add_subdirectory(video)

View File

@@ -1,45 +0,0 @@
cmake_minimum_required(VERSION 3.14)
project(advanced-scene-switcher-opencv)
# --- Check OpenCV requirements ---
find_package(OpenCV)
if(NOT OpenCV_FOUND)
message(
WARNING
"OpenCV not found! Video condition will be disabled!\nOpenCV sources are available under: ${CMAKE_CURRENT_SOURCE_DIR}/../../../deps/opencv"
)
return()
endif()
# --- End of section ---
add_library(advanced-scene-switcher-opencv MODULE)
target_sources(
${PROJECT_NAME}
PRIVATE area-selection.cpp
area-selection.hpp
macro-condition-video.cpp
macro-condition-video.hpp
opencv-helpers.cpp
opencv-helpers.hpp
paramerter-wrappers.cpp
paramerter-wrappers.hpp
preview-dialog.cpp
preview-dialog.hpp)
setup_advss_plugin(${PROJECT_NAME})
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
# --- OpenCV build settings ---
target_include_directories(${PROJECT_NAME} PRIVATE "${OpenCV_INCLUDE_DIRS}")
target_link_libraries(${PROJECT_NAME} PRIVATE ${OpenCV_LIBRARIES})
# --- End of section ---
install_advss_plugin(${PROJECT_NAME})
if(NOT OS_LINUX)
install_advss_plugin_dependency(TARGET ${PROJECT_NAME} DEPENDENCIES
${OpenCV_LIBS})
endif()

View File

@@ -1,113 +0,0 @@
#include "opencv-helpers.hpp"
PatternImageData createPatternData(QImage &pattern)
{
PatternImageData data{};
if (pattern.isNull()) {
return data;
}
data.rgbaPattern = QImageToMat(pattern);
std::vector<cv::Mat1b> rgbaChannelsPattern;
cv::split(data.rgbaPattern, rgbaChannelsPattern);
std::vector<cv::Mat1b> rgbChanlesPattern(
rgbaChannelsPattern.begin(), rgbaChannelsPattern.begin() + 3);
cv::merge(rgbChanlesPattern, data.rgbPattern);
cv::threshold(rgbaChannelsPattern[3], data.mask, 0, 255,
cv::THRESH_BINARY);
return data;
}
void matchPattern(QImage &img, const PatternImageData &patternData,
double threshold, cv::Mat &result, bool useAlphaAsMask)
{
if (img.isNull() || patternData.rgbaPattern.empty()) {
return;
}
if (img.height() < patternData.rgbaPattern.rows ||
img.width() < patternData.rgbaPattern.cols) {
return;
}
auto i = QImageToMat(img);
if (useAlphaAsMask) {
std::vector<cv::Mat1b> rgbaChannelsImage;
cv::split(i, rgbaChannelsImage);
std::vector<cv::Mat1b> rgbChanlesImage(
rgbaChannelsImage.begin(),
rgbaChannelsImage.begin() + 3);
cv::Mat3b rgbImage;
cv::merge(rgbChanlesImage, rgbImage);
cv::matchTemplate(rgbImage, patternData.rgbPattern, result,
cv::TM_CCORR_NORMED, patternData.mask);
cv::threshold(result, result, threshold, 0, cv::THRESH_TOZERO);
} else {
cv::matchTemplate(i, patternData.rgbaPattern, result,
cv::TM_CCOEFF_NORMED);
cv::threshold(result, result, threshold, 0, cv::THRESH_TOZERO);
}
}
void matchPattern(QImage &img, QImage &pattern, double threshold,
cv::Mat &result, bool useAlphaAsMask)
{
auto data = createPatternData(pattern);
matchPattern(img, data, threshold, result, useAlphaAsMask);
}
std::vector<cv::Rect> matchObject(QImage &img, cv::CascadeClassifier &cascade,
double scaleFactor, int minNeighbors,
cv::Size minSize, cv::Size maxSize)
{
if (img.isNull() || cascade.empty()) {
return {};
}
auto i = QImageToMat(img);
cv::Mat frameGray;
cv::cvtColor(i, frameGray, cv::COLOR_BGR2GRAY);
equalizeHist(frameGray, frameGray);
std::vector<cv::Rect> objects;
cascade.detectMultiScale(frameGray, objects, scaleFactor, minNeighbors,
0, minSize, maxSize);
return objects;
}
uchar getAvgBrightness(QImage &img)
{
auto i = QImageToMat(img);
cv::Mat hsvImage, rgbImage;
cv::cvtColor(i, rgbImage, cv::COLOR_RGBA2RGB);
cv::cvtColor(rgbImage, hsvImage, cv::COLOR_RGB2HSV);
long long brightnessSum = 0;
for (int i = 0; i < hsvImage.rows; ++i) {
for (int j = 0; j < hsvImage.cols; ++j) {
brightnessSum += hsvImage.at<cv::Vec3b>(i, j)[2];
}
}
return brightnessSum / (hsvImage.rows * hsvImage.cols);
}
// Assumption is that QImage uses Format_RGBA8888.
// Conversion from: https://github.com/dbzhang800/QtOpenCV
cv::Mat QImageToMat(const QImage &img)
{
if (img.isNull()) {
return cv::Mat();
}
return cv::Mat(img.height(), img.width(), CV_8UC(img.depth() / 8),
(uchar *)img.bits(), img.bytesPerLine());
}
QImage MatToQImage(const cv::Mat &mat)
{
if (mat.empty()) {
return QImage();
}
return QImage(mat.data, mat.cols, mat.rows,
QImage::Format::Format_RGBA8888);
}

View File

@@ -1,27 +0,0 @@
#pragma once
#include <QImage>
#undef NO // MacOS macro that can conflict with OpenCV
#include <opencv2/opencv.hpp>
constexpr int minMinNeighbors = 3;
constexpr int maxMinNeighbors = 6;
constexpr double defaultScaleFactor = 1.1;
struct PatternImageData {
cv::Mat4b rgbaPattern;
cv::Mat3b rgbPattern;
cv::Mat1b mask;
};
PatternImageData createPatternData(QImage &pattern);
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<cv::Rect> matchObject(QImage &img, cv::CascadeClassifier &cascade,
double scaleFactor, int minNeighbors,
cv::Size minSize, cv::Size maxSize);
uchar getAvgBrightness(QImage &img);
cv::Mat QImageToMat(const QImage &img);
QImage MatToQImage(const cv::Mat &mat);

View File

@@ -0,0 +1,63 @@
cmake_minimum_required(VERSION 3.14)
project(advanced-scene-switcher-opencv)
# --- Check OpenCV requirements ---
find_package(OpenCV)
if(NOT OpenCV_FOUND)
message(
WARNING
"OpenCV not found!\n"
"Video condition will be disabled!\n\n"
"OpenCV sources are available under: ${CMAKE_CURRENT_SOURCE_DIR}/../../../deps/opencv"
)
return()
endif()
# --- Check optional OCR dependencies ---
find_package(Leptonica)
find_package(Tesseract)
# --- End of section ---
add_library(${PROJECT_NAME} MODULE)
if(Leptonica_FOUND AND Tesseract_FOUND)
target_compile_definitions(${PROJECT_NAME} PRIVATE OCR_SUPPORT)
target_link_libraries(${PROJECT_NAME} PRIVATE Tesseract::libtesseract
${Leptonica_LIBRARIES})
target_include_directories(${PROJECT_NAME} PRIVATE ${Tesseract_INCLUDE_DIRS}
${Leptonica_INCLUDE_DIRS})
else()
message(
WARNING
"OCR capabilities of video condition disabled!\n"
"Leptonica or Tesseract dependencies were not found!\n\n"
"Sources are available under: ${CMAKE_CURRENT_SOURCE_DIR}/../../../deps/")
endif()
target_sources(
${PROJECT_NAME}
PRIVATE area-selection.cpp
area-selection.hpp
macro-condition-video.cpp
macro-condition-video.hpp
opencv-helpers.cpp
opencv-helpers.hpp
paramerter-wrappers.cpp
paramerter-wrappers.hpp
preview-dialog.cpp
preview-dialog.hpp)
setup_advss_plugin(${PROJECT_NAME})
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
target_include_directories(${PROJECT_NAME} PRIVATE ${OpenCV_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} PRIVATE ${OpenCV_LIBRARIES})
install_advss_plugin(${PROJECT_NAME})
if(NOT OS_LINUX)
install_advss_plugin_dependency(TARGET ${PROJECT_NAME} DEPENDENCIES
${OpenCV_LIBS})
endif()

View File

@@ -61,7 +61,6 @@ SizeSelection::SizeSelection(int min, int max, QWidget *parent)
auto layout = new QHBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(_x);
layout->addWidget(new QLabel(" x "));
layout->addWidget(_y);
setLayout(layout);
}
@@ -92,12 +91,14 @@ AreaSelection::AreaSelection(int min, int max, QWidget *parent)
_x(new SizeSelection(min, max)),
_y(new SizeSelection(min, max))
{
_x->_x->setToolTip("X");
_x->_y->setToolTip("Y");
_y->_x->setToolTip(
obs_module_text("AdvSceneSwitcher.condition.video.width"));
_y->_y->setToolTip(
obs_module_text("AdvSceneSwitcher.condition.video.height"));
_x->_x->setPrefix("X:");
_x->_y->setPrefix("Y:");
_y->_x->setPrefix(QString(obs_module_text(
"AdvSceneSwitcher.condition.video.width")) +
":");
_y->_y->setPrefix(QString(obs_module_text(
"AdvSceneSwitcher.condition.video.height")) +
":");
connect(_x, SIGNAL(SizeChanged(advss::Size)), this,
SLOT(XSizeChanged(advss::Size)));

View File

@@ -10,6 +10,7 @@
#include <QToolTip>
#include <QMessageBox>
#include <QtGlobal>
#include <QColorDialog>
const std::string MacroConditionVideo::id = "video";
@@ -35,6 +36,9 @@ const static std::map<VideoCondition, std::string> conditionTypes = {
"AdvSceneSwitcher.condition.video.condition.object"},
{VideoCondition::BRIGHTNESS,
"AdvSceneSwitcher.condition.video.condition.brightness"},
#ifdef OCR_SUPPORT
{VideoCondition::OCR, "AdvSceneSwitcher.condition.video.condition.ocr"},
#endif
};
const static std::map<VideoInput::Type, std::string> videoInputTypes = {
@@ -46,6 +50,36 @@ const static std::map<VideoInput::Type, std::string> videoInputTypes = {
"AdvSceneSwitcher.condition.video.type.scene"},
};
const static std::map<cv::TemplateMatchModes, std::string> patternMatchModes = {
{cv::TemplateMatchModes::TM_CCOEFF_NORMED,
"AdvSceneSwitcher.condition.video.patternMatchMode.correlationCoefficient"},
{cv::TemplateMatchModes::TM_CCORR_NORMED,
"AdvSceneSwitcher.condition.video.patternMatchMode.crossCorrelation"},
{cv::TemplateMatchModes::TM_SQDIFF_NORMED,
"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"},
};
cv::CascadeClassifier initObjectCascade(std::string &path)
{
cv::CascadeClassifier cascade;
@@ -58,7 +92,7 @@ cv::CascadeClassifier initObjectCascade(std::string &path)
return cascade;
}
bool requiresFileInput(VideoCondition t)
static bool requiresFileInput(VideoCondition t)
{
return t == VideoCondition::MATCH || t == VideoCondition::DIFFER ||
t == VideoCondition::PATTERN;
@@ -126,6 +160,7 @@ bool MacroConditionVideo::Save(obs_data_t *obj) const
obs_data_set_double(obj, "brightness", _brightnessThreshold);
_patternMatchParameters.Save(obj);
_objMatchParameters.Save(obj);
_ocrParamters.Save(obj);
obs_data_set_bool(obj, "throttleEnabled", _throttleEnabled);
obs_data_set_int(obj, "throttleCount", _throttleCount);
_areaParameters.Save(obj);
@@ -144,6 +179,7 @@ bool MacroConditionVideo::Load(obs_data_t *obj)
_brightnessThreshold = obs_data_get_double(obj, "brightness");
_patternMatchParameters.Load(obj);
_objMatchParameters.Load(obj);
_ocrParamters.Load(obj);
_throttleEnabled = obs_data_get_bool(obj, "throttleEnabled");
_throttleCount = obs_data_get_int(obj, "throttleCount");
_areaParameters.Load(obj);
@@ -203,12 +239,18 @@ std::string MacroConditionVideo::GetModelDataPath() const
return _objMatchParameters.modelPath;
}
void MacroConditionVideo::SetPageSegMode(tesseract::PageSegMode mode)
{
_ocrParamters.SetPageMode(mode);
}
bool MacroConditionVideo::ScreenshotContainsPattern()
{
cv::Mat result;
matchPattern(_screenshotData.image, _patternImageData,
_patternMatchParameters.threshold, result,
_patternMatchParameters.useAlphaAsMask);
_patternMatchParameters.useAlphaAsMask,
_patternMatchParameters.matchMode);
return countNonZero(result) > 0;
}
@@ -219,7 +261,8 @@ bool MacroConditionVideo::OutputChanged()
_patternImageData = createPatternData(_matchImage);
matchPattern(_screenshotData.image, _patternImageData,
_patternMatchParameters.threshold, result,
_patternMatchParameters.useAlphaAsMask);
_patternMatchParameters.useAlphaAsMask,
_patternMatchParameters.matchMode);
return countNonZero(result) == 0;
}
return _screenshotData.image != _matchImage;
@@ -238,8 +281,31 @@ bool MacroConditionVideo::ScreenshotContainsObject()
bool MacroConditionVideo::CheckBrightnessThreshold()
{
_currentBrigthness = getAvgBrightness(_screenshotData.image) / 255.;
return _currentBrigthness > _brightnessThreshold;
_currentBrightness = getAvgBrightness(_screenshotData.image) / 255.;
return _currentBrightness > _brightnessThreshold;
}
bool MacroConditionVideo::CheckOCR()
{
if (!_ocrParamters.Initialized()) {
return false;
}
auto text = runOCR(_ocrParamters.GetOCR(), _screenshotData.image,
_ocrParamters.color);
if (_ocrParamters.regex.Enabled()) {
auto expr = _ocrParamters.regex.GetRegularExpression(
_ocrParamters.text);
if (!expr.isValid()) {
return false;
}
auto match = expr.match(QString::fromStdString(text));
return match.hasMatch();
}
SetVariableValue(text);
return text == std::string(_ocrParamters.text);
}
bool MacroConditionVideo::Compare()
@@ -251,6 +317,10 @@ bool MacroConditionVideo::Compare()
_areaParameters.area.height);
}
if (_condition != VideoCondition::OCR) {
SetVariableValue("");
}
switch (_condition) {
case VideoCondition::MATCH:
return _screenshotData.image == _matchImage;
@@ -268,6 +338,8 @@ bool MacroConditionVideo::Compare()
return ScreenshotContainsObject();
case VideoCondition::BRIGHTNESS:
return CheckBrightnessThreshold();
case VideoCondition::OCR:
return CheckOCR();
default:
break;
}
@@ -288,6 +360,22 @@ static inline void populateConditionSelection(QComboBox *list)
}
}
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 inline void populatePatternMatchModeSelection(QComboBox *list)
{
for (const auto &[mode, name] : patternMatchModes) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(mode));
}
}
MacroConditionVideoEdit::MacroConditionVideoEdit(
QWidget *parent, std::shared_ptr<MacroConditionVideo> entryData)
: QWidget(parent),
@@ -309,6 +397,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
"AdvSceneSwitcher.condition.video.patternThresholdDescription"))),
_useAlphaAsMask(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.condition.video.patternThresholdUseAlphaAsMask"))),
_patternMatchModeLayout(new QHBoxLayout()),
_patternMatchMode(new QComboBox()),
_brightnessThreshold(new SliderSpinBox(
0., 1.,
obs_module_text(
@@ -316,6 +406,13 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
obs_module_text(
"AdvSceneSwitcher.condition.video.brightnessThresholdDescription"))),
_currentBrightness(new QLabel),
_ocrLayout(new QVBoxLayout),
_matchText(new VariableTextEdit(this)),
_regex(new RegexConfigWidget(this)),
_textColor(new QLabel),
_selectColor(new QPushButton(obs_module_text(
"AdvSceneSwitcher.condition.video.selectColor"))),
_pageSegMode(new QComboBox()),
_modelDataPath(new FileSelection()),
_modelPathLayout(new QHBoxLayout),
_objectScaleThreshold(new SliderSpinBox(
@@ -349,8 +446,12 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
_imagePath->Button()->disconnect();
_usePatternForChangedCheck->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip"));
_patternMatchMode->setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchMode.tip"));
populatePatternMatchModeSelection(_patternMatchMode);
_minNeighbors->setMinimum(minMinNeighbors);
_minNeighbors->setMaximum(maxMinNeighbors);
populatePageSegModeSelection(_pageSegMode);
_throttleCount->setMinimum(1 * GetSwitcher()->interval);
_throttleCount->setMaximum(10 * GetSwitcher()->interval);
_throttleCount->setSingleStep(GetSwitcher()->interval);
@@ -380,6 +481,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
this, SLOT(PatternThresholdChanged(double)));
QWidget::connect(_useAlphaAsMask, SIGNAL(stateChanged(int)), this,
SLOT(UseAlphaAsMaskChanged(int)));
QWidget::connect(_patternMatchMode, SIGNAL(currentIndexChanged(int)),
this, SLOT(PatternMatchModeChanged(int)));
QWidget::connect(_brightnessThreshold,
SIGNAL(DoubleValueChanged(double)), this,
SLOT(BrightnessThresholdChanged(double)));
@@ -414,10 +517,26 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
&_previewDialog, SLOT(ConditionChanged(int)));
QWidget::connect(_selectArea, SIGNAL(clicked()), this,
SLOT(SelectAreaClicked()));
QWidget::connect(_selectColor, SIGNAL(clicked()), this,
SLOT(SelectColorClicked()));
QWidget::connect(_matchText, SIGNAL(textChanged()), this,
SLOT(MatchTextChanged()));
QWidget::connect(_regex, SIGNAL(RegexConfigChanged(RegexConfig)), this,
SLOT(RegexChanged(RegexConfig)));
QWidget::connect(_pageSegMode, SIGNAL(currentIndexChanged(int)), this,
SLOT(PageSegModeChanged(int)));
populateVideoInputSelection(_videoInputTypes);
populateConditionSelection(_condition);
_patternMatchModeLayout->setContentsMargins(0, 0, 0, 0);
_ocrLayout->setContentsMargins(0, 0, 0, 0);
_modelPathLayout->setContentsMargins(0, 0, 0, 0);
_neighborsControlLayout->setContentsMargins(0, 0, 0, 0);
_sizeLayout->setContentsMargins(0, 0, 0, 0);
_checkAreaControlLayout->setContentsMargins(0, 0, 0, 0);
_throttleControlLayout->setContentsMargins(0, 0, 0, 0);
QHBoxLayout *entryLine1Layout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{videoInputTypes}}", _videoInputTypes},
@@ -435,9 +554,17 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
{"{{checkAreaEnable}}", _checkAreaEnable},
{"{{checkArea}}", _checkArea},
{"{{selectArea}}", _selectArea},
{"{{textColor}}", _textColor},
{"{{selectColor}}", _selectColor},
{"{{textType}}", _pageSegMode},
{"{{patternMatchingModes}}", _patternMatchMode},
};
placeWidgets(obs_module_text("AdvSceneSwitcher.condition.video.entry"),
entryLine1Layout, widgetPlaceholders);
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchMode"),
_patternMatchModeLayout, widgetPlaceholders);
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.video.entry.modelPath"),
@@ -454,6 +581,24 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
"AdvSceneSwitcher.condition.video.entry.checkArea"),
_checkAreaControlLayout, widgetPlaceholders);
_ocrLayout->addWidget(_matchText);
auto regexLayout = new QHBoxLayout;
regexLayout->addWidget(_regex);
regexLayout->addStretch();
_ocrLayout->addLayout(regexLayout);
auto pageModeSegLayout = new QHBoxLayout();
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.video.entry.orcTextType"),
pageModeSegLayout, widgetPlaceholders);
_ocrLayout->addLayout(pageModeSegLayout);
auto colorPickLayout = new QHBoxLayout();
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.video.entry.orcColorPick"),
colorPickLayout, widgetPlaceholders);
_ocrLayout->addLayout(colorPickLayout);
QGridLayout *sizeGrid = new QGridLayout;
sizeGrid->addWidget(
new QLabel(obs_module_text(
@@ -467,7 +612,6 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
sizeGrid->addWidget(_maxSize, 1, 1);
_sizeLayout->addLayout(sizeGrid);
_sizeLayout->addStretch();
_sizeLayout->setContentsMargins(0, 0, 0, 0);
QHBoxLayout *showMatchLayout = new QHBoxLayout;
showMatchLayout->addWidget(_showMatch);
@@ -477,8 +621,10 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
mainLayout->addWidget(_usePatternForChangedCheck);
mainLayout->addWidget(_patternThreshold);
mainLayout->addWidget(_useAlphaAsMask);
mainLayout->addLayout(_patternMatchModeLayout);
mainLayout->addWidget(_brightnessThreshold);
mainLayout->addWidget(_currentBrightness);
mainLayout->addLayout(_ocrLayout);
mainLayout->addLayout(_modelPathLayout);
mainLayout->addWidget(_objectScaleThreshold);
mainLayout->addLayout(_neighborsControlLayout);
@@ -586,6 +732,8 @@ void MacroConditionVideoEdit::ConditionChanged(int cond)
_entryData->_objMatchParameters.cascade =
initObjectCascade(path);
}
SetupPreviewDialogParams();
}
void MacroConditionVideoEdit::ImagePathChanged(const QString &text)
@@ -703,8 +851,7 @@ void MacroConditionVideoEdit::UsePatternForChangedCheckChanged(int value)
std::lock_guard<std::mutex> lock(GetSwitcher()->m);
_entryData->_patternMatchParameters.useForChangedCheck = value;
_patternThreshold->setVisible(value);
adjustSize();
SetWidgetVisibility();
}
void MacroConditionVideoEdit::PatternThresholdChanged(double value)
@@ -742,6 +889,20 @@ void MacroConditionVideoEdit::UseAlphaAsMaskChanged(int value)
_entryData->_patternMatchParameters);
}
void MacroConditionVideoEdit::PatternMatchModeChanged(int idx)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(GetSwitcher()->m);
_entryData->_patternMatchParameters.matchMode =
static_cast<cv::TemplateMatchModes>(
_patternMatchMode->itemData(idx).toInt());
_previewDialog.PatternMatchParamtersChanged(
_entryData->_patternMatchParameters);
}
void MacroConditionVideoEdit::BrightnessThresholdChanged(double value)
{
if (_loading || !_entryData) {
@@ -808,6 +969,78 @@ void MacroConditionVideoEdit::MaxSizeChanged(advss::Size value)
_entryData->_objMatchParameters);
}
void MacroConditionVideoEdit::SetupColorLabel(const QColor &color)
{
_textColor->setText(color.name());
_textColor->setPalette(QPalette(color));
_textColor->setAutoFillBackground(true);
}
void MacroConditionVideoEdit::SelectColorClicked()
{
if (_loading || !_entryData) {
return;
}
const QColor color = QColorDialog::getColor(
_entryData->_ocrParamters.color, this,
obs_module_text("AdvSceneSwitcher.condition.video.selectColor"),
QColorDialog::ColorDialogOption());
if (!color.isValid()) {
return;
}
SetupColorLabel(color);
std::lock_guard<std::mutex> lock(GetSwitcher()->m);
_entryData->_ocrParamters.color = color;
_previewDialog.OCRParamtersChanged(_entryData->_ocrParamters);
}
void MacroConditionVideoEdit::MatchTextChanged()
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(GetSwitcher()->m);
_entryData->_ocrParamters.text =
_matchText->toPlainText().toUtf8().constData();
adjustSize();
updateGeometry();
_previewDialog.OCRParamtersChanged(_entryData->_ocrParamters);
}
void MacroConditionVideoEdit::RegexChanged(RegexConfig conf)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(GetSwitcher()->m);
_entryData->_ocrParamters.regex = conf;
adjustSize();
updateGeometry();
_previewDialog.OCRParamtersChanged(_entryData->_ocrParamters);
}
void MacroConditionVideoEdit::PageSegModeChanged(int idx)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(GetSwitcher()->m);
_entryData->SetPageSegMode(static_cast<tesseract::PageSegMode>(
_pageSegMode->itemData(idx).toInt()));
_previewDialog.OCRParamtersChanged(_entryData->_ocrParamters);
}
void MacroConditionVideoEdit::CheckAreaEnableChanged(int value)
{
if (_loading || !_entryData) {
@@ -905,37 +1138,37 @@ void MacroConditionVideoEdit::ModelPathChanged(const QString &text)
}
}
bool needsShowMatch(VideoCondition cond)
static bool needsShowMatch(VideoCondition cond)
{
return cond == VideoCondition::PATTERN ||
cond == VideoCondition::OBJECT;
cond == VideoCondition::OBJECT || cond == VideoCondition::OCR;
}
bool needsObjectControls(VideoCondition cond)
static bool needsObjectControls(VideoCondition cond)
{
return cond == VideoCondition::OBJECT;
}
bool needsThrottleControls(VideoCondition cond)
static bool needsThrottleControls(VideoCondition cond)
{
return cond == VideoCondition::PATTERN ||
cond == VideoCondition::OBJECT;
}
bool needsThreshold(VideoCondition cond)
static bool needsThreshold(VideoCondition cond)
{
return cond == VideoCondition::PATTERN ||
cond == VideoCondition::HAS_CHANGED ||
cond == VideoCondition::HAS_NOT_CHANGED;
}
bool patternControlIsOptional(VideoCondition cond)
static bool patternControlIsOptional(VideoCondition cond)
{
return cond == VideoCondition::HAS_CHANGED ||
cond == VideoCondition::HAS_NOT_CHANGED;
}
bool needsAreaControls(VideoCondition cond)
static bool needsAreaControls(VideoCondition cond)
{
return cond != VideoCondition::NO_IMAGE;
}
@@ -951,6 +1184,8 @@ void MacroConditionVideoEdit::SetWidgetVisibility()
_patternThreshold->setVisible(needsThreshold(_entryData->_condition));
_useAlphaAsMask->setVisible(_entryData->_condition ==
VideoCondition::PATTERN);
setLayoutVisible(_patternMatchModeLayout,
_entryData->_condition == VideoCondition::PATTERN);
_brightnessThreshold->setVisible(_entryData->_condition ==
VideoCondition::BRIGHTNESS);
_currentBrightness->setVisible(_entryData->_condition ==
@@ -962,6 +1197,8 @@ void MacroConditionVideoEdit::SetWidgetVisibility()
needsObjectControls(_entryData->_condition));
_minNeighborsDescription->setVisible(
needsObjectControls(_entryData->_condition));
setLayoutVisible(_ocrLayout,
_entryData->_condition == VideoCondition::OCR);
setLayoutVisible(_sizeLayout,
needsObjectControls(_entryData->_condition));
setLayoutVisible(_modelPathLayout,
@@ -977,11 +1214,27 @@ void MacroConditionVideoEdit::SetWidgetVisibility()
_entryData->_condition == VideoCondition::HAS_NOT_CHANGED) {
_patternThreshold->setVisible(
_entryData->_patternMatchParameters.useForChangedCheck);
setLayoutVisible(
_patternMatchModeLayout,
_entryData->_patternMatchParameters.useForChangedCheck);
}
adjustSize();
}
void MacroConditionVideoEdit::SetupPreviewDialogParams()
{
_previewDialog.PatternMatchParamtersChanged(
_entryData->_patternMatchParameters);
_previewDialog.ObjDetectParamtersChanged(
_entryData->_objMatchParameters);
_previewDialog.OCRParamtersChanged(_entryData->_ocrParamters);
_previewDialog.VideoSelectionChanged(_entryData->_video);
_previewDialog.AreaParamtersChanged(_entryData->_areaParameters);
_previewDialog.ConditionChanged(
static_cast<int>(_entryData->_condition));
}
void MacroConditionVideoEdit::UpdateEntryData()
{
if (!_entryData) {
@@ -1001,10 +1254,17 @@ void MacroConditionVideoEdit::UpdateEntryData()
_entryData->_patternMatchParameters.threshold);
_useAlphaAsMask->setChecked(
_entryData->_patternMatchParameters.useAlphaAsMask);
_patternMatchMode->setCurrentIndex(_patternMatchMode->findData(
_entryData->_patternMatchParameters.matchMode));
_brightnessThreshold->SetDoubleValue(_entryData->_brightnessThreshold);
_modelDataPath->SetPath(_entryData->GetModelDataPath().c_str());
_objectScaleThreshold->SetDoubleValue(
_entryData->_objMatchParameters.scaleFactor);
_matchText->setPlainText(_entryData->_ocrParamters.text);
_regex->SetRegexConfig(_entryData->_ocrParamters.regex);
SetupColorLabel(_entryData->_ocrParamters.color);
_pageSegMode->setCurrentIndex(_pageSegMode->findData(
static_cast<int>(_entryData->_ocrParamters.GetPageMode())));
_minNeighbors->setValue(_entryData->_objMatchParameters.minNeighbors);
_minSize->SetSize(_entryData->_objMatchParameters.minSize);
_maxSize->SetSize(_entryData->_objMatchParameters.maxSize);
@@ -1014,15 +1274,6 @@ void MacroConditionVideoEdit::UpdateEntryData()
_checkAreaEnable->setChecked(_entryData->_areaParameters.enable);
_checkArea->SetArea(_entryData->_areaParameters.area);
UpdatePreviewTooltip();
SetupPreviewDialogParams();
SetWidgetVisibility();
// Prepare previewDialog
_previewDialog.PatternMatchParamtersChanged(
_entryData->_patternMatchParameters);
_previewDialog.ObjDetectParamtersChanged(
_entryData->_objMatchParameters);
_previewDialog.VideoSelectionChanged(_entryData->_video);
_previewDialog.AreaParamtersChanged(_entryData->_areaParameters);
_previewDialog.ConditionChanged(
static_cast<int>(_entryData->_condition));
}

View File

@@ -8,6 +8,7 @@
#include <file-selection.hpp>
#include <screenshot-helper.hpp>
#include <slider-spinbox.hpp>
#include <variable-text-edit.hpp>
#include <QWidget>
#include <QComboBox>
@@ -21,7 +22,7 @@ class PreviewDialog;
class MacroConditionVideo : public MacroCondition {
public:
MacroConditionVideo(Macro *m) : MacroCondition(m) {}
MacroConditionVideo(Macro *m) : MacroCondition(m, true){};
bool CheckCondition();
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
@@ -37,7 +38,8 @@ public:
bool LoadModelData(std::string &path);
std::string GetModelDataPath() const;
void ResetLastMatch() { _lastMatchResult = false; }
double GetCurrentBrightness() const { return _currentBrigthness; }
double GetCurrentBrightness() const { return _currentBrightness; }
void SetPageSegMode(tesseract::PageSegMode);
VideoInput _video;
VideoCondition _condition = VideoCondition::MATCH;
@@ -51,7 +53,8 @@ public:
bool _blockUntilScreenshotDone = false;
double _brightnessThreshold = 0.5;
PatternMatchParameters _patternMatchParameters;
ObjDetectParamerts _objMatchParameters;
ObjDetectParameters _objMatchParameters;
OCRParameters _ocrParamters;
AreaParamters _areaParameters;
bool _throttleEnabled = false;
int _throttleCount = 3;
@@ -61,6 +64,7 @@ private:
bool ScreenshotContainsPattern();
bool ScreenshotContainsObject();
bool CheckBrightnessThreshold();
bool CheckOCR();
bool Compare();
bool CheckShouldBeSkipped();
@@ -71,7 +75,8 @@ private:
bool _lastMatchResult = false;
int _runCount = 0;
double _currentBrigthness = 0.;
double _currentBrightness = 0.;
static bool _registered;
static const std::string id;
@@ -108,6 +113,7 @@ private slots:
void UsePatternForChangedCheckChanged(int value);
void PatternThresholdChanged(double);
void UseAlphaAsMaskChanged(int value);
void PatternMatchModeChanged(int value);
void BrightnessThresholdChanged(double);
@@ -117,6 +123,11 @@ private slots:
void MinSizeChanged(advss::Size value);
void MaxSizeChanged(advss::Size value);
void SelectColorClicked();
void MatchTextChanged();
void RegexChanged(RegexConfig conf);
void PageSegModeChanged(int);
void CheckAreaEnableChanged(int value);
void CheckAreaChanged(advss::Area);
void CheckAreaChanged(QRect area);
@@ -134,6 +145,8 @@ signals:
private:
void SetWidgetVisibility();
void HandleVideoInputUpdate();
void SetupPreviewDialogParams();
void SetupColorLabel(const QColor &);
QComboBox *_videoInputTypes;
SceneSelectionWidget *_scenes;
@@ -147,10 +160,19 @@ private:
SliderSpinBox *_patternThreshold;
QCheckBox *_useAlphaAsMask;
QHBoxLayout *_patternMatchModeLayout;
QComboBox *_patternMatchMode;
SliderSpinBox *_brightnessThreshold;
QLabel *_currentBrightness;
QVBoxLayout *_ocrLayout;
VariableTextEdit *_matchText;
RegexConfigWidget *_regex;
QLabel *_textColor;
QPushButton *_selectColor;
QComboBox *_pageSegMode;
FileSelection *_modelDataPath;
QHBoxLayout *_modelPathLayout;
SliderSpinBox *_objectScaleThreshold;

View File

@@ -0,0 +1,184 @@
#include "opencv-helpers.hpp"
PatternImageData createPatternData(QImage &pattern)
{
PatternImageData data{};
if (pattern.isNull()) {
return data;
}
data.rgbaPattern = QImageToMat(pattern);
std::vector<cv::Mat1b> rgbaChannelsPattern;
cv::split(data.rgbaPattern, rgbaChannelsPattern);
std::vector<cv::Mat1b> rgbChanlesPattern(
rgbaChannelsPattern.begin(), rgbaChannelsPattern.begin() + 3);
cv::merge(rgbChanlesPattern, data.rgbPattern);
cv::threshold(rgbaChannelsPattern[3], data.mask, 0, 255,
cv::THRESH_BINARY);
return data;
}
static void invertPatternMatchResult(cv::Mat &mat)
{
for (int r = 0; r < mat.rows; r++) {
for (int c = 0; c < mat.cols; c++) {
float value = mat.at<float>(r, c) =
1.0 - mat.at<float>(r, c);
}
}
}
void matchPattern(QImage &img, const PatternImageData &patternData,
double threshold, cv::Mat &result, bool useAlphaAsMask,
cv::TemplateMatchModes matchMode)
{
if (img.isNull() || patternData.rgbaPattern.empty()) {
return;
}
if (img.height() < patternData.rgbaPattern.rows ||
img.width() < patternData.rgbaPattern.cols) {
return;
}
auto input = QImageToMat(img);
if (useAlphaAsMask) {
// Remove alpha channel of input image as the alpha channel
// information is used as a stencil for the pattern instead and
// thus should not be used while matching the pattern as well
//
// Input format is Format_RGBA8888 so discard the 4th channel
std::vector<cv::Mat1b> inputChannels;
cv::split(input, inputChannels);
std::vector<cv::Mat1b> rgbChanlesImage(
inputChannels.begin(), inputChannels.begin() + 3);
cv::Mat3b rgbInput;
cv::merge(rgbChanlesImage, rgbInput);
cv::matchTemplate(rgbInput, patternData.rgbPattern, result,
matchMode, patternData.mask);
} else {
cv::matchTemplate(input, patternData.rgbaPattern, result,
matchMode);
}
// A perfect match is represented as "0" for TM_SQDIFF_NORMED
//
// For TM_CCOEFF_NORMED and TM_CCORR_NORMED a perfect match is
// represented as "1"
if (matchMode == cv::TM_SQDIFF_NORMED) {
invertPatternMatchResult(result);
}
cv::threshold(result, result, threshold, 0.0, cv::THRESH_TOZERO);
}
void matchPattern(QImage &img, QImage &pattern, double threshold,
cv::Mat &result, bool useAlphaAsMask,
cv::TemplateMatchModes matchColor)
{
auto data = createPatternData(pattern);
matchPattern(img, data, threshold, result, useAlphaAsMask, matchColor);
}
std::vector<cv::Rect> matchObject(QImage &img, cv::CascadeClassifier &cascade,
double scaleFactor, int minNeighbors,
cv::Size minSize, cv::Size maxSize)
{
if (img.isNull() || cascade.empty()) {
return {};
}
auto i = QImageToMat(img);
cv::Mat frameGray;
cv::cvtColor(i, frameGray, cv::COLOR_RGBA2GRAY);
equalizeHist(frameGray, frameGray);
std::vector<cv::Rect> objects;
cascade.detectMultiScale(frameGray, objects, scaleFactor, minNeighbors,
0, minSize, maxSize);
return objects;
}
uchar getAvgBrightness(QImage &img)
{
auto i = QImageToMat(img);
cv::Mat hsvImage, rgbImage;
cv::cvtColor(i, rgbImage, cv::COLOR_RGBA2RGB);
cv::cvtColor(rgbImage, hsvImage, cv::COLOR_RGB2HSV);
long long brightnessSum = 0;
for (int i = 0; i < hsvImage.rows; ++i) {
for (int j = 0; j < hsvImage.cols; ++j) {
brightnessSum += hsvImage.at<cv::Vec3b>(i, j)[2];
}
}
return brightnessSum / (hsvImage.rows * hsvImage.cols);
}
cv::Mat preprocessForOCR(const QImage &image, const QColor &color)
{
auto mat = QImageToMat(image);
// Only keep the desired color
cv::cvtColor(mat, mat, cv::COLOR_RGBA2RGB);
cv::cvtColor(mat, mat, cv::COLOR_RGB2HSV);
cv::inRange(mat, cv::Scalar(0, 0, 0),
cv::Scalar(color.red(), color.green(), color.blue()), mat);
// Invert to improve ORC detection
cv::bitwise_not(mat, mat);
// Scale image up if selected area is too small
// Results will probably still be unsatisfying
if (mat.rows <= 300 || mat.cols <= 300) {
double scale = 0.;
if (mat.rows < mat.cols) {
scale = 300. / mat.rows;
} else {
scale = 300. / mat.cols;
}
cv::resize(mat, mat,
cv::Size(mat.cols * scale, mat.rows * scale),
cv::INTER_CUBIC);
}
return mat;
}
std::string runOCR(tesseract::TessBaseAPI *ocr, const QImage &image,
const QColor &color)
{
#ifdef OCR_SUPPORT
auto mat = preprocessForOCR(image, color);
ocr->SetImage(mat.data, mat.cols, mat.rows, 1, mat.step);
ocr->Recognize(0);
std::unique_ptr<char[]> detectedText(ocr->GetUTF8Text());
if (!detectedText) {
return "";
}
return detectedText.get();
#else
return "";
#endif
}
// Assumption is that QImage uses Format_RGBA8888.
// Conversion from: https://github.com/dbzhang800/QtOpenCV
cv::Mat QImageToMat(const QImage &img)
{
if (img.isNull()) {
return cv::Mat();
}
return cv::Mat(img.height(), img.width(), CV_8UC(img.depth() / 8),
(uchar *)img.bits(), img.bytesPerLine());
}
QImage MatToQImage(const cv::Mat &mat)
{
if (mat.empty()) {
return QImage();
}
return QImage(mat.data, mat.cols, mat.rows,
QImage::Format::Format_RGBA8888);
}

View File

@@ -0,0 +1,62 @@
#pragma once
#include <QImage>
#undef NO // MacOS macro that can conflict with OpenCV
#include <opencv2/opencv.hpp>
#ifdef OCR_SUPPORT
#include <tesseract/baseapi.h>
#else
namespace tesseract {
enum PageSegMode {
PSM_OSD_ONLY = 0,
PSM_AUTO_OSD = 1,
PSM_AUTO_ONLY = 2,
PSM_AUTO = 3,
PSM_SINGLE_COLUMN = 4,
PSM_SINGLE_BLOCK_VERT_TEXT = 5,
PSM_SINGLE_BLOCK = 6,
PSM_SINGLE_LINE = 7,
PSM_SINGLE_WORD = 8,
PSM_CIRCLE_WORD = 9,
PSM_SINGLE_CHAR = 10,
PSM_SPARSE_TEXT = 11,
PSM_SPARSE_TEXT_OSD = 12,
PSM_RAW_LINE = 13,
PSM_COUNT
};
class TessBaseAPI {
public:
void SetPageSegMode(PageSegMode) {}
int Init(const char *, const char *) { return 0; }
void End() {}
};
}
#endif
constexpr int minMinNeighbors = 3;
constexpr int maxMinNeighbors = 6;
constexpr double defaultScaleFactor = 1.1;
struct PatternImageData {
cv::Mat4b rgbaPattern;
cv::Mat3b rgbPattern;
cv::Mat1b mask;
};
PatternImageData createPatternData(QImage &pattern);
void matchPattern(QImage &img, const PatternImageData &patternData,
double threshold, cv::Mat &result, bool useAlphaAsMask,
cv::TemplateMatchModes matchMode);
void matchPattern(QImage &img, QImage &pattern, double threshold,
cv::Mat &result, bool useAlphaAsMask,
cv::TemplateMatchModes matchMode);
std::vector<cv::Rect> matchObject(QImage &img, cv::CascadeClassifier &cascade,
double scaleFactor, int minNeighbors,
cv::Size minSize, cv::Size maxSize);
uchar getAvgBrightness(QImage &img);
cv::Mat preprocessForOCR(const QImage &image, const QColor &color);
std::string runOCR(tesseract::TessBaseAPI *, const QImage &, const QColor &);
cv::Mat QImageToMat(const QImage &img);
QImage MatToQImage(const cv::Mat &mat);

View File

@@ -6,6 +6,7 @@ bool PatternMatchParameters::Save(obs_data_t *obj) const
obs_data_set_bool(data, "useForChangedCheck", useForChangedCheck);
obs_data_set_double(data, "threshold", threshold);
obs_data_set_bool(data, "useAlphaAsMask", useAlphaAsMask);
obs_data_set_int(data, "matchMode", matchMode);
obs_data_set_obj(obj, "patternMatchData", data);
obs_data_release(data);
return true;
@@ -25,11 +26,18 @@ bool PatternMatchParameters::Load(obs_data_t *obj)
useForChangedCheck = obs_data_get_bool(data, "useForChangedCheck");
threshold = obs_data_get_double(data, "threshold");
useAlphaAsMask = obs_data_get_bool(data, "useAlphaAsMask");
// TODO: Remove this fallback in a future version
if (!obs_data_has_user_value(obj, "matchMode")) {
matchMode = cv::TM_CCORR_NORMED;
} else {
matchMode = static_cast<cv::TemplateMatchModes>(
obs_data_get_int(data, "matchMode"));
}
obs_data_release(data);
return true;
}
bool ObjDetectParamerts::Save(obs_data_t *obj) const
bool ObjDetectParameters::Save(obs_data_t *obj) const
{
auto data = obs_data_create();
obs_data_set_string(data, "modelPath", modelPath.c_str());
@@ -53,7 +61,7 @@ bool isMinNeighborsValid(int minNeighbors)
minNeighbors <= maxMinNeighbors;
}
bool ObjDetectParamerts::Load(obs_data_t *obj)
bool ObjDetectParameters::Load(obs_data_t *obj)
{
// TODO: Remove this fallback in a future version
if (!obs_data_has_user_value(obj, "patternMatchData")) {
@@ -188,3 +196,109 @@ OBSWeakSource VideoInput::GetVideo() const
}
return nullptr;
}
static void SaveColor(obs_data_t *obj, const char *name, const QColor &color)
{
auto data = obs_data_create();
obs_data_set_int(data, "red", color.red());
obs_data_set_int(data, "green", color.green());
obs_data_set_int(data, "blue", color.blue());
obs_data_set_obj(obj, name, data);
obs_data_release(data);
}
OCRParameters::OCRParameters()
{
Setup();
}
OCRParameters::~OCRParameters()
{
if (!initDone) {
return;
}
ocr->End();
}
OCRParameters::OCRParameters(const OCRParameters &other)
: text(other.text),
regex(other.regex),
color(other.color),
pageSegMode(other.pageSegMode)
{
Setup();
if (initDone) {
ocr->SetPageSegMode(pageSegMode);
}
}
OCRParameters &OCRParameters::operator=(const OCRParameters &other)
{
text = other.text;
regex = other.regex;
color = other.color;
pageSegMode = other.pageSegMode;
ocr->SetPageSegMode(pageSegMode);
return *this;
}
bool OCRParameters::Save(obs_data_t *obj) const
{
auto data = obs_data_create();
text.Save(data, "pattern");
regex.Save(data);
SaveColor(data, "textColor", color);
obs_data_set_int(data, "pageSegMode", static_cast<int>(pageSegMode));
obs_data_set_obj(obj, "ocrData", data);
obs_data_release(data);
return true;
}
static QColor LoadColor(obs_data_t *obj, const char *name)
{
QColor color = Qt::black;
auto data = obs_data_get_obj(obj, name);
color.setRed(obs_data_get_int(data, "red"));
color.setGreen(obs_data_get_int(data, "green"));
color.setBlue(obs_data_get_int(data, "blue"));
obs_data_release(data);
return color;
}
bool OCRParameters::Load(obs_data_t *obj)
{
auto data = obs_data_get_obj(obj, "ocrData");
text.Load(data, "pattern");
regex.Load(data);
color = LoadColor(data, "textColor");
pageSegMode = static_cast<tesseract::PageSegMode>(
obs_data_get_int(data, "pageSegMode"));
obs_data_release(data);
if (initDone) {
ocr->SetPageSegMode(pageSegMode);
}
return true;
}
void OCRParameters::SetPageMode(tesseract::PageSegMode mode)
{
pageSegMode = mode;
ocr->SetPageSegMode(mode);
}
void OCRParameters::Setup()
{
ocr = std::make_unique<tesseract::TessBaseAPI>();
if (!ocr) {
initDone = false;
return;
}
std::string dataPath = obs_get_module_data_path(obs_current_module()) +
std::string("/res/ocr");
if (ocr->Init(dataPath.c_str(), "eng") != 0) {
initDone = false;
return;
}
initDone = true;
}

View File

@@ -4,9 +4,17 @@
#include <source-selection.hpp>
#include <scene-selection.hpp>
#include <regex-config.hpp>
#include <variable.hpp>
#include <obs.hpp>
#include <obs-module.h>
#include <QMetaType>
#ifdef OCR_SUPPORT
#include <tesseract/baseapi.h>
#endif
enum class VideoCondition {
MATCH,
DIFFER,
@@ -16,6 +24,7 @@ enum class VideoCondition {
PATTERN,
OBJECT,
BRIGHTNESS,
OCR,
};
class VideoInput {
@@ -45,10 +54,11 @@ public:
QImage image;
bool useForChangedCheck = false;
bool useAlphaAsMask = false;
cv::TemplateMatchModes matchMode = cv::TM_CCORR_NORMED;
double threshold = 0.8;
};
class ObjDetectParamerts {
class ObjDetectParameters {
public:
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
@@ -64,6 +74,36 @@ public:
advss::Size maxSize{0, 0};
};
class OCRParameters {
public:
OCRParameters();
~OCRParameters();
OCRParameters(const OCRParameters &other);
OCRParameters &operator=(const OCRParameters &);
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
bool Initialized() const { return initDone; }
void SetPageMode(tesseract::PageSegMode);
tesseract::PageSegMode GetPageMode() const { return pageSegMode; }
tesseract::TessBaseAPI *GetOCR() const { return ocr.get(); }
VariableResolvingString text =
obs_module_text("AdvSceneSwitcher.enterText");
RegexConfig regex = RegexConfig::PartialMatchRegexConfig();
QColor color = Qt::black;
private:
void Setup();
tesseract::PageSegMode pageSegMode = tesseract::PSM_SINGLE_BLOCK;
std::unique_ptr<tesseract::TessBaseAPI> ocr;
bool initDone = false;
};
Q_DECLARE_METATYPE(OCRParameters)
class AreaParamters {
public:
bool Save(obs_data_t *obj) const;

View File

@@ -89,16 +89,16 @@ PreviewDialog::~PreviewDialog()
void PreviewDialog::ShowMatch()
{
Start();
_rubberBand->hide();
_type = PreviewType::SHOW_MATCH;
_rubberBand->hide();
Start();
}
void PreviewDialog::SelectArea()
{
_selectingArea = false;
Start();
_type = PreviewType::SELECT_AREA;
Start();
DrawFrame();
_statusLabel->setText(obs_module_text(
"AdvSceneSwitcher.condition.video.selectArea.status"));
@@ -123,12 +123,18 @@ void PreviewDialog::PatternMatchParamtersChanged(
_patternImageData = createPatternData(_patternMatchParams.image);
}
void PreviewDialog::ObjDetectParamtersChanged(const ObjDetectParamerts &params)
void PreviewDialog::ObjDetectParamtersChanged(const ObjDetectParameters &params)
{
std::unique_lock<std::mutex> lock(_mtx);
_objDetectParams = params;
}
void PreviewDialog::OCRParamtersChanged(const OCRParameters &params)
{
std::unique_lock<std::mutex> lock(_mtx);
_ocrParams = params;
}
void PreviewDialog::VideoSelectionChanged(const VideoInput &video)
{
std::unique_lock<std::mutex> lock(_mtx);
@@ -163,7 +169,7 @@ void PreviewDialog::UpdateImage(const QPixmap &image)
DrawFrame();
}
emit NeedImage(_video, _type, _patternMatchParams, _patternImageData,
_objDetectParams, _areaParams, _condition);
_objDetectParams, _ocrParams, _areaParams, _condition);
}
void PreviewDialog::Start()
@@ -191,7 +197,7 @@ void PreviewDialog::Start()
_thread.start();
emit NeedImage(_video, _type, _patternMatchParams, _patternImageData,
_objDetectParams, _areaParams, _condition);
_objDetectParams, _ocrParams, _areaParams, _condition);
}
void PreviewDialog::DrawFrame()
@@ -238,7 +244,8 @@ void markObjects(QImage &image, std::vector<cv::Rect> &objects)
void PreviewImage::CreateImage(const VideoInput &video, PreviewType type,
const PatternMatchParameters &patternMatchParams,
const PatternImageData &patternImageData,
ObjDetectParamerts objDetectParams,
ObjDetectParameters objDetectParams,
OCRParameters ocrParams,
const AreaParamters &areaParams,
VideoCondition condition)
{
@@ -268,7 +275,8 @@ void PreviewImage::CreateImage(const VideoInput &video, PreviewType type,
}
// Will emit status label update
MarkMatch(screenshot.image, patternMatchParams,
patternImageData, objDetectParams, condition);
patternImageData, objDetectParams, ocrParams,
condition);
} else {
emit StatusUpdate("");
}
@@ -278,14 +286,16 @@ void PreviewImage::CreateImage(const VideoInput &video, PreviewType type,
void PreviewImage::MarkMatch(QImage &screenshot,
const PatternMatchParameters &patternMatchParams,
const PatternImageData &patternImageData,
ObjDetectParamerts &objDetectParams,
ObjDetectParameters &objDetectParams,
const OCRParameters &ocrParams,
VideoCondition condition)
{
if (condition == VideoCondition::PATTERN) {
cv::Mat result;
matchPattern(screenshot, patternImageData,
patternMatchParams.threshold, result,
patternMatchParams.useAlphaAsMask);
patternMatchParams.useAlphaAsMask,
patternMatchParams.matchMode);
if (countNonZero(result) == 0) {
emit StatusUpdate(obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchFail"));
@@ -309,5 +319,13 @@ void PreviewImage::MarkMatch(QImage &screenshot,
"AdvSceneSwitcher.condition.video.objectMatchSuccess"));
markObjects(screenshot, objects);
}
} else if (condition == VideoCondition::OCR) {
auto text =
runOCR(ocrParams.GetOCR(), screenshot, ocrParams.color);
QString status(obs_module_text(
"AdvSceneSwitcher.condition.video.ocrMatchSuccess"));
emit StatusUpdate(status.arg(QString::fromStdString(text)));
// TODO: show preprocessed image
}
}

View File

@@ -21,16 +21,16 @@ class PreviewImage : public QObject {
public slots:
void CreateImage(const VideoInput &, PreviewType,
const PatternMatchParameters &,
const PatternImageData &, ObjDetectParamerts,
const AreaParamters &, VideoCondition);
const PatternImageData &, ObjDetectParameters,
OCRParameters, const AreaParamters &, VideoCondition);
signals:
void ImageReady(const QPixmap &);
void StatusUpdate(const QString &);
private:
void MarkMatch(QImage &screenshot, const PatternMatchParameters &,
const PatternImageData &, ObjDetectParamerts &,
VideoCondition);
const PatternImageData &, ObjDetectParameters &,
const OCRParameters &, VideoCondition);
};
class PreviewDialog : public QDialog {
@@ -46,7 +46,8 @@ public:
public slots:
void PatternMatchParamtersChanged(const PatternMatchParameters &);
void ObjDetectParamtersChanged(const ObjDetectParamerts &);
void ObjDetectParamtersChanged(const ObjDetectParameters &);
void OCRParamtersChanged(const OCRParameters &);
void VideoSelectionChanged(const VideoInput &);
void AreaParamtersChanged(const AreaParamters &);
void ConditionChanged(int cond);
@@ -57,8 +58,8 @@ signals:
void SelectionAreaChanged(QRect area);
void NeedImage(const VideoInput &, PreviewType,
const PatternMatchParameters &, const PatternImageData &,
ObjDetectParamerts, const AreaParamters &,
VideoCondition);
ObjDetectParameters, OCRParameters,
const AreaParamters &, VideoCondition);
private:
void Start();
@@ -71,7 +72,8 @@ private:
VideoInput _video;
PatternMatchParameters _patternMatchParams;
PatternImageData _patternImageData;
ObjDetectParamerts _objDetectParams;
ObjDetectParameters _objDetectParams;
OCRParameters _ocrParams;
AreaParamters _areaParams;
VideoCondition _condition = VideoCondition::PATTERN;

View File