Dominant Colors advanced

This commit is contained in:
LordGrey
2023-01-29 17:17:17 +01:00
parent 87fbc08e0b
commit 10bfcb00b7
8 changed files with 416 additions and 4 deletions

View File

@@ -129,6 +129,9 @@ public:
case 3:
colors = _imageToLeds->getDominantLedColor(image);
break;
case 4:
colors = _imageToLeds->getDominantLedColorAdv(image);
break;
default:
colors = _imageToLeds->getMeanLedColor(image);
}
@@ -171,6 +174,9 @@ public:
case 3:
_imageToLeds->getDominantLedColor(image, ledColors);
break;
case 4:
_imageToLeds->getDominantLedColorAdv(image, ledColors);
break;
default:
_imageToLeds->getMeanLedColor(image, ledColors);
}

View File

@@ -9,12 +9,17 @@
// 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
{
/// 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.
@@ -220,6 +225,48 @@ namespace hyperion
}
}
///
/// 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(Logger::getInstance("HYPERION"), "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:
/// The width of the indexed image
const int _width;
@@ -446,6 +493,140 @@ namespace hyperion
return calculateDominantColor(image, pixels);
}
template <typename Pixel_T>
struct ColorCluster {
ColorCluster():count(0) {}
Pixel_T color;
Pixel_T newColor;
int count;
};
///
/// 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)
{
ColorCluster<ColorRgbScalar> clusters[CLUSTER_COUNT];
// initial cluster colors
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);
}
// k-means
double min_rgb_euclidean {0};
double old_rgb_euclidean {0};
while(1)
{
for(int k = 0; k < CLUSTER_COUNT; ++k)
{
clusters[k].count = 0;
clusters[k].color = clusters[k].newColor;
clusters[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 < CLUSTER_COUNT; ++k)
{
double euclid = ColorSys::rgb_euclidean(ColorRgbScalar(pixel), clusters[k].color);
if( euclid < min_rgb_euclidean ) {
min_rgb_euclidean = euclid;
clusterIndex = k;
}
}
clusters[clusterIndex].count++;
clusters[clusterIndex].newColor += ColorRgbScalar(pixel);
}
min_rgb_euclidean = 0;
for(int k = 0; k < CLUSTER_COUNT; ++k)
{
if (clusters[k].count > 0)
{
// new color
clusters[k].newColor /= clusters[k].count;
double ecli = ColorSys::rgb_euclidean(clusters[k].newColor, clusters[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 < CLUSTER_COUNT; ++clusterIdx){
int colorsFoundinCluster = clusters[clusterIdx].count;
if (colorsFoundinCluster > colorsFoundMax) {
colorsFoundMax = colorsFoundinCluster;
dominantClusterIdx = clusterIdx;
}
}
dominantColor.red = static_cast<uint8_t>(clusters[dominantClusterIdx].newColor.red);
dominantColor.green = static_cast<uint8_t>(clusters[dominantClusterIdx].newColor.green);
dominantColor.blue = static_cast<uint8_t>(clusters[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