# 写在前面

本教程的目的是展示并教学广义上“压制”的工作。这些工作所涉及的工具非常众多且步骤繁琐，所需要的知识点也非常分散，所以教程的章节构成会比较随意，在阅读一些章节时，可能需要同时参考其他章节一并学习。笔者希望尽量以简单易懂的方法来叙述教程的每一部分，但是由于表达水平有限，语句不通等丢人问题还请包涵，希望读者能够多多见谅。

本文受到[guide.encode.moe](https://guide.encode.moe/)的启发并借鉴了其一定思路，但更多的希望专精于压制方面，更详细的阐述视频处理方面的内容。

由于AVS已经十分古老，使用的必要性也已经极其有限，所以本教程主要使用Vapoursynth作为视频处理工具并且主要在Windows平台下进行示例（你既然都用OS X/Linux了，那……）。

本教程采用[知识共享署名-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-sa/4.0/)进行许可，在分享和转发，或者使用到本教程片段时请注明出处。

5/12日更新： 作者学术垃圾制造完毕，终于可以重新着手来更新啦！

![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png)


# 帮助编写！

由于本教程是直接拿gitbook编写的，所以如果想要协力编写，请在github上向[此项目](https://github.com/pwinner/Mystery-Encoding-Guide)发起Pull Request

本教程基于Markdown编写，请在Pull Request前确认预览效果没有问题（如果有我也就顺手修掉了）

如果你发现了任何typo/不严谨或有异议，错误的地方，欢迎来github提Issue

## 添加新页面

如果你想要添加新页面，请修改SUMMARY.md，并且添加正确的新页面Markdown链接！


# 特别鸣谢

贡献者（只要是contributer我都会丢上来的啦）：[MysteryG](https://github.com/pwinner)

压制组技术支持：[VCB-S](https://vcb-s.com/)&#x20;

参考网站：

{% embed url="<https://guide.encode.moe/>" %}

{% embed url="<https://vcb-s.nmm-hd.org/>" %}

{% embed url="<https://www.nmm-hd.org/>" %}


# 什么是"压制"

## 动词的”压制“

所谓动词的“压制”（encode），便是指使用各类软件，将获得的片源经过预处理，嵌字幕，编码，封装来获得所想要的视频成品的动作，也可以专指使用编码器编码视频/音频的过程。压制的过程中会有非常多繁琐的工序，而其中大部分工序都对压制者的经验提出了非常高的要求。

## 名词的”压制“

所谓名词的“压制”（encoder），在绝大多数字幕组的招收成员类型中都会出现，也有另名为“后期”的职位，也会是需要负责压制部分的任务的，但是后期更多的还需要对字幕添加特效，制作”屏幕字“字幕等，这里主要讨论压制部分，故不再展开。

## 作为一名压制，我需要做什么？

### 1.获取片源

巧妇难为无米之炊，没有源视频我还干什么压制嘛，如今的片源获取也已经非常便利，通常字幕组会直接提供这些片源，如果想要自己去寻找的话，新番可以考虑在[nya](https://nyaa.si/)上，使用罗马音搜索来取得，而蓝光原盘在nyaa上则不多见，你可能需要拥有一些PT(Private Tracker)网站账号，才能够有机会获得这些蓝光原盘资源。

### 2.分析片源

且慢！拿到想要的片源之后，不要一股脑地就拿出”通用脚本“，修改一下输入输出丢进去就完事儿了，很多时候你可能会因为疏忽了这一步而犯下错误，导致发布的资源画质不甚理想甚至出现错误而删掉重发，不仅麻烦了自己，也让观众感到十分困扰。所以压制应该首先使用一些软件来获取片源的分辨率，交错，帧率等等信息，再使用VS/AVS编辑器载入片源，判断视频是否有瑕疵，瑕疵属于哪些类型。

### 3.编写脚本

作为压制，你需要拥有编写脚本的能力，依照片源的分析结果利用VS/AVS正确的对视频进行处理，在脚本中使用滤镜针对瑕疵进行修复，逐渐将画面调整到理想的状态，最后选取一组合适的编码参数，就完成了这部分的任务。分析片源与编写脚本是体现压制者水平重要环节，而且非常需要大量视频处理积累下来的经验。

### 4.压制

其实简单的说，这个环节就是运行编写的脚本，让编码器与视频处理软件全速运行，保证不要突然断电让自己的电费和心血白白浪费（因为突然断电基本上就得从头开始压了），佛系的等待完成就好。当然了，音频编码也应该算在这一步，如果你需要这么做的话。

### 5.封装

当你获得了压制完的视频/音频成品之后，还需要把他们合并起来，放在一个媒体容器中。有的时候你可能需要指定音频轨道，视频轨道的延迟，封入时间码，章节等信息，也有可能你需要内封字幕，使用工具将字幕所需要的字体抽出并一并附带上去。

## 再谈压制的不同级别

压制者在字幕圈不同级别所需要的自身水平是不同的，字幕组的压制通常要处理的是新番，一般仅会对新番片源使用一些简单的滤镜来对片源进行基本的处理，按照要求产出如1080p/720p的内嵌字幕版本，这样的任务对压制者的要求不高，通常一份通用脚本和稳定的一套完善的工作流程便能够完全满足需求。一些字幕组可能会对抢首发比较在意，这样则对压制是否有富裕的时间甚至是电脑设备性能提出了一定的要求。

而说到压制组，他们则都是对画质追求的狂人，经手处理的基本都是蓝光原盘，而他们的BDRip作品通常会运用非常复杂的滤镜和处理，尽可能的保留原盘观感的同时修复瑕疵，最后使用合理的编码参数将视频体积压制到合理的范围内。制作压制组级别的压制作品需要对视频处理软件的大部分滤镜都有非常深刻的了解，甚至对其处理原理都需要有一定的认知。


# 压制需要什么

压制是一项非常具有挑战性的工作，要成为优秀的压制，优秀的软硬件条件必不可少。

## 软件要求：

* **基础的编程能力:** 如果会写Python就更好不过了，因为Vapoursynth需要使用Python来进行编程。
* **细心！**&#x538B;制更多的是一门工程而不是一门科学，做到很好的级别并不只需要编程能力，最终成品的成败也取决于你犯下了多少错误，比如错误的音频封装，错误的反交错处理。没有人会希望自己收藏到了一份错误百出的BDRip！
* **一些理科基础:** 这能够帮助你快速上手掌握Expr,Lut等滤镜，不少滤镜在理解了其工作原理后才能更大的发挥它的效用。
* **挑剔的眼神:** 你需要一双刁钻的眼睛，洞察画面中出现的瑕疵，只有掌握了瑕疵的严重程度，才能对症下药，修复问题的同时不造成其他的副作用。
* **电脑软件：**&#x89C1;神一样的工具们
* **英语能力：**&#x4E0D;会点英语根本没法自己看doc，很多东西真的只有看doc才能搞清楚，教程是不够的。

## 硬件要求：

对压制来说，一台好的电脑绝对能使你的工作事半功倍，这里的好不仅是性能上的，也应该包含稳定性上的”好“，没有人希望自己压的片在完成前的最后一秒被电脑蓝屏制裁。

* **操作系统：** 64位的Windows7及以上版本，OS X 10.7及以上版本，Linux
* **屏幕：** 屏幕非常关键！一块广色域（但是需要做色彩管理）的IPS屏幕最好不过了，专业制图用的显示器是比较理想的选择，因为他们能够提供更高精度的显示，避免出现明明片源没有色带，显示出来却有色带，怎么修复还修不掉的尴尬情况。（什么，你有Pro Display XDR？土豪我们做朋友吧）
* **CPU：** ”力大砖飞“一词在这里同样适用，整一颗你买得起的最猛的CPU吧！需要注意的是，没有AVX2指令集的CPU都不推荐购买，它们太过古老，在现在的编码器上并不能以最高效运行。同时，如果你的设备使用了NUMA技术（例如AMD Threadripper 2990wx或者是双路/四路的工作站或者服务器），在压制时应该手动将任务限定在某一个NUMA节点上运行，通过同时多开多个压制任务来提高效率，而不是让所有CPU核心完成一个任务，因为这样会大幅降低效率，大量的CPU工作时间都被浪费在内存数据的交换上。
* **GPU：** 由于压制过程中CPU的处理量爆棚，一块不差的显卡可以帮助CPU分担不少压力，GPU能够使用的不错的滤镜也有很多。GTX960以上的N卡都是不错的选择，而由于a卡的opencl实现问题，仅推荐使用rx580及之前的显卡。
* **内存：** 其实内存这部分简单来说就是越大越好，至少8G起步，但是硬要说的话，理想的内存应该是CPU线程数的1.5-2倍。
* **存储：**&#x7531;于要存档大量的BD原盘（通常一盘25G-50G不等），有的时候还不能做完就删，所以好歹要有个1T吧，反正你存的下就行。


# 神一样的工具们

由于压制所需要用到的软件门类繁杂，所以我会分成几个部分来介绍，这样找起来也方便一点，这里推荐把所有工具都丢到一个文件夹下，并且将这个文件夹加入path中，这样对于日后使用和版本管理都有不小的帮助！

这里就提一下如何将工具添加到PATH里吧！

### 1.收集工具

嘛，总的来说就是把一堆exe文件一股脑丢一个文件夹里，比如我这里是C盘的一个叫pathbin的文件夹

![反正一股脑丢进去就是了](https://i.v2ex.co/EV5FNakU.png)

### 2.设置Path路径

这个解释起来比较复杂，首先右键此电脑，选择属性，接下来看图就好了：

![看图就完事儿了](https://i.v2ex.co/j9F2w60Q.png)

### 3.测试设置是否生效

这个生效只要关了所有终端再开应该就可以了，实在不行就重启呗

然后打开powershell/cmd，输入ffmpeg或者flac试试，成功了那就ok了！


# 视频工具

## Vapoursynth

为了读入片源并且处理，我们需要一个帧服务器（Frameserver）来帮助我们，同时我们也需要给Vapoursynth安装不同的插件（其实就是把编译好的dll丢进Plugins文件夹里）来实现读入，处理。

Vapoursynth的安装非常麻烦，如果需要系统全局安装的话，也可能会被路径问题搞得晕头转向，所以这里推荐开箱即用开袋即食的Portable版，而恰巧有人整理了一份囊括几乎所有插件和许多工具的VS便携版，同时它还带了最新的X265和X264，你可以在[这里](https://github.com/theChaosCoder/vapoursynth-portable-FATPACK/releases)下载到它

当然如果你有自己的想法，想要自己安装一份Vapoursynth，那你可以在下面的链接中下载到想要的东西

VS官网：<http://www.vapoursynth.com/>

插件（滤镜）仓库：<http://vsdb.top/>

如果你使用的是Linux，那可以参考使用：<https://github.com/himesaka-noa/vs-toolset-installer-ubuntu>(注意，这个脚本目前仅支持Ubuntu18.04及以上版本)

## VSEdit

既然我们已经有了Vapoursynth，那么就需要相对应的编辑器来编写，预览脚本输出的结果，所以我们需要VSEdit。如果你已经下载了上面提到的开袋即食版本，里面就已经自带有vsedit了，你可以在Vapoursynth64文件夹中找到它。

VSEdit有最基础的代码补全功能，并且能够进行视频预览，脚本测试甚至编码等操作，用过时候你一定会喜欢上它！

对了！如果你使用的是安装版或者自己做了一个Portable版的话，你可以在[这里](https://bitbucket.org/mystery_keeper/vapoursynth-editor/downloads/)下载到它

## X264/X265

没编码器你还压什么片嘛，一个预先编译好的编码器是必须要有的，当然了，如果你下载了上面的VS便携版的话，这些编码器都已经附带进去了，你可以在bin文件夹里找到它们。

X264/X265的编译版本你都可以在这里找到：<http://msystem.waw.pl/x265/>

这个页面里有非常多编译版本，X265通常选择右边Stable分支，适合你CPU指令集的编译版本，而使用GCC与VS 2019(也就是MSVC)编译的版本区别仅在压制速度上，通常可能MSVC编译的版本会运行起来更快一些

**注意！我们推荐将X264/X265加入到系统的PATH中，在以后使用CLI编码时会方便很多**

当然了，如果你要搞什么av1玩的话，我这里也就丢几个链接吧：

rav1e：<https://github.com/xiph/rav1e>

svt-av1：<https://github.com/OpenVisualCloud/SVT-AV1>


# 音频工具

**音频编码器推荐全都加入系统的PATH路径中，这样会大大便利我们使用CLI调用这些软件**

## AAC编码器

AAC编码器这里就推荐QAAC，QAAC是苹果AAC编码器的一个封装前端，所以使用QAAC就必须安装一个64位的iTunes。由于在AAC上苹果编码器做的最好，虽然麻烦但是还是用它好了。

QAAC安装指引：<https://github.com/nu774/qaac/wiki/Installation>

QAAC CLI参考：<https://github.com/nu774/qaac/wiki/Command-Line-Options>

你可以在这里下载到QAAC：<https://github.com/nu774/qaac/releases>

iTunes怎么下我就懒得写了

## FLAC编码器

如果需要无损音频编码器，那么FLAC准没错了。

你可以在这里下载到它：<https://ftp.osuosl.org/pub/xiph/releases/flac/>


# 封装工具

实际上这里摆的并不只是封装工具，抽流混流工具也应该能算吧，我就一并全丢这儿了

## MKV：MKVToolNix

万能的MKV容器的万能封装工具，不必多介绍，其自带的GUI也非常好用

下载地址：<https://mkvtoolnix.download/downloads.html>

## MP4：MeGUI/FFMPEG

其实MP4这东西兼容性确实太好，用什么工具封装出来也没什么大问题，当然了，MEGUI更好的选择，它附带的MP4 Muxer是基于MP4box的，封装应该会更符合规范，至于这俩哪下载，我觉得还是写在综合工具里比较好。

## 抽流工具:

当你获得了片源后，必然需要进行抽流，将音频/视频甚至是pgs字幕分离开来，以便将经过vs处理编码的纯视频流和音频流等媒体流结合起来成为最终成品

### tsMuxeR

tsmuxer是一个专门用来抽取ts/m2ts源的工具，当然看它名字是muxer，它当然也能拿来封装，做Remux也不在话下

下载地址：<https://www.videohelp.com/software/tsMuxeR>

### MeGUI

没错它又来了，MeGUI的Tools中又HD-DVD/Blu-ray Streams Exractor，它是基于EAC3to的，这个也挺好用的，下载地址去综合工具里找咯


# 综合工具

其实有些没法分门别类的我也丢这里了，见谅

## FFMPEG

对于音视频编码和解码史来说，FFMPEG无疑是里程碑一样的存在，支持几乎所有你能想象得到的编码格式的编解码，封装抽流什么的都不在话下，但是正因为它要顾及的东西太多了，所以做许多工作时都会出现封装不标准等等小问题，但是做许多事用它仍然是便利快捷的选择

你可以在这里下载到它：<https://www.ffmpeg.org/download.html>

## MeGUI

如果要说综合工具的话，MeGUI绝对是瑞士军刀级别的存在，它提供的抽流混流工具是我最常使用的，而且它还有章节编辑抽取工具，这在做BDRip的过程中都是非常好用的。

你可以在这里下载到它：<https://sourceforge.net/projects/megui/>

## MediaInfo

MediaInfo绝对是做压制居家必备神器，它能够分析片源音频视频轨，给出这些片源的轨道信息，包括了音频/视频/字幕/章节等等，为下一步处理乃至封装时提供**极其重要的信息。**

你可以在这里下载到它：<https://mediaarea.net/en/MediaInfo>

## IGSTools

来自皮神出品的神器，用于提取BD原盘菜单中的章节信息，具体用法会在分析片源章节讲到

下载安装指引：<https://github.com/SAPikachu/igstools>

使用它你必须得有个Python环境，所以这个得自备啦，还得记得去装pypng！

## RP-Checker

vcb-s出品工具之一，使用ffmpeg或者VS来计算对照视频与压制后视频的[PSNR](https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio)/[SSIM](https://en.wikipedia.org/wiki/Structural_similarity)/[GMSD](https://www4.comp.polyu.edu.hk/~cslzhang/IQA/GMSD/GMSD.htm)，生成图表来直观的展现结果，可以用于检查压制成品中是否出现了由于电脑不稳定导致的某一帧花屏等现象\~\~（宇宙射线使得内存出现了位翻转）\~\~，从而避免最终成品发出去还要返工rev。

你可以在这里下载到它：<https://github.com/vcb-s/rp-checker/releases>

如果你有能力帮助我们完善这个软件或者发现了bug，欢迎来提Issue与PR

## ChapterTools

vcb-s出品工具之二，能够解析蓝光原盘的playlist，提取并编辑章节信息

你可以在这里获得它：<https://bitbucket.org/TautCony/chaptertool/downloads/>

同样的，如果你有能力帮助我们完善这个软件或者发现了bug，欢迎来提Issue与PR

## AssFontSubset

油总出品的字体子集内封工具，使用它需要用`pip`安装`fonttools`

使用方法和下载地址（release里下载）：<https://github.com/youlun/AssFontSubset>


# 分析片源

还记得在”什么是压制“篇中提到的分析片源吗？我们先从这里着手开始

## 使用MediaInfo查看视频信息

假设我们已经安装好了MediaInfo，那么我们随便找一个媒体文件右键选择MediaInfo，就会弹出MediaInfo的界面：

![一个M2TS文件的Mediainfo](https://i.v2ex.co/YP5t51f0.png)

MediaInfo第一页上会呈现出大致的轨道信息，音频与视频都有，而我们选择上方的View，并选择Text，就可以进入到详细查看每一项参数的界面了。

### 视频轨：

![视频轨信息](https://i.v2ex.co/c9HR2Io7.png)

这是某一部动画的原盘特典，在视频部分我们可以看到的有：视频的基本信息，包括了码率，分辨率，色彩位深，色度采样，帧率等信息，当然我们最要关注的还是红框里的Scan type，如果它是Interleaved（交错）的话，那么就需要格外注意是否要进行一定的反交错处&#x7406;**（但是就算是Progressive也有可能会需要反交错的）**，这类问题要具体例子具体分析，单单看这里是不够的，但是这足够提醒你：这部片可能有交错现象，需要额外观察。

### 音频轨：

其实这里倒是没什么特殊的，但是我非要把视频拿出来讲，音频总得要有吧，顺带说说重复音轨的事儿

![音频轨信息](https://i.v2ex.co/xxIgG1Co.png)

有的时候BDMV里会有两条音轨，有的时候他们是5.1/2.1等多声道音轨，有的时候它们是评论轨，有的时候它们虽然听上去一样，但是说不定有哪里不一样的，也有的时候……它们就是一模一样的。

我个人快速判断音频是否重复的方法就是：用[RapidCRC](http://rapidcrc.sourceforge.net/)这类软件，对提取的俩音轨进行hash计算，看这些音轨的CRC32是否一样，如果CRC32一样那绝对就是一模一样了。

这些音轨可能体积会非常巨大，所以需要自行判断成品中是否附带上去，是否需要使用无损编码以缩减体积

### 字幕轨：

有些时候bdmv会附带PGS字幕（一种用图片做的字幕）

![pgs一例](https://i.v2ex.co/hYwiyU42.png)

看到这种还是一样，自己取舍要不要封装的时候带上

## 章节信息

![](https://i.v2ex.co/z33I44gJ.png)

有的时候原盘会附上章节信息，方便大家快速跳过op、ed或者是跳到某段特定的剧情，这些信息通常在原盘中是分开存储的，我打算在这里把它们分为章节时间信息和章节名两部分来讲

### 章节时间信息：

原盘的章节时间信息一般都存在`PLAYLIST`文件夹里，以mpls形式存放，我们直接用notepad之类文本编辑器打开它是看不到什么有用信息的，因为它是二进制流组成的，所以我们需要使用工具：ChapterTool或MeGUI来提取他们：

![](https://i.v2ex.co/KAr2zoYB.png)

我们在这里读取了一个mpls文件，可以读取到所有章节信息，到这一步可以直接用保存来保存一个可以封装用的章节文件了，但是你也应该发现了，这些章节没有名字，那么名字去哪了呢？

### 章节名：

其实做到上一步就已经很好了，差不多够了。但是如果你想更近一步，非常有爱还会一点别的语言的话（比如日语），那可以自己找出这些章节名。

那么章节名在哪里呢？他们都在BDMV STREAM中那些你没法播放的文件：

![](https://i.v2ex.co/n27KiTld.png)

反正windows都没法生成略缩图，肯定就是它们几个啦

这些文件一般MediaInfo只能看到有一个PGS轨道，视频音频完全没有的，我们需要[IGSTools](/shen-yi-yang-de-gong-ju-men/zong-he-gong-ju#igstools)这个工具来提取里面的图片信息，我们打开终端，输入：

```
igstopng 00005.m2ts
```

然后如果没出错的话，M2TS所在文件夹会多出一堆的图片，这些图片就是隐藏的章节名所在的地方啦，以《青春猪头做梦梦到兔女郎学姐》剧场版来说，就是这样的：

![](https://i.v2ex.co/TLi7G145.png)

然后如果你会日语，你可以在ChapterTools里面把这些章节名填进去，再保存，就有了一个拥有章节名的章节文件啦！~~（什么，你不会日语？打开谷歌翻译用手写输入又不是不可以）~~

![就像这样填！](https://i.v2ex.co/0srbI55Z.png)


# 编写你的第一个Vapoursynth脚本

## 先贴一段最简单的输入输出，我们开始吧！

```
import vapoursynth as vs
core = vs.get_core()
core.max_cache_size = 2000

input = r'F:\Resource workspace\00010.mp4'

src8 = core.lsmas.LWLibavSource(input)
src8.set_output()
```

## 从熟悉Vapoursynth编写语法开始

其实Vapoursynth的语法与Python非常相似，VS脚本本质上都是Python脚本，语法都是根据Python来的，所以可以看到一般开头，都是一串import，载入必要的vs和常用的library，这里只是最简单的示例，所以仅载入了vs库。

首先我们要通过这两行代码来载入vs的内核，并且指定好期望VS使用的内存容量，在执行`vs.get_core()`后，vs会自动载入所有第三方的dll滤镜，这些滤镜在portable环境中存放在”vapoursynth64\plugins“中。

```
core = vs.get_core()
core.max_cache_size = 2000
```

这时候我们就可以尝试调用滤镜了，我们这里仅用到了最基础的”源滤镜“，他们负责读取你提供的文件路径字符串（这里是input），并且对对应文件进行索引，最后将读取的结果丢给变量名。（不要有人再找我问什么字符串前面那个 **r** 是什么意思，为什么源滤镜报错了）

```
input = r'F:\Resource workspace\00010.mp4'
src8 = core.lsmas.LWLibavSource(input)
```

毕竟使用的是Python，所以弱变量类型让你可以把任何东西都丢进一个变量里，一般源滤镜读出来的结果是一个[VideoNode](http://www.vapoursynth.com/doc/pythonreference.html?highlight=videonode#VideoNode)类（点击进去可以看到VideoNode类里有哪些方法可以调用）

在调用滤镜时，你应该遵循以下的调用规则：

```
core.namespace.function()
```

每个滤镜一般都有自己的namespace，在运行`core = vs.get_core()`时，vs会自动加载插件目录下所有滤镜，并且把他们的namespace读出来，而在这些namespace下，有不同的function。

比如举例的源滤镜使用的vslsmashsource.dll，其定义的namespace是`lsmas`，在这个namespace下有个函数叫做`LWLibavSource()`，所以调用方式就是`core.lsmas.LWLibavSource()`

最后我们调用我们已经承接上读出的视频源的变量src8（它是一个VideoNode类！）的一个函数：`set_output()`

```
src8.set_output()
```

我们就能输出我们刚才读入的成品啦！

## 等等，我咋预览我刚才写的脚本？

嘿，说到点子上了！这时候你就需要一个类似IDE一样的东西来帮助你了，我们来谈谈VSEdit吧！

首先我们打开软件，把刚才的代码丢进去，按一下f6

![VSEdit主界面](https://i.v2ex.co/X178n2TWl.png)

如果没有什么大问题，你应该可以看到一条绿色的成功运行提示，就说明你的脚本基本上没什么问题，可以正常输出了。而下面一条提示告诉你输出的视频总共有多少帧，时长多少，分辨率，帧率以及输出格式等信息，这些信息能帮助你检查自己有没有弄错什么东西。

如果有，就请先检查一下你的路径到底对不对，字符串的 "\\" 有没有转义，如果都对了还不行那就只能各凭本事咯(｀･ω･)

如果没有，那再按一下f5，你应该能看到弹出来了一个预览界面，下面还有一个进度条可以拖动，那个界面现在就不介绍那么多了，反正你只要知道可以拖着玩就是了。（什么，你说没有声音？VS是处理视频的帧服务器怎么可能会有声音呢？⊂彡☆))д\`)）


# 简单的……加点滤镜

既然我们已经可以输入输出了，那我们何不在之前的脚本基础上，再加点料呢？

## 年轻人的第一个滤镜

什么，居然是年轻人的第一个滤镜？你说的是小。。。⊂彡☆))д\`)小个头啦

这里就拿最简单的字幕滤镜来说好了，我们调用Vapoursynth自带的`sub.TextFile()`来做演示好了

基于上一章我们的输入输出脚本，我们只要在输出行前一行加一句：

```
src8 = core.sub.TextFile(src8, r'F:\sub.ass')
```

如果你的字幕文件本身没什么问题，字幕文件的路径也没什么问题的话，你在vsedit使用f6/f5应该也不会遇到什么问题，如果有问题就……自裁，请(|||ﾟдﾟ)。

需要一提的是，这些滤镜函数的调用和参数传递的方式是需要注意的。比如你使用vsedit输入上述滤镜的过程中，在打到`sub.Te`的时候就会弹出自动补全的选项，按一下tab，vsedit便会把这函数所有调用方式全都写出来，需要你一个一个去填，我们还是拿`sub.TextFile()`举例子\
\
`core.sub.TextFile(clip, file, charset, scale, debuglevel, fontdir, linespacing, margins, sar, style, blend, matrix, matrix_s, transfer, transfer_s, primaries, primaries_s)`

一个函数下有非常非常多的参数可以自己传递，但是对于字幕滤镜来说，你只需要`clip,file` 这两个参数就够了，其他的参数都**可以不写**，而参数传递的方式则有两种

```
#这两种写法是等价的
src8 = core.sub.TextFile(src8, r'F:\sub.ass')    #使用位置传递
src8 = core.sub.TextFile(clip=src8, file=r'F:\sub.ass')    #使用按关键字参数传递
```

如果你使用按位置传递，你需要知道哪个值给哪个变量，这些参数的具体顺序是什么样的，这适合一些简单的滤镜或者你已经是老司机非常熟悉你用的滤镜的情况。

而按关键字传递，则更加适合新手上路以及使用陌生的滤镜，这样能降低出错的机率，它可以很清楚的让你知道，哪个值给了哪个参数变量，也可以强迫你去看滤镜的文档，知道每个参数变量到底是用来干啥的，这个取值是不是合理。

当然了，这两种方法是可以混搭的，但是直接传递必须在前面，一个个按照顺序排列，然后才是关键字传递，我们拿另一个滤镜来举例子（虽然有些超纲，但是领会意思就好）

```
#这三种写法也是等价的

#这是完全按照关键词传递
core.std.MaskedMerge(clipa=nonedge, clipb=edge, mask=mask, planes=[0,1,2], first_plane=False)
#这是完全按照位置直接传递
core.std.MaskedMerge(nonedge, edge, mask, [0,1,2], False)
#这是混搭流写法
core.std.MaskedMerge(nonedge, edge, mask, planes=[0,1,2], first_plane=False)
```

当然了，大部分滤镜第一个位置都是留给视频变量的，所以第一位的clip一般也不用指定，**但我推荐尽量使用更多关键字传递方法来写滤镜**，谁也不希望自己写的东西过一个月后来看就完全看不懂了，而且随着滤镜的更新，参数的位置可能会有变化，有些时候你的脚本丢给别人甚至会跑不起来（说的就是你，TCanny）。

尽管VS脚本写起来有很多自由，但是养成好的习惯是必要的，总结总结下来就是两点

1. 按顺序
2. 尽量用关键字传递

## 年轻人的第N个滤镜

既然知道怎么添加第一个，那就知道怎么添加第二个，第三个……第N个，如果你认真搞懂了上面的部分的话，那再加个降噪/去色带也不是问题吧。

不是问题？如果只是刚入门的话，确实还是有些小问题的！

降噪滤镜和去色带滤镜，需要在更高的色彩位深（bitdepth）下才能发挥它们全部的实力，如果仅仅在8bit下使用它们，效果会大打折扣，而参数的调整和如何最大程度减轻滤镜对画面的破坏这类细节问题也是成为优秀压制者必须知道的。

如果想要进阶，成为优秀的压制者，大胆的向“从入门到精通”进发吧！

光说不练假把式，我们从引入第一个脚本库——mvsfunc开始简单的说起吧：

`import mvsfunc as mvf`&#x20;

我们在这不深究mvsfunc里到底有啥函数，为啥要import成mvf，你乐意import成啥就是啥，在这里我们就只用它的一个函数`Depth()`来示范

```
import vapoursynth as vs
import mvsfunc as mvf
core = vs.get_core()
core.max_cache_size = 4000    #什么？怎么从2000变成4000了？滤镜用多了多给点内存肯定没错！

input = r'F:\233.mp4'
src8 = core.lsmas.LWLibavSource(input)
src16 = mvf.Depth(src8,16)    #这是啥？这是我们用mvf来把8bit输入源转换成16bit，你不信？可以试试src16.set_output()
nr = core.knlm.KNLMeansCL(src16, d=1, a=2, s=4, h=1.5, wmode="3", device_type="GPU", device_id="0")
db = core.f3kdb.Deband(nr, 12, 48, 32, 32, 0, 0, output_depth=16)
down8 = mvf.Depth(db,8)       #最后我们把16bit处理完的clip重新缩回8bit来加字幕和输出
out = core.sub.TextFile(down8,r'F:\subtitle.ass')
out.set_output()
```

如果懒得继续看的话，上面的复制一下直接用就好了，不加字幕就用#注释掉第12行

其中的KNLMeancCL是需要opencl运行环境的降噪滤镜，如果你是笔记本，可以试着把device\_id改成1来使用独显。f3kdb是非常常用的去色带滤镜，其中的参数我们暂时不具体讨论。

到这里，我们已经编写出了最基本的“读入-降噪/去色带-加字幕-输出”脚本，接下来我们就要使用编码器来接收我们的输出，并且编码成成品视频了！

### 滤镜干了什么，我看不出来啊！

为了能让人眼快速分辨出滤镜处理前后的画面区别，这里稍微提到一下`std.Interleave`它能将两个视频Clip合并成一个，例如第一个视频的0,1,2,3帧会变成第0,2,4,6帧，第二个视频的0,1,2,3帧会变成第1,3,5,7帧

我们在输出前加一句：

`out = core.std.Interleave([src8,down8])`

在vsedit中按f6，你可以看到输出的视频虽然时间不变，但是帧数和帧率翻倍了，这样我们就得到了一个经过Interleave的视频片段可供预览了

![](https://i.v2ex.co/5JBvNCbu.png)

预览的时候要记得帧数的双数是第一个视频，单数是第二个视频。


# 编码，输出，还有封装

我们假设你已经有了年轻人的第一个脚本了，是时候把脚本输出成视频流文件了！

## 视频输出及编码

假设我们已经有了上一章那样的脚本，那是时候输出它了，我们在这里介绍两种方式

### 使用vsedit输出：

如果你用vsedit编写了一个脚本，那么可以用它自带的“encode video”功能来编码视频，甚至使用“enqueue encode job”来创建一个队列，批量编码视频。

![encode界面](https://i.v2ex.co/M1IJz328l.png)

打开了encode界面之后，我们需要选择Header为Y4M，指定编码器可执行文件的位置，然后填写下面的Arguments，至于这个Arguments里面东西是啥，我们在下面解释，这里就直接贴出来方便你们复制好了：

`--demuxer y4m --preset veryslow --crf 21 --threads 16 --deblock -1:-1 --keyint 240 --min-keyint 1 --bframes 8 --ref 6 --qcomp 0.70 --rc-lookahead 70 --aq-strength 0.7 --me tesa --psy-rd 0.6:0.15 --no-fast-pskip --colormatrix bt709 --aq-mode 3 --merange 37 --input-depth 8 -o "output.mp4" -`

确认上面要设置的东西无误之后，直接按start，然后等着就好了，最后会在你的vsedit文件夹下输出一个output.mp4文件

### 使用CLI输出

尽管vsedit使用非常方便，但是总有些小问题非常恼人，所以在CLI上操作仍然是大部分人的选择，假设你已经把脚本保存到和portable版VS一起的目录（也就是vspipe.exe所在的目录，当然，x264也得在这目录里，不然你得手动指定你放x264那个目录下的这个可执行文件），我们在这个目录打开终端（注意一定要使用cmd，powershell是不支持pipeline的），然后输入：

`vspipe --y4m script.vpy - | x264 --demuxer y4m --preset veryslow --crf 21 --threads 16 --deblock -1:-1 --keyint 240 --min-keyint 1 --bframes 8 --ref 6 --qcomp 0.70 --rc-lookahead 70 --aq-strength 0.7 --me tesa --psy-rd 0.6:0.15 --no-fast-pskip --colormatrix bt709 --aq-mode 3 --merange 37 --input-depth 8 -o "output.mp4" -`

按下回车，不出意外就应该开始编码了！（不要忘记”|“前的一个”-“与最后一个”-“）

我们来看看这一串命令到底干了些什么吧！

首先是vspipe部分的`--y4m`与x264的`--demuxer y4m`，他们代表了分别是vspipe输出y4m格式的视频流以及x264使用y4m解析器来解析vspipe输出的视频流。

其次就是x264自己的一大堆编码参数，这一部分你可以到本教程编码器部分来详细了解每一个参数的作用。

最后`-o "output.mp4"`则指定了输出文件的名

#### 如果要使用VBR 2Pass指定码率呢？

很简单，运行这两行就完事儿了

`vspipe --y4m script.vpy - | x264 --demuxer y4m --preset veryslow --pass 1 --bitrate 5000 --threads 16 --deblock -1:-1 --keyint 240 --min-keyint 1 --bframes 8 --ref 6 --qcomp 0.70 --rc-lookahead 70 --aq-strength 0.7 --me tesa --psy-rd 0.6:0.15 --no-fast-pskip --colormatrix bt709 --aq-mode 3 --merange 37 --input-depth 8 -o "output.mp4" -`

`vspipe --y4m script.vpy - | x264 --demuxer y4m --preset veryslow --pass 2 --bitrate 5000 --threads 16 --deblock -1:-1 --keyint 240 --min-keyint 1 --bframes 8 --ref 6 --qcomp 0.70 --rc-lookahead 70 --aq-strength 0.7 --me tesa --psy-rd 0.6:0.15 --no-fast-pskip --colormatrix bt709 --aq-mode 3 --merange 37 --input-depth 8 -o "output.mp4" -`

可以注意到，其实这两遍唯一的区别就是`--pass 1 --pass 2`&#x20;

在跑2pass方面， 直接用CLI还是比VSEdit方便的。

### 我应该如何选择编码模式？

目前我们所能接触到的视频编码器有数种码率控制模式，大致有：CRF(恒定质量模式)、VBR(可变码率模式)、VBR 2Pass(二次编码的可变码率模式)，这些模式也各有优劣，下表将浅谈他们的优缺点：

| 名称 | CRF      | VBR 1Pass      | VBR 2Pass    |
| -- | -------- | -------------- | ------------ |
| 优点 | 速度快，质量好  | 可以准确控制体积       | 可以准确控制体积     |
| 缺点 | 不能准确控制体积 | 除了上面一个优点之外都是缺点 | 很慢，码率不到位还会翻车 |

如果没有严格的体积要求的话，crf永远是最好的选择，它会根据视频内容及画面复杂度动态调整码率分配，但是相同的crf下，复杂的视频（纹理丰富，动态激烈）体积会大很多（相对的高动态场景不会被编码器糊成一片）。而如果你想要一个特定码率的视频（比如传b站避免二压），那么就应该选择VBR模式，VBR 1Pass尽管速度快，但是码率分配非常不合理，经常出现烂帧现象，除非你的电脑是小霸王，不然尽量使用2Pass来进行编码。

## 音频抽取与编码

经过千辛万苦，我们的视频终于出炉后，就要和音频结合，成为一个可以分发播放的媒体文件了，但是我们首先得先把片源的音频抽取出来，通常针对不同的视频源的容器封装，我们需要不同的工具。

### 抽取：

#### MP4:

MP4是国际标准组织（ISO）指定的媒体文件容器，从mp4中提取音频流，我们通常使用ffmpeg来抽取音频流:

```
ffmpeg -i input.mp4 -c:a copy output.m4a
```

#### MKV：

从mkv里抽取可以使用ffmpeg，但更推荐用MeGUI的HD Streams Extractor

如果你知道你抽的源视频就一条音轨，打开终端直接来一个：

```
ffmpeg -i input.mkv -c:a copy output.mka
```

这样就完事儿了，但是MKV有的时候还有章节，更多条音轨等内容，所以更推荐使用MeGUI来抽取

我们在MeGUI的Tools里，选择HD Streams Extractor，然后具体用法看图就完事儿了

![界面简介](https://i.v2ex.co/3r4420x7.png)

#### TS/M2TS：

同MeGUI，要么你可以试试TSMuxeR，这软件的教学我就不写了。

### 编码：

#### AAC:

可以先参考这里来获得一些信息：<https://nazorip.site/archives/44/>

总的来说如果有aac编码需求，假设你的qaac以及ffmpeg都已经丢进PATH里了的话，你可以用ffmpeg来解码音频并且pipeline给qaac输出最终结果（注意，pipeline不能用powershell完成！）：

```
ffmpeg -i input.mka -hide_banner -map a:0 -f wav pipe:|qaac64 -v 192 --adts -s --ignorelength - -o "output.aac" --threading
```

#### FLAC：

如果你抽出来的音频流是无损的（基本都是PCM编码的wav），并且你也想无损的把它放进你的成品中，也想减小点体积的话，为什么不来试试神奇的flac呢？

flac的用法真的非常简单，因为本来就是无损编码也不需要指定什么码率，如果编码的是wav的话，指定个输入就行了：

```
flac input.wav
```

然后你就能获得和输入文件同名的flac文件了

当然了，如果你要把其他格式转换成flac的话（转换之前先想清楚自己做的是不是有损→无损的睿智操作），建议还是先用ffmpeg转换成wav再进行进一步操作

```
ffmpeg -i input.mka input.wav
flac input.wav
```

关于flac其他的参数，可以自行查阅doc，这里就不多做介绍了

## 封装

封装可以选择的容器有非常多，一般我们使用MP4和MKV做最终的封装容器

#### MP4：

封装MP4需要有很多的注意点，因为MP4是ISO组织定下的标准，所以它的兼容性要求非常高，能够封装进去的音频格式比较有限，例如flac什么的就没法封装进去，所以丢不进去就考虑把音频重新编码一下

但是不要考虑那么多，反正记住**mp4里抽出来的音频怎么都能封进mp4里，mkv里抽出来的就不一定了**就是了。

MP4封装仍然推荐MeGUI的MP4 Muxer，你可以在Tools-Muxer里找到它，至于怎么用看图就是了，记得按queue开始封装

![界面简介](https://i.v2ex.co/SMti27TH.png)

#### MKV:

MKV作为万能容器，基本上什么都能往里面塞，封装mkv的工具我们一般使用mkvtoolnix，你可以在工具章节的封装工具中找到它

MKVToolNix有一个GUI，使用起来也非常方便，使用方法看图就好

![看图就完事儿了](https://i.v2ex.co/KYQyfcJX.png)

如果你要添加章节等信息，你可以在输出页找到

添加好音频/视频轨道后，设置好轨道语言，默认轨道，轨道名称，确认没遗漏什么，没搞错什么之后，就按开始混流就好了

至此，一份完整的压制成品就出炉了


# 最后的检查与一些小技巧

到此为止，我们还没有对自己压制完成的成品进行检查，你可以自己完整看一遍，但更多时候比较懒还可以靠软件代劳，这里我们推荐用RPChecker对视频进行检查。

## 视频花屏检查

[RPChecker](/shen-yi-yang-de-gong-ju-men/zong-he-gong-ju#rp-checker)是vcb-s开发的检查视频是否出现花屏坏帧的工具，它需要FFMPEG或VS来正常运行，所以你可能需要手动指定这些软件的位置。

首先我们打开软件本体，由于RPChecker的FFMPEG模式有那么一点……不靠谱，所以建议将Portable版VS的文件夹（就是vspipe.exe在的那个文件夹）添加入系统PATH中，右键左上角的RP图标，右键选择`使用PSNR(VS)` 没有问题的话就能进入VS模式，当然你把rpchecker本体丢进Portable版VS里也行：

![选择vs模式，选完标题栏会变成Vapoursynth](https://i.v2ex.co/mY5Vp37P.png)

然后我们选择载入：

![](https://i.v2ex.co/2s27898g.png)

往右边那俩框里拖片源和成品，对照组也可以是其他你知道是没有问题的片源（比如jsum的）

载入完按确认，然后主界面按一下分析，等分析完后按图表：

![如果你看到的是这样，多半没什么问题](https://i.v2ex.co/Xlnt931r.png)

通常一些抗锯齿手段会导致staff片段（字幕）有较低的PSNR值，这通常没什么问题。

但是如果有某一帧或几帧特别特别低，那建议单独拿出来看一下，这些情况多半是中奖了\~\~（该返工啦）\~\~。

## 字幕及子集字体内封

许多时候我们的外挂字幕都必须附带上体积巨大的字体文件压缩包，我们可以使用[AssFontSubset](/shen-yi-yang-de-gong-ju-men/zong-he-gong-ju#assfontsubset)来只抽取字幕中使用的字的字形，生成子集字体。将子集字体和修改过的ass文件内封到mkv容器中，可以在受支持的播放器中完美的以没有字体问题的方式播放。

首先确认自己[fonttools](https://github.com/fonttools/fonttools)有没有正确安装，然后打开AssFontSubset，将准备好的ass拖到字幕文件一栏，在软件同一目录创建`fonts`文件夹，将字幕用的字体都丢进去，然后按一下开始就开始进行字体抽取操作了。

![可以一次性输入多个字幕文件](https://i.v2ex.co/7JhL6b2A.png)

如果没有报错，你就能在output文件夹获得成品字幕和子集字体了

我们打开mkvtoolnix，把**output中的字幕**加入字幕轨道，在附件中把子集字体拖进去，封装就好了

![你的附件应该长这样](https://i.v2ex.co/R9e9XN34.png)


# 番外篇：在Premiere输出时使用Vapoursynth


# 认识瑕疵

在没有更好的想法之前，可以暂时先参考vcb的[动漫视频常见的 瑕疵/缺陷介绍](https://vcb-s.com/archives/4738)

或者是guide.encode.moe中的瑕疵介绍：<https://guide.encode.moe/encoding/video-artifacts.html>

这些教程都已经讲解的非常详细了，但是在这里我还是要再写一些关于噪点相关的内容

## 关于噪点：

噪点也分好坏，好的噪点能够帮助作品塑造画面感/电影感，并且有遮盖色带的作用，而差的噪点则可能是由于编码器码率分配或者大动态导致变得非常难看，有那种血崩的感觉。

如果你看到的噪点像这样：

![](https://i.v2ex.co/Fv01zp5Z.png)

这样的噪点一般我们都认为是烂噪，需要直接抹除。我们可以通过去色带滤镜+降噪滤镜联合使用来解决，在处理之后根据情况决定是否需要再加一些噪点保持一致的目视感

而细腻的噪点我们一般建议尽量合理的保留，可以稍微控制噪点强度以保证编码结果体积不会爆炸，但如果你的作品编码时选用crf19以上，码率预算不是很充足的话，我还是建议把噪点抹干净。噪点本身属于高频信息，非常吃码率。用带着大量噪点的源喂给编码器，会让编码器将本来就窘迫的码率分配给平面噪点，而纹理和线条就会被带崩。


# 认识画面的三要素

说到底，我们所做的一切滤镜操作，都是为了针对画面的三要素进行调整，评价一部番的画面也是。通常推荐从这三个方面来入手。需要注意的是，这样的评价方法常常只适用于动画！

在处理动画时，我们的滤镜选用往往要与动画的风格联系到一起，盲目的堆积叠加滤镜会破坏整体画面感，让动画看起来根本就不是原来的样子，记住，优秀的BDRip一定是在原来的画风上修复瑕疵，减小体积，而不是大刀阔斧的对画面进行锐化，地图炮降噪！

## 线条

顾名思义，线条是动画组成中异常重要的部分，线条的锐利与否，是否有锯齿等都直接影响到了整个画面的观感，它们属于视频中的高频信息，同样也是编码器经常爆破的对象之一，而太锐的线条会让视频成品体积居高不下，所以我们要合理的处理线条部分。

另外，一切的收线，锐化操作都不可避免地会对使原盘画面的整体画风观感产生影响，在进行这些操作之前请一定要慎重考虑，评估结果的画风变化是否在可接受的范围内，我认为优秀的bdrip应该在保留原盘画风和整体观感的前提下修复瑕疵，控制体积，而不是大刀阔斧的做这类操作。

![原来它长这样](https://i.v2ex.co/2pdxQ568.png)

请不要过度收线和锐化，这些操作的结果真的就是“清晰”么？

![这样真的好吗？](https://i.v2ex.co/43z7Qrj2.png)

结果仁者见仁智者见智了，如果你觉得锐化真的好，那你可能还真的挺适合moozzi2的BDRip的（咳）

## 纹理

初次听到这个词可能很陌生，但简单解释的话，纹理就是画面中出现的“细节”部分，而举例子的话，新海诚的作品一定是首当其冲的，这种超现实的画风，一切都向着“真”靠近的，一定会有大量纹理细节。

![这张图有着异常丰富的纹理](https://i.v2ex.co/1ctMUXOd.png)

纹理是处理线条平面过程中最容易被误伤的受害者，也是编码器编码过程中容易爆破的对象之一，所以在处理其他部分时也要关注纹理细节有没有被涂抹，如果是有大量纹理的视频（例如新海诚全家桶），那更需要一些调整参数来保护纹理。

## 平面

平面顾名思义，其实就是大量的“色块”这类元素。

![墙和窗这类地方都算是平面](https://i.v2ex.co/7Wh2sC5s.png)

这类元素通常都不怎么吃码率，上面最多盖着一层噪点，但是需要注意的是，平面往往是最容易出现色带的地方，观察平面问题通常以观察色带为主


# 色彩/位深

可能有许多人会问：我用的8bit显示器，还要看10bit的视频，有意义吗？

## YUV与RGB

我们的显示器使用的一般都是RGB色彩模型，RGB三个色彩屏幕都属于强度，而视频所使用的YUV则不然。

YUV其实就是将RGB经过一定的转换后得到的的另一种表示方式，具体如何转换我们在这里暂且不说，有兴趣的可以在[这里](https://en.wikipedia.org/wiki/YUV)了解一下。

YUV模型有很多具体的实现方式，而日常我们接触最多的就是YCbCr模型。它把RGB转换成一个亮度（Y）和两个色度（CbCr）。

![Y平面](https://i.v2ex.co/Pp8ZCS0r.png)

![只有Cb色度](https://i.v2ex.co/wXyXf104.png)

![只有Cr色度](https://i.v2ex.co/5x23TI82.png)

由于人类对亮度的敏感程度远高于色度，因此我们能看到的有效信息大部分来源于亮度（也就是Y）平面，而正因为YUV模型把大部分有效信息都集中在了Y平面，所以我们可以牺牲色度平面不必要的信息来大大的节省传输过程中所占的信息量，这就引出了色度采样的概念。

## 色度采样（Chroma subsampling）

嘿，既然人眼对色彩信息不敏感，为什么我们不想办法削减色度平面信息呢！

所以，我们今天接触到的绝大多数视频都是以一种叫做`4:2:0`的方式进行色度半采样的，最简单粗暴的理解就是：色度信息的分辨率是亮度信息的四分之一，也就是如果一个`YUV`，以`4:2:0`进行色度抽样的视频的分辨率是`1920x1080`，那它的色度分辨率就只有`960x540`。如果你想对此有更深入的了解，可以从[维基百科的色度采样词条](https://zh.wikipedia.org/wiki/%E8%89%B2%E5%BA%A6%E6%8A%BD%E6%A0%B7)了解它。

![图自维基百科-色度采样词条](https://i.v2ex.co/xJ7C55a0.png)

通过将色度信息分辨率减半，我们就能获得观感差不了多少的YUV色彩模型的视频了。在播放过程中，播放器需要将色度平面通过算法拉伸到和亮度平面一样的分辨率，再转为RGB喂给显示器输出，如果你使用的是madvr，随便打开一个420采样的视频并且保持原本分辨率播放，可以不难发现madvr仅对色度平面进行了缩放，而不会对亮度平面进行缩放。这样的缩放算法好坏其实也影响了最后回放时的观感。

![madvr利用NGU拉伸色度平面，而没有对亮度平面进行处理](https://i.v2ex.co/ugiGK0BN.png)

## 位深（BitDepth）

说了那么多，我们终于回到了位深。

根据计算机基础知识，8bit位深的每个通道的每个像素点都由8位二进制组成，一个通道的像素点可以表示256种强度，而10bit则可以表示1024种。根据vcb-s的说法：“在最终输出是8bit RGB的前提下，10bit YUV比起8bit YUV依旧具有精度优势的原因就在这里。事实上，8bit YUV转换后，覆盖的精度大概相当于8bit RGB的26%，而10bit转换后的精度大约可以覆盖97%”，我们也能在此端倪10bit的重要性。而10bit相对于8bit，优势不仅在于色彩精度，在编码器量化编码的阶段，高位深带来的优势也非常明显，这里暂且不谈。

但是，位深的另一个关键作用在于：高精度可以防止色带！

尽管8bit RGB已经足够表现 [Rec. 709](https://en.wikipedia.org/wiki/Rec._709)或者 [sRGB](https://en.wikipedia.org/wiki/SRGB)的颜色了，但是，嘿！我们这可是`YUV420P8`呢，色带有些时候总是不可避免的，甚至连8bit RGB都是如此！

如果你有一块8bit或更低色彩深度的显示器，在正确打开[这张图](https://drive.google.com/open?id=1JoLezJJGeY8jAn0PXiAs-RCdaP9TLUUL)的时候，就会看到明显的色带！

那么，我们有什么办法，可以阻止色带的产生呢？那就请看下集：色带处理


# 色带处理

## 色带是如何产生的？

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

首先，色带往往出现在画面中的平面部分，大面积的色块往往是色带集中出现的区域，同时，一些渐变场景也常常会看到它。在现在的显示设备上，我们常常采用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

待续


# 认识交错与反交错


# 编码器


# 写在前面


# X264


# X265


# 简介


# 脚本百科


# mvsfunc

mvsfunc是\~\~（压片业界祖师爷）\~\~mawen1250写的一个封装脚本，里面有非常多常用的函数可供使用，这里会一一列举

## 脚本依赖：

mvsfunc需要的滤镜有：

* `fmtconv`
* `BM3D`

## 所含函数：

### `Depth()`:

此函数主要用于转换输入的视频色彩位深（实际上就是一个fmtc.resample的wrapper），并在转换时可以指定抖动（dither）算法，同时也有简单的Full Range→Limited Range转换功能

#### 参数一览：

`Depth(input, depth=None, sample=None, fulls=None, fulld=None, dither=None, useZ=None, prefer_props=None, ampo=None, ampn=None, dyn=None, staticnoise=None)`

#### &#x20;参数解释：

* `input`：
  * 参数变量类型需为`VideoNode`
  * 几乎所有Format都可以丢进本函数，反正基本什么都能用
* `sample`：
  * 参数变量类型为`int`
  * 数值范围为`1-16/32`
  * 需要注意的是1-7bit的数据都会被储存为8bit，默认与输入Clip的位深相同
* `fulls`：
  * 参数变量类型为`bool`
  * 该项指定输入片源是否为Full Range，默认为None，但是推荐YUV/GRAY输入时使用`False`，RGB/YCgCo输入时使用`True`
* `fulld`：
  * 参数变量类型为`bool`
  * 该项指定输出片源是否为Full Range，默认与`fulls`的值相同、
* `dither`：
* `useZ`：
* `prefer_props`：
* `ampo`：
* `ampn`：
* `dyn`：
* `staticnoise`：

#### 使用贴士：

这个函数最主要的功能就是方便的转换Clip的位深，如果你设置了参数`fulls=False,fulld=True`那么本函数会做一次YC伸张，将Limited Range转换为Full Range输出，反过来如果设置了`fulls=True,fulld=False`则会进行一次YC压缩，将Full Range转换为Limited Range输出，但是在转换方面，我们更推荐使用`std.Levels`来进行，因为有许多视频的Range可能会非常不规范，需要单独调整Y和UV。

如果你最终输出成品为8bit，则可以通过


# havsfunc

havsfunc是由HolyWu写的脚本，将Avisynth的一些函数迁移到VapourSynth中

## 脚本依赖：

havsfunc需要的脚本有：

* `adjust`
* `mvsfunc`
* `nnedi3_resample`

havsfunc需要的滤镜有：

* `AddGrain`
* `CTMF`
* `DCTFilter`
* `Deblock`
* `DFTTest`
* `EEDI2`
* `EEDI3`
* `FFT3DFilter`
* `FluxSmooth`
* `fmtconv`
* `Hqdn3d`
* `KNLMeansCL`
* `MVTools`
* `NNEDI3`
* `NNEDI3CL`
* `SangNom`
* `SVP`
* `TTempSmooth`
* `ZNEDI3`

## 所含函数：

### `Deblock_QED()`：

去块函数，相比其他去块函数在每一帧中使用相同的去块模式，`Deblock_QED()`以8x8块为单位进行自适应去块

#### 参数一览：

`Deblock_QED(clp, quant1=24, quant2=26, aOff1=1, bOff1=2, aOff2=1, bOff2=2, uv=3)`

#### 参数解释：

* `clp`：
  * 参数变量类型为`VideoNode`
  * 支持8-16bit整型，不支持32bit浮点型（不报错，但输出异常），支持RGB色彩空间
* `quant1`：
  * 参数变量类型为`int`
  * 参数范围为`0-60`
  * 块边缘的去块强度
  * 数值越大，去块强度越大
* `quant2`：
  * 参数变量类型为`int`
  * 参数范围为`0-60`
  * 块内的去块强度
  * 数值越大，去块强度越大
* `aOff1`：
  * 参数变量类型为`int`
  * `quant1`修饰符，用于微调块检测的阈值
  * 数值越大，意味着被检测为“块”的可能性越大
  * 当`quant1` + `aOff1`小于16时，无去块作用
* `aOff2`：
  * 参数变量类型为`int`
  * `quant2`修饰符，用于微调块检测的阈值
  * 数值越大，意味着被检测为“块”的可能性越大
  * 当`quant2` + `aOff2`小于16时，无去块作用
* `bOff1`：
  * 参数变量类型为`int`
  * 另一个`quant1`修饰符，用于微调块检测的阈值与去块强度
  * 数值越大，意味着被检测为“块”的可能性越大，去块强度也会相应增强
  * 当`quant1` + `bOff1`小于16时，无去块作用
* `bOff2`：
  * 参数变量类型为`int`
  * 另一个`quant2`修饰符，用于微调块检测的阈值与去块强度
  * 数值越大，意味着被检测为“块”的可能性越大，去块强度也会相应增强
  * 当`quant2` + `bOff2`小于16时，无去块作用
* `uv`：
  * 参数变量类型为`int`
  * 有效参数范围为`-1,1,2,3`，数值小于-1时视为-1，数值为0时视为1，数值大于3时视为3
  * 设定U、V平面的处理方式
  * `uv`= 3，对U、V平面做与Y平面一致的去块处理；`uv`= 2，不处理U、V平面（速度最快）；`uv`= 1、-1时，对U、V平面直接使用`deblock.Deblock()`函数做去块处理，其中`uv`= 1时使用`quant1、aOff1、bOff1`参数，`uv`= -1时使用`quant2、aOff2、bOff2`参数

#### 使用贴士：

对于去块，不仅可以使用`Deblock_QED()`等去块函数，还可以尝试`f3kdb.Deband()`等去色带函数，直观上来讲，这两类函数都可以实现“涂抹”的效果

### `GSMC()`：

时域降噪函数，全称为GrainStabilizeMC，降噪力度小、速度快，仅对输入源与空域降噪结果的差值进行时域降噪，并使用`rgvs.Repair()`函数和亮度自适应mask进行后处理

#### 参数一览：

`GSMC(input, p=None, Lmask=None, nrmode=None, radius=1, adapt=-1, rep=13, planes=[0, 1, 2], thSAD=300, thSADC=None, thSCD1=300, thSCD2=100, limit=None, limitc=None)`

#### 参数解释：

* `input`：
  * 参数变量类型为`VideoNode`
  * 支持8-16bit整型，不支持32bit浮点型，不支持RGB色彩空间
* `p`：
  * 参数变量类型为`VideoNode`
  * 格式需要与`input`保持一致
  * 该项为空域降噪结果，用来与输入源做差，获得时域降噪的对象
  * 默认为对输入源做均值滤波或`sbr()`降噪
* `Lmask`：
  * 参数变量类型为`VideoNode`
  * 该项为亮度mask
* `nrmode`：
  * 参数变量类型为`int`
  * 该项参数设定（作为预处理的）空域降噪模式
  * 当`nrmode`≤ 0时，对输入源做均值滤波；当`nrmode`> 0时，对输入源做`haf.sbr()`降噪，参数含义与`haf.sbr()`函数的`r`相同
  * 当参数`p`非默认（即手工指定了空域降噪结果）时，该参数无效
* `radius`：
  * 参数变量类型为`int`
  * 参数范围为`1,2,3`
  * 该项参数设定Temporal radius，以控制时域降噪的力度，数值越大则降噪力度越大、速度越慢
* `adapt`：
  * 参数变量类型为`int`
  * 该项参数用于控制后处理时的亮度mask
  * 当`adapt`≤ -1且未指定`Lmask`时，函数不做后续的亮度自适应限制处理
* `rep`：
  * 参数变量类型为`int`
  * 该项参数用于设定后处理时`rgvs.Repair()`的模式
  * 当`rep`≤ 0时，函数不做`rgvs.Repair()`；当`rep`> 0时，参数含义与`rgvs.Repair()`函数的`mode`相同
* `planes`：
  * 参数变量类型为`int`
  * 参数范围为`0,1,2`
  * 通用参数，用来选择被处理的平面
* `thSAD`
* `thSADC`
* `thSCD1`
* `thSCD2`
* `limit`
* `limitc`：
  * 以上六个参数为时域处理的通用参数

#### 使用贴士：

相比于其他降噪方法，`GSMC()`的降噪力度小，可以作为降低码率的一种手段

### `FastLineDarkenMOD()`：

加深/加黑线条函数，仅对亮度平面进行处理，速度快，不会对边缘/轮廓造成破坏，同时具有可选的收线功能，但使用收线功能会降低速度

#### 参数一览：

`FastLineDarkenMOD(c, strength=48, protection=5, luma_cap=191, threshold=4, thinning=0)`

#### 参数解释：

* `c`：
  * 参数变量类型为`VideoNode`
  * 支持8-16bit整型和32bit浮点型，不支持RGB色彩空间
* `strength`：
  * 参数变量类型为`int`
  * 参数范围为`0-256`
  * 控制线条加深的程度，数值越大，加深程度越大
* `protection`：
  * 参数变量类型为`int`
  * 参数范围为`0-50`
  * 保护暗场线条，防止暗场线条变得过暗
  * 数值越大，保护程度越大，类似阈值
* `luma_cap`：
  * 参数变量类型为`int`
  * 参数范围为`0-255`
  * 该项参数基于亮度调整线条加深的程度，抑制以下两种问题：加深线条时被周围高亮度像素干扰，对亮场的灰色线条进行不必要的加深
  * 亮度高于`luma_cap`的像素会被视为亮度与`luma_cap`一致，降低该参数会降低线条加深的程度，当`luma_cap`= 255时禁用该功能
* `threshold`：
  * 参数变量类型为`int`
  * 该项参数为阈值，低于阈值的像素不会被处理
  * 当`threshold`= 0时禁用该功能，默认值为4，该数值即为推荐值，能够避免大量随机像素被不必要地加深，当`threshold`过大时会有部分线条未被加深
* `thinning`：
  * 参数变量类型为`int`
  * 有效参数范围为`0-256`，数值小于0时视为0
  * 该项参数用于细化线条/收线，但收线后会使线条周围像素轻微变暗
  * 当`thinning`≤ 0时禁用该功能，禁用该功能可以大幅提高速度，默认即为禁用

#### 使用贴士：

`FastLineDarkenMOD()`可以作为增强目视效果的手段，特别是对于线条柔和、“发虚”的画面，加深线条可较为明显地提升观感，但最好慎重使用，不要过火


# muvsfunc


# 反交错/抗锯齿滤镜


# 源滤镜


# ffms2


# lsmas


# dgdecodenv


# avisource


# eedi2


# eedi3


# nnedi3


# 去色带滤镜


# f3kdb


# gradfun3


# 放大/缩小滤镜


# 综述


# Bilinear


# Bicubic


# Spline


# Lanczos


# Nnedi3


# Dpid


# Waifu2x


# 降噪滤镜


# KNLMeansCL


# RemoveGrain


# BM3D/V-BM3D

BM3D是一种三维块匹配算法，主要用于图像降噪。在非深度学习算法中，BM3D是效果最好的降噪算法之一，但速度较慢。V-BM3D的V指Video，在BM3D的基础上增加了时域处理。

BM3D算法由Kostadin Dabov等人提出，VapourSynth版BM3D滤镜由mawen1250编写。在文末对BM3D算法论文做了简要介绍，有兴趣可以看一下。

这篇教程的大部分内容均翻译自[VapourSynth-BM3D](https://github.com/HomeOfVapourSynthEvolution/VapourSynth-BM3D)的README。

## 总体指南：

BM3D滤镜并非单一函数，它包含了辅助函数、核心处理函数和后处理函数。在实际使用中，往往需要综合调用多个函数。

为简单起见，`mvsfunc`脚本的`BM3D()`函数提供了开箱即用的支持，详情可移步下文的“使用贴士”部分。

在“函数详解”部分，对一些专业性稍强的名词加注了英文，这些名词可以在教程最后的“扩展阅读”中查阅，也可以直接忽略（雾）。

### 滤镜依赖：

BM3D需要的滤镜：

* `libfftw3f-3.dll`

### 支持格式：

* 采样类型：8-16bit整型、32bit浮点型
* 色彩空间：灰度、RGB、YUV、YCoCg
* 色度半采样：当色度平面被处理时，不支持色度半采样，仅支持YUV444

**Note**：需要注意，上述支持格式是针对BM3D滤镜整体而言，由于BM3D滤镜包含用于转换色彩空间的辅助函数，并非所有BM3D滤镜下的函数均支持以上格式，详情可查阅下文的“函数详解”部分。

### 函数概览：

BM3D滤镜的命名空间为`bm3d`，即调用函数时的写法为`core.bm3d.function()`。

BM3D滤镜共包括7个函数，功能和具体参数会在下文的“函数详解”部分介绍，包括：

* 辅助函数：`RGB2OPP()`、`OPP2RGB()`
* BM3D核心处理函数：`Basic()`、`Final()`
* V-BM3D核心处理函数：`VBasic()`、`VFinal()`
* V-BM3D后处理函数：`VAggregate()`

### 重要提示：

* BM3D滤镜在opponent色彩空间（缩写为OPP，一种具有简单且直观系数矩阵的YUV色彩空间）的降噪效果最好，显著优于其他色彩空间。因此推荐使用RGB为输入，经BM3D滤镜的辅助函数转换为OPP并在OPP下处理，最后再转回RGB输出。
* 按上述方法操作，也避免了在RGB与OPP间频繁地转换。诚然，与BM3D核心处理相比，上述转换对速度的影响微乎其微，但可能会增大内存占用（特别是`sample`=1，即采用32bit浮点数处理时）。
* 基于简洁性与处理精度的考虑，不支持色度半采样。事实上，与BM3D核心处理相比，在YUV4xx与RGB间转换对速度的影响可以忽略。
* 对于V-BM3D系列函数，以RGB为输入时，函数总是以OPP为输出，因此需要手动调用`bm3d.OPP2RGB()`函数将处理结果转换为RGB。
* 对于V-BM3D系列函数，若某一平面未被处理（`sigma`=0），该平面的输出结果将为垃圾，必须使用`std.ShufflePlanes()`函数将处理结果与输入源合并。基于同样的原因，对于RGB，若想保留未处理的平面，必须先将其转换为OPP。

## 函数详解：

### 辅助函数：

BM3D滤镜辅助函数用于RGB色彩空间与OPP色彩空间的相互转换。

#### `RGB2OPP()`：

将RGB色彩空间转换至OPP色彩空间。

**参数一览：**

`bm3d.RGB2OPP(clip input[, int sample=0])`

**参数解释：**

* `input`：
  * 参数变量类型为`VideoNode`
  * 仅支持RGB色彩空间
* `sample`：
  * 参数变量类型为`int`
  * 设定输出采样类型，类似`bool`型参数
    * `sample`=0：输出16bit整型（默认）
    * `sample`=1：输出32bit浮点型

#### `OPP2RGB()`：

将OPP色彩空间转换至RGB色彩空间。

**参数一览：**

`bm3d.OPP2RGB(clip input[, int sample=0])`

**参数解释：**

* `input`：
  * 参数变量类型为`VideoNode`
  * 仅支持YUV色彩空间
  * 如前文所述，OPP为YUV的一种，输入通常意义的YUV不报错但颜色不正确；为使结果有意义，应当仅以OPP为输入
* `sample`：
  * 参数变量类型为`int`
  * 设定输出采样类型，类似`bool`型参数
    * `sample`=0：输出16bit整型（默认）
    * `sample`=1：输出32bit浮点型

### BM3D核心处理函数：

#### `Basic()`：

基础降噪函数，BM3D核心处理函数之一，其输出结果可以作为BM3D另一核心处理函数`bm3d.Final()`的参考源，也可直接作为降噪结果使用。

**参数一览：**

`bm3d.Basic(clip input[, clip ref=input, string profile="fast", float[] sigma=[10,10,10], int block_size, int block_step, int group_size, int bm_range, int bm_step, float th_mse, float hard_thr, int matrix=2])`

**参数解释：**

* `input`：
  * 参数变量类型为`VideoNode`
  * 输出结果与输入源保持相同格式
* `ref`：
  * 参数变量类型为`VideoNode`
  * 参考源，用于块匹配
  * 若未设定该参数，则以输入源为参考源
* `profile`：
  * 参数变量类型为`string`
  * 设定预设模式，可以对后续其他参数预设不同的默认值，详见下文表格
  * 各预设模式的含义如下：
    * `fast`：快速模式（默认）
    * `lc`：低复杂度模式
    * `np`：正常模式
    * `high`：高强度模式
    * `vn`：超强噪点模式
* `sigma`：
  * 参数变量类型为`float`数组
  * 设定各平面的降噪强度，单个平面范围为`[0, +inf)`，默认值为`[10, 10, 10]`
  * BM3D算法对`sigma`参数非常敏感，应当认真根据输入源调整该参数
  * 该参数源自高斯白噪声模型
  * 当某一平面参数为0时，不处理该平面
* `block_size`：
  * 参数变量类型为`int`
  * 设定块大小，块大小为`block_size*block_size`，范围为`[1, 64]`
  * 块为BM3D的基本处理单元，越大的块速度越慢，但块越大则可设定更大的步长（`block_size`），从而减少要处理的块
  * 在不同预设模式下，默认值均为8，兼顾速度和质量
* `block_step`：
  * 参数变量类型为`int`
  * 设定两个参考块间的距离，即步长，范围为`[1, block_size]`
  * 步长越小，在处理中会使用更多的参考块，速度也越慢
* `group_size`：
  * 参数变量类型为`int`
  * 设定每个组（group）中相似块的最大数量，范围为`[1, block_size]`
  * 参数值越大，在一个group中包含的块越多，则变换后group的稀疏程度越大，降噪效果越强，速度也越慢
  * 当`group_size`设为1时，不进行块匹配，此时进行的降噪类似无时域处理的DFTTest降噪
* `bm_range`：
  * 参数变量类型为`int`
  * 设定块匹配搜索的范围，范围为`[1, +inf)`
  * 参数值越大，速度越慢，寻找到相似块的概率越大
* `bm_step`：
  * 参数变量类型为`int`
  * 设定块匹配搜索中两搜索位置间的距离，范围为`[1, bm_range]`
  * 参数值越小，速度越慢
  * 当`bm_step`设为1时，进行逐个搜索
* `th_mse`：
  * 参数变量类型为`float`
  * 设定块匹配中均方误差的阈值，范围为`[0, +inf)`
  * 参数值越大，会有更多的块被匹配；参数值过大会导致一个group中出现低相似度的块，进而导致纹理和细节损失
  * 若输入源噪点很重，应增大该参数值，默认值会根据`sigma[0]`自行调整
* `hard_thr`：
  * 参数变量类型为`float`
  * 设定频域滤波的硬阈值（hard-thresholding），范围为`(0, +inf)`
  * 参数值越大，频域中的硬阈值滤波越强
  * 通常而言，若想降低降噪强度，调整`sigma`参数要好于调整`hard_thr`参数
* `matrix`：
  * 参数变量类型为`int`
  * 为灰度、YUV、YCoCg输入源设定系数矩阵，默认值为2
  * 由于YUV色彩空间未进行归一化，因此在BM3D内使用的`sigma`需要根据系数矩阵进行归一化，这非常重要，会影响结果的准确性
  * 基于ISO/IEC 14496-10标准，不同参数值对应的系数矩阵如下
    * 0：`GRB`
    * 1：`bt709`
    * 2：未指定，根据输入源的尺寸在`smpte170m`、`bt709`、`bt2020nc`中选择
    * 4：`fcc`
    * 5：`bt470bg`
    * 6：`smpte170m`
    * 7：`smpte240m`
    * 8：`YCgCo`（在色彩空间为YCgCo时应使用该项）
    * 9：`bt2020nc`
    * 10：`bt2020c`
    * 100：`OPP`（在使用`bm3d.RGB2OPP()`函数将RGB色彩空间转换为OPP色彩空间后，应使用该项）

#### `Final()`：

最终降噪函数，BM3D核心处理函数之二，需要使用`bm3d.Basic()`的处理结果作为参考源。

**参数一览：**

`bm3d.Final(clip input, clip ref[, string profile="fast", float[] sigma=[10,10,10], int block_size, int block_step, int group_size, int bm_range, int bm_step, float th_mse, int matrix=2])`

**参数解释：**

* `input`：
  * 参数变量类型为`VideoNode`
  * 输出结果与输入源保持相同格式
* `ref`：
  * 参数变量类型为`VideoNode`
  * 参考源，用于块匹配，并作为维纳滤波的参考
  * 该项参数必须设定，按照原始BM3D算法，参考源应设定为`bm3d.Basic()`的处理结果
  * 该项参数也可设定为其他任何恰当的降噪结果，并通过本函数进行精细处理
* `profile`、`sigma`、`block_size`、`block_step`、`group_size`、`bm_range`、`bm_step`、`th_mse`、`matrix`：
  * 与`bm3d.Basic()`相应参数含义相同

### V-BM3D核心处理函数：

V-BM3D在BM3D的基础上增加了时域处理。具体而言，在块匹配和协同滤波时，不仅会使用当前帧，还会使用前后多个帧。

由于处理结果被返回到多个帧当中，因此必须将处理过程分为两个阶段。第一阶段可以称为核心处理，类似BM3D，使用`bm3d.VBasic()`/`bm3d.VFinal()`，获得中间数据，格式为32bit浮点数，高度扩大为输入源的`(radius * 2 + 1) * 2`倍；第二阶段可以称为后处理，使用`bm3d.VAggegrate()`函数，Aggegrate意为聚合，将中间数据转换为正常图像。

由于处理中使用了多个帧，且中间数据使用32bit浮点数和倍增的尺寸，V-BM3D内存占用很大。

#### `VBasic()`：

基础降噪函数，V-BM3D核心处理函数之一，与`bm3d.Basic()`类似，增加了时域处理。

**参数一览：**

`bm3d.VBasic(clip input[, clip ref=input, string profile="fast", float[] sigma=[10,10,10], int radius, int block_size, int block_step, int group_size, int bm_range, int bm_step, int ps_num, int ps_range, int ps_step, float th_mse, float hard_thr, int matrix=2])`

**参数解释：**

* `input`、`ref`：
  * 与`bm3d.Basic()`相应参数含义相同
* `profile`、`sigma`、`block_size`、`block_step`、`group_size`、`bm_range`、`bm_step`、`th_mse`、`matrix`：
  * 与`bm3d.Basic()`相应参数含义相同
* `radius`：
  * 参数变量类型为`int`
  * 设定降噪的时间半径，范围为`[1, 16]`
  * 增大时间半径仅会给块匹配与聚合过程增加微小的计算量，不会影响协同滤波，但成倍增加内存占用
  * 只要内存足够用，这个参数就可以往大了调
* `ps_num`：
  * 参数变量类型为`int`
  * 设定预测搜索时匹配位置的数量，范围为`[1, group_size]`
  * 增大该参数值可以提高匹配到相似块的可能性，而且仅需增加很小的计算成本；但在原始的MATLAB实现中，除“lc”外，其余所有配置文件均将该参数固定为2，也许过大的数值对质量不总是有利的？
* `ps_range`：
  * 参数变量类型为`int`
  * 设定预测搜索块匹配的范围，范围为`[1, +inf)`
* `ps_step`：
  * 参数变量类型为`int`
  * 设定预测搜索块匹配中两搜索位置间的距离，范围为`[1, ps_range]`
  * 单个帧中各参考块的预测搜索位置最大数量为`(ps_range / ps_step * 2 + 1) ^ 2 * ps_num`

#### `VFinal()`：

最终降噪函数，V-BM3D核心处理函数之二，与`bm3d.Final()`类似，增加了时域处理。

**参数一览：**

`bm3d.VFinal(clip input, clip ref[, string profile="fast", float[] sigma=[10,10,10], int radius, int block_size, int block_step, int group_size, int bm_range, int bm_step, int ps_num, int ps_range, int ps_step, float th_mse, int matrix=2])`

**参数解释：**

* `input`、`ref`：
  * 与`bm3d.Final()`相应参数含义相同
* `profile`、`sigma`、`block_size`、`block_step`、`group_size`、`bm_range`、`bm_step`、`th_mse`、`matrix`：
  * 与`bm3d.Basic()`相应参数含义相同
* `radius`、`ps_num`、`ps_range`、`ps_step`：
  * 与`bm3d.VBasic()`相应参数含义相同

### V-BM3D后处理函数：

#### `VAggregate()`：

在调用`bm3d.VBasic()`和`bm3d.VFinal()`函数后，必须调用该函数，以完成聚合（Aggregation）。

如果以RGB格式为`bm3d.VBasic()`或`bm3d.VFinal()`函数的输入，需要在调用该函数后，再调用`bm3d.OPP2RGB()`。

**参数一览：**

`bm3d.VAggregate(clip input[, int radius=1, int sample=0])`

**参数解释：**

* `input`：
  * 参数变量类型为`VideoNode`
  * 输入源应为`bm3d.VBasic()`或`bm3d.VFinal()`函数的输出结果
* `radius`：
  * 参数变量类型为`int`
  * 必须与`bm3d.VBasic()`或`bm3d.VFinal()`设定的`radius`相同
* `sample`：
  * 参数变量类型为`int`
  * 设定输出采样类型，类似`bool`型参数
    * `sample=0`：输出16bit整型（默认）
    * `sample=1`：输出32bit浮点型

### 各预设模式的默认参数：

在BM3D滤镜的四个核心处理函数`bm3d.Basic()`、`bm3d.Final()`、`bm3d.VBasic()`、`bm3d.VFinal()`中，通过设定`profile`参数可以选择不同的预设模式，这些预设模式对函数其他参数给定了不同的默认值，详见下面三个表格

```
bm3d.Basic / bm3d.Final / bm3d.VBasic / bm3d.VFinal
----------------------------------------------------------------------------
| profile || block_size | block_step | group_size  | bm_range    | bm_step |
----------------------------------------------------------------------------
| "fast"  || 8/8/8/8    | 8/7/8/7    | 8/8/8/8     | 9/9/7/7     | 1/1/1/1 |
| "lc"    || 8/8/8/8    | 6/5/6/5    | 16/16/8/8   | 9/9/9/9     | 1/1/1/1 |
| "np"    || 8/8/8/8    | 4/3/4/3    | 16/32/8/8   | 16/16/12/12 | 1/1/1/1 |
| "high"  || 8/8/8/8    | 3/2/3/2    | 16/32/8/8   | 16/16/16/16 | 1/1/1/1 |
| "vn"    || 8/11/8/11  | 4/6/4/6    | 32/32/16/16 | 16/16/12/12 | 1/1/1/1 |
----------------------------------------------------------------------------
```

```
bm3d.VBasic / bm3d.VFinal
---------------------------------------------------
| profile || radius | ps_num | ps_range | ps_step |
---------------------------------------------------
| "fast"  || 1/1    | 2/2    | 4/5      | 1/1/1/1 |
| "lc"    || 2/2    | 2/2    | 4/5      | 1/1/1/1 |
| "np"    || 3/3    | 2/2    | 5/6      | 1/1/1/1 |
| "high"  || 4/4    | 2/2    | 7/8      | 1/1/1/1 |
| "vn"    || 4/4    | 2/2    | 5/6      | 1/1/1/1 |
---------------------------------------------------
```

```
bm3d.Basic & bm3d.VBasic / bm3d.Final & bm3d.VFinal
--------------------------------------------------------------
| profile || th_mse                              | hard_thr  |
--------------------------------------------------------------
| "fast"  || sigma[0]*80+400   / sigma[0]*10+200 | 2.7 / NUL |
| "lc"    || sigma[0]*80+400   / sigma[0]*10+200 | 2.7 / NUL |
| "np"    || sigma[0]*80+400   / sigma[0]*10+200 | 2.7 / NUL |
| "high"  || sigma[0]*80+400   / sigma[0]*10+200 | 2.7 / NUL |
| "vn"    || sigma[0]*150+1000 / sigma[0]*40+400 | 2.8 / NUL |
--------------------------------------------------------------
```

## 使用贴士：

### 直接使用BM3D滤镜：

综合前文的介绍，在使用BM3D滤镜时往往要综合调用多个函数，下面给出一些示例。

#### 使用BM3D系列函数：

* 输入源为YUV格式，仅使用基础降噪函数进行降噪，并对Y、U、V平面设定不同的`sigma`值

  ```python
  flt = core.bm3d.Basic(src, sigma=[10,6,8])
  ```
* 输入源为YUV格式，使用基础降噪函数+最终降噪函数进行降噪，并对U、V平面设定相同的`sigma`值

  ```python
  ref = core.bm3d.Basic(src, sigma=[10,7])
  flt = core.bm3d.Final(src, ref, sigma=[10,7])
  ```
* 输入源为YUV格式，引入额外的降噪滤镜作为预处理，将其降噪结果作为基础降噪函数的参考源，并对Y、U、V平面设定相同的`sigma`值

  ```python
  pre = haf.sbr(src, 3)
  ref = core.bm3d.Basic(src, pre, sigma=7)
  flt = core.bm3d.Final(src, ref, sigma=7)
  ```
* 输入源为RGB格式，先转换为OPP格式，在BM3D滤镜内部使用OPP格式降噪，并转回RGB输出

  ```python
  src = core.bm3d.RGB2OPP(src)
  ref = core.bm3d.Basic(src, matrix=100)
  flt = core.bm3d.Final(src, ref, matrix=100)
  flt = core.bm3d.OPP2RGB(flt)
  ```

#### 使用V-BM3D系列函数：

再次提醒，使用V-BM3D系列函数，必须在降噪函数`bm3d.VBasic()`和`bm3d.VFinal()`后使用`bm3d.VAggregate()`函数。

* 输入源为RGB格式，先转换为OPP格式，在BM3D滤镜内部使用OPP格式降噪，并转回RGB输出

  ```python
  src = core.bm3d.RGB2OPP(src)
  ref = core.bm3d.VBasic(src, radius=1, matrix=100).bm3d.VAggregate(radius=1)
  flt = core.bm3d.VFinal(src, ref, radius=1, matrix=100).bm3d.VAggregate(radius=1)
  flt = core.bm3d.OPP2RGB(flt)
  ```
* 输入源为RGB格式，若在转换成OPP格式后，不对色度平面进行处理，需要使用`std.ShufflePlanes()`函数将未处理的色度平面与BM3D的降噪结果合并

  ```python
  src = core.bm3d.RGB2OPP(src)
  ref = core.bm3d.VBasic(src, sigma=[10,0,0], radius=1, matrix=100).bm3d.VAggregate(radius=1)
  flt = core.bm3d.VFinal(src, ref, sigma=[10,0,0], radius=1, matrix=100).bm3d.VAggregate(radius=1)
  flt = core.std.ShufflePlanes([flt,src,src], [0,1,2], vs.YUV)
  flt = core.bm3d.OPP2RGB(flt)
  ```
* 对于上一个例子，更好地做法是在灰度空间下降噪，可以大幅降低内存占用

  ```python
  src = core.bm3d.RGB2OPP(src)
  srcGray = core.std.ShufflePlanes(src, 0, vs.GRAY)
  ref = core.bm3d.VBasic(srcGray, sigma=[10,0,0], radius=1, matrix=100).bm3d.VAggregate(radius=1)
  flt = core.bm3d.VFinal(srcGray, ref, sigma=[10,0,0], radius=1, matrix=100).bm3d.VAggregate(radius=1)
  flt = core.std.ShufflePlanes([flt,src,src], [0,1,2], vs.YUV)
  flt = core.bm3d.OPP2RGB(flt)
  ```
* 输入源为RGB格式，若在转换成OPP格式后，不对亮度平面进行处理，则由于块匹配基于`ref`的亮度平面，对`ref`也需要使用`std.ShufflePlanes()`进行合并

  ```python
  src = core.bm3d.RGB2OPP(src)
  ref = core.bm3d.VBasic(src, sigma=[0,10,10], radius=1, matrix=100).bm3d.VAggregate(radius=1)
  ref = core.std.ShufflePlanes([src,ref,ref], [0,1,2], vs.YUV)
  flt = core.bm3d.VFinal(src, ref, sigma=[0,10,10], radius=1, matrix=100).bm3d.VAggregate(radius=1)
  flt = core.std.ShufflePlanes([src,flt,flt], [0,1,2], vs.YUV)
  flt = core.bm3d.OPP2RGB(flt)
  ```
* 对于上一个例子，也可使用`bm3d.Basic()`代替`bm3d.VBasic()`，更快、更省内存

  ```python
  src = core.bm3d.RGB2OPP(src)
  ref = core.bm3d.Basic(src, matrix=100)
  flt = core.bm3d.VFinal(src, ref, radius=1, matrix=100).bm3d.VAggregate(radius=1)
  flt = core.bm3d.OPP2RGB(flt)
  ```
* 使用自定义降噪滤镜作为基础降噪结果，并使用V-BM3D的最终降噪函数做精细化处理

  这样做可以在两种降噪滤镜间取长补短。在下面的例子中，SMDegrain滤镜在时空平滑上很有效，但可能导致鬼影（blending）和细节损失，V-BM3D可以很好地保留细节，但对于大的噪声pattern（例如粗颗粒）效果不好

  ```python
  src = core.bm3d.RGB2OPP(src)
  ref = haf.SMDegrain(src)
  flt = core.bm3d.VFinal(src, ref, radius=1, matrix=100).bm3d.VAggregate(radius=1)
  flt = core.bm3d.OPP2RGB(flt)
  ```

### 通过`mvsfunc`脚本使用：

为简化函数调用，`mvsfunc`脚本中的`BM3D()`提供了开箱即用的支持，仅需调用单个`BM3D()`函数即可使用BM3D滤镜进行降噪。

关于`BM3D()`函数的详细说明可以移步[脚本百科 - mvsfunc](https://guide.geeking.moe/lv-jing-bai-ke/jiao-ben-bai-ke/mvsfunc)部分，下面举两个简单的例子。

```python
import mvsfunc as mvf

# src可以为任何格式

# 示例一
flt = mvf.BM3D(src, sigma=3.0, profile1="fast")

# 示例二
flt = mvf.BM3D(src, sigma=3.0, radius1=1, profile1="fast")
```

在上述两个例子中，各使用一行代码，完成了BM3D滤镜的调用。示例一没有设定`radius1`参数，表示使用BM3D系列函数进行降噪；示例二设定了`radius1`参数，表示使用V-BM3D系列函数进行降噪。

## 扩展阅读：

BM3D算法于2007年由芬兰坦佩雷理工大学的Kostadin Dabov等人提出，全称为Block-matching and 3D filtering，可译为三维块匹配算法。BM3D算法的原始论文为**Image Denoising by Sparse 3-D Transform-Domain Collaborative Filtering,&#x20;*****IEEE Transactions on image processing 16.8 (2007): 2080-2095***，我们来简要了解一下这篇论文。

论文主体包括概念说明、算法思路、快速算法、从灰度到RGB的扩展、降噪效果这几个部分。我们仅简要介绍算法思路，并说明相关概念，想了解其余部分可阅读原始论文。

### 算法思路：

如上面的教程所述，BM3D算法分为两个步骤，基础估计（Basic estimate）和最终估计（Final estimate），具体过程如下。

* **基础估计**
  * **块估计**

    对含噪点图像的每个块进行如下操作

    * **分组**：进行块匹配，在一定范围内寻找相似的块，将它们存储到一个三维数组中，称为组（group）
    * **协同硬阈值滤波**（Collaborative hard-thresholding）：对上述group进行三维变换，在频域下进行硬阈值滤波，然后进行逆变换还原，并将块复原至原始位置
  * **聚合**（Aggregation）

    对所有重叠的估计块进行加权平均，计算得到真实图像的基础估计结果（即基础降噪结果）
* **最终估计**
  * **块估计**

    对输入图像的每个块进行如下操作

    * **分组**：对基础估计结果进行块匹配，在一定范围内确定相似块的位置，基于这些位置生成两个group，一个group来自含噪点图像，另一个group来自基础估计结果
    * **协同维纳滤波**（Collaborative Wiener filtering）：对上述两个group进行三维变换，基于基础估计结果，对含噪点的group进行维纳滤波，然后进行逆变换还原，并将块复原至原始位置
  * **聚合**

    对得到的估计块进行加权平均，计算得到真实图像的最终估计结果（即最终降噪结果）

上述过程可以如下图描述。

![BM3D算法示意图](https://i.loli.net/2020/01/29/EY1KbZtwe7p2kAB.jpg)

### 概念解释：

按照论文行文的顺序，肯定要先说明概念，再进行上文的算法介绍。但这里为了防止突然扔出一堆概念，导致大家睡着，所以把概念解释放到了后面。

* **匹配**（Matching）：匹配在这里指基于参考块寻找相似块的方法，运算量较低。**通过匹配寻找相似块，即块匹配，英文为Block Matching，这便是BM3D中“BM”的由来**。
* **分组**（Grouping）：将图像切块，并将相似的块分为一组（group）。**一个块可用二维数组表示，那么一组块就要用三维数组表示，这便是BM3D中“3D”的由来**。在BM3D算法中，分组当然是通过块匹配实现。分组也可以基于K均值聚类、自组织映射、模糊聚类、向量量化等无监督学习算法，通过这些算法计算相似度，根据相似度进行分组。但与块匹配相比，这些算法的运算量较大。
* **硬阈值**（Hard-thresholding）：在变换域下，真实图像与噪点的系数存在差别，真实图像系数较大，噪点系数较小。可以基于这种差别进行降噪。直接的方法便是设定阈值，将真实图像与噪点区分开来，故称硬阈值。在BM3D算法的基础估计部分，通过硬阈值滤波进行降噪，得到基础估计结果。
* **收缩**（Shrinkage）：在变换域下降噪，可以对系数较小的部分做收缩，能使降噪的结果更接近真实图像。在BM3D算法的最终估计部分，基于基础估计结果得到经验维纳收缩系数（empirical Wiener shrinkage coefficients），进行维纳滤波，得到最终估计结果。
* **协同滤波**（Collaborative Filtering）：“协同”可以从字面意义上理解，指一个group内的每个块都协作进行对其他块的滤波。在BM3D算法中，协同滤波通过变换域下的收缩实现，包括协同硬阈值滤波和协同维纳滤波。


# Bilateral


# std滤镜


# 字幕滤镜


# VSFilter


# VSFilterMod


# Subtext


# xy-vsfilter


