13 #include "internal/Image.h"
14 #include "internal/LABAPixel.h"
15 #include "internal/PixelConverters.h"
16 #include "internal/RGBAPixel.h"
17 #include "internal/cielab.h"
19 static constexpr uint8_t COLOR_SPACE_OPTION_CIELAB{0};
20 static constexpr uint8_t COLOR_SPACE_OPTION_RGB{1};
23 template <
typename PixelT>
26 std::vector<PixelT> centroids;
28 int num_pixels = pixels.getSize();
30 std::random_device rd;
31 std::mt19937 gen(rd());
34 std::uniform_int_distribution<> dis(0, num_pixels - 1);
35 int first_index = dis(gen);
36 centroids.push_back(pixels[first_index]);
41 std::vector<double> min_dist_sq(num_pixels, std::numeric_limits<double>::max());
44 for (
int i = 1; i < k; ++i) {
45 double sum_dist_sq = 0.0;
50 for (
int j = 0; j < num_pixels; ++j) {
51 double d = PixelT::colorDistance(pixels[j], centroids.back());
55 if (d < min_dist_sq[j]) {
58 sum_dist_sq += min_dist_sq[j];
63 std::uniform_real_distribution<> dist_selector(0.0, sum_dist_sq);
64 double random_value = dist_selector(gen);
66 double current_sum = 0.0;
67 int selected_index = -1;
70 for (
int j = 0; j < num_pixels; ++j) {
71 current_sum += min_dist_sq[j];
72 if (current_sum >= random_value) {
80 if (selected_index == -1) {
81 selected_index = num_pixels - 1;
84 centroids.push_back(pixels[selected_index]);
87 std::copy(centroids.begin(), centroids.end(), out_centroids.begin());
91 void kmeans(
const uint8_t *data, uint8_t *out_data, int32_t *out_labels,
const int32_t width,
92 const int32_t height,
const int32_t k,
const int32_t max_iter,
93 const uint8_t color_space) {
95 pixels.loadFromBuffer(data, width, height, ImageLib::RGBA_CONVERTER<float>);
96 const int32_t num_pixels{pixels.getSize()};
103 std::vector<int32_t> labels(num_pixels, 0);
106 if (color_space == COLOR_SPACE_OPTION_CIELAB) {
107 for (
int i{0}; i < pixels.getSize(); ++i) {
108 rgb_to_lab<float, float>(pixels[i], lab[i]);
114 switch (color_space) {
115 case COLOR_SPACE_OPTION_RGB: {
116 kMeansPlusPlusInit<ImageLib::RGBAPixel<float>>(pixels, centroids, k);
119 case COLOR_SPACE_OPTION_CIELAB: {
120 kMeansPlusPlusInit<ImageLib::LABAPixel<float>>(lab, centroids_lab, k);
128 for (int32_t iter{0}; iter < max_iter; ++iter) {
132 for (int32_t i{0}; i < num_pixels; ++i) {
133 float min_color_dist{std::numeric_limits<float>::max()};
134 int32_t best_cluster{0};
139 for (int32_t j{0}; j < k; ++j) {
140 switch (color_space) {
141 case COLOR_SPACE_OPTION_RGB: {
145 case COLOR_SPACE_OPTION_CIELAB: {
150 if (dist < min_color_dist) {
151 min_color_dist = dist;
156 if (labels[i] != best_cluster) {
158 labels[i] = best_cluster;
170 std::vector<int32_t> counts(k, 0);
172 for (int32_t i = 0; i < num_pixels; ++i) {
173 int32_t cluster = labels[i];
174 switch (color_space) {
175 case COLOR_SPACE_OPTION_RGB: {
176 new_centroids[cluster].red += pixels[i].red;
177 new_centroids[cluster].green += pixels[i].green;
178 new_centroids[cluster].blue += pixels[i].blue;
181 case COLOR_SPACE_OPTION_CIELAB: {
182 new_centroids_lab[cluster].l += lab[i].l;
183 new_centroids_lab[cluster].a += lab[i].a;
184 new_centroids_lab[cluster].b += lab[i].b;
191 for (int32_t j = 0; j < k; ++j) {
197 switch (color_space) {
198 case COLOR_SPACE_OPTION_RGB: {
199 centroids[j].red = new_centroids[j].red / counts[j];
200 centroids[j].green = new_centroids[j].green / counts[j];
201 centroids[j].blue = new_centroids[j].blue / counts[j];
204 case COLOR_SPACE_OPTION_CIELAB: {
205 centroids_lab[j].l = new_centroids_lab[j].l / counts[j];
206 centroids_lab[j].a = new_centroids_lab[j].a / counts[j];
207 centroids_lab[j].b = new_centroids_lab[j].b / counts[j];
215 if (color_space == COLOR_SPACE_OPTION_CIELAB) {
216 for (int32_t i{0}; i < k; ++i) {
217 lab_to_rgb<float, float>(centroids_lab[i], centroids[i]);
222 for (int32_t i = 0; i < num_pixels; ++i) {
223 const int32_t cluster = labels[i];
224 out_data[i * 4 + 0] =
225 static_cast<uint8_t
>(std::clamp(centroids[cluster].red, 0.0f, 255.0f));
226 out_data[i * 4 + 1] =
227 static_cast<uint8_t
>(std::clamp(centroids[cluster].green, 0.0f, 255.0f));
228 out_data[i * 4 + 2] =
229 static_cast<uint8_t
>(std::clamp(centroids[cluster].blue, 0.0f, 255.0f));
230 out_data[i * 4 + 3] = 255;
234 std::memcpy(out_labels, labels.data(), labels.size() *
sizeof(int32_t));
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.