SAO以CTU为基本单位,通过选择一个合适的分类器将冲击的像素划分类别,然后对不同类别像素使用不同的补偿值。SAO包括两大类的补偿方式:边界补偿(EO)和边带补偿(BO),此外,还引入了参数融合技术。
对于种类1、2、3、4都需要进行补偿,对于种类0不进行补偿,同一种类的像素要使用相同的补偿值
在一定的图像区域内,像素值的波动范围很小,HEVC规定一个CTU只能选择4条连续的边带,并且只对属于这四个边带的像素进行补偿,选择哪4条边带可以通过率失真优化方法确定
更多的信息请看 HEVC/H.265理论知识(7)——环路滤波
// SAO所有的模式
typedef enum
{
SAO_MODE_OFF = 0,
SAO_MODE_NEW,
SAO_MODE_MERGE,
NUM_SAO_MODES
}SAOMode;
// SAO的merge模式的细分
typedef enum
{
SAO_MERGE_LEFT =0,
SAO_MERGE_ABOVE,
NUM_SAO_MERGE_TYPES
}SAOModeMergeTypes;
// EO、BO的详细分类
typedef enum
{
SAO_TYPE_START_EO =0,
SAO_TYPE_EO_0 = SAO_TYPE_START_EO,
SAO_TYPE_EO_90,
SAO_TYPE_EO_135,
SAO_TYPE_EO_45,
SAO_TYPE_START_BO,
SAO_TYPE_BO = SAO_TYPE_START_BO,
NUM_SAO_NEW_TYPES
}SAOModeNewTypes;
// 使用EO的时候,像素的分类
typedef enum
{
SAO_CLASS_EO_FULL_VALLEY = 0,
SAO_CLASS_EO_HALF_VALLEY = 1,
SAO_CLASS_EO_PLAIN = 2,
SAO_CLASS_EO_HALF_PEAK = 3,
SAO_CLASS_EO_FULL_PEAK = 4,
NUM_SAO_EO_CLASSES,
}SAOEOClasses;
/*
** sao的统计数据
** 保存了CTU(或者像素块)中像素的统计信息
*/
struct SAOStatData //data structure for SAO statistics
{
Int64 diff[MAX_NUM_SAO_CLASSES];
Int64 count[MAX_NUM_SAO_CLASSES];
SAOStatData(){}
~SAOStatData(){}
Void reset()
{
::memset(diff, 0, sizeof(Int64)*MAX_NUM_SAO_CLASSES);
::memset(count, 0, sizeof(Int64)*MAX_NUM_SAO_CLASSES);
}
const SAOStatData& operator=(const SAOStatData& src)
{
::memcpy(diff, src.diff, sizeof(Int64)*MAX_NUM_SAO_CLASSES);
::memcpy(count, src.count, sizeof(Int64)*MAX_NUM_SAO_CLASSES);
return *this;
}
#if SAO_ENCODE_ALLOW_USE_PREDEBLOCK
const SAOStatData& operator+= (const SAOStatData& src)
{
for(Int i=0; i< MAX_NUM_SAO_CLASSES; i++)
{
diff[i] += src.diff[i];
count[i] += src.count[i];
}
return *this;
}
#endif
};
/*
** 一个CTU分量的SAO参数信息
*/
struct SAOOffset
{
// SAO的模式:关闭,new(即EO或者BO),merge
Int modeIdc; //NEW, MERGE, OFF
// 模式的细分:EO_0, EO_90, EO_135, EO_45, BO. MERGE: left, above
Int typeIdc; //NEW: EO_0, EO_90, EO_135, EO_45, BO. MERGE: left, above
// BO带的起始索引
Int typeAuxInfo; //BO: starting band index
Int offset[MAX_NUM_SAO_CLASSES];
SAOOffset();
~SAOOffset();
Void reset();
const SAOOffset& operator= (const SAOOffset& src);
};
/*
** 一个CTU的SAO参数信息
*/
struct SAOBlkParam
{
SAOBlkParam();
~SAOBlkParam();
Void reset();
const SAOBlkParam& operator= (const SAOBlkParam& src);
SAOOffset& operator[](Int compIdx){ return offsetParam[compIdx];}
private:
// 三个分量的SAO信息
SAOOffset offsetParam[NUM_SAO_COMPONENTS];
};
HEVC中SAO的主函数是TEncSampleAdaptiveOffset::SAOProcess,它的工作流程如下:
1、调用getStatistics,收集图像中像素的统计信息,用于判断像素属于EO的哪个种类/BO的哪个边带对于图像中的每一个CTU:
(1)调用deriveLoopFilterBoundaryAvailibility,判断环路滤波是否允许跨越slice或者tile的边界,以此判断当前CTU的邻居是否可用
(2)对于三个颜色分量,分别调用getBlkStats,该函数是统计像素块中像素的特性,然后判断像素属于EO的哪一个种类
2、如果预先去方块滤波的标志为真,那么调用addPreDBFStatistics,这个函数的目的是在上一步的统计信息的基础上添加去方块滤波的一些统计信息
3、调用decidePicParams,判断像素块的三个颜色分量(Y、U、V)是否需要进行SAO操作
4、调用decideBlkParams,进行SAO处理。对于图像的每一个CTU,进行下面的处理:
(1)调用getMergeList,得到SAO的merge列表(这一步就是SAO的merge模式)
(2)按照下面的步骤分别处理EO、BO、Merge三种类型,然后选取出最优的一种
①如果是EO或者BO,那么调用deriveModeNewRDO,处理EO和BO的每一种模式,并选择最优的那种模式
②如果是merge,那么调用deriveModeMergeRDO,处理merge的每一种方式,选取最优的方式
③最后选择出所有模式中最优的那个
(3)利用最优的模式(EO、BO、Merge三者之一),调用reconstructBlkSAOParam和offsetCTU,对重建的CTU进行补偿
/*
** SAO主函数
*/
Void TEncSampleAdaptiveOffset::SAOProcess(TComPic* pPic, Bool* sliceEnabled, const Double *lambdas
#if SAO_ENCODE_ALLOW_USE_PREDEBLOCK
, Bool isPreDBFSamplesUsed
#endif
)
{
TComPicYuv* orgYuv= pPic->getPicYuvOrg();
TComPicYuv* resYuv= pPic->getPicYuvRec();
m_lambda[SAO_Y]= lambdas[0]; m_lambda[SAO_Cb]= lambdas[1]; m_lambda[SAO_Cr]= lambdas[2];
TComPicYuv* srcYuv = m_tempPicYuv;
resYuv->copyToPic(srcYuv);
srcYuv->setBorderExtension(false);
srcYuv->extendPicBorder();
//collect statistics
// 收集像素块的统计信息,用于判断像素属于EO的那个种类/BO的哪个边带
getStatistics(m_statData, orgYuv, srcYuv, pPic);
#if SAO_ENCODE_ALLOW_USE_PREDEBLOCK
if(isPreDBFSamplesUsed)
{
addPreDBFStatistics(m_statData); // 添加去方块滤波的统计信息
}
#endif
//slice on/off
// 判断三个颜色分量是否需要进行SAO
decidePicParams(sliceEnabled, pPic->getSlice(0)->getDepth());
//block on/off
SAOBlkParam* reconParams = new SAOBlkParam[m_numCTUsPic]; //temporary parameter buffer for storing reconstructed SAO parameters
// SAO处理
decideBlkParams(pPic, sliceEnabled, m_statData, srcYuv, resYuv, reconParams, pPic->getPicSym()->getSAOBlkParam());
delete[] reconParams;
}