题目中变换系数熵编码指的是对量化后的变换系数进行熵编码,这部分操作位于视频编码系统中量化模块后,熵编码模块之前。这里的变换系数是量化后的变换系数,为了叙述方便本文后面仍称其为变换系数。
量化后的变换系数进行熵编码包括两部分:一是对量化后变换系数扫描;而是对非零的变换系数位置和值进行熵编码。
变换系数扫描
变换系数扫描就是将二维变换系数变成一维变换系数。扫描时尽量使幅值相近的数排列在一起。
H.265/HEVC对变换系数的扫描是基于4x4块的,所以对于大于4x4的TB要先将其分为若干个4x4的子块,子块内部和子块间按同样方式进行扫描。
H.265/HEVC使用反向扫描方式,扫描顺序总是从右下角开始到左上角的DC系数。这一顺序和H.264/AVC扫描顺序正好相反。并且H.265/HEVC通过了三种扫描方式:对角扫描、水平扫描和垂直扫描。
以8x8TB的扫描为例,首先将其划分为4个4x4的子块,然后进行扫描。下面分别是8x8TB按对角、水平和垂直方式扫描的示意图。
每个4x4子块扫描后得到的16个连续的系数称为系数组(Coefficient Group,CG)。
采用不同预测模式的TB其系数分布往往具有一定规律,如水平预测块变换后能量往往集中在前几列,使用垂直扫描方式效率更高。H.265/HEVC采用模式依赖的系数扫描方式(Mode-Dependent Cofficient Scan,MDCS),规定了预测模式和扫描方式的对应关系因此扫描方式不需要语法元素显示表示。
帧内4x4或8x8亮度TB:如果模式号为6~14采用垂直扫描方式;如果模式号为22~30采用水平扫描方式;其他方向都采用对角扫描方式。
其他TB:如果是帧内亮度16x16或32x32TB,或是帧间亮度TB,无论什么尺寸,是否为对称划分,一律使用对角扫描方式。
对于色度TB其扫描方式和对应的亮度TB一致。
HM中相关定义如下:
//扫描类型定义
/// coefficient scanning type used in ACS
enum COEFF_SCAN_TYPE
{
SCAN_DIAG = 0, ///< up-right diagonal scan
SCAN_HOR = 1, ///< horizontal first scan
SCAN_VER = 2, ///< vertical first scan
SCAN_NUMBER_OF_TYPES = 3
};
扫描模式选择如下:
UInt TComDataCU::getCoefScanIdx(const UInt uiAbsPartIdx, const UInt uiWidth, const UInt uiHeight, const ComponentID compID) const
{
//------------------------------------------------
//this mechanism is available for intra only
//<!帧间预测块都采用对角扫描
if (!isIntra(uiAbsPartIdx))
{
return SCAN_DIAG;
}
//------------------------------------------------
//check that MDCS can be used for this TU
const ChromaFormat format = getPic()->getChromaFormat();
const UInt maximumWidth = MDCS_MAXIMUM_WIDTH >> getComponentScaleX(compID, format);
const UInt maximumHeight = MDCS_MAXIMUM_HEIGHT >> getComponentScaleY(compID, format);
//<!大于8x8的亮度块或大于4x4的色度块采用对角扫描
if ((uiWidth > maximumWidth) || (uiHeight > maximumHeight))
{
return SCAN_DIAG;
}
//------------------------------------------------
//otherwise, select the appropriate mode
UInt uiDirMode = getIntraDir(toChannelType(compID), uiAbsPartIdx);
if (uiDirMode==DM_CHROMA_IDX)
{
const TComSPS *sps=getSlice()->getSPS();
const UInt partsPerMinCU = 1<<(2*(sps->getMaxTotalCUDepth() - sps->getLog2DiffMaxMinCodingBlockSize()));
uiDirMode = getIntraDir(CHANNEL_TYPE_LUMA, getChromasCorrespondingPULumaIdx(uiAbsPartIdx, getPic()->getChromaFormat(), partsPerMinCU));
}
if (isChroma(compID) && (format == CHROMA_422))
{
uiDirMode = g_chroma422IntraAngleMappingTable[uiDirMode];
}
//------------------
//<!模式号为22~30采用水平扫描方式
if (abs((Int)uiDirMode - VER_IDX) <= MDCS_ANGLE_LIMIT)
{
return SCAN_HOR;
}//<!模式号为6~14采用垂直扫描方式
else if (abs((Int)uiDirMode - HOR_IDX) <= MDCS_ANGLE_LIMIT)
{
return SCAN_VER;
}
else
{
return SCAN_DIAG;
}
}
变换系数熵编码
一个TB经过上面扫描后得到一个一维的变换系数数组,由于变换系数经过量化后大部分值都为0如果直接按顺序对每个系数进行熵编码效率不高。H.265/HEVC中采用的是对每个非零系数的位置和幅值进行CABAC编码。
符号位隐藏
符号位隐藏(Sign Data Hiding,SDH)技术是用来隐藏每个CG最后一个非零比特符号位的技术。是否使用由PPS中相应标志位决定。
SDH技术:首先计算CG内所有非零系数绝对值之和,若和为偶数则CG内最后一个非零系数被判定为“+”;若和为奇数则CG内最后一个非零系数被判定为“-”;使用SDH后解码端可以直接判断CG中最后一个非零系数的符号,编码端可以省略它的语法元素coeff_sign_flag的熵编码。
当SDH得到的符号和CG内最后一个非零系数符号不一致时就需要调整CG中某个非零系数,将它的值加1或减1,以使其和真实符号一致。所以使用SDH技术可能会带来变换系数的失真,但符号位在熵编码时使用的是旁路编码模式开销较大,使用SDH技术节约的码率大于一个变换系数失真带来的影响。
注:视频编码中除了量化会引入失真,SDH也会。
感兴趣的请关注微信公众号Video Coding