色带处理

色带是如何产生的?

俗话说知己知彼,百战不殆,要对付色带这一号敌人,了解色带是如何产生的非常重要。

首先,色带往往出现在画面中的平面部分,大面积的色块往往是色带集中出现的区域,同时,一些渐变场景也常常会看到它。在现在的显示设备上,我们常常采用8bit每通道的RGB来量化色彩灰阶并显示画面。也就是说,RGB每个通道可以有256种灰阶表示,我们总共能显示的颜色种类就有256*256*256,也就是16777216种颜色。

你可能会说:好耶!有那么多颜色能表示,怎么会出现色带呢!

其实很简单,最简单粗暴的试想一下,屏幕上出现了一条从黑到白的线条,从最左到最右总长1920个像素,那每一个灰度区间如果从线性的角度讲,一个区间会长约7.5个像素,那区间和区间之间就会出现明显的一条痕迹,这就是色带的来历。所以,8bit下无论怎样都很有可能会出现色带,只是你能不能看得出而已。

当然了,上面特意提到了线性的角度,那么实际上,我们日常看的显示器内容,都是已经经过gamma校正了的,关于gamma校正这里不想细究,它的作用是通过重新分配灰度系数,让对黑暗不敏感,亮部敏感的人眼能够更清晰的看到画面中偏黑的区域。简单的来说,你看到的灰阶从最暗到最亮的分配并不是均匀的,越黑的部分分配的灰阶越多,这听起来可能有点怪,但是通过下图我们可以直观的感受到区别。

如果我们用5bit(32位)来表示的话,那么必然会出现色彩断层,但是线性分布和gamma校正后的感觉会完全不同

Gamma的出现虽然让人眼对画面的光暗得到了巨大的提升,但是它也打破了灰度的均匀分配,让色带更可能出现了。

那么,色带要如何解决呢?

其实方法总结下来就是两点,要么添加噪点,要么就将构成色带的像素替换为临近像素的中间值(其实就是一个字,抹!)

加噪:

很显然的,由于我们是人类,眼神十分的堪忧,没有经过特别的训练和熟悉一般很难看出色带,所以加点噪点可以更进一步的混淆人眼,让我们感觉不出色带的存在。同样的,你也很难在画面的纹理部分看出色带,只有什么都没有的纯色块/平面才会让色带看起来非常明显。

当然,这样无脑的加噪会影响观感,而且效果也只能算凑合。我们接触的大部分BDMV都会为了防止色带打上一层不痛不痒的噪点(所以我们俗称SBMV),这么做属于治标不治本的办法,但是保留噪点作为去色带操作后的保底是一个不错的选择,万一色带没抹干净好歹还有层噪点做遮羞布,其实也挺好的。

如果想要加噪,最直接的方法就是使用AddGrain来进行:

addgrain = core.grain.Add(res,1.0)

需要注意的是,加噪的时候,我们一般不推荐添加色度平面的噪点。毕竟彩色噪点看起来真的很不好看,还让本来就码率预算不充足的色度平面更加雪上加霜。所以我们推荐在加噪后,使用std.ShufflePlanes来仅将加噪后的Y平面与原Clip拼成一个新的Clip

src8   = core.lsmas.LWLibavSource(source=input)
src16  = mvf.Depth(src8,16,fulls=False,useZ=True)

noise = core.grain.Add(src16, 1.0)
# 使用ShufflePlanes提取出Y平面
noise = core.std.ShufflePlanes(noise, planes=0, colorfamily=vs.GRAY)
# 将Y平面合入原Clip
output = core.std.ShufflePlanes([noise,src16], planes=[0, 1, 2], colorfamily=vs.YUV)

当然了,全屏幕直接加噪,还是不够优雅,毕竟噪点也是占用大量码率的,我们希望能够好钢用在刀刃上——在需要加噪的地方加噪:

很显然的,大部分噪点其实出在比较暗的地方,较亮地方的噪点用打噪点也没办法很好的糊弄过去,较暗的地方打噪点效果就比较好,那么有没有一种办法,能够让噪点打在暗的地方呢?

以下部分需要对:std.Expr()有一定程度的理解,对std.MaskedMerge有一定程度的理解

这时候就需要一些简单的数学知识了:

首先我们创建一个blankclip,所有像素的值都是32768,再对这个clip进行加噪:

nullclip = core.std.Expr(src16,["32768",""])
noise = core.grain.Add(nullclip,  1.0)

然后我们可以设计一个函数(例如二次函数)作为Mask,将0-255的8bit输入(x),转化为每个16bit下的像素应有的0-65535的权值(y)

如果y越接近与0,那么意味着噪点会被尽可能的添加回去

如果y越接近65535,那么意味着噪点会被尽可能的不添加回去

例如(x48)25(x-48)^2*5这个函数,在x=48x=48的时候(8bit下这是相对暗的区域),yy的取值是0,代表在这个区域的加噪强度是最高,而由于二次函数的特性,所以接下来的曲线会特别陡,在x=128x=128时,y=32000y=32000,加噪强度已经降低到了50%,并且在x=162x=162左右时,降噪强度已经变为0%,这样的函数可以说是比较理想的,当然如果你想调整零点或者曲线开口程度,那相信你应该知道该怎么改这个函数。所以,我们可以用VS中的Expr来基于clip创建这样一个mask:

nrweight = core.std.Expr(src8, ["x 48 - dup * 5 *", ""], vs.YUV420P16)

然后我们使用std.MaskedMerge()来基于Mask合并片源与噪点层:

out = core.std.MaskedMerge(noise,nullclip,nrweight,[0,1,2],True)

我们就获得了一个能够自适应添加噪点的clip了!

抹:

抹这个操作就字如其名了,检测色带,针对色带进行平滑就是抹的主要目的,我们常常使用f3kdb来进行去色带操作,但是如果遇到真的,非常难搞的色带时,GradFun3也是个不错的选择

首先来讲讲f3kdb:

f3kdb的效果在deband滤镜中已经算是翘楚了,它的原理听起来也非常简单:将构成色带的像素替换为临近像素的中间值,并抖动加噪从而摆脱色带。很显然,这样的操作与降噪很像,并且会抹去画面中的细节。

f3kdb的所有参数的具体信息都可以查看滤镜百科中去色带滤镜部分,这里来讲讲f3kdb的常用写法:

由于BDMV里会默认添加一层噪点,它对于f3kdb滤镜去色带会有巨大的影响,所以一般我们需要在降噪之后再使用f3kdb,以保证最好的效果和最小的细节损伤,这个操作一般被称为:nr-deband

待续

Last updated