video_codec_tutorial

第七章:熵编码 (Entropy Coding)

开篇段落

欢迎来到视频编码之旅的最后一站(在解码器端是第一站)——熵编码。在此前的章节中,我们已经通过预测、变换和量化三大核心工具,极大地压缩了原始视频数据中的各种冗余。然而,经过量化后的变换系数、运动矢量等语法元素,其统计特性仍然存在着巨大的压缩潜力。熵编码就是挖掘这种统计冗余的无损压缩技术,它负责将这些语法元素高效地打包成最终的比特流,是决定编码效率的关键一步。

在现代视频编码标准中,熵编码通常能够实现额外 20%-40% 的压缩增益。对于一个典型的 1080p 视频,如果没有熵编码,输出码率可能需要增加数倍才能保持相同的视觉质量。这使得熵编码成为整个编解码流程中回报最高的技术环节之一。

本章将深入探讨熵编码的理论基础,从信息论的经典原理出发,解析两种主流的编码方法——CAVLC 和 CABAC 的精妙设计,并展望 ANS、神经网络等更前沿的技术在此领域的创新与应用。

无损压缩的最后一步

在混合编码框架中,熵编码是唯一的无损压缩环节,也是编码器的”最后守门人”。它的核心任务是为编码器输出的各种语法元素(如量化后的变换系数、运动矢量差、预测模式、量化参数等)分配尽可能短的码字。其基本思想源于信息论的根本原理:出现概率越高的符号,分配越短的码字;出现概率越低的符号,分配越长的码字。这样,在总体上就能用最短的平均码长来表示整个符号序列。

一个理想的熵编码器能将符号序列压缩到其信息熵的理论极限。信息熵 $H(X)$ 定义为: \(H(X) = -\sum_{i=1}^{n} p(x_i) \log_2 p(x_i)\) 其中,$X$ 是一个随机变量,代表要编码的符号集合,$x_i$ 是集合中的一个具体符号,$p(x_i)$ 是该符号出现的概率。$H(X)$ 的单位是比特/符号,它表示了编码一个符号所需的理论最小平均比特数。

关键洞察:香农第一定理告诉我们,任何无损数据压缩的极限都由源的熵决定。这意味着即使是最先进的熵编码器,也无法突破这个理论极限,只能无限接近它。

在视频编码中,各种语法元素都展现出强烈的非均匀分布特性:

这些高度偏斜的概率分布为熵编码提供了巨大的压缩空间。一个精心设计的熵编码器能够将这些偏斜性转化为显著的比特节省。

实践要点:理解符号的统计特性是设计高效熵编码的前提。现代编码标准在设计熵编码方案时,都是基于大量视频序列的统计分析,针对不同内容类型(如自然场景、动画、屏幕内容等)优化其概率模型。

上下文自适应的变长编码 (CAVLC)

CAVLC (Context-Adaptive Variable-Length Coding) 是一种主要用于 H.264/AVC 标准中对量化变换系数进行编码的熵编码方法。它结合了变长编码 (VLC) 和上下文自适应的思想,相比于简单的 Huffman 编码,它能更好地适应局部数据的统计特性,从而获得更高的压缩效率。

CAVLC 的核心思想

CAVLC 的设计巧妙地利用了量化后 DCT 系数的几个典型统计特征,这些特征反映了自然图像的基本规律:

  1. 高频系数多为零:经过 DCT 和量化后,图像块的能量通常集中在左上角的低频区域,而右下角的高频系数大部分为零。这是因为自然图像往往具有相对平缓的变化,高频细节较少。统计显示,在典型的视频序列中,超过 60-80% 的高频 DCT 系数在量化后变为零。

    具体数据示例

    • 静止场景:90%+ 的高频系数为零
    • 中等运动场景:70%-80% 的高频系数为零
    • 快速运动/纹理丰富场景:50%-70% 的高频系数为零
  2. Zig-Zag 扫描的智慧:通过 Zig-Zag 扫描将二维系数矩阵转换为一维序列,可以使得连续的零值(Trailing Zeros)集中在一起,便于使用行程编码 (Run-Length Encoding)。

    4×4 块的 Zig-Zag 扫描路径

    DC  1   5   6
     2  4   7  12
     3  8  11  13
     9 10  14  15
    

    扫描顺序:0→1→2→3→4→5→6→7→8→9→10→11→12→13→14→15

    这种扫描方式的优势:

    • 低频系数(通常非零)在序列前端
    • 高频系数(多为零)聚集在序列末尾
    • 形成自然的”有效信息在前,冗余信息在后”的结构
  3. 非零系数的幅值分布:非零系数的幅值遵循拉普拉斯分布,小幅值(如 ±1, ±2)的出现概率远高于大幅值。在典型视频中,约 70-80% 的非零系数的绝对值小于等于 2。特别是幅值为 ±1 的系数(称为 TrailingOnes)在高频区域尤为常见。

    典型分布统计

    • 绝对值为 1:~45%-55% 的非零系数
    • 绝对值为 2:~20%-25% 的非零系数
    • 绝对值为 3-4:~10%-15% 的非零系数
    • 绝对值 ≥ 5:~5%-10% 的非零系数
  4. 局部相关性:相邻块的非零系数个数 (TotalCoeffs) 具有强相关性。如果当前块的左邻块和上邻块包含较多非零系数,那么当前块也倾向于有较多非零系数。这种相关性可以用来预测当前块的系数分布,从而选择更合适的编码表。

    相关性数据:相邻块的 TotalCoeffs 相关系数通常在 0.6-0.8 之间,这种强相关性是 CAVLC 上下文自适应的核心基础。

CAVLC 的编码流程

CAVLC 的编码过程可以分解为以下几个关键步骤,它并不直接对每个系数值进行编码,而是对一系列组合语法元素进行编码。让我们通过一个具体例子来深入理解这个过程:

完整编码示例: 假设我们有一个 4×4 系数块(经过 Zig-Zag 扫描后):

原始序列: [5, 0, 3, 1, -1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
位置编号: [0, 1, 2, 3,  4, 5, 6, 7, 8, 9,10,11,12,13,14,15]

步骤 1:编码非零系数数量 (TotalCoeffs) 和拖尾系数数量 (TrailingOnes)

示例分析

上下文选择机制

步骤 2:编码拖尾系数的符号 (Sign of TrailingOnes)

对于步骤 1 中确定的每个 TrailingOne,用 1 比特表示其符号:

编码顺序是从 Zig-Zag 扫描的最后一个 TrailingOne 开始,向前编码。

示例继续

步骤 3:编码剩余非零系数的幅值 (Levels)

对于除了 TrailingOnes 之外的非零系数,需要编码其完整的幅值(包括符号)。编码顺序是按 Zig-Zag 扫描的逆序进行。

自适应 VLC 表选择

步骤 4:编码总零个数 (TotalZeros)

TotalZeros 是指在最后一个非零系数之前所有零系数的总数。它等于:

TotalZeros = (最后一个非零系数的位置) - (TotalCoeffs - 1)

根据 TotalCoeffs 的值选择相应的 total_zeros VLC 表。当 TotalCoeffs = 16 时(块满),不需要编码 TotalZeros。

步骤 5:编码各个游程长度 (RunBefore)

从最后一个非零系数开始,往前为每个非零系数编码其前面连续零的个数。

游程分配过程

这种设计确保了所有的零都被准确分配,无需为最后一个非零系数编码 RunBefore。

Rule-of-thumb: CAVLC 的设计哲学是一种“化整为零”的策略。它不直接编码一个复杂的系数序列,而是将其拆解为多个更易于用统计模型描述的语法元素(如 TotalCoeffs, TrailingOnes, RunBefore 等),然后为每个元素设计上下文自适应的编码表,从而在计算复杂度和压缩效率之间取得了很好的平衡。

CAVLC 的优缺点

由于这些缺点,CAVLC 在 H.264/AVC 的更高级别 (Main Profile 及以上) 中被 CABAC 所取代,并且在 HEVC 和 VVC 等后续标准中已不再使用。

上下文自适应的二进制算术编码 (CABAC)

CABAC (Context-Adaptive Binary Arithmetic Coding) 是目前主流视频编码标准(如 H.264/AVC, HEVC, VVC)中使用的高效熵编码方案。它通过将算术编码与上下文模型相结合,提供了比 CAVLC 高出 5-15% 的编码效率。

CABAC 的核心思想

CABAC 的强大之处在于其将编码过程分为三个独立的阶段:

  1. 二值化 (Binarization):将非二进制的语法元素(如运动矢量差、变换系数)转换为二进制序列(称为 “bin”)。
  2. 上下文建模 (Context Modeling):为每个 bin 选择一个概率模型。这个模型基于已经编码过的相关语法元素(即“上下文”)来估计当前 bin 是 0 还是 1 的概率。
  3. 二进制算术编码 (Binary Arithmetic Coding):根据选定的概率模型,对每个 bin 进行编码。

这种分离使得 CABAC 能够极其灵活和精确地对数据进行建模和压缩。

CABAC 的工作流程

1. 二值化 (Binarization)

二值化的目标是将一个语法元素值转换为一串二进制码(bin 序列)。不同的语法元素采用不同的二值化方案,每种方案都针对特定的概率分布优化:

主要二值化方案:

复合二值化方案: CABAC 中很多语法元素使用组合方案,如 截断一元码 + k阶指数哥伦布码 (TU+EGk)

实际示例:运动矢量差 (MVD) 的二值化:

二值化方案的选择原理: 不同的二值化方案适用于不同的概率分布模式:

二值化的设计目标是使高概率的值对应较短的 bin 序列,这样就能为后续的概率建模提供良好的基础。

2. 上下文建模 (Context Modeling)

这是 CABAC 的精髓所在。对于二值化后的序列中的每一个 bin,CABAC 都会选择一个上下文模型来预测其概率。上下文选择基于已编码的相关语法元素信息,体现了强大的自适应能力。

上下文模型的结构: 每个上下文模型是一个有限状态机,包含:

概率量化的精妙设计: CABAC 使用 64 个量化级别来表示概率,这种设计平衡了以下需求:

每个概率级别对应的 LPS 概率大致遵循几何级数递减:

p_LPS[0] ≈ 0.5,  p_LPS[1] ≈ 0.46, ..., p_LPS[63] ≈ 0.0156

上下文选择策略:

  1. 变换系数 (Transform Coefficients)
    • significant_coeff_flag:标识系数是否非零
      • 上下文基于:扫描位置、频率分量类型(亮度/色度)、相邻非零系数数量
    • last_significant_coeff_flag:标识是否为最后一个非零系数
      • 上下文基于:扫描位置、块大小
    • coeff_abs_level_greater1_flag/greater2_flag:系数幅值大于 1/2 的标识
      • 上下文基于:已编码的 greater1 标识数量
  2. 运动信息 (Motion Information)
    • mvd_greater0_flag:运动矢量差是否非零
      • 上下文基于:分量类型(水平/垂直)
    • ref_idx:参考帧索引
      • 上下文基于:相邻块的参考帧使用情况
  3. 预测模式 (Prediction Modes)
    • skip_flag:跳过模式标识
      • 上下文基于:左邻和上邻块的跳过模式状态
    • intra_pred_mode:帧内预测模式
      • 上下文基于:相邻块的预测方向

概率更新机制:

if (binVal == valMPS) {
    // 编码了 MPS,增强 MPS 概率
    if (pStateIdx < 62) pStateIdx++;
} else {
    // 编码了 LPS,处理概率逆转
    if (pStateIdx == 0) valMPS = 1 - valMPS;
    pStateIdx = transIdxLPS[pStateIdx];
}

这种更新确保了模型能够快速适应局部统计特性的变化。当 LPS 连续出现时,模型会逐渐将其转变为新的 MPS,实现概率估计的自适应性。

上下文初始化: 不同片类型(I/P/B)对应不同的初始化参数:

3. 二进制算术编码 (Binary Arithmetic Coding)

算术编码是一种能以接近信息熵理论极限进行压缩的编码技术。CABAC 使用的是专门优化的二进制算术编码引擎,采用整数运算避免浮点计算的精度问题。

编码引擎的核心状态:

编码过程:

  1. 区间划分
    qCodIRangeIdx = (codIRange >> 6) & 3    // 量化区间大小
    codIRangeLPS = rangeTabLPS[pStateIdx][qCodIRangeIdx]
    codIRangeMPS = codIRange - codIRangeLPS
    
  2. 符号编码
    if (binVal == valMPS) {
        // 编码 MPS
        codIRange = codIRangeMPS
    } else {
        // 编码 LPS
        codILow += codIRangeMPS
        codIRange = codIRangeLPS
    }
    
  3. 重归一化 (Renormalization): 当 codIRange < 256 时触发,确保区间大小维持在合理范围:
    while (codIRange < 256) {
        if (codILow < 256) {
            putBit(0)
            flushBitsOutstanding(0)
        } else if (codILow >= 512) {
            putBit(1)
            flushBitsOutstanding(1)
            codILow -= 512
        } else {
            bitsOutstanding++
            codILow -= 256
        }
        codIRange <<= 1
        codILow <<= 1
    }
    

旁路模式 (Bypass Mode): 对于概率接近 0.5 的 bin(如符号位、EG 后缀),CABAC 使用简化的旁路编码:

算术编码的关键优势:

解码过程: 解码器维护相同的状态变量,通过比较当前码值与区间边界来恢复符号:

if (codIValue >= codIRangeMPS) {
    binVal = !valMPS    // 解码出 LPS
    codIValue -= codIRangeMPS
    codIRange = codIRangeLPS
} else {
    binVal = valMPS     // 解码出 MPS
    codIRange = codIRangeMPS
}

Rule-of-thumb: CABAC 的高效率来自于“专业化分工”。二值化将问题简化为“是/否”的判断;上下文建模利用局部相关性为这个判断提供最精准的“猜测”;算术编码则根据这个“猜测”的置信度,用最高效的方式记录下最终的“答案”。

CABAC vs. CAVLC

| 特性 | CAVLC | CABAC | | :— | :— | :— | | 核心技术 | 上下文自适应变长编码 | 上下文自适应二进制算术编码 | | 压缩效率 | 较高 | 非常高 (比 CAVLC 高 5-15%) | | 概率模型 | 静态VLC表,上下文选择表 | 动态更新的概率模型 | | 输出 | 整数个比特 | 小数个比特(理论上) | | 计算复杂度| 较低 | 较高(需要乘法运算) | | 适用性 | 主要用于 H.264 的系数编码 | 适用于所有语法元素 |

高级话题:非对称数系 (Asymmetric Numeral Systems, ANS)

ANS 是一种现代熵编码方法,由 Jarosław (Jarek) Duda 在 2009 年提出。它在性能上达到了与算术编码相近的压缩率,但计算速度要快得多,因为它完全避免了乘法运算。这使得 ANS 成为许多现代压缩算法(如 Facebook 的 Zstandard, Apple 的 LZFSE)的核心。

ANS 的核心思想

ANS 的工作方式与算术编码有很大不同。算术编码通过不断缩小一个区间来表示信息,而 ANS 则将整个信息序列映射到一个巨大的整数上。它的状态(state)就是一个整数 x,编码一个符号 s 就是将当前状态 x 更新为一个新的状态 x',这个过程可以表示为一个函数 x' = C(x, s)。解码则是通过逆函数 (x, s) = D(x') 来恢复出原始状态和符号。

ANS 有两种主流的变体:

  1. rANS (range ANS):将编码区间映射到整数上,非常适合在现有硬件上高效实现。
  2. tANS (tabled ANS):将整个编码逻辑预计算到一个表中,从而实现极快的编码和解码速度,但需要额外的内存来存储状态转换表。

Rule-of-thumb: 如果说算术编码像是在一个区间 [0, 1) 上进行“小数运算”,那么 ANS 就像是在一个巨大的整数环上进行“整数运算”。通过巧妙的数字游戏,ANS 在不使用乘法的情况下实现了与算术编码同等级别的压缩率,是速度与效率完美结合的典范。

在视频编码领域,虽然主流标准尚未采纳 ANS,但它作为下一代熵编码技术的有力竞争者,在许多研究和特定应用中展示了巨大的潜力。

AI 算法改进:基于神经网络的概率模型预测

传统熵编码(如 CABAC)的上下文模型是人工设计和高度优化的,但它终究是基于有限的局部信息进行预测。随着深度学习的发展,研究人员开始探索使用神经网络来替代或增强 CABAC 中的上下文模型。

核心思路

其核心思路是利用神经网络强大的非线性建模能力,从更广泛的上下文信息中学习出更精确的符号概率分布。一个典型的神经熵编码模型会:

  1. 收集上下文:将当前待编码符号周围已经编码和重建的像素值、或其他相关语法元素作为神经网络的输入。
  2. 概率预测:通过一个(通常是轻量级的)神经网络(如 CNN 或 MLP),输出当前符号(或 bin)的概率分布。
  3. 算术编码:将这个由神经网络预测出的概率送入传统的算术编码器中,对符号进行编码。

这种方法在理论上可以捕捉到更复杂、更长距离的依赖关系,从而生成比传统 CABAC 更精确的概率模型,带来更高的压缩率。

挑战与展望

尽管前景广阔,但将神经网络集成到熵编码中仍面临巨大挑战:

目前,这类技术主要出现在一些端到端的神经视频编码研究中,或者作为对现有编码器特定模块的增强。随着硬件计算能力的提升和网络模型优化技术的发展,基于 AI 的熵编码有望在未来的视频编码标准中扮演更重要的角色。

历史事件/人物:Claude Shannon 与信息论

谈及熵编码,就不能不提及其理论基石的奠定者——克劳德·香农 (Claude Shannon)。在 1948 年发表的划时代论文《通信的数学理论》(A Mathematical Theory of Communication) 中,香农系统地创立了信息论,为现代数字通信和数据压缩领域铺平了道路。

香农在论文中首次提出了“比特”(bit) 作为信息的度量单位,并定义了“信息熵”的概念,从数学上精确地描述了信息的不确定性。他证明了任何无损压缩算法的压缩率都不可能低于信源的信息熵,为所有熵编码技术(从 Huffman 编码到算术编码)设定了理论上的黄金标准。

香农的工作不仅是一个理论突破,更是一种思想上的革命。他将信息从其具体的物理载体(如声音、图像)中抽象出来,作为一种可以被量化、分析和处理的数学实体。没有香农和他的信息论,我们今天所熟知的数字世界,包括高效的视频编解码,都将无从谈起。

当代事件/人物:Google 的 rANS 和 Facebook 的 Zstandard

进入 21 世纪,熵编码领域最重要的突破之一就是上文提到的 ANS。而将这一理论成功转化为工业级应用并大力推广的,当属 Google 和 Facebook 两大科技巨头。

这些当代事件表明,即使在信息论诞生 70 多年后的今天,熵编码这一古老而基础的领域依然充满了创新与活力。

本章小结

本章深入探讨了熵编码,这是视频压缩流程中至关重要的无损压缩环节。

常见陷阱与错误 (Gotchas)

  1. 混淆熵编码与有损压缩:一个常见的误解是认为熵编码本身会造成信息损失。必须明确,熵编码是 完全无损 的。视频压缩中的所有信息损失都发生在 量化 阶段。熵编码只是将量化后的数据更紧凑地打包而已。
  2. 认为码率控制由熵编码完成:虽然熵编码的输出是最终的比特流,但它本身并不“决定”最终的码率。码率是由 量化参数 (QP)率失真优化 (RDO) 框架下决定的。熵编码只是忠实地执行压缩任务,码率高低主要取决于输入给它的数据量和数据的统计特性。
  3. 忽略上下文的重要性:在分析 CABAC 的性能时,很容易只关注算术编码本身。但实际上,其高效性能的“秘密武器”是其高度优化的 上下文模型。没有精确的概率预测,再强大的算术编码器也无能为力。
  4. 低估实现复杂度:虽然 CABAC 的原理听起来很直接,但在实际标准中的实现极其复杂。上下文模型的选择、概率状态机的更新、算术编码器的状态管理以及与解码器的严格同步,每一个环节都充满了细节和挑战。
  5. 认为 ANS 总是优于算术编码:虽然 ANS 在速度上通常有优势,但在压缩率上,一个高度优化的算术编码器(如 AV1 中使用的)和 ANS 可以做到非常接近。在特定场景下,算术编码由于其更长的发展历史和更成熟的优化,可能仍然是最佳选择。选择哪种技术总是在压缩率、速度、内存占用和实现复杂度之间进行权衡。