Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Video tempvar #935

Merged
merged 2 commits into from
Dec 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions data/locale/en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1495,6 +1495,17 @@ AdvSceneSwitcher.tempVar.process.name="Process name"
AdvSceneSwitcher.tempVar.recording.durationSeconds="Recording duration"
AdvSceneSwitcher.tempVar.recording.durationSeconds.description="Recording duration in seconds.\nThis value does not change while the recording is paused and will be reset to zero if the recording is stopped."

AdvSceneSwitcher.tempVar.video.patternCount="Pattern count"
AdvSceneSwitcher.tempVar.video.patternCount.description="The number of times the given pattern has been found in a given video input frame."
AdvSceneSwitcher.tempVar.video.objectCount="Object count"
AdvSceneSwitcher.tempVar.video.objectCount.description="The number of objects the given model has identified in a given video input frame."
AdvSceneSwitcher.tempVar.video.brightness="Average brightness"
AdvSceneSwitcher.tempVar.video.brightness.description="The average brightness in a given video input frame in a range from 0 to 1 (dark to bright)."
AdvSceneSwitcher.tempVar.video.text="OCR text"
AdvSceneSwitcher.tempVar.video.text.description="The text detected in a given video input frame."
AdvSceneSwitcher.tempVar.video.color="Average color"
AdvSceneSwitcher.tempVar.video.color.description="The average RGB color in a given video input frame in HexArgb format."

AdvSceneSwitcher.selectScene="--select scene--"
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
AdvSceneSwitcher.selectCurrentScene="Current Scene"
Expand Down
1 change: 0 additions & 1 deletion src/macro-core/macro-segment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,6 @@ void MacroSegment::InvalidateTempVarValues()
std::optional<const TempVariable>
MacroSegment::GetTempVar(const std::string &id) const
{
TempVariable *result = nullptr;
for (auto &var : _tempVariables) {
if (var.ID() == id) {
return var;
Expand Down
129 changes: 102 additions & 27 deletions src/macro-external/video/macro-condition-video.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@ bool MacroConditionVideo::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_video.Load(obj);
_condition =
static_cast<VideoCondition>(obs_data_get_int(obj, "condition"));
SetCondition(static_cast<VideoCondition>(
obs_data_get_int(obj, "condition")));
_file = obs_data_get_string(obj, "filePath");
_blockUntilScreenshotDone =
obs_data_get_bool(obj, "blockUntilScreenshotDone");
Expand Down Expand Up @@ -261,6 +261,12 @@ bool MacroConditionVideo::SetLanguage(const std::string &language)
return _ocrParameters.SetLanguageCode(language);
}

void MacroConditionVideo::SetCondition(VideoCondition condition)
{
_condition = condition;
SetupTempVars();
}

bool MacroConditionVideo::ScreenshotContainsPattern()
{
cv::Mat result;
Expand All @@ -269,9 +275,12 @@ bool MacroConditionVideo::ScreenshotContainsPattern()
_patternMatchParameters.useAlphaAsMask,
_patternMatchParameters.matchMode);
if (result.total() == 0) {
SetTempVarValue("patternCount", "0");
return false;
}
return countNonZero(result) > 0;
const auto count = countNonZero(result);
SetTempVarValue("patternCount", std::to_string(count));
return count > 0;
}

bool MacroConditionVideo::OutputChanged()
Expand Down Expand Up @@ -300,12 +309,15 @@ bool MacroConditionVideo::ScreenshotContainsObject()
_objMatchParameters.minNeighbors,
_objMatchParameters.minSize.CV(),
_objMatchParameters.maxSize.CV());
return objects.size() > 0;
const auto count = objects.size();
SetTempVarValue("objectCount", std::to_string(count));
return count > 0;
}

bool MacroConditionVideo::CheckBrightnessThreshold()
{
_currentBrightness = GetAvgBrightness(_screenshotData.image) / 255.;
SetTempVarValue("brightness", std::to_string(_currentBrightness));
return _currentBrightness > _brightnessThreshold;
}

Expand All @@ -318,6 +330,7 @@ bool MacroConditionVideo::CheckOCR()
auto text = RunOCR(_ocrParameters.GetOCR(), _screenshotData.image,
_ocrParameters.color, _ocrParameters.colorThreshold);
SetVariableValue(text);
SetTempVarValue("text", text);
if (!_ocrParameters.regex.Enabled()) {
return text == std::string(_ocrParameters.text);
}
Expand All @@ -332,10 +345,18 @@ bool MacroConditionVideo::CheckOCR()

bool MacroConditionVideo::CheckColor()
{
return ContainsPixelsInColorRange(_screenshotData.image,
_colorParameters.color,
_colorParameters.colorThreshold,
_colorParameters.matchThreshold);
const bool ret = ContainsPixelsInColorRange(
_screenshotData.image, _colorParameters.color,
_colorParameters.colorThreshold,
_colorParameters.matchThreshold);
// Way too slow for now
//SetTempVarValue("dominantColor", GetDominantColor(_screenshotData.image, 3)
// .name(QColor::HexArgb)
// .toStdString());
SetTempVarValue("color", GetAverageColor(_screenshotData.image)
.name(QColor::HexArgb)
.toStdString());
return ret;
}

bool MacroConditionVideo::Compare()
Expand Down Expand Up @@ -378,6 +399,58 @@ bool MacroConditionVideo::Compare()
return false;
}

void MacroConditionVideo::SetupTempVars()
{
MacroCondition::SetupTempVars();
switch (_condition) {
case VideoCondition::PATTERN:
AddTempvar(
"patternCount",
obs_module_text(
"AdvSceneSwitcher.tempVar.video.patternCount"),
obs_module_text(
"AdvSceneSwitcher.tempVar.video.patternCount.description"));
break;
case VideoCondition::OBJECT:
AddTempvar(
"objectCount",
obs_module_text(
"AdvSceneSwitcher.tempVar.video.objectCount"),
obs_module_text(
"AdvSceneSwitcher.tempVar.video.objectCount.description"));
break;
case VideoCondition::BRIGHTNESS:
AddTempvar(
"brightness",
obs_module_text(
"AdvSceneSwitcher.tempVar.video.brightness"),
obs_module_text(
"AdvSceneSwitcher.tempVar.video.brightness.description"));
break;
case VideoCondition::OCR:
AddTempvar(
"text",
obs_module_text("AdvSceneSwitcher.tempVar.video.text"),
obs_module_text(
"AdvSceneSwitcher.tempVar.video.text.description"));
break;
case VideoCondition::COLOR:
AddTempvar(
"color",
obs_module_text("AdvSceneSwitcher.tempVar.video.color"),
obs_module_text(
"AdvSceneSwitcher.tempVar.video.color.description"));
break;
case VideoCondition::MATCH:
case VideoCondition::DIFFER:
case VideoCondition::HAS_NOT_CHANGED:
case VideoCondition::HAS_CHANGED:
case VideoCondition::NO_IMAGE:
default:
break;
}
}

static inline void populateVideoInputSelection(QComboBox *list)
{
for (const auto &[_, name] : videoInputTypes) {
Expand Down Expand Up @@ -1155,7 +1228,7 @@ void MacroConditionVideoEdit::UpdatePreviewTooltip()
return;
}

if (!requiresFileInput(_entryData->_condition)) {
if (!requiresFileInput(_entryData->GetCondition())) {
this->setToolTip("");
return;
}
Expand Down Expand Up @@ -1224,7 +1297,7 @@ void MacroConditionVideoEdit::ConditionChanged(int cond)
}

auto lock = LockContext();
_entryData->_condition = static_cast<VideoCondition>(cond);
_entryData->SetCondition(static_cast<VideoCondition>(cond));
_entryData->ResetLastMatch();
SetWidgetVisibility();

Expand All @@ -1239,7 +1312,7 @@ void MacroConditionVideoEdit::ConditionChanged(int cond)
_previewDialog.PatternMatchParametersChanged(
_entryData->_patternMatchParameters);

if (_entryData->_condition == VideoCondition::OBJECT) {
if (_entryData->GetCondition() == VideoCondition::OBJECT) {
auto path = _entryData->GetModelDataPath();
_entryData->_objMatchParameters.cascade =
initObjectCascade(path);
Expand Down Expand Up @@ -1482,27 +1555,28 @@ void MacroConditionVideoEdit::SetWidgetVisibility()
_sources->setVisible(_entryData->_video.type ==
VideoInput::Type::SOURCE);
_scenes->setVisible(_entryData->_video.type == VideoInput::Type::SCENE);
_imagePath->setVisible(requiresFileInput(_entryData->_condition));
_imagePath->setVisible(requiresFileInput(_entryData->GetCondition()));
_usePatternForChangedCheck->setVisible(
patternControlIsOptional(_entryData->_condition));
_patternThreshold->setVisible(needsThreshold(_entryData->_condition));
_useAlphaAsMask->setVisible(_entryData->_condition ==
patternControlIsOptional(_entryData->GetCondition()));
_patternThreshold->setVisible(
needsThreshold(_entryData->GetCondition()));
_useAlphaAsMask->setVisible(_entryData->GetCondition() ==
VideoCondition::PATTERN);
SetLayoutVisible(_patternMatchModeLayout,
_entryData->_condition == VideoCondition::PATTERN);
_brightness->setVisible(_entryData->_condition ==
_entryData->GetCondition() == VideoCondition::PATTERN);
_brightness->setVisible(_entryData->GetCondition() ==
VideoCondition::BRIGHTNESS);
_showMatch->setVisible(needsShowMatch(_entryData->_condition));
_ocr->setVisible(_entryData->_condition == VideoCondition::OCR);
_objectDetect->setVisible(_entryData->_condition ==
_showMatch->setVisible(needsShowMatch(_entryData->GetCondition()));
_ocr->setVisible(_entryData->GetCondition() == VideoCondition::OCR);
_objectDetect->setVisible(_entryData->GetCondition() ==
VideoCondition::OBJECT);
_color->setVisible(_entryData->_condition == VideoCondition::COLOR);
_color->setVisible(_entryData->GetCondition() == VideoCondition::COLOR);
SetLayoutVisible(_throttleControlLayout,
needsThrottleControls(_entryData->_condition));
_area->setVisible(needsAreaControls(_entryData->_condition));
needsThrottleControls(_entryData->GetCondition()));
_area->setVisible(needsAreaControls(_entryData->GetCondition()));

if (_entryData->_condition == VideoCondition::HAS_CHANGED ||
_entryData->_condition == VideoCondition::HAS_NOT_CHANGED) {
if (_entryData->GetCondition() == VideoCondition::HAS_CHANGED ||
_entryData->GetCondition() == VideoCondition::HAS_NOT_CHANGED) {
_patternThreshold->setVisible(
_entryData->_patternMatchParameters.useForChangedCheck);
SetLayoutVisible(
Expand All @@ -1528,7 +1602,7 @@ void MacroConditionVideoEdit::SetupPreviewDialogParams()
_previewDialog.VideoSelectionChanged(_entryData->_video);
_previewDialog.AreaParametersChanged(_entryData->_areaParameters);
_previewDialog.ConditionChanged(
static_cast<int>(_entryData->_condition));
static_cast<int>(_entryData->GetCondition()));
}

void MacroConditionVideoEdit::UpdateEntryData()
Expand All @@ -1541,7 +1615,8 @@ void MacroConditionVideoEdit::UpdateEntryData()
static_cast<int>(_entryData->_video.type));
_scenes->SetScene(_entryData->_video.scene);
_sources->SetSource(_entryData->_video.source);
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
_condition->setCurrentIndex(
static_cast<int>(_entryData->GetCondition()));
_reduceLatency->setChecked(_entryData->_blockUntilScreenshotDone);
_imagePath->SetPath(QString::fromStdString(_entryData->_file));
_usePatternForChangedCheck->setChecked(
Expand Down
8 changes: 7 additions & 1 deletion src/macro-external/video/macro-condition-video.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ class MacroConditionVideo : public MacroCondition {
void SetPageSegMode(tesseract::PageSegMode);
bool SetLanguage(const std::string &);

void SetCondition(VideoCondition);
VideoCondition GetCondition() const { return _condition; }

VideoInput _video;
VideoCondition _condition = VideoCondition::MATCH;
std::string _file = obs_module_text("AdvSceneSwitcher.enterPath");
// Enabling this will reduce matching latency, but slow down the
// the condition checks of all macros overall.
Expand Down Expand Up @@ -74,6 +76,10 @@ class MacroConditionVideo : public MacroCondition {
bool Compare();
bool CheckShouldBeSkipped();

void SetupTempVars();

VideoCondition _condition = VideoCondition::MATCH;

bool _getNextScreenshot = true;
ScreenshotHelper _screenshotData;
QImage _matchImage;
Expand Down
55 changes: 55 additions & 0 deletions src/macro-external/video/opencv-helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,61 @@ bool ContainsPixelsInColorRange(const QImage &image, const QColor &color,
return matchPercentage >= totalPixelMatchThreshold;
}

QColor GetAverageColor(const QImage &img)
{
if (img.isNull()) {
return QColor();
}

auto image = QImageToMat(img);
cv::Scalar meanColor = cv::mean(image);
int averageBlue = cvRound(meanColor[0]);
int averageGreen = cvRound(meanColor[1]);
int averageRed = cvRound(meanColor[2]);

return QColor(averageRed, averageGreen, averageBlue);
}

QColor GetDominantColor(const QImage &img, int k)
{
if (img.isNull()) {
return QColor();
}

auto image = QImageToMat(img);
cv::Mat reshapedImage = image.reshape(1, image.rows * image.cols);
reshapedImage.convertTo(reshapedImage, CV_32F);

cv::mean(reshapedImage);

// Apply k-means clustering to group similar colors
cv::TermCriteria criteria(
cv::TermCriteria::EPS + cv::TermCriteria::MAX_ITER, 100, 0.2);
cv::Mat labels, centers;
cv::kmeans(reshapedImage, k, labels, criteria, 1,
cv::KMEANS_RANDOM_CENTERS, centers);

// Find the dominant color
// Center of the cluster with the largest number of pixels
cv::Mat counts = cv::Mat::zeros(1, k, CV_32SC1);
for (int i = 0; i < labels.rows; i++) {
counts.at<int>(0, labels.at<int>(i))++;
}

cv::Point max_loc;
cv::minMaxLoc(counts, nullptr, nullptr, nullptr, &max_loc);
try {
cv::Scalar dominantColor = centers.at<cv::Scalar>(max_loc.y);
const int blue = cv::saturate_cast<int>(dominantColor.val[0]);
const int green = cv::saturate_cast<int>(dominantColor.val[1]);
const int red = cv::saturate_cast<int>(dominantColor.val[2]);
const int alpha = cv::saturate_cast<int>(dominantColor.val[3]);
return QColor(red, green, blue, alpha);
} catch (...) {
}
return QColor();
}

// Assumption is that QImage uses Format_RGBA8888.
// Conversion from: https://github.com/dbzhang800/QtOpenCV
cv::Mat QImageToMat(const QImage &img)
Expand Down
2 changes: 2 additions & 0 deletions src/macro-external/video/opencv-helpers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ std::string RunOCR(tesseract::TessBaseAPI *, const QImage &, const QColor &,
bool ContainsPixelsInColorRange(const QImage &image, const QColor &color,
double colorDeviationThreshold,
double totalPixelMatchThreshold);
QColor GetAverageColor(const QImage &img);
QColor GetDominantColor(const QImage &image, int k);
cv::Mat QImageToMat(const QImage &img);
QImage MatToQImage(const cv::Mat &mat);

Expand Down
Loading