从Avisynth到VapourSynth:一些方法的迁移

VapourSynth经过四十多个版本的更新,滤镜已经丰富。但Avisynth时代留下了诸多的教程,似乎仍是VapourSynth无法比拟的。

因此,我试着将前辈们一些用avs写的教程迁移到vs上,希望能够给大家做一点微小的贡献。

有什么错误的地方还请大家指正~

默认import如下内容。

1
2
3
4
5
import vapoursynth as vs
core = vs.get_core()

import mvsfunc as mvf
import havsfunc as haf

NR de-banding思路

avs脚本出处:https://www.nmm-hd.org/newbbs/viewtopic.php?f=7&t=1495#p12163 作者:mawen1250

NR de-banding(去色带)思路,可以降低去色带的力度、提升效果。基本的思路是先扫去噪点层,再做去色带,做完后再把噪点层加回来。

其中NR指noise reduction,降噪的意思,很多处理都可以使用降噪做预处理,以提升效果。

avs脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# stacked-16bit input
last = last # this line can be ignored, just in case someone who doesn't understand it

# pre-filter
nr16 = last.Dither_removegrain16(11, 11).Dither_removegrain16(20, 0)
# get noise diff clip
noise = last.Dither_sub16(nr16, y=3, u=3, v=3, dif=True)
# refine pre-filtered clip due to limited range of diff clip: [-32768, 32767], avoiding potential issue that the noise can not be fully added back
nr16 = last.Dither_sub16(noise, y=3, u=3, v=3, dif=True)
# de-banding on pre-filtered clip
last = nr16
last = last.f3kdb(16, 40, 40, 40, 0, 0, input_mode=1, output_mode=1).Dither_limit_dif16(last, ref=nr16, thr=0.30, elast=2.5, y=3, u=3, v=3)
# add back noise
last = last.Dither_add16(noise, y=3, u=3, v=3, dif=True)

# stacked-16bit output
last

vs脚本

1
2
3
4
5
6
7
8
9
10
11
12
# 16bit input: src16

# pre-filter
nr16 = core.rgvs.RemoveGrain(src16, [11,11]).rgvs.RemoveGrain([20,0])
# get noise diff clip
noise = core.std.MakeDiff(src16, nr16, planes=[0,1,2]) # By default all planes are processed. So the parameter for planes can be ignored.
nr16 = core.std.MakeDiff(src16, noise, planes=[0,1,2])
# de-banding on pre-filtered clip
deband = core.f3kdb.Deband(nr16, 16,40,40,40,0,0, output_depth=16)
deband = mvf.LimitFilter(deband, nr16, thr=0.30, elast=2.5, planes=[0,1,2])
# add back noise
deband = core.std.MergeDiff(deband, noise, planes=[0,1,2])

在此基础上更复杂一点的用法。做完de-banding后,不把噪点全部加回来,而是对噪点层降噪。这就是SBR降噪的思路。

当然,如果是用KNLMeansCL这类降噪滤镜扫噪点层,在参数不太高的情况下,本身对画面的破坏就已经很小。如果想做降噪,可以不把噪点层加回来。

Chroma Shift的处理

avs脚本出处:https://www.nmm-hd.org/newbbs/viewtopic.php?f=7&t=1483&p=12144#p12144 作者:mawen1250

Chroma Shift(色度偏移)的处理。关于色度偏移,可以看一下mawen1250前辈的这个帖子和EleonoreMizo前辈为fmtconv写的Docresample函数的部分。

avs脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Parameters for chroma shift, example for converting MPEG-1 chroma placement to MPEG-2 chroma placement
U_Horizontal_Shift = -0.25
U_Vertical_Shift = 0
V_Horizontal_Shift = -0.25
V_Vertical_Shift = 0

# 8bit YUV input

# Separate planes
Y = last
U = UToY8()
V = VToY8()

# Use resizer to achieve sub-pixel precision shift for each plane
U = U.Spline16Resize(U.Width(), U.Height(), U_Horizontal_Shift, U_Vertical_Shift)
V = V.Spline16Resize(V.Width(), V.Height(), V_Horizontal_Shift, V_Vertical_Shift)

# Merge planes back to YUV format
YToUV(U, V, Y)

vs脚本

1
2
3
4
5
6
7
8
9
10
# Parameters for chroma shift, example for converting MPEG-1 chroma placement to MPEG-2 chroma placement
U_Horizontal_Shift = -0.25
U_Vertical_Shift = 0
V_Horizontal_Shift = -0.25
V_Vertical_Shift = 0

# YUV input: src

# Use resizer to achieve sub-pixel precision shift for each plane
result = core.fmtc.resample(src, src.width, src.height, sx=[0, U_Horizontal_Shift, V_Horizontal_Shift], sy=[0, U_Vertical_Shift, V_Vertical_Shift], planes=[2,3,3])

Chroma Bleeding的处理

avs脚本出处:https://www.nmm-hd.org/newbbs/viewtopic.php?f=7&t=1587&p=12668#p12651 作者:mawen1250

Chroma Bleeding(色度溢出)的处理。详情可以点开上面的链接看一下原帖,mawen1250前辈做了说明。主要是用了AWarpSharp2系列的滤镜,以亮度平面为mask,将色度平面中溢出的部分砍掉。这也很好理解,多出来的就砍掉嘛~

顺带一提,收线也可用这个滤镜。道理类似,“收”就是砍的嘛~

avs脚本

脚本是8bit YUV420输入,先升至16bit YUV444,再降至8bit YUV444处理色度溢出,继而升至16bit YUV444与未修正色度溢出的16bit YUV444源比较,最后保持16bit YUV444输出。

(mawen1250前辈的教程写于2015年2月,avs版AWarpSharp2在2018年3月更新的v2.0.0版中添加了16bit支持,上述反复升降位深的做法已不必要)

1
2
3
4
5
6
7
8
9
10
11
# YUV420P8 input
nnedi3_resize16(lsb_in=False, lsb=True, output="YV24", ratiothr=2.25, kernel_u="Bicubic", a1=0.6, a2=0.4)

src16 = last
DitherPost(mode=-1)

warpe = ConvertToYV12().aSobel(128, 1).aBlur(1, 1, 1)
epp = YToUV(UToY().awarp(warpe, 6, 1), VToY().awarp(warpe, 6, 1), last)

Dither_limit_dif16(src16, epp.U16(), thr=1.0, elast=2, y=2, u=3, v=3)
# YUV444P16 output

vs脚本

这里使用了dubhater前辈的AWarpSharp2移植,由于支持高位深,所以全程在16bit下处理。

在脚本的最后用mvf.LimitFilter做了限制,这是照抄mawen1250前辈的avs脚本。但avs脚本做限制是为了保留未处理部分的16bit精度(如前面所说,在avs版AWarpSharp2支持16bit前,不得不反复升降位深,这会带来精度的损失),在全程16bit处理的情况下,这个限制是否有必要,值得思考。唔..我太笨了,就是这样

1
2
3
4
5
6
7
8
9
10
11
# YUV420P8 input: src
src16 = mvf.Depth(src, 16)
src16 = core.fmtc.resample(src16, src.width, src.height, kernel='bicubic', a1=0.6, a2=0.4, css='444') # Y plane isn't processed

warpe = mvf.GetPlane(core.warp.ASobel(src16, 128, 1).warp.ABlur(1, 1, 1), plane=0)
fixU = core.warp.AWarp(mvf.GetPlane(src16, plane=1), warpe, depth=6, chroma=0)
fixV = core.warp.AWarp(mvf.GetPlane(src16, plane=2), warpe, depth=6, chroma=0)
epp = core.std.ShufflePlanes([src16, fixU, fixV], planes=[0,0,0], colorfamily=vs.YUV)

result = mvf.LimitFilter(src16, epp, thr=1.0, elast=2.0, planes=[1,2])
# YUV444P16 output

如果不想保持16bit YUV444输出,而是输出一般的16bit YUV420,则在进行上述处理后,再用fmtc.resample缩放即可。降低分辨率通常推荐用Spline(样条采样)系列中的spline36fmtc.resample的默认参数就是它。

1
result = core.fmtc.resample(result, src.width, src.height, kernel='spline36', css='420')

当然,如果是以16bit YUV420输出,将mvf.LimitFilter的限制放到缩放后,将未拉伸色度平面的输入源与降回16bit YUV420的处理结果比较,可能会更好。因为这样多少限制了色度平面拉伸与缩小过程的损失。(我口胡的,不要打我呀~

后记

本来想打两个波浪线买一下萌,忘了markdown语法,结果变成了下面那个样子,太真实了。

1
(未完待续~~我才不会鸽的~)

(未完待续~~我才不会鸽的~)