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" {
zazizizou marked this conversation as resolved Outdated

No need to `extern "C"`` here, the header does it already.

Generally, we should not be adding such statements around #include statements, as it makes it very easy to break things when things move to C++. Some headers might need the linking specializer, and for those it is to be changed in the header itself.

No need to `extern "C"`` here, the header does it already. Generally, we should not be adding such statements around `#include` statements, as it makes it very easy to break things when things move to C++. Some headers might need the linking specializer, and for those it is to be changed in the header itself.
#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);
zazizizou marked this conversation as resolved Outdated

This doesn't seem to work. User can still set values outside the specified range. This is also the case for Vector Blur. Is there another way to specify UI range?

This doesn't seem to work. User can still set values outside the specified range. This is also the case for Vector Blur. Is there another way to specify UI range?

The UI range is the soft limit. It applies when an artist drags the slider, but it is still possible to click on the property and type a value which is within a hard limit.

The hard limit is controlled by RNA_def_property_range.

You can see example in the Subsurf modifier:

RNA_def_property_range(prop, 0, 11);
RNA_def_property_ui_range(prop, 0, 6, 1, -1);
The UI range is the soft limit. It applies when an artist drags the slider, but it is still possible to click on the property and type a value which is within a hard limit. The hard limit is controlled by `RNA_def_property_range`. You can see example in the Subsurf modifier: ``` RNA_def_property_range(prop, 0, 11); RNA_def_property_ui_range(prop, 0, 6, 1, -1); ```

This did solve it, thanks!

This did solve it, thanks!
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");
}