运动估计搜索
运动估计搜索是视频编码中的一个重要步骤,它用于确定视频序列中两个帧之间的运动向量(MV)。这些运动向量用于预测帧之间的运动,从而减少编码所需的数据量。以下是运动估计搜索的一些关键概念和步骤:
运动估计的目的:
- 运动估计的目的是找到最佳运动向量,使得当前帧(编码帧)与参考帧之间的差异最小。
运动向量:
- 运动向量是一个二维向量,表示在参考帧中找到与编码帧中某个区域最匹配的区域的位置偏移。
搜索方法:
- 有多种运动估计搜索算法,包括:
- 全搜索(Full Search):检查参考帧中的每个可能位置。
- 对分搜索(Logarithmic Search):从较大的搜索范围开始,逐渐缩小到更精细的搜索。
- 交叉钻石搜索(Cross-Diamond Search):结合了对分搜索和钻石搜索的优点。
- 钻石搜索(Diamond Search):在对分搜索的基础上,使用钻石形状的搜索路径。
- 新钻石搜索(New Diamond Search):钻石搜索的改进版本,通常在搜索精度和速度上有所提升。
搜索过程:
- 运动估计搜索通常包括以下步骤:
- 初始化:设置初始运动向量,通常为零或前一帧的运动向量。
- 搜索:根据选定的搜索算法,在参考帧中搜索最佳匹配区域。
- 计算成本:对于每个候选位置,计算与编码帧的匹配成本,如Sad(绝对差值之和)或SAD+Variance。
- 选择最佳:选择成本最低的候选位置作为最佳运动向量。
成本函数:
- 常用的成本函数包括:
- Sad:计算编码帧和参考帧对应区域的像素值差的绝对值之和。
- Sse:计算编码帧和参考帧对应区域的像素值差的平方和。
- Ssad:Sad的改进版本,考虑了像素值的梯度。
优化:
- 为了提高运动估计的效率,可以采用多种优化策略,如:
- 多分辨率搜索:先在较低分辨率上进行粗略搜索,然后在高分辨率上进行精细搜索。
- 预测运动向量:使用前一帧的运动向量作为初始估计。
- 快速算法:如快速模式决策和快速成本计算。
应用:
- 运动估计搜索是视频编码标准(如H.264, H.265, VP9等)的核心部分,对于视频压缩效率至关重要。
运动估计搜索算法的选择和实现取决于编码器的设计目标,包括压缩效率、编码速度和硬件资源等因素。
openh264 运动估计搜索原理
函数关系框架
- 说明:
- 虽然开启了 8x4、4x8、4x4 分块预测,但其实实际应用中使用宏开关关闭状态;
pfMotionSearch[1]
、pfMotionSearch[2]
虽然分别指向了WelsMotionEstimateSearchStatic
、WelsMotionEstimateSearchScrolled
两个函数,但实际并没使用。
核心函数
WelsMotionEstimateSearch
函数
- 功能:帧间预测过程中运动估计搜索的核心函数;它通过搜索最佳匹配块来最小化编码后视频的残差,从而提高压缩效率。运动估计的精度直接影响到编码效率和最终视频质量。
- 原理过程:
- 局部变量声明:
- kiStrideEnc 和 kiStrideRef 分别是当前编码帧和参考帧的步长(stride),步长是图像一行像素的字节数。
- 初始点预测:
WelsMotionEstimateInitialPoint
函数用于基于已有信息(如上一帧的运动矢量)预测当前宏块的运动估计初始点。- 如果初始点预测失败(返回值为 false),则调用
pFuncList->pfSearchMethod[pMe->uiBlockSize]
进行更精细的搜索。- 运动估计搜索方法:
pfSearchMethod
是一个函数指针数组,根据宏块的大小(pMe->uiBlockSize)选择相应的搜索算法,一般指向WelsDiamondSearch函数。- 结束整数像素搜索:
MeEndIntepelSearch
函数用于结束整数像素级别的搜索,并准备进行亚像素级别的搜索。- 计算SATD:
pfCalculateSatd
函数用于计算 SATD,这是一种衡量预测块和原始块差异的方法,用于评估运动估计的质量。pSampleDealingFuncs.pfSampleSatd[pMe->uiBlockSize]
是一个根据块大小选择的 SATD 计算函数。
- 源码:
/*!* \brief BL mb motion estimate search** \param enc Wels encoder context* \param pMe Wels me information** \return NONE*/void WelsMotionEstimateSearch (SWelsFuncPtrList* pFuncList, SDqLayer* pCurDqLayer, SWelsME* pMe, SSlice* pSlice) {const int32_t kiStrideEnc = pCurDqLayer->iEncStride[0];const int32_t kiStrideRef = pCurDqLayer->pRefPic->iLineSize[0];// Step 1: Initial point predictionif (!WelsMotionEstimateInitialPoint (pFuncList, pMe, pSlice, kiStrideEnc, kiStrideRef)) {pFuncList->pfSearchMethod[pMe->uiBlockSize] (pFuncList, pMe, pSlice, kiStrideEnc, kiStrideRef);MeEndIntepelSearch (pMe);}pFuncList->pfCalculateSatd (pFuncList->sSampleDealingFuncs.pfSampleSatd[pMe->uiBlockSize], pMe, kiStrideEnc,kiStrideRef);
}
WelsDiamondSearch
函数
- 功能:运动搜索中整像素的钻石搜索模板实现函数
- 原理过程:
- 局部变量声明:
pSad
是指向样本处理函数的指针,用于计算 SAD(Sum of Absolute Differences)。- pFref 和 kpEncMb 分别是指向参考宏块和当前编码宏块的指针。
- kpMvdCost 是指向运动向量差异成本的指针。
- 运动向量范围限制:
- ksMvStartMin 和 ksMvStartMax 定义了运动向量的搜索范围。
- 初始运动向量差值计算:
- iMvDx 和 iMvDy 计算当前运动向量与预测运动向量的差值,并且将差值扩大四倍。
- 搜索循环:
- 使用 while 循环进行迭代搜索,iTimeThreshold 作为迭代次数的阈值。
- 运动向量调整:
- 每次迭代中,将差值右移两位以恢复原始的运动向量比例。
- 运动向量范围检查:
- 使用
CheckMvInRange
函数检查调整后的运动向量是否在有效范围内。- 计算 SAD 成本:
- 调用
pSad
函数计算当前预测宏块与编码宏块之间的 SAD 成本。- 最佳成本选择:
- 使用
WelsMeSadCostSelect
函数选择最佳成本,如果当前成本更优,则更新最佳成本和相关变量。- 更新运动向量差值:
- 如果找到更优的成本,则更新差值以在下一次迭代中调整运动向量。
- 更新参考宏块指针:
- 根据选择的 X 和 Y 偏移更新参考宏块的指针。
- 结束搜索:
- 当找到最佳成本或达到迭代次数阈值时,结束搜索。
- 最终运动向量计算:
- 将最终的运动向量差值加上预测运动向量的坐标,然后右移两位以得到整数像素级别的运动向量。
- 更新运动估计信息:
- 更新 pMe 结构中的运动向量、SAD 成本、SATD 成本和参考宏块指针。
- 源码:
void WelsDiamondSearch (SWelsFuncPtrList* pFuncList, SWelsME* pMe, SSlice* pSlice,const int32_t kiStrideEnc, const int32_t kiStrideRef) {PSample4SadCostFunc pSad = pFuncList->sSampleDealingFuncs.pfSample4Sad[pMe->uiBlockSize];uint8_t* pFref = pMe->pRefMb;uint8_t* const kpEncMb = pMe->pEncMb;const uint16_t* kpMvdCost = pMe->pMvdCost;const SMVUnitXY ksMvStartMin = pSlice->sMvStartMin;const SMVUnitXY ksMvStartMax = pSlice->sMvStartMax;int32_t iMvDx = ((pMe->sMv.iMvX) * (1 << 2)) - pMe->sMvp.iMvX;int32_t iMvDy = ((pMe->sMv.iMvY) * (1 << 2)) - pMe->sMvp.iMvY;uint8_t* pRefMb = pFref;int32_t iBestCost = (pMe->uiSadCost);int32_t iTimeThreshold = ITERATIVE_TIMES;ENFORCE_STACK_ALIGN_1D (int32_t, iSadCosts, 4, 16)while (iTimeThreshold--) {pMe->sMv.iMvX = (iMvDx + pMe->sMvp.iMvX) >> 2;pMe->sMv.iMvY = (iMvDy + pMe->sMvp.iMvY) >> 2;if (!CheckMvInRange (pMe->sMv, ksMvStartMin, ksMvStartMax))continue;pSad (kpEncMb, kiStrideEnc, pRefMb, kiStrideRef, &iSadCosts[0]);int32_t iX, iY;const bool kbIsBestCostWorse = WelsMeSadCostSelect (iSadCosts, kpMvdCost, &iBestCost, iMvDx, iMvDy, &iX, &iY);if (kbIsBestCostWorse)break;iMvDx -= (iX * (1 << 2)) ;iMvDy -= (iY * (1 << 2)) ;pRefMb -= (iX + iY * kiStrideRef);}/* integer-pel mv */pMe->sMv.iMvX = (iMvDx + pMe->sMvp.iMvX) >> 2;pMe->sMv.iMvY = (iMvDy + pMe->sMvp.iMvY) >> 2;pMe->uiSatdCost = pMe->uiSadCost = (iBestCost);pMe->pRefMb = pRefMb;
}