解析H264视频编码原理——从孙艺珍的电影说起(二)

2,400 阅读9分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情

更多博文,请看音视频系统学习的浪漫马车之总目录

实践项目: 介绍一个自己刚出炉的安卓音视频播放录制开源项目

视频理论基础:
视频基础知识扫盲
音视频开发基础知识之YUV颜色编码
解析视频编码原理——从孙艺珍的电影说起(一)
解析视频编码原理——从孙艺珍的电影说起(二)
H264码流结构一探究竟

Android平台MediaCodec系列:
Android硬编解码利器MediaCodec解析——从猪肉餐馆的故事讲起(一)
Android硬编解码工具MediaCodec解析——从猪肉餐馆的故事讲起(二)
Android硬编解码工具MediaCodec解析——从猪肉餐馆的故事讲起(三)

轻松入门OpenGL系列
轻松入门OpenGL ES——图形渲染管线的那些事
轻松入门OpenGL ES——再谈OpenGL工作机制

上一篇博文解析视频编码原理——从孙艺珍的电影说起(一)简单介绍了H264历史背景,并着重介绍了H264中的帧内帧间预测,今天继续讲解H264编码剩余部分——变换量化、熵编码。

变换量化

DCT变换

首先说说图像频率是什么。图像可以看做是一个定义为二维平面上的信号,该信号的幅值对应于像素的灰度值(对于彩色图像则是RGB三个分量),如果我们仅仅考虑图像上某一行像素,则可以将之视为一个定义在一维空间上的信号,这个信号在形式上与传统的信号处理领域的时变信号是相似的,时变信号是有一定频率成分的,图像的频率又称为空间频率,它反映了图像的像素灰度在空间中变化的情况。

一般图片的高频信息多但是幅值比较小。高频信息主要描述图片的边缘或者细节信息,对于快速空间变化的图像来说,比如充满沟壑的山脉,其高频成分会相对较强,低频则较弱。而低频主要是图像的整体轮廓信息。由于人眼的视觉敏感度是有限的,有的时候我们去除了一部分高频信息之后,人眼看上去感觉区别并不大。这就是去除视觉冗余

因此,我们可以先将图片通过 DCT(离散余弦变换) 变换到频域,然后再去除一些高频信息。这样我们就可以减少信息量,从而达到压缩的目的

如何转变到频域呢?还记得傅里叶变换么,任何”周期信号都可以用一系列成谐波关系的正弦曲线来表示。由于本文不准备探讨算法部分,所以不打算深入数学部分,一张图就可以让你有总体的理解:

傅里叶变换

DCT的变换公式如下:
在这里插入图片描述

其中X为残差块,Y为变换后的块,A为:

在这里插入图片描述 当然这里不纠结数学细节,我们要知道的是,DCT的流程主要做了什么?

通常情况下 DCT 变换是在 4x4 的子块上进行的,假如当前残差块为:

(图来源于:变换量化:如何减少视觉冗余在这里插入图片描述

经过上面公式变换,得到:

(图来源于:变换量化:如何减少视觉冗余在这里插入图片描述

系数的意义

这里的关键点网络很少有讲清楚的,就是这里变换后的块的每个系数代表的意义是什么。

这里的核心思想在于谐波的叠加,和上面的傅里叶变化思想是类似的。

在DCT基本理论中,所有4*4图像块都可以用以下的基本图像通过乘以各自系数叠加组成:

在这里插入图片描述 从左上角到右下角分别是从直流分量到频率越来越高的谐波。 而通过DCT变换得到的结果的块上的每个系数,正好是和上图每个基本模式图位置一一对应的系数

(图来源于:变换量化:如何减少视觉冗余在这里插入图片描述

比如上图中,-10即为直流分量在这里插入图片描述的系数,

35即为第一行第二个基本模式图在这里插入图片描述 的系数。以此类推。

注意到在变换后的块中,左上角的系数往往比较大,越靠近右下角的系数越小,这是就是因为高频系数一般是比较小的缘故,而低频分量一般比较大。

量化

DCT变换并没有进行压缩,压缩需要后面一步的支持——量化。

量化其实就是一个除法操作。通过除法操作就可以将系数变小,而高频信息幅值比较小,就比较容易被量化成 0,这样就能够达到压缩的目的。

量化公式如下: 在这里插入图片描述

X为DCT变换后的系数,QP也叫作量化参数,round就是四舍五入。FQ就是量化后的系数。

上面例子的4*4宏块经过取QP为30的量化后得到:

(图来源于:变换量化:如何减少视觉冗余在这里插入图片描述

可以看到,现在大部分系数都变为0了,这就是通过量化去除高频分量的结果,右下角部分基本都为0。

(另外,关于H264的变换量化要说的是:

1.DCT变换由于存在浮点运算,比较慢,所以H264还引入了将图像块更快速的转换到频域的Hadamard 变换 。这里要提下H264的亮度 16x16 帧内预测块和色度 8x8 预测块中也会使用到另一种类似DCT变换的Hadamard 变换。

2.DCT 变换过程中涉及到浮点运算,在不同机器上解码会因为精度问题产生漂移导致误差。264 为了减少这种浮点型运算漂移带来的误差,将 DCT 变换改成了整数变换,DCT 变换中的浮点运算和量化过程合并,这样就只有一次浮点运算过程

不过这里属于数学细节了,本文就不纠结太细节的东西了)

熵编码

视频编码中真正实现“压缩”的步骤,主要去除信息熵冗余。而前面说的去除空间、时间、视觉冗余,其实都是为这一步做准备的。

注意到,前面的帧内帧间预测得到的残差以及后面的变换量化,都是将数据尽量转换为连续的0更多的表现形式,然后再利用合理的编码算法去编码形成最终的码流。

重排序

为了使得码流有尽量多的‘0’,根据变换量化后的块靠近右下角的基本都是‘0’的特点,将用‘Zigzag’方式扫描,使得得到的数字串有大量连续的‘0’。 在这里插入图片描述

比如上图的块用“Zigzag”方式扫描之后,得到的数字串就是“0200010000000000”,可见有大量连续的‘0’。

编码算法

重排序得到的数字串最后会进行编码,进行最后的压缩形成最终的码流。编码一般分为定长编码和变长编码,定长编码常见的有UTF-16,ASCII等,边长编码常见有哈弗曼编码。一般来说,如果是出现频率高的短数据,用边长编码更省空间。而H264根据不同数据段的特点,码流参数部分划分为定长编码和哥伦布编码,视频内容部分使用的是内容自适应的(CABAC或CAVLC)算术编码。具体编码算法这里就不深入了,不过核心思想都是利用了连续的‘0’尽可能去进行压缩。

我们应该见过一道压缩字符串的编程题目,就是将 “aaaabbbccccc” 压缩成 “4a3b5c”,字符串由 13 个字符压缩到 7 个字符,这个叫做行程编码。熵编码中使用了类似行程编码的思想处理图像扫描出来的像素数据,但是使用行程编码不一定能够压缩数据,如果刚才编程题的字符串是 “abcdabcdabcd” 的话,那么编码之后就会是 “1a1b1c1d1a1b1c1d1a1b1c1d”。字符串的大小从 13 字符变成了 25 字符,还反而变大了。如何使得变小呢,答案就是尽量使得其为连续的字符,最好是连续的‘0’,因为0可以用最少的位数存储(比如指数哥伦布编码,它就可以做到 0 只占用一个位。)。那么这里的编码,也是利用类似的思想。

总流程梳理

前面的内容比较多且零碎,最后将前面的内容捏合在一起,放在流程形成整体里面看。如下图就是H264整个编码流程: 在这里插入图片描述 整个流程分为向前的编码分支和向后的重建解码分支。

向前的编码分支:编码一个宏块分支经历帧内或者帧间预测去除时间、空间冗余信息,然后经过变换、量化去除视觉冗余信息、然后再经过重排序、熵编码几个阶段,其中前面的步骤都是为了尽量去除冗余数据以将数据量尽量减少形成更多连续的‘0’,熵编码是真正将数据编码为码流的步骤。

向后的重建分支:将编码好的宏块重新解码重建出来,是为了给帧间预测提供参考帧的,因为帧间预测的参考宏块都是之前已经编码好的宏块的重建。

解码流程和编码的后向重建分支基本一致: 在这里插入图片描述

总结

基于H264的视频编码原理就先说到这里,最后来给本文和上篇博文解析视频编码原理——从孙艺珍的电影说起(一)梳理了以下视频编码原理总体流程:

在这里插入图片描述

个人也将上述内容做成一个脑图便于理解: 在这里插入图片描述

希望大家看过本文能够对视频编码流程有所体会。

由于视频编码技术复杂,本人水平有限,有错误的地方也请各位指正哈~

参考文章:
编码原理:视频究竟是怎么编码压缩的?
帧内预测:如何减少空间冗余?
帧间预测:如何减少时间冗余?
变换量化:如何减少视觉冗余?
H264 I帧 P帧 B帧
《深入理解视频编解码技术》
《H.264和MPEG-4视频压缩 新一代多媒体的视频编码技术》

原创不易,如果觉得本文对自己有帮助,别忘了随手点赞和关注,这也是我创作的最大动力~