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。

候选来源

  1. 空间邻居:上方、左侧、右上、左下等位置的MV
  2. 时间邻居:参考帧中对应位置的MV
  3. 全局MV:全局运动参数
  4. 零MV:零运动向量

构建流程概述

运动向量栈的构建过程可以简单理解为:从多个来源收集运动向量候选,然后整理成一个候选列表

主要步骤

  1. 查找空间邻居的运动向量

    • 检查当前块的上方、左侧、右上角、左下角等相邻块
    • 如果这些相邻块已经解码,就借用它们的运动向量作为候选
    • 就像问邻居:"你们是怎么移动的?"
  2. 查找时间邻居的运动向量

    • 检查参考帧中相同位置(或附近位置)的块
    • 如果参考帧中对应位置有运动向量,也作为候选
    • 就像看上一帧:"这个位置之前是怎么移动的?"
  3. 添加全局运动向量

    • 如果整个画面有统一的运动(比如镜头平移),使用全局运动参数
    • 就像说:"整个画面都在向右移动"
  4. 添加零运动向量

    • 如果候选还不够,添加"不移动"(零向量)作为候选
    • 就像说:"也许这个块根本没动"

实际代码流程

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栈构建主流程

graph TD A[MV栈构建开始<br/>find_mv_stack] --> B[初始化<br/>NumMvFound = 0<br/>NewMvCount = 0] B --> C[设置全局MV<br/>GlobalMvs 0] C --> D{复合预测?} D -->|是| E[设置全局MV<br/>GlobalMvs 1] D -->|否| F[跳过第二个全局MV] E --> G[扫描上方行<br/>scan_row_process -1] F --> G G --> H[记录上方匹配<br/>foundAboveMatch] H --> I[扫描左侧列<br/>scan_col_process -1] I --> J[记录左侧匹配<br/>foundLeftMatch] J --> K{块宽度和高度都较小} K -->|是| L[扫描右上点<br/>scan_point_process -1 bw4] K -->|否| M[跳过右上扫描] L --> N{找到匹配?} N -->|是| O[标记上方找到匹配] N -->|否| Q[统计近距离匹配数量] O --> Q M --> Q Q --> R[记录最近候选数量] R --> S[记录新候选数量] S --> T{有最近候选?} T -->|是| U[增加权重<br/>WeightStack += REF_CAT_LEVEL] T -->|否| V[跳过权重增加] U --> W[初始化零运动向量上下文] V --> W W --> X{允许使用参考帧的运动向量?} X -->|是| Y[时间扫描过程<br/>temporal_scan_process] X -->|否| Z[跳过时间扫描] Y --> AA[扫描左下点<br/>scan_point_process -1 -1] Z --> AA AA --> AB{找到匹配?} AB -->|是| AC[标记上方找到匹配] AB -->|否| AE[扫描上方行-3<br/>scan_row_process -3] AC --> AE AE --> AF{找到匹配?} AF -->|是| AG[标记上方找到匹配] AF -->|否| AI[扫描左侧列-3<br/>scan_col_process -3] AG --> AI[扫描左侧列-3<br/>scan_col_process -3] AI --> AJ{找到匹配?} AJ -->|是| AK[标记左侧找到匹配] AJ -->|否| AM{块高度大于1?} AK --> AM AM -->|是| AN[扫描上方行-5<br/>scan_row_process -5] AM -->|否| AO[跳过上方行-5] AN --> AP{找到匹配?} AP -->|是| AQ[标记上方找到匹配] AP -->|否| AS{块宽度大于1?} AO --> AS{块宽度大于1?} AQ --> AS AS -->|是| AT[扫描左侧列-5<br/>scan_col_process -5] AS -->|否| AU[跳过左侧列-5] AT --> AV{找到匹配?} AV -->|是| AW[标记左侧找到匹配] AV -->|否| AY[统计总匹配数量] AU --> AY AW --> AY AY --> AZ[对最近候选进行排序] AZ --> BA[对其他候选进行排序] BA --> BB{候选数量少于2个?} BB -->|是| BC[额外搜索过程<br/>extra_search_process] BB -->|否| BD[跳过额外搜索] BC --> BE[上下文和裁剪过程<br/>context_and_clamping_process] BD --> BE BE --> BF[MV栈构建完成<br/>RefStackMv] style A fill:#e1f5ff style D fill:#fff9c4 style K fill:#fff9c4 style X fill:#fff9c4 style BB fill:#fff9c4 style BF fill:#c8e6c9

详细技术文档参考

说明: 以下内容来自详细技术文档,包含完整的实现细节和代码说明。这些内容主要用于技术参考。

点击展开查看详细技术文档

空间邻居扫描流程

什么是空间邻居?

空间邻居就是当前块周围已经解码的相邻块。AV1会按照一定的顺序检查这些邻居块,借用它们的运动向量作为候选。

扫描流程图

graph TD A[空间邻居扫描] --> B[扫描上方行<br/>scan_row_process] B --> C[扫描左侧列<br/>scan_col_process] C --> D{块宽度和高度都较小?} D -->|是| E[扫描右上点<br/>scan_point_process] D -->|否| F[跳过右上扫描] E --> G[扫描左下点<br/>scan_point_process -1 -1] F --> G G --> H[扫描上方行-3<br/>scan_row_process -3] H --> I[扫描左侧列-3<br/>scan_col_process -3] I --> J{块高度大于1?} J -->|是| K[扫描上方行-5<br/>scan_row_process -5] J -->|否| L[跳过上方行-5] K --> M{块宽度大于1?} L --> M M -->|是| N[扫描左侧列-5<br/>scan_col_process -5] M -->|否| O[跳过左侧列-5] N --> P[空间邻居扫描完成] O --> P style A fill:#e1f5ff style D fill:#fff9c4 style J fill:#fff9c4 style M fill:#fff9c4 style P fill:#c8e6c9

通俗解释

  1. 先找最近的邻居:检查正上方和正左侧的块(最可能相似)
  2. 再找角落的邻居:如果块不太大,检查右上角和左下角
  3. 最后找更远的邻居:如果块比较大,检查更远的位置
  4. 按顺序收集:找到的运动向量按优先级加入候选栈

MV栈构建后处理流程

什么是后处理?

在收集完所有候选运动向量后,需要对它们进行整理、排序和优化,确保候选栈的质量。

后处理流程图

graph TD A[MV栈构建后处理] --> B[统计近距离匹配数量] B --> C[记录最近候选数量<br/>记录新候选数量] C --> D{有最近候选?} D -->|是| E[增加权重<br/>WeightStack += REF_CAT_LEVEL] D -->|否| F[跳过权重增加] E --> G[初始化零运动向量上下文] F --> G G --> H{允许使用参考帧的运动向量?} H -->|是| I[时间扫描过程<br/>添加时间邻居MV] H -->|否| J[跳过时间扫描] I --> K[统计总匹配数量] J --> K K --> L[对最近候选进行排序] L --> M[对其他候选进行排序] M --> N{候选数量少于2个?} N -->|是| O[额外搜索过程<br/>添加更多候选] N -->|否| P[跳过额外搜索] O --> Q[上下文和裁剪过程<br/>处理MV上下文和边界裁剪] P --> Q Q --> R[MV栈构建完成] style A fill:#e1f5ff style D fill:#fff9c4 style H fill:#fff9c4 style N fill:#fff9c4 style R fill:#c8e6c9

通俗解释

  1. 统计匹配情况:看看在上方和左侧找到了多少匹配的运动向量
  2. 提高优先级:如果找到了紧邻块的运动向量,给它们更高的优先级(更可能被选中)
  3. 查找参考帧:如果允许,检查上一帧相同位置的运动向量
  4. 排序候选:按照优先级对所有候选进行排序,最好的候选排在前面
  5. 补充候选:如果候选不够(少于2个),添加更多候选(如零向量)
  6. 边界检查:确保所有运动向量都是合法的,不会越界

总结

运动向量栈构建为后续的Y模式解码和运动向量分配提供候选MV。通过组合空间邻居、时间邻居、全局MV和零MV,构建一个包含多个候选的MV栈,解码器从中选择最佳候选。


参考资源:

上一篇: 参考帧解码
下一篇: Y模式解码

posted @ 2026-01-10 07:52  chai51  阅读(0)  评论(0)    收藏  举报