mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2025-03-01 10:33:28 +00:00
ImageToLED - Add reduced pixel processing, make dominant color advanced configurable
This commit is contained in:
@@ -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; }
|
||||
|
||||
@@ -109,11 +123,14 @@ public:
|
||||
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);
|
||||
|
||||
@@ -121,19 +138,19 @@ public:
|
||||
switch (_mappingType)
|
||||
{
|
||||
case 1:
|
||||
colors = _imageToLeds->getUniLedColor(image);
|
||||
colors = _imageToLedColors->getUniLedColor(image);
|
||||
break;
|
||||
case 2:
|
||||
colors = _imageToLeds->getMeanLedColorSqrt(image);
|
||||
colors = _imageToLedColors->getMeanLedColorSqrt(image);
|
||||
break;
|
||||
case 3:
|
||||
colors = _imageToLeds->getDominantLedColor(image);
|
||||
colors = _imageToLedColors->getDominantLedColor(image);
|
||||
break;
|
||||
case 4:
|
||||
colors = _imageToLeds->getDominantLedColorAdv(image);
|
||||
colors = _imageToLedColors->getDominantLedColorAdv(image);
|
||||
break;
|
||||
default:
|
||||
colors = _imageToLeds->getMeanLedColor(image);
|
||||
colors = _imageToLedColors->getMeanLedColor(image);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -166,19 +183,19 @@ public:
|
||||
switch (_mappingType)
|
||||
{
|
||||
case 1:
|
||||
_imageToLeds->getUniLedColor(image, ledColors);
|
||||
_imageToLedColors->getUniLedColor(image, ledColors);
|
||||
break;
|
||||
case 2:
|
||||
_imageToLeds->getMeanLedColorSqrt(image, ledColors);
|
||||
_imageToLedColors->getMeanLedColorSqrt(image, ledColors);
|
||||
break;
|
||||
case 3:
|
||||
_imageToLeds->getDominantLedColor(image, ledColors);
|
||||
_imageToLedColors->getDominantLedColor(image, ledColors);
|
||||
break;
|
||||
case 4:
|
||||
_imageToLeds->getDominantLedColorAdv(image, ledColors);
|
||||
_imageToLedColors->getDominantLedColorAdv(image, ledColors);
|
||||
break;
|
||||
default:
|
||||
_imageToLeds->getMeanLedColor(image, ledColors);
|
||||
_imageToLedColors->getMeanLedColor(image, ledColors);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -199,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
|
||||
///
|
||||
@@ -207,30 +231,24 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,6 +257,7 @@ private slots:
|
||||
void handleSettingsUpdate(settings::type type, const QJsonDocument& config);
|
||||
|
||||
private:
|
||||
|
||||
Logger * _log;
|
||||
/// The Led-string specification
|
||||
LedString _ledString;
|
||||
@@ -247,7 +266,7 @@ private:
|
||||
hyperion::BlackBorderProcessor * _borderProcessor;
|
||||
|
||||
/// The mapping of image-pixels to LEDs
|
||||
hyperion::ImageToLedsMap* _imageToLeds;
|
||||
QSharedPointer<hyperion::ImageToLedsMap> _imageToLedColors;
|
||||
|
||||
/// Type of image to LED mapping
|
||||
int _mappingType;
|
||||
@@ -256,6 +275,9 @@ private:
|
||||
/// Type of last requested hard type
|
||||
int _hardMappingType;
|
||||
|
||||
int _accuraryLevel;
|
||||
int _reducedPixelSetFactorFactor;
|
||||
|
||||
/// Hyperion instance pointer
|
||||
Hyperion* _hyperion;
|
||||
};
|
||||
|
||||
@@ -17,15 +17,14 @@
|
||||
|
||||
namespace hyperion
|
||||
{
|
||||
/// Number of clusters for k-means calculation
|
||||
const int CLUSTER_COUNT {5};
|
||||
|
||||
///
|
||||
/// 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:
|
||||
|
||||
///
|
||||
@@ -35,17 +34,26 @@ namespace hyperion
|
||||
/// The mapping is created purely on size (width and height). The given borders are excluded
|
||||
/// from indexing.
|
||||
///
|
||||
/// @param[in] log Logger
|
||||
/// @param[in] mappingType Type of the mapping algorithm
|
||||
/// @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(int width,
|
||||
ImageToLedsMap(
|
||||
Logger* log,
|
||||
int mappingType,
|
||||
int width,
|
||||
int height,
|
||||
int horizontalBorder,
|
||||
int verticalBorder,
|
||||
const std::vector<Led> & leds);
|
||||
const std::vector<Led> & leds,
|
||||
int reducedProcessingFactor = 0,
|
||||
int accuraryLevel = 0);
|
||||
|
||||
///
|
||||
/// Returns the width of the indexed image
|
||||
@@ -64,6 +72,13 @@ namespace hyperion
|
||||
int horizontalBorder() const { return _horizontalBorder; }
|
||||
int verticalBorder() const { return _verticalBorder; }
|
||||
|
||||
///
|
||||
/// 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.
|
||||
@@ -92,7 +107,7 @@ namespace hyperion
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -133,7 +148,7 @@ namespace hyperion
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -172,7 +187,7 @@ namespace hyperion
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -211,7 +226,7 @@ namespace hyperion
|
||||
// Sanity check for the number of LEDs
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -253,7 +268,7 @@ namespace hyperion
|
||||
// Sanity check for the number of LEDs
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -267,6 +282,11 @@ namespace hyperion
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
Logger* _log;
|
||||
|
||||
int _mappingType;
|
||||
|
||||
/// The width of the indexed image
|
||||
const int _width;
|
||||
/// The height of the indexed image
|
||||
@@ -275,6 +295,12 @@ namespace hyperion
|
||||
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;
|
||||
|
||||
@@ -496,12 +522,21 @@ namespace hyperion
|
||||
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)
|
||||
@@ -519,31 +554,11 @@ namespace hyperion
|
||||
const auto pixelNum = pixels.size();
|
||||
if (pixelNum > 0)
|
||||
{
|
||||
ColorCluster<ColorRgbScalar> clusters[CLUSTER_COUNT];
|
||||
|
||||
// initial cluster colors
|
||||
switch (CLUSTER_COUNT) {
|
||||
case 5:
|
||||
clusters[4].newColor = ColorRgbScalar(ColorRgb::YELLOW);
|
||||
case 4:
|
||||
clusters[3].newColor = ColorRgbScalar(ColorRgb::RED);
|
||||
case 3:
|
||||
clusters[2].newColor = ColorRgbScalar(ColorRgb::WHITE);
|
||||
case 2:
|
||||
clusters[1].newColor = ColorRgbScalar(ColorRgb::GREEN);
|
||||
case 1:
|
||||
clusters[0].newColor = ColorRgbScalar(ColorRgb::BLACK);
|
||||
break;
|
||||
default:
|
||||
for(int k = 0; k < CLUSTER_COUNT; ++k)
|
||||
{
|
||||
int randomRed = rand() % static_cast<int>(256);
|
||||
int randomGreen = rand() % static_cast<int>(256);
|
||||
int randomBlue = rand() % static_cast<int>(256);
|
||||
|
||||
clusters[k].newColor = ColorRgbScalar(randomRed, randomGreen, randomBlue);
|
||||
}
|
||||
break;
|
||||
// initial cluster with different colors
|
||||
ColorCluster<ColorRgbScalar> clusters[_clusterCount];
|
||||
for(int k = 0; k < _clusterCount; ++k)
|
||||
{
|
||||
clusters[k].newColor = DEFAULT_CLUSTER_COLORS[k];
|
||||
}
|
||||
|
||||
// k-means
|
||||
@@ -552,7 +567,7 @@ namespace hyperion
|
||||
|
||||
while(1)
|
||||
{
|
||||
for(int k = 0; k < CLUSTER_COUNT; ++k)
|
||||
for(int k = 0; k < _clusterCount; ++k)
|
||||
{
|
||||
clusters[k].count = 0;
|
||||
clusters[k].color = clusters[k].newColor;
|
||||
@@ -566,7 +581,7 @@ namespace hyperion
|
||||
|
||||
min_rgb_euclidean = 255 * 255 * 255;
|
||||
int clusterIndex = -1;
|
||||
for(int k = 0; k < CLUSTER_COUNT; ++k)
|
||||
for(int k = 0; k < _clusterCount; ++k)
|
||||
{
|
||||
double euclid = ColorSys::rgb_euclidean(ColorRgbScalar(pixel), clusters[k].color);
|
||||
|
||||
@@ -581,7 +596,7 @@ namespace hyperion
|
||||
}
|
||||
|
||||
min_rgb_euclidean = 0;
|
||||
for(int k = 0; k < CLUSTER_COUNT; ++k)
|
||||
for(int k = 0; k < _clusterCount; ++k)
|
||||
{
|
||||
if (clusters[k].count > 0)
|
||||
{
|
||||
@@ -606,7 +621,7 @@ namespace hyperion
|
||||
int colorsFoundMax = 0;
|
||||
int dominantClusterIdx {0};
|
||||
|
||||
for(int clusterIdx=0; clusterIdx < CLUSTER_COUNT; ++clusterIdx){
|
||||
for(int clusterIdx=0; clusterIdx < _clusterCount; ++clusterIdx){
|
||||
int colorsFoundinCluster = clusters[clusterIdx].count;
|
||||
if (colorsFoundinCluster > colorsFoundMax) {
|
||||
colorsFoundMax = colorsFoundinCluster;
|
||||
|
||||
Reference in New Issue
Block a user