Cycles: add animation denoising Python operator.

This adds a cycles.denoise_animation operator, which denoises an animation
sequence or individual file. Renders must be saved as multilayer EXR files
with denoising data passes.

By default file path and frame range come from the current scene, and EXR
files are denoised in-place. Alternatively, a different input and/or output
file path can be provided.

Denoising settings come from the current view layer. Renders can be denoised
again with different settings, as the original noisy image is preserved along
with other passes and metadata.

There is no user interface yet for this feature, that comes later.

Code by Lukas with modifications by Brecht. This feature was originally
developed for Tangent Animation, thanks for the support!

Differential Revision: https://developer.blender.org/D3889
This commit is contained in:
2019-02-06 12:57:10 +01:00
committed by Brecht Van Lommel
parent 382fe85e29
commit e379a9ba91
16 changed files with 1418 additions and 48 deletions

View File

@@ -18,9 +18,12 @@
#include "blender/CCL_api.h"
#include "blender/blender_device.h"
#include "blender/blender_sync.h"
#include "blender/blender_session.h"
#include "render/denoising.h"
#include "util/util_debug.h"
#include "util/util_foreach.h"
#include "util/util_logging.h"
@@ -623,6 +626,121 @@ static PyObject *opencl_disable_func(PyObject * /*self*/, PyObject * /*value*/)
}
#endif
static bool denoise_parse_filepaths(PyObject *pyfilepaths, vector<string>& filepaths)
{
if(PyUnicode_Check(pyfilepaths)) {
const char *filepath = PyUnicode_AsUTF8(pyfilepaths);
filepaths.push_back(filepath);
return true;
}
PyObject *sequence = PySequence_Fast(pyfilepaths, "File paths must be a string or sequence of strings");
if(sequence == NULL) {
return false;
}
for(Py_ssize_t i = 0; i < PySequence_Fast_GET_SIZE(sequence); i++) {
PyObject *item = PySequence_Fast_GET_ITEM(sequence, i);
const char *filepath = PyUnicode_AsUTF8(item);
if(filepath == NULL) {
PyErr_SetString(PyExc_ValueError, "File paths must be a string or sequence of strings.");
Py_DECREF(sequence);
return false;
}
filepaths.push_back(filepath);
}
Py_DECREF(sequence);
return true;
}
static PyObject *denoise_func(PyObject * /*self*/, PyObject *args, PyObject *keywords)
{
static const char *keyword_list[] = {"preferences", "scene", "view_layer",
"input", "output",
"tile_size", "samples", NULL};
PyObject *pypreferences, *pyscene, *pyrenderlayer;
PyObject *pyinput, *pyoutput = NULL;
int tile_size = 0, samples = 0;
if (!PyArg_ParseTupleAndKeywords(args, keywords, "OOOO|Oii", (char**)keyword_list,
&pypreferences, &pyscene, &pyrenderlayer,
&pyinput, &pyoutput,
&tile_size, &samples)) {
return NULL;
}
/* Get device specification from preferences and scene. */
PointerRNA preferencesptr;
RNA_pointer_create(NULL, &RNA_UserPreferences, (void*)PyLong_AsVoidPtr(pypreferences), &preferencesptr);
BL::UserPreferences b_preferences(preferencesptr);
PointerRNA sceneptr;
RNA_id_pointer_create((ID*)PyLong_AsVoidPtr(pyscene), &sceneptr);
BL::Scene b_scene(sceneptr);
DeviceInfo device = blender_device_info(b_preferences, b_scene, true);
/* Get denoising parameters from view layer. */
PointerRNA renderlayerptr;
RNA_pointer_create((ID*)PyLong_AsVoidPtr(pyscene), &RNA_SceneRenderLayer, PyLong_AsVoidPtr(pyrenderlayer), &renderlayerptr);
PointerRNA crenderlayer = RNA_pointer_get(&renderlayerptr, "cycles");
DenoiseParams params;
params.radius = get_int(crenderlayer, "denoising_radius");
params.strength = get_float(crenderlayer, "denoising_strength");
params.feature_strength = get_float(crenderlayer, "denoising_feature_strength");
params.relative_pca = get_boolean(crenderlayer, "denoising_relative_pca");
params.neighbor_frames = get_int(crenderlayer, "denoising_neighbor_frames");
/* Parse file paths list. */
vector<string> input, output;
if(!denoise_parse_filepaths(pyinput, input)) {
return NULL;
}
if(pyoutput) {
if(!denoise_parse_filepaths(pyoutput, output)) {
return NULL;
}
}
else {
output = input;
}
if(input.empty()) {
PyErr_SetString(PyExc_ValueError, "No input file paths specified.");
return NULL;
}
if(input.size() != output.size()) {
PyErr_SetString(PyExc_ValueError, "Number of input and output file paths does not match.");
return NULL;
}
/* Create denoiser. */
Denoiser denoiser(device);
denoiser.params = params;
denoiser.input = input;
denoiser.output = output;
if (tile_size > 0) {
denoiser.tile_size = make_int2(tile_size, tile_size);
}
if (samples > 0) {
denoiser.samples_override = samples;
}
/* Run denoiser. */
if(!denoiser.run()) {
PyErr_SetString(PyExc_ValueError, denoiser.error.c_str());
return NULL;
}
Py_RETURN_NONE;
}
static PyObject *debug_flags_update_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pyscene;
@@ -783,6 +901,9 @@ static PyMethodDef methods[] = {
{"opencl_disable", opencl_disable_func, METH_NOARGS, ""},
#endif
/* Standalone denoising */
{"denoise", (PyCFunction)denoise_func, METH_VARARGS|METH_KEYWORDS, ""},
/* Debugging routines */
{"debug_flags_update", debug_flags_update_func, METH_VARARGS, ""},
{"debug_flags_reset", debug_flags_reset_func, METH_NOARGS, ""},