View Full Version : [film transfer] Remove dirt but keep grain?
zorr
10th June 2021, 23:33
Delta Restore - Dark Mask complete
I think this version of dark mask is good enough. I had to leave Spotless out of this code, doesn't fit into single post otherwise. You can copy it from the previous version.
source1= FFVideoSource("464_-trim_crop_grd_cut.avi")
source1= KillAudio(source1)
source1_8bit = ConvertBits(source1, bits=8) #I dont'know much about formats, maybe this is not ideal...
source1YV12 = ConvertToYV12 (source1_8bit) #I dont'know much about formats, maybe this is not ideal...
txt_sz = 36
BlkSz = 24
OLap = (BlkSz/2) #important not too low (eg 4 for blksz 12, because creates stairs)
Pel = 1
Tm=false
Bblur = 0.6
ThSAD = 10000
RadT = 1
Usharp_strength= 80
Usharp_radius= 5
Usharp_th = 1 #too high = too many dark pixel packs (ex trees)
#Sharpen----------------------------------------------------------------------
source1_shp=source1.UnsharpMask_avsi(Usharp_strength,Usharp_radius,Usharp_th)
#SpotRemoved_shp=SpotRemoved.UnsharpMask_avsi(Usharp_strength,Usharp_radius,Usharp_th)
#----------------------------------------------------------------------------------
#Remove spots-----------------------------------------------------------------
spotless_107=SpotLess107(source1_shp,blksz=BlkSz,OLap=Olap,pel=pel,Tm=Tm,Bblur=Bblur,thsad=thsad,RadT=RadT)
#SpotRemoved = SpotRemover(source1YV12,Spot=128, RadT=1, Pel=Pel, BlkSz=32, DeGrain=false, Chroma=true, PreFilter=true, mask=false, Tm = true,thsad=thsad, Glob = true)
#----------------------------------------------------------------------------------
#SpotRemoved = ConvertBits(SpotRemoved, bits=10) #I dont'know much about formats, maybe this is not ideal...
#SpotRemoved = ConvertToYUV422(SpotRemoved) #I dont'know much about formats, maybe this is not ideal...
# Restore grain -------------------------------------------------------------
#spotless_107_shp_Rg = mRD_RestoreGrain(spotless_107_shp, source1)
#SpotRemoved_shp_Rg = mRD_RestoreGrain(SpotRemoved_shp, source1)
#----------------------------------------------------------------------------------
# Delta Restore -------------------------------------------------------------
src = source1
filtered = spotless_107
# extra clipping (if needed)
#crop_left = 800
#crop_right = 280
#src = src.Crop(crop_left, 0, -crop_right, 0)
#filtered = filtered.Crop(crop_left, 0, -crop_right, 0)
# diff with exaggerated chroma difference
diff1 = mt_makediff(src, filtered, y=3, u=3, v=3)
diff2 = diff1.mt_adddiff(diff1, y=0, u=3, v=3)
diff2 = diff2.mt_adddiff(diff1, y=0, u=3, v=3)
diff = CombinePlanes(diff1, diff2, diff2, planes="YUV", source_planes="YUV", sample_clip=diff1)
# prepare masks --------------------------------------------------------------------
# luma mask - selects where source brightness > filtered brightness
luma_mask = LumaDeltaMask(src, filtered, ">", brightness=1.1, spotSize=3)
luma_mask = ExpandMask(luma_mask, 3, blur=4)
# chroma mask - selects where source and filtered chromas have large enough difference (formula: sqrt(u*u + v*v))
chroma_mask = ChromaDeltaMask(src, filtered, delta=3, spotSize=3)
# suppression mask - selects where source brightness < filtered brightness
suppression_mask = LumaDeltaMask(src, filtered, "<", brightness=0.9, spotSize=2)
# suppression mask removes too dark areas from chroma mask
suppressed_chroma_mask = mt_lutxy(chroma_mask, suppression_mask, expr="x y -")
# edge mask - detects sharp edges
edges = scharr(src)
# don't detect edges near the frame edges
edges = edges.ZPadding(60, 14, 14, 10)
# chroma override mask - selects larger color deltas and spot sizes than plain chroma mask and is not suppressed
chroma_override = ChromaDeltaMask(src, filtered, delta=5, spotSize=6) # orig. spotSize 6
chroma_override = ExpandMask(chroma_override, 3, blur=2)
# new approach for dark mask:
# select both darker and lighter mask and keep dark mask if it is near lighter mask or overlaps chroma override mask
dark = LumaDeltaMask(src, filtered, "<", brightness=0.97, limitBrightness=0.66, spotSize=5)
light = LumaDeltaMask(src, filtered, ">", brightness=1.04, spotSize=5)
light = light.ZPadding(60, 14, 14, 10)
dark_near_light = isNearMask(dark, light, 28)
expanded = IsNearMask(dark, light, 28, expanded=true) # for debugging
dark_final = ExpandMask(dark_near_light, 3, blur=4)
# secondary dark mask - doesn't need to be near lighter area but has stricter limits
dark2 = LumaDeltaMask(src, filtered, "<", brightness=0.97, limitBrightness=0.69, limitAbsBrightness=0.12, spotSize=5)
dark2 = ExpandMask(dark2, 2, blur=2)
dark_final = mt_logic(dark_final, dark2, "or")
# edge test for dark areas
darkEdgeMask = edges.mt_binarize(54) # at least 54 needed for frame 222
darkEdgeAreaMask = mt_hysteresis(darkEdgeMask, dark_final, chroma="-127")
dark_final = mt_lutxy(dark_final, darkEdgeAreaMask, expr="x y -")
# combine luma, chroma and chroma override masks
combined_mask = mt_logic(luma_mask, suppressed_chroma_mask, "or")
combined_mask = mt_logic(combined_mask, chroma_override, "or")
# remove mask areas where sharp edges were found (those are most likely dirt)
edgeMask = edges.mt_binarize(52)
edgeAreaMask = mt_hysteresis(edgeMask, combined_mask, chroma="-127")
combined_mask = mt_lutxy(combined_mask, edgeAreaMask, expr="x y -")
# add dark areas (no edge test for those)
combined_mask = mt_logic(combined_mask, dark_final, "or")
# restore ---------------------------------------------------------------------------
delta_restore = Overlay(filtered, src, mask=combined_mask)
edgeAreaMask = mt_logic(edgeAreaMask, darkEdgeAreaMask, "or")
#edgeAreaMask = darkEdgeAreaMask
# create mask visualization (for debugging) -----------------------------------------
black = BlankClip(src)
yellow = BlankClip(src, color=$d1cc2e) # #d1cc2e f7f140
green = BlankClip(src, color=$40f75b)
gray = BlankClip(src, color=$d0d0d0)
darkgray = BlankClip(src, color=$909090)
orange = BlankClip(src, color=$a36b02)
purple = BlankClip(src, color=$4e0091)
purple_light = BlankClip(src, color=$bc6eff)
purple_dark = BlankClip(src, color=$340061)
red = BlankClip(src, color=$9e2424)
blue = BlankClip(src, color=$0d3b7a) #24579e 243c9e
# maskviz for dark mask
maskviz = Overlay(black, purple, mask=expanded)
maskviz = Overlay(maskviz, purple_dark, mask=dark)
maskviz = Overlay(maskviz, purple_light, mask=light)
maskviz = Overlay(maskviz, black, mask=dark_near_light)
# only show dark mask?
showDarkMaskOnly = false
if (!showDarkMaskOnly) {
maskviz = Overlay(maskviz, yellow, mask=chroma_mask) # chroma mask - yellow
maskviz = Overlay(maskviz, red, mask=suppression_mask, opacity=0.7) # suppression mask - red
maskviz = Overlay(maskviz, green, mask=chroma_override) # chroma override - green
maskviz = Overlay(maskviz, gray, mask=luma_mask) # luma mask - gray
maskviz = Overlay(maskviz, blue, mask=edgeAreaMask, opacity=0.8) # sharpness mask - blue
}
# mask overlay
comp = Overlay(delta_restore, maskviz, opacity=0.45)
comp = Overlay(comp, purple_light, mask=light, opacity=0.2)
comp = Overlay(comp, black, mask=dark, opacity=0.2)
comp = Overlay(comp, black, mask=dark_final, opacity=0.6)
maskviz = Overlay(maskviz, black, mask=dark_final, opacity=0.6)
#return Interleave(StackHorizontal(src, comp), StackHorizontal(delta_restore, maskviz))
#return StackHorizontal(src, filtered, delta_restore, edgeMask, maskviz)
stack = StackHorizontal(\
sub(src, "source", txt_sz), \
sub(filtered, "Spotless", txt_sz), \
sub(delta_restore, "Delta Restore", txt_sz), \
sub(comp, "restore + mask", txt_sz), \
sub(maskviz, "masks used", txt_sz),\
sub(edgeMask, "edge mask", txt_sz),\
sub(diff, "diff", txt_sz)\
)
# scaling (if needed)
stack = stack.BilinearResize(stack.width/2 + 1, stack.height/2)
return stack
inter = Interleave(\
sub(src, "source", txt_sz), \
sub(filtered, "Spotless", txt_sz), \
sub(delta_restore, "Delta Restore", txt_sz), \
sub(comp, "restore + mask", txt_sz), \
sub(maskviz, "masks used", txt_sz),\
sub(diff, "diff", txt_sz)\
)
return inter
function sub(c, label, txt_sz) {
return subtitle(c, label, size = txt_sz, align=8)
}
video = Interleave(\
subtitle(source1,"original",size = txt_sz,align=2),\
subtitle(overlay,"delta restore",size = txt_sz,align=2),\
subtitle(restore_nosuppression,"no suppression",size = txt_sz,align=2),\
subtitle(spotless_107_shp,"spotless_107_shp",size = txt_sz,align=2)\
)
masks = Interleave(\
subtitle(BlankClip(source1),"",size = txt_sz,align=2),\
subtitle(combined_mask,"combined_mask",size = txt_sz,align=2),\
subtitle(BlankClip(spotless_107_shp),"",size = txt_sz,align=2)\
)
return StackHorizontal(video, masks)
group = StackHorizontal(\
subtitle(source1,"original",size = txt_sz,align=2),\
subtitle(spotless_107_shp,"spotless_107_shp",size = txt_sz,align=2),\
subtitle(overlay,"delta restore",size = txt_sz,align=2),\
subtitle(combined_mask,"combined mask",size = txt_sz,align=2),\
subtitle(luma_mask,"luma delta mask",size = txt_sz,align=2),\
subtitle(suppression_mask,"suppression mask",size = txt_sz,align=2),\
subtitle(suppressed_chroma_mask,"suppressed chroma mask",size = txt_sz,align=2),\
subtitle(chroma_mask,"chroma delta mask",size = txt_sz,align=2),\
subtitle(diff,"diff",size = txt_sz,align=2))
#subtitle(chroma_mask,"chroma mask",size = txt_sz,align=2),\
#subtitle(whitemask,"whitemask",size = txt_sz,align=2),\
#subtitle(whitemask,"filled whitemask",size = txt_sz,align=2),\
#subtitle(uv,"UV",size = txt_sz,align=2))
#subtitle(whitemask_orig,"orig. whitemask",size = txt_sz,align=2),\
#subtitle(SpotRemoved,"SpotRemover",size = txt_sz,align=2))
#subtitle(spotless_107_shp_Rg,"spotless_107_shp_Rg",size = txt_sz,align=2),\
return(group)#.AddBorders(1000, 0, 500, 0, color=$000000)
#~ ------------------------------------------------------------------------------------------------------------------
#~ ------------------------------------------------------------------------------------------------------------------
#~ ------------------------------------------------------------------------------------------------------------------
function OverlapsMask(clip mask, clip otherMask) {
return mt_logic(mask, mt_hysteresis(otherMask, mask), "and")
}
function IsNearMask(clip mask, clip otherMask, int maxDistance, bool "expanded") {
expanded = Default(expanded, false)
expand_amount = maxDistance / 2
mask_expanded = ExpandMask(mask, expand_amount)
other_expanded = ExpandMask(otherMask, maxDistance - expand_amount)
common = mt_logic(mask_expanded, other_expanded, "or")
if (expanded) {
return common
}
other_near = mt_hysteresis(otherMask, common)
return mt_logic(mask, other_near, "and")
}
function ZPadding(clip c, int left, int top, int right, int bottom) {
return c.Crop(left, top, -right, -bottom).AddBorders(left, top, right, bottom, color_yuv=$008080)
}
function scharr(clip c) {
scharr_x = c.mt_edge("3 0 -3 10 0 -10 3 0 -3", thY1 = 0, thY2 = 255, y=3, u=1, v=1)
scharr_y = c.mt_edge("3 10 3 0 0 0 -3 -10 -3", thY1 = 0, thY2 = 255, y=3, u=1, v=1)
scharr = mt_lutxy(scharr_x, scharr_y, yexpr=mt_polish("((x*x)+(y*y))^0.5"), chroma="-127")
return scharr
}
function RemoveSmallSpots(clip mask, int spotSize) {
mask_orig = mask
# remove too small spots ----------------------------------------------------
for (i = 1, spotSize) {
mask = mask.mt_inpand()
}
mask = mt_hysteresis(mask, mask_orig, chroma="-127")
return mask
}
function LumaDeltaMask(clip source, clip filtered, string direction, float "brightness", float "limitBrightness", float "limitAbsBrightness", int "spotSize") {
# direction is ">" if you want mask to contain areas where source is brighter than filtered by "brightness", direction "<" selects where source is darker
# adjust brightness to change how bright objects are selected, typical values between 1.0 and 1.1, larger value = brighter object
# remove too bright or dark areas using limitBrightness
# spotSize controls the smallest spot size allowed in the mask, typical values between 0 and 3
brightness = Default(brightness, 0.9)
spotSize = Default(spotSize, 2)
limitBrightness = Default(limitBrightness, brightness)
limitAbsBrightness = Default(limitAbsBrightness, 0.0)
# diff
diff = Grayscale(mt_makediff(source, filtered))
# mask
mask = mt_lut(diff, expr="x range_half "+String(brightness)+" * " + direction + " 255 scalef 0 ?")
mask_orig = mask
# limitBrightness suppression
if (limitBrightness != brightness) {
mask_suppression = mt_lut(diff, expr="x range_half "+String(limitBrightness)+" * " + direction + " 255 scalef 0 ?")
mask = mt_lutxy(mask, mask_suppression, expr="x y -")
}
# limitAbsBrightness suppression
if (limitAbsBrightness > 0.0) {
mask = mt_lutxy(mask, source, expr="y range_max "+String(limitAbsBrightness)+" * < 0 x ?")
}
# remove too small spots
mask = RemoveSmallSpots(mask, spotSize)
# limitBrightness or limitAbsBrightness suppression (part 2)
if (limitBrightness != brightness || limitAbsBrightness > 0.0) {
# if suppression didn't kill the whole mask then return it without suppression
mask = mt_hysteresis(mask, mask_orig, chroma="-127")
}
return mask
}
function ChromaDeltaMask(clip source, clip filtered, int "delta", int "spotSize") {
# adjust delta to change how different colors are selected
# spotSize controls the smallest spot size allowed in the mask, typical values between 0 and 3
delta = Default(delta, 3)
spotSize = Default(spotSize, 2)
# diff ----------------------------------------------------------------------
diff = mt_makediff(source, filtered, y=3, u=3, v=3)
# extract chroma channels
u = ExtractU(diff)
v = ExtractV(diff)
# calculate deltas
u_delta = mt_lut(u, expr="x range_half - abs")
v_delta = mt_lut(v, expr="x range_half - abs")
# combine deltas
# uv_delta = mt_lutxy(u_delta, v_delta, expr="x y +")
uv_delta = mt_lutxy(u_delta, v_delta, expr="x x * y y * + 0.5 ^")
# rescale (if needed)
if (uv_delta.width < source.width || uv_delta.height < source.height) {
uv_delta = uv_delta.BilinearResize(source.width, source.height)
}
# create mask
mask = uv_delta.mt_binarize(delta)
# remove too small spots
mask = RemoveSmallSpots(mask, spotSize)
# restore original format
blank = BlankClip(source)
mask = CombinePlanes(mask, blank, blank, planes="YUV", source_planes="YUV", sample_clip=source)
return mask
}
function ExpandMask(clip mask, int amount, float "blur") {
blur = Default(blur, 0)
for (i = 1, amount) {
mask = mask.mt_expand(chroma="-127")
}
if (blur > 0) {
#mask = mask.FastBlur(blur)
#mask = mask.binomialBlur(varY=blur)
# mask = mask.ConvertBits(16)
mask = mask.GBlur(rad=6, sd=blur, u=true)
# mask = mask.ConvertBits(10)
}
return mask
}
Function mRD_RestoreGrain (clip rd, clip o, float "str1", float "str2") {
sisavs26 = !(VersionNumber() < 2.60)
str1 = string(default(str1, 10.0))
str2 = string(default(str2, 5.0))
expr = sisavs26 ? " x range_half - abs "+str1+" scalef min "+str2+" scalef x range_half - abs - min 0 max x range_half - x range_half - abs 1 max / * range_half + " : " x 128 - abs "+str1+" min "+str2+" x 128 - abs - min 0 max x 128 - x 128 - abs 1 max / * 128 + "
Return sisavs26 ? mt_makediff(o, rd).mt_lut(expr, use_expr=2).mt_adddiff(rd, chroma="copy second") : mt_makediff(o, rd).mt_lut(expr).mt_adddiff(rd, chroma="copy second")
}
zorr
11th June 2021, 00:03
Some dark mask examples: below you can see a typical case where there are dark deltas near light deltas (easy to see in the diff image). The way dark mask works is that it tests if there are lighter areas nearby and if there are it restores the dark area. How near they have to be can be adjusted with the argument maxDistance of IsNearMask() function. The dark mask does not restore the lighter area itself, it's already handled by the luma mask. The visualization is only showing the purple-tinted dark mask related masks (you can adjust that with the variable showDarkMaskOnly). The allowed maximum distance between the lighter and darker mask is shown as well, if the "edge" of darker and lighter masks overlap then they are close enough.
https://i.postimg.cc/mZjt9L5T/dark-mask-v2-120.png
Not all dark areas have light deltas nearby so there is a secondary dark mask which doesn't have to be near the lighter areas. That one is a bit more conservative on how dark areas it will select. There's a new limit parameter limitAbsBrightness in LumaDeltaMask() which rejects mask if most of the brightness is below that value. Note that this is not the diff / delta brightness but the brightness of the source clip under the mask location. This removes dark spots which are located over dark features (like in the shadows of a building), those don't have a large delta but the spot is so dark that it is easy to detect. Such spots occur at frames 79, 108, 190 and 227 and can be dealt with limitAbsBrightness=0.12.
https://i.postimg.cc/ZKDnffmg/dark-mask-v2-137.png
Let's see how the guy's hair is doing:
https://i.postimg.cc/Gm826khp/dark-mask-v2.gif
Delta Restore has brought back all the missing details. The gif is now showing all the masks, you can see for example the green chroma override mask.
The dark mask selects quite many unnecessary, sometimes large features. However those are mostly harmless as the change is so tiny. You can see some of those in the above gif, here's another example of a larger feature:
https://i.postimg.cc/qM7gXPPy/dark-mask-v2-129.png
It does select some faint spots as well, but that's the price you have to pay in order to restore the dark features. I still think it's a good compromise and improves the end result.
[EDIT] Here's an example of a faint spot which is restored by the dark mask.
https://i.postimg.cc/pLzg4T8j/dark-mask-v2-199.png
kedautinh12
11th June 2021, 00:11
Thanks
kedautinh12
11th June 2021, 02:50
I think your script need use with DeScratch for remove some black line dirty
johnmeyer
11th June 2021, 15:09
I think your script need use with DeScratch for remove some black line dirtyRemoving movie film scratches is extremely difficult to do automatically without extensive user interaction with each frame. Nothing works well. The problem is that scratches persist in the same place from one frame to the next and therefore look like they are part of the scene. In "Hollywood" restorations they have a team of restorationists who paint each scratch and then use motion tracking software to track that scratch as it wanders slightly back and forth from one frame to the next, often persisting for many minutes.
The better way to remove scratches is during the transfer from film to video using a wet gate transfer system.
chmars
11th June 2021, 20:23
I still think it's a good compromise and improves the end result.
This seems very beautiful. Lots of sweat I guess.
I'll try that on the next footage, next week. Coming back asap to give a feed back.
zorr
11th June 2021, 22:14
I think your script need use with DeScratch for remove some black line dirty
Yes I think so too. You can see an example of DeScratch in this post (https://forum.doom9.org/showthread.php?p=1944417#post1944417).
That was just a quick test and it already removes like 90% of the scratches. It's never going to work as well as restorationists doing it frame by frame but for an easily applied filter... not bad at all.
chmars
11th June 2021, 22:34
I tried too, works well on graded footage.
But you have to rotate the image as the scratches are as vertical as possible (in case the camera is not 100% at capturing time).
So you can lower the orientation parameter "maxangle" to avoid corrections on unwanted objects.
Descratch is much less efficient with Slog3 though, contrast is too low.
Arx1meD
14th June 2021, 09:43
My version of the script to restore moving objects. Unfortunately some spots also returned. Not as complex as the zorr version.
# v1.1
# Needed plugins: MaskTools2, RgTools
function Restore_Objects(clip source, clip filtered, float "threshold_dark", float "threshold_light", int "Y", int "U", int "V") {
dark = String(Default(threshold_dark, 20.0)) # Threshold for dark objects. Range: 0...127
light = String(Default(threshold_light, 20.0)) # Threshold for light objects. The lower the thresholds, the more objects will be restored.
Y = Default(Y, 3)
U = Default(U, 2)
V = Default(V, 2)
m1 = mt_lut(source.ColorYUV(autogain=true), expr="x "+dark+" > x 255 "+light+" - < 0 255 ? 255 ?", Y=Y, U=U, V=V, scale_inputs="allf", use_expr=2).mt_expand().mt_expand().mt_expand(chroma="-128")
m2 = mt_lutxy(source, filtered, expr="x y * 255 /", Y=Y, U=U, V=V, scale_inputs="allf", use_expr=2).mt_binarize(threshold=128, chroma="-128")
mask_1 = mt_logic(m1, m2, "max").mt_expand().mt_expand(chroma="-128").Blur(1.58)#.mt_convolution(horizontal="3 1 3", vertical="3 1 3", Y=Y, U=U, V=V)
r = mt_merge(source.RemoveDust(), filtered, mask_1, Y=Y, U=U, V=V)
m3 = mt_lutxy(source, r, expr="x y - abs", Y=Y, U=U, V=V, scale_inputs="allf", use_expr=2).mt_binarize(threshold=5).mt_inpand(chroma="-128", thY=128, thC=128)
m4 = m3.mt_inpand().mt_inpand(chroma="-128")
mask_2 = mt_logic(m3, m4, "xor").Blur(1.58)#.mt_convolution(horizontal="3 1 3", vertical="3 1 3", Y=Y, U=U, V=V)
mt_merge(Repair(filtered, r, 17), filtered.RemoveDust(), mask_2, Y=Y, U=U, V=V)
}
function RemoveDust(clip input, int "mode") {
mode = Default(mode, 17)
clensed = Clense(input)
rep = Repair(clensed, input, mode=mode)
RemoveGrain(rep, mode=10)
}
Comparison script:
source = DSS2("C:\464_-trim_crop_grd_cut.avi").ConvertBits(16)
filtered = source.SpotLess(RadT=1, Pel=1, BlkSz=16, Chroma=true, Tm=true)
Restore_Objects = Restore_Objects(source, filtered, threshold_dark=30, threshold_light=30, Y=3, U=2, V=2)
StackHorizontal(source.AddBorders(0, 0, 0, 40).Subtitle("Source", align=2, size=30),
\ filtered.AddBorders(0, 0, 0, 40).Subtitle("Filtered: SpotLess(RadT=1, Pel=1, BlkSz=16, Chroma=true, Tm=true)", align=2, size=30),
\ Restore_Objects.AddBorders(0, 0, 0, 40).Subtitle("Restore_Objects(source, filtered, threshold_dark=30, threshold_light=30, Y=3, U=2, V=2)", align=2, size=30))
Comparison screenshots:
https://thumbs2.imgbox.com/d4/e1/6sREjVoT_t.jpg (https://imgbox.com/6sREjVoT) https://thumbs2.imgbox.com/cf/ed/J0L9nTyR_t.jpg (https://imgbox.com/J0L9nTyR) https://thumbs2.imgbox.com/50/8b/c4iYlYhB_t.jpg (https://imgbox.com/c4iYlYhB) https://thumbs2.imgbox.com/af/17/5Yd473hG_t.jpg (https://imgbox.com/5Yd473hG) https://thumbs2.imgbox.com/51/b3/x1VdS9tF_t.jpg (https://imgbox.com/x1VdS9tF) https://thumbs2.imgbox.com/1e/13/1QzVj147_t.jpg (https://imgbox.com/1QzVj147)
kedautinh12
14th June 2021, 10:56
Thanks
chmars
15th June 2021, 13:52
I have seen Gblur is used in the last version.
For the tests, I replaced it with the avs+ Gblur from manyPlus (http://www.avisynth.nl/users/vcmohan/manyPlus/manyPlus.html)by vcmohan (https://forum.doom9.org/showthread.php?t=174162) instead of the standard 32bit Gblur (http://avisynth.nl/index.php/GBlur).
Is it ok to do so?
kedautinh12
15th June 2021, 15:36
I think it's same cause it's same from vcmohan
johnmeyer
15th June 2021, 16:50
Here's a good clip to test whether you can mask out a white ball being thrown. When using RemoveDirtMC or Spotless with sufficiently strong settings needed to remove the dirt, the ball disappears in flight.
Dirt Removal "Torture" Test Clip (https://www.mediafire.com/file/nbvg27jotdthkv1/Dirt_Removal_Test_Clip_-_Thrown_Ball.mp4)
The clip is 16 fps progressive.
I was hoping to be able to use the software posted in this thread, but didn't have the time to turn the masking software into a function so I could import it into my existing film restoration script. Spotless is no problem.
The results posted here do look intriguing, but I need to get this project out the door so I did my usual workaround, namely doing a second render with the dirt removal turned way down, and then using that version during the time the ball is in flight.
Arx1meD
15th June 2021, 19:43
johnmeyer, try my version of SpotLess_Mod. This is the best I can do to keep as much detail as possible!
Expect some spots to remain.
Function SpotLess_Mod(clip c, bool "DeGrain", int "RadT", int "ThSAD", int "ThSAD2", int "Pel", bool "Chroma",
\ int "BlkSz", Int "Olap", bool "Tm", bool "Glob", string "Denoiser") {
DeGrain = Default(DeGrain, false) # MDeGrain in MvTools
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, 1) # 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.
Denoiser = Default(Denoiser, Undefined)
pad = Max(BlkSz, 8)
sup = c.Pre_Clip(blur=1.0, contrast=0, brightness=0).MSuper(hpad=pad, vpad=pad, pel=pel, sharp=2, chroma=Chroma)
sup_rend = MSuper(c, hpad=pad, vpad=pad, pel=pel, sharp=2, levels=1, chroma=Chroma) # Only 1 Level required where not MAnalyse-ing.
MultiVec = MAnalyse(sup, multi=true, delta=RadT, blksize=BlkSz, overlap=OLap, chroma=Chroma, truemotion=Tm, global=Glob, search=5, dct=7, divide=0, plevel=0)
#~ MultiVec = Mrecalculate(sup, MultiVec, tr=RadT, blksize=4, overlap=2, search=5, dct=7, thSAD=150, chroma=Chroma) # bed vectors recalculate
c0 = MCompensate(c, sup_rend, MultiVec, tr=RadT, thSad=ThSAD, thSad2=ThSAD2)
c0 = RadT == 1 ? Clense(c0) : MedianBlurTemporal(c0, radiusY=0, radiusU=0, radiusV=0, temporalradius=RadT)
c0 = SelectEvery(c0, RadT*2+1, RadT) # Return middle frame
c1 = Restore_Objects(c, c0, threshold_dark=30, threshold_light=30, Y=3, U=2, V=2)
clensed = Clense(c, cache=4)
# sbegin = ForwardClense(c, cache=-1)
# send = BackwardClense(c, cache=-1)
# alt=Repair(SCSelect(c, sbegin, send, clensed, debug=true), c, mode=1)
restore = Repair(clensed, c, mode=1)
c3 = RestoreMotionBlocks(clensed, restore, neighbour=c, alternative=restore, gmthreshold=70, dist=1, dmode=2, noise=10, noisy=12, grey=false, debug=false, show=false)
mask_c3 = mt_lutxy(c0, c3, expr="x y - abs", Y=3, U=2, V=2, scale_inputs="allf", use_expr=2).mt_binarize(threshold=7)
\ .mt_inpand().mt_expand().mt_expand().mt_expand().Blur(1.58)#.mt_convolution(horizontal="3 1 3", vertical="3 1 3", Y=3, U=2, V=2, chroma="-128")
clear = mt_merge(c1, c, mask_c3, Y=3, U=2, V=2)
Defined(Denoiser) || DeGrain ? Eval("""
sup2 = MSuper(clear, hpad=pad, vpad=pad, pel=pel, sharp=2, chroma=Chroma)
sup_rend2 = MSuper(clear, hpad=pad, vpad=pad, pel=pel, sharp=2, chroma=Chroma)
bvec1 = MAnalyse(sup2, isb=true, blksize=BlkSz, overlap=OLap, divide=0, delta=1, search=5, dct=7, plevel=0,
\ chroma=Chroma, truemotion=Tm, global=Glob) # backward vectors
fvec1 = MAnalyse(sup2, isb=false, blksize=BlkSz, overlap=OLap, divide=0, delta=1, search=5, dct=7, plevel=0,
\ chroma=Chroma, truemotion=Tm, global=Glob) # forward vectors
# bvec1 = Mrecalculate(sup2, bvec1, blksize=4, overlap=2, search=5, dct=7, thSAD=150, chroma=Chroma) # bed vectors recalculate
# fvec1 = Mrecalculate(sup2, fvec1, blksize=4, overlap=2, search=5, dct=7, thSAD=150, chroma=Chroma) # bed vectors recalculate
backw1 = MFlow(clear, sup_rend2, bvec1)
forw1 = MFlow(clear, sup_rend2, fvec1)
DN = Defined(Denoiser) ? Interleave(backw1, clear, forw1).Eval(Denoiser).SelectEvery(3, 1) : clear
return DeGrain ? MDeGrain1(DN, sup_rend2, bvec1, fvec1, plane=4, limit=255.0, thSad=400) : DN
""") : clear
}
function RemoveDust(clip input, int "mode") {
mode = Default(mode, 17)
clensed = Clense(input)
rep = Repair(clensed, input, mode=mode)
RemoveGrain(rep, mode=10)
}
function Pre_Clip(clip clp, float "blur", float "contrast", float "brightness"){
blur = Default(blur, 1.0)
contrast = Default(contrast, 1.2)
brightness = Default(brightness, 10.0)
v = clp.mt_lut(expr="x 20 1 x 127.5 / - 21 ^ pi x * 255 / cos 21 ^ - * 1.05 * -", Y=3, U=2, V=2, scale_inputs="allf", use_expr=2)
mask_1 = clp.Tweak(sat=0).Blur(blur).mt_edge(mode="min/max", thY1=0, thY2=255, Y=3, U=2, V=2)
pre_1 = v.Tweak(cont=contrast, bright=brightness)
pre_2 = mt_merge(v, pre_1, mask_1, Y=3, U=2, V=2)
pre_3 = pre_2.Tweak(sat=0).mt_invert().Blur(1.58).Blur(1.58).Blur(1.58)
pre_4 = mt_lutxy(pre_2, pre_3, expr="x 127.5 > y 255 x - 127.5 / * x 255 x - - + y x 127.5 / * ? ", scale_inputs="allf", use_expr=2)
return pre_4
}
# v1.1
function Restore_Objects(clip source, clip filtered, float "threshold_dark", float "threshold_light", int "Y", int "U", int "V") {
dark = String(Default(threshold_dark, 20.0)) # Threshold for dark objects. Range: 0...127
light = String(Default(threshold_light, 20.0)) # Threshold for light objects. The lower the thresholds, the more objects will be restored.
Y = Default(Y, 3)
U = Default(U, 2)
V = Default(V, 2)
m1 = mt_lut(source.ColorYUV(autogain=true), expr="x "+dark+" > x 255 "+light+" - < 0 255 ? 255 ?", Y=Y, U=U, V=V, scale_inputs="allf", use_expr=2).mt_expand().mt_expand().mt_expand(chroma="-128")
m2 = mt_lutxy(source, filtered, expr="x y * 255 /", Y=Y, U=U, V=V, scale_inputs="allf", use_expr=2).mt_binarize(threshold=128, chroma="-128")
mask_1 = mt_logic(m1, m2, "max").mt_expand().mt_expand(chroma="-128").Blur(1.58)#.mt_convolution(horizontal="3 1 3", vertical="3 1 3", Y=Y, U=U, V=V)
r = mt_merge(source.RemoveDust(), filtered, mask_1, Y=Y, U=U, V=V)
m3 = mt_lutxy(source, r, expr="x y - abs", Y=Y, U=U, V=V, scale_inputs="allf", use_expr=2).mt_binarize(threshold=5).mt_inpand(chroma="-128", thY=128, thC=128)
m4 = m3.mt_inpand().mt_inpand(chroma="-128")
mask_2 = mt_logic(m3, m4, "xor").Blur(1.58)#.mt_convolution(horizontal="3 1 3", vertical="3 1 3", Y=Y, U=U, V=V)
mt_merge(Repair(filtered, r, 17), filtered.RemoveDust(), mask_2, Y=Y, U=U, V=V)
}
SpotLess_Mod(RadT=1, Pel=1, BlkSz=8, Chroma=true, DeGrain=false, Denoiser=Undefined)
zorr
15th June 2021, 21:07
I have seen Gblur is used in the last version.
For the tests, I replaced it with the avs+ Gblur from manyPlus (http://www.avisynth.nl/users/vcmohan/manyPlus/manyPlus.html)by vcmohan (https://forum.doom9.org/showthread.php?t=174162) instead of the standard 32bit Gblur (http://avisynth.nl/index.php/GBlur).
Is it ok to do so?
Yes, that's the version I used myself. I had to find a blur function that can deal with HBD, there are not many of those. FastBlur would be great but it has some glitches and I wasn't able to create a simple test scenario to report the bug.
chmars
16th June 2021, 05:56
Hmm, too bad... How bad are the glitches? Often?
Because Gblur makes the script 3x slower (well, it's said in the name :) ) than fastblur (fps reported while using Vdub, looked on the ~25 first frames).
Zorr, if you allow, I can post the script version I am using where I turned Delta Restore into a function (updated to last version).
I know it works but can't report precise facts yet as I haven't graded the last footage yet, this allowing to see precisely what happens.
EDIT: But... Maybe bad question but, Is it really important to build the masks in HBD? Couldn't we build them in LBD and apply them in HBD?
chmars
16th June 2021, 06:54
Couldn't resist. Quick and dirty test.
Seems promising.
Had to resize the video to avoid changing all the size parameters. Forgot to resize it back.
https://nmldqjct.preview.infomaniak.website/shared_files/index.php/s/8h2CZrBTueygcxZ/download (https://nmldqjct.preview.infomaniak.website/shared_files/index.php/s/FwKmbrUZyMob1JB/download)
EDIT: (video under the picture)
chmars
16th June 2021, 22:32
After two hours examining the results of the last Delta Restore version used on my footage, I can share some feedback.
For the recall, this footage is old, captured in Slog with some overexposed scenes. So, generally very washed/light.
For this reason, I used Autolevels (In the right manner this time, corrected posts 82 + 83 (https://forum.doom9.org/showthread.php?p=1944383#post1944383) ) to generate a clip for the masks calculation.
Then, the masks are applied on the original clip.
I don't post example images as I don't want to generate too fast conclusions on some pics only. I prefer to share my impressions. There are much parameters and results can vary a lot, also depending on the images used. I kept the defaults parameters.
As said, Spotless strongly removes spots, hairs, dirt. But sometimes, also moving objects.
Delta Restore brings back these objects:
-Light objects always come back as well as light/dark objects.
-Dark only objects come back but also some medium spots and an important proportion of present hairs. All small spots are away. Rare large spots sometimes come back, sometimes not.
However, the resulting images are much cleaner than the source.
Feeling: "There are some spots sometimes, maybe 10-15% remaining, but image is much better" And people parts/objects are back.
Spotless + Delta Restore in this last flavour are perfect for a not-too-much-time-consuming use, with a good result and the security of not deleting important parts. It is well adapted to a systematic treatment, making sure that arms, balloons, feet, birds will not be deleted.
For a very perfect result, I don't know if it is preferable to: either adapt parameters to every type of images and dirt, or use the previous version of Delta Restore without dark masks and use some transparency to bring back (rare) dark only disappeared objects.
EDIT: This version has many changes and work on the masks. Maybe use this version and lower the impact of one of the dark masks?
Delta Restore also has many nice ways to visualize what is happening with all the coloured overlays.
zorr
17th June 2021, 00:13
I tried to come up with a more user friendly version of Delta Restore. I don't think making one huge function with a gazillion parameters is the way to go, Delta Restore works better as a toolbox of functions which can be adapted to almost any situation. However I still wanted to make a single function that takes all the different masks, combines them and creates the final result.
Here's the previous version written in the new style. Some lines are there only to create the debug visualization, I need to create another function for that.
#source1= AviSource("459_s2_002-XAVCI-4K-BLD_5_0-trim_crop-for_tests.avi")
#source1 = crop(source1,700,0,-500,0)
#source1= FFVideoSource("ZorrBack_test.avi")
#source1 = crop(source1,450,50,1352,-40)
source1= FFVideoSource("464_-trim_crop_grd_cut.avi")
source1= KillAudio(source1)
source1_8bit = ConvertBits(source1, bits=8) #I dont'know much about formats, maybe this is not ideal...
source1YV12 = ConvertToYV12 (source1_8bit) #I dont'know much about formats, maybe this is not ideal...
txt_sz = 36
BlkSz = 24
OLap = (BlkSz/2) #important not too low (eg 4 for blksz 12, because creates stairs)
Pel = 1
Tm=false
Bblur = 0.6
ThSAD = 10000
RadT = 1
Usharp_strength= 80
Usharp_radius= 5
Usharp_th = 1 #too high = too many dark pixel packs (ex trees)
#Sharpen----------------------------------------------------------------------
source1_shp=source1.UnsharpMask_avsi(Usharp_strength,Usharp_radius,Usharp_th)
#SpotRemoved_shp=SpotRemoved.UnsharpMask_avsi(Usharp_strength,Usharp_radius,Usharp_th)
#----------------------------------------------------------------------------------
#Remove spots-----------------------------------------------------------------
spotless_107=SpotLess107(source1_shp,blksz=BlkSz,OLap=Olap,pel=pel,Tm=Tm,Bblur=Bblur,thsad=thsad,RadT=RadT)
#SpotRemoved = SpotRemover(source1YV12,Spot=128, RadT=1, Pel=Pel, BlkSz=32, DeGrain=false, Chroma=true, PreFilter=true, mask=false, Tm = true,thsad=thsad, Glob = true)
#----------------------------------------------------------------------------------
# Delta Restore -------------------------------------------------------------
src = source1
filtered = spotless_107
# extra clipping (if needed)
crop_left = 20
crop_right = 1120
#src = src.Crop(crop_left, 0, -crop_right, 0)
#filtered = filtered.Crop(crop_left, 0, -crop_right, 0)
# diff with exaggerated chroma difference
diff1 = mt_makediff(src, filtered, y=3, u=3, v=3)
diff2 = diff1.mt_adddiff(diff1, y=0, u=3, v=3)
diff2 = diff2.mt_adddiff(diff1, y=0, u=3, v=3)
diff = CombinePlanes(diff1, diff2, diff2, planes="YUV", source_planes="YUV", sample_clip=diff1)
# prepare masks --------------------------------------------------------------------
# luma mask - selects where source brightness > filtered brightness
luma_mask = LumaDeltaMask(src, filtered, ">", brightness=1.1, spotSize=3).ExpandMask(3, blur=4)
# chroma mask - selects where source and filtered chromas have large enough difference (formula: sqrt(u*u + v*v))
chroma_mask = ChromaDeltaMask(src, filtered, delta=3, spotSize=3)
# suppression mask - selects where source brightness < filtered brightness
# suppression mask removes too dark areas from chroma mask
suppression_mask = LumaDeltaMask(src, filtered, "<", brightness=0.9, spotSize=2)
suppressed_chroma_mask = chroma_mask.SuppressWith(suppression_mask)
# edge mask - detects sharp edges
# don't detect edges near the frame edges
edges = scharr(src).ZPadding(60, 14, 14, 10)
edgeMask = edges.mt_binarize(52)
# chroma override mask - selects larger color deltas and spot sizes than plain chroma mask and is not suppressed
chroma_override = ChromaDeltaMask(src, filtered, delta=5, spotSize=6).ExpandMask(3, blur=2)
# dark mask: select both darker and lighter mask and keep dark mask if it is near lighter mask or overlaps chroma override mask
dark = LumaDeltaMask(src, filtered, "<", brightness=0.97, limitBrightness=0.66, spotSize=5)
light = LumaDeltaMask(src, filtered, ">", brightness=1.04, spotSize=5).ZPadding(60, 14, 14, 10)
dark_near_light = dark.MustBeNear(light, 28).ExpandMask(3, blur=4)
expanded = dark.MustBeNear(light, 28, expanded=true) # for debugging
# secondary dark mask - doesn't need to be near lighter area but has stricter limits
dark2 = LumaDeltaMask(src, filtered, "<", brightness=0.97, limitBrightness=0.69, limitAbsBrightness=0.12, spotSize=5).ExpandMask(2, blur=2)
# edge test for dark areas
darkEdgeMask = edges.mt_binarize(54) # at least 54 needed for frame 222
darkEdgeAreaMask = mt_hysteresis(darkEdgeMask, dark_near_light.AddTo(dark2), chroma="-128") # for debug
dark_final = DarkDeltaMask(dark_near_light, secondary=dark2, edgeMask=darkEdgeMask)
combined_mask = luma_mask.AddTo(suppressed_chroma_mask).AddTo(chroma_override)
# remove mask areas where sharp edges were found (those are most likely dirt)
edgeAreaMask = mt_hysteresis(edgeMask, combined_mask, chroma="-128") # for debug
delta_restore = DeltaRestore(filtered, src, luma=luma_mask, chroma=suppressed_chroma_mask, chromaOverride=chroma_override, edges=edgeMask, dark=dark_final)
edgeAreaMask = mt_logic(edgeAreaMask, darkEdgeAreaMask, "or")
#edgeAreaMask = darkEdgeAreaMask
# create mask visualization (for debugging) -----------------------------------------
black = BlankClip(src)
yellow = BlankClip(src, color=$d1cc2e) # #d1cc2e f7f140
green = BlankClip(src, color=$40f75b)
gray = BlankClip(src, color=$d0d0d0)
darkgray = BlankClip(src, color=$909090)
orange = BlankClip(src, color=$a36b02)
purple = BlankClip(src, color=$4e0091)
purple_light = BlankClip(src, color=$bc6eff)
purple_dark = BlankClip(src, color=$340061)
red = BlankClip(src, color=$9e2424)
blue = BlankClip(src, color=$0d3b7a) #24579e 243c9e
# maskviz for dark mask
maskviz = Overlay(black, purple, mask=expanded)
maskviz = Overlay(maskviz, purple_dark, mask=dark)
maskviz = Overlay(maskviz, purple_light, mask=light)
maskviz = Overlay(maskviz, black, mask=dark_near_light)
# only show dark mask?
showDarkMaskOnly = false
if (!showDarkMaskOnly) {
maskviz = Overlay(maskviz, yellow, mask=chroma_mask) # chroma mask - yellow
maskviz = Overlay(maskviz, red, mask=suppression_mask, opacity=0.7) # suppression mask - red
maskviz = Overlay(maskviz, green, mask=chroma_override) # chroma override - green
maskviz = Overlay(maskviz, gray, mask=luma_mask) # luma mask - gray
maskviz = Overlay(maskviz, blue, mask=edgeAreaMask, opacity=0.8) # sharpness mask - blue
}
# mask overlay
comp = Overlay(delta_restore, maskviz, opacity=0.45)
comp = Overlay(comp, purple_light, mask=light, opacity=0.2)
comp = Overlay(comp, black, mask=dark, opacity=0.2)
comp = Overlay(comp, black, mask=dark_final, opacity=0.6)
maskviz = Overlay(maskviz, black, mask=dark_final, opacity=0.6)
stack = StackHorizontal(\
sub(src, "source", txt_sz), \
sub(filtered, "Spotless", txt_sz), \
sub(delta_restore, "Delta Restore", txt_sz), \
sub(comp, "restore + mask", txt_sz), \
sub(maskviz, "masks used", txt_sz),\
sub(edgeMask, "edge mask", txt_sz),\
sub(diff, "diff", txt_sz)\
)
# scaling (if needed)
stack = stack.BilinearResize(stack.width/2 + 1, stack.height/2)
return stack
inter = Interleave(\
sub(src, "source", txt_sz), \
sub(filtered, "Spotless", txt_sz), \
sub(delta_restore, "Delta Restore", txt_sz), \
sub(comp, "restore + mask", txt_sz), \
sub(maskviz, "masks used", txt_sz),\
sub(diff, "diff", txt_sz)\
)
return inter
function sub(c, label, txt_sz) {
return subtitle(c, label, size = txt_sz, align=8)
}
#~ ------------------------------------------------------------------------------------------------------------------
#~ ------------------------------------------------------------------------------------------------------------------
#~ ------------------------------------------------------------------------------------------------------------------
function DeltaRestore(clip filtered, clip source, clip "luma", clip "chroma", clip "chromaOverride", clip "edges", clip "dark") {
Assert(luma.Defined || chroma.Defined || chromaOverride.Defined || dark.Defined, "DeltaRestore:must define at least one of luma, chroma, chromaOverride, dark")
mask = Default(luma, BlankClip(filtered))
if (chroma.Defined) {
mask = mask.AddTo(chroma)
}
if (chromaOverride.Defined) {
mask = mask.AddTo(chromaOverride)
}
if (edges.Defined) {
mask = mask.MustNotOverlap(edges)
}
if (dark.Defined) {
mask = mask.AddTo(dark)
}
return Overlay(filtered, source, mask=mask)
}
function DarkDeltaMask(clip mask, clip "secondary", clip "edgeMask") {
if (secondary.Defined) {
mask = mask.AddTo(secondary)
}
if (edgeMask.Defined) {
mask = mask.MustNotOverlap(edgeMask)
}
return mask
}
function AddTo(clip mask, clip other) {
return mt_logic(mask, other, "or")
}
function SuppressWith(clip mask, clip suppressionMask) {
return mt_lutxy(mask, suppressionMask, expr="x y -")
}
function MustNotOverlap(clip mask, clip other) {
other = mt_hysteresis(other, mask, chroma="-128")
return mt_lutxy(mask, other, expr="x y -")
}
function MustOverlap(clip mask, clip otherMask) {
return mt_logic(mask, mt_hysteresis(otherMask, mask), "and")
}
function MustBeNear(clip mask, clip otherMask, int maxDistance, bool "expanded") {
expanded = Default(expanded, false)
expand_amount = maxDistance / 2
mask_expanded = ExpandMask(mask, expand_amount)
other_expanded = ExpandMask(otherMask, maxDistance - expand_amount)
common = mt_logic(mask_expanded, other_expanded, "or")
if (expanded) {
return common
}
other_near = mt_hysteresis(otherMask, common)
return mt_logic(mask, other_near, "and")
}
function ZPadding(clip c, int left, int top, int right, int bottom) {
return c.Crop(left, top, -right, -bottom).AddBorders(left, top, right, bottom, color_yuv=$008080)
}
function scharr(clip c) {
scharr_x = c.mt_edge("3 0 -3 10 0 -10 3 0 -3", thY1 = 0, thY2 = 255, y=3, u=1, v=1)
scharr_y = c.mt_edge("3 10 3 0 0 0 -3 -10 -3", thY1 = 0, thY2 = 255, y=3, u=1, v=1)
scharr = mt_lutxy(scharr_x, scharr_y, yexpr=mt_polish("((x*x)+(y*y))^0.5"), chroma="-127")
return scharr
}
function RemoveSmallSpots(clip mask, int spotSize) {
mask_orig = mask
# remove too small spots
for (i = 1, spotSize) {
mask = mask.mt_inpand()
}
mask = mt_hysteresis(mask, mask_orig, chroma="-127")
return mask
}
function LumaDeltaMask(clip source, clip filtered, string direction, float "brightness", float "limitBrightness", float "limitAbsBrightness", int "spotSize") {
# direction is ">" if you want mask to contain areas where source is brighter than filtered by "brightness", direction "<" selects where source is darker
# adjust brightness to change how bright objects are selected, typical values between 1.0 and 1.1, larger value = brighter object
# remove too bright or dark areas using limitBrightness
# spotSize controls the smallest spot size allowed in the mask, typical values between 0 and 3
brightness = Default(brightness, 0.9)
spotSize = Default(spotSize, 2)
limitBrightness = Default(limitBrightness, brightness)
limitAbsBrightness = Default(limitAbsBrightness, 0.0)
# diff
diff = Grayscale(mt_makediff(source, filtered))
# mask
mask = mt_lut(diff, expr="x range_half "+String(brightness)+" * " + direction + " 255 scalef 0 ?")
mask_orig = mask
# limitBrightness suppression
if (limitBrightness != brightness) {
mask_suppression = mt_lut(diff, expr="x range_half "+String(limitBrightness)+" * " + direction + " 255 scalef 0 ?")
mask = mt_lutxy(mask, mask_suppression, expr="x y -")
}
# limitAbsBrightness suppression
if (limitAbsBrightness > 0.0) {
mask = mt_lutxy(mask, source, expr="y range_max "+String(limitAbsBrightness)+" * < 0 x ?")
}
# remove too small spots
mask = RemoveSmallSpots(mask, spotSize)
# limitBrightness or limitAbsBrightness suppression (part 2)
if (limitBrightness != brightness || limitAbsBrightness > 0.0) {
# if suppression didn't kill the whole mask then return it without suppression
mask = mt_hysteresis(mask, mask_orig, chroma="-127")
}
return mask
}
function ChromaDeltaMask(clip source, clip filtered, int "delta", int "spotSize") {
# adjust delta to change how different colors are selected
# spotSize controls the smallest spot size allowed in the mask, typical values between 0 and 3
delta = Default(delta, 3)
spotSize = Default(spotSize, 2)
# diff
diff = mt_makediff(source, filtered, y=3, u=3, v=3)
# extract chroma channels
u = ExtractU(diff)
v = ExtractV(diff)
# calculate deltas
u_delta = mt_lut(u, expr="x range_half - abs")
v_delta = mt_lut(v, expr="x range_half - abs")
# combine deltas
# uv_delta = mt_lutxy(u_delta, v_delta, expr="x y +")
uv_delta = mt_lutxy(u_delta, v_delta, expr="x x * y y * + 0.5 ^")
# rescale (if needed)
if (uv_delta.width < source.width || uv_delta.height < source.height) {
uv_delta = uv_delta.BilinearResize(source.width, source.height)
}
# create mask
mask = uv_delta.mt_binarize(delta)
# remove too small spots
mask = RemoveSmallSpots(mask, spotSize)
# restore original format
blank = BlankClip(source)
mask = CombinePlanes(mask, blank, blank, planes="YUV", source_planes="YUV", sample_clip=source)
return mask
}
function ExpandMask(clip mask, int amount, float "blur") {
blur = Default(blur, 0)
for (i = 1, amount) {
mask = mask.mt_expand(chroma="-127")
}
if (blur > 0) {
#mask = mask.FastBlur(blur)
#mask = mask.binomialBlur(varY=blur)
mask = mask.GBlur(rad=6, sd=blur, u=true)
}
return mask
}
Function mRD_RestoreGrain (clip rd, clip o, float "str1", float "str2") {
sisavs26 = !(VersionNumber() < 2.60)
str1 = string(default(str1, 10.0))
str2 = string(default(str2, 5.0))
expr = sisavs26 ? " x range_half - abs "+str1+" scalef min "+str2+" scalef x range_half - abs - min 0 max x range_half - x range_half - abs 1 max / * range_half + " : " x 128 - abs "+str1+" min "+str2+" x 128 - abs - min 0 max x 128 - x 128 - abs 1 max / * 128 + "
Return sisavs26 ? mt_makediff(o, rd).mt_lut(expr, use_expr=2).mt_adddiff(rd, chroma="copy second") : mt_makediff(o, rd).mt_lut(expr).mt_adddiff(rd, chroma="copy second")
}
zorr
17th June 2021, 00:21
If you don't need the debug visualization you can now call Delta Restore as one function call (although the parameters themselves are sometimes function calls). But it's easier to read and every setting is there in one place. Also maybe you don't need all the features, you can select which masks you want to use. You only need to provide one of luma, chroma, chromaOverride or dark masks. Some examples:
# example call with luma mask only
restored = DeltaRestore(filtered, src, luma = LumaDeltaMask(src, filtered, ">", brightness=1.1, spotSize=3).ExpandMask(3, blur=4))
# example call with luma and chroma only
restored = DeltaRestore(filtered, src, \
luma = LumaDeltaMask(src, filtered, ">", brightness=1.1, spotSize=3).ExpandMask(3, blur=4), \
chroma = ChromaDeltaMask(src, filtered, delta=3, spotSize=3) \
)
# example call with luma and suppressed chroma only
restored = DeltaRestore(filtered, src, \
luma = LumaDeltaMask(src, filtered, ">", brightness=1.1, spotSize=3).ExpandMask(3, blur=4), \
chroma = ChromaDeltaMask(src, filtered, delta=3, spotSize=3).SuppressWith(LumaDeltaMask(src, filtered, "<", brightness=0.9, spotSize=2)) \
)
# example call with luma, suppressed chroma and chroma override only
restored = DeltaRestore(filtered, src, \
luma = LumaDeltaMask(src, filtered, ">", brightness=1.1, spotSize=3).ExpandMask(3, blur=4), \
chroma = ChromaDeltaMask(src, filtered, delta=3, spotSize=3).SuppressWith(LumaDeltaMask(src, filtered, "<", brightness=0.9, spotSize=2)), \
chromaOverride = ChromaDeltaMask(src, filtered, delta=5, spotSize=6).ExpandMask(3, blur=2) \
)
# example call with luma, suppressed chroma, chroma override and edge mask only
restored = DeltaRestore(filtered, src, \
luma = LumaDeltaMask(src, filtered, ">", brightness=1.1, spotSize=3).ExpandMask(3, blur=4), \
chroma = ChromaDeltaMask(src, filtered, delta=3, spotSize=3).SuppressWith(LumaDeltaMask(src, filtered, "<", brightness=0.9, spotSize=2)), \
chromaOverride = ChromaDeltaMask(src, filtered, delta=5, spotSize=6).ExpandMask(3, blur=2), \
edges = src.scharr().ZPadding(60, 14, 14, 10).mt_binarize(52) \
)
# example call with luma, suppressed chroma, chroma override, edge mask and a simple dark mask only
restored = DeltaRestore(filtered, src, \
luma = LumaDeltaMask(src, filtered, ">", brightness=1.1, spotSize=3).ExpandMask(3, blur=4), \
chroma = ChromaDeltaMask(src, filtered, delta=3, spotSize=3).SuppressWith(LumaDeltaMask(src, filtered, "<", brightness=0.9, spotSize=2)), \
chromaOverride = ChromaDeltaMask(src, filtered, delta=5, spotSize=6).ExpandMask(3, blur=2), \
edges = src.scharr().ZPadding(60, 14, 14, 10).mt_binarize(52), \
dark = DarkDeltaMask(LumaDeltaMask(src, filtered, "<", brightness=0.97, limitBrightness=0.69, limitAbsBrightness=0.12, spotSize=5).ExpandMask(2, blur=2)) \
)
# example call with luma, suppressed chroma, chroma override, edge mask and an advanced dark mask
restored = DeltaRestore(filtered, src, \
luma = LumaDeltaMask(src, filtered, ">", brightness=1.1, spotSize=3).ExpandMask(3, blur=4), \
chroma = ChromaDeltaMask(src, filtered, delta=3, spotSize=3).SuppressWith(LumaDeltaMask(src, filtered, "<", brightness=0.9, spotSize=2)), \
chromaOverride = ChromaDeltaMask(src, filtered, delta=5, spotSize=6).ExpandMask(3, blur=2), \
edges = src.scharr().ZPadding(60, 14, 14, 10).mt_binarize(52), \
dark = DarkDeltaMask( \
LumaDeltaMask(src, filtered, "<", brightness=0.97, limitBrightness=0.66, spotSize=5) \
.MustBeNear(LumaDeltaMask(src, filtered, ">", brightness=1.04, spotSize=5).ZPadding(60, 14, 14, 10), 28).ExpandMask(3, blur=4), \
secondary = LumaDeltaMask(src, filtered, "<", brightness=0.97, limitBrightness=0.69, limitAbsBrightness=0.12, spotSize=5).ExpandMask(2, blur=2), \
edgeMask = src.scharr().ZPadding(60, 14, 14, 10).mt_binarize(54) \
) \
)
I hope this works for John. :)
zorr
17th June 2021, 00:40
Hmm, too bad... How bad are the glitches? Often?
Because Gblur makes the script 3x slower
It's quite rare but I don't want to have any chance of ruined masks. The glitch appears as a line or lines that extend from a mask all the way to the bottom of the screen.
Zorr, if you allow, I can post the script version I am using where I turned Delta Restore into a function (updated to last version).
Please do, I welcome all mods and improvements.
EDIT: But... Maybe bad question but, Is it really important to build the masks in HBD? Couldn't we build them in LBD and apply them in HBD?
Yes the masks could be 8-bit, the end result would be practically same.
kedautinh12
17th June 2021, 01:29
Thank zorr
StainlessS
25th June 2021, 13:58
Just a heads up.
Some time ago I used Spotless on 1946 PAL DVD "A Matter of Life and Death" AKA "Stairway to Heaven" in the USA [David Niven, Kim Hunter].
Just noticed today that in the 2nd section of Table Tennis, not a single ping pong ball was left after Spotless had swallowed the lot. [RadT=2]
Anyways, might be a good test piece if you've got the DVD. [Ping pong balls being white will probably defeat attempts to keep the balls].
https://en.wikipedia.org/wiki/A_Matter_of_Life_and_Death_(film)
chmars
25th June 2021, 22:36
These situations will probably be much enhanced with Delta Restore.
In the next post, the whole script I used on the last 10 reels of 8mm to:
-Clean the spots
-restore the swallowed parts by Spotless
-Sharpen
I hope this script being user friendly.
I added some explanations for noobs like me.
Added some variables to easily chose:
-to grade a clip just for the masks construction
-chose fastblur or Gblur
-restore grain or not
-sharpen or not
On the 8mm reels, I applied this script before grading (Resolve).
So I had time to verify the result while grading.
-> Very clean and very few image parts disappeared.
Once more, never enough, thanks to StainlesS, Zorr and Real.finder.
I don't say the result is 100% perfect. One could find to say, by looking frame by frame. But huge majority for sure! I
mperfections will not be noticeable by looking at the result at normal speed, by far.
Some flying rice (wedding) and some splashing drops disappeared sometimes. But balls, arms, legs... All was there!
What could be improved by tweaking variables:
-blur the returned parts edges a bit more. But I used FastBlur instead of Gbur, Gblur is recommanded by Zorr.
Descratch could also be added but I didn't, as I wished to keep 10bit color depth.
chmars
25th June 2021, 22:37
Part 1
/*
SpotDel (Spotless+Delta Restore) 1.0
Script intended to clean and sharpen digitalized film.
Can optionally restore grain after cleaning.
Sharpening and denoising are basic. Please see doom9 forum, VideoFred's and JohnMeyer's scripts and posts fore more knowlege. (VideoFred's sharp-blur-sharp-blur-sharp trick). Please share improvements!
Functions: Spotless:StainlessS, Delta Restore:Zorr, Restore Grain:real.finder. Huge Thanks !
Script: chmars - 17/06/2021 - some parts copied here and there, thank to solutions given by doom9 forum users.
Thread: https://forum.doom9.org/showthread.php?t=182831
Spotless thread, last versions: https://forum.doom9.org/showthread.php?t=181777
Lots of informaions on film capture/restoration: https://forum.doom9.org/showthread.php?t=144271
Script tested on HD 4:2:2 10bits sources with Avisynth+370_x64
! ! ! ! !
-You should maybe adapt the sizes parameters (blksz, radiuses, spotsizes) if your source is not HD
How it works:
Spotless removes dirt spots that are not present in neibourg frames.
But sometimes, fast moving objects are removed too (balloons, birds, hands, feet)
Hence is Delta Restore needed. It brings back details that are part of the image.
As Spotless tends to soften/degrain the image, parts brought back from the original image by Delta Restore are more grainy.
The solution for a discreete replacement (and nicer result) of these parts is to sharpen the Spotless input.
Washed out, Slog rushes:
Spotless seems to work as well with washed out images (old footage, Slog rushes), but Delta Restore needs colors and contrast to funtion properly.
In this case, a more contrasted clip can be made to feed Delta Restore and only generate masks. However, the final result will not be graded.
*/
#~ SetMemoryMax(8000) # Dunno if optimal...
#~ threads = 16 # Dunno if optimal...
#~ ### Check speed, memory consumption and CPU usage with AVSMeter64 ###
#~ SetFilterMTMode("DEFAULT_MT_MODE", 2) # Dunno if optimal...
# VARIABLES #################################################################################################
########################################################################################################################
########################################################################################################################
# Here, define what you want
# Path to file
video_file = "D:\some_path\some_file"
play_speed = 25 # This will not change your frames/fields, neither their count
txt_sz = 36 #Test size if infos displayed
Sharpen_it = True # Default True. Sharpen clip or not (recomanded, see intro)
Slog = False # Default False. If True, Generate a leveled clip f or wahed-out or Slog rushes. Final result will not be changed levelwise
#Grainitude, cancels sharpen effect
Rgr = False # Default false. True Restores grain after passed through Spotless. Might restore some dust too. 5,10 = good compromise grain/dirt.
val1 = 10
val2 = 20 # 5,10 = good compromise grain/dirt. Dunno how that works exactly...
# Sharpen variables ----------------------------------------------------------------------------------------------------------------------------------------------------------
Usharp_strength= 80 # Bigger = sharper & more grain
Usharp_radius= 5
Usharp_th = 1
# Spotless variables, to remove dirt and spots. -------------------------------------------------------------------------------------------------------------------------
# See Function script for more advanced settings
BlkSz =24 #Spot Size, Default 8. MAnalyse BlkSize. Bigger blksz quicker and perhaps better for HD clips. [Info: current Pinterf MvTools allows for BlkSize=12, and overlap=6]
Pel = 1 # Default 2. 1, 2, or 4. Maybe set 1 for HD+. (1=precision to pixel, 2=half pixel, 4=quarter pixel)
Tm=false #TrueMotion Default True. Some folk swear truemotion=false is better.
Bblur = 0.6 #Default 0.6. If used, Suggest about 0.6, where MAnalyse create vectors is performed on denoised (blurred) super clip for better motion analysis.
ThSAD = 10000 # [SAD, Sum of Absolute (pixelwise) Differences] 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)
RadT = 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).
#Delta Restore variables
dark2Brt = 0.85 #Default 0.97. Decrease if too many spots coming back
dark2BrtLimit = 0.86 # Default 0.86. Same mask. Try to change it too.
Global RiskyBlur = true # True = use fastblur and risk some glitches. False = Use Gblur and go stable, at the price of a slower script
#Denoise----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
# If needed, removes grain
#Not deeply tested, please test and report
#~ denoising_strength= 200 #denoising level of first denoiser: MDegrain() # high reduces colors
#~ block_size= 4 #block size of MVDegrain
#~ block_size_v= 4
#~ block_over= 2 #block overlapping of MVDegrainMulti()
#~ dirt_strength=10 #sets amount of dirt removal (big spots)
# / END OF VARIABLES ######################################################################################################
########################################################################################################################
########################################################################################################################
#~ source1= FFVideoSource("D:\davinci_eric_folder\rushes_cathy_new_system\463_cathy_s2_005-XAVCI-4K-BLD_5_0-trim_crop.mov").KillAudio()
#~ source1= AviSource("D:\davinci_eric_folder\rushes_cathy_new_system\462_cathy_s2_004-XAVCI-4K-BLD_5_0-trim_crop.mov").KillAudio()
source1 = FFVideoSource(video_file).KillAudio()
# Set play speed (if needed)
source1 = assumefps(source1,play_speed)
source1 = source1.Trim(294, 327)
source1 = source1.BilinearResize(source1.width*3 + 0, source1.height*3)
#~ source1 = crop(source1, 500, 200, -400, -200)
#~ source1 = source1.BilinearResize(source1.width/2 + 0, source1.height/2)
# Optional
## ------------------------------------------------------------------------------------
# Keep only from frame n to frame m
# source1 = trim(source1, 2,0)
# Resize (speeds-up for tests?)
# source1 = source1.BilinearResize(source.width/2 , source.height/2)
# Leveled clip
## -------------------------------------------------------------------------------------------------------------------------------------------------------------
#In case of washed-out footage or Slog captures
# Needed to generate dark and saturated enough clips, to feed the filters whith what they need.
# Will NOT apply to final result. Only for mask building
# If Slog = True, make a decently contrasted clip to feed the filters.
# Else, do nothing, source1_lvl will be identical to source1
source1_lvl = Slog ? source1.convertbits(8).Autolevels(filterradius = 3, sceneChgThresh = 20, autogamma = true, midpoint = 0.5, autolevel = true, border=500).convertbits(16) : source1
#~ lvl_src = Slog ? source1.convertbits(8).Autolevels(filterradius = 3, sceneChgThresh = 20, autogamma = true, midpoint = 0.5, autolevel = true, border=500, input_high = 230 ).convertbits(16) : source1
#~ lvl_src = Slog ? ColorYUV(source1, autogain = true) : source1
#Check it
##-----------------------------------------------------------------------------------
# If needed, uncomment "return..." lines to verify levels of mask construction graded clip
h = Histogram(source1,"classic")
h_lvl = Slog ? Histogram(source1_lvl,"classic") : NOP
#~ return(StackHorizontal(h, h_lvl)) # Un comment to see clips with histograms
#~ return(source1)
#Sharpening
# -----------------------------------------------------------------------------------
# Best results found by sharpening before (not after) cleaning.
source1_shp = Sharpen_it ? source1.UnsharpMask_avsi(Usharp_strength,Usharp_radius,Usharp_th) : source1
# If Using Slog rushes, also sharpen the leveled clip
source1_lvl_shp = Sharpen_it ? source1_lvl.UnsharpMask_avsi(Usharp_strength,Usharp_radius,Usharp_th) : source1_lvl
#Remove spots, Spotless, version 1.07
# ------------------------------------------------------------------------------------
source1_shp_spt = SpotLess(source1_shp,blksz=BlkSz,pel=pel,Tm=Tm,Bblur=Bblur,thsad=thsad,RadT=RadT)
source1_lvl_shp_spt = Slog ? SpotLess(source1_lvl_shp,blksz=BlkSz,pel=pel,Tm=Tm,Bblur=Bblur,thsad=thsad,RadT=RadT) : source1_shp_spt
#~ return(filtered_orig)
# Restore removed objects seen as dirt
# ------------------------------------------------------------------------------------
#~ DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
#~ RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR
# Delta Restore -------------------------------------------------------------
# extra clipping (if needed)
#~ crop_left = 20
#~ crop_right = 1120
#src = src.Crop(crop_left, 0, -crop_right, 0)
#filtered = filtered.Crop(crop_left, 0, -crop_right, 0)
# diff with exaggerated chroma difference
diff1 = mt_makediff(source1_lvl_shp, source1_lvl_shp_spt, y=3, u=3, v=3)
diff2 = diff1.mt_adddiff(diff1, y=0, u=3, v=3)
diff2 = diff2.mt_adddiff(diff1, y=0, u=3, v=3)
diff = CombinePlanes(diff1, diff2, diff2, planes="YUV", source_planes="YUV", sample_clip=diff1)
#Calculate masks with leveled clips
####----------------------------------------------------------------------------------------------------------------
# prepare masks --------------------------------------------------------------------
# luma mask - selects where source brightness > filtered brightness
# luma mask - selects where source brightness > filtered brightness
luma_mask = LumaDeltaMask(source1_lvl_shp, source1_lvl_shp_spt, ">", brightness=1.1, spotSize=3).ExpandMask(3, blur=4)
# chroma mask - selects where source and filtered chromas have large enough difference (formula: sqrt(u*u + v*v))
chroma_mask = ChromaDeltaMask(source1_lvl_shp, source1_lvl_shp_spt, delta=3, spotSize=3)
# suppression mask - selects where source brightness < filtered brightness
# suppression mask removes too dark areas from chroma mask
suppression_mask = LumaDeltaMask(source1_lvl_shp, source1_lvl_shp_spt, "<", brightness=0.9, spotSize=2)
suppressed_chroma_mask = chroma_mask.SuppressWith(suppression_mask)
# edge mask - detects sharp edges
# don't detect edges near the frame edges
edges = scharr(source1_lvl_shp).ZPadding(60, 14, 14, 10)
edgeMask = edges.mt_binarize(52)
# chroma override mask - selects larger color deltas and spot sizes than plain chroma mask and is not suppressed
chroma_override = ChromaDeltaMask(source1_lvl_shp, source1_lvl_shp_spt, delta=5, spotSize=6).ExpandMask(3, blur=2)
# dark mask: select both darker and lighter mask and keep dark mask if it is near lighter mask or overlaps chroma override mask
dark = LumaDeltaMask(source1_lvl_shp, source1_lvl_shp_spt, "<", brightness=0.97, limitBrightness=0.66, spotSize=5)
light = LumaDeltaMask(source1_lvl_shp, source1_lvl_shp_spt, ">", brightness=1.04, spotSize=5).ZPadding(60, 14, 14, 10)
dark_near_light = dark.MustBeNear(light, 28).ExpandMask(3, blur=4)
expanded = dark.MustBeNear(light, 28, expanded=true) # for debugging
# secondary dark mask - doesn't need to be near lighter area but has stricter limits
dark2 = LumaDeltaMask(source1_lvl_shp, source1_lvl_shp_spt, "<", brightness=dark2Brt, limitBrightness=dark2BrtLimit, limitAbsBrightness=0.12, spotSize=5).ExpandMask(2, blur=2)
# edge test for dark areas
darkEdgeMask = edges.mt_binarize(54) # at least 54 needed for frame 222
darkEdgeAreaMask = mt_hysteresis(darkEdgeMask, dark_near_light.AddTo(dark2), chroma="-128") # for debug
dark_final = DarkDeltaMask(dark_near_light, secondary=dark2, edgeMask=darkEdgeMask)
combined_mask = luma_mask.AddTo(suppressed_chroma_mask).AddTo(chroma_override)
# remove mask areas where sharp edges were found (those are most likely dirt)
edgeAreaMask = mt_hysteresis(edgeMask, combined_mask, chroma="-128") # for debug
#~ Apply masks with unleveled clips
####----------------------------------------------------------------------------------------------------------------
delta_restore = DeltaRestore(source1_shp_spt, source1_shp, luma=luma_mask, chroma=suppressed_chroma_mask, chromaOverride=chroma_override, edges=edgeMask, dark=dark_final)
####----------------------------------------------------------------------------------------------------------------
#~ Display the work---------------------------------------------------------------------------------------------------------------------
edgeAreaMask = mt_logic(edgeAreaMask, darkEdgeAreaMask, "or")
#edgeAreaMask = darkEdgeAreaMask
EDIT: Corrected error in variables names: Delta Restore has to compare sharpened original with sharpened spotlessed. Was: comparing unsharpened original with sharpened spotlessed. (source1_lvl_shp instead of source1_lvl)
chmars
25th June 2021, 22:38
Part 2
# create mask visualization (for debugging) -----------------------------------------
black = BlankClip(source1)
yellow = BlankClip(source1, color=$d1cc2e) # #d1cc2e f7f140
green = BlankClip(source1, color=$40f75b)
gray = BlankClip(source1, color=$d0d0d0)
darkgray = BlankClip(source1, color=$909090)
orange = BlankClip(source1, color=$a36b02)
purple = BlankClip(source1, color=$4e0091)
purple_light = BlankClip(source1, color=$bc6eff)
purple_dark = BlankClip(source1, color=$340061)
red = BlankClip(source1, color=$9e2424)
blue = BlankClip(source1, color=$0d3b7a) #24579e 243c9e
# maskviz for dark mask
maskviz = Overlay(black, purple, mask=expanded)
maskviz = Overlay(maskviz, purple_dark, mask=dark)
maskviz = Overlay(maskviz, purple_light, mask=light)
maskviz = Overlay(maskviz, black, mask=dark_near_light)
# only show dark mask?
showDarkMaskOnly = false
if (!showDarkMaskOnly) {
maskviz = Overlay(maskviz, yellow, mask=chroma_mask) # chroma mask - yellow
maskviz = Overlay(maskviz, red, mask=suppression_mask, opacity=0.7) # suppression mask - red
maskviz = Overlay(maskviz, green, mask=chroma_override) # chroma override - green
maskviz = Overlay(maskviz, gray, mask=luma_mask) # luma mask - gray
maskviz = Overlay(maskviz, blue, mask=edgeAreaMask, opacity=0.8) # sharpness mask - blue
}
# mask overlay
comp = Overlay(delta_restore, maskviz, opacity=0.45)
comp = Overlay(comp, purple_light, mask=light, opacity=0.2)
comp = Overlay(comp, black, mask=dark, opacity=0.2)
comp = Overlay(comp, black, mask=dark_final, opacity=0.6)
maskviz = Overlay(maskviz, black, mask=dark_final, opacity=0.6)
stack1 = StackHorizontal(\
sub(source1, "source", txt_sz), \
sub(source1_shp_spt, "sharp+Spotless", txt_sz), \
sub(delta_restore, "Delta Restore", txt_sz), \
sub(comp, "restore + mask", txt_sz)\
)
stack2 = StackHorizontal(\
sub(maskviz, "masks used", txt_sz),\
sub(edgeMask, "edge mask", txt_sz),\
sub(diff, "diff", txt_sz),\
sub(blankclip(source1), "", txt_sz)\
)
# scaling (if needed)
#~ stack = stack.BilinearResize(stack.width/2 + 1, stack.height/2)
stack1 = stack1.BilinearResize(stack1.width/2 , stack1.height/2)
stack2 = stack2.BilinearResize(stack2.width/2 , stack2.height/2)
stacks = StackVertical(stack1,stack2)
#~ return stacks
inter = Interleave(\
sub(source1, "source", txt_sz), \
sub(source1_shp_spt, "sharp+Spotless", txt_sz), \
sub(delta_restore, "Delta Restore", txt_sz), \
sub(comp, "restore + mask", txt_sz), \
sub(maskviz, "masks used", txt_sz),\
sub(diff, "diff", txt_sz)\
)
#~ return inter
function sub(c, label, txt_sz) {
return subtitle(c, label, size = txt_sz, align=8)
}
#~ Delta Restore End
#~ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#~/ DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
#~ /RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR
# Restore grain - cancels sharpen
#------------------------------------------------------------------------------------
restored = Rgr ? mRD_RestoreGrain(delta_restore, source1,val1, val2) : delta_restore #5,10 = good compromise grain/dirt
#~ return(Interleave(source1, restored_2, restored))
return(restored)
#~ return(interleave(restored, source1))
#~ FUNCTIONS
##########################################################################################################################
#~ Delta Restore Functions
#~ ------------------------------------------------------------------------------------------------------------------
#~ ------------------------------------------------------------------------------------------------------------------
#~ ------------------------------------------------------------------------------------------------------------------
function DeltaRestore(clip filtered, clip source, clip "luma", clip "chroma", clip "chromaOverride", clip "edges", clip "dark") {
Assert(luma.Defined || chroma.Defined || chromaOverride.Defined || dark.Defined, "DeltaRestore:must define at least one of luma, chroma, chromaOverride, dark")
mask = Default(luma, BlankClip(filtered))
if (chroma.Defined) {
mask = mask.AddTo(chroma)
}
if (chromaOverride.Defined) {
mask = mask.AddTo(chromaOverride)
}
if (edges.Defined) {
mask = mask.MustNotOverlap(edges)
}
if (dark.Defined) {
mask = mask.AddTo(dark)
}
return Overlay(filtered, source, mask=mask)
}
function DarkDeltaMask(clip mask, clip "secondary", clip "edgeMask") {
if (secondary.Defined) {
mask = mask.AddTo(secondary)
}
if (edgeMask.Defined) {
mask = mask.MustNotOverlap(edgeMask)
}
return mask
}
function AddTo(clip mask, clip other) {
return mt_logic(mask, other, "or")
}
function SuppressWith(clip mask, clip suppressionMask) {
return mt_lutxy(mask, suppressionMask, expr="x y -")
}
function MustNotOverlap(clip mask, clip other) {
other = mt_hysteresis(other, mask, chroma="-128")
return mt_lutxy(mask, other, expr="x y -")
}
function MustOverlap(clip mask, clip otherMask) {
return mt_logic(mask, mt_hysteresis(otherMask, mask), "and")
}
function MustBeNear(clip mask, clip otherMask, int maxDistance, bool "expanded") {
expanded = Default(expanded, false)
expand_amount = maxDistance / 2
mask_expanded = ExpandMask(mask, expand_amount)
other_expanded = ExpandMask(otherMask, maxDistance - expand_amount)
common = mt_logic(mask_expanded, other_expanded, "or")
if (expanded) {
return common
}
other_near = mt_hysteresis(otherMask, common)
return mt_logic(mask, other_near, "and")
}
function ZPadding(clip c, int left, int top, int right, int bottom) {
return c.Crop(left, top, -right, -bottom).AddBorders(left, top, right, bottom, color_yuv=$008080)
}
function scharr(clip c) {
scharr_x = c.mt_edge("3 0 -3 10 0 -10 3 0 -3", thY1 = 0, thY2 = 255, y=3, u=1, v=1)
scharr_y = c.mt_edge("3 10 3 0 0 0 -3 -10 -3", thY1 = 0, thY2 = 255, y=3, u=1, v=1)
scharr = mt_lutxy(scharr_x, scharr_y, yexpr=mt_polish("((x*x)+(y*y))^0.5"), chroma="-127")
return scharr
}
function RemoveSmallSpots(clip mask, int spotSize) {
mask_orig = mask
# remove too small spots
for (i = 1, spotSize) {
mask = mask.mt_inpand()
}
mask = mt_hysteresis(mask, mask_orig, chroma="-127")
return mask
}
chmars
25th June 2021, 22:38
Part 3 of 3
function LumaDeltaMask(clip source, clip filtered, string direction, float "brightness", float "limitBrightness", float "limitAbsBrightness", int "spotSize") {
# direction is ">" if you want mask to contain areas where source is brighter than filtered by "brightness", direction "<" selects where source is darker
# adjust brightness to change how bright objects are selected, typical values between 1.0 and 1.1, larger value = brighter object
# remove too bright or dark areas using limitBrightness
# spotSize controls the smallest spot size allowed in the mask, typical values between 0 and 3
brightness = Default(brightness, 0.9)
spotSize = Default(spotSize, 2)
limitBrightness = Default(limitBrightness, brightness)
limitAbsBrightness = Default(limitAbsBrightness, 0.0)
# diff
diff = Grayscale(mt_makediff(source, filtered))
# mask
mask = mt_lut(diff, expr="x range_half "+String(brightness)+" * " + direction + " 255 scalef 0 ?")
mask_orig = mask
# limitBrightness suppression
if (limitBrightness != brightness) {
mask_suppression = mt_lut(diff, expr="x range_half "+String(limitBrightness)+" * " + direction + " 255 scalef 0 ?")
mask = mt_lutxy(mask, mask_suppression, expr="x y -")
}
# limitAbsBrightness suppression
if (limitAbsBrightness > 0.0) {
mask = mt_lutxy(mask, source, expr="y range_max "+String(limitAbsBrightness)+" * < 0 x ?")
}
# remove too small spots
mask = RemoveSmallSpots(mask, spotSize)
# limitBrightness or limitAbsBrightness suppression (part 2)
if (limitBrightness != brightness || limitAbsBrightness > 0.0) {
# if suppression didn't kill the whole mask then return it without suppression
mask = mt_hysteresis(mask, mask_orig, chroma="-127")
}
return mask
}
function ChromaDeltaMask(clip source, clip filtered, int "delta", int "spotSize") {
# adjust delta to change how different colors are selected
# spotSize controls the smallest spot size allowed in the mask, typical values between 0 and 3
delta = Default(delta, 3)
spotSize = Default(spotSize, 2)
# diff
diff = mt_makediff(source, filtered, y=3, u=3, v=3)
# extract chroma channels
u = ExtractU(diff)
v = ExtractV(diff)
# calculate deltas
u_delta = mt_lut(u, expr="x range_half - abs")
v_delta = mt_lut(v, expr="x range_half - abs")
# combine deltas
# uv_delta = mt_lutxy(u_delta, v_delta, expr="x y +")
uv_delta = mt_lutxy(u_delta, v_delta, expr="x x * y y * + 0.5 ^")
# rescale (if needed)
if (uv_delta.width < source.width || uv_delta.height < source.height) {
uv_delta = uv_delta.BilinearResize(source.width, source.height)
}
# create mask
mask = uv_delta.mt_binarize(delta)
# remove too small spots
mask = RemoveSmallSpots(mask, spotSize)
# restore original format
blank = BlankClip(source)
mask = CombinePlanes(mask, blank, blank, planes="YUV", source_planes="YUV", sample_clip=source)
return mask
}
function ExpandMask(clip mask, int amount, float "blur") {
blur = Default(blur, 0)
for (i = 1, amount) {
mask = mask.mt_expand(chroma="-127")
}
if (blur > 0) {
if ( RiskyBlur == True) {
mask = mask.FastBlur(blur)
}
if ( RiskyBlur == False) {
mask = mask.GBlur(rad=6, sd=blur, u=true)
}
#mask = mask.FastBlur(blur)
#mask = mask.binomialBlur(varY=blur)
#~ mask = mask.GBlur(rad=6, sd=blur, u=true)
}
return mask
}
# RestoreGrain Function
###############################################################################################################################
###############################################################################################################################
Function mRD_RestoreGrain (clip rd, clip o, float "str1", float "str2") { #https://forum.doom9.org/showthread.php?p=1943371#post1943371
sisavs26 = !(VersionNumber() < 2.60)
str1 = string(default(str1, 10.0))
str2 = string(default(str2, 20.0))
expr = sisavs26 ? " x range_half - abs "+str1+" scalef min "+str2+" scalef x range_half - abs - min 0 max x range_half - x range_half - abs 1 max / * range_half + " : " x 128 - abs "+str1+" min "+str2+" x 128 - abs - min 0 max x 128 - x 128 - abs 1 max / * 128 + "
Return sisavs26 ? mt_makediff(o, rd).mt_lut(expr, use_expr=2).mt_adddiff(rd, chroma="copy second") : mt_makediff(o, rd).mt_lut(expr).mt_adddiff(rd, chroma="copy second")
}
# Spotless Function
###############################################################################################################################
###############################################################################################################################
# Version 1.07
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 = "SpotLess107: "
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)
# 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
}
#~ Prefetch(threads)
StainlessS
26th June 2021, 02:15
Test sequence [26secs, ~14MB]:- https://www.mediafire.com/file/v3tt00pidecdugz/AMOLAD.demuxed.m2v.7z/file
I have not re-tried, I might have used Spotless(RadT=1), rather than RadT=2.
To be fair, its quite hard to see the ball even without spotless.
chmars
26th June 2021, 12:39
Oh, sh1t! While cleaning the script to post it, I made an error in one variable name: Delta Restore has to compare sharpened original with sharpened spotlessed. Was: comparing unsharpened original with sharpened spotlessed. (source1_lvl_shp instead of source1_lvl). Must be: source1_lvl_shp
Corrected.
Noticed while trying to recall this torture ping-pong ball.
...Still not succeded btw....
chmars
26th June 2021, 13:55
:D
https://nmldqjct.preview.infomaniak.website/shared_files/index.php/s/eS8YU0gQIyMNJxZ/download (https://nmldqjct.preview.infomaniak.website/shared_files/index.php/s/IOW1Sl5ypvcNnPz/download)
luma_mask = LumaDeltaMask(source1_lvl_shp, source1_lvl_shp_spt, ">", brightness=1.01, spotSize=3).ExpandMask(3, blur=4)(brightness 1.01 instead of 1.1)
But:
I skipped edgeMask and dark_final. Would need more time to tweak:
delta_restore = DeltaRestore(source1_shp_spt, source1_shp, luma=luma_mask, chroma=suppressed_chroma_mask, chromaOverride=chroma_override)
instead of:
delta_restore = DeltaRestore(source1_shp_spt, source1_shp, luma=luma_mask, chroma=suppressed_chroma_mask, chromaOverride=chroma_override, edges=edgeMask, dark=dark_final)
Also, I didn't see spots/dirt as I know them from 8mm. So, I'didn't really understand what Spotless is intended to remove here. Some kind of noise maybe?
EDIT: New clip. Error before exporting. The "source" and "restored" parts of the stack were the same. see post #134
.
StainlessS
26th June 2021, 14:17
I had used Spotless on entire movie, spots/marks on film not necessarily on every frame or sequence.
It was only on eventual watching of the movie that I thought, "where the hell is the ball?".
[gettin' a bit sick of doing frame by frame hand adjustments].
EDIT: I spent about 2 years doing repeated little adjustments and fixes on one old movie, and
eventually took up coding again [after 14 year lapse] to do ExBlend(). After ExBlend, ended up before final encode doing 12 passes,
[to lossless each time] with each pass tweaking separate frames/sequences.
Spotless could have fixed many sections of that old movie, all on its own [after Exblend],
I dont want to spend anything like that amount of effort on a 90 mins movie, ever again,
life's just too short.
chmars
26th June 2021, 14:24
I see.
As much more expert than me, did you watch the Delta Restore result? Not bad, or?
StainlessS
26th June 2021, 14:46
As much more expert than me
Dont know bout that.
Not bad, or?
Yes, I've just now examined sample encode frame by frame, and find it hard to believe that Spotless was used on it.
Not bad indeed.
chmars
27th June 2021, 15:50
...
Yes, I've just now examined sample encode frame by frame, and find it hard to believe that Spotless was used on it.
...
Here we can see spotless has removed the ball and Delta Restore brought it back (2 and 3rd pic, near the woman's racket).
https://nmldqjct.preview.infomaniak.website/shared_files/index.php/s/ddubNMZSCa2cFst/download
Here (https://nmldqjct.preview.infomaniak.website/shared_files/index.php/s/vW2LtUOoLKBhcFV/download) is the exact script used.
Reading you, I has a doubt. Tested exact same script with a dirty clip....no differences :o :confused: : error at the end by resizing. Displayed twice the same clip.
But it was a relief to see that once corrected, I didn't dream: it wotks!
I replaced the clip I posted.
kedautinh12
2nd July 2021, 05:35
function RemoveDust(clip input, int "mode") {
mode = Default(mode, 17)
clensed = Clense(input)
rep = Repair(clensed, input, mode=mode)
RemoveGrain(rep, mode=10)
}
[/CODE]
function RemoveDust had duplicates of SpotLess_Mod and SpotRemover from your
Arx1meD
2nd July 2021, 09:06
kedautinh12, I added RemoveDust in post #114 (https://forum.doom9.org/showthread.php?p=1945133#post1945133) so as not to look for this function in other posts. If an error occurs, then remove the duplicate function.
kedautinh12
16th July 2021, 10:03
@zorr, I tested with megui but met error: "the script's return was not a video clip". Sr for noob ask Can apply script with Megui??
LoadPlugin("E:\MeGUI-2924-64\tools\ffms\ffms2.dll")
source=FFVideoSource("D:\Video encode\Da-iCE - DREAMIN' ON [1440x1080i MPEG2 SSTV HD].ts", fpsnum=30000, fpsden=1001, threads=1, colorspace="YUV420P8")
BlkSz = 24
OLap = (BlkSz/2) #important not too low (eg 4 for blksz 12, because creates stairs)
Pel = 1
Tm=false
Bblur = 0.6
ThSAD = 10000
RadT = 1
Usharp_strength= 80
Usharp_radius= 5
Usharp_th = 1 #too high = too many dark pixel packs (ex trees)
#Sharpen----------------------------------------------------------------------
source_shp=source.UnsharpMask_avsi(Usharp_strength,Usharp_radius,Usharp_th)
#SpotRemoved_shp=SpotRemoved.UnsharpMask_avsi(Usharp_strength,Usharp_radius,Usharp_th)
#----------------------------------------------------------------------------------
#Remove spots-----------------------------------------------------------------
spotless=SpotLess(source_shp,blksz=BlkSz,OLap=Olap,pel=pel,Tm=Tm,Bblur=Bblur,thsad=thsad,RadT=RadT)
#SpotRemoved = SpotRemover(sourceYV12,Spot=128, RadT=1, Pel=Pel, BlkSz=32, DeGrain=false, Chroma=true, PreFilter=true, mask=false, Tm = true,thsad=thsad, Glob = true)
#----------------------------------------------------------------------------------
# Delta Restore -------------------------------------------------------------
filtered = spotless
# extra clipping (if needed)
crop_left = 20
crop_right = 1120
#source = source.Crop(crop_left, 0, -crop_right, 0)
#filtered = filtered.Crop(crop_left, 0, -crop_right, 0)
# prepare masks --------------------------------------------------------------------
# luma mask - selects where source brightness > filtered brightness
luma_mask = LumaDeltaMask(source, filtered, ">", brightness=1.1, spotSize=3).ExpandMask(3, blur=4)
# chroma mask - selects where source and filtered chromas have large enough difference (formula: sqrt(u*u + v*v))
chroma_mask = ChromaDeltaMask(source, filtered, delta=3, spotSize=3)
# suppression mask - selects where source brightness < filtered brightness
# suppression mask removes too dark areas from chroma mask
suppression_mask = LumaDeltaMask(source, filtered, "<", brightness=0.9, spotSize=2)
suppressed_chroma_mask = chroma_mask.SuppressWith(suppression_mask)
# edge mask - detects sharp edges
# don't detect edges near the frame edges
edges = scharr(source).ZPadding(60, 14, 14, 10)
edgeMask = edges.mt_binarize(52)
# chroma override mask - selects larger color deltas and spot sizes than plain chroma mask and is not suppressed
chroma_override = ChromaDeltaMask(source, filtered, delta=5, spotSize=6).ExpandMask(3, blur=2)
# dark mask: select both darker and lighter mask and keep dark mask if it is near lighter mask or overlaps chroma override mask
dark = LumaDeltaMask(source, filtered, "<", brightness=0.97, limitBrightness=0.66, spotSize=5)
light = LumaDeltaMask(source, filtered, ">", brightness=1.04, spotSize=5).ZPadding(60, 14, 14, 10)
dark_near_light = dark.MustBeNear(light, 28).ExpandMask(3, blur=4)
expanded = dark.MustBeNear(light, 28, expanded=true) # for debugging
# secondary dark mask - doesn't need to be near lighter area but has stricter limits
dark2 = LumaDeltaMask(source, filtered, "<", brightness=0.97, limitBrightness=0.69, limitAbsBrightness=0.12, spotSize=5).ExpandMask(2, blur=2)
# edge test for dark areas
darkEdgeMask = edges.mt_binarize(54) # at least 54 needed for frame 222
darkEdgeAreaMask = mt_hysteresis(darkEdgeMask, dark_near_light.AddTo(dark2), chroma="-128") # for debug
dark_final = DarkDeltaMask(dark_near_light, secondary=dark2, edgeMask=darkEdgeMask)
delta_restore = DeltaRestore(filtered, source, luma=luma_mask, chroma=suppressed_chroma_mask, chromaOverride=chroma_override, edges=edgeMask, dark=dark_final)
StainlessS
16th July 2021, 13:22
kedautinh12,
What post is that supposed to be from ? [There is obviously some script missing at the end].
EDIT: Maybe you forgot to add/append posts following the first post of a multi-post script.
kedautinh12
16th July 2021, 13:45
I deleted scripts related mask only for check or debug view
Edit: i copy from post
https://forum.doom9.org/showthread.php?p=1945212#post1945212
StainlessS
16th July 2021, 16:24
No idea if this will work [I aint been following development of that script],
but at end maybe try
dark_final = DarkDeltaMask(dark_near_light, secondary=dark2, edgeMask=darkEdgeMask)
delta_restore = DeltaRestore(filtered, source, luma=luma_mask, chroma=suppressed_chroma_mask, chromaOverride=chroma_override, edges=edgeMask, dark=dark_final)
Return delta_restore # Added
Dont know if that is the result clip or not. [If not then have to wait for young master Zorr to appear].
EDIT: I presume that other script function code is imported somewhere else (or in Plugins as Avsi).
EDIT: If additional script function code not imported elsewhere, then can just use Zorr original script,
with that Return delta_restore line added at that same position, the extra mask and debug code will not be used.
And to view masks or debug stuff, just comment out the Return delta_restore line. [maybe Zorr accidently removed that Return line]
kedautinh12
16th July 2021, 16:47
Yes, i copy this functions to one script .avsi and put in autoload folder
function DeltaRestore(clip filtered, clip source, clip "luma", clip "chroma", clip "chromaOverride", clip "edges", clip "dark") {
Assert(luma.Defined || chroma.Defined || chromaOverride.Defined || dark.Defined, "DeltaRestore:must define at least one of luma, chroma, chromaOverride, dark")
mask = Default(luma, BlankClip(filtered))
if (chroma.Defined) {
mask = mask.AddTo(chroma)
}
if (chromaOverride.Defined) {
mask = mask.AddTo(chromaOverride)
}
if (edges.Defined) {
mask = mask.MustNotOverlap(edges)
}
if (dark.Defined) {
mask = mask.AddTo(dark)
}
return Overlay(filtered, source, mask=mask)
}
function DarkDeltaMask(clip mask, clip "secondary", clip "edgeMask") {
if (secondary.Defined) {
mask = mask.AddTo(secondary)
}
if (edgeMask.Defined) {
mask = mask.MustNotOverlap(edgeMask)
}
return mask
}
function AddTo(clip mask, clip other) {
return mt_logic(mask, other, "or")
}
function SuppressWith(clip mask, clip suppressionMask) {
return mt_lutxy(mask, suppressionMask, expr="x y -")
}
function MustNotOverlap(clip mask, clip other) {
other = mt_hysteresis(other, mask, chroma="-128")
return mt_lutxy(mask, other, expr="x y -")
}
function MustOverlap(clip mask, clip otherMask) {
return mt_logic(mask, mt_hysteresis(otherMask, mask), "and")
}
function MustBeNear(clip mask, clip otherMask, int maxDistance, bool "expanded") {
expanded = Default(expanded, false)
expand_amount = maxDistance / 2
mask_expanded = ExpandMask(mask, expand_amount)
other_expanded = ExpandMask(otherMask, maxDistance - expand_amount)
common = mt_logic(mask_expanded, other_expanded, "or")
if (expanded) {
return common
}
other_near = mt_hysteresis(otherMask, common)
return mt_logic(mask, other_near, "and")
}
function ZPadding(clip c, int left, int top, int right, int bottom) {
return c.Crop(left, top, -right, -bottom).AddBorders(left, top, right, bottom, color_yuv=$008080)
}
function scharr(clip c) {
scharr_x = c.mt_edge("3 0 -3 10 0 -10 3 0 -3", thY1 = 0, thY2 = 255, y=3, u=1, v=1)
scharr_y = c.mt_edge("3 10 3 0 0 0 -3 -10 -3", thY1 = 0, thY2 = 255, y=3, u=1, v=1)
scharr = mt_lutxy(scharr_x, scharr_y, yexpr=mt_polish("((x*x)+(y*y))^0.5"), chroma="-127")
return scharr
}
function RemoveSmallSpots(clip mask, int spotSize) {
mask_orig = mask
# remove too small spots
for (i = 1, spotSize) {
mask = mask.mt_inpand()
}
mask = mt_hysteresis(mask, mask_orig, chroma="-127")
return mask
}
function LumaDeltaMask(clip source, clip filtered, string direction, float "brightness", float "limitBrightness", float "limitAbsBrightness", int "spotSize") {
# direction is ">" if you want mask to contain areas where source is brighter than filtered by "brightness", direction "<" selects where source is darker
# adjust brightness to change how bright objects are selected, typical values between 1.0 and 1.1, larger value = brighter object
# remove too bright or dark areas using limitBrightness
# spotSize controls the smallest spot size allowed in the mask, typical values between 0 and 3
brightness = Default(brightness, 0.9)
spotSize = Default(spotSize, 2)
limitBrightness = Default(limitBrightness, brightness)
limitAbsBrightness = Default(limitAbsBrightness, 0.0)
# diff
diff = Grayscale(mt_makediff(source, filtered))
# mask
mask = mt_lut(diff, expr="x range_half "+String(brightness)+" * " + direction + " 255 scalef 0 ?")
mask_orig = mask
# limitBrightness suppression
if (limitBrightness != brightness) {
mask_suppression = mt_lut(diff, expr="x range_half "+String(limitBrightness)+" * " + direction + " 255 scalef 0 ?")
mask = mt_lutxy(mask, mask_suppression, expr="x y -")
}
# limitAbsBrightness suppression
if (limitAbsBrightness > 0.0) {
mask = mt_lutxy(mask, source, expr="y range_max "+String(limitAbsBrightness)+" * < 0 x ?")
}
# remove too small spots
mask = RemoveSmallSpots(mask, spotSize)
# limitBrightness or limitAbsBrightness suppression (part 2)
if (limitBrightness != brightness || limitAbsBrightness > 0.0) {
# if suppression didn't kill the whole mask then return it without suppression
mask = mt_hysteresis(mask, mask_orig, chroma="-127")
}
return mask
}
function ChromaDeltaMask(clip source, clip filtered, int "delta", int "spotSize") {
# adjust delta to change how different colors are selected
# spotSize controls the smallest spot size allowed in the mask, typical values between 0 and 3
delta = Default(delta, 3)
spotSize = Default(spotSize, 2)
# diff
diff = mt_makediff(source, filtered, y=3, u=3, v=3)
# extract chroma channels
u = ExtractU(diff)
v = ExtractV(diff)
# calculate deltas
u_delta = mt_lut(u, expr="x range_half - abs")
v_delta = mt_lut(v, expr="x range_half - abs")
# combine deltas
# uv_delta = mt_lutxy(u_delta, v_delta, expr="x y +")
uv_delta = mt_lutxy(u_delta, v_delta, expr="x x * y y * + 0.5 ^")
# rescale (if needed)
if (uv_delta.width < source.width || uv_delta.height < source.height) {
uv_delta = uv_delta.BilinearResize(source.width, source.height)
}
# create mask
mask = uv_delta.mt_binarize(delta)
# remove too small spots
mask = RemoveSmallSpots(mask, spotSize)
# restore original format
blank = BlankClip(source)
mask = CombinePlanes(mask, blank, blank, planes="YUV", source_planes="YUV", sample_clip=source)
return mask
}
function ExpandMask(clip mask, int amount, float "blur") {
blur = Default(blur, 0)
for (i = 1, amount) {
mask = mask.mt_expand(chroma="-127")
}
if (blur > 0) {
#mask = mask.FastBlur(blur)
#mask = mask.binomialBlur(varY=blur)
mask = mask.GBlur(rad=6, sd=blur, u=true)
}
return mask
}
StainlessS
16th July 2021, 18:15
works OK now ? [with that return line]
zorr
16th July 2021, 20:25
Do not worry mortals, young master Zorr is here! :D
I deleted scripts related mask only for check or debug view
Edit: i copy from post
https://forum.doom9.org/showthread.php?p=1945212#post1945212
The post you quoted contains a "return stack" line. It returns the source, Spotless-filtered version, delta restored version, restore + mask, mask visualization, edge mask and diff stacked horizontally. If you just want the final result of Delta Restore then what his holiness StainlessS told works fine, namely "return delta_restore".
coolgit
8th September 2021, 00:17
Thanks for the delta restore. In addition to spotless it has been a blessing. Recently i had to extract good frames to replace bad spotless frames when there is motion close to the camera.
In some cases when hands/fingers disappear at medium distance from camera but everything else doesn't.
However there is one drawback. A spot in the hand wasn't removed. How can i use delta restore minus the spot. Obviously white spot = flicker.
My codes.
LoadPlugin("C:\Program Files (x86)\AviSynth\plugins+\mvtools2.dll")
LoadPlugin("C:\Program Files (x86)\AviSynth\plugins+\MedianBlur2.dll")
source=FFVideoSource("E:\currser\BABYLON5\new\12 By Any Means Necessary\By Any Means Necessary.avi").convertTOYV12(interlaced=false).killaudio()
BlkSz = 16
OLap = (BlkSz/2) #important not too low (eg 4 for blksz 12, because creates stairs)
Pel = 2
Tm=true
Bblur = 0.0
ThSAD = 4000
RadT = 1
#Remove spots
SpotLess1=SpotLess(source,blksz=BlkSz,OLap=Olap,pel=pel,Tm=Tm,Bblur=Bblur,thsad=thsad,RadT=RadT)
# Delta Restore
src = source
filtered = SpotLess1
# diff with exaggerated chroma difference
diff1 = mt_makediff(src, filtered, y=3, u=3, v=3)
diff2 = diff1.mt_adddiff(diff1, y=0, u=3, v=3)
diff2 = diff2.mt_adddiff(diff1, y=0, u=3, v=3)
diff = CombinePlanes(diff1, diff2, diff2, planes="YUV", source_planes="YUV", sample_clip=diff1)
# restore
delta_restore = Overlay(filtered, src)
Function mRD_RestoreGrain (clip rd, clip o, float "str1", float "str2") {
sisavs26 = !(VersionNumber() < 2.60)
str1 = string(default(str1, 10.0))
str2 = string(default(str2, 5.0))
expr = sisavs26 ? " x range_half - abs "+str1+" scalef min "+str2+" scalef x range_half - abs - min 0 max x range_half - x range_half - abs 1 max / * range_half + " : " x 128 - abs "+str1+" min "+str2+" x 128 - abs - min 0 max x 128 - x 128 - abs 1 max / * 128 + "
Return sisavs26 ? mt_makediff(o, rd).mt_lut(expr, use_expr=2).mt_adddiff(rd, chroma="copy second") : mt_makediff(o, rd).mt_lut(expr).mt_adddiff(rd, chroma="copy second")
}
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,4000) # 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.0) # 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(source)
StackHorizontal(source,output,delta_restore) # stack side by side, source left, output right
#return output
#return delta_restore
I am simply using delta restore to restore missing bits and not grains etc.
My processes are:
1 deinterlace vob file using staxrip qtgmc - f1
2 f1 - staxrip crop, BlackmanResize(720, 576), MDegrain3, TemporalDegrain2 - f2 (no noise/grain and clean source)
3 f2 - spotless (works better after doing 2 above and quicker) - fspotless
4 crosscheck fspotless and f2 using any frames to replace bad fspotless frames.
zorr
8th September 2021, 22:21
Thanks for the delta restore. In addition to spotless it has been a blessing.
You're welcome.
However there is one drawback. A spot in the hand wasn't removed. How can i use delta restore minus the spot. Obviously white spot = flicker.
That's a tough situation, Delta Restore has restored a large piece of the hand and the spot is right on top of that area. That means there is no good frame to replace that spot with.
By the way are you sure that spot is something you need to remove? Even the original frame looks very clean to me except for that one dot so it might be some kind of reflection. Is that spot only in this one frame?
In order to better analyze what (if anything) can be done you should post the mask visualization output, like in this post (https://forum.doom9.org/showthread.php?p=1944708#post1944708).
coolgit
9th September 2021, 08:55
Made some changes in the script and fixed the spot in the hand. New image.
However delta restore failed in this image. Did not restore the finger and i am assuming it is because the source finger was too bright.
Can someone check if the script is correct. Not sure about the LoadPlugin. If i want to output a new file then i should use "return delta_restore", line 364 ish? Thanks.
LoadPlugin("C:\Program Files (x86)\AviSynth\plugins+\mvtools2.dll")
LoadPlugin("C:\Program Files (x86)\AviSynth\plugins+\MedianBlur2.dll")
LoadPlugin("C:\Program Files (x86)\AviSynth\plugins+\masktools2.dll")
LoadPlugin("C:\Program Files (x86)\AviSynth\plugins+\manyPlus.dll")
source=FFVideoSource("E:\currser\BABYLON5\new\12 By Any Means Necessary\By Any Means Necessary.avi").convertTOYV12(interlaced=false).killaudio()
#source= FFVideoSource("E:\currser\BABYLON5\new\11 Survivors\Survivors.avi").convertTOYV12(interlaced=false).killaudio()
txt_sz = 24
BlkSz = 12
OLap = (BlkSz/2) #important not too low (eg 4 for blksz 12, because creates stairs)
Pel = 2
Tm=false
Bblur = 0.0
ThSAD = 6000
RadT = 1
Usharp_strength= 80
Usharp_radius= 5
Usharp_th = 1 #too high = too many dark pixel packs (ex trees)
#Sharpen----------------------------------------------------------------------
#source_shp=source.UnsharpMask_avsi(Usharp_strength,Usharp_radius,Usharp_th)
#Remove spots-----------------------------------------------------------------
#SpotLess=SpotLess(source_shp,blksz=BlkSz,OLap=Olap,pel=pel,Tm=Tm,Bblur=Bblur,thsad=thsad,RadT=RadT)
SpotLess=SpotLess(source,blksz=BlkSz,OLap=Olap,pel=pel,Tm=Tm,Bblur=Bblur,thsad=thsad,RadT=RadT)
# Delta Restore -------------------------------------------------------------
src = source
filtered = SpotLess
# diff with exaggerated chroma difference
diff1 = mt_makediff(src, filtered, y=3, u=3, v=3)
diff2 = diff1.mt_adddiff(diff1, y=0, u=3, v=3)
diff2 = diff2.mt_adddiff(diff1, y=0, u=3, v=3)
diff = CombinePlanes(diff1, diff2, diff2, planes="YUV", source_planes="YUV", sample_clip=diff1)
# prepare masks --------------------------------------------------------------------
# luma mask - selects where source brightness > filtered brightness
luma_mask = LumaDeltaMask(src, filtered, ">", brightness=1.1, spotSize=3)
luma_mask = ExpandMask(luma_mask, 3, blur=4)
# chroma mask - selects where source and filtered chromas have large enough difference (formula: sqrt(u*u + v*v))
chroma_mask = ChromaDeltaMask(src, filtered, delta=3, spotSize=3)
# suppression mask - selects where source brightness < filtered brightness
suppression_mask = LumaDeltaMask(src, filtered, "<", brightness=0.9, spotSize=2)
# suppression mask removes too dark areas from chroma mask
suppressed_chroma_mask = mt_lutxy(chroma_mask, suppression_mask, expr="x y -")
# edge mask - detects sharp edges
edges = scharr(src)
# don't detect edges near the frame edges
edges = edges.ZPadding(60, 14, 14, 10)
# chroma override mask - selects larger color deltas and spot sizes than plain chroma mask and is not suppressed
chroma_override = ChromaDeltaMask(src, filtered, delta=5, spotSize=6) # orig. spotSize 6
chroma_override = ExpandMask(chroma_override, 3, blur=2)
# new approach for dark mask:
# select both darker and lighter mask and keep dark mask if it is near lighter mask or overlaps chroma override mask
dark = LumaDeltaMask(src, filtered, "<", brightness=0.97, limitBrightness=0.66, spotSize=5)
light = LumaDeltaMask(src, filtered, ">", brightness=1.04, spotSize=5)
light = light.ZPadding(60, 14, 14, 10)
dark_near_light = isNearMask(dark, light, 28)
expanded = IsNearMask(dark, light, 28, expanded=true) # for debugging
dark_final = ExpandMask(dark_near_light, 3, blur=4)
# secondary dark mask - doesn't need to be near lighter area but has stricter limits
dark2 = LumaDeltaMask(src, filtered, "<", brightness=0.97, limitBrightness=0.69, limitAbsBrightness=0.12, spotSize=5)
dark2 = ExpandMask(dark2, 2, blur=2)
dark_final = mt_logic(dark_final, dark2, "or")
# edge test for dark areas
darkEdgeMask = edges.mt_binarize(54) # at least 54 needed for frame 222
darkEdgeAreaMask = mt_hysteresis(darkEdgeMask, dark_final, chroma="-127")
dark_final = mt_lutxy(dark_final, darkEdgeAreaMask, expr="x y -")
# combine luma, chroma and chroma override masks
combined_mask = mt_logic(luma_mask, suppressed_chroma_mask, "or")
combined_mask = mt_logic(combined_mask, chroma_override, "or")
# remove mask areas where sharp edges were found (those are most likely dirt)
edgeMask = edges.mt_binarize(52)
edgeAreaMask = mt_hysteresis(edgeMask, combined_mask, chroma="-127")
combined_mask = mt_lutxy(combined_mask, edgeAreaMask, expr="x y -")
# add dark areas (no edge test for those)
combined_mask = mt_logic(combined_mask, dark_final, "or")
# restore ---------------------------------------------------------------------------
delta_restore = overlay(filtered, src, mask=combined_mask)
edgeAreaMask = mt_logic(edgeAreaMask, darkEdgeAreaMask, "or")
#edgeAreaMask = darkEdgeAreaMask
# create mask visualization (for debugging) -----------------------------------------
black = BlankClip(src)
yellow = BlankClip(src, color=$d1cc2e) # #d1cc2e f7f140
green = BlankClip(src, color=$40f75b)
gray = BlankClip(src, color=$d0d0d0)
darkgray = BlankClip(src, color=$909090)
orange = BlankClip(src, color=$a36b02)
purple = BlankClip(src, color=$4e0091)
purple_light = BlankClip(src, color=$bc6eff)
purple_dark = BlankClip(src, color=$340061)
red = BlankClip(src, color=$9e2424)
blue = BlankClip(src, color=$0d3b7a) #24579e 243c9e
# maskviz for dark mask
maskviz = overlay(black, purple, mask=expanded)
maskviz = overlay(maskviz, purple_dark, mask=dark)
maskviz = overlay(maskviz, purple_light, mask=light)
maskviz = overlay(maskviz, black, mask=dark_near_light)
# only show dark mask?
showDarkMaskOnly = false
if (!showDarkMaskOnly) {
maskviz = overlay(maskviz, yellow, mask=chroma_mask) # chroma mask - yellow
maskviz = overlay(maskviz, red, mask=suppression_mask, opacity=0.7) # suppression mask - red
maskviz = overlay(maskviz, green, mask=chroma_override) # chroma override - green
maskviz = overlay(maskviz, gray, mask=luma_mask) # luma mask - gray
maskviz = overlay(maskviz, blue, mask=edgeAreaMask, opacity=0.8) # sharpness mask - blue
}
# mask overlay
comp = overlay(delta_restore, maskviz, opacity=0.45)
comp = overlay(comp, purple_light, mask=light, opacity=0.2)
comp = overlay(comp, black, mask=dark, opacity=0.2)
comp = overlay(comp, black, mask=dark_final, opacity=0.6)
maskviz = overlay(maskviz, black, mask=dark_final, opacity=0.6)
#return Interleave(StackHorizontal(src, comp), StackHorizontal(delta_restore, maskviz))
#return StackHorizontal(src, filtered, delta_restore, edgeMask, maskviz)
#------------------------------------------------------------------------------------------------------------------
function sub(c, label, txt_sz) {
return subtitle(c, label, size = txt_sz, align=8)
}
function OverlapsMask(clip mask, clip otherMask) {
return mt_logic(mask, mt_hysteresis(otherMask, mask), "and")
}
function IsNearMask(clip mask, clip otherMask, int maxDistance, bool "expanded") {
expanded = Default(expanded, false)
expand_amount = maxDistance / 2
mask_expanded = ExpandMask(mask, expand_amount)
other_expanded = ExpandMask(otherMask, maxDistance - expand_amount)
common = mt_logic(mask_expanded, other_expanded, "or")
if (expanded) {
return common
}
other_near = mt_hysteresis(otherMask, common)
return mt_logic(mask, other_near, "and")
}
function ZPadding(clip c, int left, int top, int right, int bottom) {
return c.Crop(left, top, -right, -bottom).AddBorders(left, top, right, bottom, color_yuv=$008080)
}
function scharr(clip c) {
scharr_x = c.mt_edge("3 0 -3 10 0 -10 3 0 -3", thY1 = 0, thY2 = 255, y=3, u=1, v=1)
scharr_y = c.mt_edge("3 10 3 0 0 0 -3 -10 -3", thY1 = 0, thY2 = 255, y=3, u=1, v=1)
scharr = mt_lutxy(scharr_x, scharr_y, yexpr=mt_polish("((x*x)+(y*y))^0.5"), chroma="-127")
return scharr
}
function RemoveSmallSpots(clip mask, int spotSize) {
mask_orig = mask
# remove too small spots ----------------------------------------------------
for (i = 1, spotSize) {
mask = mask.mt_inpand()
}
mask = mt_hysteresis(mask, mask_orig, chroma="-127")
return mask
}
function LumaDeltaMask(clip source, clip filtered, string direction, float "brightness", float "limitBrightness", float "limitAbsBrightness", int "spotSize") {
# direction is ">" if you want mask to contain areas where source is brighter than filtered by "brightness", direction "<" selects where source is darker
# adjust brightness to change how bright objects are selected, typical values between 1.0 and 1.1, larger value = brighter object
# remove too bright or dark areas using limitBrightness
# spotSize controls the smallest spot size allowed in the mask, typical values between 0 and 3
brightness = Default(brightness, 0.9)
spotSize = Default(spotSize, 2)
limitBrightness = Default(limitBrightness, brightness)
limitAbsBrightness = Default(limitAbsBrightness, 0.0)
# diff
diff = Grayscale(mt_makediff(source, filtered))
# mask
mask = mt_lut(diff, expr="x range_half "+String(brightness)+" * " + direction + " 255 scalef 0 ?")
mask_orig = mask
# limitBrightness suppression
if (limitBrightness != brightness) {
mask_suppression = mt_lut(diff, expr="x range_half "+String(limitBrightness)+" * " + direction + " 255 scalef 0 ?")
mask = mt_lutxy(mask, mask_suppression, expr="x y -")
}
# limitAbsBrightness suppression
if (limitAbsBrightness > 0.0) {
mask = mt_lutxy(mask, source, expr="y range_max "+String(limitAbsBrightness)+" * < 0 x ?")
}
# remove too small spots
mask = RemoveSmallSpots(mask, spotSize)
# limitBrightness or limitAbsBrightness suppression (part 2)
if (limitBrightness != brightness || limitAbsBrightness > 0.0) {
# if suppression didn't kill the whole mask then return it without suppression
mask = mt_hysteresis(mask, mask_orig, chroma="-127")
}
return mask
}
function ChromaDeltaMask(clip source, clip filtered, int "delta", int "spotSize") {
# adjust delta to change how different colors are selected
# spotSize controls the smallest spot size allowed in the mask, typical values between 0 and 3
delta = Default(delta, 3)
spotSize = Default(spotSize, 2)
# diff ----------------------------------------------------------------------
diff = mt_makediff(source, filtered, y=3, u=3, v=3)
# extract chroma channels
u = ExtractU(diff)
v = ExtractV(diff)
# calculate deltas
u_delta = mt_lut(u, expr="x range_half - abs")
v_delta = mt_lut(v, expr="x range_half - abs")
# combine deltas
# uv_delta = mt_lutxy(u_delta, v_delta, expr="x y +")
uv_delta = mt_lutxy(u_delta, v_delta, expr="x x * y y * + 0.5 ^")
# rescale (if needed)
if (uv_delta.width < source.width || uv_delta.height < source.height) {
uv_delta = uv_delta.BilinearResize(source.width, source.height)
}
# create mask
mask = uv_delta.mt_binarize(delta)
# remove too small spots
mask = RemoveSmallSpots(mask, spotSize)
# restore original format
blank = BlankClip(source)
mask = CombinePlanes(mask, blank, blank, planes="YUV", source_planes="YUV", sample_clip=source)
return mask
}
function ExpandMask(clip mask, int amount, float "blur") {
blur = Default(blur, 0)
for (i = 1, amount) {
mask = mask.mt_expand(chroma="-127")
}
if (blur > 0) {
#mask = mask.FastBlur(blur)
#mask = mask.binomialBlur(varY=blur)
# mask = mask.ConvertBits(16)
mask = mask.GBlur(rad=6, sd=blur, u=true)
# mask = mask.ConvertBits(10)
}
return mask
}
Function mRD_RestoreGrain (clip rd, clip o, float "str1", float "str2") {
sisavs26 = !(VersionNumber() < 2.60)
str1 = string(default(str1, 10.0))
str2 = string(default(str2, 5.0))
expr = sisavs26 ? " x range_half - abs "+str1+" scalef min "+str2+" scalef x range_half - abs - min 0 max x range_half - x range_half - abs 1 max / * range_half + " : " x 128 - abs "+str1+" min "+str2+" x 128 - abs - min 0 max x 128 - x 128 - abs 1 max / * 128 + "
Return sisavs26 ? mt_makediff(o, rd).mt_lut(expr, use_expr=2).mt_adddiff(rd, chroma="copy second") : mt_makediff(o, rd).mt_lut(expr).mt_adddiff(rd, chroma="copy second")
}
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,12) # 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,false) # 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.0) # 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)
#StackHorizontal(src,output,delta_restore) # stack side by side, source left, output right
#return output
#return delta_restore
#Debug------------------------------
#StackHorizontal(\
#sub(src,"Source",size = txt_sz),\
#sub(filtered,"Spotless",size = txt_sz))
#StackHorizontal(\
#sub(src,"Source",size = txt_sz),\
#sub(filtered,"Spotless",size = txt_sz),\
#sub(delta_restore,"Delta Restore",size = txt_sz))
StackHorizontal(\
sub(src, "Source", txt_sz),\
sub(filtered, "Spotless", txt_sz),\
sub(delta_restore, "Delta Restore", txt_sz),\
sub(comp, "Restore + Mask", txt_sz),\
sub(maskviz, "Masks Used", txt_sz),\
sub(edgeMask, "Edge Mask", txt_sz),\
sub(diff, "Diff", txt_sz))
#inter = Interleave(\
#sub(src, "source", txt_sz), \
#sub(filtered, "Spotless", txt_sz), \
#sub(delta_restore, "Delta Restore", txt_sz), \
#sub(comp, "restore + mask", txt_sz), \
#sub(maskviz, "masks used", txt_sz),\
#sub(diff, "diff", txt_sz)\
#)
#return inter
Prefetch()
StainlessS
9th September 2021, 14:21
Coolgit,
Zorr,
I note that in a number of posts in spotless related thread
eg here:- https://forum.doom9.org/showthread.php?p=1944487#post1944487
function RemoveSmallSpots(clip mask, int spotSize) {
mask_orig = mask
# remove too small spots ----------------------------------------------------
for (i = 1, spotSize) {
mask = mask.mt_inpand()
}
mask = mt_hysteresis(mask, mask_orig, chroma="-127")
return mask
}
think maybe above chroma="-127" should be chroma="-128",
Appears quite a few times in the linked thread.
From Masktools 2 original docs.
EDIT: I recently found a script posted some time ago that now throws an error in recent Pinterf MaskTools, I had wrongly used
chroma="128" [instead of chroma="-128"]
and it now throws a wobbly, says should be setting chroma to -ve ie -128 [although original script did seem to work OK in older masktools].
Yes you're right. I'll fix it when I release the "official" Delta Restore version. It doesn't have any effect on the final result so it's nothing to worry about. :)
so maybe change -127 to -128.
coolgit
9th September 2021, 19:03
Thanks for that. There are other lines with chroma="-127" so do i leave it as is?
I am going to do the nest episode and hopefully this delta restore will reduce the amount of time spent of recovering bad spotless frames.
StainlessS
9th September 2021, 19:35
Nope dont leave as is, all -127 are wrong.
coolgit
10th September 2021, 12:28
A minor flaw. Delta restore output is brighter than source. The face skin tone became lighter.
Using this took 1 hr 35 minutes for 40 minutes pal sd video at 11 fps. Seems too slow or I am doing something wrong.
Any solution?
vBulletin® v3.8.11, Copyright ©2000-2026, vBulletin Solutions Inc.