Simple MDegrain Mod — SMDegrain()

 

Abstract

Author: Dogway
Version: v2.2d
Download: http://doom10.org/index.php?topic=2178.0
Category: Temporal Denoiser Script Function
Date: 05-March-2013

 

Table of contents

 

Dependencies     

Required
MaskTools2 and one of the MVTools2 plugins (mod version recommended) Optional
Use Vit's plugin mods when possible for more MT stability:

 

Aknowledgements     

Original SMDegrain() by Caroliano

This function uses code from Didée, and cretindesalpes' creations, and has parts inspired by LaTo's functions (old readme, Show panel, etc) and Jawed's Killer() function, apart of relying on third-party tools for certain features.

Special Thanks go to: cretindesalpes, Didée, Gavino, Sagekilla, and MVtools people.

 

Introduction     

SMDegrain is not a magic function, but rather a convenient one (magic comes from mvtools as you would expect). It just takes the reiterative code blocks needed for mvtools+mdegrain denoising and wraps them into this function. It has internal switches for when you deal with interlaced or YUY2 content, so all you need is to enable the parameter in the function argument, saving you from writting long lines of code and preventing from possible mistakes. YUY2 is automatically detected so it even doesn't need to be actively enabled. Apart of that it adds some small and well founded enhacements listed below, like ability for the so much discussed prefilters, slightly better motion vectors (thanks to an internal TV->PC luma expansion), option for nnedi3 subpixel accuracy, globals import/export, some dark luma enhacement parameters (for better motion vectors, not the actual clip), show panel, and contrasharp option, among others:

High bitdepth denoising: This is the first wrapper function to support 32bit (16bit pipeline) mdegrain denoising. Depending on your script code, it can cause memory leak and stability issues, use at your own risk or deal with plugin support for Dither.
Higher temporal radius (tr>3): As the above feature the mvtools2 mod allows (and hence this script) for real (no hacks) higher temporal radius.
Contrasharpening: The pretty much standarized sharpening method created by Didée is blended into the function, so you can easily use it in 16bit pipelines. An strength biased method is also possible. A CClip parameter is also supplied so you can choose another source to sharpen from, other than the default SMDegrain() input.
YUY2 support: It automatically supports YUY2 sources for all the given parameters.
Interlaced support: It supports interlaced sources, for YUY2 color spaces and most parameters as well.
Extended Subpixel Accuracy: nnedi3 is added as the 4th option for this setting, not strictly necessary in most cases, but if you are a quality mad try to set it on.
Better Motion Vectors: An internal conversion from TV levels (reduced values range) to PC levels (extended values range) is done to the clip the motion analysis will be performed on, so you can get better motion vectors (up to 16%)
Prefilters: An easy parameter to load generalist prefilters that perform well in most situations. It also accepts loading your personal prefiltered clips.
Motion Vectors Globals Input/Output: Reuse motion vectors globals for faster processing, or just use SMDegrain() as a shortcut for creating nice quality motion vectors.
RefineMotion Option (MRecalculate): If time is not an issue, you should use this one for a quality boost.
Show Panel: Everything is off by default, but some settings change in context. Check what is happening behind the scenes and have a fast look at all your settings.
Robust: Everything of the above works with each other no problem (interoperability), also in the process some error proof, bugfixes and bug workarounds were implemented or at least warned so you know that most of what is done is reliable, bugs free and optimized. The general idea is to serve as an user friendly front end for vanilla mvtools2+mdegrain or as the original script creator entitled "To make your scripts shorter and less geeky". Most parameters are turned off or defaulted to mvtools2 defaults and features are based on popular general conceived procedures.

Parameters are arranged in 3 blocks.

Basic: For people who just need the most basic parameters because either they don't have the time, knowledge or interest into looking for complex settings. Or they are content with defaults (pretty much mvtools defaults)
High Bit Depth: There's not much to think about this. This just enables (default is disabled), high bit depth denoising. That is, more quality, but also needs more processing power hence slower. Depending on your script code, it can cause memory leak issues.
Advanced: If you run into some problems, are nitpicking or just want to fine tune your settings according to the source, have a look at this block, it contains the rest and bulk of the function parameters.


Temporal denoising is quite a generic procedure for noise cleaning in most type of video sources. The only limit for SMDegrain() being when there are (very) high levels of grain, in which case you would need to rely on more "creative" and source centric solutions (see this and prefilter notes), but those are the rare cases.

To picture what the function does, a simple SMDegrain() call matches the next code (for SD resolutions):

super_search = Dither_Luma_Rebuild(S0=1.0,c=0.0625).MSuper(rfilter=4)

bv3 = super_search.MAnalyse(isb = true,  delta = 3, overlap= 4)
bv2 = super_search.MAnalyse(isb = true,  delta = 2, overlap= 4)
bv1 = super_search.MAnalyse(isb = true,  delta = 1, overlap= 4)
fv1 = super_search.MAnalyse(isb = false, delta = 1, overlap= 4)
fv2 = super_search.MAnalyse(isb = false, delta = 2, overlap= 4)
fv3 = super_search.MAnalyse(isb = false, delta = 3, overlap= 4)

MDegrain3(MSuper(levels=1), bv1, fv1, bv2, fv2, bv3, fv3, thSADC=200)


As you see no wizardry. The only changes made are the TV->PC luma expansion (Dither_Luma_Rebuild(S0=1.0,c=0.0625) similar to color_yuv(levels="TV->PC")) so you have more range to detect motion vectors, the rfilter=4 for a bit more quality super search, the changed default of overlap from 0 to 4 and thSADC from 400 to 200 for safer results. Nothing else. The problem is that as soon you want to change a little thing like say temporal radius it requires you to edit in more than a few places, so this is very annoying and so forth the main reason for SMDegrain().

 

Quick Workflow Guide     

 

Parameters     


SMDegrain (clip input, int "tr", int "thSAD", int "thSADC", bool "RefineMotion", val "contrasharp", clip "CClip", bool "interlaced", int "plane", int "Globals",                             \
                                  int "pel", int "subpixel", val "prefilter", int "blksize", int "overlap", int "search", bool "truemotion", int "limit", int "limitc", int "thSCD1", int "thSCD2", \
                                  bool "chroma", int "hpad", int "vpad", bool "lsb", bool "lsb_in", bool "lsb_out", int "mode", val "Show", float "Str", float "Amp")




Basic     


tr

[int: 1, 2, "3", 4, 5, 6, ...]

Temporal radius. Select between MDegrain 1, 2, 3, 4, 5, 6 or higher. Higher is better, but also much slower and improvements get less drastic.
This can be considered the strength of the denoising. tr 4, 5 and 6 (limit is tr=128, 64 for interlaced) require Dither's MVTools2 mod.

thSAD, thSADC

[int:"400"], [int:"200"]

"Sum of Absolute Differences" threshold. This is the spatial difference threshold where the motion search will consider whether to denoise given the next formula: Denoise_Weight = max( 0, 1 - 2*blockSAD² / (thSAD² + blockSAD²) )
This spatial difference will be compared in blocks (blksize). If your noise is not getting into consideration for the denoising try raising this value, or reduce its SAD with previous prefiltering.
You can alternatively raise the block size (blksize), which will likely even blocks SAD. Low values can result in staggered/blotchy denoising, large values can result in ghosting and artifacts.Values in the range 200~600 are usual.
thSADC is the same logic applied to chroma planes, since there are less differences on the chroma planes it uses by default thSAD/2 for safer chroma results.

*Use slightly greater values than you are used to in other mdegrain based functions since the internal clip for the motion search in SMDegrain() has the contrast increased.

Contrasharp

[bool: true, "false"] or [int: 0 ~ 100 ]

Contrasharpening is a technique that compares the differences between the clip before blurring (original) and after blurring (filtered), and sharpens locally with consequent strength.
By default the before "sharp" clip is the one used as input for SMDegrain(), the "after" clip is the denoised clip. Independently a "before" clip can be specified with the CClip parameter (See below)

Contrasharp when set to "true", will use Didée's Contrasharpening() function (or the modded version ContraHD() for HD sources) which:
"Sharpens the denoised clip, but doesn't add more to any pixel than what was removed previously"
In the practice you will get a slightly sharper result than the source, which is welcome.
In the other hand if your parameter input is an integer, LSFmod() will be used instead. It will be much slower, but maybe better for certain sources.
Its value will serve as a contrasharpening multiplier, use one around 50 for similar strength as Contrasharpening().
The drawbacks for LSFmod (integer inputs) are that they are no-suitable for HD sources and no support for planar (YUY2) inputs.

RefineMotion

[bool: true, "false"]

Refines and recalculates motion data of previously estimated (by MAnalyse) motion vectors.
Turn it on for better motion vectors, specially when dealing with ghosting issues, small details or lineart fading and whatnot.
  *It won't enhace much if you use a prefilter (in case you use it) so strong that blurs too much or kills all the details you are aiming to protect in first place.

plane

[int: 0, 1, 2, 3, "4"]

Select the planes you wish to process:

  *Keep in mind that plane=4 (2 and 3 as well) can sometimes create chroma smearing. In such case I recommend denoising chroma planes in the spatial domain.

Interlaced

[bool: true, "false"]

If you want to denoise an interlaced source set this parameter to true. Output will also be interlaced. Just keep in mind that if you pair this with lsb_out=true then you will have to add Weave().DitherPost(mode=6,interlaced=true) when converting back to 8 bit.




High Bit Depth Processing And Dithering     


lsb

[bool: true, "false"]

This enables 32 bit depth precision for denoising.
Avoids banding thanks to a dither algorithm friendly for optimum encodings (When no further non-edge processing is done)
For this to be used you will need the Dither package with its version of MVTools2.

lsb_in

[bool: true, "false"]

Set it to true if you use SMDegrain() in a 16 bits filter chain. This will make your output more optimized for encodings with same or better quality.
For this to be used you will need the Dither package with its version of MVTools2. *(EXPERIMENTAL, use under test environment)

lsb_out

[bool: true, "false"]

If you set lsb to true, you can also choose to output in 16 bits (an internal 32 bit->16 bit conversion is done) so you can keep filtering in 16 bits
or/and use your own Ditherpost().
*Use the helpers at the end of the SMDegrain() script to make Ditherpost() able to use when dealing with YUY2 formats.
For this to be used you will need the Dither package with its version of MVTools2. Depending on your script code, it can cause memory leak issues.

mode

[int: -1, "0", 1, 2, 3, 4, 5, 6, 7, 8]

If you process in 32bit and lsb_out (16bit output) is not enabled, a dithering method must be chosen for the 32bit->8bit conversion. (Interlaced content is locked to mode=6)
For this to be used you will need the Dither package with its version of MVTools2.




Advanced     


pel

[int: 1, "2", 4]

Accuracy of the motion estimation.

subpixel

[int: 0, 1, "2", 3]

Subpixel accuracy. It chooses an interpolation method for pel=2 or 4. This is the 'sharp' parameter in msuper(),
although it isn't related to output sharpness but better accuracy for the motion estimation.
For subpixel=3 you will need the nnedi3 plugin. Very useful when used along pel=4 on very small sources, although overkill and unnecesary in most cases.

prefilter

[int: "0", 1, 2, 3] or [clip: - ]


Denoises a version of the clip that will be used to obtain the motion vectors.
Useful for very damaged/grainy sources. This will help to get better motion vectors.
For sources with Gibbs noise, especially on anime, try prefilter=1 or 2 or better yet strong median filters (through clip input)
in order to soothe the high SAD of sharp edges. For blocky sources, or a general more quality/safer prefilter use option 3 (dfttest)
 By default dfttest outputs a sstring txt file to your script folder, you can delete it. This doesn't happen when using the modded dfttest (version 1.9.2 or higher) of the Dither tools.
For HD sources prefilter=2 makes more sense than 1, because it uses a wider denoising window.

If instead you want to use your own prefilter denoiser, just define it in a variable and reference it here. Remember to:
You will likely want to use spatial denoisers like median filters (removegrain=17, medianblur, etc).
For very grainy or fizz grain noise you can try prefiltering in temporal or if this is not enough,
try adding a new SMDegrain() line again but without globals. Use lsb_in, lsb_out according so results are smooth.
Another trick for fizz grain is to serve a prefiltered clip where bright values are more denoised than dark values,
you can do this through luma masks with masktools2 code. Search in Doom9 for reference.

Remember how interlaced sources must be prefiltered (when you are serving it as your own custom prefilter):
For interlaced content use: separatefields().spatial_prefilters_here()
For temporal filters odd and even frames must be processed separately: separatefields().interleave(selecteven().TempPrefilt(),selectodd().TempPrefilt())

SMDegrain() (its core: mdegrain in fact) is likely to produce blending artifacts or oversmooth in dark areas specially on cartoons, to tackle this see RefineMotion
or expand the luma range in the darks. For this there are many options, you can use your preferred gamma enhacer and input it as the prefilter,
or use the examples below for linear brightening (masktools2) and input it as a prefilter as well. Or at last and the recommended solution,
to use the built-in Str (strength) and Amp (amplitude) parameters for a curve type dark enhacement, check the effects by enabling Show
and changing your previewing conditions to PC levels.

For brightening dark values linearly (brighter parts less brightening)
str=1
mt_lut("x x "+string(str)+" 6 * - 255 - abs 255 / ^ x 1 - abs +")

Or for a more contrasty image (although not as much dark enhacing - not linear)
str=40
mt_lut("x x "+string(str)+" 6 * - 255 - abs 255 / ^ "+string(str)+" / x 1 - abs +")


Good resources for custom prefilter code:

Notes: To sum it up; generally in denoising, temporal filters are always preferred due to its natural look (versus spatial filters).
But more often than not for good results prefiltering plays a key role on the output quality, and setting it up nicely can be looked as an art by itself.

If there's a reached point where no prefiltering, no thSAD tweaking, etc, makes able to denoise a certain motion part/area/scene,
then this is due to the motion vectors preventing them from being "denoised" (also read "artifacted"), in which case you will need to rely on alternative solutions (motion compensation, filters like MCTemporalDenoise(), TemporalDegrain(), Multilevel mdegrain, etc) or just use plain spatial filters with motion masks.
Creating these discerning masks for static and motion scenes and combining them is a research task on the end user side.

Str

[float: 0.0 ~ 8.0] Default: 1.0 (inactive)

With this parameter you control the strength of the brightening of the prefilter clip. This is good when problems with
dark areas arise (check the above explanation). The benefit of using this and not an external function is because this
is done in the same stage as the TV->PC range conversion thus more memory friendly and less quantization artifacts.

Amp

[float: 0.0 ~ 1.0] Default: 0.0625

Use this together with Str (active when Str is different from 1.0). This defines the amplitude of the brightening in the luma range, for example by using 1.0 all the
luma range will be used and the brightening will find its peak at luma value 128 in the original. Default is 0.0625 (1.0/16) which just sits over luma value 16 (256/16)
This and the Str parameters are extrapolated arguments of a cretindesalpes' function, you can find a graph and more insight explanations at the original post.
Check these 2 parameters effects by enabling Show.

blksize

[int: 4, "8", 16]

Size of a block (horizontal). (Default 16 for HD material)
Larger blocks are less sensitive to noise, are faster, but also less accurate.

overlap

[int: "blksize/2"]

Must be *even* and *less* than block size. Common overlap values: blksize/4 or blksize/2.
The greater overlap, the more blocks number, and the lesser the processing speed.

Search

[int: 0, 1, 2, 3, "4", 5, 6, 7]

See details at MVTools2 documentation.

Truemotion

[bool: "true", false] * "false" for HD

Truemotion is a preset of some parameters values. It allows easy to switch default values of all "true motion" parameters at once.
Set it 'true' for true motion search (high vector coherence), set it 'false' to search motion vectors with best SAD.
Default is 'true' (as in MVtools2 default, and contrary to MCTemporalDenoise()). By turning it off it could protect better low frequency details (shading) when in motion (i.e. walls, grounds, clouds texture, motion blur, etc),
and also protect from tiny details loss but it could leave more noise unfiltered.
Default is 'true' for smoother/cleaner (more temporal coherence) high frequency details or lineart. For HD sources where the main problems are just excessive grain,
I have defaulted it to false, this is not only faster but honors detail accuracy (the main existing point for HD sources) and overall object shading.

Chroma

[bool: "true", false]

Takes chroma planes into consideration for calculating the motion vectors.
Turn it off for more speed with little cost in quality, or when your chroma is not reliable enough for motion estimation.
  *Whatch out if you turn this off when processing chroma planes (aka luma vectors when chroma denoising), it has shown problems in previous tests.


Hpad, Vpad

[int: "blksize"]

It is horizontal/vertical padding added internally to source frame (both left and right, and top and bottom). (Default 0 for HD material)
Small padding is added for more correct motion estimation near frame borders.
Try to have clean borders in your source (no NAB/black borders) to start with before tweaking this setting.
If necessary, crop beforehand ideally in multiples of 16. Turn it to 0 if you are running out of resources in your system.


thSCD1

[int: "400"] *int(pow((blksize*2.5),2))

Threshold which decides whether a block has changed between the previous frame and the current one. So it is one of the thresholds used to tweak the scene changes detection engine. Raising it will lower the number of blocks detected as changed. It may be useful for noisy or flickered video. To fix blending on scene changes on dark scenes, try first the Str and Amp parameters.


thSCD2

[int: 0~"130"~255]

Threshold which sets how many blocks have to change for the frame to be considered as a scene change.


limit,limitC

[int: 1 ~ "255"]

Maximal change of pixel luma/chroma (post-process like DeGrainMedian plugin
and LimitChange function of SSETools plugin, to prevent some artifacts)


CClip

[clip: - ]

Define here a variable name referencing an earlier stage of your processing chain as the sharp version for the contrasharpening to compare against.
Just keep in mind video resolution must be the same size and the features comparable.


Globals

[int: "0", 1, 2, 3 ]

With this parameter you can, among other things, load pre-processed motion vectors (Globals=1), so you save doing this step again.

The integers define what do you want to do with the global variables. Default is 0.


Some parameters MUST MATCH those from the processed vectors stage (e.g. Globals=2 or 3) and the read vectors stage (e.g. Globals=1). pel=, subpixel=, chroma= and vpad/hpad. Other than that the only settings that work when reading (Globals=1) are: tr=, thSAD=, plane, limit, limitc, contrasharp, CClip, interlaced and the lsb parameters. Others are ignored.
Global names that can be reused are: Super, bv1, fv1, bv2, fv2, bv3, fv3, bv4, fv4, bv6, fv6, vmulti.

e.g.:
                         SMDegrain(tr=3,thSAD=400,globals=3)    # Outputs vectors

                         SMDegrain(tr=3,thSAD=400,globals=1)    # You can use a lower "tr" or "thSAD" if you want
or

                         SMDegrain(tr=3,thSAD=400,globals=3)    # Outputs vectors

                         Super = MSuper(levels=1)               # Add this line just before if you have some processing between Globals Output and Input.
                         MDegrain3(Super, bv1, fv1, bv2, fv2, bv3, fv3, thSAD=400)



Show

[bool: true, "false" ] or [string: "Speed", "Memory", "Quality" ]

This will show the prefiltered clip to be used for the motion search to the left(<--), and the used parameters list to the right (-->)

If the parameters panel is broken i.e. overlapping lines, hidden lines, etc, most likely your settings are wrong, (e.g. lsb_in=true when input is 8bit)
so it will also work well as a debug function. If you set Show to a string as indicated above, related parameters will be highlighted to guide you on
correctly tweaking the function. This is loosely based, so it doesn't exclude you from reading carefully every parameter explanation.

Even when you are not using any prefiltering, the clip to be used for the motion search where you obtain the motion vectors,
will be converted from TV levels to PC levels, the only exception being when using Globals=1 (Read) in which case the "prefiltered" clip
will be exactly the same as input clip (that is no luma conversion, although constrained to 8 bit). Additionally you can tweak the dark expansion with
Str and Amp parameters and check the effects in the left panel, but be aware to change your viewing conditions as it is in PC levels.

The left panel preserves its properties so you can crop out the right panel and use it as a prefilter clip of any msuper().
Therefore if you are not processing chroma for motion analysis(chroma=false) the prefiltered clip will be green, don't panic, this is correct, this happens when the
U and V planes are set to a value of 0 which is a dummy value meaning nothing or "discard". In this regard for previewing tasks with prefilterings
or just the Str and Amp effect simply set chroma to true temporally. For YUY2 sources set Planar2Interleaved() after cropping for previewing.

 

Final Notes     

If there is an important parameter not implemented,
you have any issue or found a bug, please don't hesitate
and ask in its Doom10 thread:

(http://doom10.org/index.php?topic=2178.0)

 

Example Scripts     


A typical call compatible with official mvtools2 (mvtools2+masktools2 only needed)
SMDegrain(tr=3,thSAD=400,interlaced=true,prefilter=1,contrasharp=true)

Good compromise between speed and quality (denoising with high precision)
SMDegrain(tr=3,thSAD=400,contrasharp=true,refinemotion=true,lsb=true)

Similar to the previous example, the next is a personal preference. Turned all chroma off for safeness (chroma is rarely temporally reliable enough) and speed.
SMDegrain(tr=3,thSAD=400,contrasharp=true,refinemotion=true,lsb=true,chroma=false,plane=0)

This is a good example to show that interlaced YUY2 sources can be processed seamlessly.
mod4 (modulus 4) interlaced YUY2 source
SMDegrain(tr=4,thSAD=300,interlaced=true,contrasharp=true,lsb=true)

HD sources are detected automatically, and as so settings are optimized for speed; pel=1, hpad=0, vpad=0, blksize=32, overlap=16, truemotion=false
HD source (from 1280x720 pixels up)
SMDegrain(tr=3,thSAD=400,prefilter=2,refinemotion=true,lsb=true,chroma=false,plane=0)

For dealing with dark scenes (you could also add truemotion=false)
SMDegrain(tr=6,thSAD=300,contrasharp=true,str=2.0,refinemotion=true,lsb=true)

Tackling a grainy source:
pre=fluxsmootht(3).removegrain(11)
SMDegrain(tr=6,thSAD=500,contrasharp=30,prefilter=pre,str=1.2,refinemotion=true,lsb=true)

Here 2 things are shown; how to work on a 16bit pipeline, and reference a prior state of the script for the contrasharpening process. This is a potential script code for memory leak issues with Dither, lack of official support leads me to add this warning and suggestions for reports to the "Color banding and noise removal" thread for further support.
sharp=last
dfttest(tbsize=1,sigma=10,lsb=true)

SMDegrain(tr=3,thSAD=300,CClip=sharp,lsb_in=true,lsb_out=true)

LinearResize(854,480,lsb_in=true, mode=0)

(Re)using motion vectors globals
SMDegrain(tr=1,thSAD=400,prefilter=3,str=1.4,globals=3)    # Output vectors only
MFlowFps(Super, bv1, fv1, num=60,den=1)

 

Changelog     

v2.2d (05-03-2013)

v2.1d (09-09-2012)

v2.0d (07-06-2012)

v1.9d (23-03-2012)

v1.8d (18-01-2012)

v1.7d (11-01-2012)

v1.6d (24-12-2011) (first open release)

v1.5d (14-10-2011)

v1.4d (16-09-2011)

v1.3d (14-09-2011)

v1.2d (13-09-2011)

v1.1d (12-09-2011)

v1.0d (09-09-2011)

v0.9d (09-09-2011)

v0.8d (05-09-2011)

v0.7d (05-09-2011)

v0.6d (04-09-2011)

v0.5d (19-07-2011)

v0.4d (03-06-2011)

v0.3d (04-03-2011)

v0.2 (24-01-2010)

v0.1