H.264视频压缩编码的基本原理

参考来源:雷神的博客

H.264 编码是一种视频的编码规范,属于 MPEG-4 标准的第 10 部分,由视频图像专家组进行开发。

视频采集原理

当现实中的场景通过摄像头被记录下来时,得到的是一组 YUV 格式的图像帧。对这些帧进行采样且采样频率高于 16 帧/秒时,由于人眼的视觉暂留原理,看到的就是一个连续的视频。采样频率越高,视频就越流畅,反之就越卡顿。但更高的采样频率带来的是更大的数据容量,如果要进行存储,对硬盘的容量和写入速度有很高的要求;如果进行网络传输,则需要更大的带宽。所以需要对视频进行压缩,以达到对人的视觉效果和计算机硬件要求的平衡。

YUV 色彩空间

在介绍 H.264 压缩原理之前,先来熟悉一下 YUV 色彩空间的概念。和 RGB 一样,YUB 色彩空间也是经常被用在视频中的。但现在更常见的后者,因为 RGB 在每个像素上需要存储 Red、Green、Blue 这 3 个字节共 24 比特,而 YUV 中最好的一种存储方式只需要 1.5 字节/像素,共 12 比特。

YUV 色彩空间中,Y 分量代表亮度(Luma),U 和 V 分量也可以写成 Cb 和 Cr,分别代表蓝和红两种色彩。由于人眼对亮度的敏感度高于色彩,所以可以适当降低 UV 的比例来减少存储量。因此可以将其分为 3 种采样格式:

4:4:4

这种格式下,三个分量占一样的比重,也就是每个像素各有一个分量。若以一个圆圈代表 Y 分量,正方形代表 U 分量,点代表 V 分量,如图是四个像素:

这时,每个像素上都各有一个 YUV 分量,因此平均比特数是 3 字节共 24 比特,并没有比 RGB 格式少存储数据。

4:2:2

顾名思义,这种采样格式下,4 个 Y 分量分别对应着两个 UV 分量,如图:

也就是说在两列 Y 分量上,共用同一个 UV 分量,因此平均比特数是 8/4=2 字节共 16 比特,减少了一些数据。

4:2:0

看到这里,你一定会说:我懂了,4:2:0 不就是 4 个 Y 分量对应 2 个 U 分量和 0 个 V 分量嘛!

不好意思,你还是 too young 啊!这里其实是 4 个 Y 分量对应 1 个 U 分量和 1 个 V 分量。至于为什么这么命名,我现在也是百思不得解,但人家既然这么命名了,咱就认了吧。如图:

这是 4 个像素点上的 4 个 Y 分量共同对应一个 UV 分量,平均比特数是 6/4=1.5 字节共 12 比特,对应第一种方案数据量减少了 50%。这也是 H.264 中所采用的采样格式。

视频压缩原理

基本过程就是划分--运动估计和补偿--变换编码--量化--熵编码,接下来一一介绍:

划分

视频是基于帧的,所以对当前的某一帧进行编码时,需要先对其划分为更小的块,称为宏块,简称块。比如某一帧长宽比是 1280x720,就可以分为 160x90 个 8x8 的小块,然后对每一个块逐个处理。

运动估计和补偿

既然要压缩,就得减少数据量。所以不能将每一个块按照原格式存储,可以只计算当前块相对于之前已经编码过的某一个「对比块」的「差值」,然后经过后续的量化和编码过程,就可以减少数据量。

「对比块」可以来自当前块所在的帧,也可以来自其之前或之后的某一帧。由此引出两种预测方式:帧内预测和帧间预测。

所谓预测,就是利用已经编码过的块和差值,来预测当前的块。

帧内预测:即当前块的预测只依赖于当前帧内已经编码过的块。当亮度块为 4x4 大小时,共有 9 种预测模式:

当亮度块大小为 16x16 时,共有 4 种预测模式:

还有一种 8x8 色度预测模式和上面这个类似,不再赘述。

使用帧内预测时,需要将每个块的预测模式进行编码,发送给解码器。

帧间预测:当前块的预测依赖于先前已经编码过的帧中的块。因为相邻的两帧中有可能因为物体的运动而产生偏差,所以需要对每个像素点的位移进行编码,这样才能根据参考帧中的块 + 差值 + 像素位移矢量来复原出当前块。其过程是:

  1. 在参考帧中寻找与当前宏块匹配的 16x16 像素区域,称为「搜索域」。并找到一个与当前块差值最小的参考块,这样就找到了该宏块的运动矢量。这个过程也称为运动估计。
  2. 将当前宏块减去最佳匹配宏块得到的差值,和他们相对的运动矢量一起编码进行传输。这就是运动补偿的过程。在解码器中,根据该矢量去找到参考宏块,然后加上差值就可以复原当前块。

但由此带来的问题是,如果块选择过大,对导致额外编码过多,甚至超过了图像原大小。所以通常采用的办法是对图像中平缓的区域采用大块,细节丰富的区域采用小块。

通常编码器将帧分为 3 种类型:I(Intra)帧,B(Bidirection Prediction)帧和 P(Prediction)帧。其中 I 帧只使用帧内预测模式,B 和 P 帧使用帧间预测模式。

变换编码

因为图像中各个宏块之间存在着关联性,因此是无法压缩的。编码器的变换过程是图像或运动补偿后的残差图像数据转换到另外一个域上,以便于之后的压缩。

目前存在的变换大体可分为两类:块变换和图像变换。块变换有 KL 变换、SVD、傅里叶变换和 DCT 变换;图像变换有离散小波变换(DWT)。MPEG-4 同时支持 DCT 变换和 DWT。我重点写一下 DCT 变换。

DCT 变换称为离散余弦变换,是将 NxN 的样本 $X$ 变成 NxN 的系数矩阵 $Y$ 。变换过程为 $Y=AXA^T$。其中 $X$ 是样本矩阵,$Y$ 是系数矩阵,$A$ 是 NxN 的变换矩阵。$A$ 中各个元素如下:

$$A_{ij}=C_icos\frac{(2j+1)i\pi}{2N}(i>0)$$

其中

$$C_i=\sqrt{\frac{1}{N}}(i=0)$$ $$C_i=\sqrt{\frac{2}{N}}(i!=0)$$

量化

量化器的作用是将处于取值范围 $X$ 中的信号映射到一个较小的取值范围 $Y$ 中,量化后的信号比原信号所需的比特数少,达到了压缩的目的,输出一般是一个包含少数非零值和大量零值得稀疏矩阵。但量化是一个不可逆的过程,所以量化算法的选取要慎重,若映射前后差异较大,会导致视频失真。

最简单的量化方法是将实数四舍五入映射到整数域上。

熵编码

熵编码是因编码后平均码长接近信源熵而得名。其基本原理是对信源中出现概率大的符号赋予短码,概率小的符号赋予长码,从而获得统计意义上较短的平均码长。其实现通常有哈夫曼编码,算数编码,游程编码等。

H.264 的三种档次

H.264 支持三种不同的档次,如下:

H.264 的码流结构

H.264 的主要功能分为两层,视频编码层(VCL)和网络提取层(NAL),视频经过熵编码后会形成连续的比特流,即 VCL 序列。这些流经过 NAL 层的封装后,才可以用来传输或存储。每个 NAL 单元包含一个 RBSP、编码的条带(包括数据分区条带和 IDR 条带)和一个视频序列结束标志。RBSP 被定义为 VCLN 单元,所有其他数据是 NAL 单元。如下:

每个 RBSP 单元在独立的 NAL 单元内传输,NAL 头(一字节)包含了 RBSP 类型,如下表:

RBSP 类型 描述
参数集 对一个序列的全参数,如画面维数,视频格式,宏块分配映射
补充的扩展信息 对正确解码视频序列的不必要的辅助信息
画面定界符 视频画面之间的边界(可选)
编码的片 对一个条带的头和数据,这个RBSP单元包含实际的编码视频数据
数据分区 A,B,C 三个单元包含数据分区条带层数据。分区 A 包含在条带内所有 MB 的头数据,分区 B 包含帧内编码的数据,分区 C 包含帧间编码的数据
序列结束 指示下一个画面帧是 IDR 画面,即 I 帧
流结束 指示在流中没有画面了
填充的数据 用来进行字节对其

以上就是视频在编码时的大致流程,解码是相反的。还有很多细节没写出来,暂时先这样吧,目前还在持续学习中,后期会补充。疑惑之处可随时与我探讨。