目录
1 概述
1.1 视频为什么要编码?
1. 对视频进行编码的最主要目的为了压缩
2. 假设有一部YUV420格式的电影,分辨率为1080P,帧率为25fps,时长为2小时,如果不进行编码压缩,数据量约为521.4GB。如果不进行编码压缩,对于存储和传输的资源消耗都是不可接受的
1920 * 1080 * 1.5 * 25 * 2 * 3600 ≈ 521.4GB
说明:上述计算过程中的1.5B,是YUV420格式存储一个像素的Y、U、V像素值所需的空间,详情可参考视频技术基础01:图像基础和前处理 chapter 2.2.3.4
1.2 常用视频编码标准
目前市面上常见的编码标准有H264、H265、VP8、VP9和AV1,其中,
1. H264和VP8是最常用的编码标准,且二者的标准非常相似
2. H265和VP9分别是他们的下一代编码标准,这两个标准也非常相似
3. AV1是VP9的下一代编码标准
说明:需要注意的是,使用H264和H265需要专利费,而VP8和VP9则是完全免费的
1.3 视频编码对象
1. 视频是由一帧帧图像序列组成的,视频编码就是对一帧帧图像进行的
2. 进行编码的图像为YUV格式,并且是将Y分量和UV分量分开编码
3. 对于每一帧图像,又是划分为一个个块进行编码,在不同编码标准中这种块的命名和大小有所不同
① 在H264中称为宏块,在VP9、AV1中称为超级块,下文使用宏块这个名字进行叙述
② 块的大小一般为16*16(H264、VP8)、32*32(H265、VP9)、64*64(H265、VP9、AV1)、128*128(AV1)
说明:关于为什么YUV格式更适用于视频编码,可参考视频技术基础01:图像基础和前处理 chapter 2.4
1.4 视频为什么可以编码压缩?
可以对视频进行编码压缩,是因为图像一般都有数据冗余,主要包括如下4种,
1. 空间冗余
将图像划分为一个个宏块之后,相邻的块很多时候都有比较明显的相似性,这就是空间冗余
2. 时间冗余
假设帧率为25fps,由于前后两帧图像相差只有40ms,因此前后两帧图像的变化是比较小的,相似性很高,这就是时间冗余
3. 视觉冗余
人眼对于图像中高频信息的敏感度小于低频信息,有时去除图像中的一些高频信息,人眼看不出差别,这就是视觉冗余
注意:去除图像中高频信息的操作属于有损编码
4. 信息熵冗余
人们用于表达某一信息所使用的比特数总比理论上表示该信息所需要的最少比特数大,他们之间的差距就是信息熵冗余(或称作编码冗余)
2 视频编码原理推理过程
2.1 熵编码需求
1. 熵编码(entropy encoding)是指利用数据的统计信息进行压缩的无损编码,常用的熵编码方法有香农-范诺编码(Shannon-Fano)、哈夫曼编码(Huffman)、算术编码(arithmetic)和行程编码(Run Length Encoding)
2. 行程编码示例
以行程编码为例,将字符串"aaaabbbccccc"编码压缩为字符串"4a3b5a",就可以将字符串由13B压缩到7B
3. 如何将行程编码方法应用在图像编码中?
① 以对YUV420格式的图像进行H264编码为例,首先将一帧图像划分成一个个16*16的宏块,则对应的Y、U、V分量分别是16*16、8*8、8*8。由于是对三个分量分别编码,以对Y分量的编码为例,可以从图像的左上角开始以之字形扫描每个像素值,就可以得到一个像素值串
② 将行程编码方法应用在图像编码中,就是对得到的像素值串进行行程编码
4. 如何提高行程编码的压缩率?
① 假设使用行程编码方法对字符串"abcdabcdabcd"进行编码,得到的字符串为"1a1b1c1d1a1b1c1d1a1b1c1d",字符串的长度反而从13B增加到25B
可见如果要达到压缩的目的,必须要使得编码前的字符串中出现比较多连续相同的字符
② 类推到图像编码上也是一样的,必须要使得编码前的像素值串也尽量出现连续相同的像素值,最好是一连串数字很小(最好是0)的像素值串
之所以像素值最好为0,是因为在有些编码算法中(e.g. 指数哥伦布算法、算数编码)可以做到只用一个bit或者0.几个bit就可以存储0值
③ 因此在对图像进行熵编码之前的自然需求,就是先对要编码的像素值进行处理,尽可能去除其中的冗余信息,从而将要编码的像素值串变成有很多0的像素值串
说明:本文以行程编码为例进行说明只是为了便于理解,在实际的H264编码标准中,使用的熵编码方式为CAVLC和CABAC
2.2 减少空间冗余
1. 为了提高熵编码的压缩率,使用帧内预测的方法减少空间冗余
2. 帧内预测处理流程如下,
① 在当前编码图像内部已经完成编码的块中找到与当前编码块相邻的块,并通过帧内预测算法得到帧内预测块
② 将当前编码块减去帧内预测块得到残差块,从而减少空间冗余
说明1:相邻块的选择
一般选择即将编码块的左边块、上边块、左上角块和右上角块作为相邻块
说明2:预测块生成
① 使用不同的算法可以得到多个不同的预测块(上图示例中只是其中一种),因此将当前编码块与不同的预测块相减就会得到多个不同的残差块
② 最终选择这些残差块中像素值的绝对值之和最小的块作为最终的残差块,这个残差块的像素经过扫描生成的像素值串就比直接扫描当前编码块生成的像素值串更接近0
2.3 减少时间冗余
1. 同样是为了提高熵编码的压缩率,使用帧间预测的方法减少时间冗余
2. 帧间预测处理流程如下,
① 在已经编码完成的帧中,通过运动搜索算法得到帧间预测块
② 将当前编码块减去帧间预测块得到残差块,从而减少时间冗余
说明1:通过运动搜索算法得到的预测块也会有多个,因此也会得到多个不同的残差块,此时仍然选择像素值的绝对值之和最小的残差块
说明2:最终选择的预测块所在的已经编码的图像称为参考帧
2.4 减少高频信息
1. 通过帧内预测和帧间预测得到的残差块已经去除了大部分空间冗余和时间冗余,对该残差块进行熵编码可以得到更大的压缩率
但是我们的目标不只是将像素值变小,而是希望能出现连续的0像素,此时就需要通过变换和量化来去除一些高频信息
2. 为了分离图像块的高频和低频部分,需要将残差块变换到频域,通常使用离散余弦变换(DCT变换)。经过DCT变换之后,就可以从残差块得到变换块
变换块中的每个值称为系数,其中左上角的系数值是图像的低频信息,其余的是图像的高频信息
3. 高频信息与低频信息的特性与分布
① 低频信息表示一张图的总体样貌,一般低频系数的值也比较大
② 高频信息主要表示图像中人物或物体的轮廓边缘等变化剧烈的地方,因此高频系数的数量多,但是高频系数的值一般比较小
4. 通过量化减少高频信息
① 由于人眼对高频信息不太敏感,如果通过一种方法去除大部分高频信息,就可以实现在不太影响人眼观感的情况下将变换块中的大部分系数值变为0,也就达到了我们提高熵编码压缩率的目的
② 这种方法就是量化,我们将变换块中的系数都除以一个值(称为量化步长,QStep),得到的结果就是量化后的系数。由于高频系数相比低频系数值更小,量化后就更容易变成0,也就达到了去除高频信息的效果
说明1:量化步长(QStep)和量化参数(QP)
① QStep是编码器内部的概念,用户一般使用QP
② QP和QStep是一一对应的,二者的对应关系如下图所示,
说明2:QStep取值影响
① QStep越大,得到的量化后系数值就会越小,就会去除越多的高频信息,熵编码的压缩率就会更高
② 在解码时,会将QStep乘以量化后的系数得到变换系数,对于之前由于量化变为0的系数此时是无法恢复的,因此量化是一种有损编码
③ QStep值越大,损失就越大,从而解码恢复后的图像清晰度就会越低
说明3:在H264中,如果宏块的大小是16*16,一般会将其划分为16个4*4的块,然后对每个4*4的块做DCT变换
2.5 推理过程小节
1. 为了能够在熵编码时压缩率更高,我们希望进行熵编码的像素值串有尽可能多的连续0像素
2. 为了达到这个目标,先通过帧内预测和帧间预测去除空间冗余和时间冗余,从而得到一个像素值比编码块小很多的残差块
3. 再对残差块进行DCT变换得到变换块,分离出其中的低频信息和高频信息,并利用人眼对高频信息不敏感的特性对变换块的系数进行量化,从而得到有很多连续0像素的像素值串,并对该像素值串进行熵编码
说明1:以上就是视频编码原理的推理过程,视频编码的实际步骤是预测、DCT变换与量化,最后是熵编码
从上图中还可以看出帧内预测和帧间预测是同时进行的,实际上编码器会以宏块为单位遍历所有的预测模式,然后选择最优的预测方式
说明2:在对视频编码过程有了了解之后,下图给出不同编码标准的比较,从中可以看出标准越新,块划分的方式就越多,编码模式也就越多。因此压缩效率也会越大,但是带来的编码耗时也越大
说明3:目前H264和H265的硬件支持已经很好,AV1才刚开始,硬件支持较少
3 H264编码结构
3.1 帧类型
3.1.1 帧内编码帧和帧间编码帧
1. 帧内预测不需要参考已编码帧,可以自行完成编码和解码
2. 帧间预测需要参考已编码帧
① 从类型上,可以参考已编码好的帧内编码帧或帧间编码帧
② 从方向上,可以只参考前面已编码好的帧,也可以同时参考前后已编码好的帧
3.1.2 H264帧类型
1. H264将图像分为I帧(I-frame,Intra-coded picture)、P帧(P-frame,Predicte picture)和B帧(B-frame,Bidirectional predicted picture)
2. 下图是H264中各类帧参考关系的一个示例,其中箭头是从参考帧指向编码帧,其中
① I帧独自编码,不参考其他帧
② 第1个B帧同时参考第1个I帧和第1个P帧
③ 第1个P帧只参考第1个I帧
3.1.3 IDR帧
1. 如果编码或解码过程中有一个参考帧出现错误,那么参考他的P帧或B帧肯定也会出现错误,而这些出现问题的P帧和B帧又会作为其他帧的参考帧,从而导致错误不断被传递
2. 为了截断这种错误编码的传递,H264引入了一种特殊的I帧,称作IDR帧(Instantaneous Decoder Refresh,立即刷新帧)
H264编码标准中规定,IDR帧之后的帧不能参考IDR帧之前的帧。这样IDR帧之前有错误的帧,也不会被IDR帧之后的帧继续参考
说明1:相较于IDR帧,普通的I帧只是使用帧内预测编码,但是在他后面的P帧和B帧还是可以参考普通I帧之前的帧。但是在大多数情况下,会直接使用IDR帧,而不再使用普通的I帧(虽然标准上可以使用)
说明2:B帧虽然也可以作为参考帧,但实际上很少如此使用
3.2 GOP的概念
1. 在引入IDR帧之后,我们将从一个IDR帧开始到下一个IDR帧的前一帧为止的范围称作GOP(Group of Pictures,图像组)
2. GOP的大小由IDR帧之间的间隔决定,这个间隔被称作关键帧间隔
① GOP越大,编码的I帧就会越少。由于P帧和B帧的压缩率更高,因此整个视频的压缩率就越高
② 但是GOP太大,也会导致IDR帧距离太大,如果中间有参考帧错误,则会引起长时间的花屏和卡顿
③ 因此GOP不是越大越好,也不是越小越好,而是需要根据实际的场景来选择
3.3 Slice的概念
1. Slice(也称作"片")是为了并行编码而设计的
① 首先将一帧图像划分为多个相互独立、互不依赖的Slice
② 在机器性能比较高的情况下,可以多线程并行对多个Slice进行编码,从而提升编码速度
③ 但是也因为一帧图像内的Slice是相互独立的,所以如果进行帧内预测,就不能跨Slice进行,因此编码性能会差一些
2. 引入Slice的概念后,一帧图像内的层次结构如下图所示
① 一帧图像可以划分成一个或多个Slice
② 一个Slice包含多个宏块(Macro Block,MB)
③ 一个宏块又可以划分成多个不同尺寸的子块
4 H264码流结构
视频编码的码流结构是指视频经过编码之后得到的二进制数据是如何组织的
4.1 码流格式
H264码流有两种格式:Annexb格式和MP4格式
4.1.1 Annexb格式(附录B格式)
1. Annexb格式使用起始码来表示一个编码数据的开始
① 起始码本身不是图像编码的内容,只是用于分隔
② 起始码有两种,一种是4B的"00 00 00 01",一种是3B的"00 00 01"
2. 通过字节填充解决编码冲突
① 在图像编码数据中也可能出现"00 00 00 01"和"00 00 01"的模式,这样就会导致起始码和图像编码数据的混淆
② H264通过字节填充的方式解决这种冲突,H264会对图像编码数据中的如下模式进行修改,
- 将"00 00 00"修改为"00 00 03 00"
- 将"00 00 01"修改为"00 00 03 01"
- 将"00 00 02"修改为"00 00 03 02"
- 将"00 00 03"修改为"00 00 03 03"
③ 解码端在去掉起始码之后,需要将对应的模式转换回来
4.1.2 MP4格式
MP4格式没有起始码,而是在图像编码数据的开始使用4B作为长度标识,用来表示编码数据的长度
4.2 码流结构
4.2.1 参数集
1. 在H264码流中,除了图像数据,还有一些视频编码时的参数数据。为了能够将一些通用的编码参数提取出来,不在图像编码数据中重复,H264设计了两个重要的参数集
① SPS(Sequence Parameter Set,序列参数集),主要包括图像的高、宽、YUV格式和位深等基本信息
② PPS(Picture Paramter Set,图像参数集),主要包括熵编码类型、基础量化参数QP和最大参考帧数量等基本编码信息
2. H264码流中的SPS和PPS是至关重要的,如果没有SPS和PPS中的基础信息,之后的I帧、P帧和B帧都无法进行解码
3. 在引入了SPS和PPS之后,H264的码流结构如下,
由于图像帧可以划分为Slice,因此图像帧在码流中实际上是以Slice的形式呈现的
4.2.2 NALU的概念
1. 为了在码流中区分上述数据,H264设计了NALU(Network Abstraction Layer Units,网络抽象层单元)。具体结构如下图所示,其中,
① SPS是一个NALU、PPS是一个NALU、每个Slice也是一个NALU
② 每个NALU由1B的NALU Header和若干字节的NALU Data组成
③ 对于Slice NALU,其中的NALU Data又是由Slice Header和Slice Data组成,而Slice Data又是由一个个MB Data组成
2. NALU Header结构如下图所示,其中,
① F位:forbidden_zero_bit,禁止位,H264码流必须位0
② NRI字段:nal_ref_idc,表示当前NALU的重要性,取值范围为00 ~ 11。参考帧、SPS和PPS对应的NALU该字段必须大于0
③ Type字段:nal_unit_type,表示NALU的类型,其取值如下表所示,
可见对于图像Slice,NALU类型中只区分了IDR Slice(类型为5)和非IDR Slice(类型为1),至于非IDR Slice是普通I Slice、P Slice还是B Slice,需要继续解析Slice Header中的Slice Type字段得到
4.2.3 码流观察实例
1. 可以使用Elecard StreamEye工具分析H264码流
2. 查看H264码流的二进制数据,可以从中解析出各NALU,可见与预期是一致的
说明:分析H265码流可以使用Elecard HEVC工具
4.3 常见工程问题
4.3.1 如何判断哪些Slice属于同一帧?
1. 如上文所述,在H264码流中,图像帧在码流中是以Slice的形式呈现的,因此需要能够区分哪些Slice属于同一个帧
2. 这需要通过Slice的Slice Header进行判断,在Slice Header中,有一个first_mb_in_slice字段,该字段表示当前Slice的第一个宏块在当前编码图像中的序号
① 如果first_mb_in_slice字段的值为0,表示当前Slice的第一个宏块是当前编码图像的第一个宏块,也就是说当前Slice是一帧图像的第一个Slice
② 如果first_mb_in_slice字段的值不为0,表示当前Slice不是一帧图像的第一个Slice
只要使用这种方法继续检查后续的Slice,直到找到下一个first_mb_in_slice字段值为0的Slice,就代表新的一帧开始,那么其前一个Slice就是前一帧图像的最后一个Slice
说明:first_mb_in_slice以无符号指数哥伦布编码方式存储
4.3.2 如何从SPS中获取图像分辨率?
1. 在编码端编码视频时,需要设置图像分辨率,但是在解码端不需要设置。这是因为解码端可以从H264码流的SPS中获取分辨率信息
2. 在SPS中使用如下几个字段来表示分辨率的大小,解码端在解码出这几个字段后,通过一定规则的计算就可以得到分辨率的大小
说明1:使用Elecard StreamEye工具查看H264码流的SPS信息
说明2:上述字段也是以无符号指数哥伦布编码方式存储
4.3.3 如何计算得到QP值?
H264码流提供了一种(全局 --> Slice --> 宏块)的QP值设置与调节方式
1. 在PPS中有一个全局基础QP值,字段是pic_init_qp_minus26。当前序列中所有依赖该PPS的Slice共用这个基础QP
2. 每个Slice可以在这个基础QP的基础上做调整,在Slice Header的slice_qp_delta字段中记录了调整偏移值
3. 更进一步地,H264允许在宏块级别对QP值做进一步的精细化调节,这个字段在宏块数据中,叫做mb_qp_delta
说明:设置QP值的字段汇总
① 如果需要计算Slice级别的QP值,则只需要考虑前2个字段
② 如果需要计算宏块级别的QP值,则需要考虑这3个字段
具体计算公式如下,
5 帧内预测
5.1 理论基础
1. 图像具有空间相关性
一帧图像中相邻像素的亮度和色度信息是比较接近的,并且亮度和色度信息也是逐渐变化的,不太会出现突变
2. 帧内编码利用空间相关性进行编码
帧内预测通过利用已经编码的相邻像素的值来预测待编码的像素值,最后达到减少空间冗余的目的
说明:如何利用已编码的像素值预测待编码像素值
① 已编码的像素值已经变成码流,不再是一个个像素
② 在进行编码时,已经编码的块会通过解码重建像素用来做参考像素
5.2 帧内预测规则
说明:以H264标准编码YUV420图像为例
1. 宏块大小为16*16,其中亮度块为16*16,色度块为8*8
2. 16*16的亮度块可以继续划分为16个4*4的子块
因为图像中有的地方细节很多,划分为更小的块来做预测会更精细
3. 帧内预测中亮度块和色度块是分开独立进行预测的
亮度块参考已编码亮度块的像素,色度块参考已编码色度块的像素
说明:在实际帧内预测时会分为:4*4亮度块的预测、16*16亮度块的预测、8*8色度块的预测
其中4*4亮度块的预测模式最多,且基本包含了16*16亮度块和8*8色度块的预测模式,因此先对其进行说明
5.3 4*4亮度块帧内预测模式
4*4亮度块帧内预测模式共有9种,包含8种方向模式和一种DC模式,其中方向模式是指预测是有方向角度的
5.3.1 Vertical模式
1. Vertical模式中,当前编码亮度块的每一列的像素值,都是复制上边已经编码块的最下面一行的对应位置的像素值
2. Veritcal模式预测块像素值计算方法如下
3. Vertical模式只有在上边块存在时才可使用,如果不存在则该模式不可用
例如图像最上边的块就没有可参考的块存在
5.3.2 Horizontal模式
1. Horizontal模式中,当前编码亮度块的每一行的像素值,都是复制左边已经编码块的最右边一列的对应位置的像素值
2. Horizontal模式预测块像素值计算方法如下
3. Horizontal模式只有在左边块存在时才可使用,如果不存在则该模式不可用
5.3.3 DC模式
1. DC模式中,当前编码亮度块的每一个像素值,是上边已经编码块最下边一行和左边已编码块最右边一列的所有像素值的平均值
2. 根据上边块和左边块的存在情况,DC模式预测块像素值计算方法如下,可见DC模式预测块中的每个像素值是一样的
3. DC模式在上边块和左边块都不存在时仍可使用,此时会将预测块的像素值设置为1 << (位深 - 1),如果位深为8bit,则该值为128
5.3.4 Diagonal Down-Left模式
1. Diagonal Down-Left模式中,当前编码亮度块的每一个像素值,是上边块和右上块的像素通过插值得到
注意:上边块和右上块可能是同一个块,因为可能是一个16*16的亮度块
2. Diagonal Down-Left模式预测块像素值计算方法如下
3. Diagonal Down-Left模式只有在上边块和右上块都存在时才可使用,如果有一个不存在则该模式不可用
5.3.5 Diagonal Down-Right模式
1. Diagonal Down-Right模式中,当前编码亮度块的每一个像素值,是上边块、左边块和左上角对角的像素通过插值得到
2. Diagonal Down-Right模式预测块像素值计算方法如下
3. Diagonal Down-Right模式只有在上边块、左边块和左上角对角像素都存在时才可使用,如果有一个不存在则该模式不可用
5.3.6 Vertical-Right模式
1. Vertical-Right模式中,当前编码亮度块的每一个像素值,是上边块、左边块和左上角对角的像素通过插值得到
2. Vertical-Right模式预测块像素值计算方法如下
3. Vertical-Right模式只有在上边块、左边块和左上角对角像素都存在时才可使用,如果有一个不存在则该模式不可用
5.3.7 Horizontal-Down模式
1. Horizontal-Down模式中,当前编码亮度块的每一个像素值,是上边块、左边块和左上角对角的像素通过插值得到
2. Horizontal-Down模式预测块像素值计算方法如下
3. Horizontal-Down模式只有在上边块、左边块和左上角对角像素都存在时才可使用,如果有一个不存在则该模式不可用
5.3.8 Vertical-Left模式
1. Vertical-Left模式中,当前编码亮度块的每一个像素值,是上边块和右上块最下面一行的像素通过插值得到
注意:上边块和右上块可能是同一个块,因为可能是一个16*16的亮度块
2. Vertical-Left模式预测块像素值计算方法如下
3. Vertical-Left模式只有在上边块和右上块都存在时才可使用,如果有一个不存在则该模式不可用
5.3.9 Horizontal-Up模式
1. Horizontal-Up模式中,当前编码亮度块的每一个像素值,是左边块像素通过插值得到
2. Horizontal-Up模式预测块像素值计算方法如下
3. Horizontal-Up模式只有在左边块存在时才可使用,如果不存在则该模式不可用
5.4 16*16亮度块帧内预测模式
1. 16*16亮度块共有4种帧内预测模式,即Vertical模式、Horizontal模式、DC模式和Plane模式,其中前三种预测模式与4*4亮度块帧内预测模式原理相同
2. 对于Plane模式,预测块的每个像素值是将上边已编码块的最下面一行,和左边已编码块最右边一列的像素值经过如下公式计算得到
5.5 8*8色度块帧内预测模式
8*8色度块帧内预测模式与16*16亮度块帧内预测模式相同,也是Vertical模式、Horizontal模式、DC模式和Plane模式。只是块大小不同,所需参考的像素数量不同
说明1:帧内预测模式小结
说明2:在上述预测模式中,都是参考已编码的左边块、左上块、上边块或右上块。这是因为编码顺序是从一帧图像的左上角开始,按行从左向右依次进行的。因此当前编码块其他方向的块还没有编码,不能用来做参考
说明3:视频第1帧图像中的第1块应该如何选择预测模式?
① 由于是视频的第1帧图像,没有前面的帧可供参考,因此只能选择帧内预测模式
② 一帧图像中的第1块由于在帧内预测时没有已编码块可供参考,因此只能选择DC模式,该模式在没有参考像素时也可使用
5.6 帧内预测模式的选择
通过不同的帧内预测模式可以得到不同的预测块,再用待编码块减去预测块就得到相应的残差块。但是每一个块只能有一种帧内预测模式,因此需要从中选择最优预测模式,一般有如下3种方案
1. 先对每一种预测模式残差块的像素值求绝对值再求和(称之为cost),然后取cost最小的预测模式为最优预测模式
2. 对残差块先进行Hadamard变换,变换到频域之后再求绝对值和求和(同样称之为cost),然后取cost最小的预测模式为最优预测模式
说明:Hadarmard变换可以近似频域信息,并且比DCT变换更快
3. 对残差块直接进行DCT变换量化和熵编码,计算得到失真大小和编码后的码流大小,然后通过率失真优化(Rate-distortion optimization)的方法来选择最优预测模式
说明1:帧内预测模式的单位
① H264帧内预测以宏块或子块为单位,而不是以帧为单位
② 每个宏块或子块的预测模式可以是不同的,因此在编码过程中每个块的预测模式也需要编码到码流中,以供解码时使用
说明2:以16*16亮度块为例,直接使用4种16*16的帧内预测模式,最多可以得到4个预测块;也可以划分成16个4*4的子块,每个子块最多可以得到9个预测块
通过上述方法找到每一个4*4块的最优预测模式之后,将这16个4*4 块的cost值相加,与16*16块的最小cost对比,选择cost最小的块划分方式和预测模式作为帧内预测模式
说明3:如果最终选择的最优预测模式为16*16的宏块,而不是4*4的子块,则该宏块有什么特征?
① 说明该16*16宏块对应的图像是比较平坦的,细节相对较少
② 这也从另一个侧面说明了为什么16*16宏块的帧内预测模式比4*4子块少很多,这是因为此时图像比较平坦,为了提升编码速度,就减少了预测模式
如果最终选择的最优预测模式为4*4,则说明图像细节较多,此时提供更多的预测模式有利于更精细地编码
③ 因此对不同大小的块提供不同种类数量的预测模式,是为了兼顾编码精度和速度
说明4:编码速度可以通过编码器的参数进行设置(e.g. x264通过preset来设置)
① 在快速档,会有一些快速算法根据块和周边块的像素特点提前判断预测模式,而无需遍历所有的预测模式,但是得到的就不一定是最优解
② 在慢速档,就可能会遍历每种预测模式得到最优解
说明5:率失真优化思想简介
① 预测之后经过DCT变换再量化会丢失高频信息,且QP值越大,丢失的信息越多,失真就越大,但是码流大小也越小;反之,QP值越小,丢失的信息越少,但是码流大小就越大。这是一个跷跷板
② 一般会在失真和码流之间进行平衡,尽量找到在一定码率下失真最小的模式作为最优预测模式
说明6:其他最优预测模式选择方案
① 还有很多不同的最优预测模式选择方案,例如为了加速模式选择的过程,率失真计算时只进行DCT和量化,但是不进行熵编码,码流大小直接通过QP值估算或者使用预测模式的大小来代替
② 一般来说,选择过程越精细,效果越好,但是速度会越慢
6 帧间预测
6.1 理论基础
1. 图像具有时间相关性
① 每秒视频由多帧图像组成,并通过帧率表示
② 在自然状态下,人或物体的运动在1秒之内引起的画面变化并不大,且自然运动是连续的,所以前后两帧图像往往变化比较小
2. 帧间预测利用时间相关性进行编码
帧间预测通过在已编码的帧中找到一个块来预测待编码块的像素,从而达到减少时间冗余的目的
说明1:帧内预测与帧间预测的区别
① 帧内预测是在当前编码的图像帧中寻找已编码块的像素作为预测帧
② 帧间预测是在其他已经编码的图像帧中寻找已编码块作为预测块
说明2:多参考和单参考
① 帧间预测可以在多个已编码的图像中寻找预测块,这就是多参考;单参考则是只在一帧图像中寻找预测块
② 多参考和单参考的底层原理相同,只是多参考需要多搜索几个参考图像而已,因此后续的讲解以单参考为例
说明3:B帧的预测块
① 帧间预测既可以参考前面的图像,也可以参考后面的图像。如果参考后面的图像,则需要后面的图像提前先编码,然后再编码当前图像
② 只参考前面图像的帧,称为前向参考帧,也就是P帧;参考前后图像的帧,称之为双向参考帧,也就是B帧
③ B帧一个编码块可以有前后两个预测块,最后加权平均得到最终的预测块。由于P帧和B帧的底层逻辑基本相同,因此为了简化过程,后续的讲解以P帧为例
6.2 块大小
1. 与帧内预测类似,帧间预测也有不同的块和子块大小,且类型更多
2. 同样以H264标准处理YUV420图像为例
① 宏块大小为16*16
② 宏块可以划分为16*8、8*16、8*8三种子块
③ 其中8*8的子块还可以继续划分为8*4、4*8、4*4
说明1:上述示例以YUV420图像的亮度块为例,色度块的宽高都是亮度块的一半
说明2:相较于帧内预测,帧间预测是YUV分量共同预测的,而不是按分量进行。色度块不会单独划分,而是与亮度块对应
6.3 参考帧
1. 在帧间预测中,会在已编码的帧中找到一个块来作为预测块,这个已编码的帧就是参考帧
2. 在H264标准中,P帧最多支持从16个参考帧中选出一个作为编码块的参考帧
3. 和帧内预测类似,帧间预测也是以块为单位,同一帧中的不同块可以选择不同的参考帧,这就是上文中多参考的含义
说明:通常在RTC场景中(e.g. WebRTC)中,P帧中的所有块都参考同一个参考帧(也就是使用单参考),并且一般会选择当前编码帧的前一帧来作为参考帧
这么做的原因是自然界的运动一般是连续的,同时在短时间之内的变化相对比较小,所以前面的帧通常是最接近当前编码帧的,两者的差距比较小。因此比较容易从前一帧中找到一个和当前编码块差距很小的块作为预测块,这样编码块减去预测块得到的残差块的像素值就会有很多个0,从而可以提高压缩率
6.4 运动矢量
如下图的场景所示,前后两帧图像中树是不动的,而车向前行驶3个像素
1. 从编码的角度讲,右边图像黄色块的内容和左边图像黄色块的内容基本相同,只是车的行驶导致坐标不同。如果要对右边图像的黄色块进行帧间预测,并且以左边图像作为参考帧,那么左边图像中的黄色块就是最合适的预测块。因为二者内容基本相同,相减后的残差块中会有很多个0
2. 运动矢量用来表示编码帧中编码块和参考帧中的预测块之间的位置差值
例如示例场景中,车从前一幅图像中(32,80)的坐标位置移动当前图像中(80,80)的坐标位置。如果选择前一幅图像中(32,80)这个块作为当前图像中(80,80)这个编码块的预测块,运动矢量就是(32 - 80,80 - 80)=(-48,0)
3. 运动矢量需要编码到码流中,这样解码端只要解码出运动矢量,就可以在参考帧中找到预测块。我们再解码出残差块,将残差块加上预测块就可以恢复出图像
说明:编码时需要给解码端提供从残差块恢复图像块的信息
① 对于帧内预测,需要将预测模式编码到码流中
② 对于帧间预测,需要将参考帧和运动矢量编码到码流中
6.5 运动搜索
6.5.1 运动搜索目标
1. 对于上一节的示例场景,人可以很快识别出车在前后两帧图像中的位置变化,从而在参考帧中找到一个与当前编码块相似的块作为预测块。对于编码器而言,则是通过运动搜算算法来找到合适的预测块
2. 运动搜索的目标就是在参考帧中找到一个合适的块作为预测块,且该预测块与编码块的差距最小。而衡量这个差距的标准,就是相应残差块的像素值绝对值之和(后文简称为SAD,Sum of Absolute Difference)最小
6.5.2 全搜索算法
1. 假设当前编码块大小为16*16,全搜索算法就是从参考帧的第一个像素开始,将每个16*16的块都遍历一遍,然后从中选择相应残差块SAD最小的块作为预测块
2. 全搜索算法一定可以搜索到最相似的预测块,也就是全局最优解。但是缺点也非常明显,就是需要逐个像素去遍历每一个块,非常耗时,因此需要快速运算搜索算法
6.5.3 快速运动搜索算法
说明:本节快速运动搜索算法中的搜索点是指搜索块的左上角像素点
6.5.3.1 钻石搜索算法(菱形搜索算法)
钻石搜算算法以一个菱形的模式去搜索最优预测块,算法步骤如下,
1. 从搜索的起始点开始,以起始点作为菱形的中心点。首先以该中心点为左上角像素的16*16块作为预测块,求得残差块并计算SAD值。之后对菱形4个角的4个点分别做同样的操作并求得SAD值,最小SAD值对应的就是当前最佳匹配点
2. 如果最佳匹配点是菱形的中心点,那么就找到了预测块,搜索结束
3. 如果最佳匹配的不是菱形的中心点,则用以当前最佳匹配点为中心点的菱形继续搜索,重复之前的步骤,直到菱形的中心点为最佳匹配点
说明:钻石搜索算法示例
以本节图示为例,搜索步骤如下,
① 以绿色点(起始点)为中心点,搜索绿色点和旁边蓝色线连接的4个点,得到的最佳匹配点为橙色点,不是菱形的中心点
② 再以橙色点为中心点,搜索橙色点和旁边黄色线连接的4个点,得到的最佳匹配点为橙色点,是菱形的中心点。搜索结束,以橙色点为最佳匹配点
6.5.3.2 六边形搜索算法
六边形搜索算法与菱形搜索算法类似,只是搜索模式为六边形,算步骤如下,
1. 从搜索的起始点开始,以起始点作为六边形的中心点,求得以中心点作为左上角像素的预测块的SAD值。之后对六边形角上的6个点做同样的操作求得SAD值,最小SAD值对应的点就是当前最佳匹配点
2. 如果最佳匹配点是六边形的中心点,则以该点为中心点的菱形和正方形各进行一次精细化搜索。找到中心点、菱形的4个顶点和正方形的4个顶点中SAD值最小的点作为最佳匹配点
3. 如果最佳匹配点不是六边形的中心点,则用以当前最佳匹配点为中心点的六边形继续搜索,重复之前的步骤,直到中心点为最佳匹配点
说明1:六边形搜索算法示例
以本节图示为例,搜索步骤如下,
① 以绿色点(起始点)为中心点,搜索中心点和旁边蓝色线连接的6个点,得到最佳匹配点为橙色点,不是六边形的中心点
② 再以橙色点为中心点,搜索橙色点和旁边黄色线连接的6个点,得到最佳匹配点为橙色点,是六边形的中心点
③ 再以橙色点为中心点,搜索橙色点和旁边蓝色线连接的菱形的4个点,最佳匹配点为黑色点
④ 还是以橙色点为中心点,搜索旁边红色线连接的正方形的4个点,并于菱形搜索得到的最佳匹配点黑色点比较,找到最后的最佳匹配点为红色点,搜索结束
说明2:通过上述运动搜索算法就可以得到编码块在参考帧中的最佳匹配点,以最佳匹配点为左上角像素的块就是预测块,并且预测块左上角像素在参考帧中的坐标(x1,y1)与编码块在当前帧中的坐标(x0,y0)之间的插值(x1 - x0,y1 - y0)就是运动矢量
说明3:快速搜索算法不需要遍历整个参考帧的像素去寻找预测块,因此速度可以快很多;但是搜索到的预测块不一定是全局最优预测块
但是根据相关实验数据,快速搜索算法相比全搜索算法压缩性能下降非常小,但是速度却可以提升十几倍到几十倍。所以总的来说,快速搜索算法远好于全搜索算法
说明4:钻石搜索算法和六边形搜索算法比较
① 钻石搜索算法更简单,步骤更少,所以如果需要编码速度快,一般选择钻石搜索
② 六边形搜索算法步骤更多,更精细。如果编码质量要求高,同时对编码速度要求不高时,可以选择六边形搜索
说明5:关于快速运动搜索的起始点
① 我们一般会通过相邻已编码块的运动矢量来预测当前编码块的运动矢量,并且用这个预测的运动矢量作为快速运动搜索的起始点
② 这里的思路是一个块最大只有16*16像素,而运动的物体一般远大于这个块的大小,所以相邻块的运动方向大多数是相似的
6.6 亚像素插值
6.6.1 问题引入
假设前后两帧图像中的车向前行驶的不是完整的48个像素点,而是48.5或者48.25个像素点,那么相应的运动矢量该如何表示
1. 如果使用目前已有的整像素,则无法精确表示运动矢量
此时仍然可以使用(-48,0)作为运动矢量,只是预测块中车的位置与编码块中车的位置没有完全重合,会相差0.5或者0.25个像素。因此得到的残差会大一些,压缩效率也就会低一些
2. 为了更精确的表示运动矢量,解决这种半个像素或者1/4个像素的运动带来的压缩效率下降问题,引入了亚像素插值方法
所谓亚像素插值,就是通过插值的方式将半像素和1/4像素计算出来,并且也当作一个像素
说明:在引入半像素与1/4像素之前,其实不会出现运动矢量不是整数的情况,要注意这里的逻辑关系
6.6.2 亚像素插值方法
6.6.2.1 概述
1. 亚像素插值归根到底还是插值操作,都是通过已有像素点经过一定的加权计算得到需要求得的像素值
2. 在亚像素插值中,已有的像素就是整像素(即图像本身有的像素),需要插值求得半像素和1/4像素,其中,
① 半像素通过整像素插值得到
② 1/4像素通过整像素和半像素插值得到
6.6.2.2 半像素插值方法
1. 上图中,灰色为整像素点,橙色为水平半像素点,黄色为垂直半像素点,绿色为中心半像素点
2. 半像素点的插值是以6个整像素点使用六抽头插值滤波器计算得到,b / s / h / m / j这5个半像素点具体计算方式如下,
6.6.2.3 1/4像素插值方法
1. 上图中,红色点为1/4像素点,也分为水平1/4像素点,垂直1/4像素点,中心1/4像素点
2. 1/4像素点由整像素点和半像素点求平均值得到,1/4像素点a的具体计算方式如下,
6.6.3 亚像素精度运动搜索
引入亚像素插值算法后,就可以在整像素运动搜索的基础上进行亚像素精度搜索,一般步骤如下,
1. 先通过快速运动搜索算法进行整像素运动搜索,得到整像素的运动矢量
2. 对参考帧进行半像素和1/4像素插值
3. 以整像素运动矢量指向的整像素点为起点,进行钻石搜索算法,分别求得中心点及上下左右4个半像素点对应预测块的残差块,并计算SAD值。取SAD值最小的点为最佳匹配点
4. 以半像素运动搜索的最佳匹配点为起点,分别求得中心点及上下左右4个1/4像素点对应预测块的残差块,并计算SAD值。取SAD值最小的点为最佳匹配点
说明1:亚像素精度只是提供了更多的选择
① 插值得到亚像素点上的车和整像素点构成的车不是完全一样的,毕竟插值得到的像素点是利用滤波算法加权平均得到的,因此半像素插值得到的预测块并不一定就比整像素预测块的残差小
② 亚像素精度的意义是提供了很多个半像素预测块和1/4像素预测块的选择,我们可以在整像素预测块、半像素预测块和1/4像素预测块中选择一个最好的
③ 根据相关实验数据,半像素和1/4像素精度的运动搜索相比整像素精度的运动搜索可以明显提高压缩率
说明2:亚像素最终运动矢量的表示形式
如果最终选择的是亚像素精度的预测块,其中整像素运动矢量为(a0,b0),半像素最佳匹配点相对于整像素最佳匹配点的运动矢量为(a1,b1),1/4像素最佳匹配点相对于半像素最佳匹配点的运动矢量为(a2,b2),那么最终运动矢量(a,b)的计算方式如下,也就是将像素点位置都映射到1/4像素插值图像中
当然,另一种表示方式是将像素点的位置都映射到整像素图像中,但是这样就会出现小数。但是因为浮点数会有精度误差,所以我们不希望使用小数来表示运动矢量
6.7 运动矢量预测
6.7.1 运动矢量编码
1. 通过上面的整像素运动搜索和亚像素精度运动搜索,就可以得到最终的运动矢量,该运动矢量信息需要编码到码流中
2. 但是并不是将运动矢量直接编码到码流中,而是按如下步骤编码
① 先用当前编码块周围已编码块相邻块的运动矢量得到一个预测运动矢量(称之为MVP,Motion Vector Prediction)
② 将当前编码块运动矢量与MVP的残差(称之为MVD,MotionVector Difference)编码到码流中
说明:解码端使用同样的运动矢量预测算法得到MVP,并从码流中解码出运动矢量残差MVD,将MVP + MVD就恢复了运动矢量
6.7.2 运动矢量预测步骤
以16*16宏块为例说明运动矢量算法的预测步骤(也就是计算MVP的步骤),
1. 取当前编码块的左边块A、上边块B和右上块C
如果右上块不存在或者参考帧与当前编码块不同(在多参考时会出现这种情况),则使用左上块D替换C
2. 计算A、B、C块的参考帧中有多少个与当前编码块的参考帧相同,记为count
3. 如果count > 1,则取A、B、C块运动矢量的中值(也就是A、B、C块运动矢量3个x和3个y分别取中值)作为MVP
4. 如果如果count = 1,则直接将这个块的运动矢量作为MVP
5. 如果count = 0,并且B、C不存在,A存在的话,则直接将A的运动矢量作为MVP
6. 如果上述条件都不满足,则取A、B、C块运动矢量的中值作为MVP
6.8 SKIP模式
同时满足如下2个条件的编码块的模式被称作SKIP模式,
1. 运动矢量就是MVP,也就是说MVD为(0,0)
2. 残差块经过变换量化后系数全为0
由于MVD和残差块都等于0,因此压缩效率特别高
说明1:不符合SKIP模式的块,要么MVD不为0,要么变换量化后的残差系数不全为0
说明2:SKIP模式图像示例
① 对于P帧中的静止部分,前后两帧不会变换,因此运动矢量直接为0。而且残差块像素值本身基本为0,只有少部分噪声引起的比较小的值,这些值量化后也全部变为0
② 这种图像中的静止部分或者是图像中的背景部分,大多数时候都是SKIP模式
6.9 帧间预测模式的选择
帧间预测模式的选择主要涉及参考帧的选择、运动矢量的确定、块大小的选择以及SKIP模式的判断,主要步骤如下,
1. 首先通过相关算法判断当前宏块是否属于SKIP模式,如果属于SKIP模式则模式选择结束,不再进行下面的划分
2. 宏块大小为16*16,首先不划分宏块,直接使用16*16大小的块在参考帧中进行运动搜索,得到运动矢量和预测块,之后
① 通过MVP计算MVD,并估计MVD编码后占用的字节数
② 通过预测块求得残差块,并求得残差块的SATD值(残差块经过Hadamard变换后求绝对值之和)
③ 将上述2个值相加,作为cost16*16
3. 将16*16块按步骤进行划分,并计算各种cost组合值,并最终确定块大小的划分方式,从而得到帧间预测最终的cost值
4. 对比帧间预测和帧内预测的cost值,选择cost值更小的预测模式
在P帧和B帧中的宏块也可以使用帧内预测模式(预测是按块进行的),这完全由cost值决定。但是一般来说,P帧和B帧宏块决策出来绝大多数还是使用帧间预测模式
说明:帧间模式的选择主要是依据编码器的选择,上述步骤只是其中一种模式选择思路
7 变换量化简介
7.1 DCT变换概述
1. DCT变换就是离散余弦变换,他能够将图像从空域转换到频域。DCT变换主要用于视频压缩领域,目前常用的视频压缩算法中基本上都有DCT变换
2. 在视频编码中,DCT变换是在帧内预测和帧间预测之后进行的,因此DCT变换实际上是对残差块进行的
3. 图像经过DCT变换后,低频信息集中在左上角,而高频信息分散在其他位置。通常情况下,高频信主要描述图像的边缘信息,因此图像的高频信息多但是幅值比较小
之所以会有这样的频域信息分布,是因为频域平面上的变换系数是二维频域变量u和v的函数,左上角对应的是u = 0且v = 0的系数,因此称作直流分量(也称作DC系数);其余系数则是交流分量(也称作AC系数)
4. DCT变换通常在4*4的子块上进行
① 即使预测时并没有对宏块再做划分,DCT变换通常也是在4*4的子块上进行
② DCT变换也可以在8*8子块上进行,但是只有在扩展profile时才支持
5. DCT变换本身是无损的,同时也是可逆的。导致信息有丢失的,是后续的量化操作
6. 一般在编码标准中图像是进行二维DCT变换,因为图像本身是二维信号。但是在实际的代码中,经常将二维DCT变换转换成两个一维DCT变换来进行
7.2 量化概述
1. 量化操作在变换之后进行,本质上就是除法操作,计算公式如下,
2. 量化操作并不是只针对AC系数进行,DC系数也同样会做量化。只是在通常情况下,DC系数比较大,从而量化后变为0的概率比AC系数要小
从上图示例中可见,DC系数也可能被量化为0
3. 通常QStep值越大,DC系数和AC系数被量化为0的概率也越大,从而压缩程度就越大,但是丢失的信息也就越多
如果QStep值较小的话,压缩程序也会比较小,从而图像失真就会比较小,但是压缩之后的码流就会比较大
7.3 Hadamard变换概述
1. Hadamard变换可以在一定程度上粗略代替DCT变换,从而简化运算
DCT变换会涉及浮点数运算,但是Hadamard变换没有浮点运算,因此计算速度更快,并且也能将图像从空域变换到频域
2. 在视频编码过程中,Hadamard变换也经常会用到,在上文介绍的帧内预测和帧间预测中均会用到
7.4 H264中的变换与量化
1. DCT变换会涉及浮点运算,而量化过程的除法运算大多数时候得到的结果也是浮点数。H264为了减少浮点运算带来的误差,将DCT变换改成了整数变换。DCT变换中的浮点运算和量化过程合并,这样就只有一次浮点运算过程
2. H264各模式块的DCT变换和量化过程
① 亮度16*16帧内预测块
- 亮度16*16的宏块首先被划分成16个4*4的小块做整数变换
- 变换后将16个4*4小块的DC系数都拿出来,组成一个4*4的DC块,再对这个4*4的DC块进行Hadamard变换
- 最后再总体进行量化操作
② 其他模式亮度块
- 将宏块划分成4*4的小块进行整数变换
- 对整数变换的结果直接进行量化操作
③ 色度块
- 对于YUV420图像,色度块大小是8*8。首先将其划分成4个4*4的小块做整数变换
- 变换之后将4个小块的DC系数拿出来,组成一个2*2的DC块,再对这个DC块进行Hadamard变换
- 最后总体进行量化操作
说明:对DC系数再做一次Hadamard变换是因为有时候DC系数相关性比较高,进一步变换之后可提高压缩性能