3.3 块划分(Block Partitioning)
作者:chai51
出处:https://www.cnblogs.com/chai51
版权:本文版权归作者和博客园共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
引言
块划分(Block Partitioning)是AV1解码的核心技术之一,它通过递归地将Superblock划分为更小的块,使得编码器可以为不同区域选择最优的预测模式和编码参数。块划分直接影响解码效率和图像质量,是AV1实现高效压缩的关键。
源码说明: 本文档基于作者自己编写的AV1解码器Python实现,所有代码示例和实现细节均来自实际可运行的源码。源码仓库:GitHub - av1_learning
块划分概述
基本概念
块划分是AV1将图像划分为不同尺寸块的过程:
- 递归划分:从Superblock(64x64或128x128)开始,递归划分为更小的块
- 最小块尺寸:最小块为4x4像素(1个MI单位)
- 自适应选择:根据图像内容选择最优的划分方式
- 独立解码:每个划分后的块可以独立解码
划分模式
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
主要步骤
块划分解码是一个递归过程,主要步骤包括:
- 边界检查:检查块是否超出图像边界
- 可用性检查:检查上方和左侧块是否可用
- 尺寸计算:计算块尺寸和子块尺寸
- 边界条件判断:判断是否有足够的行和列进行划分
- 划分模式解码:根据边界条件解码划分模式
- 递归解码:根据划分模式递归解码子块
核心代码实现
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. 根据划分模式递归解码
# ... 见下文详细说明
块划分解码流程图
划分模式详解
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: 不划分,直接解码
递归终止条件
递归划分在以下情况终止:
- 块尺寸小于8x8:强制使用
PARTITION_NONE - 达到最小块尺寸:4x4块(1个MI单位)
- 选择PARTITION_NONE:编码器选择不继续划分
上下文选择
划分模式的解码使用上下文自适应熵解码,上下文根据以下因素选择:
- 块尺寸:
bsl = Mi_Width_Log2[bSize] - 上方块可用性:
AvailU - 左侧块可用性:
AvailL - 上方块尺寸:
Mi_Width_Log2[tile_group.MiSizes[r - 1][c]] - 左侧块尺寸:
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解码的关键技术,主要特点包括:
- 递归划分:从Superblock开始,递归划分为更小的块
- 10种划分模式:支持多种划分方式,适应不同图像特征
- 边界处理:完善的边界条件检查和处理
- 上下文自适应:使用上下文自适应熵解码提高效率
- 灵活性:可以根据图像内容选择最优划分方式
块划分直接影响解码效率和图像质量,是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解码
下一篇: 模式信息解码

浙公网安备 33010602011771号