第一部分: 处理视频和图像的基础知识
什么? 视频编解码器是一种压缩和/或解压缩数字视频的软件/硬件。
为了什么? 尽管在带宽和带宽方面存在一定的限制
而在数据存储空间方面,市场对视频质量的要求也越来越高。 您还记得在上一篇文章中我们如何计算每秒 30 帧、每像素 24 位、分辨率为 480x240 所需的最小值吗? 我们在未压缩的情况下收到了 82,944 Mbit/s。 压缩是目前将 HD/FullHD/4K 传输到电视屏幕和互联网的唯一方法。 这是如何实现的? 现在我们简单看一下主要方法。
我们从事
视频监控系统集成 和我们正在开发显微断层扫描仪 .
编解码器与容器
新手常犯的一个错误是混淆数字视频编解码器和数字视频容器。 容器是一定的格式。 包含视频(也可能是音频)元数据的包装器。 压缩视频可以被视为容器有效负载。
通常,视频文件的扩展名指示其容器类型。 例如,文件 video.mp4 可能是一个容器 MPEG-4 Part 14,并且最有可能的是名为 video.mkv 的文件
有一点历史
在我们到达之前 怎么样?,让我们深入了解一下历史,以便更好地理解一些较旧的编解码器。
视频编解码器 H.261 出现于 1990 年(技术上是 1988 年),其创建的目的是以 64 Kbps 的数据传输速率运行。 它已经使用了颜色子采样、宏块等想法。 视频编解码标准于1995年发布 H.263,一直发展到2001年。
В 2003 году была завершена первая версия H.264 / AVC。 同年,TrueMotion 发布了其免费的有损视频编解码器,名为 VP3。 谷歌于 2008 年收购了该公司,并发布 VP8 同年。 2012年XNUMX月,谷歌发布 VP9,大约 XNUMX/XNUMX 的浏览器市场(包括移动设备)都支持它。
AV1 是一个新的免费开源视频编解码器,由 开放媒体联盟 (媒体),其中包括最著名的公司,例如:Google、Mozilla、Microsoft、Amazon、Netflix、AMD、ARM、NVidia、Intel 和 Cisco。 该编解码器的第一个版本 0.1.0 于 7 年 2016 月 XNUMX 日发布。
AV1的诞生
2015 年初,谷歌正致力于 VP10Xiph(由 Mozilla 所有)正在致力于 达拉,并且思科制作了自己的免费视频编解码器,称为 托尔.
然后 MPEG 洛杉矶 首次公布的年度限额 HEVC (H.265),费用比 H.8 高 264 倍,但他们很快又改变了规则:
无年度限制,
内容费(收入的 0,5%)和
单价比H.10高264倍左右。
开放媒体联盟 由来自不同领域的公司创建:设备制造商(英特尔、AMD、ARM、Nvidia、思科)、内容提供商(谷歌、Netflix、亚马逊)、浏览器创建者(谷歌、Mozilla)等。
这些公司有一个共同的目标——免版税视频编解码器。 然后出现 AV1 具有更简单的专利许可。 Timothy B. Terryberry 做了一场令人惊叹的演讲,成为当前 AV1 概念及其许可模式的起源。
你会惊讶地发现你可以通过浏览器分析AV1编解码器(有兴趣的可以去
通用编解码器
让我们看看通用视频编解码器的主要机制。 这些概念中的大多数都很有用,并且在现代编解码器中使用,例如 VP9, AV1 и HEVC。 我警告你,解释的许多事情都会被简化。 有时,现实世界的示例(如 H.264)将用于演示技术。
第一步——分割图像
第一步是将框架分为几个部分、小部分等等。
为了什么? 原因有很多。 当我们分割图像时,我们可以通过对小运动部分使用小部分来更准确地预测运动矢量。 对于静态背景,您可以将自己限制在较大的部分。
编解码器通常将这些部分组织成部分(或块)、宏块(或编码树块)和多个子部分。 这些分区的最大大小各不相同,HEVC 将其设置为 64x64,而 AVC 使用 16x16,并且子分区最多可以分割为 4x4 大小。
您还记得上一篇文章中的框架类型吗? 同样的情况也适用于块,因此我们可以有 I 片段、B 块、P 宏块等。
对于那些想要练习的人,请观看图像如何划分为部分和子部分。 为此,您可以使用上一篇文章中已经提到的方法。
第二步——预测
一旦我们有了部分,我们就可以对它们进行占星预测。 为了 国际米兰预测 必须转移 运动矢量 和其余部分,对于 INTRA 预测,它被传输 预测方向 和其余的。
第三步——转型
一旦我们有了残差块(预测部分→真实部分),就可以对其进行变换,以便我们知道可以丢弃哪些像素,同时保持整体质量。 有一些转换可以提供确切的行为。
虽然还有其他方法,但让我们更详细地看看它们。 离散余弦变换 (DCT -来自 离散余弦变换)。 DCT的主要功能:
- 将像素块转换为大小相等的频率系数块。
- 凝聚力量,帮助消除空间冗余。
- 提供可逆性。
2 年 2017 月 14 日 辛特拉 R.J. (辛特拉,RJ)和拜耳 F.M. (Bayer FM) 发表了一篇关于图像压缩的类 DCT 变换的文章,该变换仅需要 XNUMX 次加法。
如果您不了解每一点的好处,请不要担心。 下面我们通过具体的例子来看看它们的真正价值。
我们以这个 8x8 的像素块为例:
该块被渲染成以下 8 x 8 像素图像:
对这个像素块应用 DCT 并得到一个 8x8 的系数块:
如果我们渲染这个系数块,我们将得到以下图像:
正如您所看到的,它看起来不像原始图像。 您可以看到第一个系数与所有其他系数非常不同。 第一个系数称为 DC 系数,它表示输入数组中的所有样本,类似于平均值。
该系数块有一个有趣的特性:它将高频分量与低频分量分开。
在图像中,大部分功率集中在较低频率,因此如果将图像转换为其频率分量并丢弃较高频率系数,则可以减少描述图像所需的数据量,而无需牺牲太多图像质量。
频率是指信号变化的速度。
让我们尝试应用在测试用例中获得的知识,使用 DCT 将原始图像转换为其频率(系数块),然后丢弃部分最不重要的系数。
首先我们将其转换到频域。
接下来,我们丢弃部分(67%)系数,主要是右下部分。
最后,我们从这个被丢弃的系数块中重建图像(记住,它必须是可逆的)并将其与原始图像进行比较。
我们看到它与原始图像相似,但与原始图像有很多差异。 我们扔掉了 67,1875%,但仍然得到了与原始版本类似的东西。 可以更仔细地丢弃系数以获得质量更好的图像,但这是下一个主题。
每个系数都是使用所有像素生成的
重要提示:每个系数并不直接映射到一个像素,而是所有像素的加权和。 这个令人惊叹的图表显示了如何使用每个指数特有的权重来计算第一和第二系数。
您还可以尝试通过查看基于 DCT 的简单图像形成来可视化 DCT。 例如,下面是使用每个系数权重生成的符号 A:
第四步——量化
在我们在上一步中丢弃一些系数之后,在最后一步(转换)中,我们执行一种特殊形式的量化。 在此阶段,丢失信息是可以接受的。 或者,更简单地说,我们将量化系数以实现压缩。
如何量化一个系数块? 最简单的方法之一是均匀量化,当我们取一个块时,将其除以一个值(除以 10)并对结果进行四舍五入。
我们可以反转这组系数吗? 是的,我们可以,乘以除以的相同值。
这种方法并不是最好的,因为它没有考虑到每个系数的重要性。 人们可以使用量化器矩阵而不是单个值,并且该矩阵可以通过量化右下角的大部分和左上角的少数来利用 DCT 属性。
步骤 5 - 熵编码
一旦我们量化了数据(图像块、片段、帧),我们仍然可以对其进行无损压缩。 有许多算法方法可以压缩数据。 我们将快速浏览其中的一些内容,为了更深入地了解您可以阅读《理解压缩:现代开发人员的数据压缩》一书(“
使用VLC进行视频编码
假设我们有一个字符流: a, e, r и t。 此表显示了每个字符在流中出现的频率的概率(范围从 0 到 1)。
a | e | r | t | |
---|---|---|---|---|
可能性 | 0,3 | 0,3 | 0,2 | 0,2 |
我们可以将唯一的二进制代码(最好是小的二进制代码)分配给最有可能的代码,并将较大的代码分配给不太可能的代码。
a | e | r | t | |
---|---|---|---|---|
可能性 | 0,3 | 0,3 | 0,2 | 0,2 |
二进制代码 | 0 | 10 | 110 | 1110 |
我们压缩流,假设最终每个字符花费 8 位。 如果不进行压缩,每个字符将需要 24 位。 如果将每个字符替换为其代码,则可以节省成本!
第一步是对字符进行编码 e,等于 10,第二个字符是 a,添加(不是以数学方式):[10][0],最后是第三个字符 t,这使得我们最终的压缩比特流等于 [10][0][1110] 或 1001110,仅需要 7 位(比原始空间少 3,4 倍)。
请注意,每个代码必须是带有前缀的唯一代码。
编码器和解码器都必须能够访问带有二进制代码的符号表。 因此,还需要发送一个表作为输入。
算术编码
假设我们有一个字符流: a, e, r, s и t,其概率列于该表中。
a | e | r | s | t | |
---|---|---|---|---|---|
可能性 | 0,3 | 0,3 | 0,15 | 0,05 | 0,2 |
使用此表,我们将构建包含所有可能字符的范围,并按最大数字排序。
现在让我们对三个字符的流进行编码: 吃.
首先选择第一个字符 e,其范围为 0,3 至 0,6(不含)。 我们采用这个子范围并按照与之前相同的比例再次划分它,但对于这个新范围。
让我们继续编码我们的流 吃。 现在取第二个字符 a,位于从 0,3 到 0,39 的新子范围内,然后取最后一个字符 t 再次重复相同的过程,我们得到从 0,354 到 0,372 的最终子范围。
我们只需要在最后一个子范围 0,354 到 0,372 之间选择一个数字。 我们选择 0,36(但您可以选择此子范围中的任何其他数字)。 只有有了这个数字,我们才能恢复原来的流。 就好像我们在范围内画一条线来编码我们的流。
逆运算(即 解码)同样简单:使用我们的数字 0,36 和初始范围,我们可以运行相同的过程。 但现在,使用这个数字,我们可以识别使用这个数字编码的流。
对于第一个范围,我们注意到我们的数字对应于切片,因此这是我们的第一个字符。 现在我们按照与之前相同的过程再次划分这个子范围。 这里可以看到0,36对应的符号 a,重复这个过程后我们得到了最后一个字符 t (形成我们的原始编码流 吃).
编码器和解码器都必须有一个符号概率表,因此也有必要在输入数据中发送它。
相当优雅,不是吗? 想出这个解决方案的人真是太聪明了。 一些视频编解码器使用这种技术(或者至少提供它作为一种选项)。
这个想法是无损压缩量化的比特流。 当然,这篇文章缺少大量的细节、原因、权衡等。 但如果你是一名开发人员,你应该了解更多。 新的编解码器尝试使用不同的熵编码算法,例如 ANS.
第 6 步 - 比特流格式
完成所有这些后,剩下的就是在所执行的步骤的上下文中解压缩压缩帧。 解码器必须明确告知编码器所做的决定。 必须向解码器提供所有必要的信息:位深度、颜色空间、分辨率、预测信息(运动向量、定向帧间预测)、配置文件、级别、帧速率、帧类型、帧编号等等。
我们将快速浏览一下比特流 H.264。 我们的第一步是创建一个最小的 H.264 比特流(FFmpeg 默认情况下添加所有编码选项,例如 塞纳尔 ——我们稍后会知道它是什么)。 我们可以使用我们自己的存储库和 FFmpeg 来完成此操作。
./s/ffmpeg -i /files/i/minimal.png -pix_fmt yuv420p /files/v/minimal_yuv420.h264
该命令将生成原始比特流 H.264 一帧,64×64分辨率,带色彩空间 YUV420。 在这种情况下,使用以下图像作为框架。
H.264 比特流
标准 AVC的 (H.264)确定信息将以宏帧(在网络意义上)发送,称为 NAL (这是网络抽象级别)。 NAL 的主要目标是提供“网络友好”的视频演示。 该标准应该适用于电视(基于流)、互联网(基于数据包)。
有一个同步标记来定义 NAL 元素的边界。 每个同步令牌包含一个值 0x00 0x00 0x01, 除了第一个,它等于 0x00 0x00 0x00 0x01. 如果我们推出 十六进制转储 对于生成的 H.264 比特流,我们在文件开头至少标识了三个 NAL 模式。
如上所述,解码器不仅必须知道图像数据,还必须知道视频、帧、颜色、使用的参数等等的详细信息。 每个 NAL 的第一个字节定义其类别和类型。
NAL 类型标识符 | 使用说明 |
---|---|
0 | 未知类型 |
1 | 没有 IDR 的编码图像片段 |
2 | 编码切片数据部分 A |
3 | 编码切片数据部分 B |
4 | 编码切片数据部分 C |
5 | IDR 图像的编码 IDR 片段 |
6 | 有关 SEI 扩展的更多信息 |
7 | SPS 序列参数集 |
8 | PPS图像参数集 |
9 | 访问分隔符 |
10 | 序列结束 |
11 | 线程结束 |
... | ... |
通常比特流的第一个 NAL 是 SPS。 这种类型的 NAL 负责通知常见的编码变量,例如配置文件、级别、分辨率等。
如果我们跳过第一个同步标记,我们可以解码第一个字节以找出第一个 NAL 类型。
例如,同步令牌之后的第一个字节是 01100111,其中第一位 (0) 位于字段 f 中orbidden_zero_bit。 接下来的 2 位 (11) 告诉我们这个字段 nal_ref_idc, 指示该 NAL 是否是参考字段。 剩下的 5 位(00111) 告诉我们这个字段 nal_unit_type, 在本例中它是 SPS 块(7) 纳尔。
第二个字节(二进制=01100100, 十六进制=0x64, 十二月=100) 在 SPS NAL 中是该字段 配置文件_idc, 它显示了编码器使用的配置文件。 在本例中,使用了有限的高轮廓(即没有双向 B 段支持的高轮廓)。
如果你看一下比特流规范 H.264 对于SPS NAL,我们会发现很多参数名称、类别和描述的值。 例如,让我们看一下字段 pic_width_in_mbs_minus_1 и pic_height_in_map_units_minus_1.
参数名称 | 类别 | 使用说明 |
---|---|---|
pic_width_in_mbs_minus_1 | 0 | 尿素(v) |
pic_height_in_map_units_minus_1 | 0 | 尿素(v) |
如果我们对这些字段的值进行一些数学运算,我们将获得分辨率。 可以使用 1920 x 1080 来表示 pic_width_in_mbs_minus_1 值为 119 ((119 + 1) * macroblock_size = 120 * 16 = 1920)。 同样,为了节省空间,我们没有编码 1920,而是使用 119。
如果我们继续以二进制形式检查我们创建的视频(例如: xxd -b -c 11 v/minimal_yuv420.h264),然后您可以转到最后一个 NAL,即帧本身。
这里我们看到它的前 6 个字节值: 01100101 10001000 10000100 00000000 00100001 11111111。 由于已知第一个字节指示 NAL 类型,在本例中 (00101)是一个IDR片段(5),然后你可以进一步探索它:
使用规范信息,可以解码片段类型(切片类型)和帧号(帧数)等重要领域。
获取某些字段的值(ue(v), me(v), se(v)或 te(v)),我们需要使用基于的特殊解码器来解码片段
意 切片类型 и 帧数 该视频的 7(I 片段)和 0(第一帧)。
比特流可以被认为是一种协议。 如果你想了解更多关于比特流的信息,你应该参考规范 国际电联H.264。 这是一个显示图像数据所在位置的宏图(YUV 以压缩形式)。
可以检查其他比特流,例如 VP9, H.265 (HEVC)或者甚至是我们新的最佳比特流 AV1。 它们都相似吗? 不,但是一旦你至少理解了其中一个,理解其余的就容易多了。
想练习吗? 探索 H.264 比特流
您可以生成单帧视频并使用 MediaInfo 检查比特流 H.264。 事实上,没有什么可以阻止您查看分析比特流的源代码 H.264 (AVC的).
作为练习,您可以使用 Intel Video Pro Analyzer(我是否已经说过该程序是付费的,但有一个限制为 10 帧的免费试用版?)。
查看
请注意,许多现代编解码器使用我们刚刚研究的相同模型。 这里,我们看一下视频编解码器的框图 托尔。 它包含我们经历过的所有步骤。 这篇文章的重点是至少让您更好地了解该领域的创新和文档。
此前计算出,存储一个 139p 质量、720 fps 持续一小时的视频文件需要 30 GB 磁盘空间。 如果您使用本文中讨论的方法(帧间和内部预测、变换、量化、熵编码等),那么您可以实现(基于我们每个像素花费 0,031 位的事实)相当长的视频质量令人满意,仅占用 367,82 MB,而不是 139 GB 内存。
H.265如何实现比H.264更好的压缩比?
现在我们对编解码器的工作原理有了更多了解,就更容易理解较新的编解码器如何以更少的位数提供更高的分辨率。
如果我们比较 AVC的 и HEVC,值得记住的是,这几乎总是在更大的 CPU 负载和压缩比之间进行选择。
HEVC 有更多的部分(和小节)选项 AVC的、更多内部预测方向、改进的熵编码等等。 所有这些改进都已完成 H.265 能够压缩50%以上 H.264.
第一部分: 处理视频和图像的基础知识
来源: habr.com