4. 菱形搜索法运动估计

任务目标

在运动估计中,全搜索法虽精度高但计算量巨大。本案例实现菱形搜索法(Diamond Search, DS),使用大菱形(LDSP)与小菱形(SDSP)两种模板,在保证估计质量接近全搜索的前提下,显著减少搜索点数,并分析其收敛速度与精度特性。

核心实现

import numpy as np

def sad(block1, block2):
    """计算两个块之间的SAD(差值和)"""
    return np.sum(np.abs(block1.astype(np.int16) - block2.astype(np.int16)))

def diamond_search(ref_frame, cur_block, block_pos, search_range=7):
    """
    菱形搜索运动估计
    ref_frame: 参考帧(灰度图,numpy 2D数组)
    cur_block: 当前块(2D数组)
    block_pos: 当前块在帧中的位置 (row, col)
    search_range: 搜索范围(菱形扩展的最大半径)
    返回: (mv_row, mv_col, search_points)
    """
    height, width = ref_frame.shape
    block_h, block_w = cur_block.shape
    br, bc = block_pos

    # 大菱形模板(LDSP):中心+上下左右各2步
    ldsp_offsets = [(0, 0), (0, -2), (0, 2), (-2, 0), (2, 0),
                    (-1, -1), (-1, 1), (1, -1), (1, 1)]
    # 小菱形模板(SDSP):中心+上下左右各1步
    sdsp_offsets = [(0, 0), (0, -1), (0, 1), (-1, 0), (1, 0)]

    best_mv = (0, 0)
    best_sad = float('inf')
    search_points = 0

    # 初始位置:搜索中心为当前块位置
    center_r, center_c = br, bc

    # 第一阶段:LDSP迭代搜索
    while True:
        min_sad = float('inf')
        min_offset = (0, 0)

        for dr, dc in ldsp_offsets:
            ref_r = center_r + dr
            ref_c = center_c + dc
            # 边界检查
            if (ref_r < 0 or ref_r + block_h > height or
                ref_c < 0 or ref_c + block_w > width):
                continue
            ref_block = ref_frame[ref_r:ref_r+block_h, ref_c:ref_c+block_w]
            cost = sad(cur_block, ref_block)
            search_points += 1
            if cost < min_sad:
                min_sad = cost
                min_offset = (dr, dc)

        # 更新最佳匹配
        if min_sad < best_sad:
            best_sad = min_sad
            best_mv = (center_r - br + min_offset[0], center_c - bc + min_offset[1])

        # 检查是否到达搜索范围边界
        if (abs(center_r - br) >= search_range or
            abs(center_c - bc) >= search_range):
            break

        # 若最小点在中心,则转入SDSP;否则移动中心到最小点
        if min_offset == (0, 0):
            break
        else:
            center_r += min_offset[0]
            center_c += min_offset[1]

    # 第二阶段:SDSP精细搜索
    for dr, dc in sdsp_offsets:
        ref_r = center_r + dr
        ref_c = center_c + dc
        if (ref_r < 0 or ref_r + block_h > height or
            ref_c < 0 or ref_c + block_w > width):
            continue
        ref_block = ref_frame[ref_r:ref_r+block_h, ref_c:ref_c+block_w]
        cost = sad(cur_block, ref_block)
        search_points += 1
        if cost < best_sad:
            best_sad = cost
            best_mv = (center_r - br + dr, center_c - bc + dc)

    return best_mv, search_points

def full_search(ref_frame, cur_block, block_pos, search_range=7):
    """全搜索法(用于对比)"""
    height, width = ref_frame.shape
    block_h, block_w = cur_block.shape
    br, bc = block_pos
    best_mv = (0, 0)
    best_sad = float('inf')
    search_points = 0

    for dr in range(-search_range, search_range + 1):
        for dc in range(-search_range, search_range + 1):
            ref_r = br + dr
            ref_c = bc + dc
            if (ref_r < 0 or ref_r + block_h > height or
                ref_c < 0 or ref_c + block_w > width):
                continue
            ref_block = ref_frame[ref_r:ref_r+block_h, ref_c:ref_c+block_w]
            cost = sad(cur_block, ref_block)
            search_points += 1
            if cost < best_sad:
                best_sad = cost
                best_mv = (dr, dc)

    return best_mv, search_points

运行与验证

# 生成测试数据:模拟一个简单运动场景
frame_size = 64
ref_frame = np.random.randint(0, 256, (frame_size, frame_size), dtype=np.uint8)
# 构造当前帧:将参考帧中某块向右下平移(3,2)
block_size = 8
true_mv = (3, 2)
block_pos = (20, 20)  # 当前块在帧中的位置

# 提取当前块(模拟运动后的块,加入少量噪声)
cur_block = ref_frame[block_pos[0]+true_mv[0]:block_pos[0]+true_mv[0]+block_size,
                      block_pos[1]+true_mv[1]:block_pos[1]+true_mv[1]+block_size].copy()
cur_block = np.clip(cur_block.astype(np.int16) + np.random.normal(0, 3, cur_block.shape), 0, 255).astype(np.uint8)

# 执行菱形搜索
ds_mv, ds_points = diamond_search(ref_frame, cur_block, block_pos, search_range=7)
# 执行全搜索对比
fs_mv, fs_points = full_search(ref_frame, cur_block, block_pos, search_range=7)

print(f"真实运动矢量: {true_mv}")
print(f"菱形搜索: 运动矢量={ds_mv}, 搜索点数={ds_points}")
print(f"全搜索:   运动矢量={fs_mv}, 搜索点数={fs_points}")

# 验证:计算两种方法的SAD误差
def get_block(frame, pos, size):
    r, c = pos
    return frame[r:r+size, c:c+size]

ds_sad = sad(cur_block, get_block(ref_frame, (block_pos[0]+ds_mv[0], block_pos[1]+ds_mv[1]), block_size))
fs_sad = sad(cur_block, get_block(ref_frame, (block_pos[0]+fs_mv[0], block_pos[1]+fs_mv[1]), block_size))
print(f"菱形搜索SAD: {ds_sad}, 全搜索SAD: {fs_sad}")

# 收敛性分析:统计搜索点数的减少比例
reduction = (1 - ds_points / fs_points) * 100
print(f"搜索点数减少: {reduction:.1f}%")

预期输出与判定标准

运行上述代码,预期输出类似:

真实运动矢量: (3, 2)
菱形搜索: 运动矢量=(3, 2), 搜索点数=21
全搜索:   运动矢量=(3, 2), 搜索点数=225
菱形搜索SAD: 略大于全搜索SAD(因噪声影响)
搜索点数减少: 90.7%

通过条件:

  • 菱形搜索的运动矢量与真实运动矢量一致(或误差≤1像素);
  • 搜索点数显著少于全搜索(通常减少80%以上);
  • 两种方法的SAD差值在噪声允许范围内(本例噪声σ=3,SAD差值应小于块内像素数×噪声方差的一半,即8×8×3²/2≈288)。

收敛性分析要点

  • 收敛速度:菱形搜索的搜索点数约为全搜索的1/10,且与搜索范围大小呈对数关系而非线性关系;
  • 收敛精度:对于单峰误差曲面,DS能收敛到全局最优;对于多峰曲面(如纹理复杂区域),DS可能陷入局部最优,但SAD代价通常接近全局最优;
  • 模板选择:LDSP负责粗定位(大步长快速接近最优),SDSP负责精调(小步长精确收敛),两步结合保证搜索效率与精度平衡。
菱形搜索收敛过程示意(搜索范围=7) 水平搜索范围(像素) 垂直搜索范围(像素) 搜索范围边界 真实MV(3,2) 起始中心 起始点 LDSP迭代点 收敛点 真实MV

说明:上图展示了菱形搜索的收敛路径。搜索从中心点开始,通过多次LDSP迭代快速向最优区域移动(每次移动2像素),当LDSP中心点成为最小SAD点时,切换SDSP进行1像素精度的精细搜索。整个过程仅需约20个搜索点,而全搜索需要225个点(15×15),计算量降低约90%。