Dominant Color support (#1569)

* Dominant Color and Mean Color Squared

* Workaround - Suppress empty LED updates

* Add missing text

* Dominant Colors advanced

* Test with fixed initial colors

* Test with fixed initial colors

* Support new processing values via API

* ImageToLED - Add reduced pixel processing, make dominant color advanced configurable

* Updates on Grabber fps setting

* ImageToLedMap - Remove maptype and update test

* Update dynamic cluster array allocation
This commit is contained in:
LordGrey 2023-02-17 16:02:51 +01:00 committed by GitHub
parent 093361d3e4
commit 1ae37d151e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1048 additions and 155 deletions

View File

@ -253,6 +253,8 @@
"edt_conf_bb_unknownFrameCnt_title": "Unknown frames",
"edt_conf_bge_heading_title": "Background Effect/Color",
"edt_conf_bobls_heading_title": "Boblight Server",
"edt_conf_color_accuracyLevel_expl": "Level how accurate dominat colors are evaluated. A higher level creates more accurate results, but also requries more processing power. Should to be combined with reduced pixel processing.",
"edt_conf_color_accuracyLevel_title": "Accuracy level",
"edt_conf_color_backlightColored_expl": "Add some color to your backlight.",
"edt_conf_color_backlightColored_title": "Colored backlight",
"edt_conf_color_backlightThreshold_expl": "The minimum amount of brightness (backlight). Disabled during effects, colors and in status \"Off\"",
@ -281,7 +283,7 @@
"edt_conf_color_heading_title": "Color Calibration",
"edt_conf_color_id_expl": "User given name",
"edt_conf_color_id_title": "ID",
"edt_conf_color_imageToLedMappingType_expl": "Overwrites the LED area assignment of your LED layout if it's not \"multicolor\"",
"edt_conf_color_imageToLedMappingType_expl": "Overwrites the LED area assignment of your LED layout if it's not \"Mean Color Simple\"",
"edt_conf_color_imageToLedMappingType_title": "LED area assignment",
"edt_conf_color_leds_expl": "Assign this adjustment to all LEDs (*) or just some (0-24).",
"edt_conf_color_leds_title": "LED index",
@ -293,6 +295,8 @@
"edt_conf_color_saturationGain_title": "Saturation gain",
"edt_conf_color_brightnessGain_expl": "Adjusts the brightness of colors. 1.0 means no change, over 1.0 increases brightness, under 1.0 decreases brightness.",
"edt_conf_color_brightnessGain_title": "Brightness gain",
"edt_conf_color_reducedPixelSetFactorFactor_expl": "Evaluate only a set of pixels per LED area defined, Low ~25%, Medium ~10%, High ~6%",
"edt_conf_color_reducedPixelSetFactorFactor_title": "Reduced pixel processing",
"edt_conf_color_white_expl": "The calibrated white value.",
"edt_conf_color_white_title": "White",
"edt_conf_color_yellow_expl": "The calibrated yellow value.",
@ -322,6 +326,8 @@
"edt_conf_enum_color": "Color",
"edt_conf_enum_custom": "Custom",
"edt_conf_enum_decay": "Decay",
"edt_conf_enum_delay": "Delay only",
"edt_conf_enum_disabled": "Disabled",
"edt_conf_enum_dl_error": "Error",
"edt_conf_enum_dl_informational": "Informational",
"edt_conf_enum_dl_nodebug": "No Debug output",
@ -330,9 +336,12 @@
"edt_conf_enum_dl_verbose1": "Verbosity level 1",
"edt_conf_enum_dl_verbose2": "Verbosity level 2",
"edt_conf_enum_dl_verbose3": "Verbosity level 3",
"edt_conf_enum_dominant_color": "Dominant Color - per LED",
"edt_conf_enum_dominant_color_advanced": "Dominant Color Advanced - per LED",
"edt_conf_enum_effect": "Effect",
"edt_conf_enum_gbr": "GBR",
"edt_conf_enum_grb": "GRB",
"edt_conf_enum_high": "High",
"edt_conf_enum_hsv": "HSV",
"edt_conf_enum_left_right": "Left to right",
"edt_conf_enum_linear": "Linear",
@ -340,7 +349,10 @@
"edt_conf_enum_logsilent": "Silent",
"edt_conf_enum_logverbose": "Verbose",
"edt_conf_enum_logwarn": "Warning",
"edt_conf_enum_multicolor_mean": "Multicolor",
"edt_conf_enum_low": "Low",
"edt_conf_enum_medium": "Medium",
"edt_conf_enum_multicolor_mean": "Mean Color Simple - per LED",
"edt_conf_enum_multicolor_mean_squared": "Mean Color Squared - per LED",
"edt_conf_enum_please_select": "Please Select",
"edt_conf_enum_rbg": "RBG",
"edt_conf_enum_rgb": "RGB",
@ -350,7 +362,7 @@
"edt_conf_enum_transeffect_sudden": "Sudden",
"edt_conf_enum_udp_ddp": "DDP",
"edt_conf_enum_udp_raw": "RAW",
"edt_conf_enum_unicolor_mean": "Unicolor",
"edt_conf_enum_unicolor_mean": "Mean Color Image - applied to all LEDs",
"edt_conf_fbs_heading_title": "Flatbuffers Server",
"edt_conf_fbs_timeout_expl": "If no data is received for the given period, the component will be (soft) disabled.",
"edt_conf_fbs_timeout_title": "Timeout",
@ -974,8 +986,11 @@
"remote_losthint": "Note: All changes will be lost after a restart.",
"remote_maptype_intro": "Usually the LED layout defines which LED covers a specific picture area. You can change it here: $1.",
"remote_maptype_label": "Mapping type",
"remote_maptype_label_multicolor_mean": "Multicolor",
"remote_maptype_label_unicolor_mean": "Unicolor",
"remote_maptype_label_dominant_color": "Dominant Color",
"remote_maptype_label_dominant_color_advanced": "Dominant Color Advanced",
"remote_maptype_label_multicolor_mean": "Mean Color Simple",
"remote_maptype_label_multicolor_mean_squared": "Mean Color Squared",
"remote_maptype_label_unicolor_mean": "Mean Color Image",
"remote_optgroup_syseffets": "System Effects",
"remote_optgroup_templates_custom": "User Templates",
"remote_optgroup_templates_system": "System Templates",

View File

@ -147,10 +147,7 @@ private slots:
void handleSourceRequest(hyperion::Components component, int hyperionInd, bool listen);
///
/// @brief Update Update capture rate
/// @param type interval between frames in milliseconds
///
void updateTimer(int interval);
protected:
@ -168,6 +165,11 @@ protected:
///
virtual bool close() { return true; }
/// @brief Update Update capture rate
/// @param type interval between frames in milliseconds
///
void updateTimer(int interval);
QString _grabberName;

View File

@ -1,6 +1,7 @@
#pragma once
#include <QString>
#include <QSharedPointer>
// Utils includes
#include <utils/Image.h>
@ -46,7 +47,7 @@ public:
/// @param[in] width The new width of the buffer-image
/// @param[in] height The new height of the buffer-image
///
void setSize(unsigned width, unsigned height);
void setSize(int width, int height);
///
/// @brief Update the led string (eg on settings change)
@ -56,6 +57,19 @@ public:
/// Returns state of black border detector
bool blackBorderDetectorEnabled() const;
///
/// Factor to reduce the number of pixels evaluated during processing
///
/// @param[in] count Use every "count" pixel
void setReducedPixelSetFactorFactor(int count);
///
/// Set the accuracy used during processing
/// (only for selected types)
///
/// @param[in] level The accuracy level (0-4)
void setAccuracyLevel(int level);
/// Returns the current _userMappingType, this may not be the current applied type!
int getUserLedMappingType() const { return _userMappingType; }
@ -98,30 +112,45 @@ public:
}
///
/// Processes the image to a list of led colors. This will update the size of the buffer-image
/// if required and call the image-to-leds mapping to determine the mean color per led.
/// Processes the image to a list of LED colors. This will update the size of the buffer-image
/// if required and call the image-to-LEDs mapping to determine the color per LED.
///
/// @param[in] image The image to translate to led values
/// @param[in] image The image to translate to LED values
///
/// @return The color value per led
/// @return The color value per LED
///
template <typename Pixel_T>
std::vector<ColorRgb> process(const Image<Pixel_T>& image)
{
std::vector<ColorRgb> colors;
if (image.width()>0 && image.height()>0)
{
// Ensure that the buffer-image is the proper size
setSize(image);
assert(!_imageToLedColors.isNull());
// Check black border detection
verifyBorder(image);
// Create a result vector and call the 'in place' function
switch (_mappingType)
{
case 1: colors = _imageToLeds->getUniLedColor(image); break;
default: colors = _imageToLeds->getMeanLedColor(image);
case 1:
colors = _imageToLedColors->getUniLedColor(image);
break;
case 2:
colors = _imageToLedColors->getMeanLedColorSqrt(image);
break;
case 3:
colors = _imageToLedColors->getDominantLedColor(image);
break;
case 4:
colors = _imageToLedColors->getDominantLedColorAdv(image);
break;
default:
colors = _imageToLedColors->getMeanLedColor(image);
}
}
else
@ -136,8 +165,8 @@ public:
///
/// Determines the led colors of the image in the buffer.
///
/// @param[in] image The image to translate to led values
/// @param[out] ledColors The color value per led
/// @param[in] image The image to translate to LED values
/// @param[out] ledColors The color value per LED
///
template <typename Pixel_T>
void process(const Image<Pixel_T>& image, std::vector<ColorRgb>& ledColors)
@ -153,8 +182,20 @@ public:
// Determine the mean or uni colors of each led (using the existing mapping)
switch (_mappingType)
{
case 1: _imageToLeds->getUniLedColor(image, ledColors); break;
default: _imageToLeds->getMeanLedColor(image, ledColors);
case 1:
_imageToLedColors->getUniLedColor(image, ledColors);
break;
case 2:
_imageToLedColors->getMeanLedColorSqrt(image, ledColors);
break;
case 3:
_imageToLedColors->getDominantLedColor(image, ledColors);
break;
case 4:
_imageToLedColors->getDominantLedColorAdv(image, ledColors);
break;
default:
_imageToLedColors->getMeanLedColor(image, ledColors);
}
}
else
@ -164,9 +205,9 @@ public:
}
///
/// Get the hscan and vscan parameters for a single led
/// Get the hscan and vscan parameters for a single LED
///
/// @param[in] led Index of the led
/// @param[in] led Index of the LED
/// @param[out] hscanBegin begin of the hscan
/// @param[out] hscanEnd end of the hscan
/// @param[out] vscanBegin begin of the hscan
@ -175,6 +216,13 @@ public:
bool getScanParameters(size_t led, double & hscanBegin, double & hscanEnd, double & vscanBegin, double & vscanEnd) const;
private:
void registerProcessingUnit(
int width,
int height,
int horizontalBorder,
int verticalBorder);
///
/// Performs black-border detection (if enabled) on the given image
///
@ -183,34 +231,25 @@ private:
template <typename Pixel_T>
void verifyBorder(const Image<Pixel_T> & image)
{
if (!_borderProcessor->enabled() && ( _imageToLeds->horizontalBorder()!=0 || _imageToLeds->verticalBorder()!=0 ))
if (!_borderProcessor->enabled() && ( _imageToLedColors->horizontalBorder()!=0 || _imageToLedColors->verticalBorder()!=0 ))
{
Debug(_log, "Reset border");
_borderProcessor->process(image);
delete _imageToLeds;
_imageToLeds = new hyperion::ImageToLedsMap(image.width(), image.height(), 0, 0, _ledString.leds());
registerProcessingUnit(image.width(), image.height(), 0, 0);
}
if(_borderProcessor->enabled() && _borderProcessor->process(image))
{
const hyperion::BlackBorder border = _borderProcessor->getCurrentBorder();
// Clean up the old mapping
delete _imageToLeds;
if (border.unknown)
{
// Construct a new buffer and mapping
_imageToLeds = new hyperion::ImageToLedsMap(image.width(), image.height(), 0, 0, _ledString.leds());
registerProcessingUnit(image.width(), image.height(), 0, 0);
}
else
{
// Construct a new buffer and mapping
_imageToLeds = new hyperion::ImageToLedsMap(image.width(), image.height(), border.horizontalSize, border.verticalSize, _ledString.leds());
registerProcessingUnit(image.width(), image.height(), border.horizontalSize, border.verticalSize);
}
//Debug(Logger::getInstance("BLACKBORDER"), "CURRENT BORDER TYPE: unknown=%d hor.size=%d vert.size=%d",
// border.unknown, border.horizontalSize, border.verticalSize );
}
}
@ -218,6 +257,7 @@ private slots:
void handleSettingsUpdate(settings::type type, const QJsonDocument& config);
private:
Logger * _log;
/// The Led-string specification
LedString _ledString;
@ -226,15 +266,18 @@ private:
hyperion::BlackBorderProcessor * _borderProcessor;
/// The mapping of image-pixels to LEDs
hyperion::ImageToLedsMap* _imageToLeds;
QSharedPointer<hyperion::ImageToLedsMap> _imageToLedColors;
/// Type of image 2 led mapping
/// Type of image to LED mapping
int _mappingType;
/// Type of last requested user type
int _userMappingType;
/// Type of last requested hard type
int _hardMappingType;
int _accuraryLevel;
int _reducedPixelSetFactorFactor;
/// Hyperion instance pointer
Hyperion* _hyperion;
};

View File

@ -1,72 +1,90 @@
#pragma once
#ifndef IMAGETOLEDSMAP_H
#define IMAGETOLEDSMAP_H
// STL includes
#include <cassert>
#include <memory>
#include <sstream>
#include <cmath>
// hyperion-utils includes
#include <utils/Image.h>
#include <utils/Logger.h>
#include <utils/ColorRgbScalar.h>
#include <utils/ColorSys.h>
// hyperion includes
#include <hyperion/LedString.h>
namespace hyperion
{
///
/// The ImageToLedsMap holds a mapping of indices into an image to leds. It can be used to
/// calculate the average (or mean) color per led for a specific region.
/// The ImageToLedsMap holds a mapping of indices into an image to LEDs. It can be used to
/// calculate the average (aka mean) or dominant color per LED for a given region.
///
class ImageToLedsMap
class ImageToLedsMap : public QObject
{
Q_OBJECT
public:
///
/// Constructs an mapping from the absolute indices in an image to each led based on the border
/// definition given in the list of leds. The map holds absolute indices to any given image,
/// Constructs an mapping from the absolute indices in an image to each LED based on the border
/// definition given in the list of LEDs. The map holds absolute indices to any given image,
/// provided that it is row-oriented.
/// The mapping is created purely on size (width and height). The given borders are excluded
/// from indexing.
///
/// @param[in] log Logger
/// @param[in] width The width of the indexed image
/// @param[in] height The width of the indexed image
/// @param[in] horizontalBorder The size of the horizontal border (0=no border)
/// @param[in] verticalBorder The size of the vertical border (0=no border)
/// @param[in] leds The list with led specifications
/// @param[in] reducedProcessingFactor Factor to reduce the number of pixels evaluated during processing
/// @param[in] accuraryLevel The accuracy used during processing (only for selected types)
///
ImageToLedsMap(
const unsigned width,
const unsigned height,
const unsigned horizontalBorder,
const unsigned verticalBorder,
const std::vector<Led> & leds);
Logger* log,
int width,
int height,
int horizontalBorder,
int verticalBorder,
const std::vector<Led> & leds,
int reducedProcessingFactor = 0,
int accuraryLevel = 0);
///
/// Returns the width of the indexed image
///
/// @return The width of the indexed image [pixels]
///
unsigned width() const;
int width() const;
///
/// Returns the height of the indexed image
///
/// @return The height of the indexed image [pixels]
///
unsigned height() const;
int height() const;
unsigned horizontalBorder() const { return _horizontalBorder; }
unsigned verticalBorder() const { return _verticalBorder; }
int horizontalBorder() const { return _horizontalBorder; }
int verticalBorder() const { return _verticalBorder; }
///
/// Determines the mean color for each led using the mapping the image given
/// Set the accuracy used during processing
/// (only for selected types)
///
/// @param[in] level The accuracy level (0-4)
void setAccuracyLevel (int level);
///
/// Determines the mean color for each LED using the LED area mapping given
/// at construction.
///
/// @param[in] image The image from which to extract the led colors
///
/// @return ledColors The vector containing the output
/// @return The vector containing the output
///
template <typename Pixel_T>
std::vector<ColorRgb> getMeanLedColor(const Image<Pixel_T> & image) const
@ -77,20 +95,18 @@ namespace hyperion
}
///
/// Determines the mean color for each led using the mapping the image given
/// Determines the mean color for each LED using the LED area mapping given
/// at construction.
///
/// @param[in] image The image from which to extract the led colors
/// @param[in] image The image from which to extract the LED colors
/// @param[out] ledColors The vector containing the output
///
template <typename Pixel_T>
void getMeanLedColor(const Image<Pixel_T> & image, std::vector<ColorRgb> & ledColors) const
{
// Sanity check for the number of leds
//assert(_colorsMap.size() == ledColors.size());
if(_colorsMap.size() != ledColors.size())
{
Debug(Logger::getInstance("HYPERION"), "ImageToLedsMap: colorsMap.size != ledColors.size -> %d != %d", _colorsMap.size(), ledColors.size());
Debug(_log, "ImageToLedsMap: colorsMap.size != ledColors.size -> %d != %d", _colorsMap.size(), ledColors.size());
return;
}
@ -104,12 +120,52 @@ namespace hyperion
}
///
/// Determines the uni color for each led using the mapping the image given
/// Determines the mean color squared for each LED using the LED area mapping given
/// at construction.
///
/// @param[in] image The image from which to extract the led colors
///
/// @return ledColors The vector containing the output
/// @return The vector containing the output
///
template <typename Pixel_T>
std::vector<ColorRgb> getMeanLedColorSqrt(const Image<Pixel_T> & image) const
{
std::vector<ColorRgb> colors(_colorsMap.size(), ColorRgb{0,0,0});
getMeanLedColorSqrt(image, colors);
return colors;
}
///
/// Determines the mean color squared for each LED using the LED area mapping given
/// at construction.
///
/// @param[in] image The image from which to extract the LED colors
/// @param[out] ledColors The vector containing the output
///
template <typename Pixel_T>
void getMeanLedColorSqrt(const Image<Pixel_T> & image, std::vector<ColorRgb> & ledColors) const
{
if(_colorsMap.size() != ledColors.size())
{
Debug(_log, "ImageToLedsMap: colorsMap.size != ledColors.size -> %d != %d", _colorsMap.size(), ledColors.size());
return;
}
// Iterate each led and compute the mean
auto led = ledColors.begin();
for (auto colors = _colorsMap.begin(); colors != _colorsMap.end(); ++colors, ++led)
{
const ColorRgb color = calcMeanColorSqrt(image, *colors);
*led = color;
}
}
///
/// Determines the mean color of the image and assigns it to all LEDs
///
/// @param[in] image The image from which to extract the led color
///
/// @return The vector containing the output
///
template <typename Pixel_T>
std::vector<ColorRgb> getUniLedColor(const Image<Pixel_T> & image) const
@ -120,57 +176,145 @@ namespace hyperion
}
///
/// Determines the uni color for each led using the mapping the image given
/// at construction.
/// Determines the mean color of the image and assigns it to all LEDs
///
/// @param[in] image The image from which to extract the led colors
/// @param[in] image The image from which to extract the LED colors
/// @param[out] ledColors The vector containing the output
///
template <typename Pixel_T>
void getUniLedColor(const Image<Pixel_T> & image, std::vector<ColorRgb> & ledColors) const
{
// Sanity check for the number of leds
// assert(_colorsMap.size() == ledColors.size());
if(_colorsMap.size() != ledColors.size())
{
Debug(Logger::getInstance("HYPERION"), "ImageToLedsMap: colorsMap.size != ledColors.size -> %d != %d", _colorsMap.size(), ledColors.size());
Debug(_log, "ImageToLedsMap: colorsMap.size != ledColors.size -> %d != %d", _colorsMap.size(), ledColors.size());
return;
}
// calculate uni color
const ColorRgb color = calcMeanColor(image);
//Update all LEDs with same color
std::fill(ledColors.begin(),ledColors.end(), color);
}
private:
/// The width of the indexed image
const unsigned _width;
/// The height of the indexed image
const unsigned _height;
const unsigned _horizontalBorder;
const unsigned _verticalBorder;
/// The absolute indices into the image for each led
std::vector<std::vector<int32_t>> _colorsMap;
///
/// Determines the dominant color for each LED using the LED area mapping given
/// at construction.
///
/// @param[in] image The image from which to extract the LED color
///
/// @return The vector containing the output
///
template <typename Pixel_T>
std::vector<ColorRgb> getDominantLedColor(const Image<Pixel_T> & image) const
{
std::vector<ColorRgb> colors(_colorsMap.size(), ColorRgb{0,0,0});
getDominantLedColor(image, colors);
return colors;
}
///
/// Calculates the 'mean color' of the given list. This is the mean over each color-channel
/// Determines the dominant color for each LED using the LED area mapping given
/// at construction.
///
/// @param[in] image The image from which to extract the LED colors
/// @param[out] ledColors The vector containing the output
///
template <typename Pixel_T>
void getDominantLedColor(const Image<Pixel_T> & image, std::vector<ColorRgb> & ledColors) const
{
// Sanity check for the number of LEDs
if(_colorsMap.size() != ledColors.size())
{
Debug(_log, "ImageToLedsMap: colorsMap.size != ledColors.size -> %d != %d", _colorsMap.size(), ledColors.size());
return;
}
// Iterate each led and compute the dominant color
auto led = ledColors.begin();
for (auto colors = _colorsMap.begin(); colors != _colorsMap.end(); ++colors, ++led)
{
const ColorRgb color = calculateDominantColor(image, *colors);
*led = color;
}
}
///
/// Determines the dominant color using a k-means algorithm for each LED using the LED area mapping given
/// at construction.
///
/// @param[in] image The image from which to extract the LED color
///
/// @return The vector containing the output
///
template <typename Pixel_T>
std::vector<ColorRgb> getDominantLedColorAdv(const Image<Pixel_T> & image) const
{
std::vector<ColorRgb> colors(_colorsMap.size(), ColorRgb{0,0,0});
getDominantLedColorAdv(image, colors);
return colors;
}
///
/// Determines the dominant color using a k-means algorithm for each LED using the LED area mapping given
/// at construction.
///
/// @param[in] image The image from which to extract the LED colors
/// @param[out] ledColors The vector containing the output
///
template <typename Pixel_T>
void getDominantLedColorAdv(const Image<Pixel_T> & image, std::vector<ColorRgb> & ledColors) const
{
// Sanity check for the number of LEDs
if(_colorsMap.size() != ledColors.size())
{
Debug(_log, "ImageToLedsMap: colorsMap.size != ledColors.size -> %d != %d", _colorsMap.size(), ledColors.size());
return;
}
// Iterate each led and compute the dominant color
auto led = ledColors.begin();
for (auto colors = _colorsMap.begin(); colors != _colorsMap.end(); ++colors, ++led)
{
const ColorRgb color = calculateDominantColorAdv(image, *colors);
*led = color;
}
}
private:
Logger* _log;
/// The width of the indexed image
const int _width;
/// The height of the indexed image
const int _height;
const int _horizontalBorder;
const int _verticalBorder;
/// Evaluate every "count" pixel
int _nextPixelCount;
/// Number of clusters used during dominant color advanced processing (k-means)
int _clusterCount;
/// The absolute indices into the image for each led
std::vector<std::vector<int>> _colorsMap;
///
/// Calculates the 'mean color' over the given image. This is the mean over each color-channel
/// (red, green, blue)
///
/// @param[in] image The image a section from which an average color must be computed
/// @param[in] colors The list with colors
/// @param[in] pixels The list of pixel indices for the given image to be evaluated///
///
/// @return The mean of the given list of colors (or black when empty)
///
template <typename Pixel_T>
ColorRgb calcMeanColor(const Image<Pixel_T> & image, const std::vector<int32_t> & colors) const
ColorRgb calcMeanColor(const Image<Pixel_T> & image, const std::vector<int32_t> & pixels) const
{
const auto colorVecSize = colors.size();
if (colorVecSize == 0)
const auto pixelNum = pixels.size();
if (pixelNum == 0)
{
return ColorRgb::BLACK;
}
@ -179,20 +323,20 @@ namespace hyperion
uint_fast32_t cummRed = 0;
uint_fast32_t cummGreen = 0;
uint_fast32_t cummBlue = 0;
const auto& imgData = image.memptr();
for (const unsigned colorOffset : colors)
const auto& imgData = image.memptr();
for (const int pixelOffset : pixels)
{
const auto& pixel = imgData[colorOffset];
const auto& pixel = imgData[pixelOffset];
cummRed += pixel.red;
cummGreen += pixel.green;
cummBlue += pixel.blue;
}
// Compute the average of each color channel
const uint8_t avgRed = uint8_t(cummRed/colorVecSize);
const uint8_t avgGreen = uint8_t(cummGreen/colorVecSize);
const uint8_t avgBlue = uint8_t(cummBlue/colorVecSize);
const uint8_t avgRed = uint8_t(cummRed/pixelNum);
const uint8_t avgGreen = uint8_t(cummGreen/pixelNum);
const uint8_t avgBlue = uint8_t(cummBlue/pixelNum);
// Return the computed color
return {avgRed, avgGreen, avgBlue};
@ -213,11 +357,11 @@ namespace hyperion
uint_fast32_t cummRed = 0;
uint_fast32_t cummGreen = 0;
uint_fast32_t cummBlue = 0;
const unsigned imageSize = image.width() * image.height();
const unsigned pixelNum = image.width() * image.height();
const auto& imgData = image.memptr();
for (unsigned idx=0; idx<imageSize; idx++)
for (unsigned idx=0; idx<pixelNum; idx++)
{
const auto& pixel = imgData[idx];
cummRed += pixel.red;
@ -226,13 +370,289 @@ namespace hyperion
}
// Compute the average of each color channel
const uint8_t avgRed = uint8_t(cummRed/imageSize);
const uint8_t avgGreen = uint8_t(cummGreen/imageSize);
const uint8_t avgBlue = uint8_t(cummBlue/imageSize);
const uint8_t avgRed = uint8_t(cummRed/pixelNum);
const uint8_t avgGreen = uint8_t(cummGreen/pixelNum);
const uint8_t avgBlue = uint8_t(cummBlue/pixelNum);
// Return the computed color
return {avgRed, avgGreen, avgBlue};
}
///
/// Calculates the 'mean color' squared over the given image. This is the mean over each color-channel
/// (red, green, blue)
///
/// @param[in] image The image a section from which an average color must be computed
/// @param[in] pixels The list of pixel indices for the given image to be evaluated
///
/// @return The mean of the given list of colors (or black when empty)
///
template <typename Pixel_T>
ColorRgb calcMeanColorSqrt(const Image<Pixel_T> & image, const std::vector<int32_t> & pixels) const
{
const auto pixelNum = pixels.size();
if (pixelNum == 0)
{
return ColorRgb::BLACK;
}
// Accumulate the squared sum of each separate color channel
uint_fast32_t cummRed = 0;
uint_fast32_t cummGreen = 0;
uint_fast32_t cummBlue = 0;
const auto& imgData = image.memptr();
for (const int colorOffset : pixels)
{
const auto& pixel = imgData[colorOffset];
cummRed += pixel.red * pixel.red;
cummGreen += pixel.green * pixel.green;
cummBlue += pixel.blue * pixel.blue;
}
// Compute the average of each color channel
const uint8_t avgRed = uint8_t(std::min(std::lround(sqrt(static_cast<double>(cummRed/pixelNum))), 255L));
const uint8_t avgGreen = uint8_t(std::min(std::lround(sqrt(static_cast<double>(cummGreen/pixelNum))), 255L));
const uint8_t avgBlue = uint8_t(std::min(std::lround(sqrt(static_cast<double>(cummBlue/pixelNum))), 255L));
// Return the computed color
return {avgRed, avgGreen, avgBlue};
}
///
/// Calculates the 'mean color' squared over the given image. This is the mean over each color-channel
/// (red, green, blue)
///
/// @param[in] image The image a section from which an average color must be computed
///
/// @return The mean of the given list of colors (or black when empty)
///
template <typename Pixel_T>
ColorRgb calcMeanColorSqrt(const Image<Pixel_T> & image) const
{
// Accumulate the squared sum of each separate color channel
uint_fast32_t cummRed = 0;
uint_fast32_t cummGreen = 0;
uint_fast32_t cummBlue = 0;
const unsigned pixelNum = image.width() * image.height();
const auto& imgData = image.memptr();
for (int idx=0; idx<pixelNum; ++idx)
{
const auto& pixel = imgData[idx];
cummRed += pixel.red * pixel.red;
cummGreen += pixel.green * pixel.green;
cummBlue += pixel.blue * pixel.blue;
}
// Compute the average of each color channel
const uint8_t avgRed = uint8_t(std::lround(sqrt(static_cast<double>(cummRed/pixelNum))));
const uint8_t avgGreen = uint8_t(std::lround(sqrt(static_cast<double>(cummGreen/pixelNum))));
const uint8_t avgBlue = uint8_t(std::lround(sqrt(static_cast<double>(cummBlue/pixelNum))));
// Return the computed color
return {avgRed, avgGreen, avgBlue};
}
///
/// Calculates the 'dominant color' of an image area defined by a list of pixel indices
///
/// @param[in] image The image for which a dominant color is to be computed
/// @param[in] pixels The list of pixel indices for the given image to be evaluated
///
/// @return The image area's dominant color or black, if no pixel indices provided
///
template <typename Pixel_T>
ColorRgb calculateDominantColor(const Image<Pixel_T> & image, const std::vector<int> & pixels) const
{
ColorRgb dominantColor {ColorRgb::BLACK};
const auto pixelNum = pixels.size();
if (pixelNum > 0)
{
const auto& imgData = image.memptr();
QMap<QRgb,int> colorDistributionMap;
int count = 0;
for (const int pixelOffset : pixels)
{
QRgb color = imgData[pixelOffset].rgb();
if (colorDistributionMap.contains(color)) {
colorDistributionMap[color] = colorDistributionMap[color] + 1;
}
else {
colorDistributionMap[color] = 1;
}
int colorsFound = colorDistributionMap[color];
if (colorsFound > count) {
dominantColor.setRgb(color);
count = colorsFound;
}
}
}
return dominantColor;
}
///
/// Calculates the 'dominant color' of an image
///
/// @param[in] image The image for which a dominant color is to be computed
///
/// @return The image's dominant color
///
template <typename Pixel_T>
ColorRgb calculateDominantColor(const Image<Pixel_T> & image) const
{
const unsigned pixelNum = image.width() * image.height();
std::vector<int> pixels(pixelNum);
std::iota(pixels.begin(), pixels.end(), 0);
return calculateDominantColor(image, pixels);
}
template <typename Pixel_T>
struct ColorCluster {
ColorCluster():count(0) {}
ColorCluster(Pixel_T color):count(0),color(color) {}
Pixel_T color;
Pixel_T newColor;
int count;
};
const ColorRgb DEFAULT_CLUSTER_COLORS[5] {
{ColorRgb::BLACK},
{ColorRgb::GREEN},
{ColorRgb::WHITE},
{ColorRgb::RED},
{ColorRgb::YELLOW}
};
///
/// Calculates the 'dominant color' of an image area defined by a list of pixel indices
/// using a k-means algorithm (https://robocraft.ru/computervision/1063)
///
/// @param[in] image The image for which a dominant color is to be computed
/// @param[in] pixels The list of pixel indices for the given image to be evaluated
///
/// @return The image area's dominant color or black, if no pixel indices provided
///
template <typename Pixel_T>
ColorRgb calculateDominantColorAdv(const Image<Pixel_T> & image, const std::vector<int> & pixels) const
{
ColorRgb dominantColor {ColorRgb::BLACK};
const auto pixelNum = pixels.size();
if (pixelNum > 0)
{
// initial cluster with different colors
auto clusters = std::unique_ptr< ColorCluster<ColorRgbScalar> >(new ColorCluster<ColorRgbScalar>[_clusterCount]);
for(int k = 0; k < _clusterCount; ++k)
{
clusters.get()[k].newColor = DEFAULT_CLUSTER_COLORS[k];
}
// k-means
double min_rgb_euclidean {0};
double old_rgb_euclidean {0};
while(1)
{
for(int k = 0; k < _clusterCount; ++k)
{
clusters.get()[k].count = 0;
clusters.get()[k].color = clusters.get()[k].newColor;
clusters.get()[k].newColor.setRgb(ColorRgb::BLACK);
}
const auto& imgData = image.memptr();
for (const int pixelOffset : pixels)
{
const auto& pixel = imgData[pixelOffset];
min_rgb_euclidean = 255 * 255 * 255;
int clusterIndex = -1;
for(int k = 0; k < _clusterCount; ++k)
{
double euclid = ColorSys::rgb_euclidean(ColorRgbScalar(pixel), clusters.get()[k].color);
if( euclid < min_rgb_euclidean ) {
min_rgb_euclidean = euclid;
clusterIndex = k;
}
}
clusters.get()[clusterIndex].count++;
clusters.get()[clusterIndex].newColor += ColorRgbScalar(pixel);
}
min_rgb_euclidean = 0;
for(int k = 0; k < _clusterCount; ++k)
{
if (clusters.get()[k].count > 0)
{
// new color
clusters.get()[k].newColor /= clusters.get()[k].count;
double ecli = ColorSys::rgb_euclidean(clusters.get()[k].newColor, clusters.get()[k].color);
if(ecli > min_rgb_euclidean)
{
min_rgb_euclidean = ecli;
}
}
}
if( fabs(min_rgb_euclidean - old_rgb_euclidean) < 1)
{
break;
}
old_rgb_euclidean = min_rgb_euclidean;
}
int colorsFoundMax = 0;
int dominantClusterIdx {0};
for(int clusterIdx=0; clusterIdx < _clusterCount; ++clusterIdx){
int colorsFoundinCluster = clusters.get()[clusterIdx].count;
if (colorsFoundinCluster > colorsFoundMax) {
colorsFoundMax = colorsFoundinCluster;
dominantClusterIdx = clusterIdx;
}
}
dominantColor.red = static_cast<uint8_t>(clusters.get()[dominantClusterIdx].newColor.red);
dominantColor.green = static_cast<uint8_t>(clusters.get()[dominantClusterIdx].newColor.green);
dominantColor.blue = static_cast<uint8_t>(clusters.get()[dominantClusterIdx].newColor.blue);
}
return dominantColor;
}
///
/// Calculates the 'dominant color' of an image area defined by a list of pixel indices
/// using a k-means algorithm (https://robocraft.ru/computervision/1063)
///
/// @param[in] image The image for which a dominant color is to be computed
///
/// @return The image's dominant color
///
template <typename Pixel_T>
ColorRgb calculateDominantColorAdv(const Image<Pixel_T> & image) const
{
const unsigned pixelNum = image.width() * image.height();
std::vector<int> pixels(pixelNum);
std::iota(pixels.begin(), pixels.end(), 0);
return calculateDominantColorAdv(image, pixels);
}
};
} // end namespace hyperion
#endif // IMAGETOLEDSMAP_H

View File

@ -6,6 +6,7 @@
#include <QString>
#include <QTextStream>
#include <QRgb>
///
/// Plain-Old-Data structure containing the red-green-blue color specification. Size of the
@ -52,6 +53,18 @@ struct ColorRgb
return a;
}
QRgb rgb() const
{
return qRgb(red,green,blue);
}
void setRgb(QRgb rgb)
{
red = static_cast<uint8_t>(qRed(rgb));
green = static_cast<uint8_t>(qGreen(rgb));
blue = static_cast<uint8_t>(qBlue(rgb));
}
QString toQString() const
{
return QString("(%1,%2,%3)").arg(red).arg(green).arg(blue);

View File

@ -0,0 +1,203 @@
#ifndef COLORRGBSCALAR_H
#define COLORRGBSCALAR_H
// STL includes
#include <cstdint>
#include <iostream>
#include <QString>
#include <QTextStream>
#include <QRgb>
#include <utils/ColorRgb.h>
///
/// Plain-Old-Data structure containing the red-green-blue color specification. Size of the
/// structure is exactly 3 times int for easy writing to led-device
///
struct ColorRgbScalar
{
/// The red color channel
int red;
/// The green color channel
int green;
/// The blue color channel
int blue;
/// 'Black' RgbColor (0, 0, 0)
static const ColorRgbScalar BLACK;
/// 'Red' RgbColor (255, 0, 0)
static const ColorRgbScalar RED;
/// 'Green' RgbColor (0, 255, 0)
static const ColorRgbScalar GREEN;
/// 'Blue' RgbColor (0, 0, 255)
static const ColorRgbScalar BLUE;
/// 'Yellow' RgbColor (255, 255, 0)
static const ColorRgbScalar YELLOW;
/// 'White' RgbColor (255, 255, 255)
static const ColorRgbScalar WHITE;
ColorRgbScalar() = default;
ColorRgbScalar(int _red, int _green,int _blue):
red(_red),
green(_green),
blue(_blue)
{
}
ColorRgbScalar(ColorRgb rgb):
red(rgb.red),
green(rgb.green),
blue(rgb.blue)
{
}
ColorRgbScalar operator-(const ColorRgbScalar& b) const
{
ColorRgbScalar a(*this);
a.red -= b.red;
a.green -= b.green;
a.blue -= b.blue;
return a;
}
void setRgb(QRgb rgb)
{
red = qRed(rgb);
green = qGreen(rgb);
blue = qBlue(rgb);
}
void setRgb(ColorRgb rgb)
{
red = rgb.red;
green = rgb.green;
blue = rgb.blue;
}
QString toQString() const
{
return QString("(%1,%2,%3)").arg(red).arg(green).arg(blue);
}
};
/// Assert to ensure that the size of the structure is 'only' 3 times int
static_assert(sizeof(ColorRgbScalar) == 3 * sizeof(int), "Incorrect size of ColorRgbInt");
///
/// Stream operator to write ColorRgbInt to an outputstream (format "'{'[red]','[green]','[blue]'}'")
///
/// @param os The output stream
/// @param color The color to write
/// @return The output stream (with the color written to it)
///
inline std::ostream& operator<<(std::ostream& os, const ColorRgbScalar& color)
{
os << "{"
<< static_cast<unsigned>(color.red) << ","
<< static_cast<unsigned>(color.green) << ","
<< static_cast<unsigned>(color.blue)
<< "}";
return os;
}
///
/// Stream operator to write ColorRgbInt to a QTextStream (format "'{'[red]','[green]','[blue]'}'")
///
/// @param os The output stream
/// @param color The color to write
/// @return The output stream (with the color written to it)
///
inline QTextStream& operator<<(QTextStream &os, const ColorRgbScalar& color)
{
os << "{"
<< static_cast<unsigned>(color.red) << ","
<< static_cast<unsigned>(color.green) << ","
<< static_cast<unsigned>(color.blue)
<< "}";
return os;
}
/// Compare operator to check if a color is 'equal' to another color
inline bool operator==(const ColorRgbScalar & lhs, const ColorRgbScalar & rhs)
{
return lhs.red == rhs.red &&
lhs.green == rhs.green &&
lhs.blue == rhs.blue;
}
/// Compare operator to check if a color is 'smaller' than another color
inline bool operator<(const ColorRgbScalar & lhs, const ColorRgbScalar & rhs)
{
return lhs.red < rhs.red &&
lhs.green < rhs.green &&
lhs.blue < rhs.blue;
}
/// Compare operator to check if a color is 'not equal' to another color
inline bool operator!=(const ColorRgbScalar & lhs, const ColorRgbScalar & rhs)
{
return !(lhs == rhs);
}
/// Compare operator to check if a color is 'smaller' than or 'equal' to another color
inline bool operator<=(const ColorRgbScalar & lhs, const ColorRgbScalar & rhs)
{
return lhs.red <= rhs.red &&
lhs.green <= rhs.green &&
lhs.blue <= rhs.blue;
}
/// Compare operator to check if a color is 'greater' to another color
inline bool operator>(const ColorRgbScalar & lhs, const ColorRgbScalar & rhs)
{
return lhs.red > rhs.red &&
lhs.green > rhs.green &&
lhs.blue > rhs.blue;
}
/// Compare operator to check if a color is 'greater' than or 'equal' to another color
inline bool operator>=(const ColorRgbScalar & lhs, const ColorRgbScalar & rhs)
{
return lhs.red >= rhs.red &&
lhs.green >= rhs.green &&
lhs.blue >= rhs.blue;
}
inline ColorRgbScalar& operator+=(ColorRgbScalar& lhs, const ColorRgbScalar& rhs)
{
lhs.red += rhs.red;
lhs.green += rhs.green;
lhs.blue += rhs.blue;
return lhs;
}
inline ColorRgbScalar operator+(ColorRgbScalar lhs, const ColorRgbScalar rhs)
{
lhs += rhs;
return lhs;
}
inline ColorRgbScalar& operator/=(ColorRgbScalar& lhs, int count)
{
if (count > 0)
{
lhs.red /= count;
lhs.green /= count;
lhs.blue /= count;
}
return lhs;
}
inline ColorRgbScalar operator/(ColorRgbScalar lhs, int count)
{
lhs /= count;
return lhs;
}
#endif // COLORRGBSCALAR_H

View File

@ -30,11 +30,11 @@ struct ColorRgba
static const ColorRgba WHITE;
};
/// Assert to ensure that the size of the structure is 'only' 3 bytes
/// Assert to ensure that the size of the structure is 'only' 4 bytes
static_assert(sizeof(ColorRgba) == 4, "Incorrect size of ColorARGB");
///
/// Stream operator to write ColorRgb to an outputstream (format "'{'[alpha]', '[red]','[green]','[blue]'}'")
/// Stream operator to write ColorRgba to an outputstream (format "'{'[alpha]', '[red]','[green]','[blue]'}'")
///
/// @param os The output stream
/// @param color The color to write

View File

@ -105,6 +105,19 @@ public:
/// @note See https://bottosson.github.io/posts/colorpicker/#okhsv
///
static void okhsv2rgb(double hue, double saturation, double value, uint8_t & red, uint8_t & green, uint8_t & blue);
template <typename Pixel_T>
static double rgb_euclidean(Pixel_T p1, Pixel_T p2)
{
double val = sqrt(
(p1.red - p2.red) * (p1.red - p2.red) +
(p1.green - p2.green) * (p1.green - p2.green) +
(p1.blue - p2.blue) * (p1.blue - p2.blue)
);
return val;
}
};
#endif // COLORSYS_H

View File

@ -12,7 +12,7 @@
},
"mappingType": {
"type" : "string",
"enum" : ["multicolor_mean", "unicolor_mean"]
"enum" : ["multicolor_mean", "unicolor_mean", "multicolor_mean_squared", "dominant_color", "dominant_color_advanced"]
}
},
"additionalProperties": false

View File

@ -74,9 +74,6 @@ void VideoWrapper::handleSettingsUpdate(settings::type type, const QJsonDocument
// Device resolution
_grabber.setWidthHeight(obj["width"].toInt(0), obj["height"].toInt(0));
// Device framerate
_grabber.setFramerate(obj["fps"].toInt(15));
// Device encoding format
_grabber.setEncoding(obj["encoding"].toString("NO_CHANGE"));
@ -124,6 +121,11 @@ void VideoWrapper::handleSettingsUpdate(settings::type type, const QJsonDocument
obj["blueSignalThreshold"].toDouble(0.0)/100.0,
obj["noSignalCounterThreshold"].toInt(50));
// Device framerate
_grabber.setFramerate(obj["fps"].toInt(15));
updateTimer(_ggrabber->getUpdateInterval());
// Reload the Grabber if any settings have been changed that require it
_grabber.reload(getV4lGrabberState());
}

View File

@ -149,6 +149,8 @@ bool Grabber::setWidthHeight(int width, int height)
bool Grabber::setFramerate(int fps)
{
Debug(_log,"Set new frames per second to: %i fps, current fps: %i", fps, _fps);
if((fps > 0) && (_fps != fps))
{
Info(_log,"Set new frames per second to: %i fps", fps);

View File

@ -186,7 +186,8 @@ void GrabberWrapper::updateTimer(int interval)
}
void GrabberWrapper::handleSettingsUpdate(settings::type type, const QJsonDocument& config)
{ if(type == settings::SYSTEMCAPTURE && !_grabberName.startsWith("V4L"))
{
if(type == settings::SYSTEMCAPTURE && !_grabberName.startsWith("V4L"))
{
// extract settings
const QJsonObject& obj = config.object();

View File

@ -4,26 +4,83 @@
#include <hyperion/ImageProcessor.h>
#include <hyperion/ImageToLedsMap.h>
// Blacborder includes
// Blackborder includes
#include <blackborder/BlackBorderProcessor.h>
#include <QSharedPointer>
#include <QRgb>
using namespace hyperion;
void ImageProcessor::registerProcessingUnit(
int width,
int height,
int horizontalBorder,
int verticalBorder)
{
if (width > 0 && height > 0)
{
_imageToLedColors = QSharedPointer<ImageToLedsMap>(new ImageToLedsMap(
_log,
width,
height,
horizontalBorder,
verticalBorder,
_ledString.leds(),
_reducedPixelSetFactorFactor,
_accuraryLevel
));
}
else
{
_imageToLedColors = QSharedPointer<ImageToLedsMap>(nullptr);
}
}
// global transform method
int ImageProcessor::mappingTypeToInt(const QString& mappingType)
{
if (mappingType == "unicolor_mean" )
{
return 1;
}
else if (mappingType == "multicolor_mean_squared" )
{
return 2;
}
else if (mappingType == "dominant_color" )
{
return 3;
}
else if (mappingType == "dominant_color_advanced" )
{
return 4;
}
return 0;
}
// global transform method
QString ImageProcessor::mappingTypeToStr(int mappingType)
{
if (mappingType == 1 )
return "unicolor_mean";
QString typeText;
switch (mappingType) {
case 1:
typeText = "unicolor_mean";
break;
case 2:
typeText = "multicolor_mean_squared";
break;
case 3:
typeText = "dominant_color";
break;
case 4:
typeText = "dominant_color_advanced";
break;
default:
typeText = "multicolor_mean";
break;
}
return "multicolor_mean";
return typeText;
}
ImageProcessor::ImageProcessor(const LedString& ledString, Hyperion* hyperion)
@ -31,10 +88,12 @@ ImageProcessor::ImageProcessor(const LedString& ledString, Hyperion* hyperion)
, _log(nullptr)
, _ledString(ledString)
, _borderProcessor(new BlackBorderProcessor(hyperion, this))
, _imageToLeds(nullptr)
, _imageToLedColors(nullptr)
, _mappingType(0)
, _userMappingType(0)
, _hardMappingType(0)
, _hardMappingType(-1)
, _accuraryLevel(0)
, _reducedPixelSetFactorFactor(1)
, _hyperion(hyperion)
{
QString subComponent = hyperion->property("instance").toString();
@ -48,7 +107,6 @@ ImageProcessor::ImageProcessor(const LedString& ledString, Hyperion* hyperion)
ImageProcessor::~ImageProcessor()
{
delete _imageToLeds;
}
void ImageProcessor::handleSettingsUpdate(settings::type type, const QJsonDocument& config)
@ -61,39 +119,40 @@ void ImageProcessor::handleSettingsUpdate(settings::type type, const QJsonDocume
{
setLedMappingType(newType);
}
int reducedPixelSetFactorFactor = obj["reducedPixelSetFactorFactor"].toString().toInt();
setReducedPixelSetFactorFactor(reducedPixelSetFactorFactor);
int accuracyLevel = obj["accuracyLevel"].toInt();
setAccuracyLevel(accuracyLevel);
}
}
void ImageProcessor::setSize(unsigned width, unsigned height)
void ImageProcessor::setSize(int width, int height)
{
// Check if the existing buffer-image is already the correct dimensions
if (_imageToLeds && _imageToLeds->width() == width && _imageToLeds->height() == height)
if (!_imageToLedColors.isNull() && _imageToLedColors->width() == width && _imageToLedColors->height() == height)
{
return;
}
// Clean up the old buffer and mapping
delete _imageToLeds;
// Construct a new buffer and mapping
_imageToLeds = (width>0 && height>0) ? (new ImageToLedsMap(width, height, 0, 0, _ledString.leds())) : nullptr;
registerProcessingUnit(width, height, 0, 0);
}
void ImageProcessor::setLedString(const LedString& ledString)
{
if ( _imageToLeds != nullptr)
Debug(_log,"");
if ( !_imageToLedColors.isNull() )
{
_ledString = ledString;
// get current width/height
unsigned width = _imageToLeds->width();
unsigned height = _imageToLeds->height();
// Clean up the old buffer and mapping
delete _imageToLeds;
int width = _imageToLedColors->width();
int height = _imageToLedColors->height();
// Construct a new buffer and mapping
_imageToLeds = new ImageToLedsMap(width, height, 0, 0, _ledString.leds());
registerProcessingUnit(width, height, 0, 0);
}
}
@ -107,15 +166,55 @@ bool ImageProcessor::blackBorderDetectorEnabled() const
return _borderProcessor->enabled();
}
void ImageProcessor::setReducedPixelSetFactorFactor(int count)
{
int currentReducedPixelSetFactor= _reducedPixelSetFactorFactor;
_reducedPixelSetFactorFactor = count;
Debug(_log, "Set reduced pixel set factor to %d", _reducedPixelSetFactorFactor);
if (currentReducedPixelSetFactor != _reducedPixelSetFactorFactor && !_imageToLedColors.isNull())
{
int width = _imageToLedColors->width();
int height = _imageToLedColors->height();
// Construct a new buffer and mapping
registerProcessingUnit(width, height, 0, 0);
}
}
void ImageProcessor::setAccuracyLevel(int level)
{
_accuraryLevel = level;
Debug(_log, "Set processing accuracy level to %d", _accuraryLevel);
if (!_imageToLedColors.isNull())
{
_imageToLedColors->setAccuracyLevel(_accuraryLevel);
}
}
void ImageProcessor::setLedMappingType(int mapType)
{
int currentMappingType = _mappingType;
// if the _hardMappingType is >-1 we aren't allowed to overwrite it
_userMappingType = mapType;
Debug(_log, "set user led mapping to %s", QSTRING_CSTR(mappingTypeToStr(mapType)));
Debug(_log, "Set user LED mapping to %s", QSTRING_CSTR(mappingTypeToStr(mapType)));
if(_hardMappingType == -1)
{
_mappingType = mapType;
}
if (currentMappingType != _mappingType && !_imageToLedColors.isNull())
{
int width = _imageToLedColors->width();
int height = _imageToLedColors->height();
registerProcessingUnit(width, height, 0, 0);
}
}
void ImageProcessor::setHardLedMappingType(int mapType)

View File

@ -3,17 +3,26 @@
using namespace hyperion;
ImageToLedsMap::ImageToLedsMap(
unsigned width,
unsigned height,
unsigned horizontalBorder,
unsigned verticalBorder,
const std::vector<Led>& leds)
: _width(width)
Logger* log,
int width,
int height,
int horizontalBorder,
int verticalBorder,
const std::vector<Led>& leds,
int reducedPixelSetFactor,
int accuracyLevel)
: _log(log)
, _width(width)
, _height(height)
, _horizontalBorder(horizontalBorder)
, _verticalBorder(verticalBorder)
, _nextPixelCount(reducedPixelSetFactor)
, _clusterCount()
, _colorsMap()
{
_nextPixelCount = reducedPixelSetFactor + 1;
setAccuracyLevel(accuracyLevel);
// Sanity check of the size of the borders (and width and height)
Q_ASSERT(_width > 2*_verticalBorder);
Q_ASSERT(_height > 2*_horizontalBorder);
@ -23,10 +32,14 @@ ImageToLedsMap::ImageToLedsMap(
// Reserve enough space in the map for the leds
_colorsMap.reserve(leds.size());
const unsigned xOffset = _verticalBorder;
const unsigned actualWidth = _width - 2 * _verticalBorder;
const unsigned yOffset = _horizontalBorder;
const unsigned actualHeight = _height - 2 * _horizontalBorder;
const int xOffset = _verticalBorder;
const int actualWidth = _width - 2 * _verticalBorder;
const int yOffset = _horizontalBorder;
const int actualHeight = _height - 2 * _horizontalBorder;
size_t totalCount = 0;
size_t totalCapacity = 0;
int ledCounter = 0;
for (const Led& led : leds)
{
@ -38,10 +51,10 @@ ImageToLedsMap::ImageToLedsMap(
}
// Compute the index boundaries for this led
unsigned minX_idx = xOffset + unsigned(qRound(actualWidth * led.minX_frac));
unsigned maxX_idx = xOffset + unsigned(qRound(actualWidth * led.maxX_frac));
unsigned minY_idx = yOffset + unsigned(qRound(actualHeight * led.minY_frac));
unsigned maxY_idx = yOffset + unsigned(qRound(actualHeight * led.maxY_frac));
int minX_idx = xOffset + int32_t(qRound(actualWidth * led.minX_frac));
int maxX_idx = xOffset + int32_t(qRound(actualWidth * led.maxX_frac));
int minY_idx = yOffset + int32_t(qRound(actualHeight * led.minY_frac));
int maxY_idx = yOffset + int32_t(qRound(actualHeight * led.maxY_frac));
// make sure that the area is at least a single led large
minX_idx = qMin(minX_idx, xOffset + actualWidth - 1);
@ -56,31 +69,70 @@ ImageToLedsMap::ImageToLedsMap(
}
// Add all the indices in the above defined rectangle to the indices for this led
const auto maxYLedCount = qMin(maxY_idx, yOffset+actualHeight);
const auto maxXLedCount = qMin(maxX_idx, xOffset+actualWidth);
const int maxYLedCount = qMin(maxY_idx, yOffset+actualHeight);
const int maxXLedCount = qMin(maxX_idx, xOffset+actualWidth);
std::vector<int32_t> ledColors;
ledColors.reserve((size_t) maxXLedCount*maxYLedCount);
const int realYLedCount = qAbs(maxYLedCount - minY_idx);
const int realXLedCount = qAbs(maxXLedCount - minX_idx);
for (unsigned y = minY_idx; y < maxYLedCount; ++y)
bool skipPixelProcessing {false};
if (_nextPixelCount > 1)
{
for (unsigned x = minX_idx; x < maxXLedCount; ++x)
skipPixelProcessing = true;
}
size_t totalSize = static_cast<size_t>(realYLedCount * realXLedCount);
if (!skipPixelProcessing && totalSize > 1600)
{
skipPixelProcessing = true;
_nextPixelCount = 2;
Warning(_log, "Mapping LED/light [%d]. The current mapping area contains %d pixels which is huge. Therefore every %d pixels will be skipped. You can enable reduced processing to hide that warning.", ledCounter, totalSize, _nextPixelCount);
}
std::vector<int> ledColors;
ledColors.reserve(totalSize);
for (int y = minY_idx; y < maxYLedCount; y += _nextPixelCount)
{
for (int x = minX_idx; x < maxXLedCount; x += _nextPixelCount)
{
ledColors.push_back(y*width + x);
ledColors.push_back( y * width + x);
}
}
// Add the constructed vector to the map
_colorsMap.push_back(ledColors);
totalCount += ledColors.size();
totalCapacity += ledColors.capacity();
ledCounter++;
}
Debug(_log, "Total index number is: %d (memory: %d). Reduced pixel set factor: %d, Accuracy level: %d, Image size: %d x %d, LED areas: %d",
totalCount, totalCapacity, reducedPixelSetFactor, accuracyLevel, width, height, leds.size());
}
unsigned ImageToLedsMap::width() const
int ImageToLedsMap::width() const
{
return _width;
}
unsigned ImageToLedsMap::height() const
int ImageToLedsMap::height() const
{
return _height;
}
void ImageToLedsMap::setAccuracyLevel (int accuracyLevel)
{
if (accuracyLevel > 4 )
{
Warning(_log, "Accuracy level %d is too high, it will be set to 4", accuracyLevel);
accuracyLevel = 4;
}
//Set cluster number for dominant color advanced
_clusterCount = accuracyLevel + 1;
}

View File

@ -9,20 +9,44 @@
"type" : "string",
"required" : true,
"title" : "edt_conf_color_imageToLedMappingType_title",
"enum" : ["multicolor_mean", "unicolor_mean"],
"enum" : ["multicolor_mean", "unicolor_mean", "multicolor_mean_squared", "dominant_color", "dominant_color_advanced"],
"default" : "multicolor_mean",
"options" : {
"enum_titles" : ["edt_conf_enum_multicolor_mean", "edt_conf_enum_unicolor_mean"]
"enum_titles" : ["edt_conf_enum_multicolor_mean", "edt_conf_enum_unicolor_mean", "edt_conf_enum_multicolor_mean_squared", "edt_conf_enum_dominant_color", "edt_conf_enum_dominant_color_advanced"]
},
"propertyOrder" : 1
},
"accuracyLevel": {
"type": "integer",
"title": "edt_conf_color_accuracyLevel_title",
"minimum": 1,
"maximum": 4,
"default": 2,
"propertyOrder": 2,
"options": {
"dependencies": {
"imageToLedMappingType": "dominant_color_advanced"
}
}
},
"reducedPixelSetFactorFactor": {
"type": "string",
"title": "edt_conf_color_reducedPixelSetFactorFactor_title",
"default": 0,
"enum" : ["0", "1", "2", "3"],
"default" : "0",
"options" : {
"enum_titles" : ["edt_conf_enum_disabled", "edt_conf_enum_low", "edt_conf_enum_medium", "edt_conf_enum_high"]
},
"propertyOrder": 3
},
"channelAdjustment" :
{
"type" : "array",
"title" : "edt_conf_color_channelAdjustment_header_title",
"minItems": 1,
"required" : true,
"propertyOrder" : 3,
"propertyOrder" : 4,
"items" :
{
"type" : "object",

View File

@ -2,6 +2,7 @@
// Utils includes
#include <utils/Image.h>
#include <utils/jsonschema/QJsonFactory.h>
#include <utils/Logger.h>
// Hyperion includes
#include <utils/hyperion.h>
@ -9,6 +10,9 @@
int main()
{
Logger* log = Logger::getInstance("TestImageLedsMap");
Logger::setLogLevel(Logger::DEBUG);
const QString schemaFile = ":/hyperion-schema";
const QString configFile = ":/hyperion_default.config";
@ -25,7 +29,7 @@ int main()
const ColorRgb testColor = {64, 123, 12};
Image<ColorRgb> image(64, 64, testColor);
hyperion::ImageToLedsMap map(64, 64, 0, 0, ledString.leds());
hyperion::ImageToLedsMap map(log, 64, 64, 0, 0, ledString.leds());
std::vector<ColorRgb> ledColors(ledString.leds().size());
map.getMeanLedColor(image, ledColors);