Compositor: add new node: Kuwahara filter #107015
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
|
||||
#include "COM_KuwaharaClassicOperation.h"
|
||||
|
||||
extern "C" {
|
||||
zazizizou marked this conversation as resolved
Outdated
Sergey Sharybin
commented
No need to `extern "C"`` here, the header does it already. Generally, we should not be adding such statements around 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -879,7 +879,7 @@ typedef struct NodeBilateralBlurData {
|
|||
typedef struct NodeKuwaharaData {
|
||||
short kernel_size;
|
||||
short variation;
|
||||
float sigma;
|
||||
int sigma;
|
||||
} NodeKuwaharaData;
|
||||
|
||||
typedef struct NodeAntiAliasingData {
|
||||
|
|
|
@ -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
Habib Gahbiche
commented
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?
Sergey Sharybin
commented
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 You can see example in the Subsurf modifier:
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);
```
Habib Gahbiche
commented
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");
|
||||
}
|
||||
|
||||
|
|
In other performance critical areas we do
Again, not something which i know for sure will show performance impact, but might worth doing so nevertheless.