You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

112 lines
16 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 04编码原理视频究竟是怎么编码压缩的
你好,我是李江。今天我们一起来聊一聊视频编码。
说到视频我们首先想到的可能就是占内存。我们知道一个视频是由一连串图像序列组成的视频中图像一般是YUV格式。现在我们假设有一个电影视频分辨率是1080P帧率是25fps并且时长是2小时如果不做视频压缩的话它的大小是1920 x 1080 x 1.5 x 25 x 2 x 3600 = 521.4G。而我们的电脑一般是500G的硬盘那就连2部电影都放不下了。如果是在视频通话场景下的话按照这个大小去传输视频数据对流量和带宽资源的消耗也是非常大的并且如此大的数据发送对网络要求也非常高很显然我们是接受不了的。因此做视频编码压缩就非常有必要。
那么,接下来我就深入讲讲视频编码,带你从预测编码、变换编码、熵编码等方面,系统了解下视频编码的原理。相信这节课过后,你会对视频编码有一个全面的了解。
## 视频编码的原理
视频编码是对一帧帧图像来进行的。一般我们所熟知的彩色图像的格式是RGB的即用红绿蓝三个分量的组合来表示所有颜色。但是RGB三个颜色是有相关性的为了去掉这个相关性减少需要编码的信息量我们通常会把RGB转换成YUV也就是 **1个亮度分量和2个色度分量**
另外人眼对于亮度信息更加敏感而对于色度信息稍弱所以视频编码是将Y分量和UV分量分开来编码的。
而对于每一帧图像,又是划分成一个个块来进行编码的,**这一个个块在H264中叫做宏块**而在VP9、AV1中称之为超级块其实概念是一样的。宏块大小一般是16x16H264、VP832x32H265、VP964x64H265、VP9、AV1128x128AV1这几种。这里提到的H264、H265、VP8、VP9和AV1都是市面上常见的编码标准下面我会介绍这里就不再详细讲述。
**图像一般都是有数据冗余的**主要包括以下4种
* **空间冗余。**比如说将一帧图像划分成一个个16x16的块之后相邻的块很多时候都有比较明显的相似性这种就叫空间冗余。
* **时间冗余。**一个帧率为25fps的视频中前后两帧图像相差只有40ms两张图像的变化是比较小的相似性很高这种叫做时间冗余。
* **视觉冗余。**我们的眼睛是有视觉灵敏度这个东西的。人的眼睛对于图像中高频信息的敏感度是小于低频信息的。有的时候去除图像中的一些高频信息,人眼看起来跟不去除高频信息差别不大,这种叫做视觉冗余。
* **信息熵冗余。**我们一般会使用Zip等压缩工具去压缩文件将文件大小减小这个对于图像来说也是可以做的这种冗余叫做信息熵冗余。
视频编码就是通过减少上述4种冗余来达到压缩视频的目的。接下来我们就一起来慢慢剥开视频编码这个“洋葱”吧。
对于一个YUV图像我们把它划分成一个个16x16的宏块以H264为例Y、U、V分量的大小分别是16x16、8x8、8x8。这里我们只对Y分量进行分析U、V分量同理。假设Y分量这16x16个像素就是一个个数字我们从左上角开始之字形扫描每一个像素值则可以得到一个“像素串”。如下图所示
![](https://static001.geekbang.org/resource/image/6c/16/6c879e4971995d927e9de46616256b16.jpg?wh=1280x720)
如果你是程序员的话,你肯定做过一个压缩字符串的编程题目,就是将 “aaaabbbccccc” 压缩成 “4a3b5c”字符串由13个字节压缩到7个字节这个叫做**行程编码**。如果我们对图像宏块扫描出来的这个“像素串”做同样的行程编码操作,是不是也有可能减小图像块呢?
在对“像素串”行程编码之前,我们先回过头来看看另一个行程编码的例子。如果刚才编程题的字符串是 “abcdabcdabcd” 的话,那么编码之后就会是 “1a1b1c1d1a1b1c1d1a1b1c1d”。字符串的大小从13字节变成了25字节还变大了。
我们发现如果想要达到压缩的目的我们必须要使得编码前的字符串中出现比较多连续相同的字符。这对于图像块也是一样的。我们需要使得扫描出来的“像素串”也尽量出现连续相同的像素值最好是一连串数字很小比如0的“像素串”因为0在二进制中只占1个位就可以了。
这个地方你可能会有疑惑0也是至少要一个字节存储需要8个位怎么会是1个位呢这个有的编码算法是可以做到的比如指数哥伦布编码它就可以做到0只占用一个位。事实上算术编码可以做到一个符号只占用0点几个位连一个位都不用这里不详细展开了感兴趣的话你可以去查阅下资料。
那我们**如何做到将这串像素值变成有很多0的“像素串”呢**
首先第一步,**我们通过减少图像块的空间冗余和时间冗余来接近这个目标**。刚才我们也说到,图像内部相邻宏块之间有很多相似性,并且两张图像之间也有很多相似性。因此,根据图像的这个特点,我们可以在编码的时候进行**帧内预测和帧间预测**。
帧内预测就是在当前编码图像内部已经编码完成的块中找到与将要编码的块相邻的块。一般就是即将编码块的左边块、上边块、左上角块和右上角块,通过将这些块与编码块相邻的像素经过多种不同的算法得到多个不同的预测块。
然后我们再用编码块减去每一个预测块得到一个个残差块。最后,我们取这些算法得到的残差块中像素的绝对值加起来最小的块为预测块。而得到这个预测块的算法为帧内预测模式。
![](https://static001.geekbang.org/resource/image/69/6a/691de4570939f2a5953c848b697e0c6a.jpg?wh=1326x743)
由于这个残差块中像素的绝对值之和最小这个残差块的像素值经过扫描之后的“像素串”是不是就比直接扫描编码块的“像素串”中的像素值更接近0了
同理,帧间预测也是一样的。我们在前面已经编码完成的图像中,循环遍历每一个块,将它作为预测块,用当前的编码块与这个块做差值,得到残差块,取残差块中像素值的绝对值加起来最小的块为预测块,预测块所在的已经编码的图像称为参考帧。预测块在参考帧中的坐标值 (x0, y0) 与编码块在编码帧中的坐标值 (x1, y1) 的差值 (x0 - x1, y0 - y1) 称之为运动矢量。而在参考帧中去寻找预测块的过程称之为运动搜索。事实上编码过程中真正的运动搜索不是一个个块去遍历寻找的,而是有快速的运动搜索算法的。之后我们在帧间预测的课中会详细介绍。
总之,通过预测得到的残差块的像素值相比编码块的像素值,去除了大部分空间冗余信息和时间冗余信息,这样得到的像素值更小。如果把这个残差块做扫描得到的像素串送去做行程编码,是不是相比直接拿编码块的像素串去做编码更有可能得到更大的压缩率?
**但是我们的目标不只是将像素值变小而是希望能出现连续的0像素那怎么办呢**
这就需要利用我们人眼的视觉敏感性的特点了。我们刚才说了人眼对高频信息不太敏感。因为人眼看到的效果可能差别不大,所以我们可以去除一些高频信息。这个就是接下来我们要讨论的 **DCT变换和量化**
为了分离图像块的高频和低频信息我们需要将图像块变换到频域。常用的变换是DCT变换。DCT变换又叫离散余弦变换。在H264里面如果一个块大小是16x16的我们一般会划分成16个4x4的块当然也有划分成8x8做变换的我们这里以4x4为例。然后对每个4x4的块做DCT变换得到相应的4x4的变换块。
变换块的每一个“像素值”我们称为系数。变换块左上角的系数值就是图像的低频信息,其余的就是图像的高频信息,并且高频信息占大部分。低频信息表示的是一张图的总体样貌。一般低频系数的值也比较大。而高频信息主要表示的是图像中人物或物体的轮廓边缘等变化剧烈的地方。高频系数的数量多,但高频系数的值一般比较小(注意不是所有的高频系数都一定小于低频,只是大多数高频系数比较小)。如下图所示(黄色为低频,绿色为高频):
![](https://static001.geekbang.org/resource/image/b7/ed/b737f23d14a7980d305d16d4136e62ed.jpg?wh=1280x720)
这样做完了DCT变换之后低频和高频信息就分离开来了。由于低频信息在左上角其余的都是高频信息。那么如果我们对变换块的像素值进行“之字形”扫描这样得到的像素串前面的就是数值比较大的低频系数后面就是数值比较小的高频部分。
由于人眼对高频信息不太敏感如果我们通过一种手段去除掉大部分高频信息也就是将大部分高频信息置为0但又不太影响人的观感是不是就可以达到我们最初的目标即可以得到有一连串0的像素串这就涉及到量化操作了。
我们让变换块的系数都同时除以一个值,这个值我们称之为**量化步长**也就是QStepQStep是编码器内部的概念用户一般使用**量化参数** QP这个值QP和QStep一一对应你可以自行去网上查询一下转换表得到的结果就是量化后的系数。**QStep越大得到量化后的系数就会越小**。同时相同的QStep值高频系数值相比低频系数值更小量化后就更容易变成0。这样一来我们就可以将大部分高频系数变成0。如下图所示
![](https://static001.geekbang.org/resource/image/41/4d/41b9be2b425a8925c32bfe008f382f4d.jpg?wh=1280x720)
解码的时候我们会将QStep乘以量化后的系数得到变换系数很明显这个**变换系数和原始没有量化的变换系数是不一样的**,这个就是我们常说的有损编码。而到底损失多少呢?
这由QStep来控制QStep越大损失就越大。QStep跟QP一一对应也就是说确定了一个QP值就确定了一个QStep。所以从编码器应用角度来看**QP值越大损失就越大从而画面的清晰度就会越低**。同时QP值越大系数被量化成0的概率就越大这样编码之后码流大小就会越小压缩就会越高。
以上就是视频编码的推理过程。总结一下就是为了能够在最后熵编码的时候压缩率更高我们希望送到熵编码以行程编码为例的“像素串”是一串含有很多0并且最好连续为0的“像素串”。
为了达到这个目标我们先通过帧内预测或者帧间预测去除空间冗余和时间冗余从而得到一个像素值相比编码块小很多的残差块。之后我们再通过DCT变换将低频和高频信息分离开来得到变换块然后再对变换块的系数做量化。由于高频系数通常比较小很容易量化为0同时人眼对高频信息不太敏感这样我们就得到了一串含有很多个0大多数情况下是一串含有连续0的“像素串”并且人的观感还不会太明显。这样最后熵编码就能把图像压缩成比较小的数据以此达到视频压缩的目的。这就是**视频编码的原理**。
## **编码器的对比及选择**
说完了编码器的原理,那么常用的编码标准有哪些呢?它们的区别又是什么?我们怎么选择合适的编码器?
现在市面上常见的编码标准有H264、H265、VP8、VP9和AV1。目前H264和VP8是最常用的编码标准且两者的标准非常相似。H265和VP9分别是他们的下一代编码标准这两个标准也非常相似。AV1是VP9的下一代编码标准。
H264和H265是需要专利费的而VP8和VP9是完全免费的。由于H265需要付高额的版权费以谷歌为首的互联网和芯片巨头公司组织了AOM联盟开发了新一代压缩编码算法AV1并宣布完全免费以此来对抗高额专利费的H265。
目前普通产品还是使用H264最多而H265因为专利费使用得比较少。VP8是WebRTC默认的编码标准且WebRTC使用VP8最多。同时WebRTC也支持VP9和AV1。YouTube使用了VP9和AV1。Netflix也使用了AV1。我们下面来比较一下H264、H265、AV1这三代编码算法的区别。现在你不理解没有关系等你学完后面的课程会很容易明白下面表格中所涉及的知识的。
![](https://static001.geekbang.org/resource/image/b9/a9/b94991907d00bd54743cef34b70e01a9.jpg?wh=1085x467)
从上面表格中可以看到标准越新最大编码块就越大块划分的方式也越多编码模式也就越多。因此压缩效率也会越高但是带来的编码耗时也越大。所以在选择编码器的时候需要根据自己的实际应用场景来选择同时还需要考虑专利费的问题。还有一个就是考虑有没有硬件支持的问题。目前H264和H265的硬件支持已经很好了AV1才刚开始硬件支持较少之后会有更多硬编硬件支持。
我做了个简单的编码清晰度和耗时对比都是在软件编码下进行的。具体如下表所示。我们可以看到相同码率下AV1清晰度稍好于H265而H264最差但是编码耗时则相反AV1最高H265次之H264速度最快。
![](https://static001.geekbang.org/resource/image/c4/22/c48b488cfbd982114a7742f9618c1322.jpg?wh=853x186)
所以如果是在性能比较差的机器上编码最好使用H264和VP8等速度快的编码器。如果是在比较新的机器上可以考虑H265编码。中等机器如果支持H265硬编也是可以考虑的。但有一个问题就是H265需要考虑专利费的问题同时浏览器原生不支持H265编码所以有这方面需求的最好不要使用H265可以考虑使用VP9甚至可以考虑AV1。另外由于AV1原生标准就支持屏幕编码的优化所以屏幕编码场景下可以考虑使用AV1编码。
## 小结
总结一下,我们今天主要讲了视频编码的必要性,以及视频编码的原理。**视频编码主要分为熵编码、预测、DCT变换和量化这几个步骤。**
1. 熵编码以行程编码为例视频编码中真正实现“压缩”的步骤主要去除信息熵冗余。在出现连续多个0像素的时候压缩率会更高。
2. 帧内预测:为了提高熵编码的压缩率,先将当前编码块的相邻块像素经过帧内预测算法得到帧内预测块,再用当前编码块减去帧内预测块得到残差块,从而去掉空间冗余。
3. 帧间预测:类似于帧内预测,在已经编码完成的帧中,先通过运动搜索得到帧间预测块,再与编码块相减得到残差块,从而去除时间冗余。
4. DCT变换和量化将残差块变换到频域分离高频和低频信息。由于高频信息数量多但大小相对较小又人眼对高频信息相对不敏感我们利用这个特点使用QStep对DCT系数进行量化将大部分高频信息量化为0达到去除视觉冗余的目的。
这里你需要注意的是视频编码实际的步骤是预测、DCT变换和量化最后是熵编码。经过这几步操作之后视频中的冗余信息大部分被去除达到了编码压缩的效果。当然如何做帧内预测和帧间预测如何找到最优的预测块DCT变换和量化又是怎么做的呢敬请期待我们接下来的课程我会和你细聊。
## 思考题
现在请你思考一下,视频编码过程中,一帧图像能同时进行帧内预测和帧间预测吗?
你可以把你的答案和感受写下来,分享到留言区,与我一起讨论。下节课再见。