Log in

View Full Version : SpotLess - Temporal denoise


Pages : 1 2 3 [4] 5 6 7 8

MysteryX
6th October 2021, 21:34
That ain't faster anymore. Waiting half-an-hour for the first frame of a 5K clip to come out. I give up.

Another way to restore details: run this as a prefilter to BM3D.

Edit: LOL it was resizing up by a factor of 3x

MysteryX
6th October 2021, 21:45
I'm comparing this to xClean. I'm very surprised by the results. Trees are back but....... look different. They actually look more like trees than in the source??? How is that possible? Image details are also a lot clearer. This is looking damn good. It's just that -- the way it completely alters moving trees is a bit risqué

Runs at 0.4fps on 5K

Compared to xClean, noise is about the same, image is clearer/sharper, and moving objects are... transformed, for better or worse. Haven't tested whether it works well for all types of content. On this test clip, Spotless gives better visible result (perhaps a bit too sharp), while xClean better respects the source.

coolgit
6th October 2021, 21:55
It is the overlap parameter used by the MAnalyze function of MVTools2, which is the motion estimation plugin that is at the heart of Spotless. In MVTools2 you can specify the size of the block used to "track" objects in the video. Typical sizes are 4, 8, and 16. To improve the quality of certain operations the algorithm can overlap adjacent blocks. This sometimes improves noise reduction or synthesis of intermediate frames, but you don't know until you try, as you did in fact do.

Is there an image example somewhere explaining how it works?

Are you saying that if a block is currently analyse the overlap can do the next block to exist the current block.

How does BlkSz/3 do a better job than BlkSz/2 - assuming / means divide.

MysteryX
6th October 2021, 22:07
Is there an image example somewhere explaining how it works?

Are you saying that if a block is currently analyse the overlap can do the next block to exist the current block.

How does BlkSz/3 do a better job than BlkSz/2 - assuming / means divide.
For blksize=16, if you set an overlap of 8, half-way through each block begins the next block.

johnmeyer
6th October 2021, 22:25
Is there an image example somewhere explaining how it works?

Are you saying that if a block is currently analyse the overlap can do the next block to exist the current block.

How does BlkSz/3 do a better job than BlkSz/2 - assuming / means divide.http://avisynth.org.ru/mvtools/mvtools2.html

https://forum.doom9.org/showthread.php?p=564686#post564686

Search them both for "overlap."

StainlessS
7th October 2021, 02:37
Assuming just 8 bit depth,
I nearly always avoid LSMash and ffms2, too many probs.
Maybe this of use [in a demo format]

DEMO.BAT

REM https://forum.doom9.org/showthread.php?p=1908619#post1908619
REM We DO NOT LIKE SPACES IN FILE NAMES (REM == REMark ie comment)
REM We DO NOT LIKE ACCENTS IN FILE NAMES.


setlocal

REM Where to Find ffmpeg
set FFMPEG="C:\BIN\ffmpeg.exe"

REM Where to get INPUT files, No terminating Backslash, "." = current directory (ie same as dir .bat file)
set INDIR="."

REM Where to place OUTPUT files, No terminating Backslash. "." would be same as .bat file, ".\OUT" = OUT folder in same directory as bat file.
set OUTDIR="D:"

REM Below, can add INPUT extensions as eg *.WEBM (SPACE separated, Batch Processes all INPUT type files in INDIR)
FOR %%A IN (*.AVI *.MKV *.MP4 *.MOV *.QT *.3GP *.DVB *.VOB *.MPG *.MPEG *M2P *.PS *.TS *.DIVX *.XVID *.FLV *.WMV *.ASF *.MXF) DO (

REM ****** Un-REM [ie SELECT] ONLY one of below lines, Comment out ie REM the remaining lines.
%FFMPEG% -i "%INDIR%\%%A" -vcodec utvideo -acodec pcm_s16le "%OUTDIR%\%%~nxA.AVI"
REM %FFMPEG% -i "%INDIR%\%%A" -vcodec huffyuv -acodec pcm_s16le "%OUTDIR%\%%~nxA.AVI"
REM %FFMPEG% -i "%INDIR%\%%A" -vcodec magicyuv -acodec pcm_s16le "%OUTDIR%\%%~nxA.AVI"
REM %FFMPEG% -i "%INDIR%\%%A" -vcodec v410 -acodec pcm_s16le "%OUTDIR%\%%~nxA.AVI"
REM %FFMPEG% -i "%INDIR%\%%A" -vcodec r210 -acodec pcm_s16le "%OUTDIR%\%%~nxA.AVI"
REM %FFMPEG% -i "%INDIR%\%%A" -vcodec copy -acodec copy -sn "%OUTDIR%\%%~nxA.MKV"
REM %FFMPEG% -i "%INDIR%\%%A" -vcodec utvideo -acodec copy "%OUTDIR%\%%~nxA.MKV"
REM %FFMPEG% -i "%INDIR%\%%A" -vn -acodec pcm_s16le "%OUTDIR%\%%~nxA.WAV"
REM %FFMPEG% -i "%INDIR%\%%A" -vcodec rawvideo -f rawvideo -an "%OUTDIR%\%%~nxA.rawvideo"


REM *****************************************************************************************.

)
Pause

REM ... Above Command lines, What they do if UnREM'ed (and all others REM'ed, UnREM=UnCOMMENT ) :
REM (1) Convert Video to UtVideo lossless, Convert Audio to 16 bit PCM audio (output AVI).
REM (2) Convert Video to HuffYUV lossless, Convert Audio to 16 bit PCM audio (output AVI).
REM (3) Convert Video to MagicYUV lossless, Convert Audio to 16 bit PCM audio (output AVI).
REM (4) Convert Video to v410 Uncompressed 4:4:4 10-bit lossless, Convert Audio to 16 bit PCM audio (output AVI).
REM (5) Convert Video to r210 Uncompressed RGB 10-bit lossless, Convert Audio to 16 bit PCM audio (output AVI).
REM (6) Remux NO SUBTITLES, copy both video and audio but NOT subtitles(output MKV - see file extension at the end of the Remux line, ie MKV).
REM (7) Convert Video to UtVideo lossless, copy audio (output MKV).
REM (8) Skip any video, Convert Audio to 16 bit PCM (output WAV).
REM (9) Convert Video to rawvideo uncompressed, Convert Audio to 16 bit PCM audio (output y4m).
REM *****************************************************************************************.
REM In the UnREM'ed command Line [ie without a preceding REM]:-
REM '-vcodec utvideo' means convert video using utvideo codec.
REM '-vcodec copy' means copy video rather than convert.
REM '-vn' means no video output, use instead of eg '-vcodec utvideo'
REM '-acodec pcm_s16le' means convert audio using pcm_s16le codec.
REM '-acodec copy' means copy audio rather than convert.
REM '-an' means no audio output, use instead of eg '-acodec pcm_s16le'
REM The file extension at end of the line determines output container, eg '.AVI'
REM
REM From Command line with ffmpeg somewhere in your environment PATH:-
REM 'ffmpeg -codecs >D:\ffmpeg_codecs.txt'
REM Writes a txt file of ffmpeg available codecs to D:\
REM At start of txt file it shows a 'legend' or 'key' for codecs that can be used for video and audio,
REM use only ENCODING SUPPORTED codecs, prefereably LOSSLESS.
REM Supported codecs will vary with ffmpeg version.
REM *****************************************************************************************.


I have a dedicated conversion directory where the script lives.
Copy video files to that directory.
Click the bat, batch converts all in directory.
Job done. Use reliable AviSource for the lot.

MysteryX
7th October 2021, 02:41
I had accidently posted the ffsm2 issue in the wrong thread; but since you answered... not sure converting 5K video to UT Video is a great idea. It would need enormous space!

Edit: for a 94MB video of 12 seconds, it produces a 2.2GB video and lists these errors:
- Using non-standard frame rate 29/1
- deprecated pixel format used, make sure you did set range correctly
- Duplicate POC in a sequence
- Error parsing NAL unit #0
- Error while decoding stream #0:0: Invalid data found when processing input

Video is only missing the last frame and colours are wrong. lol.

StainlessS
7th October 2021, 02:45
Maybe, but they would work without problems.

I think GoPro would be mp4, just use LSmashVideoSource, that one is usually fine.

MysteryX
7th October 2021, 03:02
Maybe, but they would work without problems.

I think GoPro would be mp4, just use LSmashVideoSource, that one is usually fine.
I think I'll just denoise the videos and let my video editor fix remaining glitches. Next time, I'll encode in h264 instead of h265, with High-Bit-Depth enabled, and re-encode every clip while transferring to my computer at a desirable bitrate. I hope they have less bug in their H264 encoder.

Also taking GoPro pictures -- need to set sharpening to LOW, and then add sharpening and contrast with Photoshop and it then looks as good or better than my cellphone. GoPro is peculiar to use...

MysteryX
7th October 2021, 04:43
I tried Spotless+DR on other clips. As I suspected, it works great on some content and not so great on other types (by the nature of MVTools). Main issue is that MVTools blurs out what I call "fine textures", which is why xClean passes MVTools through BM3D which restores the textures; and then through KNLMeans which always improves upon the prefilter.

Other than lost textures, Spotless has nearly identical noise reduction as xClean. Sharpening is too high!

kedautinh12
7th October 2021, 05:24
You can try Restore_Objects to restore trees. It's not as complex as the zorr version
https://forum.doom9.org/showthread.php?p=1945014#post1945014

StainlessS
7th October 2021, 07:46
Sharpening is too high!
ssS dont have no sharpening.

MysteryX
7th October 2021, 16:17
I'm converting the basic SpotLess function to VapourSynth to compare... it's using undocumented parameters!

MVTools doc (http://avisynth.org.ru/mvtools/mvtools2.html)

MAnalyse has no Multi parameter, and MCompensate has no tr parameter.

VapourSynth has such parameters even less.

Edit: Those parameters are in pinterf version. This script is not VapourSynth compatible then?

coolgit
7th October 2021, 16:29
ssS dont have no sharpening.
I think mysteryx was using spotless with delta restore and it has sharpening in it.

By default i do not use it as qtgmc does sharpening during deinterlacing.

StainlessS
7th October 2021, 18:31
MX,
Pinterf version [not sure exactly when it happened] Mvtools2 has MDegrainN, and additional args to MAnalyse,
to avoid many nearly identical lines of script with differing Delta arg.
You can still do it the old way eg, getting fwrd/bkwrd vectors for each required/supported delta [ie RadT, temporal radius],
and choose the appropriate forward backward vectors and use eg MDegrain1, or MDegrain2, or MDegrain3 or for whatever
radius [I think pre MDegrainN supports up to MDegrain6].

https://forum.doom9.org/showthread.php?p=1923580#post1923580


So, with Pinterf version, could use eg RadT=10, which would not be supported in old MvTools2, where is capped at delta 6 max.
Also, a number of bug fixed [eg cant mrecalculate where overlap=0], also additional block sizes supported in Pinterf ver$.
Also some weird bug fixed in the masks produced in old mvtools2 [long time bugged].

EDIT: Does away with repetative script lines as below in blue
[just any old script, I found here:- https://forum.doom9.org/showthread.php?p=1710446#post1710446 ]


#Denoising function, progressive video
function MDegrainProg(clip source, int "blksize", int "overlap", int "dct")
{
overlap=default(overlap,0) # overlap value (0 to 4 for blksize=8)
dct=default(dct,0) # use dct=1 for clip with light flicker
preNR = source.fft3dfilter(sigma=4,interlaced=false)

preNR_super = preNR.MSuper(pel=2, sharp=1)
src_super = source.MSuper(pel=2, sharp=1,levels=1)

backward_vec2 = MAnalyse(preNR_super,isb = true, delta = 2, blksize=blksize, overlap=overlap, dct=dct)
backward_vec1 = MAnalyse(preNR_super,isb = true, delta = 1, blksize=blksize, overlap=overlap, dct=dct)
forward_vec1 = MAnalyse(preNR_super,isb = false, delta = 1, blksize=blksize, overlap=overlap, dct=dct)
forward_vec2 = MAnalyse(preNR_super,isb = false, delta = 2, blksize=blksize, overlap=overlap, dct=dct)

MDegrain2(source,src_super, backward_vec1,forward_vec1,backward_vec2,forward_vec2,thSAD=800)
}

And above, only supports a single radius of 2.

Additional Documentation, seems only available in Pinterf ver$ zip.

johnmeyer
7th October 2021, 18:42
I'm converting the basic SpotLess function to VapourSynth to compare... it's using undocumented parameters!

MVTools doc (http://avisynth.org.ru/mvtools/mvtools2.html)

MAnalyse has no Multi parameter, and MCompensate has no tr parameter.As SpotlessS says, you need to use the Pinterf version. I switched to that in order to try out SpotlessS, and found that all my older scripts which use MVTools2 still seem to work just fine. I also find it more stable when used with AVISynth+ and multi-threading.

StainlessS
7th October 2021, 18:51
MX,
Old style MDegrain() script function [supporting temporal radius up to 3, in Frames]

Function MCDegrain(clip c, int "frames",int "plane",int "limit",int "limitC")
{ # By Didee, http://forum.doom9.org/showthread.php?t=161594
frames = default(frames, 2)
plane=Default(plane,0) # as FFT3DFilter
limit=Default(limit,255) # maximal change in pixl luma (default 255 [no limit])
limitC=Default(limitC,limit) # maximal change in pixl chroma (default as luma limit)
bs = (c.width>960) ? 16 : 8
super = c.MSuper(pel=2, sharp=1)
backward_vec3 = MAnalyse(super, isb = true, delta = 3, blksize=bs, overlap=bs/2)
backward_vec2 = MAnalyse(super, isb = true, delta = 2, blksize=bs, overlap=bs/2)
backward_vec1 = MAnalyse(super, isb = true, delta = 1, blksize=bs, overlap=bs/2)
forward_vec1 = MAnalyse(super, isb = false, delta = 1, blksize=bs, overlap=bs/2)
forward_vec2 = MAnalyse(super, isb = false, delta = 2, blksize=bs, overlap=bs/2)
forward_vec3 = MAnalyse(super, isb = false, delta = 3, blksize=bs, overlap=bs/2)
(frames<=0) ? c :\
(frames==1) ? c.MDegrain1(super, backward_vec1,forward_vec1,thSAD=400,plane=plane,limit=limit,limitC=limitC) :\
(frames==2) ? c.MDegrain2(super, backward_vec1,forward_vec1,backward_vec2,forward_vec2,thSAD=400,plane=plane,limit=limit,limitC=limitC) :\
c.MDegrain3(super, backward_vec1,forward_vec1,backward_vec2,forward_vec2,backward_vec3,forward_vec3,thSAD=400,plane=plane,limit=limit,limitC=limitC)
return(last)
}


New style [although this one is mod with extra args (no cap of temporal Radius <=3, on 'frames']

Function McDegrain(clip c, int "frames",
\ Float "Limit",Float "LimitC",
\ float "bblur", int "Pel",bool "Chroma", Int "BlkSz",Int "OLap",Int "Plane",bool "Tm",Bool "glob", Int "ThSAD",Int "ThSAD2") {
myName = "McDegrain: "
frames = Default(frames, 2)
Limit = Default(Limit,255)
LimitC = Default(LimitC,Limit)
bblur = Default(bblur,0.0) # Denoising for Super Clip.
bblur = (bblur>=0.0) ? bblur : (c.width>1920) ? 0.75 : (c.width>1280) ? 0.7 : (c.width>960 ) ? 0.65 : 0.6
pel = Default(pel,2)
chroma = Default(chroma,true) # Use chroma in MAnalyse for vectors
BlkSz = Default(BlkSz,(c.width>1920) ? 32 : (c.width>1280) ? 24 : (c.width>960 ) ? 16 : 8)
OLap = Default(OLap,BlkSz/2)
Plane = Default(Plane,4)
tm = Default(tm,true) # TrueMotion
Glob = Default(glob,Tm) # Default Tm, Allow set MAnalyse(global) independently of TrueMotion.
ThSAD = Default(ThSAD,400)
ThSAD2 = Default(ThSAD2,ThSAD)
Assert(0.0 <= bblur <= 1.58, myName + "0.0 <= bblur <= 1.58")
Assert(pel==1 || pel==2 || pel==4, myName + "pel==1 || pel==2 || pel==4")
Assert(0 <= Plane <= 4, myName + "0 <= Plane <= 4")
super = (bblur==0.0 ? c : c.blur(bblur)).MSuper(pel=pel, sharp=1)
super_rend = (bblur==0.0) ? super : c.MSuper(pel=pel, sharp=1)
MultiVec = super.MAnalyse(multi=true,delta=frames,blksize=BlkSz,overlap=OLap,chroma=Chroma,truemotion=Tm,global=Glob)
(frames<=0)
\ ? c
\ : c.MDegrainN(super_rend, MultiVec, Frames, thSAD=ThSAD, plane=Plane, Limit=Limit, LimitC=LimitC, thsad2=ThSAD2)
return Last
}

MysteryX
7th October 2021, 19:15
Except that here it's MCompensate, not MDegrain. It takes a single vector clip.

zorr
7th October 2021, 22:21
I think mysteryx was using spotless with delta restore and it has sharpening in it.

Delta Restore doesn't have sharpening either. The script by chmars which used Spotless and Delta Restore did have sharpening though.

StainlessS
7th October 2021, 22:46
MX,

Something like this

/*
SpotLess v1.07Beta. temporal spot/noise remover, by StainlessS @ Doom9. https://forum.doom9.org/showthread.php?t=181777
v1.07Beta, bBlur Default changed from 0.0 OFF, to 0.6, slower but better for mc analysis. Maybe try 1.0 for HD to overcome HD 'grittiness'.
Added dc arg, detect clip.
*/

Function SpotLess(clip c,int "RadT",int "ThSAD",int "ThSAD2",int "pel",bool "chroma", int "BlkSz",Int "Olap",bool "tm",Bool "glob",
\ Float "bBlur", clip "dc" ) {
myName = "SpotLess: "
RadT = Default(RadT,1) # Temporal radius. (MCompensate arg)
ThSAD = Default(ThSAD,10000) # SAD threshold at radius 1 (Default Nearly OFF).
ThSAD2 = Default(ThSAD2,ThSAD) # SAD threshold at radius RadT.
Pel = Default(pel,2) # Default 2. 1, 2, or 4. Maybe set 1 for HD+. (1=precision to pixel, 2=precision to half pixel, 4=quarter pixel)
Chroma = Default(chroma,True) # MAnalyse chroma arg. If set to true, use chroma in block matching.
BlkSz = Default(BlkSz,8) # Default 8. MAnalyse BlkSize. Bigger blksz quicker and perhaps better, esp for HD clips. Maybe also better where BIG noise.
OLap = Default(OLap, BlkSz/2) # Default half of BlkSz.
Tm = Default(tm,True) # TrueMotion, Some folk swear MAnalyse(truemotion=false) is better.
Glob = Default(glob,True) # Default True, Allow set MAnalyse(global) independently of TrueMotion.
bBlur = Default(bblur,0.6) # Default 0.6. Suggest about 0.6 for better motion analysis, but a bit slower.
HasDC = dc.Defined # bblur ignored if HasDC, ie user already provided prefiltered clip.
Assert(1 <= RadT,myName + " 1 <= RadT")
Assert(0.0 <= bblur <= 1.58, myName + "0.0 <= bblur <= 1.58")
Assert(pel==1 || pel==2 || pel==4, myName + "pel==1 || pel==2 || pel==4")
pad = max(BlkSz,8)
sup = (HasDC ? dc : bBlur>0.0 ? c.blur(bBlur) : c ).MSuper(hpad=pad,vpad=pad,pel=pel, sharp=2)



sup_rend = (HasDC||bBlur>0.0) ? c.MSuper(hpad=pad,vpad=pad,pel=pel, sharp=2,Levels=1) : sup
MultiVec = sup.MAnalyse(multi=true, delta=RadT,blksize=BlkSz,overlap=OLap,chroma=Chroma,truemotion=Tm,global=Glob)
c.MCompensate(sup_rend, MultiVec, tr=RadT, thSad=ThSAD, thSad2=ThSAD2)
MedianBlurTemporal(radiusY=0,radiusU=0,radiusV=0,temporalradius=RadT) # Temporal median blur only [not spatial]
SelectEvery(RadT*2+1,RadT) # Return middle frame
}


# Support 1 <= RadT <= 3
Function ZpotLezz(clip c,int "RadT",int "ThSAD",int "ThSAD2",int "pel",bool "chroma", int "BlkSz",Int "Olap",bool "tm",Bool "glob",
\ Float "bBlur", clip "dc" ) {
myName = "ZpotLezz: "
RadT = Default(RadT,1) # Temporal radius. (MCompensate arg)
ThSAD = Default(ThSAD,10000) # SAD threshold at radius 1 (Default Nearly OFF).
ThSAD2 = Default(ThSAD2,ThSAD) # SAD threshold at radius RadT.
Pel = Default(pel,2) # Default 2. 1, 2, or 4. Maybe set 1 for HD+. (1=precision to pixel, 2=precision to half pixel, 4=quarter pixel)
Chroma = Default(chroma,True) # MAnalyse chroma arg. If set to true, use chroma in block matching.
BlkSz = Default(BlkSz,8) # Default 8. MAnalyse BlkSize. Bigger blksz quicker and perhaps better, esp for HD clips. Maybe also better where BIG noise.
OLap = Default(OLap, BlkSz/2) # Default half of BlkSz.
Tm = Default(tm,True) # TrueMotion, Some folk swear MAnalyse(truemotion=false) is better.
Glob = Default(glob,True) # Default True, Allow set MAnalyse(global) independently of TrueMotion.
bBlur = Default(bblur,0.6) # Default 0.6. Suggest about 0.6 for better motion analysis, but a bit slower.
HasDC = dc.Defined # bblur ignored if HasDC, ie user already provided prefiltered clip.
Assert(1 <= RadT <= 3,myName + " 1 <= RadT")
Assert(0.0 <= bblur <= 1.58, myName + "0.0 <= bblur <= 1.58")
Assert(pel==1 || pel==2 || pel==4, myName + "pel==1 || pel==2 || pel==4")
pad = max(BlkSz,8)
sup = (HasDC ? dc : bBlur>0.0 ? c.blur(bBlur) : c ).MSuper(hpad=pad,vpad=pad,pel=pel, sharp=2)

# equivalent


sup_rend = (HasDC||bBlur>0.0) ? c.MSuper(hpad=pad,vpad=pad,pel=pel, sharp=2,Levels=1) : sup

bv1 = sup.MAnalyse(isb=true, delta=1,blksize=BlkSz,overlap=OLap,chroma=Chroma,truemotion=Tm,global=Glob)
bv2 = sup.MAnalyse(isb=true, delta=2,blksize=BlkSz,overlap=OLap,chroma=Chroma,truemotion=Tm,global=Glob)
bv3 = sup.MAnalyse(isb=true, delta=3,blksize=BlkSz,overlap=OLap,chroma=Chroma,truemotion=Tm,global=Glob)
fv1 = sup.MAnalyse(isb=false,delta=1,blksize=BlkSz,overlap=OLap,chroma=Chroma,truemotion=Tm,global=Glob)
fv2 = sup.MAnalyse(isb=false,delta=2,blksize=BlkSz,overlap=OLap,chroma=Chroma,truemotion=Tm,global=Glob)
fv3 = sup.MAnalyse(isb=false,delta=3,blksize=BlkSz,overlap=OLap,chroma=Chroma,truemotion=Tm,global=Glob)

bc1 = c.MCompensate(sup_rend, bv1, thSad=ThSAD, thSad2=ThSAD2)
bc2 = c.MCompensate(sup_rend, bv2, thSad=ThSAD, thSad2=ThSAD2)
bc3 = c.MCompensate(sup_rend, bv3, thSad=ThSAD, thSad2=ThSAD2)
fc1 = c.MCompensate(sup_rend, fv1, thSad=ThSAD, thSad2=ThSAD2)
fc2 = c.MCompensate(sup_rend, fv2, thSad=ThSAD, thSad2=ThSAD2)
fc3 = c.MCompensate(sup_rend, fv3, thSad=ThSAD, thSad2=ThSAD2)
/*
i1 = Interleave(fc1, c , bc1)
i2 = Interleave(fc2, fc1, c , bc1, bc2)
i3 = Interleave(fc3, fc2, fc1, c , bc1, bc2, bc3)
*/
i1 = Interleave(bc1, c , fc1)
i2 = Interleave(bc2, bc1, c , fc1, fc2)
i3 = Interleave(bc3, bc2, bc1, c , fc1, fc2, fc3)



(RadT==1) ? i1.MedianBlurTemporal(radiusY=0,radiusU=0,radiusV=0,temporalradius=RadT)
\ : (RadT==2) ? i2.MedianBlurTemporal(radiusY=0,radiusU=0,radiusV=0,temporalradius=RadT)
\ : i3.MedianBlurTemporal(radiusY=0,radiusU=0,radiusV=0,temporalradius=RadT)

SelectEvery(RadT*2+1,RadT) # Return middle frame
}

AviSource(".\Replace Bad Fields.avi").ConvertToYV12

O=Last
RADT=3
A=SpotLess(RadT=RADT)
B=ZpotLezz(RadT=RADT)
TOP=StackHorizontal(A,B)
D=ClipDelta(A,B)
D2=ClipDelta(A,B,true)
BOT=StackHorizontal(D,D2)
StackVertical(TOP,BOT)
return last

# Return Clip Difference of input clips (amp==true = Amplified, show==true = show background)
Function ClipDelta(clip clip1,clip clip2,bool "amp",bool "show") {
amp=Default(amp,false)
show=Default(show,false)
c2=clip1.levels(128-32,1.0,128+32,128-32,128+32).greyscale()
c1=clip1.subtract(clip2)
c1=(amp)?c1.levels(127,1.0,129,0,255):c1
return (show)?c1.Merge(c2):c1
}


Above mod not exactly the same, but looks similar, dont know why is different, maybe I screwed up somewhere.
I'm struggling to stay awake and so am about to go to bed.
EDIT: Even produces the odd frame not identical when RadT=1, so I maybe my error.

coolgit
7th October 2021, 23:04
Delta Restore doesn't have sharpening either. The script by chmars which used Spotless and Delta Restore did have sharpening though.

Yes... I just thought it was part of delta restore but personally I didn't want it.

A question. I had a big spot, a penny size, 1st frame after scene change. Spotless does nothing but delta restore somehow fixed it 60% ish with lighter colour. I.e say the background is red and the next frame is the same, the white spot get replaced with pinkish colour. I suspect an opacity issue here but still can't get my head around your codes.

Any pointers?

kedautinh12
8th October 2021, 05:00
I'm converting the basic SpotLess function to VapourSynth to compare... it's using undocumented parameters!

MVTools doc (http://avisynth.org.ru/mvtools/mvtools2.html)

MAnalyse has no Multi parameter, and MCompensate has no tr parameter.

VapourSynth has such parameters even less.

Edit: Those parameters are in pinterf version. This script is not VapourSynth compatible then?

You can try spotless_mod to keep detail lost in spotless
https://forum.doom9.org/showthread.php?p=1945133#post1945133

MysteryX
8th October 2021, 16:01
Here's the translation in VapourSynth. But the image turns full of green horizontal lines. Can you see what's wrong?


def SpotLess(c: vs.VideoNode, radt: int = 1, thsad: int = 10000, thsad2: Optional[int] = None, pel: int = 2, chroma: bool = True,
blksize: int = 8, overlap: Optional[int] = None, truemotion: bool = True, pglobal: int = True,
blur: float = 0.0, ref: Optional[vs.VideoNode] = None) -> vs.VideoNode:
if radt < 1 or radt > 3:
raise ValueError("Spotless: radt must be between 1 and 3")
if not pel in [1, 2, 4]:
raise ValueError("Spotless: pel must be 1, 2 or 4")

icalc = c.format.bits_per_sample < 32
S = core.mv.Super if icalc else core.mvsf.Super
A = core.mv.Analyse if icalc else core.mvsf.Analyse
C = core.mv.Compensate if icalc else core.mvsf.Compensate
pad = max(blksize, 8)

sup = ref or (c.std.Convolution(matrix=[1, 2, 1, 2, 4, 2, 1, 2, 1]) if blur > 0.0 else c)
sup = S(sup, hpad=pad, vpad=pad, pel=pel, sharp=2)
sup_rend = S(c, hpad=pad, vpad=pad, pel=pel, sharp=2, levels=1) if ref or blur > 0.0 else sup

bv1 = A(sup, isb=True, delta=1, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)
bv2 = A(sup, isb=True, delta=2, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)
bv3 = A(sup, isb=True, delta=3, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)
fv1 = A(sup, isb=False, delta=1, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)
fv2 = A(sup, isb=False, delta=2, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)
fv3 = A(sup, isb=False, delta=3, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)

bc1 = C(c, sup_rend, bv1, thsad=thsad, thsad2=thsad2)
bc2 = C(c, sup_rend, bv2, thsad=thsad, thsad2=thsad2)
bc3 = C(c, sup_rend, bv3, thsad=thsad, thsad2=thsad2)
fc1 = C(c, sup_rend, fv1, thsad=thsad, thsad2=thsad2)
fc2 = C(c, sup_rend, fv2, thsad=thsad, thsad2=thsad2)
fc3 = C(c, sup_rend, fv3, thsad, thsad2=thsad2)

i1 = core.std.Interleave([bc1, c, fc1])
i2 = core.std.Interleave([bc2, bc1, c, fc1, fc2])
i3 = core.std.Interleave([bc3, bc2, bc1, c, fc1, fc2, fc3])

output = core.tmedian.TemporalMedian(i3 if radt==3 else i2 if radt==2 else i1, radius=radt)
return output.std.SelectEvery(radt*2+1, radt) # Return middle frame


Edit: Compensate in VapourSynth doesn't have thsad2; and if I remove it, it's the output of Compensate that is corrupted with green lines.

jmac698
10th October 2021, 09:04
You're still going! Good on you.

I've become an image (color/distortion/vignetting/black frames) calibration expert since, and better programmer. I do data science. Still love image processing, and actually had a chance to use Avisynth at work! So fun.

StainlessS
10th October 2021, 10:08
MX,

Edit: Compensate in VapourSynth doesn't have thsad2; and if I remove it, it's the output of Compensate that is corrupted with green lines.
Not sure what to make of that, if it dont have thsad2, how come you can use it ??? [I dont speak ParselTongue (EDIT: Snake Speak, ie Python)]
I have not checked, but presume that ThSAD2 is new in Pinterf Multivec version MvTools,
and, maybe should calc individual ThSAD_Delta1, ThSAD_Delta2, ThSAD_Delta3, linear interp where ThSAD_Delta1 = thSAD and ThSAD_Delta3=ThSAD2 and ThSAD_Delta2=(ThSAD+ThSAD2)/2 [when RadT=3],
(and similar for RadT=2, and 1).
EDIT:

ThSAD_Delta1 = thSAD [when RadT=1]
ThSAD_Delta1 = thSAD and ThSAD_Delta2=ThSAD2 [when RadT=2]



fc2 = C(c, sup_rend, fv2, thsad=thsad, thsad2=thsad2)
fc3 = C(c, sup_rend, fv3, thsad, thsad2=thsad2)

Above, why is red different,from others.

Yo JMac, you still alive ? thought you'de be long dead by now.

EDIT:
Above mod not exactly the same, but looks similar, dont know why is different, maybe I screwed up somewhere.
Maybe I should not have set MCompensate ThSAD2, think maybe only for MultiVec usage.

StainlessS
10th October 2021, 12:11
MX, see above edit.

Mod

/*
SpotLess v1.07Beta. temporal spot/noise remover, by StainlessS @ Doom9. https://forum.doom9.org/showthread.php?t=181777
v1.07Beta, bBlur Default changed from 0.0 OFF, to 0.6, slower but better for mc analysis. Maybe try 1.0 for HD to overcome HD 'grittiness'.
Added dc arg, detect clip.
*/

Function SpotLess(clip c,int "RadT",int "ThSAD",int "ThSAD2",int "pel",bool "chroma", int "BlkSz",Int "Olap",bool "tm",Bool "glob",
\ Float "bBlur", clip "dc" ) {
myName = "SpotLess: "
RadT = Default(RadT,1) # Temporal radius. (MCompensate arg)
ThSAD = Default(ThSAD,10000) # SAD threshold at radius 1 (Default Nearly OFF).
ThSAD2 = Default(ThSAD2,ThSAD) # SAD threshold at radius RadT.
Pel = Default(pel,2) # Default 2. 1, 2, or 4. Maybe set 1 for HD+. (1=precision to pixel, 2=precision to half pixel, 4=quarter pixel)
Chroma = Default(chroma,True) # MAnalyse chroma arg. If set to true, use chroma in block matching.
BlkSz = Default(BlkSz,8) # Default 8. MAnalyse BlkSize. Bigger blksz quicker and perhaps better, esp for HD clips. Maybe also better where BIG noise.
OLap = Default(OLap, BlkSz/2) # Default half of BlkSz.
Tm = Default(tm,True) # TrueMotion, Some folk swear MAnalyse(truemotion=false) is better.
Glob = Default(glob,True) # Default True, Allow set MAnalyse(global) independently of TrueMotion.
bBlur = Default(bblur,0.6) # Default 0.6. Suggest about 0.6 for better motion analysis, but a bit slower.
HasDC = dc.Defined # bblur ignored if HasDC, ie user already provided prefiltered clip.
Assert(1 <= RadT,myName + " 1 <= RadT")
Assert(0.0 <= bblur <= 1.58, myName + "0.0 <= bblur <= 1.58")
Assert(pel==1 || pel==2 || pel==4, myName + "pel==1 || pel==2 || pel==4")
pad = max(BlkSz,8)
sup = (HasDC ? dc : bBlur>0.0 ? c.blur(bBlur) : c ).MSuper(hpad=pad,vpad=pad,pel=pel, sharp=2)



sup_rend = (HasDC||bBlur>0.0) ? c.MSuper(hpad=pad,vpad=pad,pel=pel, sharp=2,Levels=1) : sup
MultiVec = sup.MAnalyse(multi=true, delta=RadT,blksize=BlkSz,overlap=OLap,chroma=Chroma,truemotion=Tm,global=Glob)
c.MCompensate(sup_rend, MultiVec, tr=RadT, thSad=ThSAD, thSad2=ThSAD2)
MedianBlurTemporal(radiusY=0,radiusU=0,radiusV=0,temporalradius=RadT) # Temporal median blur only [not spatial]
SelectEvery(RadT*2+1,RadT) # Return middle frame
}


# Support 1 <= RadT <= 3
Function ZpotLezz(clip c,int "RadT",int "ThSAD",int "ThSAD2",int "pel",bool "chroma", int "BlkSz",Int "Olap",bool "tm",Bool "glob",
\ Float "bBlur", clip "dc" ) {
myName = "ZpotLezz: "
RadT = Default(RadT,1) # Temporal radius. (MCompensate arg)
ThSAD = Default(ThSAD,10000) # SAD threshold at radius 1 (Default Nearly OFF).
ThSAD2 = Default(ThSAD2,ThSAD) # SAD threshold at radius RadT.
Pel = Default(pel,2) # Default 2. 1, 2, or 4. Maybe set 1 for HD+. (1=precision to pixel, 2=precision to half pixel, 4=quarter pixel)
Chroma = Default(chroma,True) # MAnalyse chroma arg. If set to true, use chroma in block matching.
BlkSz = Default(BlkSz,8) # Default 8. MAnalyse BlkSize. Bigger blksz quicker and perhaps better, esp for HD clips. Maybe also better where BIG noise.
OLap = Default(OLap, BlkSz/2) # Default half of BlkSz.
Tm = Default(tm,True) # TrueMotion, Some folk swear MAnalyse(truemotion=false) is better.
Glob = Default(glob,True) # Default True, Allow set MAnalyse(global) independently of TrueMotion.
bBlur = Default(bblur,0.6) # Default 0.6. Suggest about 0.6 for better motion analysis, but a bit slower.
HasDC = dc.Defined # bblur ignored if HasDC, ie user already provided prefiltered clip.
Assert(1 <= RadT <= 3,myName + " 1 <= RadT")
Assert(0.0 <= bblur <= 1.58, myName + "0.0 <= bblur <= 1.58")
Assert(pel==1 || pel==2 || pel==4, myName + "pel==1 || pel==2 || pel==4")
pad = max(BlkSz,8)
sup = (HasDC ? dc : bBlur>0.0 ? c.blur(bBlur) : c ).MSuper(hpad=pad,vpad=pad,pel=pel, sharp=2)
sup_rend = (HasDC||bBlur>0.0) ? c.MSuper(hpad=pad,vpad=pad,pel=pel, sharp=2,Levels=1) : sup

th1 = thSAD
th2 = (RadT==3) ? (thSAD + thSAD2)/2 : thSAD2
th3 = thSAD2

bv1 = sup.MAnalyse(isb=true, delta=1,blksize=BlkSz,overlap=OLap,chroma=Chroma,truemotion=Tm,global=Glob)
fv1 = sup.MAnalyse(isb=false,delta=1,blksize=BlkSz,overlap=OLap,chroma=Chroma,truemotion=Tm,global=Glob)
bv2 = (RadT>=2) ? sup.MAnalyse(isb=true, delta=2,blksize=BlkSz,overlap=OLap,chroma=Chroma,truemotion=Tm,global=Glob) : NOP
fv2 = (RadT>=2) ? sup.MAnalyse(isb=false,delta=2,blksize=BlkSz,overlap=OLap,chroma=Chroma,truemotion=Tm,global=Glob) : NOP
bv3 = (RadT>=3) ? sup.MAnalyse(isb=true, delta=3,blksize=BlkSz,overlap=OLap,chroma=Chroma,truemotion=Tm,global=Glob) : NOP
fv3 = (RadT>=3) ? sup.MAnalyse(isb=false,delta=3,blksize=BlkSz,overlap=OLap,chroma=Chroma,truemotion=Tm,global=Glob) : NOP

bc1 = c.MCompensate(sup_rend, bv1, thSad=th1)
fc1 = c.MCompensate(sup_rend, fv1, thSad=th1)
bc2 = (RadT>=2) ? c.MCompensate(sup_rend, bv2, thSad=th2) : NOP
fc2 = (RadT>=2) ? c.MCompensate(sup_rend, fv2, thSad=th2) : NOP
bc3 = (RadT>=3) ? c.MCompensate(sup_rend, bv3, thSad=th3) : NOP
fc3 = (RadT>=3) ? c.MCompensate(sup_rend, fv3, thSad=th3) : NOP

ic = (RadT==1) ? Interleave(bc1, c , fc1) :
\ (RadT==2) ? Interleave(bc2, bc1, c , fc1, fc2) :
\ Interleave(bc3, bc2, bc1, c , fc1, fc2, fc3)

ic.MedianBlurTemporal(radiusY=0,radiusU=0,radiusV=0,temporalradius=RadT)

SelectEvery(RadT*2+1,RadT) # Return middle frame
}

AviSource(".\Replace Bad Fields.avi").ConvertToYV12

O=Last
RADT=1
A=SpotLess(RadT=RADT)
B=ZpotLezz(RadT=RADT)
TOP=StackHorizontal(A,B)
D=ClipDelta(A,B)
D2=ClipDelta(A,B,true)
BOT=StackHorizontal(D,D2)
StackVertical(TOP,BOT)
return last

# Return Clip Difference of input clips (amp==true = Amplified, show==true = show background)
Function ClipDelta(clip clip1,clip clip2,bool "amp",bool "show") {
amp=Default(amp,false)
show=Default(show,false)
c2=clip1.levels(128-32,1.0,128+32,128-32,128+32).greyscale()
c1=clip1.subtract(clip2)
c1=(amp)?c1.levels(127,1.0,129,0,255):c1
return (show)?c1.Merge(c2):c1
}


EDIT: On a test sample, 4 frames out of 75 produce non identical result to SpotLess, maybe something like rounding in multivec
routine is a bit different [visually pretty much the same].

MysteryX
10th October 2021, 19:10
OK... this works in 32-bit. The dubhater plugin version has a bug outputing green horizontal lines.


def SpotLess(c: vs.VideoNode, radt: int = 1, thsad: int = 10000, thsad2: Optional[int] = None, pel: int = 2, chroma: bool = True,
blksize: int = 8, overlap: Optional[int] = None, truemotion: bool = True, pglobal: bool = True, blur: float = 0.0,
ref: Optional[vs.VideoNode] = None) -> vs.VideoNode:
if radt < 1 or radt > 3:
raise ValueError("Spotless: radt must be between 1 and 3")
if not pel in [1, 2, 4]:
raise ValueError("Spotless: pel must be 1, 2 or 4")

thsad2 = thsad2 or thsad
icalc = c.format.bits_per_sample < 32
S = core.mv.Super if icalc else core.mvsf.Super
A = core.mv.Analyse if icalc else core.mvsf.Analyse
C = core.mv.Compensate if icalc else core.mvsf.Compensate
pad = max(blksize, 8)

sup = ref or (c.std.Convolution(matrix=[1, 2, 1, 2, 4, 2, 1, 2, 1]) if blur > 0.0 else c)
sup = S(sup, hpad=pad, vpad=pad, pel=pel, sharp=2)
sup_rend = S(c, hpad=pad, vpad=pad, pel=pel, sharp=2, levels=1) if ref or blur > 0.0 else sup

th1 = thsad
th2 = (thsad + thsad2)/2 if radt==3 else thsad2
th3 = thsad2

bv1 = A(sup, isb=True, delta=1, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)
fv1 = A(sup, isb=False, delta=1, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)
if radt >= 2:
bv2 = A(sup, isb=True, delta=2, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)
fv2 = A(sup, isb=False, delta=2, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)
if radt >= 3:
bv3 = A(sup, isb=True, delta=3, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)
fv3 = A(sup, isb=False, delta=3, blksize=blksize, overlap=overlap, chroma=chroma, truemotion=truemotion, pglobal=pglobal)

bc1 = C(c, sup_rend, bv1, thsad=th1)
fc1 = C(c, sup_rend, fv1, thsad=th1)
if radt >= 2:
bc2 = C(c, sup_rend, bv2, thsad=th2)
fc2 = C(c, sup_rend, fv2, thsad=th2)
if radt >= 3:
bc3 = C(c, sup_rend, bv3, thsad=th3)
fc3 = C(c, sup_rend, fv3, thsad=th3)

ic = core.std.Interleave([bc1, c, fc1]) if radt == 1 else \
core.std.Interleave([bc2, bc1, c, fc1, fc2]) if radt == 2 else \
core.std.Interleave([bc3, bc2, bc1, c, fc1, fc2, fc3])

output = core.tmedian.TemporalMedian(ic, radius=radt)
return output.std.SelectEvery(radt*2+1, radt) # Return middle frame

MysteryX
10th October 2021, 19:38
OK I compared xClean with mClean (MDegrain) vs SpotLess (Compensate) as the first of 3 passes. It doesn't suffer from missing objects because they come back with pass 2 (BM3D) and 3 (KNLMeansCL).

Can't test performance because only the 32-bit version of Compensate is working.

According to preliminary tests, there's no difference on noise. I'm unsure about mClean vs SpotLess radt=1 or 2, but if I set radt=3, the SpotLess version seems to have a bit more realistic volume to the output. It feels less flat.

Might be a worthy contestant if I can get it to run in 8-bit.

StainlessS
10th October 2021, 19:53
Maybe you can also persuade DubHater that MultiVec stuff is a good idea [whole helluva lot less script - EDIT: Especially when greater than RadT=3].

coolgit
10th October 2021, 21:08
Stainlesss

I have been doing some experiments to deal with spotty frames at scene changes. Also tried to find a better way of dealing with motion as delta restore is a bit of a hit and miss depending on colours etc. I came up with my first avs(i) script borrowing your spotless function.

1. Remove last and first frames of scene changes with 2nd last and 2nd frames. Currently did it with SCSelect with dfactor=5. A couple of false detections. Worked and saved hours.
Currently I produced a spotless file then use the new file for SCSelect. Stumbled on your SCSelect_HBD and trying that one out next. Tried to merge scene detection with spotless so it can do both functions at the same time, however I can't seem to get it to work. However and as usual flashes still an issue. All scene change when there one colour, black, white, orange, etc. An example white frame, cgi frame, becomes white frame, white frame. Not an issue when watching it on TV, just pointing out the weakness.

How many defects I had to fix using photoshop? 2. Two. TWO. TTTWWWOOO... Get it. Previously I was doing something like 30 to 50 per episode.

2. I created a script that allows spotless to output 2 files with truemotion=true and truemotion=false, with tweaks. Did some analysis and turned out better than delta restore in term of time spent and work level. Neither does any restoring.

SpotLessT=SpotLess(src,BlkSz=BlkSzT,OLap=OLapT,pel=pel,Tm=True,Bblur=BblurT,ThSAD=6000,RadT=RadT). The T after SpotLess etc is Tm=True. BlkSz of 16 is best.

SpotLessF=SpotLess(src,BlkSz=BlkSzF,OLap=OLapF,pel=1,Tm=False,Bblur=BblurF,ThSAD=1000,RadT=RadT). The F after SpotLess etc is Tm=False. BlkSz of 8 is a must. Going higher creates more lost of motion details.

T thsad at 6000 is excellent at removing all spots and allow medium motion speed and distance from camera motion details to be retain. With thsad at 10000 there was more motion defects and thus more work replacing those frames and the spots fixed were the same as 6000. This is the primary spotless file where all frames are extracted.

F thsad at 1000 will do small spot but excellent at retaining motion, very much the same as source file except for flashes(as usual) and extreme motion speed close to the camera. This file is use to replace bad frames on the above primary T file. In some cases spots are removed in the first frame after scene change, never last frame, and never using T. Since above SCSelect is being use, I suppose this is now a moot point. The number of frames extracted was less compare to previous episodes, mostly because of SCSelect and T.

Source file is still used to replace primary file where there are flashes, but far less due to F and far less chance of having to fix spotty source frame. Today = zero.

I have enclosed a zip file with some images. You can compare the results of the above with other mod. After all the proof is in the pudding.

Here are the codes.

source=FFVideoSource("F:\BABYLON5\Season 2\7 A Race Through Dark Places\A Race Through Dark Places.avi").convertTOYV12(interlaced=false).killaudio()

# VARIABLES--------------------------------------------------------------------

txt_sz = 24 # Test size if infos displayed
src = source

# Spotless variables, to remove dirt and spots.-------------------------------
RadT = 1 # Temporal radius. (MCompensate arg)
Pel = 2 # Default 2. 1, 2, or 4. Maybe set 1 for HD+. (1=precision to pixel, 2=precision to half pixel, 4=quarter pixel)
BlkSzT = 16 # Default 8. MAnalyse BlkSize. 16 is better for eliminating all spots. Maybe also better where BIG noise.
BlkSzF = 8 # Default 8. MAnalyse BlkSize. Stay on 8 for greater accuracy in motion analysis and keeping details same as source file.
OLapT = (BlkSzT/2) #important not too low (eg 4 for blksz 12, because creates stairs). Can use 4 if blksz 16 in some cases.
OLapF = (BlkSzF/2) #important not too low (eg 4 for blksz 12, because creates stairs)
BblurT = 0.3 # Default 0.6. Suggest about 0.3 for denoised temporal clean clip.
BblurF = 0.6 # Default 0.6. Rare times about 0.9 helps with hands movement close to camera for better motion analysis, but a bit slower.

# Remove spots-----------------------------------------------------------------
SpotLessT=SpotLess(src,BlkSz=BlkSzT,OLap=OLapT,pel=pel,Tm=True,Bblur=BblurT,ThSAD=6000,RadT=RadT) # Truemotion=True for fixing almost all spots but will have missing details where there is too much motion.
SpotLessF=SpotLess(src,BlkSz=BlkSzF,OLap=OLapF,pel=1,Tm=False,Bblur=BblurF,ThSAD=1000,RadT=RadT) # Truemotion=False for retaining almost all details when there is too much motion but less and smaller spots fixed.
Use these frames to overwrite above frames whenever necessary.

# FUNCTIONS
#------------------------------------------------------------------------------------------------------------------
function sub(c, label, txt_sz) {
return subtitle(c, label, size = txt_sz, align=8)
}

Function SpotLess(clip c,int "RadT",int "ThSAD",int "ThSAD2",int "pel",bool "chroma", int "BlkSz",Int "Olap",bool "tm",Bool "glob", Float "bBlur", clip "dc" ) {
myName = "SpotLess: "
RadT = Default(RadT,1) # Temporal radius. (MCompensate arg)
ThSAD = Default(ThSAD,6000) # SAD threshold at radius 1 (Default Nearly OFF).
ThSAD2 = Default(ThSAD2,ThSAD) # SAD threshold at radius RadT.
Pel = Default(pel,2) # Default 2. 1, 2, or 4. Maybe set 1 for HD+. (1=precision to pixel, 2=precision to half pixel, 4=quarter pixel)
Chroma = Default(chroma,True) # MAnalyse chroma arg. If set to true, use chroma in block matching.
BlkSz = Default(BlkSz,16) # Default 8. MAnalyse BlkSize. Bigger blksz quicker and perhaps better, esp for HD clips. Maybe also better where BIG noise.
OLap = Default(OLap,BlkSz/2) # Default half of BlkSz.
Tm = Default(tm,True) # TrueMotion, Some folk swear MAnalyse(truemotion=false) is better.
Glob = Default(glob,True) # Default True, Allow set MAnalyse(global) independently of TrueMotion.
bBlur = Default(bblur,0.6) # Default 0.6. Suggest about 0.6 for better motion analysis, but a bit slower.
HasDC = dc.Defined # bblur ignored if HasDC, ie user already provided prefiltered clip.
Assert(1 <= RadT,myName + " 1 <= RadT")
Assert(0.0 <= bblur <= 1.58, myName + "0.0 <= bblur <= 1.58")
Assert(pel==1 || pel==2 || pel==4, myName + "pel==1 || pel==2 || pel==4")
pad = max(BlkSz,8)
sup = (HasDC ? dc : bBlur>0.0 ? c.blur(bBlur) : c ).MSuper(hpad=pad,vpad=pad,pel=pel,sharp=2)
# Only 1 Level required where not MAnalyse-ing.
sup_rend = (HasDC||bBlur>0.0) ? c.MSuper(hpad=pad,vpad=pad,pel=pel,sharp=2,Levels=1) : sup
MultiVec = sup.MAnalyse(multi=true,delta=RadT,blksize=BlkSz,overlap=OLap,chroma=Chroma,truemotion=Tm,global=Glob)
c.MCompensate(sup_rend,MultiVec,tr=RadT,thSad=ThSAD,thSad2=ThSAD2)
MedianBlurTemporal(radiusY=0,radiusU=0,radiusV=0,temporalradius=RadT) # Temporal median blur only [not spatial]
SelectEvery(RadT*2+1,RadT) # Return middle frame
}

output=SpotLess(src) # stack side by side, Source top left, Spotless TM true top right, Spotless TM false bottom left, ? bottom right.
S1 = StackHorizontal(\
sub(src, "Source", txt_sz),\
sub(SpotLessT, "Spotless TM true", txt_sz)\
)
S2 = StackHorizontal(\
sub(SpotLessF, "Spotless TM false", txt_sz),\
sub(blankclip(src), "", txt_sz)\
)
output = StackVertical(S1,S2)

#return output
#return SpotLessT
return SpotLessF

Trying to merge scene change into spotless - not working.

source=FFVideoSource("F:\BABYLON5\Season 2\6 A Spider In The Web\A Spider In The Web.avi").convertTOYV12(interlaced=false).killaudio()

# VARIABLES--------------------------------------------------------------------

txt_sz = 24 # Test size if infos displayed
src = source

# Spotless variables, to remove dirt and spots.-------------------------------
RadT = 1 # Temporal radius. (MCompensate arg)
Pel = 2 # Default 2. 1, 2, or 4. Maybe set 1 for HD+. (1=precision to pixel, 2=precision to half pixel, 4=quarter pixel)
BlkSzT = 16 # Default 8. MAnalyse BlkSize. 16 is better for eliminating all spots. Maybe also better where BIG noise.
BlkSzF = 8 # Default 8. MAnalyse BlkSize. Stay on 8 for greater accuracy in motion analysis and keeping details same as source file.
OLapT = (BlkSzT/2) #important not too low (eg 4 for blksz 12, because creates stairs). Can use 4 if blksz 16 in some cases.
OLapF = (BlkSzF/2) #important not too low (eg 4 for blksz 12, because creates stairs)
BblurT = 0.3 # Default 0.6. Suggest about 0.3 for denoised temporal clean clip.
BblurF = 0.6 # Default 0.6. Rare times about 0.9 helps with hands movement close to camera for better motion analysis, but a bit slower.

# Remove spots-----------------------------------------------------------------
SpotLessT=SpotLess(src,BlkSz=BlkSzT,OLap=OLapT,pel=pel,Tm=True,Bblur=BblurT,ThSAD=6000,RadT=RadT) # Truemotion=True for fixing almost all spots but will have missing details where there is too much motion.
SpotLessF=SpotLess(src,BlkSz=BlkSzF,OLap=OLapF,pel=1,Tm=False,Bblur=BblurF,ThSAD=1000,RadT=RadT) # Truemotion=False for retaining almost all details when there is too much motion but less and smaller spots fixed.
Use these frames to overwrite above frames whenever necessary.

# FUNCTIONS
#------------------------------------------------------------------------------------------------------------------
function sub(c, label, txt_sz) {
return subtitle(c, label, size = txt_sz, align=8)
}

Function SCSelect_HBD(clip dClip,clip Start,clip End,clip Motion, float "dfactor",Float "MinDif",bool "show",String "S_Frames",String "E_Frames") {
dfactor = Float(Default(dfactor,4.5)) # Suggest 2.5 -> 4.0 ::: Change default to 4.0 to emulate ScSelect
Mindif = Float(Default(MinDif ,1.0)) # Suggest 0.5 -> 4.0 ::: Change default to 0.0 to emulate ScSelect
Show = Default(show,true)
S_Frames= Default(S_Frames,"")
E_Frames= Default(E_Frames,"")
Assert(!DClip.IsRGB,"SCSelect_HBD: DClip YUV Only")
Assert(1.0 <= dFactor,"SCSelect_HBD: 1.0 <= dFactor")
Assert(0.0 <= Mindif ,"SCSelect_HBD: 0.0 <= MinDif")
try { bpc = DClip.BitsPerComponent } catch(err_msg) { bpc = 8 }
DClip = (bpc!=8) ? DClip.ConvertBits(8) : DClip
Sz = Max(Round(Motion.Height*0.05),14)
S_Frames = (S_Frames!="") ? S_Frames.RT_GetFullPathName : ""
E_Frames = (E_Frames!="") ? E_Frames.RT_GetFullPathName : ""
(S_Frames !="") ? RT_FileDelete(S_Frames) : NOP
(E_Frames !="") ? RT_FileDelete(E_Frames) : NOP
Motion.GScriptClip("""
n = current_frame
pDf = Dclip.YDifferenceFromPrevious
nDf = Dclip.YDifferenceToNext
pTh = nDf*dfactor+MinDif
nTh = pDf*dfactor+MinDif
SC = 2 # Default To SC = MOTION (Last is MOTION clip)
if (n==0) { SC = 0 Start S_Frames!=""?RT_WriteFile(S_Frames,"%d",n,Append=true):NOP} # SOS
else if(n==FrameCount-1){ SC = 1 End E_Frames!=""?RT_WriteFile(E_Frames,"%d",n,Append=true):NOP} # EOS, Fixes ScSelect SOS detect on Last frame
else if(pDf>pTh) { SC = 0 Start S_Frames!=""?RT_WriteFile(S_Frames,"%d",n,Append=true):NOP} # SOS
else if(nDf>nTh) { SC = 1 End E_Frames!=""?RT_WriteFile(E_Frames,"%d",n,Append=true):NOP} # EOS
if(show) {
mx = Max(pDf,nDf)
mn = Min(pDf,nDf)
Can_dF = (mx > mn + Mindif) # Can any values of dfactor(1.0 and aove) cause scene detect ?
Can_Md = (mx > dFactor * mn) # Can any values of MinDif(0.0 and above) cause scene detect ?
if(Can_dF || Can_Md) {
E_dF = (Can_dF) ? String(Min((mx - Mindif) / max(mn,0.00001),99.9999),"dFact=%7.4f") : ""
E_Md = (Can_Md) ? String(mx - dFactor * mn,"MinD=%7.4f") : ""
EdfS = E_dF + ((Can_dF && Can_Md) ? ":" : "") + E_Md + "}"
EdfS = ((pDf==mx) ? "{SOS:" :"{EOS:") + EdfS
} else { EdfS = "" }
Subtitle(
\ String(n,"%.0f] ") + MidStr("SEG",SC+1,1) + " " + EdfS + "\n" +
\ String(pDf,"SOS: P{%7.3f} > ") + String(pTh,"pTh{%7.3f}") + (SC==0 ? " <==":"") + "\n" +
\ String(nDf,"EOS: N{%7.3f} > ") + String(nTh,"nTh{%7.3f}") + (SC==1 ? " <==":"")
\ ,Font="Courier New",Size=Sz,lsp=0)
}
Return Last
""",Args="dfactor,Start,End,Dclip,MinDif,show,S_Frames,E_Frames,Sz",Local=true) # Needs Grunt for args
return Last
}

Function SpotLess(clip c,int "RadT",int "ThSAD",int "ThSAD2",int "pel",bool "chroma", int "BlkSz",Int "Olap",bool "tm",Bool "glob", Float "bBlur", clip "dc" ) {
myName = "SpotLess: "
RadT = Default(RadT,1) # Temporal radius. (MCompensate arg)
ThSAD = Default(ThSAD,6000) # SAD threshold at radius 1 (Default Nearly OFF).
ThSAD2 = Default(ThSAD2,ThSAD) # SAD threshold at radius RadT.
Pel = Default(pel,2) # Default 2. 1, 2, or 4. Maybe set 1 for HD+. (1=precision to pixel, 2=precision to half pixel, 4=quarter pixel)
Chroma = Default(chroma,True) # MAnalyse chroma arg. If set to true, use chroma in block matching.
BlkSz = Default(BlkSz,16) # Default 8. MAnalyse BlkSize. Bigger blksz quicker and perhaps better, esp for HD clips. Maybe also better where BIG noise.
OLap = Default(OLap,BlkSz/2) # Default half of BlkSz.
Tm = Default(tm,True) # TrueMotion, Some folk swear MAnalyse(truemotion=false) is better.
Glob = Default(glob,True) # Default True, Allow set MAnalyse(global) independently of TrueMotion.
bBlur = Default(bblur,0.6) # Default 0.6. Suggest about 0.6 for better motion analysis, but a bit slower.
HasDC = dc.Defined # bblur ignored if HasDC, ie user already provided prefiltered clip.
Assert(1 <= RadT,myName + " 1 <= RadT")
Assert(0.0 <= bblur <= 1.58, myName + "0.0 <= bblur <= 1.58")
Assert(pel==1 || pel==2 || pel==4, myName + "pel==1 || pel==2 || pel==4")
pad = max(BlkSz,8)
sup = (HasDC ? dc : bBlur>0.0 ? c.blur(bBlur) : c ).MSuper(hpad=pad,vpad=pad,pel=pel,sharp=2)
# Only 1 Level required where not MAnalyse-ing.
sup_rend = (HasDC||bBlur>0.0) ? c.MSuper(hpad=pad,vpad=pad,pel=pel,sharp=2,Levels=1) : sup
MultiVec = sup.MAnalyse(multi=true,delta=RadT,blksize=BlkSz,overlap=OLap,chroma=Chroma,truemotion=Tm,global=Glob)
c.MCompensate(sup_rend,MultiVec,tr=RadT,thSad=ThSAD,thSad2=ThSAD2)
MedianBlurTemporal(radiusY=0,radiusU=0,radiusV=0,temporalradius=RadT) # Temporal median blur only [not spatial]
SelectEvery(RadT*2+1,RadT) # Return middle frame
}


prev = SpotLessT.selectevery(1,-1)
next = SpotLessT.selectevery(1,1)
SpotLessT=SCSelect_HBD(Last,prev,next)

output=SpotLess(src) # stack side by side, Source top left, Spotless TM true top right, Spotless TM false bottom left, ? bottom right.
S1 = StackHorizontal(\
sub(src, "Source", txt_sz),\
sub(SpotLessT, "Spotless TM true", txt_sz)\
)
S2 = StackHorizontal(\
sub(SpotLessF, "Spotless TM false", txt_sz),\
sub(blankclip(src), "", txt_sz)\
)
output = StackVertical(S1,S2)

return output
#return SpotLessT
#return SpotLessF

StainlessS
10th October 2021, 21:37
Some interesting observations, thank you.

EDIT: this dont do anything [output overwritten later]

output=SpotLess(src) # stack side by side, Source top left, Spotless TM true top right, Spotless TM false bottom left, ? bottom right.


EDIT: Your sample PAL DVD ?
My B5 has Progressive, Interlaced and even Perverse Telecine sections.

coolgit
10th October 2021, 22:15
Some interesting observations, thank you.

EDIT: this dont do anything [output overwritten later]

output=SpotLess(src) # stack side by side, Source top left, Spotless TM true top right, Spotless TM false bottom left, ? bottom right.


EDIT: Your sample PAL DVD ?
My B5 has Progressive, Interlaced and even Perverse Telecine sections.

PAL yes. Same problems with yours.
However my dad just bought the new B5, yucky 4:3 live but brilliant cgi. I have now changed tack and now using original 16/9 live with new 4/3 cgi with black v bars. Brilliant, no need to worry about deinterlacing old cgi which is a mess.

StainlessS
10th October 2021, 22:35
OK thanks, gotcha.
Had no idea they had done that.
https://www.google.com/search?client=firefox-b-d&q=babylon+5+live+cgi

Some difference :) :- https://www.youtube.com/watch?v=p_oFTEAU9O8

coolgit
10th October 2021, 23:34
Amazingly got spotless and scene change to work.

Just need to output the frame numbers of detected scene frames to a text file for cross checking accuracy of detection. Your current output is of no use to me and I don't understand it either.

source=FFVideoSource("F:\BABYLON5\Season 2\9 The Coming Of Shadows\The Coming Of Shadows SA.avi").convertTOYV12(interlaced=false).killaudio()
#filename="F:\BABYLON5\Season 2\8 Soul Mates\Spotless and sc_hbd.txt"

# VARIABLES--------------------------------------------------------------------

txt_sz = 24 # Test size if infos displayed
src = source


# Spotless variables, to remove dirt and spots.-------------------------------
RadT = 1 # Temporal radius. (MCompensate arg)
Pel = 2 # Default 2. 1, 2, or 4. Maybe set 1 for HD+. (1=precision to pixel, 2=precision to half pixel, 4=quarter pixel)
BlkSzT = 16 # Default 8. MAnalyse BlkSize. 16 is better for eliminating all spots. Maybe also better where BIG noise.
BlkSzF = 8 # Default 8. MAnalyse BlkSize. Stay on 8 for greater accuracy in motion analysis and keeping details same as source file.
OLapT = (BlkSzT/2) #important not too low (eg 4 for blksz 12, because creates stairs). Can use 4 if blksz 16 in some cases.
OLapF = (BlkSzF/2) #important not too low (eg 4 for blksz 12, because creates stairs)
BblurT = 0.3 # Default 0.6. Suggest about 0.3 for denoised temporal clean clip.
BblurF = 0.6 # Default 0.6. Rare times about 0.9 helps with hands movement close to camera for better motion analysis, but a bit slower.

# Remove spots-----------------------------------------------------------------
SpotLessT=SpotLess(src,BlkSz=BlkSzT,OLap=OLapT,pel=pel,Tm=True,Bblur=BblurT,ThSAD=6000,RadT=RadT) # Truemotion=True for fixing almost all spots but will have missing details where there is too much motion.
SpotLessF=SpotLess(src,BlkSz=BlkSzF,OLap=OLapF,pel=1,Tm=False,Bblur=BblurF,ThSAD=1000,RadT=RadT) # Truemotion=False for retaining almost all details when there is too much motion but less and smaller spots fixed.
# Use these frames to overwrite above frames whenever necessary.


# FUNCTIONS
#------------------------------------------------------------------------------------------------------------------
function sub(c, label, txt_sz) {
return subtitle(c, label, size = txt_sz, align=8)
}

Function SCSelect_HBD(clip dClip,clip Start,clip End,clip Motion, float "dfactor",Float "MinDif",bool "show",String "S_Frames",String "E_Frames") {
dfactor = Float(Default(dfactor,4.5)) # Suggest 2.5 -> 4.0 ::: Change default to 4.0 to emulate ScSelect
Mindif = Float(Default(MinDif,1.0)) # Suggest 0.5 -> 4.0 ::: Change default to 0.0 to emulate ScSelect
Show = Default(show,true)
S_Frames= Default(S_Frames,"")
E_Frames= Default(E_Frames,"")
Assert(!DClip.IsRGB,"SCSelect_HBD: DClip YUV Only")
Assert(1.0 <= dFactor,"SCSelect_HBD: 1.0 <= dFactor")
Assert(0.0 <= Mindif ,"SCSelect_HBD: 0.0 <= MinDif")
try { bpc = DClip.BitsPerComponent } catch(err_msg) { bpc = 8 }
DClip = (bpc!=8) ? DClip.ConvertBits(8) : DClip
Sz = Max(Round(Motion.Height*0.05),12)
S_Frames = (S_Frames!="") ? S_Frames.RT_GetFullPathName : ""
E_Frames = (E_Frames!="") ? E_Frames.RT_GetFullPathName : ""
(S_Frames !="") ? RT_FileDelete(S_Frames) : NOP
(E_Frames !="") ? RT_FileDelete(E_Frames) : NOP
Motion.GScriptClip("""
n = current_frame
pDf = Dclip.YDifferenceFromPrevious
nDf = Dclip.YDifferenceToNext
pTh = nDf*dfactor+MinDif
nTh = pDf*dfactor+MinDif
SC = 2 # Default To SC = MOTION (Last is MOTION clip)
if (n==0) { SC = 0 Start S_Frames!=""?RT_WriteFile(S_Frames,"%d",n,Append=true):NOP} # SOS
else if(n==FrameCount-1){ SC = 1 End E_Frames!=""?RT_WriteFile(E_Frames,"%d",n,Append=true):NOP} # EOS, Fixes ScSelect SOS detect on Last frame
else if(pDf>pTh) { SC = 0 Start S_Frames!=""?RT_WriteFile(S_Frames,"%d",n,Append=true):NOP} # SOS
else if(nDf>nTh) { SC = 1 End E_Frames!=""?RT_WriteFile(E_Frames,"%d",n,Append=true):NOP} # EOS
if(show) {
mx = Max(pDf,nDf)
mn = Min(pDf,nDf)
Can_dF = (mx > mn + Mindif) # Can any values of dfactor(1.0 and aove) cause scene detect ?
Can_Md = (mx > dFactor * mn) # Can any values of MinDif(0.0 and above) cause scene detect ?
if(Can_dF || Can_Md) {
E_dF = (Can_dF) ? String(Min((mx - Mindif) / max(mn,0.00001),99.9999),"dFact=%7.4f") : ""
E_Md = (Can_Md) ? String(mx - dFactor * mn,"MinD=%7.4f") : ""
EdfS = E_dF + ((Can_dF && Can_Md) ? ":" : "") + E_Md + ""
EdfS = ((pDf==mx) ? "SOS:" :"EOS:") + EdfS
} else { EdfS = "" }
Subtitle(
\ String() + EdfS + "\n" +
\ String(pDf,"SOS") + (SC==0 ? " <":"") + "\n" +
\ String(nDf,"EOS") + (SC==1 ? " <":"")
\ ,Font="Courier New",Size=Sz,lsp=0)
}
Return Last
""",Args="dfactor,Start,End,Dclip,MinDif,show,S_Frames,E_Frames,Sz",Local=true) # Needs Grunt for args
return Last
}

Function SpotLess(clip c,int "RadT",int "ThSAD",int "ThSAD2",int "pel",bool "chroma", int "BlkSz",Int "Olap",bool "tm",Bool "glob", Float "bBlur", clip "dc" ) {
myName = "SpotLess: "
RadT = Default(RadT,1) # Temporal radius. (MCompensate arg)
ThSAD = Default(ThSAD,6000) # SAD threshold at radius 1 (Default Nearly OFF).
ThSAD2 = Default(ThSAD2,ThSAD) # SAD threshold at radius RadT.
Pel = Default(pel,2) # Default 2. 1, 2, or 4. Maybe set 1 for HD+. (1=precision to pixel, 2=precision to half pixel, 4=quarter pixel)
Chroma = Default(chroma,True) # MAnalyse chroma arg. If set to true, use chroma in block matching.
BlkSz = Default(BlkSz,16) # Default 8. MAnalyse BlkSize. Bigger blksz quicker and perhaps better, esp for HD clips. Maybe also better where BIG noise.
OLap = Default(OLap,BlkSz/2) # Default half of BlkSz.
Tm = Default(tm,True) # TrueMotion, Some folk swear MAnalyse(truemotion=false) is better.
Glob = Default(glob,True) # Default True, Allow set MAnalyse(global) independently of TrueMotion.
bBlur = Default(bblur,0.6) # Default 0.6. Suggest about 0.6 for better motion analysis, but a bit slower.
HasDC = dc.Defined # bblur ignored if HasDC, ie user already provided prefiltered clip.
Assert(1 <= RadT,myName + " 1 <= RadT")
Assert(0.0 <= bblur <= 1.58, myName + "0.0 <= bblur <= 1.58")
Assert(pel==1 || pel==2 || pel==4, myName + "pel==1 || pel==2 || pel==4")
pad = max(BlkSz,8)
sup = (HasDC ? dc : bBlur>0.0 ? c.blur(bBlur) : c ).MSuper(hpad=pad,vpad=pad,pel=pel,sharp=2)
# Only 1 Level required where not MAnalyse-ing.
sup_rend = (HasDC||bBlur>0.0) ? c.MSuper(hpad=pad,vpad=pad,pel=pel,sharp=2,Levels=1) : sup
MultiVec = sup.MAnalyse(multi=true,delta=RadT,blksize=BlkSz,overlap=OLap,chroma=Chroma,truemotion=Tm,global=Glob)
c.MCompensate(sup_rend,MultiVec,tr=RadT,thSad=ThSAD,thSad2=ThSAD2)
MedianBlurTemporal(radiusY=0,radiusU=0,radiusV=0,temporalradius=RadT) # Temporal median blur only [not spatial]
SelectEvery(RadT*2+1,RadT) # Return middle frame
}

prev = SpotLessT.selectevery(1,-1)
next = SpotLessT.selectevery(1,1)
SpotLessSCT=src.SCSelect_HBD(next,prev,SpotLessT,show=false)

S1 = StackHorizontal(\
sub(src, "Source", txt_sz),\
sub(SpotLessT, "Spotless TM true", txt_sz)\
)
S2 = StackHorizontal(\
sub(SpotLessF, "Spotless TM false", txt_sz),\
sub(SpotLessSCT, "Spotless and scene change", txt_sz)\
)
output = StackVertical(S1,S2)

#Return output # Comparison

return SpotLessSCT # Conversion
#return SpotLessT
#return SpotLessF

# Debugging to check scene change detection------------------------------------
prev = SpotLessT.selectevery(1,-1)
next = SpotLessT.selectevery(1,1)
SpotLessSCTD=src.SCSelect_HBD(next,prev,SpotLessT,show=true)

SSCTD = StackHorizontal(\
sub(src, "Source", txt_sz),\
sub(SpotLessSCTD, "Spotless and scene change", txt_sz)\
)

#Return SSCTD # Comparison

#WriteFileIf(SpotLessSCT, filename,
#\ "SC==0", "current_frame", """ ": Start" """,
#\ "SC==1", "current_frame", """ ": End" """, flush=true)

#WriteFileIf(src, filename, "(SC==0) || (SC==1)", "current_frame", """ "(: Start)" """ || """ "(: End)" """, flush=true)

See post 190 for updated script.

coolgit
10th October 2021, 23:44
OK thanks, gotcha.
Had no idea they had done that.
https://www.google.com/search?client=firefox-b-d&q=babylon+5+live+cgi

Some difference :) :- https://www.youtube.com/watch?v=p_oFTEAU9O8

Saw them years ago. There was one showing orangey tint making white people look like Donald Trump and i thought, cricky i ain't buying that 4/3 orangutan Trump 5.

coolgit
13th October 2021, 12:52
Stainlesss

How do I move your scselect_hbd text output higher to the added black bar?

I used addborder(0,100,0,0) in the debug section for debugging purpose only so the text can go in there. I got the spotless text in there but not the rest.

Checking that spot was fully removed is hard when the text is in the way.

StainlessS
13th October 2021, 15:18
If you are taling about the ScSelect_HBD metrics, then cannot be done, ScSelect_HBD knows nothing of the [ external to ScSelect_HBD() ] subtitled border that will be added later.
Your only option might be to eg create a clip with metrics, and chop off and keep the metrics, and another without metrics,
stack the metrics above the no metric clip.
You would also need a dummy [blankclip] clip so that your Stacked frames are same dimensions.

Do that asnwer your question ?

EDIT: I dont actually see anywhere in script that has an Addborders nor a BlackClip.

coolgit
13th October 2021, 18:05
The addborder was removed as it didn't work. I will do a small border for spotless script.

SO i should have source clip then clip with metrics then clip without metric then blank clip? 2x2 stacked.

StainlessS
14th October 2021, 01:14
2x2 stack sounds good.

coolgit
14th October 2021, 14:00
Final update. Works like a dream.

Changed to dfactor,5.0 and MinDif,5.0 to prevent false scene changes in static scenes like SCSelect did. On average most scene changes were 20 or above for both.

source=FFVideoSource("F:\whatever filename directory .avi").convertTOYV12(interlaced=false).killaudio()


# VARIABLES--------------------------------------------------------------------

txt_sz = 24 # Test size if infos displayed
src = source


# Spotless variables, to remove dirt and spots.-------------------------------
RadT = 1 # Temporal radius. (MCompensate arg)
BlkSzT = 16 # Default 8. MAnalyse BlkSize. 16 is better for eliminating all spots. Maybe also better where BIG noise.
BlkSzF = 8 # Default 8. MAnalyse BlkSize. Stay on 8 for greater accuracy in motion analysis and keeping details same as source file.
OLapT = (BlkSzT/2) #important not too low (eg 4 for blksz 12, because creates stairs). Can use 4 if blksz 16 in some cases.
OLapF = (BlkSzF/2) #important not too low (eg 4 for blksz 12, because creates stairs)
BblurT = 0.3 # Default 0.6. Suggest about 0.3 for denoised temporal clean clip.
BblurF = 0.6 # Default 0.6. Rare times about 0.9 helps with hands movement close to camera for better motion analysis, but a bit slower.

# Remove spots-----------------------------------------------------------------
SpotLessT=SpotLess(src,BlkSz=BlkSzT,OLap=OLapT,pel=2,Tm=True,Bblur=BblurT,ThSAD=6000,RadT=RadT) # Truemotion=True for fixing almost all spots but will have missing details where there is too much motion.
SpotLessF=SpotLess(src,BlkSz=BlkSzF,OLap=OLapF,pel=1,Tm=False,Bblur=BblurF,ThSAD=1000,RadT=RadT) # Truemotion=False for retaining almost all details when there is too much motion but less and smaller spots fixed.
# Use these frames to overwrite above frames whenever necessary.


# FUNCTIONS
#------------------------------------------------------------------------------------------------------------------
function sub(c, label, txt_sz) {
return subtitle(c, label, size = txt_sz, align=8)
}

Function SCSelect_HBD(clip dClip,clip Start,clip End,clip Motion, float "dfactor",Float "MinDif",bool "show",String "S_Frames",String "E_Frames") {
dfactor = Float(Default(dfactor,5.0)) # Suggest 2.5 -> 4.0 ::: Change default to 4.0 to emulate ScSelect
Mindif = Float(Default(MinDif,5.0)) # Suggest 0.5 -> 4.0 ::: Change default to 0.0 to emulate ScSelect
Show = Default(show,true)
S_Frames= Default(S_Frames,"")
E_Frames= Default(E_Frames,"")
Assert(!DClip.IsRGB,"SCSelect_HBD: DClip YUV Only")
Assert(1.0 <= dFactor,"SCSelect_HBD: 1.0 <= dFactor")
Assert(0.0 <= Mindif ,"SCSelect_HBD: 0.0 <= MinDif")
try { bpc = DClip.BitsPerComponent } catch(err_msg) { bpc = 8 }
DClip = (bpc!=8) ? DClip.ConvertBits(8) : DClip
Sz = Max(Round(Motion.Height*0.05),12)
S_Frames = (S_Frames!="") ? S_Frames.RT_GetFullPathName : ""
E_Frames = (E_Frames!="") ? E_Frames.RT_GetFullPathName : ""
(S_Frames !="") ? RT_FileDelete(S_Frames) : NOP
(E_Frames !="") ? RT_FileDelete(E_Frames) : NOP
Motion.GScriptClip("""
n = current_frame
pDf = Dclip.YDifferenceFromPrevious
nDf = Dclip.YDifferenceToNext
pTh = nDf*dfactor+MinDif
nTh = pDf*dfactor+MinDif
SC = 2 # Default To SC = MOTION (Last is MOTION clip)
if (n==0) { SC = 0 Start S_Frames!=""?RT_WriteFile(S_Frames,"%d",n,Append=true):NOP} # SOS
else if(n==FrameCount-1){ SC = 1 End E_Frames!=""?RT_WriteFile(E_Frames,"%d",n,Append=true):NOP} # EOS, Fixes ScSelect SOS detect on Last frame
else if(pDf>pTh) { SC = 0 Start S_Frames!=""?RT_WriteFile(S_Frames,"%d",n,Append=true):NOP} # SOS
else if(nDf>nTh) { SC = 1 End E_Frames!=""?RT_WriteFile(E_Frames,"%d",n,Append=true):NOP} # EOS
if(show) {
mx = Max(pDf,nDf)
mn = Min(pDf,nDf)
Can_dF = (mx > mn + Mindif) # Can any values of dfactor(1.0 and aove) cause scene detect ?
Can_Md = (mx > dFactor * mn) # Can any values of MinDif(0.0 and above) cause scene detect ?
if(Can_dF || Can_Md) {
E_dF = (Can_dF) ? String(Min((mx - Mindif) / max(mn,0.00001),99.9999),"dFact=%7.4f") : ""
E_Md = (Can_Md) ? String(mx - dFactor * mn,"MinD=%7.4f") : ""
EdfS = E_dF + ((Can_dF && Can_Md) ? ":" : "") + E_Md + ""
EdfS = ((pDf==mx) ? "SOS:" :"EOS:") + EdfS
} else { EdfS = "" }
Subtitle(
\ String() + EdfS + "\n" +
\ String(pDf,"SOS") + (SC==0 ? " <":"") + "\n" +
\ String(nDf,"EOS") + (SC==1 ? " <":"")
\ ,Font="Courier New",Size=Sz,lsp=0)
}
Return Last
""",Args="dfactor,Start,End,Dclip,MinDif,show,S_Frames,E_Frames,Sz",Local=true) # Needs Grunt for args
return Last
}

Function SpotLess(clip c,int "RadT",int "ThSAD",int "ThSAD2",int "pel",bool "chroma", int "BlkSz",Int "Olap",bool "tm",Bool "glob", Float "bBlur", clip "dc" ) {
myName = "SpotLess: "
RadT = Default(RadT,1) # Temporal radius. (MCompensate arg)
ThSAD = Default(ThSAD,6000) # SAD threshold at radius 1 (Default Nearly OFF).
ThSAD2 = Default(ThSAD2,ThSAD) # SAD threshold at radius RadT.
Pel = Default(pel,2) # Default 2. 1, 2, or 4. Maybe set 1 for HD+. (1=precision to pixel, 2=precision to half pixel, 4=quarter pixel)
Chroma = Default(chroma,True) # MAnalyse chroma arg. If set to true, use chroma in block matching.
BlkSz = Default(BlkSz,16) # Default 8. MAnalyse BlkSize. Bigger blksz quicker and perhaps better, esp for HD clips. Maybe also better where BIG noise.
OLap = Default(OLap,BlkSz/2) # Default half of BlkSz.
Tm = Default(tm,True) # TrueMotion, Some folk swear MAnalyse(truemotion=false) is better.
Glob = Default(glob,True) # Default True, Allow set MAnalyse(global) independently of TrueMotion.
bBlur = Default(bblur,0.6) # Default 0.6. Suggest about 0.6 for better motion analysis, but a bit slower.
HasDC = dc.Defined # bblur ignored if HasDC, ie user already provided prefiltered clip.
Assert(1 <= RadT,myName + " 1 <= RadT")
Assert(0.0 <= bblur <= 1.58, myName + "0.0 <= bblur <= 1.58")
Assert(pel==1 || pel==2 || pel==4, myName + "pel==1 || pel==2 || pel==4")
pad = max(BlkSz,8)
sup = (HasDC ? dc : bBlur>0.0 ? c.blur(bBlur) : c ).MSuper(hpad=pad,vpad=pad,pel=pel,sharp=2)
# Only 1 Level required where not MAnalyse-ing.
sup_rend = (HasDC||bBlur>0.0) ? c.MSuper(hpad=pad,vpad=pad,pel=pel,sharp=2,Levels=1) : sup
MultiVec = sup.MAnalyse(multi=true,delta=RadT,blksize=BlkSz,overlap=OLap,chroma=Chroma,truemotion=Tm,global=Glob)
c.MCompensate(sup_rend,MultiVec,tr=RadT,thSad=ThSAD,thSad2=ThSAD2)
MedianBlurTemporal(radiusY=0,radiusU=0,radiusV=0,temporalradius=RadT) # Temporal median blur only [not spatial]
SelectEvery(RadT*2+1,RadT) # Return middle frame
}

prev = SpotLessT.selectevery(1,-1)
next = SpotLessT.selectevery(1,1)
SpotLessSCT=src.SCSelect_HBD(next,prev,SpotLessT,show=false)

S1 = StackHorizontal(\
sub(src, "Source", txt_sz),\
sub(SpotLessT, "Spotless TM true", txt_sz)\
)
S2 = StackHorizontal(\
sub(SpotLessF, "Spotless TM false", txt_sz),\
sub(SpotLessSCT, "Spotless and scene change", txt_sz)\
)
output = StackVertical(S1,S2)

#Return output # Comparison

#return SpotLessSCT # Conversion
#return SpotLessT
#return SpotLessF

# Debugging to check scene change detection------------------------------------
prev = SpotLessT.selectevery(1,-1)
next = SpotLessT.selectevery(1,1)
SpotLessSCTDM=src.SCSelect_HBD(next,prev,SpotLessT,show=true)

SSCTD1 = StackHorizontal(\
sub(src.AddBorders(0,30,0,0), "Source", txt_sz),\
sub(SpotLessSCTDM.AddBorders(0,30,0,0), "Spotless and scene change with metrics", txt_sz)\
)
SSCTD2 = StackHorizontal(\
sub(SpotLessSCT.AddBorders(0,30,0,0), "Spotless and scene change without metrics", txt_sz),\
sub(BlankClip(src).AddBorders(0,30,0,0), "Blank", txt_sz)\
)
outputD = StackVertical(SSCTD1,SSCTD2)

Return outputD # Comparison

StainlessS
14th October 2021, 15:11
CoolG, bout ScSecelct_HBD,
Did this lot make any sense to you, was it at all useful/usable ? [its not immediately obvious to me unless I got the below doc in front of me :) ].


Show, Default False. In original ScSelect, this arg is called Debug, and output some info to DebugView (google),
we show info on frame and so have changed arg name to Show but of roughly same functionality.

Metrics when Show=True (metrics provided as if in 8 bit range):
>>>>>>>>>>>>>>>>>
20531 ] S {SOS: dFact= 19.7365:MinD=54.6495}
SOS: P{ 66.214} > pTh{ 12.565}<==
EOS: N{ 3.304} > nTh{232.750}
<<<<<<<<<<<<<<<<<

20531, Is current Frame Number
S, Is flagging a detected Start-Of-Scene (Also 'E'=End-Of-Scene, and 'G' = Global motion.
SOS: P{ 66.214} > pTh{ 12.565} <== The arrow "<==" at the end denotes a SOS (Start Of Scene) detection.
'P' (short for pDf, screen space saving) is difference to previous frame.
pTh = dFactor * nDf + MinDif
EOS: N{ 3.304} > nTh{232.750} Much as Above, but no EOS (End Of Scene) detection.
'N' (short for nDf), is difference to next frame.
nTh = dFactor * pDf + MinDif

{SOS: dFact= 19.7365:MinD=54.6495}, NOT ALWAYS SHOWN.
The Scene change Arg, matching estimate info, in this case for SOS estimate.
Shown only if pDf, nDF, and arg limits on dFactor and MinDif could
produce either SOS or EOS. This indicator only makes any sense on frames
that YOU have decided should be a scene change frame, otherwise ignore.
dFact is the dFactor that will produce an EXACT match of the threshold
(SOS pTh or EOS nTh) to the higher difference {SOS pDf or EOS nDf), when
MinDif unchanged.
As this example shows SOS, so the threshold is pTh and higher difference is pDf,
so it balances this line,
P{ 66.214} == pTh{66.214}, or
P{ 66.214} == {dfact * nDf + MinDif)
P{ 66.214} == {19.7365 * nDf + MinDif)
So in this particular instance it shows that the maximum value for dFactor arg
that could produce SOS on this frame with current Mindif is LESS THAN 19.7365.
It shows that lowest value of dFactor that would fail to produce a SOS, so the maxium
value of dFactor that would succeed to detect SOS for current frame is just below this
value. (pDf has to be greater than pTh so when equal will fail SOS detect).
Similar can be deduced when EOS estimator is shown.
MinD is similar estimator (in this case again for SOS) which shows where MinDif
will balance (with current dFactor unchanged)
P{ 66.214} == pTh{66.214}, or
P{ 66.214} == {dfactor * nDf + MinD)
P{ 66.214} == {dfactor * nDf + 54.641195)
Similar can be deduced when EOS estimator is shown.
Hope some of that made at least a little sense, is intended to give you some
idea about how much you can raise (or lower) dFactor or Mindif to get
a successful detection on current frame.


Changed to dfactor,5.0 and MinDif,5.0 to prevent false scene changes in static scenes like SCSelect did
Did you actually experience false detects ?
Default values should have been pretty good. MinDif=1.0 (default) should avoid almost all false scene change detects [possible exception some anime].

coolgit
14th October 2021, 16:27
CoolG, bout ScSecelct_HBD,
Did this lot make any sense to you, was it at all useful/usable ?

A bit of it.

Only dfactor and mindif are important in deciding scene change. The rest doesn't do anything hence why I'd removed it from the subtitle. There is such a thing called 'information overload'. If both are above the variables of, currently, 5.0 then it is a scene change.

Test it on s2 ep7 A Race Through Dark Places, frames 3300 onwards where Bester was torturing a guy for info. Pay attention to the static frames where the background was black and white (like chess board pattern), behind Bester. Some frames jumped up 2 ish pixels then back down to normal. SCSelect flagged this as scene change and swap frames. Yours, because of mindif, didn't except for 1 frame because mindif was 1.0. Hence why putting it up to 5.0 is the solution and doesn't affect real scene changes.

In GROPOS I had 1 scene change that wasn't detected but was detected by vdub. My vdub is set at 240. It does the job and stops when there too much motion as those frames will mostly needs replacing by spotlessF file. The failed detection was because the background was the same except the camera moved from 1 actor to another. Does it matter? If vdub stops then i can see if there is any spots before moving on. If there isn't then detection failure doesn't matter, no frames are swapped. However in this case there was a spot and that is when I knew the detection failed. That was the only frame I had to fix in photoshop, just 1, bloody brilliant compare to 50 to 70.

In essence explaining dfactor and mindif is enough. P, Pth, ndf, are not important and just confused the hell out of me.

StainlessS
15th October 2021, 00:15
Thanks for your insight :)
[it is though of use to me to find out why something caused +ve/-ve detect, even if diags from numbers not immediately obvious, even to me].

EDIT: I could though add a "verbose" type arg to allow switch on/off dfact/minD estimates.

coolgit
16th October 2021, 15:49
Just curious how this happened. Clear scene change but not detected and low dfactor and 0 mindif. Vdub had no problems.

https://i.ibb.co/vHpKztV/029835.png

https://i.ibb.co/MfFphtG/029836.png

Also Mindif reduce to 3.0. It worked better as 5.0 was an overkill on my part.

coolgit
16th October 2021, 16:05
Full script in two parts. Include both functions explanations. Put it together ok.

/*
SpotLess v1.07Beta. temporal spot/noise remover, by StainlessS @ Doom9. https://forum.doom9.org/showthread.php?t=181777
Original idea from Didée post :- https://forum.doom9.org/showthread.php?p=1402690#post1402690
Req:- Pinterf MvTools2(), Pinterf Medianblur2() with MedianBlurTemporal rather than the MedianBlurT() from Didée post.
With appropriate plugins, will be AVS+ colorspace and x64 compatible.
Fine using Avs+ Prefetch() so long as current Pinterf plugins, and Frame Accurate source. RequestLinear() following the source filter might suffice as frame accurate source filter.

NOT FOR cartoon/anime, live video only, sorry.

v1.01, Remove RadT Max check. Add MSuper(hpad=16,vpad=16). Add BlkSz arg.
v1.02, Add some stuff.
v1.03, Frame size auto config of args removed (found instances where caused problems). Added Glob and bBlur args.
v1.04, Script breaking changes, I guess is more flexible if can also set ThSAD, inserted ThSAD 3rd arg. RadT now default 1, was 2, dont over denoise unless requested.
v1.05, Additional checks on args.
v1.06, Glob Default true, Almost always better.
v1.07Beta, bBlur Default changed from 0.0 OFF, to 0.6, slower but better for mc analysis. Maybe try 1.0 for HD to overcome HD 'gritiness'.
Added dc arg, detect clip.

SpotLess(clip c,int "RadT"=1,int "ThSAD"=10000,int "ThSAD2"=ThSAD,int "pel"=2,bool "chroma"=true, int "BlkSz"=8,Int "Olap"=BlkSz/2,
\ bool "tm"=true,Bool "glob"=True,Float "bBlur"=0.6, clip "dc"=Undefined)

RadT, 1 or more, Default 1. Removes Spots on up to RadT [Temporal Radius] consecutive frames.
RadT > 2 will usually be overkill. Setting too high could possibly result in blurring.
Each pixel in result frame is median pixel value of (2*RadT+1) motion compensated frames (including source, ie current_frame-RadT to current_frame+RadT).

ThSAD, Default 10000=NEARLY OFF(ie ignore hardly any bad blocks), 0 < ThSAD < 16320(8*8*255). 8x8 block SAD threshold at radius 1 (ie at current_frame +- 1) [SAD, Sum of Absolute (pixelwise) Differences].
ThSAD and ThSAD2 suggested absolute minimum of maybe about 400.
ThSAD and ThSAD2 thresholds are based on 8 bit 8x8 block, irrespective of colorspace depth or BlkSz, max=8x8x255=16320, use same thresholds where High Bit Depth.
In mvTools MCompensate(), when creating a compensated block the SAD between compensated block and the same original block in current_frame, the 8 bit SAD is measured and if
greater than SAD threshold then that block is ignored and uses original block from current frame instead. [The compensated block is judged too different, so ignored & original block used instead
in the result MCompensated frame].
Where eg ThSAD=64, AVERAGE absolute single pixel difference threshold would be 64/(8*8)=1, so AVERAGE absolute pixel difference greater than 1 would ignore that mcompensated block and use the
block from current frame in the resulting mcompensated frame instead. This example allows for all pixels in a 8x8 block to be different by 1, or a single pixel in 8x8 block to be different by 64,
or some other mixture.
A problem with above is, if a low ThSAD and current_frame block is mostly noise, so compensated blocks could be judged bad because they are too different to the bad noisey block, and the result
block may/will be just as bad as the noisy source block. A possible solution to this problem is to have a higher SAD threshold and/or have a bigger BlkSize so that the number of bad source pixels
after converting/scaling to as if an 8x8 block, will contain fewer bad noise pixels. So, SpotLess BlkSz arg would ideally maybe 4 or more times the area of the largest spots that you have, and a SAD
threshold big enough so as to not ignore the block [ wild guess minimum SAD threshold for big spot sizes of (8x8x255)/4 = 4080 ].
Where a complete source frame is bad, then maybe should have very high (eg 10000) SAD threshold, and BlkSz may not really matter too much.
It is not the end of the world if some of the compensated blocks are ignored and swapped for the original current_frame block. Nor is it the end of the world if
no blocks were ignored because of high SAD threshold. The final result pixel is median pixel value of (2*RadT+1) motion compensated blocks, so allowing for some mistakes by choosing the
middle pixel value.
I've just tested real bad double frame, full frame luma and chroma corruption, with below line:
SpotLess(RadT=5,ThSAD=1000000,ThSAD2=1000000,pel=2,chroma=false,BlkSz=8,Olap=4,tm=false,glob=false,bBlur=0.0)
And although both SAD thresholds of 1 million, are totally impossible and so no blocks could possibly be ignored and yet we still got pretty good results, all frames were fixed
as we still had the temporal median filter to fall back on and pick the middle pixel value.

From mvtools2 docs:
ThSAD is SAD threshold for safe (dummy) compensation.
If block SAD is above the thSAD, the block is bad, and we use source block instead of the compensated block. Default is 10000 (practically disabled).

ThSAD2, Default ThSAD, 0 < ThSAD2 < 16320(8*8*255), Lower removes fewer spots, but less chance of blurring.
ThSAD2 sets the SAD [Sum of Absolute Differences] threshold for most distant frame from current_frame at distance RadT, with those frames that are distances in-between 1 and RadT
acquiring a SAD threshold linearly interpolated between the two.
From mvtools2 docs:
ThSAD2:
Defines the SAD soft threshold for the furthest frames at current_frame +- RadT.
The actual SAD threshold for each reference frame is a smooth interpolation between the original thSAD (close to the current frame)
and thSAD2. Setting thSAD2 lower than thSAD allows large temporal radii and good compensation for low SAD blocks while reducing the global error and the
risk of bluring when the result of MCompensate is passed to a temporal denoising filter.
EDIT: Although I have said that SAD threshold being too high could result in blurred frames, that is really taken from above "risk of bluring" line from mvtools docs,
however, that warning says "temporal denoising filter", which might suggest pixel averaging, whereas we are using pixel median. I'm not sure that blurring would be the result
of having too high a SAD threshold.

Pel, Default 2. 1, 2, or 4. Maybe set 1 for HD+. (1=precision to pixel, 2=half pixel, 4=quarter pixel)

Chroma, Default True. MAnalyse chroma arg. If true, use chroma in block matching when creating vectors. Maybe use False if source B&W or color noise.

BlkSz, Default 8. MAnalyse BlkSize. Bigger blksz quicker and perhaps better for HD clips. [Info: current Pinterf MvTools allows for BlkSize=12, and overlap=6]

OLap, Default half BlkSz, Block overlap.

Tm, TrueMotion Default True. Some folk swear truemotion=false is better.

Glob, Default True (True v1.06, was same as Tm, true almost always better), Allow set MAnalyse(global) independently of TrueMotion.
From MvTools2 docs for MAnalyse,
global
Estimate global motion (at every level) and use it as an additional predictor.
Only pan shift is estimated (no zoom and rotation).
Use false to disable, use true to enable.

bBlur, Default 0.6. If used, Suggest about 0.6, where MAnalyse create vectors is performed on denoised (blurred) super clip
for better motion analysis. Maybe try 1.0 for HD clip to counteract HD 'grittiness'. bBlur ignored if dc clip specified,
If providing bBlur, then ALWAYS specify as named argument ie bBlur=bBlur, we will likely insert any additional MvTools args
before bBlur to keep them together.

dc, Default UnDefined. If dc Specified, then must be same size and colorspace as source clip.
If detection clip specified then bBlur prefilter arg is ignored, and analysis is performed on
dc.MSuper() clip [ instead of c.Blur(bBlur).MSuper() ]. Allows to provide your own prefiltered clip.
If providing dc clip, then ALWAYS specify as named argument ie dc=DC, we will likely insert any additional MvTools args
before bBlur to keep them together.

#---------------------------------------------------------------------------------------------------------------------------------------

ScSelect_HBD(), v0.01. by StainlessS @ Doom9. : https://forum.doom9.org/showthread.php?t=182392
Requires Grunt (c) Gavino. If S_Frames or E_Frames != "" then also requires RT_Stats.

SCSelect_HBD(clip dClip,clip Start,clip End,clip Motion,
float "dfactor"=3.5,Float "MinDif"=1.0,
bool "show"=False,String "S_Frames"="",String "E_Frames"="")

ScSelect_HBD(), Mod of ScSelect_Like():- https://forum.doom9.org/showthread.php?p=1644023#post1644023
Named with "_HDB" extender as should work ok with HBD in AVS+.
ScSelect_Like() emulated ScSelect() from RemoveDirt.
RemoveDirt's SCSelect(clip input, clip scene_begin, clip scene_end, clip global_motion, float dfactor, bool debug, bool planar)
To emulate original ScSelect results, use ScSelect_HBD(dFactor=4.0,MinDif=0.0).

Args:
dClip, Detection clip. May be different size and colorspace to Start, End and Motion clips. YUV only.
If High Bit Depth (greater than 8 bits per channel) then will be converted to 8 bit for scene change
detection stage. For larger source clips, you may want to downsize the dClip to speed up detection .

Start, Clip of frames that will replace frames from Motion clip when Start-Of-Scene detected.
End, Clip of frames that will replace frames from Motion clip when End-Of-Scene detected.
Motion, Clip the will be used where neither start nor end of scene detected.
Start, End, and Motion, clips can be different colorspace and size compared to dClip (unlike ScSelect),
but must all be same colorspace and size as each other, also can be high-bit-depth or RGB.

dFactor, Default 3.5. (1.0 <= dFactor) Detection factor, a multiplier applied to frame difference
metrics to decide if a scene change.
Mindif, Default 1.0. (0.0 <= Mindif, Supply MinDif as if in 8 bit range, 0.0 -> 255.0)
An additional component part of scene change detection.
Where difference measurements,
pDf = Average per pixel Luma difference, between dClip current frame and previous frame (0.0->255.0).
nDf = Average per pixel Luma difference, between dClip current frame and next frame (0.0->255.0).
pTh = dFactor * nDf + MinDif : If pDf greater than this then is SOS, Start-Of-Scene.
nTh = dFactor * pDf + MinDif : If nDf greater than this then is EOS, End-Of-Scene.
Detect method,
SOS = pDf > pTh : SOS = pDf > dFactor * nDf + MinDif, Also,
EOS = nDf > nTh : EOS = nDf > dFactor * pDf + MinDif.
(If pDf > pTh, then pDf is also greater than nDf as dFactor is at least 1.0 and MinDif at least 0.0)
Original ScSelect, used default dFactor=4.0, and equivalent of MinDif=0.0 (It had no MinDif arg).
Using only dFactor, and no Mindif (ie 0.0), is not too good as a detector, especially where
scene of low motion or near static scene, eg when nDf=0.0 then pDf value of anything above 0.0
would detect a scene change, not exactly as desired for near static scene, additinal component is needed.
In same situation where MinDif=1.0 and nDf=0.0, so SOS = pDf > pTh : SOS = pDf > 4.0 * 0.0 + 1.0
so, to detect Start of scene then pDf has to be greater than 1.0 for a start of scene detect.
In near static scene, we are most reliant upon Mindif being of sufficient magnitude to avoid a false
detection. In scene with plenty of motion, then value of dFactor becomes more important to prevent false
detection. In a very jerky or anime scene, then probably a high dFactor and high Mindif are required.
In this crazy mixed up world, you have to find a good compromise for dFactor and MinDif
so as to detect scene changes without producing false detections.
Our default of dFactor=3.5 and Mindif=1.0, is just one combination, you might prefer bigger Mindif
and smaller dFactor,(whatever you choose, some clips will require an alternative set).
I suggest dFactor usually no less than about 2.5 and no more than 4.0, and Mindif no less than 0.5, and
maybe no more than about 4.0. As you lower dFactor, you might want to raise Mindif, and vise versa.
Anime is likely to have different requirments with bigger MinDif (maybe 4 or more but depends upon the clip).

coolgit
16th October 2021, 16:07
Part 2

Show, Default False. In original ScSelect, this arg is called Debug, and output some info to DebugView (google),
we show info on frame and so have changed arg name to Show but of roughly same functionality.

Metrics when Show=True (metrics provided as if in 8 bit range):
>>>>>>>>>>>>>>>>>
20531 ] S {SOS: dFact= 19.7365:MinD=54.6495}
SOS: P{ 66.214} > pTh{ 12.565}<==
EOS: N{ 3.304} > nTh{232.750}
<<<<<<<<<<<<<<<<<

20531, Is current Frame Number
S, Is flagging a detected Start-Of-Scene (Also 'E'=End-Of-Scene, and 'G' = Global motion.
SOS: P{ 66.214} > pTh{ 12.565} <== The arrow "<==" at the end denotes a SOS (Start Of Scene) detection.
'P' (short for pDf, screen space saving) is difference to previous frame.
pTh = dFactor * nDf + MinDif
EOS: N{ 3.304} > nTh{232.750} Much as Above, but no EOS (End Of Scene) detection.
'N' (short for nDf), is difference to next frame.
nTh = dFactor * pDf + MinDif

{SOS: dFact= 19.7365:MinD=54.6495}, NOT ALWAYS SHOWN.
The Scene change Arg, matching estimate info, in this case for SOS estimate.
Shown only if pDf, nDF, and arg limits on dFactor and MinDif could
produce either SOS or EOS. This indicator only makes any sense on frames
that YOU have decided should be a scene change frame, otherwise ignore.
dFact is the dFactor that will produce an EXACT match of the threshold
(SOS pTh or EOS nTh) to the higher difference {SOS pDf or EOS nDf), when
MinDif unchanged.
As this example shows SOS, so the threshold is pTh and higher difference is pDf,
so it balances this line,
P{ 66.214} == pTh{66.214}, or
P{ 66.214} == {dfact * nDf + MinDif)
P{ 66.214} == {19.7365 * nDf + MinDif)
So in this particular instance it shows that the maximum value for dFactor arg
that could produce SOS on this frame with current Mindif is LESS THAN 19.7365.
It shows that lowest value of dFactor that would fail to produce a SOS, so the maxium
value of dFactor that would succeed to detect SOS for current frame is just below this
value. (pDf has to be greater than pTh so when equal will fail SOS detect).
Similar can be deduced when EOS estimator is shown.
MinD is similar estimator (in this case again for SOS) which shows where MinDif
will balance (with current dFactor unchanged)
P{ 66.214} == pTh{66.214}, or
P{ 66.214} == {dfactor * nDf + MinD)
P{ 66.214} == {dfactor * nDf + 54.641195)
Similar can be deduced when EOS estimator is shown.
Hope some of that made at least a little sense, is intended to give you some
idea about how much you can raise (or lower) dFactor or Mindif to get
a successful detection on current frame.

S_Frames, Default "". Name of Start-Of-Scene Frames text file. Req RT_Stats if non "".
E_Frames, Default "". Name of End-Of-Scene Frames text file. Req RT_Stats if non "".
*/

source=FFVideoSource("F:\whatever .avi").convertTOYV12(interlaced=false).killaudio()

# VARIABLES--------------------------------------------------------------------

txt_sz = 24 # Test size if infos displayed
src = source


# Spotless variables, to remove dirt and spots.-------------------------------
RadT = 1 # Temporal radius. (MCompensate arg)
BlkSzT = 16 # Default 8. MAnalyse BlkSize. 16 is better for eliminating all spots. Maybe also better where BIG noise.
BlkSzF = 8 # Default 8. MAnalyse BlkSize. Stay on 8 for greater accuracy in motion analysis and keeping details same as source file.
OLapT = (BlkSzT/2) #important not too low (eg 4 for blksz 12, because creates stairs). Can use 4 if blksz 16 in some cases.
OLapF = (BlkSzF/2) #important not too low (eg 4 for blksz 12, because creates stairs)
BblurT = 0.3 # Default 0.6. Suggest about 0.3 for denoised temporal clean clip.
BblurF = 0.6 # Default 0.6. Rare times about 0.9 helps with hands movement close to camera for better motion analysis, but a bit slower.

# Remove spots-----------------------------------------------------------------
SpotLessT=SpotLess(src,BlkSz=BlkSzT,OLap=OLapT,pel=2,Tm=True,Bblur=BblurT,ThSAD=6000,RadT=RadT) # Truemotion=True for fixing almost all spots but will have missing details where there is too much motion.
SpotLessF=SpotLess(src,BlkSz=BlkSzF,OLap=OLapF,pel=1,Tm=False,Bblur=BblurF,ThSAD=1000,RadT=RadT) # Truemotion=False for retaining almost all details when there is too much motion but less and smaller spots fixed.
# Use these frames to overwrite above frames whenever necessary.


# FUNCTIONS
#------------------------------------------------------------------------------------------------------------------
function sub(c, label, txt_sz) {
return subtitle(c, label, size = txt_sz, align=8)
}

Function SCSelect_HBD(clip dClip,clip Start,clip End,clip Motion, float "dfactor",Float "MinDif",bool "show",String "S_Frames",String "E_Frames") {
dfactor = Float(Default(dfactor,3.5)) # Suggest 2.5 -> 4.0 ::: Change default to 4.0 to emulate ScSelect
Mindif = Float(Default(MinDif,1.0)) # Suggest 0.5 -> 4.0 ::: Change default to 0.0 to emulate ScSelect
Show = Default(show,true)
S_Frames= Default(S_Frames,"")
E_Frames= Default(E_Frames,"")
Assert(!DClip.IsRGB,"SCSelect_HBD: DClip YUV Only")
Assert(1.0 <= dFactor,"SCSelect_HBD: 1.0 <= dFactor")
Assert(0.0 <= Mindif ,"SCSelect_HBD: 0.0 <= MinDif")
try { bpc = DClip.BitsPerComponent } catch(err_msg) { bpc = 8 }
DClip = (bpc!=8) ? DClip.ConvertBits(8) : DClip
Sz = Max(Round(Motion.Height*0.05),12)
S_Frames = (S_Frames!="") ? S_Frames.RT_GetFullPathName : ""
E_Frames = (E_Frames!="") ? E_Frames.RT_GetFullPathName : ""
(S_Frames !="") ? RT_FileDelete(S_Frames) : NOP
(E_Frames !="") ? RT_FileDelete(E_Frames) : NOP
Motion.GScriptClip("""
n = current_frame
pDf = Dclip.YDifferenceFromPrevious
nDf = Dclip.YDifferenceToNext
pTh = nDf*dfactor+MinDif
nTh = pDf*dfactor+MinDif
SC = 2 # Default To SC = MOTION (Last is MOTION clip)
if (n==0) { SC = 0 Start S_Frames!=""?RT_WriteFile(S_Frames,"%d",n,Append=true):NOP} # SOS
else if(n==FrameCount-1){ SC = 1 End E_Frames!=""?RT_WriteFile(E_Frames,"%d",n,Append=true):NOP} # EOS, Fixes ScSelect SOS detect on Last frame
else if(pDf>pTh) { SC = 0 Start S_Frames!=""?RT_WriteFile(S_Frames,"%d",n,Append=true):NOP} # SOS
else if(nDf>nTh) { SC = 1 End E_Frames!=""?RT_WriteFile(E_Frames,"%d",n,Append=true):NOP} # EOS
if(show) {
mx = Max(pDf,nDf)
mn = Min(pDf,nDf)
Can_dF = (mx > mn + Mindif) # Can any values of dfactor(1.0 and aove) cause scene detect ?
Can_Md = (mx > dFactor * mn) # Can any values of MinDif(0.0 and above) cause scene detect ?
if(Can_dF || Can_Md) {
E_dF = (Can_dF) ? String(Min((mx - Mindif) / max(mn,0.00001),99.9999),"dFact=%7.4f") : ""
E_Md = (Can_Md) ? String(mx - dFactor * mn,"MinD=%7.4f") : ""
EdfS = E_dF + ((Can_dF && Can_Md) ? ":" : "") + E_Md + ""
EdfS = ((pDf==mx) ? "SOS:" :"EOS:") + EdfS
} else { EdfS = "" }
Subtitle(
\ String(n,"%.0f ") + MidStr("SEG",SC+1,1) + " " + EdfS + "\n" +
\ String(pDf,"SOS: P{%7.3f} > ") + String(pTh,"pTh{%7.3f}") + (SC==0 ? " <==":"") + "\n" +
\ String(nDf,"EOS: N{%7.3f} > ") + String(nTh,"nTh{%7.3f}") + (SC==1 ? " <==":"")
\ ,Font="Courier New",Size=Sz,lsp=0)
}
Return Last
""",Args="dfactor,Start,End,Dclip,MinDif,show,S_Frames,E_Frames,Sz",Local=true) # Needs Grunt for args
return Last
}

Function SpotLess(clip c,int "RadT",int "ThSAD",int "ThSAD2",int "pel",bool "chroma", int "BlkSz",Int "Olap",bool "tm",Bool "glob", Float "bBlur", clip "dc" ) {
myName = "SpotLess: "
RadT = Default(RadT,1) # Temporal radius. (MCompensate arg)
ThSAD = Default(ThSAD,6000) # SAD threshold at radius 1 (Default Nearly OFF).
ThSAD2 = Default(ThSAD2,ThSAD) # SAD threshold at radius RadT.
Pel = Default(pel,2) # Default 2. 1, 2, or 4. Maybe set 1 for HD+. (1=precision to pixel, 2=precision to half pixel, 4=quarter pixel)
Chroma = Default(chroma,True) # MAnalyse chroma arg. If set to true, use chroma in block matching.
BlkSz = Default(BlkSz,16) # Default 8. MAnalyse BlkSize. Bigger blksz quicker and perhaps better, esp for HD clips. Maybe also better where BIG noise.
OLap = Default(OLap,BlkSz/2) # Default half of BlkSz.
Tm = Default(tm,True) # TrueMotion, Some folk swear MAnalyse(truemotion=false) is better.
Glob = Default(glob,True) # Default True, Allow set MAnalyse(global) independently of TrueMotion.
bBlur = Default(bblur,0.6) # Default 0.6. Suggest about 0.6 for better motion analysis, but a bit slower.
HasDC = dc.Defined # bblur ignored if HasDC, ie user already provided prefiltered clip.
Assert(1 <= RadT,myName + " 1 <= RadT")
Assert(0.0 <= bblur <= 1.58, myName + "0.0 <= bblur <= 1.58")
Assert(pel==1 || pel==2 || pel==4, myName + "pel==1 || pel==2 || pel==4")
pad = max(BlkSz,8)
sup = (HasDC ? dc : bBlur>0.0 ? c.blur(bBlur) : c ).MSuper(hpad=pad,vpad=pad,pel=pel,sharp=2)
# Only 1 Level required where not MAnalyse-ing.
sup_rend = (HasDC||bBlur>0.0) ? c.MSuper(hpad=pad,vpad=pad,pel=pel,sharp=2,Levels=1) : sup
MultiVec = sup.MAnalyse(multi=true,delta=RadT,blksize=BlkSz,overlap=OLap,chroma=Chroma,truemotion=Tm,global=Glob)
c.MCompensate(sup_rend,MultiVec,tr=RadT,thSad=ThSAD,thSad2=ThSAD2)
MedianBlurTemporal(radiusY=0,radiusU=0,radiusV=0,temporalradius=RadT) # Temporal median blur only [not spatial]
SelectEvery(RadT*2+1,RadT) # Return middle frame
}

prev = SpotLessT.selectevery(1,-1)
next = SpotLessT.selectevery(1,1)
SpotLessSCT=src.SCSelect_HBD(next,prev,SpotLessT,show=false)

S1 = StackHorizontal(\
sub(src, "Source", txt_sz),\
sub(SpotLessT, "Spotless TM true", txt_sz)\
)
S2 = StackHorizontal(\
sub(SpotLessF, "Spotless TM false", txt_sz),\
sub(SpotLessSCT, "Spotless and scene change", txt_sz)\
)
output = StackVertical(S1,S2)

#Return output # Comparison

return SpotLessSCT # Conversion
#return SpotLessT
#return SpotLessF

# Debugging to check scene change detection------------------------------------
prev = SpotLessT.selectevery(1,-1)
next = SpotLessT.selectevery(1,1)
SpotLessSCTDM=src.SCSelect_HBD(next,prev,SpotLessT,show=true)

SSCTD1 = StackHorizontal(\
sub(src.AddBorders(0,30,0,0), "Source", txt_sz),\
sub(SpotLessSCTDM.AddBorders(0,30,0,0), "Spotless and scene change with metrics", txt_sz)\
)
SSCTD2 = StackHorizontal(\
sub(SpotLessSCT.AddBorders(0,30,0,0), "Spotless and scene change without metrics", txt_sz),\
sub(BlankClip(src).AddBorders(0,30,0,0), "Blank", txt_sz)\
)
outputD = StackVertical(SSCTD1,SSCTD2)

#Return outputD # Comparison

StainlessS
16th October 2021, 17:43
You should not really change defaults inside the script,
Defaults

dfactor = Float(Default(dfactor,3.5)) # Suggest 2.5 -> 4.0 ::: Change default to 4.0 to emulate ScSelect
Mindif = Float(Default(MinDif ,1.0)) # Suggest 0.5 -> 4.0 ::: Change default to 0.0 to emulate ScSelect


Your mod

dfactor = Float(Default(dfactor,5.0)) # Suggest 2.5 -> 4.0 ::: Change default to 4.0 to emulate ScSelect
Mindif = Float(Default(MinDif,3.0)) # Suggest 0.5 -> 4.0 ::: Change default to 0.0 to emulate ScSelect

They are both still a bit on the high side, and i would only really suggest MinDif = 3.0 or 4.0 for some anime.

On 29836 [SOS] it shows that with current MinDif (3.0) the maximum DFact that could detect as SOS is 2.0028 which is a lot less
than your DFact setting of 5.0. Even reducing Dfact to suggested 3.5 and MidDif to suggested default 1.0, it would still not detect,
problem is the big frame 29836 difference to next frame 29837 which is 8.641,
Difference to Previous (ie 20.306) has to be bigger than (5.0 * 8.641 + 3.0) [ie 46.205] to be detected as SOS,
even with defaults, would have to be greater than (3.5 * 8.641 + 1.0) [ie 31.2435] to be detected as SOS.
EDIT: 29836 -> 29837, diff will be a bit of a jolt, maybe a small pan. [EDIT: We dont know, we cant see it]

SCSelect / SCSelect_HBD are not a perfect solution to detect scene change, sad but there you go.

coolgit
16th October 2021, 20:47
You should not really change defaults inside the script,
Defaults

dfactor = Float(Default(dfactor,3.5)) # Suggest 2.5 -> 4.0 ::: Change default to 4.0 to emulate ScSelect
Mindif = Float(Default(MinDif ,1.0)) # Suggest 0.5 -> 4.0 ::: Change default to 0.0 to emulate ScSelect


Your mod

dfactor = Float(Default(dfactor,5.0)) # Suggest 2.5 -> 4.0 ::: Change default to 4.0 to emulate ScSelect
Mindif = Float(Default(MinDif,3.0)) # Suggest 0.5 -> 4.0 ::: Change default to 0.0 to emulate ScSelect

They are both still a bit on the high side, and i would only really suggest MinDif = 3.0 or 4.0 for some anime.

Done.

On 29836 [SOS] it shows that with current MinDif (3.0) the maximum DFact that could detect as SOS is 2.0028 which is a lot less
than your DFact setting of 5.0. Even reducing Dfact to suggested 3.5 and MidDif to suggested default 1.0, it would still not detect,
problem is the big frame 29836 difference to next frame 29837 which is 8.641,
Difference to Previous (ie 20.306) has to be bigger than (5.0 * 8.641 + 3.0) [ie 46.205] to be detected as SOS,
even with defaults, would have to be greater than (3.5 * 8.641 + 1.0) [ie 31.2435] to be detected as SOS.
EDIT: 29836 -> 29837, diff will be a bit of a jolt, maybe a small pan.

SCSelect / SCSelect_HBD are not a perfect solution to detect scene change, sad but there you go.

Just curious how it works. Perhaps the lack of colours in 29839 threw off the calculations.

However some common sense has to come into play here.

If there is an end of scene change then obviously the next frame must be the start of scene change, no matter what the maths is. What else can it be?

StainlessS
16th October 2021, 21:31
then obviously the next frame must be the start of scene change
ScSelect always had the problem of SOS sometimes not following EOS or EOS not always before SOS, my mod does not change that.
EOS frame considers only itself and those either side, SOS frame considers only itself and those either side,
so EOS knows nothing of the frame following SOS and SOS knows nothing of the frame before EOS.
no matter what the maths is
It aint the math, its logic that fails, ie the algorithm, but IIRC, mvTools scene change detect has exact same problem. [ie mismatched EOS / SOS detects]

ScSelect_HBD is definitely better than ScSelect, but it aint perfect by any means.

EDIT: Maybe you can make some use of the stuff here:- https://forum.doom9.org/showthread.php?p=1954985#post1954985
EndOfSceneClip() and StartOfSceneClip.

StainlessS
16th October 2021, 22:36
Or maybe you could try this [not much testing at all].


Function SceneCutSelectClip(clip dClip,clip Start,clip End,clip Both,clip Motion,Int "thSCD1",Int "thSCD2") { # (c) ssS: https://forum.doom9.org/showthread.php?p=1955111#post1955111
thSCD1=Default(thSCD1,400) thSCD2=Default(thSCD2,130)
Sup = dClip.MSuper(pel=1,sharp=0,rfilter=2,hpad=16, vpad=16)
BvEos= Sup.MAnalyse(isb=True, delta=1,blksize=16)
FvSos= Sup.MAnalyse(isb=False, delta=1,blksize=16)
Eos = dClip.MSCDetection(BvEos,thSCD1=thSCD1,thSCD2=thSCD2).crop(0,0,4,4)
Sos = dClip.MSCDetection(FvSos,thSCD1=thSCD1,thSCD2=thSCD2).crop(0,0,4,4)
CondS="""
ix = (Sos.AverageLuma>0?1:0) + (Eos.AverageLuma>0?2:0)
Return ix==0?Motion:ix==1?Start:ix==2?End:Both
"""
ARGS="Motion,CondS,Motion,Start,End,Both,Eos,Sos"
Motion.GSCriptClip(CondS,Args=ARGS,After_Frame=True,Local=True) # Requires Grunt
}

EDIT: Added the Crop(0,0,4,4) things.

It works sorta like ScSelect/ScSelect_HBD, but you must also Provide a "BOTH" [ie both EOS and SOS detect] clip (you can choose whatever solution you like for that possible outcome).

AviSource("D:\hard sub - 01 WEBdlRip 720p, 23.976.mkv.AVI")
dclip = BilinearResize(320,240).Blur(1.0) # Whatever (just testing frame size can be different to other clips)
ConvertToRGB32 # Just testing works where Dclip colorspace is differenct from the other clips.
Motion = Last
Start = Subtitle("START",size=64,align=5)
End = Subtitle("END",size=64,align=5)
Both = Subtitle("BOTH",size=64,align=5)
SceneCutSelectClip(dClip,Start,End,Both,Motion,400,130)


EDIT: You will likely fire a BOTH if there is a single frame scene cut, ie an 'odd' frame that belongs neither with previous nor following scenes.
EDIT: There is at least one frame that will fire BOTH in either X-Men 1 or X-Men 2. [excluding intro where CGI flashes wildly and you cant tell where real scene changes are]