按:本文是少数派共创栏目《经验卷轴:视频修复简明教程》首篇文章的预览版本。在共创模式下,我们鼓励少数派作者向我们提交创作提案和样稿,择优秀提案发布在少数派共创平台上,以便读者能更早获知教程上新动态。同时,我们将邀请读者预览大纲和部分篇目,并就内容规划、写法和风格等问题提供反馈,以此作为决定是否上架及继续完善内容的依据。

就本栏目而言,少数派及作者希望重点了解——

  1. 对于本试读篇目反映的写法、深浅程度是否满意?
  2. 对于目前大纲篇目(请前往项目页面查看)有无进一步建议?是否有其他希望看到的修复案例讲解?
  3. 对于本栏目更新完成后的交流和答疑渠道是否有需求?希望以何种方式提供?

欢迎读者从即日起至 4 月 20 日期间,通过本文评论区就上述问题提供反馈。我们将挑选 10 名给出优质评论的朋友,免费赠送本教程的正式版本。


开始之前

什么是视频修复?显然,视频修复就是提升视频的观感,减少瑕疵。为什么要做视频修复?每个人能可能有自己的答案:想要追忆往昔,怀缅故人;想要钻研视频相关技术;想要收藏「完美版」动画,等等。简而言之,视频修复就是让视频变得更好看。

纵然每个人对「好看」的理解不同,但有一些属性——例如噪点柔和、颜色过渡自然、物体边缘干净清晰等——是人人都同意的。本教程正是就这些放之四海而皆准的属性,试图用数篇文章来简要介绍人人都可上手的视频修复工作。

我认为,在数字时代,视频修复是一项必备技能。在这个时代,人们与视频的交互如此频繁,能够正确识别视频中存在的瑕疵,并有能力进行修复,将是一种独特优势。对视频内容生产者,确保自己的视频能够准确、高质量地呈现给观众,是事半功倍的选择。对于一般人,能够让自己喜欢的视频观感更好,也不失为一件乐事。

在开始正式教程之前,我想先提出视频修复的基本原则,那就是原始视频的质量对修复工作有着至关重要的影响,一个更好的原始视频可以节省大量的工作,因此花费一些时间来找高质量的视频绝对是有必要的。

与之相对的,原始视频的质量越低,就可以使用越激进的修复方法。这也很好理解,如同翻译工作一样,视频修复也应该尽可能忠实于原始视频。如果原始视频的质量很高,那么在进行修复工作时就应该忠实于作者想要传达的信息,仅仅对一些由于视频压制或碟片制作等外部原因产生的瑕疵进行处理,而对画面主体的色彩、锐利度和线条形状等尽量保持不变。

反之,如果原始视频的质量很差,那么提升观感就成为修复工作的主要目的,因为我们相信,无论是大量的色块、色带,物体边缘的脏边还是劣质锐化造成的线条光晕,都不是作者想要传达的信息。修复中就要将这些瑕疵尽可能完全去除,即使对画面主体有所影响也在所难免。

以上就是视频修复前要了解的一些背景,下面让我们开始介绍视频修复所用的工具和软件。本教程仅作技术交流,对所修复的视频来源不做道德评判,但希望大家有能力可以支持正版。阅读本教程要求读者有初步的 Python 基础,能够理解函数及其参数即可。

15年前,视频修复与压制爱好者们使用的是于 2000 年发布的 Avisynth,它是一款支持非线性视频编辑的软件,被称为「帧服务器」。然而,时过境迁,随着计算机与视频技术的发展,Avisynth 逐渐落后于时代,例如,作为 32 位应用,它现在可能无法在某些 64 位操作系统上运行,它也不支持多线程运行,等等。

开源社区推出了多款软件来解决这些问题,例如支持 64 位系统的 Avisynth+ 等。

不过在这一教程中,我将要介绍另一款名为 Vapoursynth 的软件。它诞生于 2012 年,是作者 Fredrik Mellbin 作为 Avisynth 的替代品而开发的,与 Avisynth 相比,Vapoursynth 支持多线程、多种色彩空间,可向视频帧添加任意信息,并且基于 Python 语言,十分易于上手使用。

与其他视频编辑软件如 Adobe Premiere 或 DaVinci Resolve 等相比,Vapoursynth 最大的优点是开源免费。其次,Vapoursynth 有着活跃的社区成员,维护许多实用滤镜和脚本。如果现有功能不能满足要求,也很容易通过 Python 编写自己所需的脚本。Vapoursynth 作为一种基于代码的软件,自然不如以上拥有图形界面的软件易用,但这也使调整滤镜或脚本的参数变得非常容易,在调整的自由度上也会更胜一筹。

安装

如果没有特殊要求1,建议安装 Vapoursynth 的最新版本。安装有两种方式,一种是直接使用官方安装包安装,另一种是使用便携版(免安装)。

安装包安装

安装 Python 并配置环境变量后,使用官方安装包(Vapoursynth-x64-Rxx.exe)进行安装即可。如果是初次安装则可以选择安装位置,若无特殊要求,组件全部安装即可。

安装过程中,如果本机上有多个 Python 环境,可以选择给哪一个环境安装。

最后是一些杂项,建议选中将 VSPipe 加入 PATH 环境变量,以便后续压制时用命令行调用。

在细节页面确认后即可点击 Next 完成安装。 如果系统内 Python 目录下有旧的 Vapoursynth 文件,建议在安装新版本前移除,以免产生问题。安装包会妥善处理 Python 环境,安装后打开命令行进入 Python,分行键入以下内容:

from vapoursynth import core
print(core.version())

能正常看到输出即为安装完成:

安装目录下的结构如下:

core 文件夹内是 Vapoursynth 核心文件,Vapoursynth Editor 会在这里寻找运行库。plugins 文件夹内可以存放滤镜的动态链接库(.dll)文件。sdk 文件夹内是一些开发用接口文件,普通用户一般不需要使用。

便携版安装

如果不希望将 Vapoursynth 安装到系统目录,或者想要生成一个可以在任何电脑上运行的便携版 Vapoursynth,可以在官网下载嵌入式(Embedded) Python 压缩包2,并在 Vapoursynth 的 Github 项目中选择想要安装的 Vapoursynth 版本,下载便携版(Vapoursynth64-Portable-Rxx.zip 或 Vapoursynth64-Portable-Rxx.7z)压缩包,将两个压缩包解压到同一文件夹内,即可完成 Vapoursynth 的安装。

这种安装方法的 Vapoursynth 核心文件位于这一文件夹内,如有需要将 Vapoursynth Editor 的 Vapoursynth 库文件夹(Vapoursynth library search paths)指向这里即可。建议将这一目录加入系统 PATH 环境变量中,方便后续用命令行调用。

Vapoursynth Editor 安装

由于此时的 Vapoursynth 仍然只有命令行界面,为了便于修改脚本与实时预览视频,我们需要安装 Vapoursynth Editor。根据上述 Vapoursynth 的安装方式,对应的 Vapoursynth Editor 安装方式也有不同。

  • 如果采用安装包安装,则可以从 Github 项目中下载安装包进行安装,完成后不需要进一步操作。
  • 如果选择使用 Vapoursynth Editor 便携版压缩包,则可能需要在设置中将 Vapoursynth 库文件夹指向对应目录。

而如果采用便携版安装,则从 Github 项目中下载便携版安装包,将其解压到Vapoursynth 的相同目录即可。

无论哪种安装方式,打开目录下的 vsedit.exe,没有报错即说明安装成功。Vapoursynth 的脚本扩展名为 .vpy,可以将其默认打开程序设置为 Vapoursynth Editor。

由于 Vapoursynth 目前还在频繁更新中,如果 Vapoursynth Editor 的版本比 Vapoursynth 更旧(以发布时间计),则可能无法使用。因此在下载 Vapoursynth Editor 时要留意它的发布时间,并与 Vapoursynth 对应版本的发布时间比较。

滤镜及脚本库的安装

滤镜是用于处理视频的一系列已经编译好的动态链接库(DLL)的简称,这些滤镜中的函数在被 Vapoursynth 读取后可以直接被 Vapoursynth 核心调用,而无需再导入其它脚本库。

除此之外,许多开源社区作者也发布了自己开发的、调用这些滤镜的脚本库,例如mawen1250、Muonium、Holy 等人开发的 mvsfunc、muvsfunc、havsfunc 等等。这些脚本库中某些函数可以有效完成一些修复操作,从而避免我们再造轮子。

  • 滤镜的存放地点是 Vapoursynth 安装目录\plugins Vapoursynth 安装目录\core\plugins(安装包安装法)与 Vapoursynth 安装目录\vapoursynth64\plugins(便携版)。
  • 脚本库由于本质是 Python 脚本,需要放到 Python 安装目录\Lib\site-packages(安装包安装法)或 Python 安装目录下 (便携版)。

我打包了自己的滤镜与脚本库,方便读者快速上手,下载链接可以在这里找到。如果需要加载特定位置的滤镜,可以在 Vapoursynth Editor 中使用 core.std.LoadPlugin(PATH) 函数。

VSDB.top 是一个记录各类滤镜与脚本的网站,它们的版本号及对应项目网址(常常包含使用文档)可以在这里找到。

值得强调的是,由于 Vapoursynth 目前还在频繁更新中,某些滤镜和脚本可能会因为过时或缺乏依赖而无法使用,此时不妨到 VSDB.top 上查阅对应的项目是否有更新,使用最新版和补全缺失的依赖可能可以解决问题。

macOS的安装

macOS安装需要先行安装 Xcode 和 Homebrew。随后在命令行中运行这一命令:

brew install vapoursynth

等待安装完成即可。配置脚本库和滤镜的自动加载请参考官网教程VSDB.top 上有一个页面存放 macOS 版本的各种滤镜,而脚本库由于是 Python 编写,基本上是跨平台的。但如果滤镜过时或在某些版本的 macOS 上无法运行,则可能需要用户自行下载源码并编译才能使用。

编译已经超过本文要介绍的范畴,有兴趣的用户可以到 Vapoursynth 官方文档上自行了解。

Vapoursynth 脚本入门

正如之前所说,Vapoursynth 脚本是以 Python 为基础,因此语法基本与 Python 相同。Vapoursynth Editor 的本质是一个带有视频预览功能的文本编辑器,因此我们写脚本时正常编程即可。如图所示,Vapoursynth Editor 上方是脚本编辑区,下方是日志区,用于显示一些调试、警告或错误信息。

Vapoursynth 脚本以 .vpy 为扩展名,当我们在 Vapoursynth Editor 中新建一个脚本时,会看到自动生成的两行代码:

import vapoursynth as vs
core = vs.core

第一行是导入 Vapoursynth 核心库,第二行是获取用于处理视频的 Vapoursynth 核心实例。滤镜与脚本库导入方法的区别在于,滤镜在成功加载后,可以使用 core.xxx.Func 式直接调用,且可以在自动补全中看到完整的函数名与变量名;而脚本库导入时,与 Python 库的导入一样,需要使用 import 导入,且调用函数时不会有自动补全。实例如下所示:

import vapoursynth as vs
import mvsfunc as mvf

core = vs.core

source = r"X:\Y\Z\ABC.mp4"
src = core.lsmas.LWLibavSource(source)

src = mvf.Depth(src,16,0,False,False)

其中,lsmas 是一个滤镜,在 Vapoursynth 启动时自动加载,因此可以通过 core 调用;而 mvsfunc 是一个脚本库,需要用 import 导入后再调用其中的函数。

视频的导入与导出

既然是视频处理工具,自然需要用到视频的导入与导出。Vapoursynth 中负责视频导入的有两个滤镜:LSMAS(LSMASHSource)与 FFMS2(FFmpegSource 2)。这两个滤镜的作用类似,都是将编码过的视频读入,分离视频轨,解码并输出为 Vapoursynth 可用的视频流。

二者在输出视频流之前,都必须先建立原始视频的帧索引,如果视频体积很大或者时间很长,这一过程可能需要持续数分钟,在这一过程中 Vapoursynth Editor 会失去响应,只需耐心等待即可。 

一般遇到的视频分为固定帧率(constant framerate,cfr)与可变帧率(variable framerate,vfr)两种,对这两种视频,我们需要采取的措施略有不同。首先以较为简单的固定帧率为例,将视频文件的路径输入后,即可读入视频文件:

import vapoursynth as vs

core = vs.core
source = r"X:\Y\Z\ABC.mp4"
src = core.lsmas.LWLibavSource(source)
src = core.ffms2.Source(source)

而视频文件的输出只需将想要输出的视频对象设定为输出即可:

src.set_output()

在 Vapoursynth Editor 中,按 F6 键即可查看输出的视频对象的基本信息,如帧数、时长、分辨率、帧率和格式等等:

而按 F5 则可以预览视频对象:

设置基本保持默认即可,我出于个人喜好将色度平面重采样算法设置成了 Lanczos,但默认的 Bicubic 算法也没有任何问题。对于 YUV 转 RGB 矩阵,只有在原视频不包含这一信息时,编辑器中的设置才起效,709 与 470BG 分别代表国际电联(ITU)BT.709 和 BT.470 System B/G 标准定义的转换矩阵。

对于超高清(UHD,4K)内容可能会用到 BT.2020 NCL 或 BT.2020 CL 所定义的转换矩阵。而对于色度平面降采样方式,现代视频通常都是 MPEG2 排列,除非明确知道视频的降采样方式,否则不需要更改。总而言之就是:不知道怎么改就保持默认。

然而,如果我们将同样的方法用于可变帧率的视频,我们会发现帧率是一个常数,但总时长与在播放器中看到的不一致:

下方为正确的时长,这是由于 LSMAS 默认输入视频是固定帧率造成的。可变帧率需要使用时码(timecode)文件来确定每帧在时间轴上的位置,这时我们就需要使用 FFMS2 进行读取:

import vapoursynth as vs

core = vs.core
source = r"X:\Y\Z\ABC.mp4"
src = core.ffms2.Source(source, timecodes=r"X:\Y\Z\timecode.txt")

ffms2.Source 函数中的 timecodes 参数设置时码文件的保存位置,默认为空字符串,即不保存。如果后续想保持视频的可变帧率,则需要编码完成后将时码文件和视频流封装到容器中。而如果想把视频转换为固定帧率,可以使用 Vapoursynth 中的 VFRToCFR 滤镜将其转换为固定帧率:

cfr = core.vfrtocfr.VFRToCFR(src, timecodes=r"X:\Y\Z\timecode.txt", fpsnum=60000, fpsden=1001, drop=True)

其中 timecodes 为时码文件的路径,fpsnum 为帧率分子的值,而 fpsden 为帧率分母的值,根据具体需要修改。drop 参数控制局域帧率超过指定帧率时是否允许丢帧,设定为 False 时如果需要丢帧则报错。 视频的输出与之前的脚本相同:

cfr.set_output()

图片的导入与导出

有时我们也需要导入图片,这就要用到 Vapoursynth 内置的另一个滤镜 imwri 了。它的代码是:

picture_path = r"X:\Y\Z\mask.png"
picture_src = core.imwri.Read(picture_path,0)

imwri.Read 中第二个参数是起始编号,因为 imwri.Read 支持形如 %03d.png 的图片序列。至于为什么图片要叫做 mask,暂且卖个关子,后续教程中会涉及。 图片的导出也可以用 imwri 实现:

picture_save = core.imwri.Write(src, imgformat = "png", filename = "%03d.png", firstnum = 1)
picture_save.set_output()

在 imwri.Write 的 imgformat 参数中可以指定格式,而文件名必须包含 %03d 等编号,firstnum 参数用于确定起始编号。特别要注意的是,如果不把 picture_save 设定为输出并预览或执行,图片无法导出。

以上就是 Vapoursynth 入门所需的全部知识,现在你应该能够成功导入与导出视频和图片,并用 Vapoursynth Editor 预览视频内容及信息。接下来我会进一步介绍 Vapoursynth 的 std 库中包含的常用滤镜,我们下期再见。

拓展阅读

什么是 YUV 格式?色度平面降采样是什么?降采样的色度像素位置分布有何区别?ITU 规定的标准中色彩原色(primary)、传输特性(transfer characteristics)和(YUV 到 RGB 的)转换矩阵(matrix)都是什么?对这些问题感兴趣的读者可以进一步阅读由老牌压制组 VCB-Studio 撰写的介绍,了解更多关于视频的基础知识。