# 色带处理

## 色带是如何产生的？

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

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

你可能会说：好耶！有那么多颜色能表示，怎么会出现色带呢！

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

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

![这是原始的8bit黑白渐变图](https://i.v2ex.co/v795yRN9.jpeg)

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

![如果是线性分布的话，暗处的断层会很明显](https://i.v2ex.co/dl4oxx5T.png)

![但是gamma校正后，我们看到的亮度就非常均匀了](https://i.v2ex.co/9fQ3rkp1.png)

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

## 那么，色带要如何解决呢？

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

### 加噪：

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

![人为制造的有色带的示例（注意墙壁上的色带）](https://i.v2ex.co/5633z3HVl.png)

![加噪之后，是不是就不明显多了](https://i.v2ex.co/U00w7zPvl.png)

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

如果想要加噪，最直接的方法就是使用[AddGrain](https://github.com/HomeOfVapourSynthEvolution/VapourSynth-AddGrain)来进行：

`addgrain = core.grain.Add(res,1.0)`&#x20;

需要注意的是，加噪的时候，我们一般不推荐添加色度平面的噪点。毕竟彩色噪点看起来真的很不好看，还让本来就码率预算不充足的色度平面更加雪上加霜。所以我们推荐在加噪后，使用`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)
```

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

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

{% hint style="info" %}
以下部分需要对：std.Expr()有一定程度的理解，对std.MaskedMerge有一定程度的理解
{% endhint %}

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

首先我们创建一个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，那么意味着噪点会被尽可能的不添加回去

![一个二次函数：(x-48)^2\*5](https://i.v2ex.co/4AiB2D2Ml.png)

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

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

&#x20;然后我们使用`std.MaskedMerge()`来基于Mask合并片源与噪点层：

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

&#x20;我们就获得了一个能够自适应添加噪点的clip了！

### 抹:

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

#### 首先来讲讲f3kdb:

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

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

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

待续
