H.264

H264 简介

H.264,同时也是 MPEG-4 第十部分,是由 ITU-T 视频编码专家组(VCEG)和 ISO/IEC 动态图像专家组(MPEG)联合组成的联合视频组(JVT,Joint Video Team)提出的高度压缩数字视频编解码器标准。这个标准通常被称之为 H.264/AVC(或者 AVC/H.264 或者 H.264/MPEG-4 AVC 或 MPEG-4/H.264 AVC)。是一种面向块,基于运动补偿的视频编码标准。

视频编码其本质就是将数据压缩,主要是去除冗余信息(包括空间上的冗余信息和时间上的冗余信息),从而实现数据量的压缩。

  • 空间冗余: 在同一图像(帧)内,相近像素之间的差别很小(甚至是相同的),所以就可以用一个特定大小的像素矩阵来表示相邻的像素。
  • 时间冗余: 视频中连续的图像(帧)之间,其中发生变化的像素占整张图像像素的比例极其微小,所以就可以用其中一帧来表示相邻的帧来减少带宽消耗。
  • 编码冗余: 不同像素出现的概率不同,所以就可以为出现概率高的像素分配尽量少的字节,对出现概率低的像素分配尽量多的字节。
  • 视觉冗余:人眼对很多像素颜色不敏感,所以就可以丢弃这些冗余的信息而并不影响人眼观看的效果。
  • 知识冗余:有许多图像的理解与某些基础知识有相当大的相关性。例如,人脸的图像有固定的结构,嘴的上方有鼻子,鼻子的上方有眼睛,鼻子位于正面图像的中线上等等。这类规律性的结构可由先验知识和背景知识得到,我们称此类冗余为知识冗余。根据已有知识,对某些图像中所包含的物体,可以构造其基本模型,并创建对应各种特征的图像库,进而图像的存储只需要保存一些特征参数,从而可以大大减少数据量。

用一个简单的例子来说明编码的必要性:
当你此刻显示器正在播放一个视频,分辨率是1280*720,帧率是25,那么一秒所产生正常的数据大小为:

1280×720(位像素)×25(帧) / 8(1字节8位)(转换结果:B) / 1024(转换结果:KB) / 1024
(转换结果:MB) = 2.74MB

那么90分钟的电影就要14.8GB,这个数据量显然在当前网络下是不现实的。

H264 功能结构

在 H.264/AVC 视频编码标准中,整个系统框架划分为如下两个层面:

  • 视频编码层(Video Coding Layer,VCL):VCL 数据即被压缩编码后的视频数据序列,负责有效表示视频数据的内容,主要包括帧内预测,帧间预测、变换量化、熵编码等压缩单元。
  • 网络抽象层(Network Abstraction Layer,NAL):负责将 VCL 数据封装到 NAL 单元中,并提供头信息,以保证数据适合各种信道和存储介质上的传输。

H264 帧类型

H264 结构中,一个视频图像编码后的数据叫做一帧,一帧由一个片(slice)或多个片组成,一个片由一个或多个宏块(MB)组成,一个宏块由16x16的 yuv 数据组成。宏块作为 H264 编码的基本单位。

经过压缩后的帧分为:I帧,P帧 和 B帧:

  • I帧:关键帧,采用==帧内压缩技术==。你可以理解为这一帧画面的完整保留,解码时只需要本帧数据就可以完成(因为包含完整画面)。
  • P帧:向前参考帧,在压缩时,只参考前面已经处理的帧。采用==帧间压缩技术==。P 帧表示的是这一帧跟之前的一个关键帧(或 P 帧)的差别,解码时需要用之前缓存的画面叠加上本帧定义的差别,生成最终画面。(也就是差别帧,P 帧没有完整画面数据,只有与前一帧的画面差别的数据)。
  • B帧:双向参考帧,在压缩时,它即参考前而的帧,又参考它后面的帧。采用==帧间压缩技术==。B 帧记录的是本帧与前后帧的差别,换言之,要解码 B 帧,不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面的与本帧数据的叠加取得最终的画面。B 帧压缩率高,但是解码时 CPU 工作量会比较大。

两个 I 帧之间是一个图像序列,即 GOP,在一个图像序列中只有一个I帧。

当运动变化比较少时,一个序列可以很长,因为运动变化少就代表图像画面的内容变动很小,所以就可以编一个 I 帧,然后一直 P 帧、B 帧了。当运动变化多时,可能一个序列就比较短了。

还有一个概念是,IDR帧:
一个序列的第一个图像叫做 IDR 图像(立即刷新图像),IDR 图像都是 I 帧图像。H.264 引入 IDR 图像是为了解码的重同步,当解码器解码到 IDR 图像时,立即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。这样,如果前一个序列出现重大错误,在这里可以获得重新同步的机会。IDR 图像之后的图像永远不会使用 IDR 之前的图像的数据来解码。IDR 图像一定是 I 图像,但 I 图像不一定是 IDR 图像。一个序列中可以有很多的I图像,I 图像之后的图像可以引用 I 图像之间的图像做运动参考。

还有一点注意的,对于 IDR 帧来说,在 IDR 帧之后的所有帧都不能引用任何 IDR 帧之前的帧的内容,与此相反,对于普通的 I 帧来说,位于其之后的 B帧 和 P 帧可以引用位于普通 I 帧之前的 I 帧。从随机存取的视频流中,播放器永远可以从一个 IDR 帧播放,因为在它之后没有任何帧引用之前的帧。但是,不能在一个没有 IDR 帧的视频中从任意点开始播放,因为后面的帧总是会引用前面的帧。
I、B、P 各帧是根据压缩算法的需要,是人为定义的,它们都是实实在在的物理帧。一般来说,I 帧的压缩率是7(跟JPG差不多),P 帧是20,B 帧可以达到50。可见使用 B 帧能节省大量空间,节省出来的空间可以用来保存多一些 I 帧,这样在相同码率下,可以提供更好的画质。

正因为有 B 帧这样的双向预测帧的存在,某一帧的解码序列和实际的显示序列是不一样的。如下图所示:

DTS: (Decode Time Stamp) 用于视频的解码序列
PTS: (Presentation Time Stamp)用于视频的显示序列。

H264 编码原理

H264 采用的核心压缩算法是帧内压缩和帧间压缩,帧内压缩是生成 I 帧的算法,帧间压缩是生成 B 帧和 P 帧的算法。

  • 帧内(Intraframe)压缩:也称为空间压缩(Spatialcompression)。当压缩一帧图像时,仅考虑本帧的数据而不考虑相邻帧之间的冗余信息,这实际上与静态图像压缩类似。帧内一般采用有损压缩算法,由于帧内压缩是编码一个完整的图像,所以可以独立的解码、显示。帧内压缩一般达不到很高的压缩,跟编码 JPEG 差不多。
  • 帧间(Interframe)压缩:也称为时间压缩(Temporalcompression),它通过比较时间轴上不同帧之间的数据进行压缩。帧间压缩一般是无损的。帧差值(Framedifferencing)算法是一种典型的时间压缩法,相邻几帧的数据有很大的相关性,它通过比较本帧与相邻帧之间的差异,仅记录本帧与其相邻帧的差值,这样可以大大减少数据量。

下面就简单描述下 H264 压缩数据的过程。通过摄像头采集到的视频帧(按每秒 30 帧算),被送到 H264 编码器的缓冲区中。编码器先要为每一幅图片划分宏块。

划分宏块

H264 默认是使用 16X16 大小的区域作为一个宏块,也可以划分成 8X8 大小。

划分好宏块后,计算宏块的象素值。

以此类推,计算一幅图像中每个宏块的像素值。

划分子块

H264 对比较平坦的图像使用 16X16 大小的宏块。但为了更高的压缩率,还可以在 16X16 的宏块上更划分出更小的子块。子块的大小可以是 8X16、 16X8、 8X8、 4X8、 8X4、 4X4非常的灵活。

帧分组

对于视频数据主要有两类数据冗余,一类是时间上的数据冗余,另一类是空间上的数据冗余。其中时间上的数据冗余是最大的。为什么这么说呢?假设摄像头每秒抓取30帧,这30帧的数据大部分情况下都是相关联的。也有可能不止30帧的的数据,可能几十帧,上百帧的数据都是关联特别密切的。
对于这些关联特别密切的帧,其实我们只需要保存一帧的数据,其它帧都可以通过这一帧再按某种规则预测出来,所以说视频数据在时间上的冗余是最多的。
为了达到相关帧通过预测的方法来压缩数据,就需要将视频帧进行分组。

H264 编码器会按顺序,每次取出两幅相邻的帧进行宏块比较,计算两帧的相似度。

通过宏块扫描与宏块搜索可以发现这两个帧的关联度是非常高的。进而发现这一组帧的关联度都是非常高的。因此,上面这几帧就可以划分为一组。其算法是:在相邻几幅图像画面中,一般有差别的像素只有10%以内的点,亮度差值变化不超过2%,而色度差值的变化只有1%以内,我们认为这样的图可以分到一组。
在这样一组帧中,经过编码后,我们只保留第一帖的完整数据,其它帧都通过参考上一帧计算出来。我们称第一帧为 IDR/I 帧,其它帧我们称为 P/B 帧,这样编码后的数据帧组我们称为 GOP。

运动估计与补偿

在 H264 编码器中将帧分组后,就要计算帧组内物体的运动矢量了。

H264 编码器首先按顺序从缓冲区头部取出两帧视频数据,然后进行宏块扫描。当发现其中一幅图片中有物体时,就在另一幅图的邻近位置(搜索窗口中)进行搜索。如果此时在另一幅图中找到该物体,那么就可以计算出物体的运动矢量了。

运动矢量计算出来后,将相同部分(也就是绿色部分)减去,就得到了补偿数据。我们最终只需要将补偿数据进行压缩保存,以后在解码时就可以恢复原图了。压缩补偿后的数据只需要记录很少的一点数据。

我们把运动矢量与补偿称为帧间压缩技术,它解决的是视频帧在时间上的数据冗余。除了帧间压缩,帧内也要进行数据压缩,帧内数据压缩解决的是空间上的数据冗余。下面我们就来介绍一下帧内压缩技术。

帧内预测

人眼对图象都有一个识别度,对低频的亮度很敏感,对高频的亮度不太敏感。所以基于一些研究,可以将一幅图像中人眼不敏感的数据去除掉。这样就提出了帧内预测技术。

一幅图像被划分好宏块后,对每个宏块可以进行 9 种模式的预测。找出与原图最接近的一种预测模式。

将原始图像与帧内预测后的图像相减得残差值。

再将我们之前得到的预测模式信息一起保存起来,这样我们就可以在解码时恢复原图了。

经过帧内与帧间的压缩后,虽然数据有大幅减少,但还有优化的空间。

对残差数据做 DCT 变换

可以将残差数据做整数离散余弦变换,去掉数据的相关性,进一步压缩数据。

离散余弦变换(DCT)的作用:DCT的主要目的是将图像从空间域(即像素表示)转换到频率域。在频率域中,图像的信息被表示为不同频率的余弦波的组合。高频成分通常对应于图像中的细节部分(如边缘),而低频成分则对应于图像中的平滑区域。

压缩与质量的权衡:在DCT和图像压缩中,总是存在着数据量(压缩率)和图像质量之间的权衡。压缩图像通常涉及保留更多的低频信息(因为它们对人眼更重要)并丢弃或减少高频信息。块的大小直接影响这种权衡:较小的块提供更好的图像质量但较低的压缩率,而较大的块则提供更高的压缩率但可能牺牲图像的细节质量。

变换系数的阈值处理:在DCT后,通常会对变换系数进行阈值处理,即保留最重要的系数(通常是最大的系数,代表了图像中最显著的频率成分),而将其它系数设置为零。这种方法在大块尺寸下特别有效,因为在这些情况下,仅需要少量的系数就能代表整个块的主要信息。

总结:离散余弦变换(DCT)在图像处理中的应用涉及块大小选择的重要考虑。不同的块大小会影响DCT在压缩效率和图像质量之间的平衡,小块更适合高频细节的保留,而大块更有利于高压缩率的实现,但可能牺牲一些细节质量。理解这些原理有助于在图像压缩和处理中做出更合适的技术选择。

熵编码(CABAC)

上面的帧内压缩是属于有损压缩技术,也就是说图像被压缩后,无法完全复原。而熵编码压缩是一种无损压缩,其实现原理是使用新的编码来表示输入的数据,给高频的词一个短码,给低频词一个长码,从而达到压缩的效果。常用的熵编码有游程编码,哈夫曼编码和 CAVLC 编码等。

在 H.264/MPEG-4 AVC 中使用的熵编码算法是 CABAC(ContextAdaptive Binary Arithmatic Coding)。CABAC 在不同的上下文环境中使用不同的概率模型来编码。其编码过程大致是这样:首先,将欲编码的符号用二进制 bit 表示;然后对于每个 bit,编码器选择一个合适的概率模型,并通过相邻元素的信息来优化这个概率模型;最后,使用算术编码压缩数据。
我们以 A-Z 作为例子,A属于高频数据,Z属于低频数据。

-------------已经到底啦!-------------