mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-08-15 22:04:17 -05:00
Add color matching
This commit is contained in:
@@ -204,6 +204,7 @@ 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.condition.color="matches color"
|
||||
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"
|
||||
@@ -245,6 +246,10 @@ 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.colorMatchThreshold="Match threshold:"
|
||||
AdvSceneSwitcher.condition.video.colorMatchThresholdDescription="How much of the image has to match the given color?\nA value of 1 requires every pixel of the input image to match the given color."
|
||||
AdvSceneSwitcher.condition.video.colorDeviationThreshold="Color deviation:"
|
||||
AdvSceneSwitcher.condition.video.colorDeviationThresholdDescription="How similar can the clolor be to the provided input color for it to still count as a match?\nA value of 0 requires a perfect color match, while a higher value includes similar colors also."
|
||||
AdvSceneSwitcher.condition.video.type.main="OBS's main output"
|
||||
AdvSceneSwitcher.condition.video.type.source="Source"
|
||||
AdvSceneSwitcher.condition.video.type.scene="Scene"
|
||||
@@ -256,6 +261,7 @@ AdvSceneSwitcher.condition.video.entry.checkAreaEnable="Perform check only in ar
|
||||
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.entry.color="Check for color:{{color}}{{selectColor}}"
|
||||
AdvSceneSwitcher.condition.video.minSize="Minimum size:"
|
||||
AdvSceneSwitcher.condition.video.maxSize="Maximum size:"
|
||||
AdvSceneSwitcher.condition.video.selectArea="Select area"
|
||||
|
||||
@@ -40,6 +40,8 @@ const static std::map<VideoCondition, std::string> conditionTypes = {
|
||||
#ifdef OCR_SUPPORT
|
||||
{VideoCondition::OCR, "AdvSceneSwitcher.condition.video.condition.ocr"},
|
||||
#endif
|
||||
{VideoCondition::COLOR,
|
||||
"AdvSceneSwitcher.condition.video.condition.color"},
|
||||
};
|
||||
|
||||
const static std::map<VideoInput::Type, std::string> videoInputTypes = {
|
||||
@@ -162,6 +164,7 @@ bool MacroConditionVideo::Save(obs_data_t *obj) const
|
||||
_patternMatchParameters.Save(obj);
|
||||
_objMatchParameters.Save(obj);
|
||||
_ocrParameters.Save(obj);
|
||||
_colorParameters.Save(obj);
|
||||
obs_data_set_bool(obj, "throttleEnabled", _throttleEnabled);
|
||||
obs_data_set_int(obj, "throttleCount", _throttleCount);
|
||||
_areaParameters.Save(obj);
|
||||
@@ -186,6 +189,7 @@ bool MacroConditionVideo::Load(obs_data_t *obj)
|
||||
_patternMatchParameters.Load(obj);
|
||||
_objMatchParameters.Load(obj);
|
||||
_ocrParameters.Load(obj);
|
||||
_colorParameters.Load(obj);
|
||||
_throttleEnabled = obs_data_get_bool(obj, "throttleEnabled");
|
||||
_throttleCount = obs_data_get_int(obj, "throttleCount");
|
||||
_areaParameters.Load(obj);
|
||||
@@ -314,6 +318,14 @@ bool MacroConditionVideo::CheckOCR()
|
||||
return text == std::string(_ocrParameters.text);
|
||||
}
|
||||
|
||||
bool MacroConditionVideo::CheckColor()
|
||||
{
|
||||
return ContainsPixelsInColorRange(_screenshotData.image,
|
||||
_colorParameters.color,
|
||||
_colorParameters.colorThreshold,
|
||||
_colorParameters.matchThreshold);
|
||||
}
|
||||
|
||||
bool MacroConditionVideo::Compare()
|
||||
{
|
||||
if (_areaParameters.enable && _condition != VideoCondition::NO_IMAGE) {
|
||||
@@ -346,6 +358,8 @@ bool MacroConditionVideo::Compare()
|
||||
return CheckBrightnessThreshold();
|
||||
case VideoCondition::OCR:
|
||||
return CheckOCR();
|
||||
case VideoCondition::COLOR:
|
||||
return CheckColor();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -714,6 +728,111 @@ void ObjectDetectEdit::ModelPathChanged(const QString &text)
|
||||
_previewDialog->ObjDetectParametersChanged(_data->_objMatchParameters);
|
||||
}
|
||||
|
||||
ColorEdit::ColorEdit(QWidget *parent,
|
||||
const std::shared_ptr<MacroConditionVideo> &data)
|
||||
: QWidget(parent),
|
||||
_matchThreshold(new SliderSpinBox(
|
||||
0., 1.,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.colorMatchThreshold"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.colorMatchThresholdDescription"),
|
||||
true)),
|
||||
_colorThreshold(new SliderSpinBox(
|
||||
0., 1.,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.colorDeviationThreshold"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.colorDeviationThresholdDescription"),
|
||||
true)),
|
||||
_color(new QLabel),
|
||||
_selectColor(new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.selectColor"))),
|
||||
_data(data)
|
||||
{
|
||||
QWidget::connect(_selectColor, SIGNAL(clicked()), this,
|
||||
SLOT(SelectColorClicked()));
|
||||
QWidget::connect(
|
||||
_matchThreshold,
|
||||
SIGNAL(DoubleValueChanged(const NumberVariable<double> &)),
|
||||
this,
|
||||
SLOT(MatchThresholdChanged(const NumberVariable<double> &)));
|
||||
QWidget::connect(
|
||||
_colorThreshold,
|
||||
SIGNAL(DoubleValueChanged(const NumberVariable<double> &)),
|
||||
this,
|
||||
SLOT(ColorThresholdChanged(const NumberVariable<double> &)));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{color}}", _color},
|
||||
{"{{selectColor}}", _selectColor},
|
||||
};
|
||||
|
||||
auto colorLayout = new QHBoxLayout;
|
||||
PlaceWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.condition.video.entry.color"),
|
||||
colorLayout, widgetPlaceholders);
|
||||
|
||||
auto layout = new QVBoxLayout;
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->addLayout(colorLayout);
|
||||
layout->addWidget(_colorThreshold);
|
||||
layout->addWidget(_matchThreshold);
|
||||
setLayout(layout);
|
||||
|
||||
_matchThreshold->SetDoubleValue(_data->_colorParameters.matchThreshold);
|
||||
_colorThreshold->SetDoubleValue(_data->_colorParameters.colorThreshold);
|
||||
SetupColorLabel(_data->_colorParameters.color);
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void ColorEdit::SetupColorLabel(const QColor &color)
|
||||
{
|
||||
_color->setText(color.name());
|
||||
_color->setPalette(QPalette(color));
|
||||
_color->setAutoFillBackground(true);
|
||||
}
|
||||
|
||||
void ColorEdit::MatchThresholdChanged(const DoubleVariable &value)
|
||||
{
|
||||
if (_loading || !_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_data->_colorParameters.matchThreshold = value;
|
||||
}
|
||||
|
||||
void ColorEdit::ColorThresholdChanged(const DoubleVariable &value)
|
||||
{
|
||||
if (_loading || !_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = LockContext();
|
||||
_data->_colorParameters.colorThreshold = value;
|
||||
}
|
||||
|
||||
void ColorEdit::SelectColorClicked()
|
||||
{
|
||||
if (_loading || !_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QColor color = QColorDialog::getColor(
|
||||
_data->_colorParameters.color, this,
|
||||
obs_module_text("AdvSceneSwitcher.condition.video.selectColor"),
|
||||
QColorDialog::ColorDialogOption());
|
||||
|
||||
if (!color.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetupColorLabel(color);
|
||||
auto lock = LockContext();
|
||||
_data->_colorParameters.color = color;
|
||||
}
|
||||
|
||||
AreaEdit::AreaEdit(QWidget *parent, PreviewDialog *previewDialog,
|
||||
const std::shared_ptr<MacroConditionVideo> &data)
|
||||
: QWidget(parent),
|
||||
@@ -831,6 +950,7 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
|
||||
_brightness(new BrightnessEdit(this, entryData)),
|
||||
_ocr(new OCREdit(this, &_previewDialog, entryData)),
|
||||
_objectDetect(new ObjectDetectEdit(this, &_previewDialog, entryData)),
|
||||
_color(new ColorEdit(this, entryData)),
|
||||
_area(new AreaEdit(this, &_previewDialog, entryData)),
|
||||
_throttleControlLayout(new QHBoxLayout),
|
||||
_throttleEnable(new QCheckBox()),
|
||||
@@ -855,6 +975,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
|
||||
QSizePolicy::Preferred);
|
||||
_objectDetect->setSizePolicy(QSizePolicy::MinimumExpanding,
|
||||
QSizePolicy::Preferred);
|
||||
_color->setSizePolicy(QSizePolicy::MinimumExpanding,
|
||||
QSizePolicy::Preferred);
|
||||
_area->setSizePolicy(QSizePolicy::MinimumExpanding,
|
||||
QSizePolicy::Preferred);
|
||||
|
||||
@@ -943,6 +1065,7 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
|
||||
mainLayout->addWidget(_brightness);
|
||||
mainLayout->addWidget(_ocr);
|
||||
mainLayout->addWidget(_objectDetect);
|
||||
mainLayout->addWidget(_color);
|
||||
mainLayout->addLayout(_throttleControlLayout);
|
||||
mainLayout->addWidget(_area);
|
||||
mainLayout->addWidget(_reduceLatency);
|
||||
@@ -1299,6 +1422,7 @@ void MacroConditionVideoEdit::SetWidgetVisibility()
|
||||
_ocr->setVisible(_entryData->_condition == VideoCondition::OCR);
|
||||
_objectDetect->setVisible(_entryData->_condition ==
|
||||
VideoCondition::OBJECT);
|
||||
_color->setVisible(_entryData->_condition == VideoCondition::COLOR);
|
||||
SetLayoutVisible(_throttleControlLayout,
|
||||
needsThrottleControls(_entryData->_condition));
|
||||
_area->setVisible(needsAreaControls(_entryData->_condition));
|
||||
|
||||
@@ -57,6 +57,7 @@ public:
|
||||
PatternMatchParameters _patternMatchParameters;
|
||||
ObjDetectParameters _objMatchParameters;
|
||||
OCRParameters _ocrParameters;
|
||||
ColorParameters _colorParameters;
|
||||
AreaParameters _areaParameters;
|
||||
bool _throttleEnabled = false;
|
||||
int _throttleCount = 3;
|
||||
@@ -67,6 +68,7 @@ private:
|
||||
bool ScreenshotContainsObject();
|
||||
bool CheckBrightnessThreshold();
|
||||
bool CheckOCR();
|
||||
bool CheckColor();
|
||||
bool Compare();
|
||||
bool CheckShouldBeSkipped();
|
||||
|
||||
@@ -160,6 +162,30 @@ private:
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
class ColorEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ColorEdit(QWidget *parent,
|
||||
const std::shared_ptr<MacroConditionVideo> &);
|
||||
|
||||
private slots:
|
||||
void SelectColorClicked();
|
||||
void MatchThresholdChanged(const NumberVariable<double> &);
|
||||
void ColorThresholdChanged(const NumberVariable<double> &);
|
||||
|
||||
private:
|
||||
void SetupColorLabel(const QColor &);
|
||||
|
||||
SliderSpinBox *_matchThreshold;
|
||||
SliderSpinBox *_colorThreshold;
|
||||
QLabel *_color;
|
||||
QPushButton *_selectColor;
|
||||
|
||||
std::shared_ptr<MacroConditionVideo> _data;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
class AreaEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
@@ -258,6 +284,7 @@ private:
|
||||
BrightnessEdit *_brightness;
|
||||
OCREdit *_ocr;
|
||||
ObjectDetectEdit *_objectDetect;
|
||||
ColorEdit *_color;
|
||||
AreaEdit *_area;
|
||||
|
||||
QHBoxLayout *_throttleControlLayout;
|
||||
|
||||
@@ -174,6 +174,37 @@ std::string RunOCR(tesseract::TessBaseAPI *ocr, const QImage &image,
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ContainsPixelsInColorRange(const QImage &image, const QColor &color,
|
||||
double colorDeviationThreshold,
|
||||
double totalPixelMatchThreshold)
|
||||
{
|
||||
int totalPixels = image.width() * image.height();
|
||||
int matchingPixels = 0;
|
||||
int maxColorDiff = static_cast<int>(colorDeviationThreshold * 255.0);
|
||||
|
||||
for (int y = 0; y < image.height(); y++) {
|
||||
for (int x = 0; x < image.width(); x++) {
|
||||
const auto pixelColor = image.pixelColor(x, y);
|
||||
const int diffRed =
|
||||
std::abs(pixelColor.red() - color.red());
|
||||
const int diffGreen =
|
||||
std::abs(pixelColor.green() - color.green());
|
||||
const int diffBlue =
|
||||
std::abs(pixelColor.blue() - color.blue());
|
||||
|
||||
if (diffRed <= maxColorDiff &&
|
||||
diffGreen <= maxColorDiff &&
|
||||
diffBlue <= maxColorDiff) {
|
||||
matchingPixels++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double matchPercentage =
|
||||
static_cast<double>(matchingPixels) / totalPixels;
|
||||
return matchPercentage >= totalPixelMatchThreshold;
|
||||
}
|
||||
|
||||
// Assumption is that QImage uses Format_RGBA8888.
|
||||
// Conversion from: https://github.com/dbzhang800/QtOpenCV
|
||||
cv::Mat QImageToMat(const QImage &img)
|
||||
|
||||
@@ -61,6 +61,9 @@ std::vector<cv::Rect> MatchObject(QImage &img, cv::CascadeClassifier &cascade,
|
||||
uchar GetAvgBrightness(QImage &img);
|
||||
cv::Mat PreprocessForOCR(const QImage &image, const QColor &color);
|
||||
std::string RunOCR(tesseract::TessBaseAPI *, const QImage &, const QColor &);
|
||||
bool ContainsPixelsInColorRange(const QImage &image, const QColor &color,
|
||||
double colorDeviationThreshold,
|
||||
double totalPixelMatchThreshold);
|
||||
cv::Mat QImageToMat(const QImage &img);
|
||||
QImage MatToQImage(const cv::Mat &mat);
|
||||
|
||||
|
||||
@@ -315,4 +315,25 @@ void OCRParameters::Setup()
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
bool ColorParameters::Save(obs_data_t *obj) const
|
||||
{
|
||||
auto data = obs_data_create();
|
||||
SaveColor(data, "color", color);
|
||||
colorThreshold.Save(data, "colorThreshold");
|
||||
matchThreshold.Save(data, "matchThreshold");
|
||||
obs_data_set_obj(obj, "colorData", data);
|
||||
obs_data_release(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ColorParameters::Load(obs_data_t *obj)
|
||||
{
|
||||
auto data = obs_data_get_obj(obj, "colorData");
|
||||
color = LoadColor(data, "color");
|
||||
colorThreshold.Load(data, "colorThreshold");
|
||||
matchThreshold.Load(data, "matchThreshold");
|
||||
obs_data_release(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace advss
|
||||
|
||||
@@ -28,6 +28,7 @@ enum class VideoCondition {
|
||||
OBJECT,
|
||||
BRIGHTNESS,
|
||||
OCR,
|
||||
COLOR,
|
||||
};
|
||||
|
||||
class VideoInput {
|
||||
@@ -104,6 +105,16 @@ private:
|
||||
bool initDone = false;
|
||||
};
|
||||
|
||||
class ColorParameters {
|
||||
public:
|
||||
bool Save(obs_data_t *obj) const;
|
||||
bool Load(obs_data_t *obj);
|
||||
|
||||
QColor color = Qt::black;
|
||||
DoubleVariable colorThreshold = 0.1;
|
||||
DoubleVariable matchThreshold = 0.8;
|
||||
};
|
||||
|
||||
class AreaParameters {
|
||||
public:
|
||||
bool Save(obs_data_t *obj) const;
|
||||
|
||||
Reference in New Issue
Block a user