2#include "internal/cielab.h"
3#include "internal/gpu.h"
4#include "internal/Image.h"
5#include "internal/kmeans_gpu.h"
6#include "internal/LABAPixel.h"
7#include "internal/PixelConverters.h"
8#include "internal/RGBAPixel.h"
21static constexpr uint8_t COLOR_SPACE_OPTION_CIELAB {0};
22static constexpr uint8_t COLOR_SPACE_OPTION_RGB {1};
25template <
typename PixelT>
26void kMeansPlusPlusInit(
29 std::vector<PixelT> centroids;
31 int num_pixels = pixels.getSize();
33 std::random_device rd;
34 std::mt19937 gen(rd());
37 std::uniform_int_distribution<> dis(0, num_pixels - 1);
38 int first_index = dis(gen);
39 centroids.push_back(pixels[first_index]);
44 std::vector<double> min_dist_sq(num_pixels, std::numeric_limits<double>::max());
47 for (
int i = 1; i < k; ++i) {
48 double sum_dist_sq = 0.0;
53 for (
int j = 0; j < num_pixels; ++j) {
54 double d = PixelT::colorDistance(pixels[j], centroids.back());
58 if (d < min_dist_sq[j]) {
61 sum_dist_sq += min_dist_sq[j];
66 std::uniform_real_distribution<> dist_selector(0.0, sum_dist_sq);
67 double random_value = dist_selector(gen);
69 double current_sum = 0.0;
70 int selected_index = -1;
73 for (
int j = 0; j < num_pixels; ++j) {
74 current_sum += min_dist_sq[j];
75 if (current_sum >= random_value) {
83 if (selected_index == -1) {
84 selected_index = num_pixels - 1;
87 centroids.push_back(pixels[selected_index]);
90 std::copy(centroids.begin(), centroids.end(), out_centroids.begin());
94 const uint8_t* data, uint8_t* out_data, int32_t* out_labels,
const int32_t width,
95 const int32_t height,
const int32_t k,
const int32_t max_iter,
const uint8_t color_space
98 pixels.loadFromBuffer(data, width, height, ImageLib::RGBA_CONVERTER<float>);
99 const int32_t num_pixels {pixels.getSize()};
106 std::vector<int32_t> labels(num_pixels, 0);
109 if (color_space == COLOR_SPACE_OPTION_CIELAB) {
110 for (
int i {0}; i < pixels.getSize(); ++i) {
111 rgb_to_lab<float, float>(pixels[i], lab[i]);
117 switch (color_space) {
118 case COLOR_SPACE_OPTION_RGB: {
119 kMeansPlusPlusInit<ImageLib::RGBAPixel<float>>(pixels, centroids, k);
122 case COLOR_SPACE_OPTION_CIELAB: {
123 kMeansPlusPlusInit<ImageLib::LABAPixel<float>>(lab, centroids_lab, k);
131 for (int32_t iter {0}; iter < max_iter; ++iter) {
132 bool changed {
false};
135 for (int32_t i {0}; i < num_pixels; ++i) {
136 float min_color_dist {std::numeric_limits<float>::max()};
137 int32_t best_cluster {0};
142 for (int32_t j {0}; j < k; ++j) {
143 switch (color_space) {
144 case COLOR_SPACE_OPTION_RGB: {
148 case COLOR_SPACE_OPTION_CIELAB: {
153 if (dist < min_color_dist) {
154 min_color_dist = dist;
159 if (labels[i] != best_cluster) {
161 labels[i] = best_cluster;
173 std::vector<int32_t> counts(k, 0);
175 for (int32_t i = 0; i < num_pixels; ++i) {
176 int32_t cluster = labels[i];
177 switch (color_space) {
178 case COLOR_SPACE_OPTION_RGB: {
179 new_centroids[cluster].red += pixels[i].red;
180 new_centroids[cluster].green += pixels[i].green;
181 new_centroids[cluster].blue += pixels[i].blue;
184 case COLOR_SPACE_OPTION_CIELAB: {
185 new_centroids_lab[cluster].l += lab[i].l;
186 new_centroids_lab[cluster].a += lab[i].a;
187 new_centroids_lab[cluster].b += lab[i].b;
194 for (int32_t j = 0; j < k; ++j) {
200 switch (color_space) {
201 case COLOR_SPACE_OPTION_RGB: {
202 centroids[j].red = new_centroids[j].red / counts[j];
203 centroids[j].green = new_centroids[j].green / counts[j];
204 centroids[j].blue = new_centroids[j].blue / counts[j];
207 case COLOR_SPACE_OPTION_CIELAB: {
208 centroids_lab[j].l = new_centroids_lab[j].l / counts[j];
209 centroids_lab[j].a = new_centroids_lab[j].a / counts[j];
210 centroids_lab[j].b = new_centroids_lab[j].b / counts[j];
218 if (color_space == COLOR_SPACE_OPTION_CIELAB) {
219 for (int32_t i {0}; i < k; ++i) {
220 lab_to_rgb<float, float>(centroids_lab[i], centroids[i]);
225 for (int32_t i = 0; i < num_pixels; ++i) {
226 const int32_t cluster = labels[i];
227 out_data[i * 4 + 0] =
228 static_cast<uint8_t
>(std::clamp(centroids[cluster].red, 0.0f, 255.0f));
229 out_data[i * 4 + 1] =
230 static_cast<uint8_t
>(std::clamp(centroids[cluster].green, 0.0f, 255.0f));
231 out_data[i * 4 + 2] =
232 static_cast<uint8_t
>(std::clamp(centroids[cluster].blue, 0.0f, 255.0f));
233 out_data[i * 4 + 3] = 255;
237 std::memcpy(out_labels, labels.data(), labels.size() *
sizeof(int32_t));
242 const uint8_t* data, uint8_t* out_data, int32_t* out_labels,
const int32_t width,
243 const int32_t height,
const int32_t k,
const int32_t max_iter,
const uint8_t color_space
245 GPU::getClassInstance().init_gpu();
247 if (GPU::getClassInstance().is_initialized()) {
248 kmeans_gpu(data, out_data, out_labels, width, height, k, max_iter, color_space);
250 kmeans_cpu(data, out_data, out_labels, width, height, k, max_iter, color_space);
Core image processing functions for img2num project.
void kmeans(const uint8_t *data, uint8_t *out_data, int32_t *out_labels, const int32_t width, const int32_t height, const int32_t k, const int32_t max_iter, const uint8_t color_space)
Perform k-means clustering on image data.