5.2.2 运动向量栈构建
作者:chai51
出处:https://www.cnblogs.com/chai51
版权:本文版权归作者和博客园共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
引言
运动向量栈(MV Stack)构建是帧间预测模式信息解码的关键步骤,它构建运动向量候选栈,用于预测当前块的运动向量。候选栈包含多个候选MV,解码器从中选择最佳候选作为预测MV。
源码说明: 本文档基于作者自己编写的AV1解码器Python实现,所有代码示例和实现细节均来自实际可运行的源码。源码仓库:GitHub - av1_learning
运动向量栈构建概述
位置: src/mode/find_mv_stack.py - find_mv_stack()
规范文档: 7.10.2 Find MV stack process
功能说明
构建运动向量候选栈,用于预测当前块的运动向量。候选栈包含多个候选MV,解码器从中选择最佳候选作为预测MV。
候选来源
- 空间邻居:上方、左侧、右上、左下等位置的MV
- 时间邻居:参考帧中对应位置的MV
- 全局MV:全局运动参数
- 零MV:零运动向量
构建流程概述
运动向量栈的构建过程可以简单理解为:从多个来源收集运动向量候选,然后整理成一个候选列表。
主要步骤:
-
查找空间邻居的运动向量
- 检查当前块的上方、左侧、右上角、左下角等相邻块
- 如果这些相邻块已经解码,就借用它们的运动向量作为候选
- 就像问邻居:"你们是怎么移动的?"
-
查找时间邻居的运动向量
- 检查参考帧中相同位置(或附近位置)的块
- 如果参考帧中对应位置有运动向量,也作为候选
- 就像看上一帧:"这个位置之前是怎么移动的?"
-
添加全局运动向量
- 如果整个画面有统一的运动(比如镜头平移),使用全局运动参数
- 就像说:"整个画面都在向右移动"
-
添加零运动向量
- 如果候选还不够,添加"不移动"(零向量)作为候选
- 就像说:"也许这个块根本没动"
实际代码流程:
def find_mv_stack(self, av1: AV1Decoder, isCompound: int):
"""查找MV栈过程 - 规范文档 7.10.2"""
frame_header = av1.frame_header
tile_group = av1.tile_group
MiSize = tile_group.MiSize
bw4 = Num_4x4_Blocks_Wide[MiSize]
bh4 = Num_4x4_Blocks_High[MiSize]
# 1. 初始化
tile_group.NumMvFound = 0
self._NewMvCount = 0
# 2. 设置全局运动向量
tile_group.GlobalMvs[0] = self._setup_global_mv_process(av1, 0)
if isCompound == 1:
tile_group.GlobalMvs[1] = self._setup_global_mv_process(av1, 1)
# 3. 扫描空间邻居(上方、左侧等)
self._scan_row_process(av1, -1, isCompound) # 扫描上方行
foundAboveMatch = self._FoundMatch
self._scan_col_process(av1, -1, isCompound) # 扫描左侧列
foundLeftMatch = self._FoundMatch
# 4. 扫描角落位置(如果块尺寸允许)
if max(bw4, bh4) <= 16:
self._scan_point_process(av1, -1, bw4, isCompound) # 扫描右上点
self._scan_point_process(av1, -1, -1, isCompound) # 扫描左下点
# 5. 扫描更远的位置
self._scan_row_process(av1, -3, isCompound) # 扫描上方行-3
self._scan_col_process(av1, -3, isCompound) # 扫描左侧列-3
if bh4 > 1:
self._scan_row_process(av1, -5, isCompound) # 扫描上方行-5
if bw4 > 1:
self._scan_col_process(av1, -5, isCompound) # 扫描左侧列-5
# 6. 时间邻居扫描(如果允许)
if frame_header.use_ref_frame_mvs == 1:
self._temporal_scan_process(av1, isCompound) # 扫描参考帧中的MV
# 7. 排序候选
numNearest = tile_group.NumMvFound
self._sorting_process(av1, 0, numNearest, isCompound)
self._sorting_process(av1, numNearest, tile_group.NumMvFound, isCompound)
# 8. 额外搜索(如果候选不够)
if tile_group.NumMvFound < 2:
# 添加全局MV
# 添加零MV(如果栈未满)
self._extra_search_process(av1, isCompound)
# 9. 上下文处理和边界裁剪
self._context_and_clamping_process(av1, isCompound, numNew)
return self._RefStackMv
代码说明:
_scan_row_process(av1, -1, isCompound): 扫描上方行的相邻块,-1表示上方1个位置_scan_col_process(av1, -1, isCompound): 扫描左侧列的相邻块,-1表示左侧1个位置_scan_point_process(av1, -1, bw4, isCompound): 扫描特定点的相邻块(如右上角、左下角)_temporal_scan_process(av1, isCompound): 扫描参考帧中对应位置的MV_sorting_process(): 对候选进行排序,按优先级排列_extra_search_process(): 如果候选不够,添加更多候选(如零向量、全局向量)_context_and_clamping_process(): 处理MV上下文信息并进行边界裁剪
运动向量栈构建流程图
MV栈构建主流程
详细技术文档参考
说明: 以下内容来自详细技术文档,包含完整的实现细节和代码说明。这些内容主要用于技术参考。
点击展开查看详细技术文档
空间邻居扫描流程
什么是空间邻居?
空间邻居就是当前块周围已经解码的相邻块。AV1会按照一定的顺序检查这些邻居块,借用它们的运动向量作为候选。
扫描流程图:
通俗解释:
- 先找最近的邻居:检查正上方和正左侧的块(最可能相似)
- 再找角落的邻居:如果块不太大,检查右上角和左下角
- 最后找更远的邻居:如果块比较大,检查更远的位置
- 按顺序收集:找到的运动向量按优先级加入候选栈
MV栈构建后处理流程
什么是后处理?
在收集完所有候选运动向量后,需要对它们进行整理、排序和优化,确保候选栈的质量。
后处理流程图:
通俗解释:
- 统计匹配情况:看看在上方和左侧找到了多少匹配的运动向量
- 提高优先级:如果找到了紧邻块的运动向量,给它们更高的优先级(更可能被选中)
- 查找参考帧:如果允许,检查上一帧相同位置的运动向量
- 排序候选:按照优先级对所有候选进行排序,最好的候选排在前面
- 补充候选:如果候选不够(少于2个),添加更多候选(如零向量)
- 边界检查:确保所有运动向量都是合法的,不会越界
总结
运动向量栈构建为后续的Y模式解码和运动向量分配提供候选MV。通过组合空间邻居、时间邻居、全局MV和零MV,构建一个包含多个候选的MV栈,解码器从中选择最佳候选。
参考资源:
- AV1规范文档
- 源码实现: GitHub - av1_learning
- MV栈构建:
src/mode/find_mv_stack.py
- MV栈构建:

浙公网安备 33010602011771号