14#include "internal/Image.h"
15#include "internal/LABAPixel.h"
16#include "internal/PixelConverters.h"
17#include "internal/RGBAPixel.h"
18#include "internal/cielab.h"
19#include "internal/gpu.h"
20#include "internal/kmeans_gpu.h"
22static constexpr uint8_t COLOR_SPACE_OPTION_CIELAB{0};
23static constexpr uint8_t COLOR_SPACE_OPTION_RGB{1};
26template <
typename PixelT>
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());
93void kmeans_cpu(
const uint8_t *data, uint8_t *out_data, int32_t *out_labels,
const int32_t width,
94 const int32_t height,
const int32_t k,
const int32_t max_iter,
95 const uint8_t color_space) {
97 pixels.loadFromBuffer(data, width, height, ImageLib::RGBA_CONVERTER<float>);
98 const int32_t num_pixels{pixels.getSize()};
105 std::vector<int32_t> labels(num_pixels, 0);
108 if (color_space == COLOR_SPACE_OPTION_CIELAB) {
109 for (
int i{0}; i < pixels.getSize(); ++i) {
110 rgb_to_lab<float, float>(pixels[i], lab[i]);
116 switch (color_space) {
117 case COLOR_SPACE_OPTION_RGB: {
118 kMeansPlusPlusInit<ImageLib::RGBAPixel<float>>(pixels, centroids, k);
121 case COLOR_SPACE_OPTION_CIELAB: {
122 kMeansPlusPlusInit<ImageLib::LABAPixel<float>>(lab, centroids_lab, k);
130 for (int32_t iter{0}; iter < max_iter; ++iter) {
134 for (int32_t i{0}; i < num_pixels; ++i) {
135 float min_color_dist{std::numeric_limits<float>::max()};
136 int32_t best_cluster{0};
141 for (int32_t j{0}; j < k; ++j) {
142 switch (color_space) {
143 case COLOR_SPACE_OPTION_RGB: {
147 case COLOR_SPACE_OPTION_CIELAB: {
152 if (dist < min_color_dist) {
153 min_color_dist = dist;
158 if (labels[i] != best_cluster) {
160 labels[i] = best_cluster;
172 std::vector<int32_t> counts(k, 0);
174 for (int32_t i = 0; i < num_pixels; ++i) {
175 int32_t cluster = labels[i];
176 switch (color_space) {
177 case COLOR_SPACE_OPTION_RGB: {
178 new_centroids[cluster].red += pixels[i].red;
179 new_centroids[cluster].green += pixels[i].green;
180 new_centroids[cluster].blue += pixels[i].blue;
183 case COLOR_SPACE_OPTION_CIELAB: {
184 new_centroids_lab[cluster].l += lab[i].l;
185 new_centroids_lab[cluster].a += lab[i].a;
186 new_centroids_lab[cluster].b += lab[i].b;
193 for (int32_t j = 0; j < k; ++j) {
199 switch (color_space) {
200 case COLOR_SPACE_OPTION_RGB: {
201 centroids[j].red = new_centroids[j].red / counts[j];
202 centroids[j].green = new_centroids[j].green / counts[j];
203 centroids[j].blue = new_centroids[j].blue / counts[j];
206 case COLOR_SPACE_OPTION_CIELAB: {
207 centroids_lab[j].l = new_centroids_lab[j].l / counts[j];
208 centroids_lab[j].a = new_centroids_lab[j].a / counts[j];
209 centroids_lab[j].b = new_centroids_lab[j].b / counts[j];
217 if (color_space == COLOR_SPACE_OPTION_CIELAB) {
218 for (int32_t i{0}; i < k; ++i) {
219 lab_to_rgb<float, float>(centroids_lab[i], centroids[i]);
224 for (int32_t i = 0; i < num_pixels; ++i) {
225 const int32_t cluster = labels[i];
226 out_data[i * 4 + 0] =
227 static_cast<uint8_t
>(std::clamp(centroids[cluster].red, 0.0f, 255.0f));
228 out_data[i * 4 + 1] =
229 static_cast<uint8_t
>(std::clamp(centroids[cluster].green, 0.0f, 255.0f));
230 out_data[i * 4 + 2] =
231 static_cast<uint8_t
>(std::clamp(centroids[cluster].blue, 0.0f, 255.0f));
232 out_data[i * 4 + 3] = 255;
236 std::memcpy(out_labels, labels.data(), labels.size() *
sizeof(int32_t));
240void kmeans(
const uint8_t *data, uint8_t *out_data, int32_t *out_labels,
const int32_t width,
241 const int32_t height,
const int32_t k,
const int32_t max_iter,
242 const uint8_t color_space) {
243 GPU::getClassInstance().init_gpu();
245 if (GPU::getClassInstance().is_initialized()) {
246 std::cout <<
"kmeans gpu" << std::endl;
247 kmeans_gpu(data, out_data, out_labels, width, height, k, max_iter, color_space);
249 std::cout <<
"kmeans cpu" << std::endl;
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.