组内新人培训(三):色彩范围碎碎念,以及Expr
对于应用层的视频压缩技术,大体上,从零开始学习大概要经过几个过程,
其一是学会通过顺序叠加滤镜来处理视频瑕疵,这个阶段我们称之为入门。
其二是学会使用正确的逻辑来使用滤镜,诸如进行UnsharpMask之前要加降噪预处理以防止同步锐化噪点等,这个阶段我们可以用理论知识来武装自己,使压制过程更加科学。
其三则是使用更复杂的处理流程对逻辑进一步优化,简单展望比如多重预处理结构、基于神经网络训练出的新插件、基于心理学优化的客观评价算法的自适应优化等等。
对于前两点,其实前人做了充足的工作,在之前几期组员培训中也提到过,网络上有很多不错的教学资料。而对于后一点,很多新技术还在酝酿的过程中,有一些我们也讲不清楚。所以现在的情况是,虽然大体上理论框架是齐全的,但细节部分仍需要修修补补以降低其入门门槛。你有这个能力但不去做,阻碍了广大人民群众生产力解放的责任你担得起么?
这也是我们偶尔碎碎念,做些组内教学的原因。而我们的定位也是对已有的资料做些补充,如果有机会偶尔也可能会涉猎一些黑科技。
从这个意义上讲,虽然都不是什么复杂的东西,但对于新人来说看不懂是很正常的。
这就是我说了这么长时间想告诉你的结论,后面的教学文章都是如此,往后就不再提了。
另外值得一提的是,作为一个刚入群的新人压制者,你看到第一篇的手把手教学觉得很亲切,看到第二篇就开始讲滤镜觉得有点快,看到第三篇就准备讲堆栈了有点发懵。从本篇教学开始我们所做的内容就不是面对24k纯新的了,更多是的以专题的形式呈现。如我刚才所说的,即便如此这些也都是一些基础内容,也并不存在什么难度。只不过要看懂的话我们默认你已经学会了VCB公开课程和组长视频教程的全部内容,如果没有的话建议你先不要接触这些黑魔法,先回去接受完整洗脑。
一、色彩范围碎碎念
如果你一路跟着这套教学读下来,那你应该会发现整个系列中的应用倾向性是非常强的。比如你看我几乎没提过反交错的内容,几乎没有提到过位深转换的内容,这些可是都以往的ripper必备的知识,只不过有很多陈年旧事现在不会遇到我们就不再提。
TVrange、PCrange、YC伸张、YC收缩这些都是老生常谈的问题了,前几天群里有朋友在讨论,心想现在还是偶尔会遇到的,于是再拿出来讲一下。
简单来说,TVrange和PCrange分别表示8bit下[16,235]和[0,255]的色彩范围。从TV到PC以及从PC到TV就分别被称为YC伸张和YC收缩。
如果你看过YUV模型中各个平面的颜色分布就会发现,luma平面的分布比较广泛,而chroma平面则相反,其信息都集中在中心区域(如图)
所以通常我们在视频处理中对颜色越界的处理通常只针对luma平面,这是由YUV转换公式决定的。
当然如果在一些特殊情况下进行正规的YC变换的话还是需要联通chroma平面一同调整的。值得注意的是例如8bit下luma的TVrange是众所周知的16-235,而chroma平面则是16-240。
那么为什么要进行YC变换呢?
由于人眼观感上对于极端低亮和高亮区域的分离不明显,当我们发现在某些场景中,由于SB制作人员的疏忽,导致了色彩范围错误,某些细节本来画上去了,但是都集中在过暗区域或过亮区域,就是看的不清楚,这种时候就要通过YC变换让这一部分细节“变”出来,如下是VCB科普里的范例图。
但是我个人对这种处理持保留态度,我认为应当具体情况具体分析。
道理可以很简单,YC伸张的直观视觉效果是画面变“暗”,YC收缩的直观效果是画面变“亮”。
如果将过暗的区域提亮,或将过亮区域的调暗,那么被隐藏的细节就会显现出来,反之,如果将不该暗(本来就很暗)的场景变得更暗,不该亮(本来就很亮)的场景变得更亮那么就会出问题,两者的共同结果是导致信息损失。一部动画中必然是在整个色彩值域上都覆盖有像素,暗场和明场都有分布。实际操作中你会发现,经常性地你在场景A使用YC收缩获得了比原来清晰的细节,但却会引起场景B中的细节丢失。所以我认为逐GOP调整是可行的,全局性调整是必然带来信息损失的。
所以什么时候需要调整,什么时候不需要调整。这是否是一种随个人兴趣而定的主观调整,而你又是否有时间去逐场景整理?这就是制作者需要思考的问题了。
二、关于Expr工具
上面的色彩范围云云都是引子,实际上本篇文章主要想讲的是expr工具。
作为vs提供的三大原生调色板工具之一,海量的功能以它为基础,比如上文的YC变换我们就可以用expr实现。
比如如果我们要进行线性伸张,那么表达式就可以写成["x 16 - 219 / 255 *","x 16 - 224 / 255 *"]
,两个双引号内分别表示亮度和色度的处理方案。
理论上你也可以采用其他伸张方案,只要函数保持单调增即可,比如一个下凹曲线,也许还会来的更加科学。
但是我们实在没办法按照自己的兴趣来控制这条曲线的曲率,所以一般不会这么做。
(可观察墙壁上纹理的区别。由于该图并没有颜色越界,所以这种处理是错误的,这里仅用作举例。值得注意的是,在vs的YC变换的实际应用中,既存在像Expr/Levels这种直来直去的逐像素变换方案,也存在fmtc.bitdepth这种附带dither的保护性方案,使用时应具体情况具体分析。)
诚然expr可以实现的所有功能lut和lut2都可以做到,不过expr却具有lut没有的优势,比如说expr对多平面同时处理时支持原生16bit的输入输出,不像lut2最多支持各平面合计20bit。比如lut的计算逻辑是在每次运行脚本时预先计算结果储存在内存中,调用时查表,而EXPR每一次调用都要再进行一次单独运算而避免查表(查表也是要耗费计算力的)。实际测试中在不同算式复杂度结合使用两种方案可以优化脚本的执行效率。
然而有这么多优势,为什么而大家还是喜欢lut而不喜欢expr呢?
因为人家lut2是可以清楚地写出逻辑结构的,而expr则需要用到堆栈。
很多人一看到堆栈就怕了,堆栈是什么几把玩意,走了走了。
实际上没有这个必要,这其实只是一点小把戏,本篇文章就是对这部分内容进行补充。
VCB公开课程中有一章详细介绍了堆栈的逻辑,其中中缀表达式和后缀表达式的转换方法都已经被介绍的很详细了。事实上,需要堆栈知识的地方仅限于“如果要编写一个自动将中缀表达式转换为后缀表达式的脚本”之类的情况(如果你真的写过会发现其实是一种不错的逻辑训练,推荐有志青年用各种姿势尝试),在应用层面,了解堆栈原理固然好,就算不求甚解,对expr使用也是完全不打紧的。不管怎么说,你在阅读过lp的教学后应该能很容易地将 (1-2)^3/4+5
这个中缀改写为["1 2 - 3 pow 4 / 5 +"]
的表达
但有一个问题是lp在教学中没有提到的,也是我刚开始一直没搞懂的,条件结构应该怎么写?
比如利用中缀表达式写的 x>y?0:255
表述这样一个逻辑
if x>y:
返回0
else:
返回255
它在后缀表达式中的对应范例为:"x y > 0 255 ?"
即"x y 判断条件 真值 假值 ?",如果有elif,向其中叠加就可以了。
试着自己写一写,你会发现它的逻辑简单到令人发指,可能你最开始还需要先列个草稿,但当你熟悉之后比如你要写一个判断当前y平面有没有颜色越界(低于16或高于235),有则标白无则标黑。那么就可以直接写出"x 15 <= 255 x 236 >= 255 0 ? ?"
连眼睛都不用眨一下。
其他的应用比如说结合GradFun3和f3kdb的优势合成一个变化范围二者取大(或取小)的去色带平面,那么可以写成这样
core.std.Expr([g3,f3,src],["x z - abs y z - abs > x y ?"])
当然如果你一定要用复杂一点的写法来展现自己已经熟练掌握了expr也是可以的(笑)
"x z - abs y z - abs > x z > z x z - abs + z x z - abs - ? y z > z y z - abs + z y z - abs - ? ?"
虽然已经讲了这么多,但我才不会告诉你如果用Lut的Functions不光可以用普通的逻辑结构写而且不会多次计算呢
迷之压制组,这个组的一切都是迷。
我们下期再见。
新人求教, 请问,公开课教程17里提到的,“LimitDiff还可以用来限制其他线条处理,比如说收线、加黑等”,请问具体怎么做,我想Upscale一个老的DVD片源,发现线条不够黑,而且很粗,请问如何实现
你好,不够黑可以参考haf.LineDarken ,太粗可以使用warp.AWarpSharp2。vcb教学中提到的问题建议你咨询vcb,毕竟我不知道他们怎么写的。
加黑方面,一般原理是先使用core.std.Maximum().std.Minimum() 杀死黑色线条,然后对结果做差,反向叠加后就是加黑,这个时候使用一个Limitfilter做限制,限制加黑强度,以及防止有些像素不变黑反变白。在这里是相对于空平面操作的,所以实际上在加黑中使用Limitfilter是比较蛋疼的行为,正常情况下是写一个Expr一步到位的。
一个简单的demo:
import vapousrynth as vs
import mvsfunc as mvf
from nazobase import *
core = vs.core
def line_darken(clip):
kill = core.std.Maximum(src16y).std.Minimum()
blank = core.std.MakeDiff(src16y, src16y) # blank clip as limiter
diff = mvf.LimitFilter(src16y.std.MakeDiff(kill), blank, thr=256, elast=2)
diff = diff.std.Expr(['x 32768 > 32768 x ?'])
# Limitfilter无论变化值大于或小于参考,都会做相同处理,但加黑时我们希望其只应用半边,即不能变白。
return clip.std.MergeDiff(diff)
src16 = dataloader('idx')
src16y = gp(src16 , 0) # 只是加载并取出一个GRAY16平面
darken = core.std.ShufflePlanes([line_darken(src16y), src16], [0,1,2], vs.YUV)
check(src16, darken).set_output() #对比检查
没有nazobase可以执行pip install nazobase安装
最后再问一个蠢问题,有能用GPU(NVENC)跑的X265吗?
您好,请问,haf.LineDarken怎么加载。我去Doom9看了HAvsFunc的FastLineDarkenMOD,不知道是不是你说的haf.LineDarken是不是指这个,因为我直接用haf.LineDarken报错module 'havsfunc' has no attribute 'LineDarken'。发现下载来是个avs文件(avs还是avsi我忘了,因为文件内写的是avs),按照文件内提示import F:FastLineDarken.avs,加载不成功。请问哪里出错了。。。您写的Demo我操作时,报错name 'gp' is not defined,另外MakeDiff(src16y, src16y)我也不是很懂,同一个平面相减有什么用途?纯新人,问题蠢的话,别见怪。
F:FastLineDarken.avs
斜杠没法显示啊。。。我以为我忘敲了。
多谢指导,容我消化消化。
你好,由于很久没看了,根据我不可靠的记忆随便一说,如果现在的名字叫做fastlinedarkenmod的话那就是它了。有关载入错误,我对avs完全没有了解,你应该是下载错了版本,我在上文中指的是python使用的havsfunc.py。有关gp无法运行,是我的一个疏忽。可能因为版本原因你下载到的版本中它不叫gp,而还是比较长的GetPlane,你把名字替换过来应该就可以使用。
控制加黑程度的话主要还是调 thr 和 elast,没效果说明数值不太对。其他思路,原理上搜索线条的.Maximum().Minimum()不太好替换,而其导致的diff后结果依据源的不同而有差异,一个方法是产生diff后再加一个filter将其变化值放大(x = (x-32768) * amp + 32768)。
视频编码的应用本身是个比较小众的领域,即使是从事一些比较核心向的开发,这种工具跟语言类似属于吃力不讨好的活计,没什么变现前途,所以也不存在什么系统的书籍(大概)。毕竟单纯应用的话是简单的,比如h2654,应该在ffmpeg里指定-vcodec h264_nvenc就轻松地实现了。
感谢,Demo正常跑起来了。想问一下能调整加黑的程度吗,我尝试着更改thr和elast的值,似乎效果没有变化。另,我电脑CPU不是特别好,编码太慢了,想着能不能直接pipe到显卡编码器上,或者有像X264那样有OpenCL加速之类的。看了NV的doc一头雾水,API搞得不是很懂。。从零开始一头雾水,有没有系统点的书籍之类的推荐呢?
同平面的相减意味着生成一个空平面,在16bit下颜色为32768,这是vs中对diff的特殊处理。生成的目的是为了利用其进一步限制颜色,因为单纯的limitfilter并无限制颜色不允许超过32768的功能。而限制其不能超过32768,则等同于限制其加回后没有像素会变白。
有关nvenc的问题,x265是一种编码器,我想你要表达的意思是nvenc这个编码器能否生产符合hevc规范的输出,这个当然是可以的,并且支持8和10bit,你可以去参考nvdia的官方文档,并且在ffmpeg的集成(大概)中比较轻松地使用。
最后,fell free to contact.