H.266/VVC专栏传送

上一篇:H.266/VVC-VTM代码学习-帧内预测17-initIntraPatternChTypeISP函数初始化ISP的帧内预测
下一篇:H.266/VVC-VTM代码学习19-CU层确定测试模式函数initCULevel

目录

  • H.266/VVC专栏传送
    • 前言
    • 一、Adaptive QP
    • 二、代码详解
      • 1. 计算指定帧中所有量化层次下的 activity(在 encodePrep() 中被调用)
      • 2. 计算指定 CU 的 delta QP
      • 3. CU层计算得到 QP

前言

VTM是H.266/VVC视频编码标准的参考软件,研究VTM代码给研究人员解释了VVC编码标准的详细标准规范与细节。

本文是笔者对VTM代码的一点学习记录,成文于笔者刚开始接触VVC期间,期间很多概念和理论框架还很不成熟,若文中存在错误欢迎批评指正,也欢迎广大视频编码学习者沟通交流、共同进步。

VTM代码的下载及编译请参考博文:
【视频编码学习】H.266/VVC参考软件VTM配置运行(VTM-6.0版本)

一、Adaptive QP

自适应QP(Adaptive QP)是为每个 CU 自适应的选择 QP 以提升编码质量,由配置参数AdaptiveQP指定是否开启该功能。默认不开启。

自适应 QP 使用的 delta QP 计算原则是:对于平坦(即 activity 较小)CU 选择较大的 QP,对于变化较快(即 activity 较大)CU 选择较小的 QP。

CU 的 activity 由其亮度分量的方差计算得到。例如,对于一个 2Nx2N 的 CU ,首先计算其 4 个 NxN 的亮度子块的方差,然后由方差计算该 CU 的 activity:act=1.0+minVaract = 1.0 + minVaract=1.0+minVar其中,minVarminVarminVar 为 CU 四个子块方差中的最小值。

再利用下面几组公式即可得到 CU 对应的 delta QP:
norm=scale⋅act+actavgact+scale⋅actavgnorm = \frac {scale\cdot act + act_{avg}}{act + scale\cdot act_{avg}}norm=act+scale⋅actavg​scale⋅act+actavg​​其中:

  • actavgact_{avg}actavg​ 为当前 CU 所在量化层次下的所有量化区域的 average activity。
  • scale=2range6scale = 2^{\frac{range}{6}}scale=26range​ 其中 range 为可设置的参数,默认值为6。

再利用:
ΔQP=int(6⋅log⁡2norm)\Delta QP = int(6 \cdot \log_{2}norm)ΔQP=int(6⋅log2​norm)
得到 delta QP。

得到 delta QP 后,将 slice 层的 initial QP 加上 delta QP 作为 baseQP,在以 baseQP 为中心的区间内取所有 QP 加入 RD 测试列表。

二、代码详解

1. 计算指定帧中所有量化层次下的 activity(在 encodePrep() 中被调用)

Lib\EncoderLib\AQp.cpp →\rightarrow→ AQpPreanalyzer::preanalyze

void AQpPreanalyzer::preanalyze( Picture* pcEPic )
{// 获得当前帧对应的亮度分量原始值const CPelBuf lumaPlane = pcEPic->getOrigBuf().Y();// 当前帧亮度分量宽const int iWidth  = lumaPlane.width;// 当前帧亮度分量高const int iHeight = lumaPlane.height;// 当前帧亮度分量缓存中的 strideconst int iStride = lumaPlane.stride;// 遍历当前帧的自适应量化层次(aqlayer),size 为 m_picHeader.getCuQpDeltaSubdivIntra()/2+1for ( uint32_t d = 0; d < pcEPic->aqlayer.size(); d++ ){// 将指针移动到当前帧亮度分量原始值起始位置const Pel* pLineY = lumaPlane.bufAt( 0, 0);// 当前遍历到的自适应量化层次AQpLayer* pcAQLayer = pcEPic->aqlayer[d];// 当前量化层次下量化区域宽(sps.getMaxCUWidth() >> d)const uint32_t uiAQPartWidth = pcAQLayer->getAQPartWidth();// 当前量化层次下量化区域高(sps.getMaxCUWidth() >> d)const uint32_t uiAQPartHeight = pcAQLayer->getAQPartHeight();// 指针指向当前量化层次第一个单元double* pcAQU = &pcAQLayer->getQPAdaptationUnit()[0];// 用于存储量化区域内所有 CU 的 activity 之和double dSumAct = 0.0;// ---------------- 以下开始遍历当前帧在当前量化层次下的所有量化区域 -----------------// 量化区域在垂直方向上的起始位置for ( uint32_t y = 0; y < iHeight; y += uiAQPartHeight ){// 当前量化区域的高设定为量化区域高和当前帧剩余高度中的较小值const uint32_t uiCurrAQPartHeight = std::min(uiAQPartHeight, iHeight-y);// 量化区域在水平方向上的起始位置for ( uint32_t x = 0; x < iWidth; x += uiAQPartWidth, pcAQU++ ){// 当前量化区域的宽设定为量化区域宽和当前帧剩余宽度中的较小值const uint32_t uiCurrAQPartWidth = std::min(uiAQPartWidth, iWidth-x);// 当前量化区域的第一个原始像素值const Pel* pBlkY = &pLineY[x];// 长度为4的数组用于存储当前量化区域内4个子块区域分别的像素和uint64_t uiSum[4] = {0, 0, 0, 0};// 长度为4的数组用于存储当前量化区域内4个子块区域分别的像素平方和uint64_t uiSumSq[4] = {0, 0, 0, 0};// by用于遍历当前量化区域子块的高uint32_t by = 0;// 遍历当前量化区域的上半区域两个子块for ( ; by < uiCurrAQPartHeight>>1; by++ ){// bx用于遍历当前量化区域子块的宽uint32_t bx = 0;// 遍历当前量化区域的左上角子块for ( ; bx < uiCurrAQPartWidth>>1; bx++ ){// 累计量化区域第一子块的像素值uiSum  [0] += pBlkY[bx];// 累计量化区域第一子块的像素平方值uiSumSq[0] += pBlkY[bx] * pBlkY[bx];}// 遍历当前量化区域的右上角子块for ( ; bx < uiCurrAQPartWidth; bx++ ){// 累计量化区域第二子块的像素值uiSum  [1] += pBlkY[bx];// 累计量化区域第二子块的像素平方值uiSumSq[1] += pBlkY[bx] * pBlkY[bx];}// 累计下一行像素值pBlkY += iStride;}// 遍历当前量化区域的下半区域两个子块for ( ; by < uiCurrAQPartHeight; by++ ){// bx用于遍历当前量化区域子块的宽uint32_t bx = 0;// 遍历当前量化区域的左下角子块for ( ; bx < uiCurrAQPartWidth>>1; bx++ ){// 累计量化区域第三子块的像素值uiSum  [2] += pBlkY[bx];// 累计量化区域第三子块的像素平方值uiSumSq[2] += pBlkY[bx] * pBlkY[bx];}// 遍历当前量化区域的右下角子块for ( ; bx < uiCurrAQPartWidth; bx++ ){// 累计量化区域第三子块的像素值uiSum  [3] += pBlkY[bx];// 累计量化区域第三子块的像素平方值uiSumSq[3] += pBlkY[bx] * pBlkY[bx];}// 累计下一行像素值pBlkY += iStride;}// 不支持量化区域宽或高为奇数CHECK((uiCurrAQPartWidth&1)!=0,  "Odd part width unsupported");CHECK((uiCurrAQPartHeight&1)!=0, "Odd part height unsupported");// 量化区域子块的宽const uint32_t pixelWidthOfQuadrants  = uiCurrAQPartWidth >>1;// 量化区域子块的高const uint32_t pixelHeightOfQuadrants = uiCurrAQPartHeight>>1;// 量化区域子块像素数量const uint32_t numPixInAQPart         = pixelWidthOfQuadrants * pixelHeightOfQuadrants;// 用于存放当前量化区域4个子块方差中最小值double dMinVar = DBL_MAX;// 若子块大小不为0if (numPixInAQPart!=0){// 遍历4个子块for ( int i=0; i<4; i++){// 计算当前子块的像素平均值const double dAverage = double(uiSum[i]) / numPixInAQPart;// 计算当前子块的像素方差const double dVariance = double(uiSumSq[i]) / numPixInAQPart - dAverage * dAverage;// 记录四个子块的方差最小值dMinVar = std::min(dMinVar, dVariance);}}else{// 若字块大小为0,则方差最小值设为0dMinVar = 0.0;}// 当前量化区域的 activity 设置为 1+dMinVar const double dActivity = 1.0 + dMinVar;// 记录当前量化区域的 activity*pcAQU = dActivity;// 累计所有量化区域的 activity 之和dSumAct += dActivity;} // 遍历完当前水平方向的量化区域pLineY += iStride * uiCurrAQPartHeight;} // 遍历完当前帧的所有量化区域// 计算并记录当前量化层次下所有量化区域的平均 activityconst double dAvgAct = dSumAct / (pcAQLayer->getNumAQPartInWidth() * pcAQLayer->getNumAQPartInHeight());pcAQLayer->setAvgActivity( dAvgAct );} // 遍历完所有量化层次
}

2. 计算指定 CU 的 delta QP

Lib\EncoderLib\EncModeCtrl.cpp →\rightarrow→ EncModeCtrl::xComputeDQP

int EncModeCtrl::xComputeDQP( const CodingStructure &cs, const Partitioner &partitioner )
{// 当前帧Picture* picture    = cs.picture;// AQ 的深度(量化层次)为当前 CU 划分深度和最大划分层次的较小值unsigned uiAQDepth  = std::min( partitioner.currSubdiv/2, ( uint32_t ) picture->aqlayer.size() - 1 );// 当前 CU 对应的量化层次AQpLayer* pcAQLayer = picture->aqlayer[uiAQDepth];// 计算 scale 值 (m_pcEncCfg->getQPAdaptationRange()为可设置参数,默认值为6)double dMaxQScale   = pow( 2.0, m_pcEncCfg->getQPAdaptationRange() / 6.0 );// 获得当前帧当前量化层次下的 average activitydouble dAvgAct      = pcAQLayer->getAvgActivity();// 获得当前 CU 的 activitydouble dCUAct       = pcAQLayer->getActivity( cs.area.Y().topLeft() );// norm = (scale * act) / (act + scale * avgAct)double dNormAct     = ( dMaxQScale*dCUAct + dAvgAct ) / ( dCUAct + dMaxQScale*dAvgAct );// delta QP = log2(norm) * 6double dQpOffset    = log( dNormAct ) / log( 2.0 ) * 6.0;// 将 delta QP 四舍五入转为整数int    iQpOffset    = int( floor( dQpOffset + 0.49999 ) );return iQpOffset;
}

3. CU层计算得到 QP

Lib\EncoderLib\EncModeCtrl.cpp →\rightarrow→ EncModeCtrlMTnoRQT::initCULevel

  // baseQP 为 slice 级的 initial QPint baseQP = cs.baseQP;// 当前划分树不是separate tree(分离树)是 joint tree(联合树)或当前为亮度分量if (!partitioner.isSepTree(cs) || isLuma(partitioner.chType)){// 使用 adaptive QPif (m_pcEncCfg->getUseAdaptiveQP()){// baseQP 取 -(6 * (m_bitDepth[CHANNEL_TYPE_LUMA] - 8) 和 63 和 sliceQP + delta 中的中值baseQP = Clip3(-cs.sps->getQpBDOffset(CHANNEL_TYPE_LUMA), MAX_QP, baseQP + xComputeDQP(cs, partitioner));}
#if SHARP_LUMA_DELTA_QP// 另一种获得 delta QP 的机制if (m_pcEncCfg->getLumaLevelToDeltaQPMapping().isEnabled()){if (partitioner.currQgEnable()){m_lumaQPOffset = calculateLumaDQP (cs.getOrgBuf (clipArea (cs.area.Y(), cs.picture->Y())));}baseQP = Clip3 (-cs.sps->getQpBDOffset (CHANNEL_TYPE_LUMA), MAX_QP, baseQP - m_lumaQPOffset);}
#endif}// -------------------- 以下是在 base QP 附近设定 minQP 和 maxQP 再 RD 测试两者区间内所有取值 -------------int minQP = baseQP;int maxQP = baseQP;xGetMinMaxQP( minQP, maxQP, cs, partitioner, baseQP, *cs.sps, *cs.pps, CU_QUAD_SPLIT );bool checkIbc = true;if (partitioner.chType == CHANNEL_TYPE_CHROMA){checkIbc = false;}// Add coding modes here// NOTE: Working back to front, as a stack, which is more efficient with the container// NOTE: First added modes will be processed at the end.//// Add unit split modesif( !cuECtx.get<bool>( QT_BEFORE_BT ) ){for( int qp = maxQP; qp >= minQP; qp-- ){m_ComprCUCtxList.back().testModes.push_back( { ETM_SPLIT_QT, ETO_STANDARD, qp } );}}if( partitioner.canSplit( CU_TRIV_SPLIT, cs ) ){// add split modesfor( int qp = maxQP; qp >= minQP; qp-- ){m_ComprCUCtxList.back().testModes.push_back( { ETM_SPLIT_TT_V, ETO_STANDARD, qp } );}}if( partitioner.canSplit( CU_TRIH_SPLIT, cs ) ){// add split modesfor( int qp = maxQP; qp >= minQP; qp-- ){m_ComprCUCtxList.back().testModes.push_back( { ETM_SPLIT_TT_H, ETO_STANDARD, qp } );}}......

得到 maxQP 和 minQP 后,将该区间内所有 QP 形成的模式存入 m_ComprCUCtxList.testModes 中。

xCompressCU 函数通过下列代码,提取 m_ComprCUCtxList.testModes 中的待测试模式,并调用 xCheckRDCostIntra 函数,xCheckRDCostIntra 函数内部又调用 estIntraPredLumaQT 函数,进而分别对各个待选模式选择合适的 intra 模式。

  do{for (int i = compBegin; i < (compBegin + numComp); i++){ComponentID comID = jointPLT ? (ComponentID)compBegin : ((i > 0) ? COMPONENT_Cb : COMPONENT_Y);tempCS->prevPLT.curPLTSize[comID] = curLastPLTSize[comID];memcpy(tempCS->prevPLT.curPLT[i], curLastPLT[i], curLastPLTSize[comID] * sizeof(Pel));}// 获取m_ComprCUCtxList.testModesEncTestMode currTestMode = m_modeCtrl->currTestMode();...else if( currTestMode.type == ETM_INTRA ){if (slice.getSPS()->getUseColorTrans() && !CS::isDualITree(*tempCS)){bool skipSecColorSpace = false;skipSecColorSpace = xCheckRDCostIntra(tempCS, bestCS, partitioner, currTestMode, (m_pcEncCfg->getRGBFormatFlag() ? true : false));...}}   } while( m_modeCtrl->nextMode( *tempCS, partitioner ) );

上一篇:H.266/VVC-VTM代码学习-帧内预测17-initIntraPatternChTypeISP函数初始化ISP的帧内预测
下一篇:H.266/VVC-VTM代码学习19-CU层确定测试模式函数initCULevel

H.266/VVC-VTM代码学习18-自适应QP设置(Adaptive QP)相关推荐

  1. H.266/VVC相关技术学习笔记4:HEVC和VVC中块划分的差别

    关于H.265/HEVC和H.266/VVC中的块划分的区别: 一.HEVC中首先需要将一个图像固定划分为多个CTU. ① CTU的尺寸固定划分为64×64,一个CTU由一个亮度CTB和两个色度CTB ...

  2. H.266/VVC相关技术学习笔记18:帧间预测中的AMVR技术(自适应运动适量精度)

    AMVR技术也称为自适应运动适量精度技术,就是在以前的HEVC中,MVD的精度只有一个默认的1/4像素精度,但是由于要适应不同分辨率的图像,仅仅使用一个精度去表示MVD是远远不够的,因此在VTM6.0 ...

  3. H.266/VVC相关技术学习笔记21:帧间预测中五种Merge模式的熵编码方式

    今天主要详细讲一下帧间预测中五种Merge模式的熵编码方式,以及对应的VTM的代码中的编码方式的实现.现阶段VTM6.0中Merge模式大致上分为五种,分别是Subblock_Merge.MMVD_M ...

  4. H.266/VVC相关技术学习笔记20:帧间预测技术中的MMVD技术(Merge mode with MVD)

    今天介绍一下帧间预测技术中的MMVD技术(Merge mode with MVD),也称带有运动矢量差的融合技术,MMVD也属于基于Merge的技术中的一种,在解码端的语法元素中也属于Merge分支. ...

  5. H.266/VVC相关技术学习笔记16:VTM6.0中的CIIP技术(帧内帧间联合预测)

    今天讲一下目前VTM6.0版本中的CIIP技术,CIIP即为帧内帧间联合预测技术,这属于Merge系列的一个分支. 该技术需要先计算当前预测块的帧内预测值,即用Planar.DC.角度预测等传统的帧内 ...

  6. H.266/VVC SCC技术学习:帧内块拷贝(Intra block copy, IBC)

    帧内块拷贝 (Intra block copy, IBC) 是 HEVC 针对屏幕内容编码(Screen content coding)序列的扩展工具,它显着提高了屏幕内容序列的编码效率. IBC 是 ...

  7. H.266/VVC代码学习:帧内预测之角度预测函数(predIntraAng、xPredIntraAng)

    predIntraAng函数 VTM中,帧内预测的角度预测的入口函数为predIntraAng函数,该函数主要是用于进行传统的帧内预测(Planar.DC.角度预测),然后对Planar和DC模式使用 ...

  8. H.266/VVC代码学习20:角度预测入口 / 特殊模式的PDPC技术(predIntraAng)

    1.predIntraAng函数 predIntraAng是帧内0~66这67种预测的入口.其中可细分为: 模式0:PLANAR模式 模式1:DC模式 模式2~66:角度模式 此函数在亮度预测和色度预 ...

  9. H.266/VVC代码学习21:帧内角度预测的实现 / 近对角模式的PDPC(xPredIntraAng)

    xPredIntraAng函数的作用是对任意大小的块和任意模式,如何将参考像素的值根据其模式的角度填充进每一个像素. 下图是basketball drill的一个16*16的块,其预测模式为10(偏斜 ...

  10. H.266/VVC代码学习17:帧内亮度预测的编解码(intra_luma_pred_modes)

    引--亮度预测:H.266/VVC代码学习5:VTM4.0帧内亮度预测代码(estIntraPredLumaQT) 一.结论: 亮度编解码根据MPM列表的值分为两个阶段: 1 亮度模式在MPM列表中: ...

最新文章

  1. 在实践中我遇到stompjs, websocket和nginx的问题与总结
  2. c语言switch计算利润,求助。。关于用switch编写简易计算器
  3. Opencv中三种操作像素的方法
  4. cloud一分钟 | 腾讯云联手斗鱼、虎牙两大头部游戏直播平台开启 定制道具的创新互动...
  5. MySQL8怎么设置时区为东八区_mysql时区设置为东八区
  6. leetcode —— 39. 组合总和
  7. kill 进程_如何查杀stopped进程
  8. 群晖 mysql 自动备份_宝塔定时备份网站及数据库至群晖FTP存储空间
  9. 通过给目标函数增加一个正则项来防止其过拟合
  10. 了解 XSS 攻击原理
  11. Xenko C#开源游戏引擎
  12. 基于开源 Rexsee 的 UP 移动浏览器开放测试
  13. ActiveMQ 默认用户名和密码
  14. data在c语言中的作用,data是什么意思
  15. qq屏蔽怎么知道对方信息(qq屏蔽怎么让对方知道)
  16. O2O、B2B、C2C(通俗讲解)
  17. Matlab abs防抱死系统,防抱死制动系统建模
  18. 第三章:电子商务平台选择
  19. AS3:fla swf 坐标 导出类
  20. java.lang.UnsupportedOperationException解决方法【转】

热门文章

  1. 各领域机器学习数据集汇总(附下载地址)
  2. hive建表与mysql建表的区别
  3. 视频编码:H.264编码
  4. GPIO之推挽输出和开漏输出
  5. 微信小程序引入原生组件——WeUI组件库,详细步骤
  6. 你好,我是计算机组成原理(计算机组成原理1)
  7. GDAL 遥感 图像处理 锐化(Laplace算子、Sobel算子)
  8. 多少天计算机通知用户更改密码,win10系统怎么设置电脑定期强制提醒用户更改登录密码...
  9. 【电气设计】理论知识学习(持续更新中...)
  10. 十进制浮点数的表示方法