Add color matching

This commit is contained in:
WarmUpTill
2023-05-18 11:22:59 +02:00
committed by WarmUpTill
parent 2eca2cc2e9
commit 339418b5f3
7 changed files with 223 additions and 0 deletions

View File

@@ -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"

View File

@@ -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));

View File

@@ -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;

View File

@@ -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)

View File

@@ -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);

View File

@@ -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

View File

@@ -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;