Compositor: add new node: Kuwahara filter #107015

Merged
Habib Gahbiche merged 22 commits from zazizizou/blender:com-kuwahara-filter-node into main 2023-06-08 16:14:51 +02:00
5 changed files with 56 additions and 34 deletions
Showing only changes of commit 104f744ed6 - Show all commits

View File

@ -62,11 +62,7 @@ void KuwaharaNode::convert_to_operations(NodeConverter &converter,
converter.add_link(sobel_y->get_output_socket(0), sobel_xy->get_input_socket(1));
/* blurring for more robustness. */
// Note: blurring doesn't make as big of a difference as I was expecting,
// especially around edges.
// Todo: investigate further and remove if necessary. For now the parameter is kept for
// better user feedback
float sigma = data->sigma;
const int sigma = data->sigma;
auto blur_sobel_xx = new FastGaussianBlurOperation();
auto blur_sobel_yy = new FastGaussianBlurOperation();

View File

@ -4,6 +4,10 @@
#include "COM_KuwaharaAnisotropicOperation.h"
#include "BLI_vector.hh"
extern "C" {
#include "IMB_colormanagement.h"
}
namespace blender::compositor {
KuwaharaAnisotropicOperation::KuwaharaAnisotropicOperation()
@ -69,11 +73,16 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou
MemoryBuffer *s_yy = inputs[2];
MemoryBuffer *s_xy = inputs[3];
// BLI_assert all inputs have same size
BLI_assert(image->get_width() == s_xx->get_width());
BLI_assert(image->get_height() == s_xx->get_height());
BLI_assert(image->get_width() == s_yy->get_width());
BLI_assert(image->get_height() == s_yy->get_height());
BLI_assert(image->get_width() == s_xy->get_width());
BLI_assert(image->get_height() == s_xy->get_height());
int n_div = 8; // recommended by authors in original paper
double angle = 2.0 * M_PI / n_div;
double q = 3.0;
const int n_div = 8; // recommended by authors in original paper
const float angle = 2.0 * M_PI / n_div;
const float q = 3.0;
const float EPS = 1.0e-10;
@ -81,9 +90,6 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou
const int x = it.x;
const int y = it.y;
/* Compute orientation */
// todo: make orientation separate operation
// for now use green channel to compute orientation
// todo: convert to HSV and compute orientation and strength on luminance channel
const float a = s_xx->get_value(x, y, 1);
@ -102,8 +108,8 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou
const double strength = (lambda1 == 0 && lambda2 == 0) ? 0 : (lambda1 - lambda2) / (lambda1 + lambda2);
for(int ch = 0; ch < 3; ch++) {
// todo: compute anisotropy and weights on luminance channel to avoid color artifacts
Vector<float> mean(n_div, 0.0f);
Vector<float> sum(n_div, 0.0f);
Vector<float> var(n_div, 0.0f);
Vector<float> weight(n_div, 0.0f);
@ -127,12 +133,17 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou
float ddy2 = (float)dy2;
float theta = atan2(ddy2, ddx2) + M_PI;
int t = static_cast<int>(floor(theta / angle)) % n_div;
float d2 = dx2 * dx2 + dy2 * dy2;
float g = exp(-d2 / (2.0 * kernel_size_));
float v = image->get_value(xx, yy, ch);
zazizizou marked this conversation as resolved
Review

In other performance critical areas we do

const float weight_inv = 1.0f / weight;
a = ... foo * weight_inv ..;
b = ... bar * weight_inv ..;
c = ... baz * weight_inv ..;

Again, not something which i know for sure will show performance impact, but might worth doing so nevertheless.

In other performance critical areas we do ``` const float weight_inv = 1.0f / weight; a = ... foo * weight_inv ..; b = ... bar * weight_inv ..; c = ... baz * weight_inv ..; ``` Again, not something which i know for sure will show performance impact, but might worth doing so nevertheless.
sum[t] += g * v;
var[t] += g * v * v;
float color[4];
image->read_elem(xx, yy, color);
// todo(zazizizou): only compute lum once per region
const float lum = IMB_colormanagement_get_luminance(color);
// todo(zazizizou): only compute mean for the selected region
mean[t] += g * v;
sum[t] += g * lum;
var[t] += g * lum * lum;
weight[t] += g;
}
}
@ -142,13 +153,14 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou
float de = 0.0;
float nu = 0.0;
for (int i = 0; i < n_div; i++) {
mean[i] = weight[i] != 0 ? mean[i] / weight[i] : 0.0;
sum[i] = weight[i] != 0 ? sum[i] / weight[i] : 0.0;
var[i] = weight[i] != 0 ? var[i] / weight[i] : 0.0;
var[i] = var[i] - sum[i] * sum[i];
var[i] = var[i] > EPS ? sqrt(var[i]) : EPS;
var[i] = var[i] > FLT_EPSILON ? sqrt(var[i]) : FLT_EPSILON;
float w = powf(var[i], -q);
de += sum[i] * w;
de += mean[i] * w;
nu += w;
}

View File

@ -3,6 +3,10 @@
#include "COM_KuwaharaClassicOperation.h"
extern "C" {
#include "IMB_colormanagement.h"
}
namespace blender::compositor {
KuwaharaClassicOperation::KuwaharaClassicOperation()
@ -55,6 +59,7 @@ void KuwaharaClassicOperation::update_memory_buffer_partial(MemoryBuffer *output
for (int ch = 0; ch < 3; ch++) {
float sum[4] = {0.0f, 0.0f, 0.0f, 0.0f};
float mean[4] = {0.0f, 0.0f, 0.0f, 0.0f};
float var[4] = {0.0f, 0.0f, 0.0f, 0.0f};
int cnt[4] = {0, 0, 0, 0};
@ -65,30 +70,36 @@ void KuwaharaClassicOperation::update_memory_buffer_partial(MemoryBuffer *output
int xx = x + dx;
int yy = y + dy;
if (xx >= 0 && yy >= 0 && xx < image->get_width() && yy < image->get_height()) {
float v;
v = image->get_value(xx, yy, ch);
const float v = image->get_value(xx, yy, ch);
float color[4];
image->read_elem(xx, yy, color);
const float lum = IMB_colormanagement_get_luminance(color);
if (dx <= 0 && dy <= 0) {
sum[0] += v;
var[0] += v * v;
mean[0] += v;
sum[0] += lum;
var[0] += lum * lum;
cnt[0]++;
}
if (dx >= 0 && dy <= 0) {
sum[1] += v;
var[1] += v * v;
mean[1] += v;
sum[1] += lum;
var[1] += lum * lum;
cnt[1]++;
}
if (dx <= 0 && dy >= 0) {
sum[2] += v;
var[2] += v * v;
mean[2] += v;
sum[2] += lum;
var[2] += lum * lum;
cnt[2]++;
}
if (dx >= 0 && dy >= 0) {
sum[3] += v;
var[3] += v * v;
mean[3] += v;
sum[3] += lum;
var[3] += lum * lum;
cnt[3]++;
}
}
@ -97,6 +108,7 @@ void KuwaharaClassicOperation::update_memory_buffer_partial(MemoryBuffer *output
/* Compute region variances */
for (int i = 0; i < 4; i++) {
mean[i] = cnt[i] != 0 ? mean[i] / cnt[i] : 0.0f;
sum[i] = cnt[i] != 0 ? sum[i] / cnt[i] : 0.0f;
var[i] = cnt[i] != 0 ? var[i] / cnt[i] : 0.0f;
const float temp = sum[i] * sum[i];
@ -112,7 +124,7 @@ void KuwaharaClassicOperation::update_memory_buffer_partial(MemoryBuffer *output
min_index = i;
}
}
output->get_value(x, y, ch) = sum[min_index];
output->get_value(x, y, ch) = mean[min_index];
}
}
}

View File

@ -879,7 +879,7 @@ typedef struct NodeBilateralBlurData {
typedef struct NodeKuwaharaData {
short kernel_size;
short variation;
float sigma;
int sigma;
} NodeKuwaharaData;
typedef struct NodeAntiAliasingData {

View File

@ -9256,8 +9256,9 @@ static void def_cmp_kuwahara(StructRNA *srna)
prop = RNA_def_property(srna, "kernel_size", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "kernel_size");
RNA_def_property_ui_range(prop, 4, 50, 1, -1);
RNA_def_property_ui_text(
prop, "Kernel Size", "Kernel size of filter. The larger the stronger the effect");
prop, "Kernel Size", "Kernel size of filter. Larger values give stronger stylized effect");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
prop = RNA_def_property(srna, "variation", PROP_ENUM, PROP_NONE);
@ -9266,12 +9267,13 @@ static void def_cmp_kuwahara(StructRNA *srna)
RNA_def_property_ui_text(prop, "", "Variation of Kuwahara filter to use.");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
prop = RNA_def_property(srna, "sigma", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "sigma");
prop = RNA_def_property(srna, "sigma", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "sigma");
RNA_def_property_ui_range(prop, 0, 50, 1, -1);
RNA_def_property_ui_text(
prop,
"Sigma",
"Edges get smoothed before applying filter. Sigma controls smoothing degree.");
"Smoothing degree before applying filter. Higher values remove details and give smoother edges");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
}