H.266/VVC专栏传送
上一篇:H.266/VVC-VTM代码学习-帧内预测13-ISP模式在estIntraPredLumaQT中的设定(1)
下一篇:H.266/VVC-VTM代码学习-帧内预测15-解码端解压缩decompressCtu函数及xReconIntraQT调用xIntraRecQT函数完成帧内预测重建
目录
- H.266/VVC专栏传送
- 前言
- 一、RD cost细选时ISP模式相关代码
- 1.estIntraPredLumaQT函数中RD cost细选时对为ISP预留的候选模式进行处理
- 2.xSortISPCandList函数准备使用RD cost测试的潜在帧内模式候选列表(入口在1中)
- 3.xGetNextISPMode函数决定ISP列表中的哪个模式可以被全RD测试(入口在1中)
- 4.xFinishISPModes函数在水平和垂直划分均终止时,为下个LFNST index准备(入口在3中)
- 5.xSortISPCandListLFNST函数为LFNST index大于0的情况构造模式候选列表(入口在4中)
- 6.xFindAlreadyTestedNearbyIntraModes函数在当前模式附近寻找已测试的模式以获得当前未测试模式的分区数(入口在3中)
前言
VTM是H.266/VVC视频编码标准的参考软件,研究VTM代码给研究人员解释了VVC编码标准的详细标准规范与细节。
本文是笔者对VTM代码的一点学习记录,成文于笔者刚开始接触VVC期间,期间很多概念和理论框架还很不成熟,若文中存在错误欢迎批评指正,也欢迎广大视频编码学习者沟通交流、共同进步。
VTM代码的下载及编译请参考博文:
【视频编码学习】H.266/VVC参考软件VTM配置运行(VTM-6.0版本)
本文涉及的代码主要存在于工程下的Lib\EncoderLib\IntraSearch.cpp文件中。
一、RD cost细选时ISP模式相关代码
1.estIntraPredLumaQT函数中RD cost细选时对为ISP预留的候选模式进行处理
下面的代码片段摘自estIntraPredLumaQT函数中RD cost细选时对uiRdModeList进行遍历的循环内部。
当前遍历到的候选模式是为ISP预留的模式时:
- 若当前是候选列表中第一个ISP模式:调用xSortISPCandList函数,准备使用RD cost测试的潜在帧内模式候选列表。
- xGetNextISPMode函数决定ISP列表中的哪个模式可以被全RD测试。
- 若当前模式ISP模式仍为为ISP预留的模式,则跳过对此模式的RD测试
- 载入当前模式,准备进行RD cost检查
//若不适用BDPCM,且当前模式ISP的模式为4 即如果是为ISP预留的模式
if (!cu.bdpcmMode && uiRdModeList[mode].ispMod == INTRA_SUBPARTITIONS_RESERVED)
{
//若当前模式号为非ISP模式的数量(即候选列表中第一个ISP模式)
if (mode == numNonISPModes)
{
//若使用快速ISP
if (m_pcEncCfg->getUseFastISP())
{
m_modeCtrl->setBestPredModeDCT2(uiBestPUMode.modeId);
}
//排列ISP模式列表
if (!xSortISPCandList(bestCurrentCost, csBest->cost, uiBestPUMode))
{
break;
}
}
//决定ISP列表中的哪个模式可以被全RD测试
xGetNextISPMode(uiRdModeList[mode], (mode > 0 ? &uiRdModeList[mode - 1] : nullptr), Size(width, height));
//若当前模式的ISP模式是4(即为ISP预留的位置)
if (uiRdModeList[mode].ispMod == INTRA_SUBPARTITIONS_RESERVED)
{
continue;
}
//将当前模式载入,接下来进行RD cost检查
cu.lfnstIdx = m_curIspLfnstIdx;
uiOrgMode = uiRdModeList[mode];
}
2.xSortISPCandList函数准备使用RD cost测试的潜在帧内模式候选列表(入口在1中)
- 若使用快速ISP,则检查ISP模式的测试能否被取消。最佳非ISP的cost大于目前最佳cost*1.4时取消ISP模式的测试;获取bestISPModeInRelCU失败时取消ISP模式的测试(不失败时成功获取Rel CU的最佳ISP模式)。
- 遍历m_ispCandListHor,设置模式isp类型为水平分块。将m_ispCandListHor保存到origHadList后清空m_ispCandListHor和m_ispCandListVer。
- 得到最佳常规模式。
- 重新创建m_ispCandListHor,将原ISP测试模式列表的第一个模式作为参考模式,之后加入m_ispCandListHor的所有模式的MIP flag、mRefId、ispMode都按参考模式设置。先将RelCU中的最优ISP模式放入,再将PLANAR放入,再将最佳常规模式放入,再遍历常规模式列表将非DC的模式放入,最后放入DC模式。
- 更新ISP测试模式数量后,遍历原ISP测试模式列表,将当中的三个没加入现ISP测试模式列表的模式加入。
- 若使用fastISP且RelCU存在最佳ISP模式,将ISP测试模式列表长度设置为1。
- 将水平ISP候选模式列表复制到垂直ISP候选模式列表。将ISP测试列表中所有模式重置为未测试过。
//It prepares the list of potential intra modes candidates that will be tested using RD costs
//准备使用RD cost测试的潜在帧内模式候选列表
bool IntraSearch::xSortISPCandList(double bestCostSoFar, double bestNonISPCost, ModeInfo bestNonISPMode)
{
int bestISPModeInRelCU = -1;
m_modeCtrl->setStopNonDCT2Transforms(false);
//如果使用快速ISP
//we check if the ISP tests can be cancelled
//检查ISP测试能否被取消
if (m_pcEncCfg->getUseFastISP())
{
double thSkipISP = 1.4;
//最佳非ISP的cost大于目前最佳cost*1.4时
if (bestNonISPCost > bestCostSoFar * thSkipISP)
{
//splitIdx = 0 and 1 0代表水平分块,1代表垂直分块
for (int splitIdx = 0; splitIdx < NUM_INTRA_SUBPARTITIONS_MODES - 1; splitIdx++)
{
for (int j = 0; j < NUM_LFNST_NUM_PER_SET; j++)
{
//标记分块已完成
m_ispTestedModes[j].splitIsFinished[splitIdx] = true;
}
}
//使该函数返回false,停止RD cost细选,不再对ISP进行测试
return false;
}
//获取bestISPModeInRelCU
if (!updateISPStatusFromRelCU(bestNonISPCost, bestNonISPMode, bestISPModeInRelCU))
{
//使该函数返回false,停止RD cost细选,不再对ISP进行测试
return false;
}
}//if (m_pcEncCfg->getUseFastISP())
//遍历ISP候选模式列表,设置模式isp类型为水平分块
for (int k = 0; k < m_ispCandListHor.size(); k++)
{
//we set the correct ISP split type value
m_ispCandListHor.at(k).ispMod = HOR_INTRA_SUBPARTITIONS;
}
// save the original hadamard list of regular intra
//将原始常规帧内候选列表保存
auto origHadList = m_ispCandListHor;
bool modeIsInList[NUM_LUMA_MODE] = { false };
//清空ISP候选列表
m_ispCandListHor.clear();
m_ispCandListVer.clear();
// we sort the normal intra modes according to their full RD costs
//将常规帧内模式根据其RD cost进行排序
std::sort(m_regIntraRDListWithCosts.begin(), m_regIntraRDListWithCosts.end(), ModeInfoWithCost::compareModeInfoWithCost);
// we get the best angle from the regular intra list
//得到最佳常规角度模式
int bestNormalIntraAngle = -1;
for (int modeIdx = 0; modeIdx < m_regIntraRDListWithCosts.size(); modeIdx++)
{
if (bestNormalIntraAngle == -1 && m_regIntraRDListWithCosts.at(modeIdx).modeId > DC_IDX)
{
bestNormalIntraAngle = m_regIntraRDListWithCosts.at(modeIdx).modeId;
break;
}
}
//模式1设为PLANAR模式号
int mode1 = PLANAR_IDX;
//模式2设为最佳常规角度模式号
int mode2 = bestNormalIntraAngle;
//参考模式是原候选列表中的最佳模式
ModeInfo refMode = origHadList.at(0);
//目标位置设为ISP候选列表
auto* destListPtr = &m_ispCandListHor;
//List creation
if (m_pcEncCfg->getUseFastISP() && bestISPModeInRelCU != -1)
{
//将RelCU中的最优ISP模式放入当前CU的ISP候选模式列表
destListPtr->push_back(
ModeInfo(refMode.mipFlg, refMode.mipTrFlg, refMode.mRefId, refMode.ispMod, bestISPModeInRelCU));
modeIsInList[bestISPModeInRelCU] = true;
}
// Planar
//若PLANAR不在当前CU的ISP候选模式列表中
if (!modeIsInList[mode1])
{
//将PLANAR加入
destListPtr->push_back(ModeInfo(refMode.mipFlg, refMode.mipTrFlg, refMode.mRefId, refMode.ispMod, mode1));
modeIsInList[mode1] = true;
}
// Best angle in regular intra
//若最佳常规模式不在当前CU的ISP候选模式列表中
if (mode2 != -1 && !modeIsInList[mode2])
{
//将最佳常规模式加入
destListPtr->push_back(ModeInfo(refMode.mipFlg, refMode.mipTrFlg, refMode.mRefId, refMode.ispMod, mode2));
modeIsInList[mode2] = true;
}
// Remaining regular intra modes that were full RD tested (except DC, which is added after the angles from regular intra)
//其余经过完整RD测试的常规帧内模式(DC除外,它是在与常规帧内角度之后添加的)
int dcModeIndex = -1;
for (int remModeIdx = 0; remModeIdx < m_regIntraRDListWithCosts.size(); remModeIdx++)
{
int currentMode = m_regIntraRDListWithCosts.at(remModeIdx).modeId;
//没添加过
if (currentMode != mode1 && currentMode != mode2 && !modeIsInList[currentMode])
{
//非DC模式
if (currentMode > DC_IDX)
{
//将当前模式加入
destListPtr->push_back(ModeInfo(refMode.mipFlg, refMode.mipTrFlg, refMode.mRefId, refMode.ispMod, currentMode));
modeIsInList[currentMode] = true;
}
//DC模式
else if (currentMode == DC_IDX)
{
//将DC模式在候选常规模式列表中的索引记录
dcModeIndex = remModeIdx;
}
}
}
// DC is added after the angles from regular intra
//将DC模式加入(前提是常规候选模式列表中有DC)
if (dcModeIndex != -1 && !modeIsInList[DC_IDX])
{
destListPtr->push_back(ModeInfo(refMode.mipFlg, refMode.mipTrFlg, refMode.mRefId, refMode.ispMod, DC_IDX));
modeIsInList[DC_IDX] = true;
}
// We add extra candidates to the list that will only be tested if ISP is likely to win
//在列表中加入一些只有当ISP看上去会赢时才测试的额外的候选
for (int j = 0; j < NUM_LFNST_NUM_PER_SET; j++)
{
//更新ISP测试模式数量
m_ispTestedModes[j].numOrigModesToTest = (int)destListPtr->size();
}
const int addedModesFromHadList = 3;
int newModesAdded = 0;
//遍历原ISP候选列表,加入前三个没加入过的模式
for (int k = 0; k < origHadList.size(); k++)
{
//计数加入三个模式
if (newModesAdded == addedModesFromHadList)
{
break;
}
if (!modeIsInList[origHadList.at(k).modeId])
{
destListPtr->push_back( ModeInfo( refMode.mipFlg, refMode.mipTrFlg, refMode.mRefId, refMode.ispMod, origHadList.at(k).modeId ) );
newModesAdded++;
}
}
//若使用fastISP且RelCU存在最佳ISP模式
if (m_pcEncCfg->getUseFastISP() && bestISPModeInRelCU != -1)
{
//将候选列表设置长度为1
destListPtr->resize(1);
}
// Copy modes to other split-type list
//复制水平ISP候选到垂直ISP候选
m_ispCandListVer = m_ispCandListHor;
for (int i = 0; i < m_ispCandListVer.size(); i++)
{
m_ispCandListVer[i].ispMod = VER_INTRA_SUBPARTITIONS;
}
// Reset the tested modes information to 0
//重置已测试信息,全部设置为未测试过
for (int j = 0; j < NUM_LFNST_NUM_PER_SET; j++)
{
for (int i = 0; i < m_ispCandListHor.size(); i++)
{
m_ispTestedModes[j].clearISPModeInfo(m_ispCandListHor[i].modeId);
}
}
return true;
}
3.xGetNextISPMode函数决定ISP列表中的哪个模式可以被全RD测试(入口在1中)
该函数决定了ISP列表中哪些模式可以进行全RD测试。
- 若当前ISP的 LFNST index 为大于等于3,则说明所有 LFNST index 下的ISP已检查完毕,直接返回。
- 对当前 LFNST index 下的当前模式ISP划分模式进行设定:
- 若当前 LFNST index 下的水平和垂直划分均未结束,则根据下述原则选择当前划分模式:① 若候选模式中当前模式的上一模式存在,则与上一模式的ISP划分类型相反。② 若候选模式中当前模式的上一模式不存在,则当前模式的ISP划分类型设置为水平。
- 若当前 LFNST index 下的水平或垂直划分结束,则将当前模式的ISP划分类型设置为未结束的划分类型。
- 若当前LFNST index 下的水平与垂直划分模式均划分结束,则调用xFinishISPModes()完成当前 LFNST index 的ISP设置进行下一 LFNST index 的准备,并返回。
- 根据上一步获得的当前ISP划分类型,获取最大划分区域数。
- 若当前 LFNST index > 0 且当前ISP最大划分区域数为1(不划分):
- 求出当前ISP划分的第一个测试模式下划分区域数。
- 若当前ISP划分的第一个测试模式下划分区域数小于当前ISP划分类型下最大划分区域数,stopThisSplit标志位设为有效,并在此时检查若使用 fast ISP 且当前 LFNST index = 1 且当前ISP划分的第一个测试模式下划分区域数小于0,若满足条件则stopThisSplitAllLfnsts标志位设为有效。
- 若stopThisSplit标志位有效,则设置当前划分模式已完成,同时若当前 LFNST index = 1 且当前stopThisSplitAllLfnsts标志位有效,则标记 LFNST index = 2 时的当前划分模式也划分完成。并直接返回。
- 若当前 LFNST index = 0 且当前ISP 划分类型的测试模式数为2:
- 模式1设置为当前ISP划分类型下第一个测试模式(若为DC则设为-1),求得当前ISP划分类型下模式1的划分区域数(若为DC则设为-1)。模式2设置为当前ISP划分类型下第二个测试模式(若为DC则设为-1),求得当前ISP划分类型下模式2的划分区域数(若为DC则设为-1)。
- 模式1和模式2均不为DC时,若两模式分区数均小于当前ISP划分类型的最大分区数,则将stopThisSplit标志位置有效,且此时若当前 LFNST index = 0 且两模式分区数均小于当前ISP划分类型的最大分区数-1,则将stopThisSplitForAllLFNSTs标志位置有效。
- 模式1和模式2均不为DC时,若两模式分区数不同时小于当前ISP划分类型的最大分区数,则计算两模式的 RD cost 若两模式cost中存在正无穷,则stopThisSplit标志位置有效。
- stopThisSplit标志位无效时,求与当前ISP划分类型相反划分类型下模式2的分区数量,若模式2不是DC且目前最佳划分模式不是当前ISP划分类型:① 若另一划分类型下模式2的分区数量大于当前划分类型下模式2的分区数量,stopThisSplit标志位置有效。② 若另一划分类型下模式2的分区数量等于当前划分类型下模式2的分区数量:Ⅰ. 相等且等于当前 LFNST index 下最大分区数时,若当前ISP划分下模式2的cost为正无穷或乘1.3后大于相反划分模式的模式2cost,则stopThisSplit标志位置有效。Ⅱ. 相等且不等于当前 LFNST index 下最大分区数时,若模式1不为DC且另一划分情况下模式1的分区数大于模式1下的最佳划分块数,则stopThisSplit标志位置有效。
- 若stopThisSplit标志位置有效则设置当前划分模式已完成,同时若当前stopThisSplitAllLfnsts标志位有效,则标记 LFNST index = 1和2 时的当前划分模式也划分完成。并直接返回。
- 若当前ISP划分类型最大分区数大于2,且当前 LFNST index > 0 或当前候选的模式不为DC且测试模式数量大于1,调用xFindAlreadyTestedNearbyIntraModes函数在当前模式附近寻找已测试的模式,若找到的已测试模式分区数大于numSubPartsLimit(samples >= 256:最大分区数-1,samples < 256:2),则将该函数入口处当前测试模式替换为当前模式。
- 若不满足上一步的条件,则记录当前ISP划分模式已完成。
// It decides which modes from the ISP lists can be full RD tested
//决定了ISP列表中的哪个模式可以被全RD测试
void IntraSearch::xGetNextISPMode(ModeInfo& modeInfo, const ModeInfo* lastMode, const Size cuSize)
{
static_vector<ModeInfo, FAST_UDI_MAX_RDMODE_NUM>* rdModeLists[2] = { &m_ispCandListHor, &m_ispCandListVer };
//现在ISP的LFNSTindex
const int curIspLfnstIdx = m_curIspLfnstIdx;
//如果现在ISP的LFNSTindex大于等于3,说明所有的LFNST索引已被检查
if (curIspLfnstIdx >= NUM_LFNST_NUM_PER_SET)
{
return;
}
ISPType nextISPcandSplitType;
//获取当前LFNSTindex下ISP的分块情况
auto& ispTestedModes = m_ispTestedModes[curIspLfnstIdx];
//水平方向是否划分终止(划分数为0)
const bool horSplitIsTerminated = ispTestedModes.splitIsFinished[HOR_INTRA_SUBPARTITIONS - 1];
//垂直方向是否划分终止(划分数为0)
const bool verSplitIsTerminated = ispTestedModes.splitIsFinished[VER_INTRA_SUBPARTITIONS - 1];
//若水平和垂直都未划分终止
if (!horSplitIsTerminated && !verSplitIsTerminated)
{
//若当前模式上个候选模式存在,则当前ISP候选的分块类型为与上个候选模式相反。若当前模式上个候选模式不存在,则当前ISP候选分块类型为水平
nextISPcandSplitType = !lastMode ? HOR_INTRA_SUBPARTITIONS : lastMode->ispMod == HOR_INTRA_SUBPARTITIONS ? VER_INTRA_SUBPARTITIONS : HOR_INTRA_SUBPARTITIONS;
}
//若垂直划分终止,水平划分未终止,则当前ISP候选的分块类型为水平划分
else if (!horSplitIsTerminated && verSplitIsTerminated)
{
nextISPcandSplitType = HOR_INTRA_SUBPARTITIONS;
}
//若水平划分终止,垂直划分未终止,则当前ISP候选的分块类型为垂直划分
else if (horSplitIsTerminated && !verSplitIsTerminated)
{
nextISPcandSplitType = VER_INTRA_SUBPARTITIONS;
}
//若水平和垂直划分均终止,为下个LFNSTindex准备
else
{
xFinishISPModes();
// no more modes will be tested
return;
}
//当前ISP候选的分块类型下最大划分区域数
int maxNumSubPartitions = ispTestedModes.numTotalParts[nextISPcandSplitType - 1];
// We try to break the split here for lfnst > 0 according to the first mode
//我们尝试根据第一种模式在此处跳出lfnst> 0的拆分
//若当前ISP的LFNSTindex > 0,且当前ISP候选划分模式对应的测试模式数量为1
if (curIspLfnstIdx > 0 && ispTestedModes.numTestedModes[nextISPcandSplitType - 1] == 1)
{
//获取当前ISP候选的划分类型下第一个测试模式
int firstModeThisSplit = ispTestedModes.getTestedIntraMode(nextISPcandSplitType, 0);
//获取当前ISP候选划分类型第一个测试模式下的划分区域数
int numSubPartsFirstModeThisSplit = ispTestedModes.getNumCompletedSubParts(nextISPcandSplitType, firstModeThisSplit);
CHECK(numSubPartsFirstModeThisSplit < 0, "wrong number of subpartitions!");
//停止当前划分的标志位初始化为false
bool stopThisSplit = false;
//停止在所有LFNSTindex下的当前划分的标志位初始化为false
bool stopThisSplitAllLfnsts = false;
//若当前ISP候选划分类型第一个测试模式下的划分区域数 小于 当前ISP候选划分类型下的最大划分区域数
if (numSubPartsFirstModeThisSplit < maxNumSubPartitions)
{
//停止当前划分
stopThisSplit = true;
//若使用fast ISP,且当前LFNSTindex为1,且当前ISP候选划分类型的第一个测试模式下的划分区域数 小于0
if (m_pcEncCfg->getUseFastISP() && curIspLfnstIdx == 1 && numSubPartsFirstModeThisSplit < maxNumSubPartitions - 1)
{
//停止所有LFNSTindex下的当前划分
stopThisSplitAllLfnsts = true;
}
}
//若停止当前划分
if (stopThisSplit)
{
//标定当前划分模式已完成
ispTestedModes.splitIsFinished[nextISPcandSplitType - 1] = true;
//若停止在所有LFNSTindex下的当前划分
if (curIspLfnstIdx == 1 && stopThisSplitAllLfnsts)
{
//标记下一LFNSTindex下当前划分模式已完成
m_ispTestedModes[2].splitIsFinished[nextISPcandSplitType - 1] = true;
}
return;
}
}//if (curIspLfnstIdx > 0 && ispTestedModes.numTestedModes[nextISPcandSplitType - 1] == 1)
// We try to break the split here for lfnst = 0 or all lfnst indices according to the first two modes
//我们尝试根据前两种模式跳出划分模式在lfnst = 0或所有lfnst索引的拆分
//若当前ISP的LFNSTindex为0,且当前ISP候选划分模式对应的测试模式数量为2
if (curIspLfnstIdx == 0 && ispTestedModes.numTestedModes[nextISPcandSplitType - 1] == 2)
{
// Split stop criteria after checking the performance of previously tested intra modes
//停止当前划分的阈值设置为当前ISP候选分块类型下最大划分区域数
const int thresholdSplit1 = maxNumSubPartitions;
//停止当前划分的标志位初始化为false
bool stopThisSplit = false;
//停止在所有LFNSTindex下的当前划分的标志位初始化为false
bool stopThisSplitForAllLFNSTs = false;
//停止在所有LFNSTindex下的当前划分的阈值为当前ISP候选分块类型下最大划分区域数-1
const int thresholdSplit1ForAllLFNSTs = maxNumSubPartitions - 1;
//模式1为当前ISP候选分块类型的第一个测试模式
int mode1 = ispTestedModes.getTestedIntraMode((ISPType)nextISPcandSplitType, 0);
//若模式1为DC,设置为-1
mode1 = mode1 == DC_IDX ? -1 : mode1;
//模式1下的划分区域数
int numSubPartsBestMode1 = mode1 != -1 ? ispTestedModes.getNumCompletedSubParts((ISPType)nextISPcandSplitType, mode1) : -1;
//模式2为当前ISP候选分块类型的第二个测试模式
int mode2 = ispTestedModes.getTestedIntraMode((ISPType)nextISPcandSplitType, 1);
//若模式2为DC,设置为-1
mode2 = mode2 == DC_IDX ? -1 : mode2;
//模式2下的划分区域数
int numSubPartsBestMode2 = mode2 != -1 ? ispTestedModes.getNumCompletedSubParts((ISPType)nextISPcandSplitType, mode2) : -1;
// 1) The 2 most promising modes do not reach a certain number of sub-partitions
//两种最可能模式未达到一个确定数量的分区数
//两个模式都不是DC
if (numSubPartsBestMode1 != -1 && numSubPartsBestMode2 != -1)
{
//两模式的分区数都小于当前ISP候选分块类型下最大划分区域数
if (numSubPartsBestMode1 < thresholdSplit1 && numSubPartsBestMode2 < thresholdSplit1)
{
//停止这种划分
stopThisSplit = true;
//若当前LFNSTindex为0,且两模式的划分区域数都小于当前ISP候选分块类型下最大划分区域数-1
if (curIspLfnstIdx == 0 && numSubPartsBestMode1 < thresholdSplit1ForAllLFNSTs && numSubPartsBestMode2 < thresholdSplit1ForAllLFNSTs)
{
//停止当前划分在所有LFNSTindex下的应用
stopThisSplitForAllLFNSTs = true;
}
}
//两模式的分区数不同时小于当前ISP候选分块类型下最大划分区域数
//we stop also if the cost is MAX_DOUBLE for both modes
else
{
//计算两模式的RDcost
double mode1Cost = ispTestedModes.getRDCost(nextISPcandSplitType, mode1);
double mode2Cost = ispTestedModes.getRDCost(nextISPcandSplitType, mode2);
//若两模式中有无穷大RD cost
if (!(mode1Cost < MAX_DOUBLE || mode2Cost < MAX_DOUBLE))
{
//停止当前划分
stopThisSplit = true;
}
}
}//if (numSubPartsBestMode1 != -1 && numSubPartsBestMode2 != -1)
//若不停止当前划分
if (!stopThisSplit)
{
// 2) One split type may be discarded by comparing the number of sub-partitions of the best angle modes of both splits
//通过比较两个划分的最佳角度模式的分区数,可以丢弃一个分割类型
//与当前ISP候选划分类型相反的类型
ISPType otherSplit = nextISPcandSplitType == HOR_INTRA_SUBPARTITIONS ? VER_INTRA_SUBPARTITIONS : HOR_INTRA_SUBPARTITIONS;
//另一种划分模式下模式2的划分数量
int numSubPartsBestMode2OtherSplit = mode2 != -1 ? ispTestedModes.getNumCompletedSubParts(otherSplit, mode2) : -1;
//若模式2不为DC,且目前最佳分块模式不是当前ISP候选划分类型
if (numSubPartsBestMode2OtherSplit != -1 && numSubPartsBestMode2 != -1 && ispTestedModes.bestSplitSoFar != nextISPcandSplitType)
{
//另一种划分模式下模式2的划分数量 大于 当前ISP候选划分模式下模式2的划分数量
if (numSubPartsBestMode2OtherSplit > numSubPartsBestMode2)
{
//停止当前划分
stopThisSplit = true;
}
// both have the same number of subpartitions
//另一种划分模式下模式2的划分数量 等于 当前ISP候选划分模式下模式2的划分数量
else if (numSubPartsBestMode2OtherSplit == numSubPartsBestMode2)
{
// both have the maximum number of subpartitions, so it compares RD costs to decide
//比较RD cost
//若两种划分下模式2的分区数相同,且为最大分区数
if (numSubPartsBestMode2OtherSplit == maxNumSubPartitions)
{
//当前ISP候选划分下模式2的RDcost
double rdCostBestMode2ThisSplit = ispTestedModes.getRDCost(nextISPcandSplitType, mode2);
//另一种划分模式下模式2的RDcost
double rdCostBestMode2OtherSplit = ispTestedModes.getRDCost(otherSplit, mode2);
//阈值设定为1.3
double threshold = 1.3;
//若当前ISP候选划分下模式2的RDcost为正无穷,或乘1.3后大于另一划分模式下的模式2RDcost
if (rdCostBestMode2ThisSplit == MAX_DOUBLE || rdCostBestMode2OtherSplit < rdCostBestMode2ThisSplit * threshold)
{
//停止当前划分
stopThisSplit = true;
}
}
// none of them reached the maximum number of subpartitions with the best angle modes, so it compares the results with the the planar mode
//都没有达到最佳角度模式下的最大分区数量,因此将结果与PLANAR进行了比较
else
{
//另一划分情况下模式1的的划分块数
int numSubPartsBestMode1OtherSplit = mode1 != -1 ? ispTestedModes.getNumCompletedSubParts(otherSplit, mode1) : -1;
//模式1不为DC,且另一划分情况下模式1的划分块数 大于 当前ISP候选划分模式下模式1的划分数量
if (numSubPartsBestMode1OtherSplit != -1 && numSubPartsBestMode1 != -1 && numSubPartsBestMode1OtherSplit > numSubPartsBestMode1)
{
//停止当前划分
stopThisSplit = true;
}
}
}//else if (numSubPartsBestMode2OtherSplit == numSubPartsBestMode2)
}//if (numSubPartsBestMode2OtherSplit != -1 && numSubPartsBestMode2 != -1 && ispTestedModes.bestSplitSoFar != nextISPcandSplitType)
}//if (!stopThisSplit)
//如果停止当前划分
if (stopThisSplit)
{
//当前ISP候选划分模式设置为已完成
ispTestedModes.splitIsFinished[nextISPcandSplitType - 1] = true;
//若停止所有LFNSTindex下的当前ISP候选划分模式划分
if (stopThisSplitForAllLFNSTs)
{
for (int lfnstIdx = 1; lfnstIdx < NUM_LFNST_NUM_PER_SET; lfnstIdx++)
{
//将LFNSTindex为1和2时的当前ISP候选划分模式设置为已完成
m_ispTestedModes[lfnstIdx].splitIsFinished[nextISPcandSplitType - 1] = true;
}
}
return;
}//if (stopThisSplit)
}//if (curIspLfnstIdx == 0 && ispTestedModes.numTestedModes[nextISPcandSplitType - 1] == 2)
// Now a new mode is retrieved from the list and it has to be decided whether it should be tested or not
//现在,从列表中检索一种新模式,必须确定是否应该对其进行测试。
//候选在候选列表中的索引 小于 m_ispCandListHor or m_ispCandListVer 的尺寸
if (ispTestedModes.candIndexInList[nextISPcandSplitType - 1] < rdModeLists[nextISPcandSplitType - 1]->size())
{
//当前候选
ModeInfo candidate = rdModeLists[nextISPcandSplitType - 1]->at(ispTestedModes.candIndexInList[nextISPcandSplitType - 1]);
//下次是下一候选
ispTestedModes.candIndexInList[nextISPcandSplitType - 1]++;
// extra modes are only tested if ISP has won so far
//其他模式只有在ISP目前获胜的情况下测试
//若当前候选索引超过原本要测试的模式长度
if (ispTestedModes.candIndexInList[nextISPcandSplitType - 1] > ispTestedModes.numOrigModesToTest)
{
//若目前最佳划分类型与当前候选不同 或 目前最佳模式是PLANAR
if (ispTestedModes.bestSplitSoFar != candidate.ispMod || ispTestedModes.bestModeSoFar == PLANAR_IDX)
{
//当前LFNSTindex下的对应划分类型已划分完毕
ispTestedModes.splitIsFinished[nextISPcandSplitType - 1] = true;
return;
}
}
bool testCandidate = true;
// we look for a reference mode that has already been tested within the window and decide to test the new one according to the reference mode costs
//我们在窗口中寻找一种已经过测试的参考模式,并根据参考模式成本测试新模式
//若当前ISP候选的划分类型下最大划分区域数大于2,且当前ISP的LFNSTindex大于0 或 当前候选的模式不为DC且测试模式数量大于1
if (maxNumSubPartitions > 2 && (curIspLfnstIdx > 0 || (candidate.modeId >= DC_IDX && ispTestedModes.numTestedModes[nextISPcandSplitType - 1] >= 2)))
{
//参考的LFNSTindex初始化为-1
int refLfnstIdx = -1;
//角度窗尺寸为5
const int angWindowSize = 5;
int numSubPartsLeftMode, numSubPartsRightMode, numSubPartsRefMode, leftIntraMode = -1, rightIntraMode = -1;
//当前候选的模式为DC时,窗大小为1。角度模式下为5
int windowSize = candidate.modeId > DC_IDX ? angWindowSize : 1;
//CU采样点数量
int numSamples = cuSize.width << floorLog2(cuSize.height);
//分区数限制:若采样点数大于等于256:最大分区数-1, 若采样点数小于256:2
int numSubPartsLimit = numSamples >= 256 ? maxNumSubPartitions - 1 : 2;
//在当前帧内模式附近找到已检查过的模式
xFindAlreadyTestedNearbyIntraModes(curIspLfnstIdx, (int)candidate.modeId, &refLfnstIdx, &leftIntraMode, &rightIntraMode, (ISPType)candidate.ispMod, windowSize);
//若找到参考LFNSTindex
if (refLfnstIdx != -1 && refLfnstIdx != curIspLfnstIdx)
{
CHECK(leftIntraMode != candidate.modeId || rightIntraMode != candidate.modeId, "wrong intra mode and lfnstIdx values!");
//分块数与参考LFNSTindex下相同
numSubPartsRefMode = m_ispTestedModes[refLfnstIdx].getNumCompletedSubParts((ISPType)candidate.ispMod, candidate.modeId);
CHECK(numSubPartsRefMode <= 0, "Wrong value of the number of subpartitions completed!");
}
//未找到参考LFNSTindex
//分块数按照找到的同一LFNSTindex的窗内帧内模式设定
else
{
numSubPartsLeftMode = leftIntraMode != -1 ? ispTestedModes.getNumCompletedSubParts((ISPType)candidate.ispMod, leftIntraMode) : -1;
numSubPartsRightMode = rightIntraMode != -1 ? ispTestedModes.getNumCompletedSubParts((ISPType)candidate.ispMod, rightIntraMode) : -1;
numSubPartsRefMode = std::max(numSubPartsLeftMode, numSubPartsRightMode);
}
if (numSubPartsRefMode > 0)
{
// The mode was found. Now we check the condition
//测试当前候选的条件是参考模式分区数大于分区数限制(samples >= 256:最大分区数-1,samples < 256:2)
testCandidate = numSubPartsRefMode > numSubPartsLimit;
}
}
//若测试当前候选,则将当前候选装入modeInfo
if (testCandidate)
{
modeInfo = candidate;
}
}//if (ispTestedModes.candIndexInList[nextISPcandSplitType - 1] < rdModeLists[nextISPcandSplitType - 1]->size())
else
{
//the end of the list was reached, so the split is invalidated
//所有候选都测试后,当前划分已完成
ispTestedModes.splitIsFinished[nextISPcandSplitType - 1] = true;
}
}
4.xFinishISPModes函数在水平和垂直划分均终止时,为下个LFNST index准备(入口在3中)
- 当前LFNST索引已完成ISP模式测试,LFNST索引号+1。
- 若下一LFNST索引号为1(即刚完成的是 LFNST index = 0 的ISP模式测试),则检查 LFNST index = 1 或 2 时是否能进行ISP测试:即是否还有水平或垂直方向划分未终止的情况。
- 若LFNST index为1或2时能进行ISP测试,则调用xSortISPCandListLFNST()为LFNST index大于0的情况构造模式候选列表。
void IntraSearch::xFinishISPModes()
{
//Continue to the next lfnst index
//当前LFNST索引已经完成ISP模式设定,继续下个LFNST索引
m_curIspLfnstIdx++;
//下个LFNST index仍有效(小于3)
if (m_curIspLfnstIdx < NUM_LFNST_NUM_PER_SET)
{
//Check if LFNST is applicable
//检查LFNST是否适用
//若下面设定LFNST index = 1的ISP
if (m_curIspLfnstIdx == 1)
{
//能测试LFNST的标志初始化为false
bool canTestLFNST = false;
for (int lfnstIdx = 1; lfnstIdx < NUM_LFNST_NUM_PER_SET; lfnstIdx++)
{
//检查LFNST index为1或2时,水平方向或垂直方向是否有未终止的情况,若有,则canTestLFNST有效
canTestLFNST |= !m_ispTestedModes[lfnstIdx].splitIsFinished[HOR_INTRA_SUBPARTITIONS - 1] || !m_ispTestedModes[lfnstIdx].splitIsFinished[VER_INTRA_SUBPARTITIONS - 1];
}
if (canTestLFNST)
{
//Construct the intra modes candidates list for the lfnst > 0 cases
//为lfnst> 0情况构造帧内模式候选列表
xSortISPCandListLFNST();
}
}
}
}
5.xSortISPCandListLFNST函数为LFNST index大于0的情况构造模式候选列表(入口在4中)
本函数通过检查 LFNST index = 0 时的RD cost来建立 LFNST index > 0 的帧内模式候选列表。
遍历水平和垂直划分两种划分状态,若当前划分状态的分区未完成且 LFNST index = 0 时的测试ISP模式数大于1时:
- 记录对应分区类型的候选模式列表中第二个模式(第一个模式固定为PLANAR)的模式号、分区数量、RD cost,以此初始化最佳模式。
- 循环遍历对应分区类型的全部候选模式,若某个候选模式的分区数大于第二个模式的分区数,或某个候选模式的cost小于第二个模式的cost,则更新当前候选模式为最佳模式。
- 若第二个模式为DC,且为最佳模式,则不做任何处理。否则,将最佳模式放至候选模式列表的第一位,原第一位到最佳模式前的所有模式顺延。
void IntraSearch::xSortISPCandListLFNST()
{
//It resorts the list of intra mode candidates for lfnstIdx > 0 by checking the RD costs for lfnstIdx = 0
//通过检查lfnstIdx = 0的RD cost来建立lfnstIdx> 0的帧内模式候选列表
//LFNST index = 0 时的测试模式作为ISP测试模式参考
ISPTestedModesInfo& ispTestedModesRef = m_ispTestedModes[0];
//遍历分区索引,0水平1垂直
for (int splitIdx = 0; splitIdx < NUM_INTRA_SUBPARTITIONS_MODES - 1; splitIdx++)
{
ISPType ispMode = splitIdx ? VER_INTRA_SUBPARTITIONS : HOR_INTRA_SUBPARTITIONS;
//若当前分区索引(水平或垂直)分区未终止,且LFNST index为0时的测试IPS模式数大于1
if (!m_ispTestedModes[m_curIspLfnstIdx].splitIsFinished[splitIdx] && ispTestedModesRef.testedModes[splitIdx].size() > 1)
{
//对应候选列表
auto& candList = ispMode == HOR_INTRA_SUBPARTITIONS ? m_ispCandListHor : m_ispCandListVer;
//最佳模式ID(非角度为-1)(candList[0]恒为PLANAR)
int bestModeId = candList[1].modeId > DC_IDX ? candList[1].modeId : -1;
//最佳模式分区数
int bestSubParts = candList[1].modeId > DC_IDX ? ispTestedModesRef.getNumCompletedSubParts(ispMode, bestModeId) : -1;
//最佳模式RD cost
double bestCost = candList[1].modeId > DC_IDX ? ispTestedModesRef.getRDCost(ispMode, bestModeId) : MAX_DOUBLE;
//遍历候选模式列表
for (int i = 0; i < candList.size(); i++)
{
//候选分区数
const int candSubParts = ispTestedModesRef.getNumCompletedSubParts(ispMode, candList[i].modeId);
//候选cost
const double candCost = ispTestedModesRef.getRDCost(ispMode, candList[i].modeId);
//若当前模式的候选分区数大于最佳分区数,或当前模式的cost小于最佳cost
if (candSubParts > bestSubParts || candCost < bestCost)
{
//更新最佳
bestModeId = candList[i].modeId;
bestCost = candCost;
bestSubParts = candSubParts;
}
}
//排除第二个模式为DC,且为最佳模式的情况
if (bestModeId != -1)
{
//最佳模式不为候选列表第一个模式
if (bestModeId != candList[0].modeId)
{
auto prevMode = candList[0];
//将候选列表第一个模式设置为最优模式
candList[0].modeId = bestModeId;
//从第二个模式到最优模式原位置设置为原第一个模式到最优模式的前一个
for (int i = 1; i < candList.size(); i++)
{
auto nextMode = candList[i];
candList[i] = prevMode;
if (nextMode.modeId == bestModeId)
{
break;
}
prevMode = nextMode;
}
}//if (bestModeId != candList[0].modeId)
}//if (bestModeId != -1)
}//if (!m_ispTestedModes[m_curIspLfnstIdx].splitIsFinished[splitIdx] && ispTestedModesRef.testedModes[splitIdx].size() > 1)
}//for (int splitIdx = 0; splitIdx < NUM_INTRA_SUBPARTITIONS_MODES - 1; splitIdx++)
}
6.xFindAlreadyTestedNearbyIntraModes函数在当前模式附近寻找已测试的模式以获得当前未测试模式的分区数(入口在3中)
本函数在当前帧内模式附近找到已测试的模式:
- 若在其他LFNSTindex下的同样划分模式的当前模式已被测试过,则记录LFNSTindex为参考LFNSTindex,左侧和右侧帧内模式设置为当前模式。
- 若1中找不到,则在当前LFNSTindex下指定大小窗内寻找已被测试的模式,记录为左侧或右侧帧内模式,记录当前LFNSTindex为参考LFNSTinde。
//在当前模式附近寻找已经测试过的模式。
void IntraSearch::xFindAlreadyTestedNearbyIntraModes(int lfnstIdx, int currentIntraMode, int* refLfnstIdx, int* leftIntraMode, int* rightIntraMode, ISPType ispOption, int windowSize)
{
bool leftModeFound = false, rightModeFound = false;
*leftIntraMode = -1;
*rightIntraMode = -1;
*refLfnstIdx = -1;
//ISP划分对应的index
const unsigned st = ispOption - 1;
//first we check if the exact intra mode was already tested for another lfnstIdx value
//首先,我们检查是否已经为另一个lfnstIdx测试了确切的帧内模式
//若当前LFNSTindex大于0
if (lfnstIdx > 0)
{
bool sameIntraModeFound = false;
//若当前LFNSTindex为2,且在LFNSTindex为1时已检查过当前帧内模式
if (lfnstIdx == 2 && m_ispTestedModes[1].modeHasBeenTested[currentIntraMode][st])
{
//找到了同样的帧内模式已被检查过
sameIntraModeFound = true;
//参考的LFNSTindex为1
*refLfnstIdx = 1;
}
//若在LFNSTindex为0时检查过当前帧内模式
else if (m_ispTestedModes[0].modeHasBeenTested[currentIntraMode][st])
{
//找到了同样的帧内模式已被检查过
sameIntraModeFound = true;
//参考的LFNSTindex为0
*refLfnstIdx = 0;
}
//若找到了同样的帧内模式已被检查过
if (sameIntraModeFound)
{
//左侧帧内模式设置为当前帧内模式
*leftIntraMode = currentIntraMode;
//右侧帧内模式设置为当前帧内模式
*rightIntraMode = currentIntraMode;
return;
}
}
//未找到同样的帧内模式已被检查过
//The mode has not been checked for another lfnstIdx value, so now we look for a similar mode within a window using the same lfnstIdx
//当前模式在其他LFNSTindex中未被检查过,所以现在我们在同一LFNSTindex下的窗内寻找相似的模式
//k取值从1到窗口大小
for (int k = 1; k <= windowSize; k++)
{
int off = currentIntraMode - 2 - k;
//off小于0时,左侧模式为67+off(67+当前模式-2-k),否则,左侧模式为当前模式-k
int leftMode = (off < 0) ? NUM_LUMA_MODE + off : currentIntraMode - k;
//当前模式为角度模式时,右侧模式为(当前模式-2+k)% 65 + 2,否则,右侧模式为PLANAR
int rightMode = currentIntraMode > DC_IDX ? (((int)currentIntraMode - 2 + k) % 65) + 2 : PLANAR_IDX;
//左侧模式与当前模式不相等 且 左侧模式在当前LFNSTindex和划分下被测试过,则左侧模式被找到
leftModeFound = leftMode != (int)currentIntraMode ? m_ispTestedModes[lfnstIdx].modeHasBeenTested[leftMode][st] : false;
//右侧模式与当前模式不相等 且 右侧模式在当前LFNSTindex和划分下被测试过,则右侧模式被找到
rightModeFound = rightMode != (int)currentIntraMode ? m_ispTestedModes[lfnstIdx].modeHasBeenTested[rightMode][st] : false;
//左侧或右侧模式被找到时,记录对应侧模式
if (leftModeFound || rightModeFound)
{
*leftIntraMode = leftModeFound ? leftMode : -1;
*rightIntraMode = rightModeFound ? rightMode : -1;
*refLfnstIdx = lfnstIdx;
break;
}
}
}
上一篇:H.266/VVC-VTM代码学习-帧内预测13-ISP模式在estIntraPredLumaQT中的设定(1)
下一篇:H.266/VVC-VTM代码学习-帧内预测15-解码端解压缩decompressCtu函数及xReconIntraQT调用xIntraRecQT函数完成帧内预测重建