diff --git a/src-core/common/image/image.h b/src-core/common/image/image.h index e3c51dc89..d096b050a 100644 --- a/src-core/common/image/image.h +++ b/src-core/common/image/image.h @@ -69,6 +69,7 @@ namespace image T *data() { return d_data; } // Return the raw image data buffer T *channel(int channel) { return &d_data[d_width * d_height * channel]; } // Return a pointer to a specific channel + T Image::wraparound_read(T* c, int x, int y); // Returns pixel value, wrapping around at the edge void to_rgb(); // Convert this image from B&W to RGB (if it is B&W / RGBA) void to_rgba(); // Convert this image from to RGBA (if it is B&W / RGB) @@ -96,6 +97,7 @@ namespace image void linear_invert(); // Invert the entire image void simple_despeckle(int thresold = 10); // Very basic despeckle algorithm void median_blur(); // Median blur algorithm + void kuwahara_filter(); // Adaptive noise reduction filter // Drawing functions void draw_pixel(int x, int y, T color[]); // Set a pixel's color diff --git a/src-core/common/image/image_processing.cpp b/src-core/common/image/image_processing.cpp index 6afc0e17b..bc6889f0e 100644 --- a/src-core/common/image/image_processing.cpp +++ b/src-core/common/image/image_processing.cpp @@ -436,6 +436,90 @@ namespace image } } + template + void Image::kuwahara_filter() + { + const int radius = 1; + const float num_pixels = (float)((radius + 1) * (radius + 1)); + + Image tmp = *this; + init(d_width, d_height, d_channels); + for (int c = 0; c < d_channels; c++) + { + for (size_t y = 0; y < d_height; y++) + { + for (size_t x = 0; x < d_width; x++) + { + float average[4] = { 0 }; + float variance[4] = { 0 }; + + // Calculate values for the four regions + for (int j = -radius; j <= 0; ++j) + for (int i = -radius; i <= 0; ++i) + average[0] += wraparound_read(tmp.channel(c), x + i, y + j); + average[0] /= num_pixels; + for (int j = -radius; j <= 0; ++j) + for (int i = -radius; i <= 0; ++i) + variance[0] += pow(wraparound_read(tmp.channel(c), x + i, y + j) - average[0], 2); + + for (int j = -radius; j <= 0; ++j) + for (int i = 0; i <= radius; ++i) + average[1] += wraparound_read(tmp.channel(c), x + i, y + j); + average[1] /= num_pixels; + for (int j = -radius; j <= 0; ++j) + for (int i = 0; i <= radius; ++i) + variance[1] += pow(wraparound_read(tmp.channel(c), x + i, y + j) - average[1], 2); + + for (int j = 0; j <= radius; ++j) + for (int i = 0; i <= radius; ++i) + average[2] += wraparound_read(tmp.channel(c), x + i, y + j); + average[2] /= num_pixels; + for (int j = 0; j <= radius; ++j) + for (int i = 0; i <= radius; ++i) + variance[2] += pow(wraparound_read(tmp.channel(c), x + i, y + j) - average[2], 2); + + for (int j = 0; j <= radius; ++j) + for (int i = -radius; i <= 0; ++i) + average[3] += wraparound_read(tmp.channel(c), x + i, y + j); + average[3] /= num_pixels; + for (int j = 0; j <= radius; ++j) + for (int i = -radius; i <= 0; ++i) + variance[3] += pow(wraparound_read(tmp.channel(c), x + i, y + j) - average[3], 2); + + // Find the region with the smallest variance and use its mean as the new pixel value + float min_sigma2 = FLT_MAX; + for (int k = 0; k < 4; k++) + { + variance[k] /= num_pixels; + // Find Sigma 2 + if (variance[k] < 0) + variance[k] = -variance[k]; + + if (variance[k] < min_sigma2) + { + min_sigma2 = variance[k]; + channel(c)[y * d_width + x] = (T)average[k]; + } + } + } + } + } + } + + template + T Image::wraparound_read(T* c, int x, int y) + { + if (x < 0) + x += d_width; + if (y < 0) + y += d_height; + if (x >= d_width) + x -= d_width; + if (y >= d_height) + y -= d_height; + return c[y * d_width + x]; + } + // Generate Images for uint16_t and uint8_t template class Image; template class Image; diff --git a/src-core/common/lua/lua_utils.cpp b/src-core/common/lua/lua_utils.cpp index 8c2eb2056..638e658b1 100644 --- a/src-core/common/lua/lua_utils.cpp +++ b/src-core/common/lua/lua_utils.cpp @@ -82,6 +82,7 @@ namespace lua_utils image_type["linear_invert"] = &image::Image::linear_invert; image_type["simple_despeckle"] = &image::Image::simple_despeckle; image_type["median_blur"] = &image::Image::median_blur; + image_type["despeckle"] = &image::Image::kuwahara_filter; image_type["draw_pixel"] = &image::Image::draw_pixel; image_type["draw_line"] = &image::Image::draw_line; diff --git a/src-interface/viewer/image_handler.cpp b/src-interface/viewer/image_handler.cpp index e9b75218e..e171d0e73 100644 --- a/src-interface/viewer/image_handler.cpp +++ b/src-interface/viewer/image_handler.cpp @@ -109,6 +109,9 @@ namespace satdump if (median_blur) current_image.median_blur(); + if (despeckle) + current_image.kuwahara_filter(); + if (rotate_image) current_image.mirror(true, true); @@ -524,6 +527,9 @@ namespace satdump if (ImGui::Checkbox("Median Blur", &median_blur)) asyncUpdate(); + if (ImGui::Checkbox("Despeckle", &despeckle)) + asyncUpdate(); + if (ImGui::Checkbox("Rotate", &rotate_image)) asyncUpdate(); diff --git a/src-interface/viewer/image_handler.h b/src-interface/viewer/image_handler.h index 897d2dfa0..0e38442d6 100644 --- a/src-interface/viewer/image_handler.h +++ b/src-interface/viewer/image_handler.h @@ -30,6 +30,7 @@ namespace satdump // Other controls bool median_blur = false; + bool despeckle = false; bool rotate_image = false; bool equalize_image = false; bool individual_equalize_image = false;