3.3 块划分(Block Partitioning)

作者:chai51
出处:https://www.cnblogs.com/chai51
版权:本文版权归作者和博客园共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任

引言

块划分(Block Partitioning)是AV1解码的核心技术之一,它通过递归地将Superblock划分为更小的块,使得编码器可以为不同区域选择最优的预测模式和编码参数。块划分直接影响解码效率和图像质量,是AV1实现高效压缩的关键。

源码说明: 本文档基于作者自己编写的AV1解码器Python实现,所有代码示例和实现细节均来自实际可运行的源码。源码仓库:GitHub - av1_learning


块划分概述

基本概念

块划分是AV1将图像划分为不同尺寸块的过程:

  1. 递归划分:从Superblock(64x64或128x128)开始,递归划分为更小的块
  2. 最小块尺寸:最小块为4x4像素(1个MI单位)
  3. 自适应选择:根据图像内容选择最优的划分方式
  4. 独立解码:每个划分后的块可以独立解码

划分模式

AV1支持10种划分模式:

划分模式 说明 子块数量 适用块尺寸
PARTITION_NONE 不划分 1 所有尺寸
PARTITION_HORZ 水平划分 2 ≥8x8
PARTITION_VERT 垂直划分 2 ≥8x8
PARTITION_SPLIT 四等分 4 ≥8x8
PARTITION_HORZ_A 水平划分A 3 ≥16x16
PARTITION_HORZ_B 水平划分B 3 ≥16x16
PARTITION_VERT_A 垂直划分A 3 ≥16x16
PARTITION_VERT_B 垂直划分B 3 ≥16x16
PARTITION_HORZ_4 水平四等分 4 ≥32x32
PARTITION_VERT_4 垂直四等分 4 ≥32x32

划分模式示意图

PARTITION_NONE:        PARTITION_HORZ:      PARTITION_VERT:
┌─────────┐           ┌───────┐            ┌───┬───┐
│         │           │  上   │            │   │   │
│         │           ├───────┤            │   │   │
│         │           │  下   │            │   │   │
└─────────┘           └───────┘            └───┴───┘

PARTITION_SPLIT:      PARTITION_HORZ_A:   PARTITION_HORZ_B:
┌─────┬─────┐         ┌───┬───┐           ┌───────┐
│ 左上│右上 │         │上左│上右│          │  上   │
├─────┼─────┤         ├───────┤           ├───┬───┤
│ 左下│右下 │         │  下   │           │下左│下右│
└─────┴─────┘         └───────┘           └───┴───┘

PARTITION_VERT_A:     PARTITION_VERT_B:   PARTITION_HORZ_4:
┌───┬───────┐         ┌───────┬───┐      ┌───────┐
│左 │  右   │         │  左   │右 │      │   1   │
│上 │       │         │       │上 │      ├───────┤
├───┤       │         ├───────┤   │      │   2   │
│左 │       │         │  左   │   │      ├───────┤
│下 │       │         │       │下 │      │   3   │
└───┴───────┘         └───────┴───┘      ├───────┤
                                        │   4   │
                                        └───────┘

块划分解码流程

位置: src/tile/tile_group.py - __decode_partition()

规范文档: 5.11.4 Decode partition syntax

主要步骤

块划分解码是一个递归过程,主要步骤包括:

  1. 边界检查:检查块是否超出图像边界
  2. 可用性检查:检查上方和左侧块是否可用
  3. 尺寸计算:计算块尺寸和子块尺寸
  4. 边界条件判断:判断是否有足够的行和列进行划分
  5. 划分模式解码:根据边界条件解码划分模式
  6. 递归解码:根据划分模式递归解码子块

核心代码实现

def __decode_partition(self, av1: AV1Decoder, r: int, c: int, bSize: SUB_SIZE):
    """
    解码划分
    规范文档 5.11.4 Decode partition syntax
    """
    frame_header = av1.frame_header
    tile_group = self.tile_group
    MiRows = frame_header.MiRows
    MiCols = frame_header.MiCols

    # 1. 边界检查
    if r >= MiRows or c >= MiCols:
        return 0

    # 2. 可用性检查
    tile_group.AvailU = is_inside(av1, r - 1, c)
    tile_group.AvailL = is_inside(av1, r, c - 1)
    
    # 3. 尺寸计算
    num4x4 = Num_4x4_Blocks_Wide[bSize]
    halfBlock4x4 = num4x4 >> 1
    quarterBlock4x4 = halfBlock4x4 >> 1
    hasRows = (r + halfBlock4x4) < MiRows
    hasCols = (c + halfBlock4x4) < MiCols

    # 4. 划分模式解码
    if bSize < SUB_SIZE.BLOCK_8X8:
        tile_group.partition = PARTITION.PARTITION_NONE
    elif hasRows and hasCols:
        tile_group.partition = read_S(av1, "partition", r=r, c=c, bSize=bSize)
    elif hasCols:
        split_or_horz = read_S(av1, "split_or_horz", r=r, c=c, bSize=bSize)
        tile_group.partition = PARTITION.PARTITION_SPLIT if split_or_horz else PARTITION.PARTITION_HORZ
    elif hasRows:
        split_or_vert = read_S(av1, "split_or_vert", r=r, c=c, bSize=bSize)
        tile_group.partition = PARTITION.PARTITION_SPLIT if split_or_vert else PARTITION.PARTITION_VERT
    else:
        tile_group.partition = PARTITION.PARTITION_SPLIT

    # 5. 计算子块尺寸
    subSize: SUB_SIZE = Partition_Subsize[tile_group.partition][bSize]
    splitSize = Partition_Subsize[PARTITION.PARTITION_SPLIT][bSize]

    # 6. 根据划分模式递归解码
    # ... 见下文详细说明

块划分解码流程图

graph TD A[开始块划分解码<br/>__decode_partition] --> B{边界检查<br/>r >= MiRows or c >= MiCols?} B -->|是| C[返回] B -->|否| D[检查上方和左侧块可用性<br/>AvailU AvailL] D --> E[计算块尺寸<br/>num4x4 halfBlock4x4 quarterBlock4x4] E --> F[检查边界条件<br/>hasRows hasCols] F --> G{块尺寸检查} G -->|bSize < 8x8| H[PARTITION_NONE] G -->|hasRows and hasCols| I[解码完整划分模式<br/>read_S partition] G -->|only hasCols| J[解码split_or_horz] G -->|only hasRows| K[解码split_or_vert] G -->|else| L[PARTITION_SPLIT] J --> M{split_or_horz?} M -->|是| L M -->|否| N[PARTITION_HORZ] K --> O{split_or_vert?} O -->|是| L O -->|否| P[PARTITION_VERT] H --> Q[计算子块尺寸<br/>subSize] I --> Q N --> Q P --> Q L --> Q Q --> R{划分模式} R -->|PARTITION_NONE| S[解码单个块<br/>__decode_block] R -->|PARTITION_SPLIT| T[递归解码4个子块<br/>__decode_partition x4] R -->|PARTITION_HORZ| U[解码上下2个子块<br/>__decode_block x2] R -->|PARTITION_VERT| V[解码左右2个子块<br/>__decode_block x2] R -->|其他模式| W[解码对应子块] S --> X[块划分解码完成] T --> X U --> X V --> X W --> X style A fill:#e1f5ff style G fill:#fff9c4 style R fill:#fff9c4 style X fill:#c8e6c9

划分模式详解

1. PARTITION_NONE(不划分)

最简单的划分模式,块不进行划分,直接解码。

if tile_group.partition == PARTITION.PARTITION_NONE:
    self.__decode_block(av1, r, c, subSize)

适用条件

  • 块尺寸小于8x8时强制使用
  • 其他尺寸时可选

2. PARTITION_HORZ(水平划分)

将块水平划分为上下两个子块。

elif tile_group.partition == PARTITION.PARTITION_HORZ:
    self.__decode_block(av1, r, c, subSize)  # 上子块
    if hasRows:
        self.__decode_block(av1, r + halfBlock4x4, c, subSize)  # 下子块

子块尺寸:每个子块为原块高度的一半

3. PARTITION_VERT(垂直划分)

将块垂直划分为左右两个子块。

elif tile_group.partition == PARTITION.PARTITION_VERT:
    self.__decode_block(av1, r, c, subSize)  # 左子块
    if hasCols:
        self.__decode_block(av1, r, c + halfBlock4x4, subSize)  # 右子块

子块尺寸:每个子块为原块宽度的一半

4. PARTITION_SPLIT(四等分)

将块划分为4个相等的子块,这是递归划分的核心。

elif tile_group.partition == PARTITION.PARTITION_SPLIT:
    self.__decode_partition(av1, r, c, subSize)  # 左上
    self.__decode_partition(av1, r, c + halfBlock4x4, subSize)  # 右上
    self.__decode_partition(av1, r + halfBlock4x4, c, subSize)  # 左下
    self.__decode_partition(av1, r + halfBlock4x4, c + halfBlock4x4, subSize)  # 右下

特点

  • 递归调用__decode_partition,可以继续划分
  • 每个子块为原块尺寸的一半

5. PARTITION_HORZ_A(水平划分A)

将块划分为3个子块:上左、上右、下。

elif tile_group.partition == PARTITION.PARTITION_HORZ_A:
    self.__decode_block(av1, r, c, splitSize)  # 上左
    self.__decode_block(av1, r, c + halfBlock4x4, splitSize)  # 上右
    self.__decode_block(av1, r + halfBlock4x4, c, subSize)  # 下

子块布局

  • 上部分:左右两个splitSize块
  • 下部分:一个subSize块

6. PARTITION_HORZ_B(水平划分B)

将块划分为3个子块:上、下左、下右。

elif tile_group.partition == PARTITION.PARTITION_HORZ_B:
    self.__decode_block(av1, r, c, subSize)  # 上
    self.__decode_block(av1, r + halfBlock4x4, c, splitSize)  # 下左
    self.__decode_block(av1, r + halfBlock4x4, c + halfBlock4x4, splitSize)  # 下右

子块布局

  • 上部分:一个subSize块
  • 下部分:左右两个splitSize块

7. PARTITION_VERT_A(垂直划分A)

将块划分为3个子块:左上、左下、右。

elif tile_group.partition == PARTITION.PARTITION_VERT_A:
    self.__decode_block(av1, r, c, splitSize)  # 左上
    self.__decode_block(av1, r + halfBlock4x4, c, splitSize)  # 左下
    self.__decode_block(av1, r, c + halfBlock4x4, subSize)  # 右

子块布局

  • 左部分:上下两个splitSize块
  • 右部分:一个subSize块

8. PARTITION_VERT_B(垂直划分B)

将块划分为3个子块:左、右上、右下。

elif tile_group.partition == PARTITION.PARTITION_VERT_B:
    self.__decode_block(av1, r, c, subSize)  # 左
    self.__decode_block(av1, r, c + halfBlock4x4, splitSize)  # 右上
    self.__decode_block(av1, r + halfBlock4x4, c + halfBlock4x4, splitSize)  # 右下

子块布局

  • 左部分:一个subSize块
  • 右部分:上下两个splitSize块

9. PARTITION_HORZ_4(水平四等分)

将块水平划分为4个相等的子块。

elif tile_group.partition == PARTITION.PARTITION_HORZ_4:
    self.__decode_block(av1, r + quarterBlock4x4 * 0, c, subSize)  # 第1块
    self.__decode_block(av1, r + quarterBlock4x4 * 1, c, subSize)  # 第2块
    self.__decode_block(av1, r + quarterBlock4x4 * 2, c, subSize)  # 第3块
    if r + quarterBlock4x4 * 3 < MiRows:
        self.__decode_block(av1, r + quarterBlock4x4 * 3, c, subSize)  # 第4块

适用条件:块高度≥32x32

10. PARTITION_VERT_4(垂直四等分)

将块垂直划分为4个相等的子块。

elif tile_group.partition == PARTITION.PARTITION_VERT_4:
    self.__decode_block(av1, r, c + quarterBlock4x4 * 0, subSize)  # 第1块
    self.__decode_block(av1, r, c + quarterBlock4x4 * 1, subSize)  # 第2块
    self.__decode_block(av1, r, c + quarterBlock4x4 * 2, subSize)  # 第3块
    if c + quarterBlock4x4 * 3 < MiCols:
        self.__decode_block(av1, r, c + quarterBlock4x4 * 3, subSize)  # 第4块

适用条件:块宽度≥32x32


边界条件处理

边界检查

在解码划分模式之前,需要检查块是否超出图像边界:

if r >= MiRows or c >= MiCols:
    return 0

可用性检查

检查上方和左侧块是否可用,用于上下文选择:

tile_group.AvailU = is_inside(av1, r - 1, c)  # 上方块是否在图像内
tile_group.AvailL = is_inside(av1, r, c - 1)  # 左侧块是否在图像内

边界条件判断

根据是否有足够的行和列,选择不同的解码方式:

hasRows = (r + halfBlock4x4) < MiRows  # 是否有足够的行进行划分
hasCols = (c + halfBlock4x4) < MiCols  # 是否有足够的列进行划分

if bSize < SUB_SIZE.BLOCK_8X8:
    # 小于8x8,强制不划分
    tile_group.partition = PARTITION.PARTITION_NONE
elif hasRows and hasCols:
    # 有足够的行和列,可以解码完整划分模式
    tile_group.partition = read_S(av1, "partition", r=r, c=c, bSize=bSize)
elif hasCols:
    # 只有列,只能水平划分或四等分
    split_or_horz = read_S(av1, "split_or_horz", r=r, c=c, bSize=bSize)
    tile_group.partition = PARTITION.PARTITION_SPLIT if split_or_horz else PARTITION.PARTITION_HORZ
elif hasRows:
    # 只有行,只能垂直划分或四等分
    split_or_vert = read_S(av1, "split_or_vert", r=r, c=c, bSize=bSize)
    tile_group.partition = PARTITION.PARTITION_SPLIT if split_or_vert else PARTITION.PARTITION_VERT
else:
    # 没有足够的行和列,只能四等分
    tile_group.partition = PARTITION.PARTITION_SPLIT

递归划分过程

块划分是递归的,从Superblock开始,可以递归划分为更小的块:

Superblock (128x128 或 64x64)
  └─> 根据划分模式划分
      ├─> PARTITION_SPLIT: 递归划分为4个子块
      │   ├─> 子块1 ──> 继续划分或解码
      │   ├─> 子块2 ──> 继续划分或解码
      │   ├─> 子块3 ──> 继续划分或解码
      │   └─> 子块4 ──> 继续划分或解码
      ├─> PARTITION_HORZ/VERT: 划分为2个子块
      │   ├─> 子块1 ──> 继续划分或解码
      │   └─> 子块2 ──> 继续划分或解码
      └─> PARTITION_NONE: 不划分,直接解码

递归终止条件

递归划分在以下情况终止:

  1. 块尺寸小于8x8:强制使用PARTITION_NONE
  2. 达到最小块尺寸:4x4块(1个MI单位)
  3. 选择PARTITION_NONE:编码器选择不继续划分

上下文选择

划分模式的解码使用上下文自适应熵解码,上下文根据以下因素选择:

  1. 块尺寸bsl = Mi_Width_Log2[bSize]
  2. 上方块可用性AvailU
  3. 左侧块可用性AvailL
  4. 上方块尺寸Mi_Width_Log2[tile_group.MiSizes[r - 1][c]]
  5. 左侧块尺寸Mi_Height_Log2[tile_group.MiSizes[r][c - 1]]

位置: src/entropy/cdf_selection.py - partition()

def partition(av1: AV1Decoder, r: int, c: int, bSize: SUB_SIZE) -> List[int]:
    """
    partition CDF选择
    规范文档 8.3.2: 根据bsl选择TilePartitionW*Cdf[ctx]
    """
    tile_group = av1.tile_group
    decoder = av1.decoder
    AvailU = tile_group.AvailU
    AvailL = tile_group.AvailL

    bsl = Mi_Width_Log2[bSize]
    above = AvailU and Mi_Width_Log2[tile_group.MiSizes[r - 1][c]] < bsl
    left = AvailL and Mi_Height_Log2[tile_group.MiSizes[r][c - 1]] < bsl
    ctx = left * 2 + above

    if bsl == 1:
        return decoder.tile_cdfs['TilePartitionW8Cdf'][ctx]
    elif bsl == 2:
        return decoder.tile_cdfs['TilePartitionW16Cdf'][ctx]
    elif bsl == 3:
        return decoder.tile_cdfs['TilePartitionW32Cdf'][ctx]
    elif bsl == 4:
        return decoder.tile_cdfs['TilePartitionW64Cdf'][ctx]
    else:
        return decoder.tile_cdfs['TilePartitionW128Cdf'][ctx]

关键数据结构

划分模式枚举

class PARTITION(IntEnum):
    PARTITION_NONE = 0
    PARTITION_HORZ = 1
    PARTITION_VERT = 2
    PARTITION_SPLIT = 3
    PARTITION_HORZ_A = 4
    PARTITION_HORZ_B = 5
    PARTITION_VERT_A = 6
    PARTITION_VERT_B = 7
    PARTITION_HORZ_4 = 8
    PARTITION_VERT_4 = 9

子块尺寸查找表

Partition_Subsize[partition][bSize]:根据划分模式和当前块尺寸,查找子块尺寸。


与解码流程的集成

块划分是Tile解码的核心步骤:

Tile解码
  → Superblock解码
    → 块划分解码(__decode_partition)
      → 递归划分或解码块
        → 模式信息解码
        → 预测解码
        → 残差解码
        → 重建块

块划分决定了后续解码的块结构,影响:

  • 模式信息解码的粒度
  • 预测块的大小
  • 残差解码的块结构
  • 重建块的顺序

总结

块划分是AV1解码的关键技术,主要特点包括:

  1. 递归划分:从Superblock开始,递归划分为更小的块
  2. 10种划分模式:支持多种划分方式,适应不同图像特征
  3. 边界处理:完善的边界条件检查和处理
  4. 上下文自适应:使用上下文自适应熵解码提高效率
  5. 灵活性:可以根据图像内容选择最优划分方式

块划分直接影响解码效率和图像质量,是AV1实现高效压缩的重要技术。


参考资源:

  • AV1规范文档
  • 源码实现: GitHub - av1_learning
    • 块划分: src/tile/tile_group.py - __decode_partition()
    • 上下文选择: src/entropy/cdf_selection.py - partition()
    • 常量定义: src/constants.py - PARTITION, Partition_Subsize

上一篇: Superblock解码
下一篇: 模式信息解码

posted @ 2026-01-24 09:37  chai51  阅读(3)  评论(0)    收藏  举报