logo Sign In

LDnoise : A Laserdisc denoiser for Avisynth.

Author
Time
 (Edited)

I came up with this idea a couple of weeks ago and it worked a lot better than I thought it would so I'm posting it here to hopefully get some feedback and so others can use it if they want.

There are a few requirements for this function to work properly:

1.) Clip needs a resolution of 720x480.

2.) Must be in YUY2 colorspace.

3.) Must be widescreen format. (This denoiser samples the black bars for noise and uses this information to denoise the film area.)

4.) Requires MVTOOLS2 and DFTTEST along with knowledge of avisynth.

function LDnoise(clip input, float "strength", int "mc", int "temporalframes",\

int "blksize", int "search", int "searchparam", int "overlap", int "dct")

{

 

# Set default options. 

 

strength=default(strength,1)

temporalframes=default(temporalframes,1)

mc = default(mc, 0)

 

# Prepare supersampled clip.

 

super = input.MSuper(levels=6,chroma=true)

 

# Motion vector search.

 

b5vec = MAnalyse(super, delta=5, isb=true,chroma=true, search=search, searchparam=searchparam, overlap=overlap, dct=dct)

b4vec = MAnalyse(super, delta=4, isb=true,chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)

b3vec = MAnalyse(super, delta=3, isb=true,chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)

b2vec = MAnalyse(super, delta=2, isb=true,chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)

b1vec = MAnalyse(super, delta=1, isb=true,chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)

f1vec = MAnalyse(super, delta=1, chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)

f2vec = MAnalyse(super, delta=2, chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)

f3vec = MAnalyse(super, delta=3, chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)

f4vec = MAnalyse(super, delta=4, chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)

f5vec = MAnalyse(super, delta=5, chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)

 

# Motion Compensation.

 

b5clip = MCompensate(input,super, b5vec)

b4clip = MCompensate(input,super, b4vec)

b3clip = MCompensate(input,super, b3vec)

b2clip = MCompensate(input,super, b2vec)

b1clip = MCompensate(input,super, b1vec)

f1clip = MCompensate(input,super, f1vec)

f2clip = MCompensate(input,super, f2vec)

f3clip = MCompensate(input,super, f3vec)

f4clip = MCompensate(input,super, f4vec)

f5clip = MCompensate(input,super, f5vec)

 

# Create compensated clip.

 

interleaved = mc >= 5 ? Interleave(f5clip, f4clip, f3clip, f2clip, f1clip, input, b1clip, b2clip, b3clip, b4clip, b5clip) :

\ mc == 4 ? Interleave(f4clip, f3clip, f2clip, f1clip, input, b1clip, b2clip, b3clip, b4clip) :

\ mc == 3 ? Interleave(f3clip, f2clip, f1clip, input, b1clip, b2clip, b3clip) :

\ mc == 2 ? Interleave(f2clip, f1clip, input, b1clip, b2clip) :

\ mc == 1 ? Interleave(f1clip, input, b1clip):

\ input

 

#Perform DFTTEST

 

params="""dfttest(y=true,u=true,v=true,f0beta=0.5,sigma=0,dither=0,sbsize=25,sosize=20,tbsize="""+string(temporalframes)+""",tosize="""+string(temporalframes)+"""/3,nstring=\

    "a:"""+string(strength)+""" \

    "+string(current_frame)+",0,440,10 \

    "+string(current_frame)+",0,10,10 \

    "+string(current_frame)+",0,10,340 \

    "+string(current_frame)+",0,10,680 \

    "+string(current_frame)+",0,440,680 \

    "+string(current_frame)+",1,440/2,3 \

    "+string(current_frame)+",1,3,3 \

    "+string(current_frame)+",1,3,340/2 \

    "+string(current_frame)+",1,3,680/2 \

    "+string(current_frame)+",1,440/2,350 \

    "+string(current_frame)+",2,120,3 \

    "+string(current_frame)+",2,3,3 \

    "+string(current_frame)+",2,3,340/2 \

    "+string(current_frame)+",2,3,680/2 \

    "+string(current_frame)+",2,440/2,680/2")

    """

filter=eval("scriptclip(interleaved,params)")

return SelectEvery(filter, mc * 2 + 1,mc)

 

}

 

There are only 3 parameters that you need to adjust really.  They are:

 

Strength: It defaults to 1.  Valid range is .01 and up.  1-10 is a good area to stay within. Every source needs tweaked however, so numbers can vary wildly.  There is no set and forget.

Temporalframes: Defaults to 1. 1 enables spatial only filtering.  Must be an odd number. Numbers above 9 are overkill and will probably cause a crash. I recommend 1 or 3.

MC: Defaults to 0. Valid range is 0-5. Defines the number of motion compensated frames. I recommend setting this to 5 if you have enabled temporal filtering with the temporalframes parameter.

 

This is basically a motion compensated dfttest wrapped into a function.  It's different from just running dfttest and letting the denoiser blindly search for noise though.  It samples noise from 5 separate blocks in the widescreen bars, for the frame that it is processing, in the luma and chroma planes and applies this information when denoising. If you enable temporal frames it starts comparing noise from the previous and future frames as well.  The motion compensation prevents stuff like laser bolts from getting erased when temporal filtering is enabled, etc.  It has the same problems as most denoisers and if you crank it up everything will turn to plastic.  It's strong point is that it is much better at keeping detail with less artifacting. It can cause banding with higher values, especially in the black bar areas.

This won't work on the GOUT,for example, because there isn't any analog noise in the black bar areas as far as I know. BTW, this will work with the JSC captures because I've set the coordinates for the noise sampling blocks outside of any areas that would contain subtitling.

Luke threw twice…maybe.

Author
Time

althor1138 said:

I came up with this idea a couple of weeks ago ... (This denoiser samples the black bars for noise and uses this information to denoise the film area.)

Well done! I've always been enamored of temporal noise self-cancellation but never considered spacial noise (though out of useful picture area) for self-cancellation in the same fashion. Your idea is clever, to the point of genius.

I have used the MVDenoise() [from ver. 1.x, now superseded] and MVAnalyse() functions from Manao & Fizick's Avisynth filter MVTools http://avisynth.org.ru/mvtools/mvtools2.html to good cleaning effect (shown here with other corrections, so just ignore those parts):

Only one temporal frame, backward & forward, made for effective and unobtrusive de-noising of this 16mm frame (reducing multi-generation grain instead of electronic noise -- both "random"). However, experience has shown that one cannot use too many backward & forward frames without risking obvious noise crawl from repeated usage of those frames. (This may be a filter flaw, but it's hard to tell. So much of this filter does not have quantifiable feedback on exactly what is being done by user settings.)

Could you post a representative before/after full-sized frame .PNG?

 

Author
Time
 (Edited)

I chose this frame with some very high settings.  I think it shows how well it works in choosing noise over detail. The fine strands of hair stay pretty much intact after the denoising. Also, the detail in the soldier's uniform in the background seems to have weathered the eraser well too.

BTW, I've found that enabling temporal frames with this setup doesn't always do much.  The most important thing it seems is to have enough noise samples to get a complete "picture" of all the noise contained in the picture.  I think I could add at least 2 more noise sampling areas to the script without interfering with subtitle areas.

Luke threw twice…maybe.

Author
Time
 (Edited)

Here is something that is maybe a bit more informative.  The top is fft3dfilter.  Bottom is LDnoise. I adjusted the two images until I couldn't see any grain in the sky.  As best as my eyes can do anyway.  If you look close there is detail lost in the sand on the fft3d frame.

EDIT: Here's the original for comparison as well.

 

Luke threw twice…maybe.

Author
Time
 (Edited)

Well done!

But if someone copy it verbatim, it doesn't work... here you are the working version (only changed some spacing and CR):

function LDnoise(clip input, float "strength", int "mc", int "temporalframes",\
int "blksize", int "search", int "searchparam", int "overlap", int "dct")
{

# Set default options.

strength=default(strength,1)
    temporalframes=default(temporalframes,1)
mc = default(mc, 0)

# Prepare supersampled clip.

super = input.MSuper(levels=6,chroma=true)
 
# Motion vector search.

b5vec = MAnalyse(super, delta=5, isb=true,chroma=true, search=search, searchparam=searchparam, overlap=overlap, dct=dct)
b4vec = MAnalyse(super, delta=4, isb=true,chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)
b3vec = MAnalyse(super, delta=3, isb=true,chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)
b2vec = MAnalyse(super, delta=2, isb=true,chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)
b1vec = MAnalyse(super, delta=1, isb=true,chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)
f1vec = MAnalyse(super, delta=1, chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)
f2vec = MAnalyse(super, delta=2, chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)
f3vec = MAnalyse(super, delta=3, chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)
f4vec = MAnalyse(super, delta=4, chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)
f5vec = MAnalyse(super, delta=5, chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)
 
# Motion Compensation.

b5clip = MCompensate(input,super, b5vec)
b4clip = MCompensate(input,super, b4vec)
b3clip = MCompensate(input,super, b3vec)
b2clip = MCompensate(input,super, b2vec)
b1clip = MCompensate(input,super, b1vec)
f1clip = MCompensate(input,super, f1vec)
f2clip = MCompensate(input,super, f2vec)
f3clip = MCompensate(input,super, f3vec)
f4clip = MCompensate(input,super, f4vec)
f5clip = MCompensate(input,super, f5vec)
 
# Create compensated clip.

interleaved = mc >= 5 ? Interleave(f5clip, f4clip, f3clip, f2clip, f1clip, input, b1clip, b2clip, b3clip, b4clip, b5clip) :
\ mc == 4 ? Interleave(f4clip, f3clip, f2clip, f1clip, input, b1clip, b2clip, b3clip, b4clip) :
\ mc == 3 ? Interleave(f3clip, f2clip, f1clip, input, b1clip, b2clip, b3clip) :
\ mc == 2 ? Interleave(f2clip, f1clip, input, b1clip, b2clip) :
\ mc == 1 ? Interleave(f1clip, input, b1clip):
\ input
 
#Perform DFTTEST

params="""dfttest(y=true,u=true,v=true,f0beta=0.5,sigma=0,dither=0,sbsize=25,sosize=20,tbsize="""+string(temporalframes)+""",tosize="""+string(temporalframes)+"""/3,nstring=\
    "a:"""+string(strength)+""" \
    "+string(current_frame)+",0,440,10 \
    "+string(current_frame)+",0,10,10 \
    "+string(current_frame)+",0,10,340 \
    "+string(current_frame)+",0,10,680 \
    "+string(current_frame)+",0,440,680 \
    "+string(current_frame)+",1,440/2,3 \
    "+string(current_frame)+",1,3,3 \
    "+string(current_frame)+",1,3,340/2 \
    "+string(current_frame)+",1,3,680/2 \
    "+string(current_frame)+",1,440/2,350 \
    "+string(current_frame)+",2,120,3 \
    "+string(current_frame)+",2,3,3 \
    "+string(current_frame)+",2,3,340/2 \
    "+string(current_frame)+",2,3,680/2 \
    "+string(current_frame)+",2,440/2,680/2")
    """
filter=eval("scriptclip(interleaved,params)")
return SelectEvery(filter, mc * 2 + 1,mc)

}

I renamed the funtion LDdenoise, as to me LDnoise seems to add laserdisc noise... (^^,)

Sadly my projects are lost due to an HDD crash… 😦 | [Fundamental Collection] thread | blog.spoRv.com | fan preservation forum: fanres.com

Author
Time
 (Edited)

Thanks. I've updated the first post to mirror your function.  Avspmod adds some text formatting which shouldn't affect functionality but obviously if you can't run the function then it should be laid out so that it will work for everyone.  If that's even possible with a program like avisynth lol.  I'm using avisynth 2.58 btw.

It's good that you had the knowledge to make it work :)

EDIT:  I also named it LDdenoise at first but then I thought dnoise sounds the same as denoise and there's one less character to type.  I'm a lazy bastard lol.

 

Luke threw twice…maybe.

Author
Time

Now you should only modify it slightly to be compatible also with PAL captures (720x576)... (^^,)

Sadly my projects are lost due to an HDD crash… 😦 | [Fundamental Collection] thread | blog.spoRv.com | fan preservation forum: fanres.com

Author
Time

Ha. I didn't even think about making it PAL compatible.  That could easily be done.  How many lines are above and below the picture for a pal widescreen release?

Luke threw twice…maybe.

Author
Time

Here you are the rough guide I wrote myself, that I follow when I have to deal with analog captures - calculated with square pixels in mind, 640x480 NTSC full frame, 768x576 PAL full frame; captured at 720 pixel width, as most capture cards' native horizontal resolution have this width:

 AR     DAR   NTSC frame mod8 (16)

1.33    4/3   720 x 480
1.66    4/3   720 x 386 (384)
1.78    4/3   720 x 360 (352)
1.85    4/3   720 x 344 (336)
2.20    4/3   720 x 288
2.35    4/3   720 x 272

 AR     DAR   PAL frame  mod8 (16)

1.33    4/3   720 x 576
1.66    4/3   720 x 462 (464)
1.78    4/3   720 x 432
1.85    4/3   720 x 416
2.20    4/3   720 x 344 (336)
2.35    4/3   720 x 324 (320)

Sadly my projects are lost due to an HDD crash… 😦 | [Fundamental Collection] thread | blog.spoRv.com | fan preservation forum: fanres.com

Author
Time
 (Edited)

Hey Andrea, your pm box is full.  I'll just post it here though.  It should work with ntsc and pal without any extra input from the user.

EDIT:  Had to separate luma and chroma filtering.  The chroma sampling was actually sampling luma and removing too much noise.  Separating luma and chroma filtering has caused it to run very slow but the output is much better quality.  So it is.

function LDnoise(clip input, float "strength", int "mc", int "temporalframes",\

int "blksize", int "search", int "searchparam", int "overlap", int "dct")

{

 

# Set default options. 

 

width=width(input)

height=height(input)

strength=default(strength,1)

temporalframes=default(temporalframes,1)

mc = default(mc, 0)

 

# Prepare supersampled clip.

 

super = input.MSuper(levels=6,chroma=true)

 

# Motion vector search.

 

b5vec = MAnalyse(super, delta=5, isb=true,chroma=true, search=search, searchparam=searchparam, overlap=overlap, dct=dct)

b4vec = MAnalyse(super, delta=4, isb=true,chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)

b3vec = MAnalyse(super, delta=3, isb=true,chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)

b2vec = MAnalyse(super, delta=2, isb=true,chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)

b1vec = MAnalyse(super, delta=1, isb=true,chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)

f1vec = MAnalyse(super, delta=1, chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)

f2vec = MAnalyse(super, delta=2, chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)

f3vec = MAnalyse(super, delta=3, chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)

f4vec = MAnalyse(super, delta=4, chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)

f5vec = MAnalyse(super, delta=5, chroma=true, search=search, searchparam=searchparam, overlap=overlap, blksize=blksize, dct=dct)

 

# Motion Compensation.

 

b5clip = MCompensate(input,super, b5vec)

b4clip = MCompensate(input,super, b4vec)

b3clip = MCompensate(input,super, b3vec)

b2clip = MCompensate(input,super, b2vec)

b1clip = MCompensate(input,super, b1vec)

f1clip = MCompensate(input,super, f1vec)

f2clip = MCompensate(input,super, f2vec)

f3clip = MCompensate(input,super, f3vec)

f4clip = MCompensate(input,super, f4vec)

f5clip = MCompensate(input,super, f5vec)

 

# Create compensated clip.

 

interleaved = mc >= 5 ? Interleave(f5clip, f4clip, f3clip, f2clip, f1clip, input, b1clip, b2clip, b3clip, b4clip, b5clip) :

\ mc == 4 ? Interleave(f4clip, f3clip, f2clip, f1clip, input, b1clip, b2clip, b3clip, b4clip) :

\ mc == 3 ? Interleave(f3clip, f2clip, f1clip, input, b1clip, b2clip, b3clip) :

\ mc == 2 ? Interleave(f2clip, f1clip, input, b1clip, b2clip) :

\ mc == 1 ? Interleave(f1clip, input, b1clip):

\ input

 

#Perform DFTTEST

 

yparams="""dfttest(y=true,u=false,v=false,f0beta=0.5,sigma=0,dither=0,sbsize=25,sosize=20,tbsize="""+string(temporalframes)+""",tosize="""+string(temporalframes)+"""/3,nstring=\

    "a:"""+string(strength)+""" \

    "+string(current_frame)+",0,"""+string(height-40)+""",10 \

    "+string(current_frame)+",0,10,10 \

    "+string(current_frame)+",0,10,"""+string(width/4)+""" \

    "+string(current_frame)+",0,10,"""+string(width/2)+""" \

    "+string(current_frame)+",0,10,"""+string(width/1.5)+""" \

    "+string(current_frame)+",0,10,"""+string(width-40)+""" \

    "+string(current_frame)+",0,"""+string(height-40)+""","""+string(width-40)+""" ")

    """

 

 

 yfilter=eval("scriptclip(interleaved,yparams)")

 

 uvparams="""dfttest(y=false,u=true,v=true,f0beta=0.5,sigma=0,dither=0,sbsize=25,sosize=20,tbsize="""+string(temporalframes)+""",tosize="""+string(temporalframes)+"""/3,nstring=\

    "a:"""+string(strength)+""" \

    "+string(current_frame)+",1,"""+string((height/2)-30)+""",3 \

    "+string(current_frame)+",1,3,3 \

    "+string(current_frame)+",1,3,"""+string((width/2)/2)+""" \

    "+string(current_frame)+",1,3,"""+string((width/2)-30)+""" \

    "+string(current_frame)+",1,"""+string((height/2)-30)+""","""+string((width/2)-30)+""" \

    "+string(current_frame)+",2,"""+string((height/2)-30)+""",3 \

    "+string(current_frame)+",2,3,3 \

    "+string(current_frame)+",2,3,"""+string((width/2)/2)+""" \

    "+string(current_frame)+",2,3,"""+string((width/2)-30)+""" \

    "+string(current_frame)+",2,"""+string((height/2)-30)+""","""+string((width/2)-30)+""" ")

    """

 

uvfilter=eval("scriptclip(yfilter,uvparams)")

 

return SelectEvery(uvfilter, mc * 2 + 1,mc)

 

}

Luke threw twice…maybe.