HEVC帧间预测在AMVP模式下是依靠xEstimateMvPredAMVP
函数获取预测MV(MVP)的。
这部分内容的学习还可以参考这两篇博客:
HEVC代码学习15:AMVP相关函数
HM编码器代码阅读(16)——帧间预测之AMVP模式(四)预测MV的获取
xEstimateMvPredAMVP
xEstimateMvPredAMVP
的作用是建立MVP列表并获取最优MVP,最终将最优MVP以及其索引等信息返回给上层函数——preInterSearch
xEstimateMvPredAMVP
的基本流程如下:
(1)判断bFilled标识,该标识表示MVP候选列表是否已经建好;
(2)如果bFilled是false,通过fillMvpCand
获取MVP候选列表;否则,不需要重新建立MVP候选列表
(3)先把MVP候选列表中的第一个MVP作为最优的MVP
(4)如果候选列表中MVP的数量小于等于1,那么直接把步骤3选出的MVP返回
(5)如果bFilled是true,表示MVP候选列表是原来已经建立好的,那么直接根据PU的相关信息得到最优的MVP,然后返回
(6)遍历MVP候选列表,选出代价最小的MVP作为当前PU的MVP,并设置相关信息,然后返回
xEstimateMvPredAMVP
代码如下:
Void TEncSearch::xEstimateMvPredAMVP( TComDataCU* pcCU, TComYuv* pcOrgYuv, UInt uiPartIdx, RefPicList eRefPicList, Int iRefIdx, TComMv& rcMvPred, Bool bFilled, Distortion* puiDistBiP )
{
AMVPInfo* pcAMVPInfo = pcCU->getCUMvField(eRefPicList)->getAMVPInfo();
TComMv cBestMv; //最优MV
Int iBestIdx = 0; //最优MV在候选中的索引
TComMv cZeroMv;
TComMv cMvPred;
Distortion uiBestCost = std::numeric_limits<Distortion>::max();
UInt uiPartAddr = 0; // PU地址
Int iRoiWidth, iRoiHeight; // PU长宽
Int i;
Int minMVPCand;
Int maxMVPCand;
// 获取当前PU的索引和大小
pcCU->getPartIndexAndSize( uiPartIdx, uiPartAddr, iRoiWidth, iRoiHeight );
// Fill the MV Candidates
if (!bFilled)
{
pcCU->fillMvpCand( uiPartIdx, uiPartAddr, eRefPicList, iRefIdx, pcAMVPInfo ); // 建立MVP候选列表
}
// initialize Mvp index & Mvp
#if MCTS_ENC_CHECK
if (m_pcEncCfg->getTMCTSSEITileConstraint() && pcCU->isLastColumnCTUInTile() && (pcAMVPInfo->numSpatialMVPCandidates < pcAMVPInfo->iN))
{
iBestIdx = (pcAMVPInfo->numSpatialMVPCandidates == 0) ? 1 : 0;
cBestMv = pcAMVPInfo->m_acMvCand[(pcAMVPInfo->numSpatialMVPCandidates == 0) ? 1 : 0];
minMVPCand = (pcAMVPInfo->numSpatialMVPCandidates == 0) ? 1 : 0;
maxMVPCand = (pcAMVPInfo->numSpatialMVPCandidates == 0) ? pcAMVPInfo->iN : 1;
}
else
{
iBestIdx = 0;
cBestMv = pcAMVPInfo->m_acMvCand[0];
minMVPCand = 0;
maxMVPCand = pcAMVPInfo->iN;
}
#else
iBestIdx = 0;
cBestMv = pcAMVPInfo->m_acMvCand[0];
minMVPCand = 0;
maxMVPCand = pcAMVPInfo->iN;
#endif
if (pcAMVPInfo->iN <= 1)
{
rcMvPred = cBestMv;
pcCU->setMVPIdxSubParts( iBestIdx, eRefPicList, uiPartAddr, uiPartIdx, pcCU->getDepth(uiPartAddr));
pcCU->setMVPNumSubParts( pcAMVPInfo->iN, eRefPicList, uiPartAddr, uiPartIdx, pcCU->getDepth(uiPartAddr));
if(pcCU->getSlice()->getMvdL1ZeroFlag() && eRefPicList==REF_PIC_LIST_1)
{
(*puiDistBiP) = xGetTemplateCost( pcCU, uiPartAddr, pcOrgYuv, &m_cYuvPredTemp, rcMvPred, 0, AMVP_MAX_NUM_CANDS, eRefPicList, iRefIdx, iRoiWidth, iRoiHeight);
}
return;
}
// 上述步骤(5)
if (bFilled)
{
assert(pcCU->getMVPIdx(eRefPicList,uiPartAddr) >= 0);
rcMvPred = pcAMVPInfo->m_acMvCand[pcCU->getMVPIdx(eRefPicList,uiPartAddr)];
return;
}
m_cYuvPredTemp.clear();
//-- Check Minimum Cost.
// 比较得出最优MV
for ( i = minMVPCand ; i < maxMVPCand; i++)
{
Distortion uiTmpCost;
uiTmpCost = xGetTemplateCost( pcCU, uiPartAddr, pcOrgYuv, &m_cYuvPredTemp, pcAMVPInfo->m_acMvCand[i], i, AMVP_MAX_NUM_CANDS, eRefPicList, iRefIdx, iRoiWidth, iRoiHeight);
if ( uiBestCost > uiTmpCost )
{
uiBestCost = uiTmpCost;
cBestMv = pcAMVPInfo->m_acMvCand[i];
iBestIdx = i;
(*puiDistBiP) = uiTmpCost;
}
}
m_cYuvPredTemp.clear();
// Setting Best MVP
// 设置最优MVP的信息,将获取的最优MVP信息返回给preInterSearch
rcMvPred = cBestMv;
pcCU->setMVPIdxSubParts( iBestIdx, eRefPicList, uiPartAddr, uiPartIdx, pcCU->getDepth(uiPartAddr));
pcCU->setMVPNumSubParts( pcAMVPInfo->iN, eRefPicList, uiPartAddr, uiPartIdx, pcCU->getDepth(uiPartAddr));
return;
}
而在上层函数preInterSearch
中,则通过以下语句调用xEstimateMvPredAMVP
并获取从其中返回的上述三个值:cBestMv(最优MV)、iBestIdx(最优MV索引)、pcAMVPInfo->iN(AMVP候选列表中有效候选数量)
xEstimateMvPredAMVP( pcCU, pcOrgYuv, iPartIdx, eRefPicList, iRefIdxTemp, cMvPred[iRefList][iRefIdxTemp], false, &biPDistTemp);
aaiMvpIdx[iRefList][iRefIdxTemp] = pcCU->getMVPIdx(eRefPicList, uiPartAddr);
aaiMvpNum[iRefList][iRefIdxTemp] = pcCU->getMVPNum(eRefPicList, uiPartAddr);
通过调用上述语句,实则将上述三个值分别赋给以下三个值,以供后续使用:
//最优MV存入cMvPred[iRefList][iRefIdxTemp]
cMvPred[iRefList][iRefIdxTemp] = cBestMv
// 最优MVP索引存入CU中(执行xEstimateMvPredAMVP的结果),然后赋值给aaiMvpIdx[iRefList][iRefIdxTemp]
aaiMvpIdx[iRefList][iRefIdxTemp] = pcCU->m_apiMVPIdx[eRefPicList][uiPartAddr]
// MVP候选数量存入CU中(执行xEstimateMvPredAMVP的结果),然后赋值给aaiMvpNum[iRefList][iRefIdxTemp]
aaiMvpNum[iRefList][iRefIdxTemp] = pcCU->m_apiMVPNum[eRefPicList][uiPartAddr]
AMVPInfo
另需要特别注意的是,在HM中,AMVP候选列表是用一个AMVPInfo
类的结构体进行存储和传递的
typedef struct _AMVPInfo
{
// MVP候选列表
TComMv m_acMvCand[ AMVP_MAX_NUM_CANDS ]; ///< array of motion vector predictor candidates
// 候选列表中有效候选数量
Int iN; ///< number of motion vector predictor candidates
#if MCTS_ENC_CHECK
// 候选列表中空域候选数量
UInt numSpatialMVPCandidates;
#endif
} AMVPInfo;
这个存储MVP候选列表信息的结构体,在xEstimateMvPredAMVP
中名为pcAMVPInfo
,在fillMVPCand
中名为pInfo
fillMVPCand
下面学习fillMVPCand
函数,该函数用于建立MVP候选列表。
该函数学习可参考博客:
HEVC代码学习30:fillMvpCand函数
由于在视频中经常出现一大片区域(比如同一物体)运动方向一样的情况,因此运动向量MV不论在空域上还是时域上都有着很强的相关性,因此可以用当前PU在空域、时域上相邻的块的MV近似代替当前PU的MV以实现运动矢量预测(MVP)。
AMVP技术及将理论上与当前PU的MV最可能相近的空域相邻块、时域相邻块的若干MV收录进MVP候选列表中,以供之后进行选择。而这个建立MVP候选列表并收录MVP的过程就是在fillMVPCand
函数中实现的。
建立顺序如下:
一、建立空域候选列表:
1、按顺序搜索左侧块A0-A1-scaled A0-scaled A1,只要有一个MV存在,写入候选列表,跳出进行下一步。
2、按顺序搜索上方块B0-B1-B2(-scaled B0-scaled B1-scaled B2),只要有一个MV存在,写入候选列表,跳出进行下一步。而其中scaled B0-scaled B1-scaled B2只有当A0和A1的MV都不存在时,才进行搜索。
3、空域候选列表长度为2时,即左侧和上方各选出了一个候选,则对两者进行比较,相同时进行合并。
4、空域候选列表建立完成。
二、当空域候选数量少于2个,且启用时域候选时,建立时域候选列表。
三、如果空域+时域候选个数少于2,则使用(0,0)补足。
fillMVPCand
代码如下:
Void TComDataCU::fillMvpCand ( const UInt partIdx, const UInt partAddr, const RefPicList eRefPicList, const Int refIdx, AMVPInfo* pInfo ) const
{
pInfo->iN = 0; // 候选数量先置零
if (refIdx < 0)
{
#if MCTS_ENC_CHECK
pInfo->numSpatialMVPCandidates = 0; // 空域候选数量先置零
#endif
return;
}
//-- Get Spatial MV
// 获取相邻块地址
UInt partIdxLT, partIdxRT, partIdxLB;
deriveLeftRightTopIdx( partIdx, partIdxLT, partIdxRT );
deriveLeftBottomIdx( partIdx, partIdxLB );
Bool isScaledFlagLX = false; /// variable name from specification; true when the PUs below left or left are available (availableA0 || availableA1).
{
UInt idx;
const TComDataCU* tmpCU = getPUBelowLeft(idx, partIdxLB); // 获取左下角CU
isScaledFlagLX = (tmpCU != NULL) && (tmpCU->isInter(idx));
if (!isScaledFlagLX)
{
tmpCU = getPULeft(idx, partIdxLB);
isScaledFlagLX = (tmpCU != NULL) && (tmpCU->isInter(idx));
}
}
// Left predictor search
// 获取左侧块MV
if (isScaledFlagLX)
{
Bool bAdded = xAddMVPCandUnscaled( *pInfo, eRefPicList, refIdx, partIdxLB, MD_BELOW_LEFT); // 获取A0的MV
if (!bAdded)
{
bAdded = xAddMVPCandUnscaled( *pInfo, eRefPicList, refIdx, partIdxLB, MD_LEFT ); // 获取A1的MV
if(!bAdded)
{
bAdded = xAddMVPCandWithScaling( *pInfo, eRefPicList, refIdx, partIdxLB, MD_BELOW_LEFT); // 获取scaled A0的MV
if (!bAdded)
{
xAddMVPCandWithScaling( *pInfo, eRefPicList, refIdx, partIdxLB, MD_LEFT ); // 获取scaled A1的MV
}
}
}
}
// Above predictor search
{
Bool bAdded = xAddMVPCandUnscaled( *pInfo, eRefPicList, refIdx, partIdxRT, MD_ABOVE_RIGHT);
if (!bAdded)
{
bAdded = xAddMVPCandUnscaled( *pInfo, eRefPicList, refIdx, partIdxRT, MD_ABOVE);
if(!bAdded)
{
xAddMVPCandUnscaled( *pInfo, eRefPicList, refIdx, partIdxLT, MD_ABOVE_LEFT);
}
}
}
if(!isScaledFlagLX)
{
Bool bAdded = xAddMVPCandWithScaling( *pInfo, eRefPicList, refIdx, partIdxRT, MD_ABOVE_RIGHT);
if (!bAdded)
{
bAdded = xAddMVPCandWithScaling( *pInfo, eRefPicList, refIdx, partIdxRT, MD_ABOVE);
if(!bAdded)
{
xAddMVPCandWithScaling( *pInfo, eRefPicList, refIdx, partIdxLT, MD_ABOVE_LEFT);
}
}
}
if ( pInfo->iN == 2 )
{
if ( pInfo->m_acMvCand[ 0 ] == pInfo->m_acMvCand[ 1 ] )
{
pInfo->iN = 1;
}
}
#if MCTS_ENC_CHECK
pInfo->numSpatialMVPCandidates = pInfo->iN;
#endif
// 获取时域MVP
if (pInfo->iN < AMVP_MAX_NUM_CANDS && getSlice()->getEnableTMVPFlag() )
{
// Get Temporal Motion Predictor
const UInt numPartInCtuWidth = m_pcPic->getNumPartInCtuWidth();
const UInt numPartInCtuHeight = m_pcPic->getNumPartInCtuHeight();
const Int refIdx_Col = refIdx;
TComMv cColMv;
UInt partIdxRB;
UInt absPartIdx;
deriveRightBottomIdx( partIdx, partIdxRB );
UInt absPartAddr = m_absZIdxInCtu + partAddr;
//---- co-located RightBottom Temporal Predictor (H) ---//
absPartIdx = g_auiZscanToRaster[partIdxRB];
Int ctuRsAddr = -1;
if ( ( ( m_pcPic->getCtu(m_ctuRsAddr)->getCUPelX() + g_auiRasterToPelX[absPartIdx] + m_pcPic->getMinCUWidth () ) < m_pcSlice->getSPS()->getPicWidthInLumaSamples () ) // image boundary check
&& ( ( m_pcPic->getCtu(m_ctuRsAddr)->getCUPelY() + g_auiRasterToPelY[absPartIdx] + m_pcPic->getMinCUHeight() ) < m_pcSlice->getSPS()->getPicHeightInLumaSamples() ) )
{
if ( ( absPartIdx % numPartInCtuWidth < numPartInCtuWidth - 1 ) && // is not at the last column of CTU
( absPartIdx / numPartInCtuWidth < numPartInCtuHeight - 1 ) ) // is not at the last row of CTU
{
absPartAddr = g_auiRasterToZscan[ absPartIdx + numPartInCtuWidth + 1 ];
ctuRsAddr = getCtuRsAddr();
}
else if ( absPartIdx % numPartInCtuWidth < numPartInCtuWidth - 1 ) // is not at the last column of CTU But is last row of CTU
{
absPartAddr = g_auiRasterToZscan[ (absPartIdx + numPartInCtuWidth + 1) % m_pcPic->getNumPartitionsInCtu() ];
}
else if ( absPartIdx / numPartInCtuWidth < numPartInCtuHeight - 1 ) // is not at the last row of CTU But is last column of CTU
{
absPartAddr = g_auiRasterToZscan[ absPartIdx + 1 ];
ctuRsAddr = getCtuRsAddr() + 1;
}
else //is the right bottom corner of CTU
{
absPartAddr = 0;
}
}
if ( ctuRsAddr >= 0 && xGetColMVP( eRefPicList, ctuRsAddr, absPartAddr, cColMv, refIdx_Col ) )
{
pInfo->m_acMvCand[pInfo->iN++] = cColMv;
}
else
{
UInt uiPartIdxCenter;
xDeriveCenterIdx( partIdx, uiPartIdxCenter );
if (xGetColMVP( eRefPicList, getCtuRsAddr(), uiPartIdxCenter, cColMv, refIdx_Col ))
{
pInfo->m_acMvCand[pInfo->iN++] = cColMv;
}
}
//---- co-located RightBottom Temporal Predictor ---//
}
// 若候选列表不满,补零
while (pInfo->iN < AMVP_MAX_NUM_CANDS)
{
pInfo->m_acMvCand[pInfo->iN].set(0,0);
pInfo->iN++;
}
return ;
}
可见,最终构建的MVP候选列表存入了上述的名为pInfo
的AMVPInfo
结构体中,并返回给xEstimateMvPredAMVP
的pcAMVPInfo
。
另外,从相邻块获取MV的函数分别为xAddMVPCandUnscaled
以及xAddMVPCandWithScaling
。前者为获取A0~B2 MV的函数,后者为获取scaled A0~B2的函数。
文章浏览阅读4k次,点赞11次,收藏35次。目录背景虚拟PWM库特性源码介绍头文件 virtual_pwm.h源文件 vir_pwm.c使用说明背景现在的主流MCU都支持硬件PWM输出,以STM32F103为例,通用定时器可以支持4路占空比可调的PWM输出,高级定时器可以支持4路带互补输出的PWM输出。硬件产生PWM,具有稳定可靠、执行效率高的特点。但是,硬件产生的PWM也有一些限制,例如:1.输出引脚位置固定,PCB连线可能会不方便;2.输出引脚的数量有限,在一些需要多通道输出的应用中(如多路控温)会占用过多定时器。虚拟PWM库特性由于项_一个定时器输出多路pwm
文章浏览阅读4.9k次。目录一.使用内置tomcat启动二.使用外置tomcat启动三.Tomcat顶层结构图一.使用内置tomcat启动我们知道 springboot项目内置了 tomcat 服务器,表现在pom.xml中<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></depend_内嵌tomcat和外部tomcat
文章浏览阅读567次,点赞9次,收藏18次。authors : '【美】丹尼尔·戈尔曼\\n【美】理查德·博亚特兹\\n【美】安妮·麦基'authors : '【美】乔治·阿克洛夫\\n【美】罗伯特·席勒'authors : '【英】蒂姆·维卡利,萨菲娜·德福奇 等'title : '大谋小计五十年:诸葛亮传(全五册)',title : '周浩晖推理悬疑经典集(共10册)',authors : '【英】吉尔伯特·基思·切斯特顿'title : '春秋战国真有趣(全六册)',authors : '【美】马里奥·普佐'
文章浏览阅读1.4k次。当前顶点状态当一个顶点数组被定义,但是数据还不可用时,当前值常用于联合顶点辅助的数据。当前值可能在任何时候被独占命令改变。使用以下的命令可设置当前的RGBA颜色值。void Color4{xf}(T red, T green, T blue, T alpha);void Color4ub(ubyte red, ubyte green, ubyte blue, uby_查看当前顶点在哪个顶点组
文章浏览阅读603次。(完结篇)实现自动行走的功能(Move类)类成员与构造函数private long time;private ImageView imageView;private int direID; double x; double maxx; double width; Rectangle2D screenBounds = Screen.getPrimary().getVisualBounds(); Stage stage;private EventListener lis
文章浏览阅读3.9w次,点赞39次,收藏182次。AnacondaAnaconda指的是一个开源的Python发行版本,其包含了conda、Python等180多个科学包及其依赖项。因为包含了大量的科学包,Anaconda的下载文件比较大(约531 MB),如果只需要某些包,或者需要节省带宽或存储空间,也可以使用Miniconda这个较小的发行版(仅包含conda和Python)。Conda是一个开源的包、环境管理器,可以用于在同一个机器上安装不同版本的软件包及其依赖,并能够在不同的环境之间切换。Anaconda包括Conda、Pyth..._anaconda环境变量
文章浏览阅读408次。 区块链是分布式数据存储、点对点传输、共识机制、加密算法等计算机技术的新型应用模式,可以用于登记与发行数字化资产、产权凭证等,如比特币。实质上,区块链作为比特币的底层技术和基础架构,可以理解为为了保证虚拟交易时数据的全用户可知并认同、不可篡改性、记录的合理修改性等等问题而存在的一个储存加密货币的交易记录的公共帐本。 其中分布式数据存储即为,将数据分散存储到多个可进行数据..._metis 区块链
文章浏览阅读8.8k次,点赞4次,收藏8次。``` user_id create_time0 38441 15410016021 38442 15410016642 38443 15410017443 38444 15410019264 38445 15410020125 38446 15410024136 38447 15410..._pandas to_datetime 北京时间
文章浏览阅读1.6k次。一、创建数据库mysql> create database news character set utf8;Query OK, 0 rows affected (0.09 sec)二、创建用户mysql> create user ‘news’@‘39.15.16.14’ identified by ‘123news’;Query OK, 0 rows affected (0.09 sec)三、授权用户mysql> grant all privileges on news.* _mysql8.0 添加创建数据库权限
文章浏览阅读1.9k次。本文主要内容是sonarqube安装插件实现功能扩展,也对sonarqube的API功能进行了可用性确认。_sonar-pdf-plugin
文章浏览阅读6.4w次,点赞6次,收藏44次。基本操作传送门显示效果图添加效果图代码:<template> <div style="width:90%;margin: 20px auto;"> <!-- ref 用来拿到这个日历对象 defaultView 默认以月份方式显示 locale 语言 header 头部的按钮 buttonText 头部按钮的文字 plugins 插件 wee......_vue 行程插件
文章浏览阅读1.2w次,点赞5次,收藏8次。layout_centerHorizontal是相对于RelativeLayout的布局属性如果设置为true,就将该控价设置在相对于父控件水平居中的位置layout_gravity针对LinearLayout的一种控件对齐方式,可以把值设置成下列值:center_vertical、center_horizontal、center等等gravity控制控件内文字的对齐方式举个栗子:在写一个简单的