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" {
|
||||
#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);
|
||||
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");
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
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.