与变换量化有关的其他知识

变换

哈达玛变换

哈达玛变换是广义傅立叶变换的一种,它的变换矩阵Hm是一个2^m x 2^m的矩阵。

哈达玛变换及其矩阵有下面的几个特性:
1、hadamard矩阵元素都是正负1,且其特征值也只包含正负1
2、hadamard矩阵为正交、对称矩阵,相应的hadamard变换是正交变换
3、hadamard矩阵奇数行(列)偶对称,偶数行(列)奇对称
4、hadamard变换满足变换前后能量守恒

使用哈达玛变换的地方

哈达玛变换在HEVC中的主要运用是在帧内预测的时候计算SAD,下面的函数使用了哈达玛变换

Void TEncSearch::estIntraPredQT( TComDataCU* pcCU, TComYuv*    pcOrgYuv, TComYuv*    pcPredYuv, TComYuv*    pcResiYuv, TComYuv*    pcRecoYuv,UInt&       ruiDistC,Bool        bLumaOnly )
{// 省略...// 循环处理每一个预测块PUfor( UInt uiPU = 0; uiPU < uiNumPU; uiPU++, uiPartOffset += uiQNumParts ){// 省略...// 使用快速搜索模式if (doFastSearch){// 省略...for( Int modeIdx = 0; modeIdx < numModesAvailable; modeIdx++ ) // 总共有35种模式,numModesAvailable = 35{UInt uiMode = modeIdx;// 对亮度块进行预测predIntraLumaAng( pcCU->getPattern(), uiMode, piPred, uiStride, uiWidth, uiHeight, bAboveAvail, bLeftAvail );// use hadamard transform here// 使用hadamard变换UInt uiSad = m_pcRdCost->calcHAD(g_bitDepthY, piOrg, uiStride, piPred, uiStride, uiWidth, uiHeight );UInt   iModeBits = xModeBitsIntra( pcCU, uiMode, uiPU, uiPartOffset, uiDepth, uiInitTrDepth );// 计算此种模式的代价Double cost      = (Double)uiSad + (Double)iModeBits * m_pcRdCost->getSqrtLambda();// 更新候选列表CandNum += xUpdateCandList( uiMode, cost, numModesForFullRD, uiRdModeList, CandCostList );}// 省略...}else{// 省略...}// 省略...} // PU loop// 省略...
}
Void TEncSearch::preestChromaPredMode( TComDataCU* pcCU, TComYuv*    pcOrgYuv, TComYuv*    pcPredYuv )
{// 省略...for( UInt uiMode  = uiMinMode; uiMode < uiMaxMode; uiMode++ ){//--- get prediction ---// 色度分量的帧内预测predIntraChromaAng( pPatChromaU, uiMode, piPredU, uiStride, uiWidth, uiHeight, bAboveAvail, bLeftAvail );predIntraChromaAng( pPatChromaV, uiMode, piPredV, uiStride, uiWidth, uiHeight, bAboveAvail, bLeftAvail );//--- get SAD ---// 计算sadUInt  uiSAD  = m_pcRdCost->calcHAD(g_bitDepthC, piOrgU, uiStride, piPredU, uiStride, uiWidth, uiHeight );uiSAD       += m_pcRdCost->calcHAD(g_bitDepthC, piOrgV, uiStride, piPredV, uiStride, uiWidth, uiHeight );//--- check ---// 更新最优代价if( uiSAD < uiMinSAD ){uiMinSAD   = uiSAD;uiBestMode = uiMode;}}//===== set chroma pred mode =====// 保存预测模式pcCU->setChromIntraDirSubParts( uiBestMode, 0, pcCU->getDepth( 0 ) );
}

使用哈达玛变换计算SAD

// 使用hadamard变换来计算SAD
UInt TComRdCost::calcHAD(Int bitDepth, Pel* pi0, Int iStride0, Pel* pi1, Int iStride1, Int iWidth, Int iHeight )
{UInt uiSum = 0;Int x, y;// 如果宽高是8的整数倍(进入这里)if ( ( (iWidth % 8) == 0 ) && ( (iHeight % 8) == 0 ) ){for ( y=0; y<iHeight; y+= 8 ){for ( x=0; x<iWidth; x+= 8 ){uiSum += xCalcHADs8x8( &pi0[x], &pi1[x], iStride0, iStride1, 1 );}pi0 += iStride0*8;pi1 += iStride1*8;}}else{// 特别针对宽高等于4的情况assert(iWidth % 4 == 0 && iHeight % 4 == 0);for ( y=0; y<iHeight; y+= 4 ){for ( x=0; x<iWidth; x+= 4 ){uiSum += xCalcHADs4x4( &pi0[x], &pi1[x], iStride0, iStride1, 1 );}pi0 += iStride0*4;pi1 += iStride1*4;}}// 结果返回return uiSum >> DISTORTION_PRECISION_ADJUSTMENT(bitDepth-8);}

哈达玛变换函数

// 8x8的hadamard变换
UInt TComRdCost::xCalcHADs8x8( Pel *piOrg, Pel *piCur, Int iStrideOrg, Int iStrideCur, Int iStep )
{Int k, i, j, jj, sad=0;Int diff[64], m1[8][8], m2[8][8], m3[8][8];assert( iStep == 1 );for( k = 0; k < 64; k += 8 ){diff[k+0] = piOrg[0] - piCur[0];diff[k+1] = piOrg[1] - piCur[1];diff[k+2] = piOrg[2] - piCur[2];diff[k+3] = piOrg[3] - piCur[3];diff[k+4] = piOrg[4] - piCur[4];diff[k+5] = piOrg[5] - piCur[5];diff[k+6] = piOrg[6] - piCur[6];diff[k+7] = piOrg[7] - piCur[7];piCur += iStrideCur;piOrg += iStrideOrg;}//horizontalfor (j=0; j < 8; j++){jj = j << 3;m2[j][0] = diff[jj  ] + diff[jj+4];m2[j][1] = diff[jj+1] + diff[jj+5];m2[j][2] = diff[jj+2] + diff[jj+6];m2[j][3] = diff[jj+3] + diff[jj+7];m2[j][4] = diff[jj  ] - diff[jj+4];m2[j][5] = diff[jj+1] - diff[jj+5];m2[j][6] = diff[jj+2] - diff[jj+6];m2[j][7] = diff[jj+3] - diff[jj+7];m1[j][0] = m2[j][0] + m2[j][2];m1[j][1] = m2[j][1] + m2[j][3];m1[j][2] = m2[j][0] - m2[j][2];m1[j][3] = m2[j][1] - m2[j][3];m1[j][4] = m2[j][4] + m2[j][6];m1[j][5] = m2[j][5] + m2[j][7];m1[j][6] = m2[j][4] - m2[j][6];m1[j][7] = m2[j][5] - m2[j][7];m2[j][0] = m1[j][0] + m1[j][1];m2[j][1] = m1[j][0] - m1[j][1];m2[j][2] = m1[j][2] + m1[j][3];m2[j][3] = m1[j][2] - m1[j][3];m2[j][4] = m1[j][4] + m1[j][5];m2[j][5] = m1[j][4] - m1[j][5];m2[j][6] = m1[j][6] + m1[j][7];m2[j][7] = m1[j][6] - m1[j][7];}//verticalfor (i=0; i < 8; i++){m3[0][i] = m2[0][i] + m2[4][i];m3[1][i] = m2[1][i] + m2[5][i];m3[2][i] = m2[2][i] + m2[6][i];m3[3][i] = m2[3][i] + m2[7][i];m3[4][i] = m2[0][i] - m2[4][i];m3[5][i] = m2[1][i] - m2[5][i];m3[6][i] = m2[2][i] - m2[6][i];m3[7][i] = m2[3][i] - m2[7][i];m1[0][i] = m3[0][i] + m3[2][i];m1[1][i] = m3[1][i] + m3[3][i];m1[2][i] = m3[0][i] - m3[2][i];m1[3][i] = m3[1][i] - m3[3][i];m1[4][i] = m3[4][i] + m3[6][i];m1[5][i] = m3[5][i] + m3[7][i];m1[6][i] = m3[4][i] - m3[6][i];m1[7][i] = m3[5][i] - m3[7][i];m2[0][i] = m1[0][i] + m1[1][i];m2[1][i] = m1[0][i] - m1[1][i];m2[2][i] = m1[2][i] + m1[3][i];m2[3][i] = m1[2][i] - m1[3][i];m2[4][i] = m1[4][i] + m1[5][i];m2[5][i] = m1[4][i] - m1[5][i];m2[6][i] = m1[6][i] + m1[7][i];m2[7][i] = m1[6][i] - m1[7][i];}for (i = 0; i < 8; i++){for (j = 0; j < 8; j++){sad += abs(m2[i][j]);}}sad=((sad+2)>>2);return sad;
}

DST

DST主要用在4x4模式的亮度块上,使用过程如下:
1、变换量化处理进入transformNxN函数
2、transformNxN调用xT函数进行变换处理
3、xT调用xTrMxN函数进行蝶形快速变换

// TU的大小最大是32,最小是4
void xTrMxN(Int bitDepth, Short *block,Short *coeff, Int iWidth, Int iHeight, UInt uiMode)
{// 省略...if( iWidth == 4 && iHeight == 4){if (uiMode != REG_DCT){// 快速变换fastForwardDst(block,tmp,shift_1st); // Forward DST BY FAST ALGORITHM, block input, tmp outputfastForwardDst(tmp,coeff,shift_2nd); // Forward DST BY FAST ALGORITHM, tmp input, coeff output}else{// 蝴蝶型变换partialButterfly4(block, tmp, shift_1st, iHeight);partialButterfly4(tmp, coeff, shift_2nd, iWidth);}}// 省略...
}

使用蝶形快速变换实现的DST

// 使用蝶形快速变换实现的DST
void partialButterfly4(Short *src,Short *dst,Int shift, Int line)
{Int j;Int E[2],O[2];Int add = 1<<(shift-1);for (j=0; j<line; j++){    /* E and O */E[0] = src[0] + src[3];O[0] = src[0] - src[3];E[1] = src[1] + src[2];O[1] = src[1] - src[2];dst[0] = (g_aiT4[0][0]*E[0] + g_aiT4[0][1]*E[1] + add)>>shift;dst[2*line] = (g_aiT4[2][0]*E[0] + g_aiT4[2][1]*E[1] + add)>>shift;dst[line] = (g_aiT4[1][0]*O[0] + g_aiT4[1][1]*O[1] + add)>>shift;dst[3*line] = (g_aiT4[3][0]*O[0] + g_aiT4[3][1]*O[1] + add)>>shift;src += 4;dst ++;}
}

量化

量化矩阵

1、使用量化矩阵的原因是,对不同位置的系数使用不同的量化步长,这样能提高视频的主观质量
2、量化矩阵作用于比例缩放过程,其大小和TU相同(从4x4到32x32)
3、HEVC定义了4x4和8x8两种大小的默认量化矩阵,并规定16x16、32x32量化矩阵可以由8x8量化矩阵采样得到
4、HEVC对量化矩阵中的元素使用差分编码!

量化矩阵的定义

Int g_quantTSDefault4x4[16] =
{16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16
};Int g_quantIntraDefault8x8[64] =
{16,16,16,16,17,18,21,24,16,16,16,16,17,19,22,25,16,16,17,18,20,22,25,29,16,16,18,21,24,27,31,36,17,17,20,24,30,35,41,47,18,19,22,27,35,44,54,65,21,22,25,31,41,54,70,88,24,25,29,36,47,65,88,115
};Int g_quantInterDefault8x8[64] =
{16,16,16,16,17,18,20,24,16,16,16,17,18,20,24,25,16,16,17,18,20,24,25,28,16,17,18,20,24,25,28,33,17,18,20,24,25,28,33,41,18,20,24,25,28,33,41,54,20,24,25,28,33,41,54,71,24,25,28,33,41,54,71,91
};

量化矩阵的选择

在compressGOP中有一段代码是设置量化矩阵的

量化步长的来源有三种:
1、平坦的,使用平坦的量化矩阵(即,使用标量量化),TU中每个系数使用量化步长都一样
2、默认的,使用编码器提供的默认量化矩阵
3、自定义的,从文件中读取用户自定义的量化矩阵

        // 如果没有使用缩放列表if(m_pcEncTop->getUseScalingListId() == SCALING_LIST_OFF){// 默认缩放列表是关闭的// 进入m_pcEncTop->getTrQuant()->setFlatScalingList();m_pcEncTop->getTrQuant()->setUseScalingList(false);m_pcEncTop->getSPS()->setScalingListPresentFlag(false);m_pcEncTop->getPPS()->setScalingListPresentFlag(false);}// 使用了默认的缩放列表else if(m_pcEncTop->getUseScalingListId() == SCALING_LIST_DEFAULT){pcSlice->setDefaultScalingList ();m_pcEncTop->getSPS()->setScalingListPresentFlag(false);m_pcEncTop->getPPS()->setScalingListPresentFlag(false);m_pcEncTop->getTrQuant()->setScalingList(pcSlice->getScalingList());m_pcEncTop->getTrQuant()->setUseScalingList(true);}// 从文件中读取缩放列表else if(m_pcEncTop->getUseScalingListId() == SCALING_LIST_FILE_READ){if(pcSlice->getScalingList()->xParseScalingList(m_pcCfg->getScalingListFile())){pcSlice->setDefaultScalingList ();}pcSlice->getScalingList()->checkDcOfMatrix();m_pcEncTop->getSPS()->setScalingListPresentFlag(pcSlice->checkDefaultScalingList());m_pcEncTop->getPPS()->setScalingListPresentFlag(false);m_pcEncTop->getTrQuant()->setScalingList(pcSlice->getScalingList());m_pcEncTop->getTrQuant()->setUseScalingList(true);}else{printf("error : ScalingList == %d no support\n",m_pcEncTop->getUseScalingListId());assert(0);}

为slice设置量化矩阵

以setDefaultScalingList函数为例子,看看怎么样为slice设置量化矩阵式

Void TComSlice::setDefaultScalingList()
{for(UInt sizeId = 0; sizeId < SCALING_LIST_SIZE_NUM; sizeId++){for(UInt listId=0;listId<g_scalingListNum[sizeId];listId++){getScalingList()->processDefaultMatrix(sizeId, listId);}}
}

调用processDefaultMatrix把量化矩阵保存到slice中

Void TComScalingList::processDefaultMatrix(UInt sizeId, UInt listId)
{::memcpy(getScalingListAddress(sizeId, listId),getScalingListDefaultAddress(sizeId,listId),sizeof(Int)*min(MAX_MATRIX_COEF_NUM,(Int)g_scalingListSize[sizeId]));setScalingListDC(sizeId,listId,SCALING_LIST_DC);
}

其中getScalingListDefaultAddress用于获取编码器定义的默认量化矩阵

Int* TComScalingList::getScalingListDefaultAddress(UInt sizeId, UInt listId)
{Int *src = 0;switch(sizeId){case SCALING_LIST_4x4:src = g_quantTSDefault4x4;break;case SCALING_LIST_8x8:src = (listId<3) ? g_quantIntraDefault8x8 : g_quantInterDefault8x8;break;case SCALING_LIST_16x16:src = (listId<3) ? g_quantIntraDefault8x8 : g_quantInterDefault8x8;break;case SCALING_LIST_32x32:src = (listId<1) ? g_quantIntraDefault8x8 : g_quantInterDefault8x8;break;default:assert(0);src = NULL;break;}return src;
}

通过上面的步骤之后,量化矩阵被保存到了TComSlice的m_scalingList成员中

为量化对象TComTrQuant设置量化矩阵

在compressGOP函数中为量化对象TComTrQuant对象设置量化矩阵,量化矩阵来自TComSlice的m_scalingList成员,这个成员在上一步中已经设置好了

m_pcEncTop->getTrQuant()->setScalingList(pcSlice->getScalingList());
Void TComTrQuant::setScalingList(TComScalingList *scalingList)
{UInt size,list;UInt qp;for(size=0;size<SCALING_LIST_SIZE_NUM;size++){for(list = 0; list < g_scalingListNum[size]; list++){for(qp=0;qp<SCALING_LIST_REM_NUM;qp++){xSetScalingListEnc(scalingList,list,size,qp);xSetScalingListDec(scalingList,list,size,qp);setErrScaleCoeff(list,size,qp);}}}
}

把量化矩阵的数据保存到TComTrQuant的m_quantCoef成员中

Void TComTrQuant::xSetScalingListEnc(TComScalingList *scalingList, UInt listId, UInt sizeId, UInt qp)
{UInt width = g_scalingListSizeX[sizeId];UInt height = g_scalingListSizeX[sizeId];UInt ratio = g_scalingListSizeX[sizeId]/min(MAX_MATRIX_SIZE_NUM,(Int)g_scalingListSizeX[sizeId]);Int *quantcoeff;Int *coeff = scalingList->getScalingListAddress(sizeId,listId); // 量化矩阵quantcoeff   = getQuantCoeff(listId, qp, sizeId); // TComTrQuant的m_quantCoef成员// 把量化矩阵经过一定处理后复制给TComTrQuant的m_quantCoef成员processScalingListEnc(coeff,quantcoeff,g_quantScales[qp]<<4,height,width,ratio,min(MAX_MATRIX_SIZE_NUM,(Int)g_scalingListSizeX[sizeId]),scalingList->getScalingListDC(sizeId,listId));
}
Void TComTrQuant::processScalingListEnc( Int *coeff, Int *quantcoeff, Int quantScales, UInt height, UInt width, UInt ratio, Int sizuNum, UInt dc)
{Int nsqth = (height < width) ? 4: 1; //height ratio for NSQTInt nsqtw = (width < height) ? 4: 1; //width ratio for NSQTfor(UInt j=0;j<height;j++){for(UInt i=0;i<width;i++){quantcoeff[j*width + i] = quantScales / coeff[sizuNum * (j * nsqth / ratio) + i * nsqtw /ratio];}}if(ratio > 1){quantcoeff[0] = quantScales / dc;}
}

使用量化矩阵

现在,TComTrQuant的m_quantCoef成员中已经保存了量化矩阵了,下面看看在量化过程中怎么样使用量化矩阵

在TComTrQuant::xQuant函数中有下面这样一段代码

Int *piQuantCoeff = 0;
// 使用量化矩阵
piQuantCoeff = getQuantCoeff(scalingListType,m_cQP.m_iRem,uiLog2TrSize-2);
// ...
iLevel = ((Int64)abs(iLevel) * piQuantCoeff[uiBlockPos] + iAdd ) >> iQBits; // 量化核心操作

HM编码器代码阅读(20)——与变换量化有关的其他知识相关推荐

  1. HM编码器代码阅读(32)——帧间预测之AMVP/Merge模式(七)encodeResAndCalcRdInterCU函数:残差计算、变换量化

    encodeResAndCalcRdInterCU 原理和细节 经过运动估计.运动补偿,我们得到了MV以及参考块,那么接下来是计算残差.计算MVD,然后对系数进行变换.量化. encodeResAnd ...

  2. HM编码器代码阅读(18)——变换

    变换 原理以及公式     对于大部分图像来说,它们都有很多平坦区域和内容变换缓慢的区域,而且相邻像素点的相关性很强,通过变换,可以把这些相关性减少,同时把图像的能量在空间域的分散分布转换为在变换域的 ...

  3. HM编码器代码阅读(38)——帧内预测(五)帧内预测之正式的预测操作

    正式的预测操作 在前面的操作中,我们已经得到了模式候选列表,但是我们的目的是要得到一个最优的模式,因此我们还需要对这个列表中的模式进行遍历,对于每一个模式,进行预测操作,为了计算率失真代价还必须进行变 ...

  4. HM编码器代码阅读(13)——帧间预测之AMVP模式(一)总体流程

    帧间预测的原理 AMVP的原理 帧间预测的实质就是为当前的PU在参考帧中寻找一块最相似块(相似度的判断准则有SAD等方法).但是参考图像通常都比较大,我们直接去搜索的话就太费时了,应该使用某种方法在参 ...

  5. HM编码器代码阅读(9)——片编码器的初始化

    入口函数:TEncSlice::initEncSlice. 在处理图像组的时候,遍历图像组的每一帧,对每一帧调用TEncSlice::initEncSlice.主要是设置和计算一些参数,为片的编码做准 ...

  6. HM编码器代码阅读(30)——帧间预测之AMVP模式(五)运动估计

    运动估计 通过 点击打开链接 介绍的方法得到MVP之后,可以根据该MVP确定运动估计的搜索起点,然后进行运动估计 xMotionEstimation就是进行运动估计的入口函数     1.先进行一些初 ...

  7. HM编码器代码阅读(16)——帧间预测之AMVP模式(四)预测MV的获取

    帧间预测的原理 AMVP的原理 帧间预测的实质就是为当前的PU在参考帧中寻找一块最相似块(相似度的判断准则有SAD等方法).但是参考图像通常都比较大,我们直接去搜索的话就太费时了,应该使用某种方法在参 ...

  8. HM编码器代码阅读(14)——帧间预测之AMVP模式(二)predInterSearch函数

    简介     predInterSearch主要的工作是ME(运动估计)和MC(运动补偿).     函数中有一个bTestNormalMC变量,它表示是否进行正常的MC过程,正常的MC过程就是进行M ...

  9. HM编码器代码阅读(31)——帧间预测之AMVP/Merge模式(六)运动补偿

    运动补偿 原理 说实话一直很难理解运动补偿中"补偿"二字的意思,在参考了 http://blog.csdn.net/hevc_cjl/article/details/8457642 ...

最新文章

  1. OpenvSwitch命令总结
  2. bzoj1116 [POI2008]CLO
  3. 学习nginx接口调用之摘录
  4. robo3t设置密码链接
  5. eclipse启动报错No java virtual machine was found after seearching the locations:XXXXX
  6. 最流行的 IDE 之争:Eclipse 反超 Visual Studio 成第一
  7. getprivateprofilestring读不到数据_SpringBoot2.x系列教程66--Spring Boot整合分布式事务之数据库事务回顾
  8. 类别不均衡的分类问题
  9. 【渗透】浅谈webshell隐藏
  10. python根据出生年份计算年龄_python根据出生日期计算年龄的代码详解
  11. 山东大学项目实训设计系统(四)管理员端
  12. jboss启动oracle表不存在,JBoss的部署及运行
  13. android tablayout放图片,Android TabLayout的Indicator如何设置为图片
  14. Web Push功能使用
  15. 【你好,windows】Win10 X86x64 1709.2166企业G纯净版2020.11.27
  16. sus 逆向 writrup
  17. 03_建立 Windows 和虚拟机 Ubuntu 共享文件夹
  18. shell 文件处理 并集 交集 差集
  19. 完美解决WebSocket 服务器 The WebSocket session [0] has been closed and no method...异常信息
  20. Delphi与C#之父:技术理想架构开发传奇

热门文章

  1. 什么是 IconFont?有什么优缺点?
  2. 如何降低运放噪声?运放电路噪声降低措施
  3. apk安装提示:Failure [INSTALL_FAILED_DUPLICATE_PERMISSION perm=XXX]
  4. wince 访问共享文件_WINCE 访问PC共享文件夹
  5. 中国电子签名发展研究报告 (2006-2007年)
  6. OpenLayers 6.13 新特性
  7. Python与有趣的数学2
  8. 【LeetCode 3-中等】无重复字符的最长子串(高清截图)
  9. 微信小程序的导航栏颜色
  10. 考研数据结构学习与总结笔记---1.1数据结构的基本概念