xPatternSearchFracDIF函数是用于由xMotionEstimation函数调用,进行亚像素运动估计。
- 首先进行 1/2 像素插值,以整像素最佳MV为搜索起点,搜索包含当前点在内的 9 个 1/2 像素点;
- 再进行 1/4 像素插值,其中 1/4 像素插值是在上一轮最优的 1/2 像素最优点附近插值,搜索包括当前最优 1/2 像素点在内的 9 个1/4 像素点。
代码及注释如下:
void InterSearch::xPatternSearchFracDIF(
const PredictionUnit& pu,
RefPicList eRefPicList,
int iRefIdx,
IntTZSearchStruct& cStruct,
const Mv& rcMvInt,
Mv& rcMvHalf,
Mv& rcMvQter,
Distortion& ruiCost
)
{
// Reference pattern initialization (integer scale) 到预测块像素位置的偏移
int iOffset = rcMvInt.getHor() + rcMvInt.getVer() * cStruct.iRefStride;
CPelBuf cPatternRoi(cStruct.piRefY + iOffset, cStruct.iRefStride, *cStruct.pcPatternKey); //当前块的整数位置预测像素
if (m_skipFracME)
{
Mv baseRefMv(0, 0);
rcMvHalf.setZero();
m_pcRdCost->setCostScale(0);
xExtDIFUpSamplingH(&cPatternRoi, cStruct.useAltHpelIf);
rcMvQter = rcMvInt; rcMvQter <<= 2; // for mv-cost
ruiCost = xPatternRefinement(cStruct.pcPatternKey, baseRefMv, 1, rcMvQter, !pu.cs->slice->getDisableSATDForRD());
return;
}
if (cStruct.imvShift > IMV_FPEL || (m_useCompositeRef && cStruct.zeroMV))
{
m_pcRdCost->setDistParam(m_cDistParam, *cStruct.pcPatternKey, cStruct.piRefY + iOffset, cStruct.iRefStride, m_lumaClpRng.bd, COMPONENT_Y, 0, 1, m_pcEncCfg->getUseHADME() && !pu.cs->slice->getDisableSATDForRD());
ruiCost = m_cDistParam.distFunc( m_cDistParam );
ruiCost += m_pcRdCost->getCostOfVectorWithPredictor( rcMvInt.getHor(), rcMvInt.getVer(), cStruct.imvShift );
return;
}
// Half-pel refinement 1/2像素细化
m_pcRdCost->setCostScale(1);
xExtDIFUpSamplingH(&cPatternRoi, cStruct.useAltHpelIf);//1/2位置像素上采样
rcMvHalf = rcMvInt; rcMvHalf <<= 1; // for mv-cost
Mv baseRefMv(0, 0); //亚像素精度搜索匹配,得到1/2精度mv rcMvHalf
// 此时 baseRefMv 为0,即搜索起点整像素最佳MV
ruiCost = xPatternRefinement(cStruct.pcPatternKey, baseRefMv, 2, rcMvHalf, (!pu.cs->slice->getDisableSATDForRD()));
// quarter-pel refinement 1/4像素细化
if (cStruct.imvShift == IMV_OFF)
{
m_pcRdCost->setCostScale( 0 );
xExtDIFUpSamplingQ ( &cPatternRoi, rcMvHalf ); //1/4位置像素上采样
baseRefMv = rcMvHalf;
baseRefMv <<= 1; // 放大到 1/2 像素精度
rcMvQter = rcMvInt; rcMvQter <<= 1; // for mv-cost
rcMvQter += rcMvHalf; rcMvQter <<= 1; // 放大到 1/4 像素精度
//亚像素精度搜索匹配,得到1/4精度mv rcMvQter,baseRefMv此时为 1/2 像素精度最佳偏移
ruiCost = xPatternRefinement(cStruct.pcPatternKey, baseRefMv, 1, rcMvQter, (!pu.cs->slice->getDisableSATDForRD()));
}
}
在进行插值时,先对整像素所在的行或者列进行插值;再在此基础上,对剩余亚像素位置进行插值,以下图为例:
1.对整数像素所在行或列进行插值。以A(0,0)点附近的亚像素点为例,a(0,0),b(0,0),c(0,0)可以用水平方向的整像素点计算得出,d(0,0),h(0,0),n(0,0)可以用垂直方向上的整像素点计算得出。
如1/4位置点a(0,0) = -A(-3,0) + 4A(-2,0) -10A(-1,0) + 58A(0,0) + 17A(1,0) - 5A(2,0) + A(3,0)
2.对剩余亚像素位置进行插值,使用步骤1中已计算得到的亚像素位置计算。
如e(0,0) = -a(-3,0) + 4a(-2,0) -10a(-1,0) + 58a(0,0) + 17a(1,0) - 5a(2,0) + a(3,0) >>6
注意,经过亚像素插值后所有像素点都放大了64倍,目的是在中间过程中保持一定精度,在后续的加权预测环节中,会被还原。
在代码实现中,是使用m_filteredBlockTmp[]和m_filteredBlock[][]变量存储插值后的像素块的。
1.m_filteredBlockTmp
m_filteredBlockTmp[]是临时变量,实际用于存储水平插值得到的各像素位置的YUV信息。[]中的数字指示水平插值的像素位置。
对参考图像进行水平插值,整像素位置直接复制给了m_filteredBlockTmp[0],1/2像素位置插值后给了m_filteredBlockTmp[2]。
在1/4像素插值xExtDIFUpSamplingQ中类似,1/4和3/4像素位置水平插值后给了m_filteredBlockTmp[1]和m_filteredBlockTmp[3]。
2.m_filteredBlock
在做完水平插值之后,会进行垂直插值,得到最终的数据,存储与m_filteredBlock[][]中。m_filteredBlock[][]用来存储最终的插值数据。
m_filteredBlock中存储了不同插值位置的像素数据,第一个[]中的数字指示垂直插值位置,第二个指示水平插值位置。每个m_filteredBlock[ver][hor]的大小都是width*height,不同的索引组合代表不同插值位置得到的像素数据。
xExtDIFUpSamplingH函数主要进行1/2像素插值
/**
* \brief Generate half-sample interpolated block 生成半采样插值块
*
* \param pattern Reference picture ROI 模式参考图片ROI
* \param biPred Flag indicating whether block is for biprediction 指示块是否用于biprediction的标志
*/
void InterSearch::xExtDIFUpSamplingH(CPelBuf* pattern, bool useAltHpelIf)
{
const ClpRng& clpRng = m_lumaClpRng;
int width = pattern->width;
int height = pattern->height;
int srcStride = pattern->stride;
int intStride = width + 1;
int dstStride = width + 1;
Pel *intPtr;
Pel *dstPtr;
int filterSize = NTAPS_LUMA; // 8
int halfFilterSize = (filterSize>>1); // 4
const Pel *srcPtr = pattern->buf - halfFilterSize*srcStride - 1;
const ChromaFormat chFmt = m_currChromaFormat; //色度格式
//水平整像素插值,结果存储在m_filteredBlockTmp[0]中
m_if.filterHor(COMPONENT_Y, srcPtr, srcStride, m_filteredBlockTmp[0][0], intStride, width + 1, height + filterSize, 0 << MV_FRACTIONAL_BITS_DIFF, false, chFmt, clpRng, 0, false, useAltHpelIf);
if (!m_skipFracME)
{
//水平1/2像素插值,结果存储在m_filteredBlockTmp[2]中
m_if.filterHor(COMPONENT_Y, srcPtr, srcStride, m_filteredBlockTmp[2][0], intStride, width + 1, height + filterSize, 2 << MV_FRACTIONAL_BITS_DIFF, false, chFmt, clpRng, 0, false, useAltHpelIf);
}
intPtr = m_filteredBlockTmp[0][0] + halfFilterSize * intStride + 1;
dstPtr = m_filteredBlock[0][0][0];
// 水平整像素插值后的Y做垂直方向整像素插值,结果存储在m_filteredBlock[0][0]中
m_if.filterVer(COMPONENT_Y, intPtr, intStride, dstPtr, dstStride, width + 0, height + 0, 0 << MV_FRACTIONAL_BITS_DIFF, false, true, chFmt, clpRng, 0, false, useAltHpelIf);
if (m_skipFracME)
{
return;
}
intPtr = m_filteredBlockTmp[0][0] + (halfFilterSize - 1) * intStride + 1;
dstPtr = m_filteredBlock[2][0][0];
//水平整像素插值后的Y做垂直方向1/2像素插值,结果存储在m_filteredBlock[2][0]中
m_if.filterVer(COMPONENT_Y, intPtr, intStride, dstPtr, dstStride, width + 0, height + 1, 2 << MV_FRACTIONAL_BITS_DIFF, false, true, chFmt, clpRng, 0, false, useAltHpelIf);
intPtr = m_filteredBlockTmp[2][0] + halfFilterSize * intStride;
dstPtr = m_filteredBlock[0][2][0];
//水平1/2像素插值后的Y做垂直方向整像素插值,结果存储在m_filteredBlock[0][2]中
m_if.filterVer(COMPONENT_Y, intPtr, intStride, dstPtr, dstStride, width + 1, height + 0, 0 << MV_FRACTIONAL_BITS_DIFF, false, true, chFmt, clpRng, 0, false, useAltHpelIf);
intPtr = m_filteredBlockTmp[2][0] + (halfFilterSize - 1) * intStride;
dstPtr = m_filteredBlock[2][2][0];
//水平1/2像素插值后的Y做垂直方向1/2像素插值,结果存储在m_filteredBlock[2][2]中
m_if.filterVer(COMPONENT_Y, intPtr, intStride, dstPtr, dstStride, width + 1, height + 1, 2 << MV_FRACTIONAL_BITS_DIFF, false, true, chFmt, clpRng, 0, false, useAltHpelIf);
}
xExtDIFUpSamplingQ函数进行1/4像素插值
/**
* \brief Generate quarter-sample interpolated blocks
*
* \param pattern Reference picture ROI
* \param halfPelRef Half-pel mv 1/2像素精度的MV
* \param biPred Flag indicating whether block is for biprediction
*/
void InterSearch::xExtDIFUpSamplingQ( CPelBuf* pattern, Mv halfPelRef )
{
const ClpRng& clpRng = m_lumaClpRng;
int width = pattern->width;
int height = pattern->height;
int srcStride = pattern->stride;
Pel const* srcPtr;
int intStride = width + 1;
int dstStride = width + 1;
Pel *intPtr;
Pel *dstPtr;
int filterSize = NTAPS_LUMA; // 8
int halfFilterSize = (filterSize>>1); // 4
int extHeight = (halfPelRef.getVer() == 0) ? height + filterSize : height + filterSize-1;
const ChromaFormat chFmt = m_currChromaFormat;
// Horizontal filter 1/4
srcPtr = pattern->buf - halfFilterSize * srcStride - 1;
intPtr = m_filteredBlockTmp[1][0];//将1/4位置像素存储在m_filteredBlockTmp[1]中
if (halfPelRef.getVer() > 0)
{
srcPtr += srcStride;
}
if (halfPelRef.getHor() >= 0)
{
srcPtr += 1;
}
m_if.filterHor(COMPONENT_Y, srcPtr, srcStride, intPtr, intStride, width, extHeight, 1 << MV_FRACTIONAL_BITS_DIFF, false, chFmt, clpRng);
// Horizontal filter 3/4
srcPtr = pattern->buf - halfFilterSize*srcStride - 1;
intPtr = m_filteredBlockTmp[3][0];//将3/4位置像素存储在m_filteredBlockTmp[3]中
if (halfPelRef.getVer() > 0)
{
srcPtr += srcStride;
}
if (halfPelRef.getHor() > 0)
{
srcPtr += 1;
}
m_if.filterHor(COMPONENT_Y, srcPtr, srcStride, intPtr, intStride, width, extHeight, 3 << MV_FRACTIONAL_BITS_DIFF, false, chFmt, clpRng);
// Generate @ 1,1
intPtr = m_filteredBlockTmp[1][0] + (halfFilterSize-1) * intStride;
dstPtr = m_filteredBlock[1][1][0];
if (halfPelRef.getVer() == 0)
{
intPtr += intStride;
}
//水平1/4像素插值后的Y分量做垂直1/4像素插值,结果存储在m_filteredBlock[1][1]中
m_if.filterVer(COMPONENT_Y, intPtr, intStride, dstPtr, dstStride, width, height, 1 << MV_FRACTIONAL_BITS_DIFF, false, true, chFmt, clpRng);
// Generate @ 3,1
intPtr = m_filteredBlockTmp[1][0] + (halfFilterSize-1) * intStride;
dstPtr = m_filteredBlock[3][1][0];
//水平1/4像素插值后的Y分量做垂直3/4像素插值,结果存储在m_filteredBlock[3][1]中
m_if.filterVer(COMPONENT_Y, intPtr, intStride, dstPtr, dstStride, width, height, 3 << MV_FRACTIONAL_BITS_DIFF, false, true, chFmt, clpRng);
if (halfPelRef.getVer() != 0)
{
// Generate @ 2,1
intPtr = m_filteredBlockTmp[1][0] + (halfFilterSize - 1) * intStride;
dstPtr = m_filteredBlock[2][1][0];
if (halfPelRef.getVer() == 0)
{
intPtr += intStride;
}
//水平1/4像素插值后的Y分量做垂直1/2像素插值,结果存储在m_filteredBlock[2][1]中
m_if.filterVer(COMPONENT_Y, intPtr, intStride, dstPtr, dstStride, width, height, 2 << MV_FRACTIONAL_BITS_DIFF, false, true, chFmt, clpRng);
// Generate @ 2,3
intPtr = m_filteredBlockTmp[3][0] + (halfFilterSize - 1) * intStride;
dstPtr = m_filteredBlock[2][3][0];
if (halfPelRef.getVer() == 0)
{
intPtr += intStride;
}
//水平3/4像素插值后的Y分量做垂直1/2像素插值,结果存储在m_filteredBlock[2][3]中
m_if.filterVer(COMPONENT_Y, intPtr, intStride, dstPtr, dstStride, width, height, 2 << MV_FRACTIONAL_BITS_DIFF, false, true, chFmt, clpRng);
}
else
{
// Generate @ 0,1
intPtr = m_filteredBlockTmp[1][0] + halfFilterSize * intStride;
dstPtr = m_filteredBlock[0][1][0];
//水平1/4像素插值后的Y分量做垂直整像素插值,结果存储在m_filteredBlock[0][1]中
m_if.filterVer(COMPONENT_Y, intPtr, intStride, dstPtr, dstStride, width, height, 0 << MV_FRACTIONAL_BITS_DIFF, false, true, chFmt, clpRng);
// Generate @ 0,3
intPtr = m_filteredBlockTmp[3][0] + halfFilterSize * intStride;
dstPtr = m_filteredBlock[0][3][0];
//水平3/4像素插值后的Y分量做垂直整像素插值,结果存储在m_filteredBlock[0][3]中
m_if.filterVer(COMPONENT_Y, intPtr, intStride, dstPtr, dstStride, width, height, 0 << MV_FRACTIONAL_BITS_DIFF, false, true, chFmt, clpRng);
}
if (halfPelRef.getHor() != 0)
{
// Generate @ 1,2
intPtr = m_filteredBlockTmp[2][0] + (halfFilterSize - 1) * intStride;
dstPtr = m_filteredBlock[1][2][0];
if (halfPelRef.getHor() > 0)
{
intPtr += 1;
}
if (halfPelRef.getVer() >= 0)
{
intPtr += intStride;
}
//水平1/2像素插值后的Y分量做垂直1/4像素插值,结果存储在m_filteredBlock[1][2]中
m_if.filterVer(COMPONENT_Y, intPtr, intStride, dstPtr, dstStride, width, height, 1 << MV_FRACTIONAL_BITS_DIFF, false, true, chFmt, clpRng);
// Generate @ 3,2
intPtr = m_filteredBlockTmp[2][0] + (halfFilterSize - 1) * intStride;
dstPtr = m_filteredBlock[3][2][0];
if (halfPelRef.getHor() > 0)
{
intPtr += 1;
}
if (halfPelRef.getVer() > 0)
{
intPtr += intStride;
}
//水平1/2像素插值后的Y分量做垂直3/4像素插值,结果存储在m_filteredBlock[3][2]中
m_if.filterVer(COMPONENT_Y, intPtr, intStride, dstPtr, dstStride, width, height, 3 << MV_FRACTIONAL_BITS_DIFF, false, true, chFmt, clpRng);
}
else
{
// Generate @ 1,0
intPtr = m_filteredBlockTmp[0][0] + (halfFilterSize - 1) * intStride + 1;
dstPtr = m_filteredBlock[1][0][0];
if (halfPelRef.getVer() >= 0)
{
intPtr += intStride;
}
//水平整像素插值后的Y分量做垂直1/4像素插值,结果存储在m_filteredBlock[1][0]中
m_if.filterVer(COMPONENT_Y, intPtr, intStride, dstPtr, dstStride, width, height, 1 << MV_FRACTIONAL_BITS_DIFF, false, true, chFmt, clpRng);
// Generate @ 3,0
intPtr = m_filteredBlockTmp[0][0] + (halfFilterSize - 1) * intStride + 1;
dstPtr = m_filteredBlock[3][0][0];
if (halfPelRef.getVer() > 0)
{
intPtr += intStride;
}
//水平整像素插值后的Y分量做垂直3/4像素插值,结果存储在m_filteredBlock[3][0]中
m_if.filterVer(COMPONENT_Y, intPtr, intStride, dstPtr, dstStride, width, height, 3 << MV_FRACTIONAL_BITS_DIFF, false, true, chFmt, clpRng);
}
// Generate @ 1,3
intPtr = m_filteredBlockTmp[3][0] + (halfFilterSize - 1) * intStride;
dstPtr = m_filteredBlock[1][3][0];
if (halfPelRef.getVer() == 0)
{
intPtr += intStride;
}
//水平3/4像素插值后的Y分量做垂直1/4像素插值,结果存储在m_filteredBlock[1][3]中
m_if.filterVer(COMPONENT_Y, intPtr, intStride, dstPtr, dstStride, width, height, 1 << MV_FRACTIONAL_BITS_DIFF, false, true, chFmt, clpRng);
// Generate @ 3,3
intPtr = m_filteredBlockTmp[3][0] + (halfFilterSize - 1) * intStride;
dstPtr = m_filteredBlock[3][3][0];
//水平3/4像素插值后的Y分量做垂直3/4像素插值,结果存储在m_filteredBlock[3][3]中
m_if.filterVer(COMPONENT_Y, intPtr, intStride, dstPtr, dstStride, width, height, 3 << MV_FRACTIONAL_BITS_DIFF, false, true, chFmt, clpRng);
}
xPatternRefinement函数用来1/2像素精度的MV和1/4像素精度的MV细化,选出最佳的MV
Distortion InterSearch::xPatternRefinement( const CPelBuf* pcPatternKey, //
Mv baseRefMv, //1/2像素时为(0,0),1/4像素时为rcMvHalf
int iFrac, Mv& rcMvFrac, //1/2像素时为rcMvHalf,1/4像素时为rcMvQter
bool bAllowUseOfHadamard )
{
Distortion uiDist;
Distortion uiDistBest = std::numeric_limits<Distortion>::max();
uint32_t uiDirecBest = 0;
Pel* piRefPos;
int iRefStride = pcPatternKey->width + 1;
//初始化失真参数
m_pcRdCost->setDistParam( m_cDistParam, *pcPatternKey, m_filteredBlock[0][0][0], iRefStride, m_lumaClpRng.bd, COMPONENT_Y, 0, 1, m_pcEncCfg->getUseHADME() && bAllowUseOfHadamard );
const Mv* pcMvRefine = (iFrac == 2 ? s_acMvRefineH : s_acMvRefineQ); //搜索邻域的偏移量
for (uint32_t i = 0; i < 9; i++)
{
if (m_skipFracME && i > 0)
{
break;
}
Mv cMvTest = pcMvRefine[i];
cMvTest += baseRefMv; // 偏移后的MV
int horVal = cMvTest.getHor() * iFrac;
int verVal = cMvTest.getVer() * iFrac;
piRefPos = m_filteredBlock[verVal & 3][horVal & 3][0];
if (horVal == 2 && (verVal & 1) == 0)
{
piRefPos += 1;
}
if ((horVal & 1) == 0 && verVal == 2)
{
piRefPos += iRefStride;
}
cMvTest = pcMvRefine[i];
cMvTest += rcMvFrac;
m_cDistParam.cur.buf = piRefPos;
uiDist = m_cDistParam.distFunc( m_cDistParam ); //获取失真
uiDist += m_pcRdCost->getCostOfVectorWithPredictor( cMvTest.getHor(), cMvTest.getVer(), 0 );
if ( uiDist < uiDistBest )
{
uiDistBest = uiDist;
uiDirecBest = i;
m_cDistParam.maximumDistortionForEarlyExit = uiDist;
}
}
参考:
HEVC代码学习17:m_filteredBlockTmp[]和m_filteredBlock[][]_岳麓吹雪的博客-CSDN博客