您的位置:首页 > 编程语言

SAO样点自适应补偿技术实现代码详解(一)

2018-01-29 20:03 525 查看
作者:66

惯例推荐前辈的专栏: http://blog.csdn.net/HEVC_CJL/article/category/1283611/3

量化后重构的图像边缘部分会产生波纹现象,对肉眼观察到的客观图像质量影响很大,称为振铃效应。SAO主要是采用补偿技术,将图像边缘部分进行像素值补偿,提高图像的主客观质量。

HEVC主要做的工作就是去除图像中多余的信息,主要是一帧中像素相同的块(帧内),临近帧中重复的部分(帧间)等。HEVC所有的技术都是围绕在能恢复原图像情况下尽可能多的去除重复的像素信息。因为对于不同的图像,特点不同,预测方式和算法只能面向所有的图像,根据归纳出图像共有的纹理特点(垂直、角度、DC等),采用对应的手段。但这样预测出来的图像与原图仍存在很多的不同之处,于是在HEVC中将原图与预测的信息进行对比,传输预测图像与原图的误差,称为残差。可以看出,如果残差越小,即预测像素与原像素越接近,需要传输的数据就越小(残差为零不传输)。如果残差传到接收端,接收端按相同的算法是可以无失真的恢复出原始图像的,但残差的无失真传输仍需要很大的数据量,不能够满足压缩的需求,于是需要将数据中相近的残差通过量化归到一个数值上,类似于模拟信号到数字信号的转换,HEVC里主要的误差就出现在这里,不同的量化参数,解码端重构图像的质量不同,但传输数据量与最后重建图像的质量依然是成正比的。引入了误差,解码媏重建的图像就有失真,两个明显的失真就是方块效应和振铃效应(为什么会这样总结,等我看完代码自己实验一下)。

在HM14中的SAO与HM10的差别太大了,这个得自己研究了。在HM14.0中找到了处理SAO的那部分,时间紧迫,遂放弃,下载了一个HM10.0版本,继续跟着老哥HEVC_CJL的脚步走下去。

HM10.0中也有很多让我莫名其妙理解不了的地方。

理论部分:

SAO以CTB为单位进行滤波,将一个CTB内像素分为不同类别,同一类别使用相同的补偿值。

分为EO(Edge offset)边缘补偿与BO(bound
offset)边带补偿。

1.EO

EO模式通过比较邻近像素与当前像素值大小关系进行归类,为均衡复杂度与编码效率使用了一维三像素的分类模式。

相邻像素未必是水平相邻,也可以是垂直、45度斜角等。H.265边界补偿根据比较像素位置共分了四种,水平模式(EO_0)、垂直模式(EO_1)、135度角模式(EO_2)、45度角模式(EO_3)。

 


图一、四种模式
 


图二、代码中模式序号
其中,每个边界补偿模式根据当前像素与边界相邻像素的相对像素值大小又分为以下5个种类:
1. 当前像素值小于两相邻像素值
2. 当前像素值大于一个相邻像素值且等于另一个
3. 当前像素值小于一个相邻像素值且等于另一个
4. 当前像素值大于两相邻像素值
0.  不属于以上四种情况的其他像素值
对于种类0的像素不进行补偿,其他同种使用相同的补偿值。
 


图三、边界补偿分类
上图来源于书《H.265/HEVC原理、标准与实现》,万帅、杨付正编著。
在HEVC中,通过重构像素与原图像像素值的对比,统计同一种类下的像素数和误差和,初步用总误差除以像素数确定补偿值,再对补偿值进行取整限幅得到补偿值范围,遍历所有候选补偿值,选率失真最小的一个。
2.BO
在边带补偿模式中,将256个像素值划分为32条边带,每个边带覆盖8个像素值。对每个边带里的像素值采用相同的补偿值补偿。每个CTU最多使用四条补偿边带。补偿值确定过程类似于EO。
3. merge参数融合
SAO中也引入了类似于帧内预测中使用邻块参数的模式,可参考左块或者上块的SAO参数。至此,当前CTB块的最终补偿类型可以选择如下8中:不补偿、EO_0、EO_1、EO_2、EO_3、BO、左相邻参数融合、上相邻参数融合。
在HM10.0中,是先对整个Slice中的CTU进行信息统计,再确定其SAO参数。最后进行补偿,而不是一次对每个CTB进行补偿。
4.代码分析:
SAO的整个过程在函数TEncSampleAdaptiveOffset::SAOProcess中,大部分参数名和语句我还不能够了解它的意思,但在其中调用了rdoSaoUnitAll函数,rdoSaoUnitAll它完成了对整个Frame的所有LCU的YUV进行reset
stats(各类参数像误差值、像素个数的统计)和calcSaoStatsCu以及最后encodeSaoOffset的工作。

Void TEncSampleAdaptiveOffset::SAOProcess(SAOParam *pcSaoParam, Double dLambdaLuma, Double dLambdaChroma, Int depth)

{

...

rdoSaoUnitAll(pcSaoParam, dLambdaLuma, dLambdaChroma, depth);//完成对整个Frame的所有LCU的YUV进行reset
stats和calcSaoStatsCu,以及saoComponentParamDist得到最佳SAO_TYPE选择,最后encodeSaoOffset。

...

if (pcSaoParam->bSaoFlag[0])

  {

    processSaoUnitAll( pcSaoParam->saoLcuParam[0], pcSaoParam->oneUnitFlag[0], 0);processSaoUnitAll主要完成SAO解码的操作,就是对重建帧进行SAOoffset叠加。

  }

  if (pcSaoParam->bSaoFlag[1])

  {

    processSaoUnitAll( pcSaoParam->saoLcuParam[1], pcSaoParam->oneUnitFlag[1], 1);

    processSaoUnitAll( pcSaoParam->saoLcuParam[2], pcSaoParam->oneUnitFlag[2], 2);

  }

}

第二个processSaoUnitAll在解码时对重建帧进行操作,暂时不用理它,进入第一个rdoSaoUnitAll中。

(1) 首先经过一系列参数初始化,开始以每个CTB(注释中为LCU)为单位对图像进行遍历,检查左上邻块是否可用(同一个Tile与Slice中邻块才可用,另外第一列的CTB不用左邻块、第一行的CTB不用上邻块)。

(2) 接下来,初始化本CTB块亮度色度统计数据的参数,像m_iCount像素个数、m_iOffsetOrg像素值误差、m_iOffset补偿值

(3) 所有前期准备工作做完了,调用calcSaoStatsCu(主要调用calcSaoStatsBlock完成)统计当前CTB各个模式下的重建像素值与原始像素值的误差。对这个函数进行解析

(4) 调用saoComponentParamDist与sao2ChromaParamDist分别选择亮度和色度的最佳滤波模式。(这里参数融合模式没有参与比较,最后一点代码的解析明天理解了补上)

(5) 计算参数融合模式和不补偿模式下的率失真,对比之前的模式,确定最佳滤波方式。

1.rdoSaoUnitAll:
/** rate distortion optimization of all SAO units
* \param saoParam SAO parameters
* \param lambda
* \param lambdaChroma
*/
#if SAO_ENCODING_CHOICE
Void TEncSampleAdaptiveOffset::rdoSaoUnitAll(SAOParam *saoParam, Double lambda, Double lambdaChroma, Int depth)
#else
Void TEncSampleAdaptiveOffset::rdoSaoUnitAll(SAOParam *saoParam, Double lambda, Double lambdaChroma)
#endif
{

Int idxY;
Int idxX;
Int frameHeightInCU = saoParam->numCuInHeight;
Int frameWidthInCU  = saoParam->numCuInWidth;
Int j, k;
Int addr = 0;
Int addrUp = -1;
Int addrLeft = -1;
Int compIdx = 0;
SaoLcuParam mergeSaoParam[3][2];
Double compDistortion[3];

saoParam->bSaoFlag[0] = true;
saoParam->bSaoFlag[1] = true;
saoParam->oneUnitFlag[0] = false;
saoParam->oneUnitFlag[1] = false;
saoParam->oneUnitFlag[2] = false;

#if SAO_ENCODING_CHOICE
#if SAO_ENCODING_CHOICE_CHROMA
Int numNoSao[2];
numNoSao[0] = 0;// Luma
numNoSao[1] = 0;// Chroma
if( depth > 0 && m_depthSaoRate[0][depth-1] > SAO_ENCODING_RATE )
{
saoParam->bSaoFlag[0] = false;
}
if( depth > 0 && m_depthSaoRate[1][depth-1] > SAO_ENCODING_RATE_CHROMA )
{
saoParam->bSaoFlag[1] = false;
}
#else
Int numNoSao = 0;

if( depth > 0 && m_depth0SaoRate > SAO_ENCODING_RATE )
{
saoParam->bSaoFlag[0] = false;
saoParam->bSaoFlag[1] = false;
}
#endif
#endif
//以LCU为单位对图像中的每个LCU进行遍历
for (idxY = 0; idxY< frameHeightInCU; idxY++)
{
for (idxX = 0; idxX< frameWidthInCU; idxX++)
{
addr     = idxX  + frameWidthInCU*idxY;//当前LCU位置
addrUp   = addr < frameWidthInCU ? -1:idxX   + frameWidthInCU*(idxY-1);//当前LCU上邻块地址,如果是第一排的LCU,参考左上的LCU
addrLeft = idxX == 0               ? -1:idxX-1 + frameWidthInCU*idxY;//当前LCU左邻块地址。
Int allowMergeLeft = 1;//标志左参数融合模式是否可用
Int allowMergeUp   = 1;//类比上一个
UInt rate;
Double bestCost, mergeCost;//最优编码代价,融合编码代价
if (idxX!=0)//处理非第一列的数据
{
// check tile id and slice id 检查与左邻块是否属于同一个tile或slice,不是的话,设置邻块不可用
if ( (m_pcPic->getPicSym()->getTileIdxMap(addr-1) != m_pcPic->getPicSym()->getTileIdxMap(addr)) || (m_pcPic->getCU(addr-1)->getSlice()->getSliceIdx() != m_pcPic->getCU(addr)->getSlice()->getSliceIdx()))
{
allowMergeLeft = 0;//不可用
}
}
else
{
allowMergeLeft = 0;//第一列不用左邻块
}
if (idxY!=0)//类比上一段
{
if ( (m_pcPic->getPicSym()->getTileIdxMap(addr-m_iNumCuInWidth) != m_pcPic->getPicSym()->getTileIdxMap(addr)) || (m_pcPic->getCU(addr-m_iNumCuInWidth)->getSlice()->getSliceIdx() != m_pcPic->getCU(addr)->getSlice()->getSliceIdx()))
{
allowMergeUp = 0;
}
}
else
{
allowMergeUp = 0;
}

compDistortion[0] = 0;//Y distortion
compDistortion[1] = 0;//Cb distortion
compDistortion[2] = 0;//Cr distortion
m_pcRDGoOnSbacCoder->load(m_pppcRDSbacCoder[0][CI_CURR_BEST]);
if (allowMergeLeft)//
{
m_pcEntropyCoder->m_pcEntropyCoderIf->codeSaoMerge(0);//编码句法元素,sao_merge_left_flag????
}
if (allowMergeUp)
{
m_pcEntropyCoder->m_pcEntropyCoderIf->codeSaoMerge(0);//编码句法元素,sao_merge_up_flag????
}
m_pcRDGoOnSbacCoder->store( m_pppcRDSbacCoder[0][CI_TEMP_BEST] );
// reset stats Y, Cb, Cr重置
for ( compIdx=0;compIdx<3;compIdx++)
{
for ( j=0;j<MAX_NUM_SAO_TYPE;j++)//EO:4,BO:1,5种
{
for ( k=0;k< MAX_NUM_SAO_CLASS;k++)//MAX_NUM_SAO_CLASS = 33
{
m_iOffset   [compIdx][j][k] = 0;//像素补偿值
if( m_saoLcuBasedOptimization && m_saoLcuBoundary ){//true and false
m_iCount    [compIdx][j][k] = m_count_PreDblk    [addr][compIdx][j][k];
m_iOffsetOrg[compIdx][j][k] = m_offsetOrg_PreDblk[addr][compIdx][j][k];
}
else
{
m_iCount    [compIdx][j][k] = 0;
m_iOffsetOrg[compIdx][j][k] = 0;
}
}
}
saoParam->saoLcuParam[compIdx][addr].typeIdx       =  -1;
saoParam->saoLcuParam[compIdx][addr].mergeUpFlag   = 0;
saoParam->saoLcuParam[compIdx][addr].mergeLeftFlag = 0;
saoParam->saoLcuParam[compIdx][addr].subTypeIdx    = 0;
#if SAO_ENCODING_CHOICE
if( (compIdx ==0 && saoParam->bSaoFlag[0])|| (compIdx >0 && saoParam->bSaoFlag[1]) )
#endif
{
//统计BO和EO的各个模式下,对应classIdx下滤波前的重建像素值与原始像素值的差值的总和,以及对classIdx的计数
//addr当前LCU首地址,compIdx是指Y,Cb,Cr
calcSaoStatsCu(addr, compIdx,  compIdx);
}
}
//Y分量最佳滤波模式的选择
saoComponentParamDist(allowMergeLeft, allowMergeUp, saoParam, addr, addrUp, addrLeft, 0,  lambda, &mergeSaoParam[0][0], &compDistortion[0]);//完成最佳SAPTYPE的选择,得到最佳dCostPartBest和tyepIdx
//CbCr最佳滤波模式选择
sao2ChromaParamDist(allowMergeLeft, allowMergeUp, saoParam, addr, addrUp, addrLeft, lambdaChroma, &mergeSaoParam[1][0], &mergeSaoParam[2][0], &compDistortion[0]);

//计算亮度和色度模式下的参数融合率失真
if( saoParam->bSaoFlag[0] || saoParam->bSaoFlag[1] )
{
// Cost of new SAO_params初始化参数
m_pcRDGoOnSbacCoder->load(m_pppcRDSbacCoder[0][CI_CURR_BEST]);
m_pcRDGoOnSbacCoder->resetBits();
if (allowMergeLeft)
{
m_pcEntropyCoder->m_pcEntropyCoderIf->codeSaoMerge(0);
}
if (allowMergeUp)
{
m_pcEntropyCoder->m_pcEntropyCoderIf->codeSaoMerge(0);
}
for ( compIdx=0;compIdx<3;compIdx++)
{
if( (compIdx ==0 && saoParam->bSaoFlag[0]) || (compIdx >0 && saoParam->bSaoFlag[1]))
{
m_pcEntropyCoder->encodeSaoOffset(&saoParam->saoLcuParam[compIdx][addr], compIdx);
//cost of merge
}
}

rate = m_pcEntropyCoder->getNumberOfWrittenBits();
bestCost = compDistortion[0] + (Double)rate;
m_pcRDGoOnSbacCoder->store(m_pppcRDSbacCoder[0][CI_TEMP_BEST]);

//计算参数融合模式与不补偿模式下的率失真
for(Int mergeUp=0; mergeUp<2; ++mergeUp)
{
if ( (allowMergeLeft && (mergeUp==0)) || (allowMergeUp && (mergeUp==1)) )
{
m_pcRDGoOnSbacCoder->load(m_pppcRDSbacCoder[0][CI_CURR_BEST]);
m_pcRDGoOnSbacCoder->resetBits();
if (allowMergeLeft)
{
m_pcEntropyCoder->m_pcEntropyCoderIf->codeSaoMerge(1-mergeUp);
}
if ( allowMergeUp && (mergeUp==1) )
{
m_pcEntropyCoder->m_pcEntropyCoderIf->codeSaoMerge(1);
}

rate = m_pcEntropyCoder->getNumberOfWrittenBits();//码率
mergeCost = compDistortion[mergeUp+1] + (Double)rate;//率失真
if (mergeCost < bestCost)//更新最佳滤波模式
{
bestCost = mergeCost;
m_pcRDGoOnSbacCoder->store(m_pppcRDSbacCoder[0][CI_TEMP_BEST]);
for ( compIdx=0;compIdx<3;compIdx++)
{
mergeSaoParam[compIdx][mergeUp].mergeLeftFlag = 1-mergeUp;
mergeSaoParam[compIdx][mergeUp].mergeUpFlag = mergeUp;
if( (compIdx==0 && saoParam->bSaoFlag[0]) || (compIdx>0 && saoParam->bSaoFlag[1]))
{
copySaoUnit(&saoParam->saoLcuParam[compIdx][addr], &mergeSaoParam[compIdx][mergeUp] );
}
}
}
}
}
#if SAO_ENCODING_CHOICE
#if SAO_ENCODING_CHOICE_CHROMA
if( saoParam->saoLcuParam[0][addr].typeIdx == -1)//Y分量不存在SAO参数
{
numNoSao[0]++;
}
if( saoParam->saoLcuParam[1][addr].typeIdx == -1)//CbCr分量不存在SAO参数
{
numNoSao[1]+=2;
}
#else
for ( compIdx=0;compIdx<3;compIdx++)
{
if( depth == 0 && saoParam->saoLcuParam[compIdx][addr].typeIdx == -1)
{
numNoSao++;
}
}
#endif
#endif
m_pcRDGoOnSbacCoder->load(m_pppcRDSbacCoder[0][CI_TEMP_BEST]);
m_pcRDGoOnSbacCoder->store(m_pppcRDSbacCoder[0][CI_CURR_BEST]);
}//!< if( saoParam->bSaoFlag[0] || saoParam->bSaoFlag[1] )
}//!< for (idxX = 0; idxX< frameWidthInCU; idxX++)
}//!< for (idxY = 0; idxY< frameHeightInCU; idxY++)
#if SAO_ENCODING_CHOICE
#if SAO_ENCODING_CHOICE_CHROMA
if( !saoParam->bSaoFlag[0])
{
m_depthSaoRate[0][depth] = 1.0;
}
else
{
m_depthSaoRate[0][depth] = numNoSao[0]/((Double) frameHeightInCU*frameWidthInCU);
}
if( !saoParam->bSaoFlag[1])
{
m_depthSaoRate[1][depth] = 1.0;
}
else
{
m_depthSaoRate[1][depth] = numNoSao[1]/((Double) frameHeightInCU*frameWidthInCU*2);
}
#else
if( depth == 0)
{
// update SAO Rate
m_depth0SaoRate = numNoSao/((Double) frameHeightInCU*frameWidthInCU*3);
}
#endif
#endif

}


2.calcSaoStatsCu:

/** Calculate SAO statistics for current LCU
* \param  iAddr,  iPartIdx,  iYCbCr
*/
Void TEncSampleAdaptiveOffset::calcSaoStatsCu(Int iAddr, Int iPartIdx, Int iYCbCr)
{
if(!m_bUseNIF)//true for performing non-cross slice  boundary ALF
{
calcSaoStatsCuOrg( iAddr, iPartIdx, iYCbCr);
}
else
{
Int64** ppStats = m_iOffsetOrg[iPartIdx];
Int64** ppCount = m_iCount    [iPartIdx];

//parameters
Int  isChroma = (iYCbCr != 0)? 1:0;
Int  stride   = (iYCbCr != 0)?(m_pcPic->getCStride()):(m_pcPic->getStride());
Pel* pPicOrg = getPicYuvAddr (m_pcPic->getPicYuvOrg(), iYCbCr);
Pel* pPicRec  = getPicYuvAddr(m_pcYuvTmp, iYCbCr);

std::vector<NDBFBlockInfo>& vFilterBlocks = *(m_pcPic->getCU(iAddr)->getNDBFilterBlocks());

//variables
UInt  xPos, yPos, width, height;
Bool* pbBorderAvail;
UInt  posOffset;

for(Int i=0; i< vFilterBlocks.size(); i++)//对色度块大小做基于对应亮度块大小的调整
{
xPos        = vFilterBlocks[i].posX   >> isChroma;
yPos        = vFilterBlocks[i].posY   >> isChroma;
width       = vFilterBlocks[i].width  >> isChroma;
height      = vFilterBlocks[i].height >> isChroma;
pbBorderAvail = vFilterBlocks[i].isBorderAvailable;

posOffset = (yPos* stride) + xPos;
//! 对ppStats,ppCount赋值,分别计算出对应滤波模式下原始像素与重建像素之间的差值,重建值对应的classIdx的统计值  
calcSaoStatsBlock(pPicRec+ posOffset, pPicOrg+ posOffset, stride, ppStats, ppCount,width, height, pbBorderAvail, iYCbCr);
}
}

}

3.calcSaoStatsCu:
/** Calculate SAO statistics for current LCU
* \param  iAddr,  iPartIdx,  iYCbCr
*/
Void TEncSampleAdaptiveOffset::calcSaoStatsCu(Int iAddr, Int iPartIdx, Int iYCbCr)
{
if(!m_bUseNIF)//true for performing non-cross slice  boundary ALF
{
calcSaoStatsCuOrg( iAddr, iPartIdx, iYCbCr);
}
else
{
Int64** ppStats = m_iOffsetOrg[iPartIdx];
Int64** ppCount = m_iCount    [iPartIdx];

//parameters
Int  isChroma = (iYCbCr != 0)? 1:0;
Int  stride   = (iYCbCr != 0)?(m_pcPic->getCStride()):(m_pcPic->getStride());
Pel* pPicOrg = getPicYuvAddr (m_pcPic->getPicYuvOrg(), iYCbCr);
Pel* pPicRec  = getPicYuvAddr(m_pcYuvTmp, iYCbCr);

std::vector<NDBFBlockInfo>& vFilterBlocks = *(m_pcPic->getCU(iAddr)->getNDBFilterBlocks());

//variables
UInt  xPos, yPos, width, height;
Bool* pbBorderAvail;
UInt  posOffset;

for(Int i=0; i< vFilterBlocks.size(); i++)//对色度块大小做基于对应亮度块大小的调整
{
xPos        = vFilterBlocks[i].posX   >> isChroma;
yPos        = vFilterBlocks[i].posY   >> isChroma;
width       = vFilterBlocks[i].width  >> isChroma;
height      = vFilterBlocks[i].height >> isChroma;
pbBorderAvail = vFilterBlocks[i].isBorderAvailable;

posOffset = (yPos* stride) + xPos;
//! 对ppStats,ppCount赋值,分别计算出对应滤波模式下原始像素与重建像素之间的差值,重建值对应的classIdx的统计值  
calcSaoStatsBlock(pPicRec+ posOffset, pPicOrg+ posOffset, stride, ppStats, ppCount,width, height, pbBorderAvail, iYCbCr);
}
}

}

4.calcSaoStatsBlock
/** Calculate SAO statistics for non-cross-slice or non-cross-tile processing
* \param  pRecStart to-be-filtered block buffer pointer
* \param  pOrgStart original block buffer pointer
* \param  stride picture buffer stride
* \param  ppStat statistics buffer
* \param  ppCount counter buffer
* \param  width block width
* \param  height block height
* \param  pbBorderAvail availabilities of block border pixels
*/
Void TEncSampleAdaptiveOffset::calcSaoStatsBlock( Pel* pRecStart, Pel* pOrgStart, Int stride, Int64** ppStats, Int64** ppCount, UInt width, UInt height, Bool* pbBorderAvail, Int iYCbCr)
{
Int64 *stats, *count;
Int classIdx, posShift, startX, endX, startY, endY, signLeft,signRight,signDown,signDown1;
Pel *pOrg, *pRec;
UInt edgeType;
Int x, y;
Pel *pTableBo = (iYCbCr==0)?m_lumaTableBo:m_chromaTableBo;//band offset 的索引表,共32个bands

//--------- Band offset-----------//
stats = ppStats[SAO_BO];
count = ppCount[SAO_BO];
pOrg   = pOrgStart;
pRec   = pRecStart;
for (y=0; y< height; y++)
{
for (x=0; x< width; x++)
{
classIdx = pTableBo[pRec[x]];//当前像素所属边带
if (classIdx)
{
stats[classIdx] += (pOrg[x] - pRec[x]); //边带差值求和
count[classIdx] ++;//对应的classIdx统计值加1
}
}
pOrg += stride;
pRec += stride;
}
//---------- Edge offset 0--------------//水平模式
stats = ppStats[SAO_EO_0];
count = ppCount[SAO_EO_0];
pOrg   = pOrgStart;
pRec   = pRecStart;

//设置起点和终点
startX = (pbBorderAvail[SGU_L]) ? 0 : 1;
endX   = (pbBorderAvail[SGU_R]) ? width : (width -1);
for (y=0; y< height; y++)
{
signLeft = xSign(pRec[startX] - pRec[startX-1]);//初始化第一个标志
for (x=startX; x< endX; x++)
{
signRight =  xSign(pRec[x] - pRec[x+1]); //第二个标志
edgeType =  signRight + signLeft + 2;//像素种类
signLeft  = -signRight;//减少了一次计算量

stats[m_auiEoTable[edgeType]] += (pOrg[x] - pRec[x]);//像素差值
count[m_auiEoTable[edgeType]] ++;
}
pRec  += stride;
pOrg += stride;
}

//---------- Edge offset 1--------------//垂直模式
stats = ppStats[SAO_EO_1];
count = ppCount[SAO_EO_1];
pOrg   = pOrgStart;
pRec   = pRecStart;

startY = (pbBorderAvail[SGU_T]) ? 0 : 1;
endY   = (pbBorderAvail[SGU_B]) ? height : height-1;
if (!pbBorderAvail[SGU_T])
{
pRec  += stride;
pOrg  += stride;
}

for (x=0; x< width; x++)
{
m_iUpBuff1[x] = xSign(pRec[x] - pRec[x-stride]);
}
for (y=startY; y<endY; y++)
{
for (x=0; x< width; x++)
{
signDown     =  xSign(pRec[x] - pRec[x+stride]);
edgeType    =  signDown + m_iUpBuff1[x] + 2;
m_iUpBuff1[x] = -signDown;

stats[m_auiEoTable[edgeType]] += (pOrg[x] - pRec[x]);
count[m_auiEoTable[edgeType]] ++;
}
pOrg += stride;
pRec += stride;
}
//---------- Edge offset 2--------------//135度方向,这里根据边界像素是否可用又分了4种情况
stats = ppStats[SAO_EO_2];
count = ppCount[SAO_EO_2];
pOrg   = pOrgStart;
pRec   = pRecStart;

posShift= stride + 1;//对比像素的位移,这些处理的都是non-cross slice 与non-cross Tile的像素

startX = (pbBorderAvail[SGU_L]) ? 0 : 1 ;//左边不可用就放弃一个像素
endX   = (pbBorderAvail[SGU_R]) ? width : (width-1);//右边不可用就放弃一个像素

//prepare 2nd line upper sign
pRec += stride;
for (x=startX; x< endX+1; x++)
{
m_iUpBuff1[x] = xSign(pRec[x] - pRec[x- posShift]);
}

//1st line
pRec -= stride;
if(pbBorderAvail[SGU_TL])
{
x= 0;
edgeType      =  xSign(pRec[x] - pRec[x- posShift]) - m_iUpBuff1[x+1] + 2;
stats[m_auiEoTable[edgeType]] += (pOrg[x] - pRec[x]);
count[m_auiEoTable[edgeType]] ++;
}
if(pbBorderAvail[SGU_T])
{
for(x= 1; x< endX; x++)
{
edgeType      =  xSign(pRec[x] - pRec[x- posShift]) - m_iUpBuff1[x+1] + 2;
stats[m_auiEoTable[edgeType]] += (pOrg[x] - pRec[x]);
count[m_auiEoTable[edgeType]] ++;
}
}
pRec   += stride;
pOrg   += stride;

//middle lines
for (y= 1; y< height-1; y++)
{
for (x=startX; x<endX; x++)
{
signDown1      =  xSign(pRec[x] - pRec[x+ posShift]) ;//当前和左上
edgeType      =  signDown1 + m_iUpBuff1[x] + 2;//类型
stats[m_auiEoTable[edgeType]] += (pOrg[x] - pRec[x]);//计算差值
count[m_auiEoTable[edgeType]] ++;//统计数目

m_iUpBufft[x+1] = -signDown1;
}
m_iUpBufft[startX] = xSign(pRec[stride+startX] - pRec[startX-1]);

ipSwap     = m_iUpBuff1;
m_iUpBuff1 = m_iUpBufft;
m_iUpBufft = ipSwap;

pRec  += stride;
pOrg  += stride;
}

//last line
if(pbBorderAvail[SGU_B])
{
for(x= startX; x< width-1; x++)
{
edgeType =  xSign(pRec[x] - pRec[x+ posShift]) + m_iUpBuff1[x] + 2;
stats[m_auiEoTable[edgeType]] += (pOrg[x] - pRec[x]);
count[m_auiEoTable[edgeType]] ++;
}
}
if(pbBorderAvail[SGU_BR])
{
x= width -1;
edgeType =  xSign(pRec[x] - pRec[x+ posShift]) + m_iUpBuff1[x] + 2;
stats[m_auiEoTable[edgeType]] += (pOrg[x] - pRec[x]);
count[m_auiEoTable[edgeType]] ++;
}

//---------- Edge offset 3--------------//45度方向,处理方式类似上一个

//惯例获取操作buf的地址
stats = ppStats[SAO_EO_3];
count = ppCount[SAO_EO_3];
pOrg   = pOrgStart;
pRec   = pRecStart;

posShift     = stride - 1;
startX = (pbBorderAvail[SGU_L]) ? 0 : 1;//同样L、R。
endX   = (pbBorderAvail[SGU_R]) ? width : (width -1);

//prepare 2nd line upper sign,计算第二行的upper
pRec += stride;
for (x=startX-1; x< endX; x++)//先循环一行
{
m_iUpBuff1[x] = xSign(pRec[x] - pRec[x- posShift]);//计算除第一行沿方向45度的像素差值和
}

//first line
pRec -= stride;//回到第一行
if(pbBorderAvail[SGU_T])//上边界可用
{
for(x= startX; x< width -1; x++)
{
edgeType = xSign(pRec[x] - pRec[x- posShift]) -m_iUpBuff1[x-1] + 2;
stats[m_auiEoTable[edgeType]] += (pOrg[x] - pRec[x]);
count[m_auiEoTable[edgeType]] ++;
}
}
if(pbBorderAvail[SGU_TR])//右上可用
{
x= width-1;
edgeType = xSign(pRec[x] - pRec[x- posShift]) -m_iUpBuff1[x-1] + 2;
stats[m_auiEoTable[edgeType]] += (pOrg[x] - pRec[x]);
count[m_auiEoTable[edgeType]] ++;
}
pRec  += stride;
pOrg  += stride;

//middle lines除第一行和最后一行
for (y= 1; y< height-1; y++)
{
for(x= startX; x< endX; x++)
{
signDown1      =  xSign(pRec[x] - pRec[x+ posShift]) ;
edgeType      =  signDown1 + m_iUpBuff1[x] + 2;

stats[m_auiEoTable[edgeType]] += (pOrg[x] - pRec[x]);
count[m_auiEoTable[edgeType]] ++;
m_iUpBuff1[x-1] = -signDown1;

}
m_iUpBuff1[endX-1] = xSign(pRec[endX-1 + stride] - pRec[endX]);

pRec  += stride;
pOrg  += stride;
}

//last line//最后一行不可用就舍弃
if(pbBorderAvail[SGU_BL])//bottom left available
{
x= 0;
edgeType = xSign(pRec[x] - pRec[x+ posShift]) + m_iUpBuff1[x] + 2;
stats[m_auiEoTable[edgeType]] += (pOrg[x] - pRec[x]);
count[m_auiEoTable[edgeType]] ++;

}
if(pbBorderAvail[SGU_B])
{
for(x= 1; x< endX; x++)
{
edgeType = xSign(pRec[x] - pRec[x+ posShift]) + m_iUpBuff1[x] + 2;
stats[m_auiEoTable[edgeType]] += (pOrg[x] - pRec[x]);
count[m_auiEoTable[edgeType]] ++;
}
}
}


5.saoComponentParamDist

Void TEncSampleAdaptiveOffset::saoComponentParamDist(Int allowMergeLeft, Int allowMergeUp, SAOParam *saoParam, Int addr, Int addrUp, Int addrLeft, Int yCbCr, Double lambda, SaoLcuParam *compSaoParam, Double *compDistortion)
{
//参数初始化
Int typeIdx;

Int64 estDist;
Int classIdx;
Int shift = 2 * DISTORTION_PRECISION_ADJUSTMENT(((yCbCr==0)?g_bitDepthY:g_bitDepthC)-8);//0 for 8bit-depth
Int64 bestDist;

SaoLcuParam*  saoLcuParam = &(saoParam->saoLcuParam[yCbCr][addr]);
SaoLcuParam*  saoLcuParamNeighbor = NULL;

resetSaoUnit(saoLcuParam);
resetSaoUnit(&compSaoParam[0]);//左邻块的SAO参数
resetSaoUnit(&compSaoParam[1]);//上邻块的SAO参数

Double dCostPartBest = MAX_DOUBLE;

Double  bestRDCostTableBo = MAX_DOUBLE;
Int     bestClassTableBo    = 0;
Int     currentDistortionTableBo[MAX_NUM_SAO_CLASS];
Double  currentRdCostTableBo[MAX_NUM_SAO_CLASS];

SaoLcuParam   saoLcuParamRdo;
Double   estRate = 0;

resetSaoUnit(&saoLcuParamRdo);

m_pcRDGoOnSbacCoder->load(m_pppcRDSbacCoder[0][CI_TEMP_BEST]);
m_pcRDGoOnSbacCoder->resetBits();
m_pcEntropyCoder->encodeSaoOffset(&saoLcuParamRdo, yCbCr);

dCostPartBest = m_pcEntropyCoder->getNumberOfWrittenBits()*lambda ;
copySaoUnit(saoLcuParam, &saoLcuParamRdo );
bestDist = 0;

for (typeIdx=0; typeIdx<MAX_NUM_SAO_TYPE; typeIdx++)//遍历所有的滤波类型
{
estDist = estSaoTypeDist(yCbCr, typeIdx, shift, lambda, currentDistortionTableBo, currentRdCostTableBo);//得到当前滤波类型的像素差值

if( typeIdx == SAO_BO )
{
// Estimate Best Position
Double currentRDCost = 0.0;

//边带模式,每次以4个band为单位进行RDcost的累加,选出代价最小的四个边带
for(Int i=0; i< SAO_MAX_BO_CLASSES -SAO_BO_LEN +1; i++)
{
currentRDCost = 0.0;
for(UInt uj = i; uj < i+SAO_BO_LEN; uj++)
{
currentRDCost += currentRdCostTableBo[uj];
}

if( currentRDCost < bestRDCostTableBo)
{
bestRDCostTableBo = currentRDCost;
bestClassTableBo  = i;
}
}

// Re code all Offsets
// Code Center
//计算最佳四个边带模式下的estDist(像素误差和)
estDist = 0;
for(classIdx = bestClassTableBo; classIdx < bestClassTableBo+SAO_BO_LEN; classIdx++)
{
estDist += currentDistortionTableBo[classIdx];
}
}

resetSaoUnit(&saoLcuParamRdo);
saoLcuParamRdo.length = m_iNumClass[typeIdx];
saoLcuParamRdo.typeIdx = typeIdx;
saoLcuParamRdo.mergeLeftFlag = 0;
saoLcuParamRdo.mergeUpFlag   = 0;
saoLcuParamRdo.subTypeIdx = (typeIdx == SAO_BO) ? bestClassTableBo : 0;
for (classIdx = 0; classIdx < saoLcuParamRdo.length; classIdx++)
{
saoLcuParamRdo.offset[classIdx] = (Int)m_iOffset[yCbCr][typeIdx][classIdx+saoLcuParamRdo.subTypeIdx+1];
}
m_pcRDGoOnSbacCoder->load(m_pppcRDSbacCoder[0][CI_TEMP_BEST]);
m_pcRDGoOnSbacCoder->resetBits();
m_pcEntropyCoder->encodeSaoOffset(&saoLcuParamRdo, yCbCr);

estRate = m_pcEntropyCoder->getNumberOfWrittenBits();
m_dCost[yCbCr][typeIdx] = (Double)((Double)estDist + lambda * (Double) estRate);

if(m_dCost[yCbCr][typeIdx] < dCostPartBest)//更新最佳值
{
dCostPartBest = m_dCost[yCbCr][typeIdx];
copySaoUnit(saoLcuParam, &saoLcuParamRdo );
bestDist = estDist;
}
}//!< for (typeIdx=0; typeIdx<MAX_NUM_SAO_TYPE; typeIdx++)
compDistortion[0] += ((Double)bestDist/lambda);
m_pcRDGoOnSbacCoder->load(m_pppcRDSbacCoder[0][CI_TEMP_BEST]);
m_pcEntropyCoder->encodeSaoOffset(saoLcuParam, yCbCr);
m_pcRDGoOnSbacCoder->store( m_pppcRDSbacCoder[0][CI_TEMP_BEST] );

// merge left or merge up

for (Int idxNeighbor=0;idxNeighbor<2;idxNeighbor++)
{
saoLcuParamNeighbor = NULL;
if (allowMergeLeft && addrLeft>=0 && idxNeighbor ==0)//!< 左邻块可用
{
saoLcuParamNeighbor = &(saoParam->saoLcuParam[yCbCr][addrLeft]);//!< 取左邻块的SAO参数
}
else if (allowMergeUp && addrUp>=0 && idxNeighbor ==1)//!< 上邻块可用
{
saoLcuParamNeighbor = &(saoParam->saoLcuParam[yCbCr][addrUp]);//!< 取上邻块的SAO参数
}
if (saoLcuParamNeighbor!=NULL)
{
estDist = 0;
typeIdx = saoLcuParamNeighbor->typeIdx;//取邻块的SAO模式
if (typeIdx>=0)
{
Int mergeBandPosition = (typeIdx == SAO_BO)?saoLcuParamNeighbor->subTypeIdx:0;
Int   merge_iOffset;
for(classIdx = 0; classIdx < m_iNumClass[typeIdx]; classIdx++)
{
merge_iOffset = saoLcuParamNeighbor->offset[classIdx];
estDist   += estSaoDist(m_iCount [yCbCr][typeIdx][classIdx+mergeBandPosition+1], merge_iOffset, m_iOffsetOrg[yCbCr][typeIdx][classIdx+mergeBandPosition+1],  shift);
}
}
else
{
estDist = 0;
}

copySaoUnit(&compSaoParam[idxNeighbor], saoLcuParamNeighbor );
compSaoParam[idxNeighbor].mergeUpFlag   = idxNeighbor;
compSaoParam[idxNeighbor].mergeLeftFlag = !idxNeighbor;

compDistortion[idxNeighbor+1] += ((Double)estDist/lambda);
}
}
}


6.补充一个calcSaoStatsCuOrgcalcSaoStatsCu中被调用

/** Calculate SAO statistics for current LCU without non-crossing slice
* \param  iAddr,  iPartIdx,  iYCbCr
*/
//得到LCU中所有像素各种SAOType的所有信息和状态统计,分别记录SAOType(SAO_EO_0,...,SAO_BO)以及各种SAOTypeLen和YUV分量对应的m_iOffsetOrg和m_iCount。
Void TEncSampleAdaptiveOffset::calcSaoStatsCuOrg(Int iAddr, Int iPartIdx, Int iYCbCr)
{
Int x,y;
TComDataCU *pTmpCu = m_pcPic->getCU(iAddr);
TComSPS *pTmpSPS =  m_pcPic->getSlice(0)->getSPS();

Pel* pOrg;
Pel* pRec;
Int iStride;
Int iLcuHeight = pTmpSPS->getMaxCUHeight();
Int iLcuWidth  = pTmpSPS->getMaxCUWidth();
UInt uiLPelX   = pTmpCu->getCUPelX();
UInt uiTPelY   = pTmpCu->getCUPelY();
UInt uiRPelX;
UInt uiBPelY;
Int64* iStats;
Int64* iCount;
Int iClassIdx;
Int iPicWidthTmp;
Int iPicHeightTmp;
Int iStartX;
Int iStartY;
Int iEndX;
Int iEndY;
Pel* pTableBo = (iYCbCr==0)?m_lumaTableBo:m_chromaTableBo;

Int iIsChroma = (iYCbCr!=0)? 1:0;
Int numSkipLine = iIsChroma? 2:4;
if (m_saoLcuBasedOptimization == 0)
{
numSkipLine = 0;
}

Int numSkipLineRight = iIsChroma? 3:5;
if (m_saoLcuBasedOptimization == 0)
{
numSkipLineRight = 0;
}

iPicWidthTmp  = m_iPicWidth  >> iIsChroma;
iPicHeightTmp = m_iPicHeight >> iIsChroma;
iLcuWidth     = iLcuWidth    >> iIsChroma;
iLcuHeight    = iLcuHeight   >> iIsChroma;
uiLPelX       = uiLPelX      >> iIsChroma;
uiTPelY       = uiTPelY      >> iIsChroma;
uiRPelX       = uiLPelX + iLcuWidth  ;
uiBPelY       = uiTPelY + iLcuHeight ;
uiRPelX       = uiRPelX > iPicWidthTmp  ? iPicWidthTmp  : uiRPelX;
uiBPelY       = uiBPelY > iPicHeightTmp ? iPicHeightTmp : uiBPelY;
iLcuWidth     = uiRPelX - uiLPelX;
iLcuHeight    = uiBPelY - uiTPelY;

iStride    =  (iYCbCr == 0)? m_pcPic->getStride(): m_pcPic->getCStride();

//如果是BO,通过
//if(iSaoType == BO_0 || iSaoType == BO_1)
{
if( m_saoLcuBasedOptimization && m_saoLcuBoundary )
{
numSkipLine = iIsChroma? 1:3;
numSkipLineRight = iIsChroma? 2:4;
}
iStats = m_iOffsetOrg[iPartIdx][SAO_BO];//YUV and SAO_TYPE
iCount = m_iCount    [iPartIdx][SAO_BO];

pOrg = getPicYuvAddr(m_pcPic->getPicYuvOrg(), iYCbCr, iAddr);
pRec = getPicYuvAddr(m_pcPic->getPicYuvRec(), iYCbCr, iAddr);

iEndX   = (uiRPelX == iPicWidthTmp) ? iLcuWidth : iLcuWidth-numSkipLineRight;
iEndY   = (uiBPelY == iPicHeightTmp) ? iLcuHeight : iLcuHeight-numSkipLine;
for (y=0; y<iEndY; y++)
{
for (x=0; x<iEndX; x++)
{
iClassIdx = pTableBo[pRec[x]];//确定属于那个条带,并记录
if (iClassIdx)//确定后做记录
{
iStats[iClassIdx] += (pOrg[x] - pRec[x]); //iClassIdx大小为0~32???但边带仅有32条??
iCount[iClassIdx] ++;
}
}
pOrg += iStride;
pRec += iStride;
}

}
Int iSignLeft;
Int iSignRight;
Int iSignDown;
Int iSignDown1;
Int iSignDown2;

UInt uiEdgeType;

//如果是EO, 通过iClassIdx =m_auiEoTable[uiEdgeType]来确定模板类型,并记录iStats[iClassIdx] += (pOrg[x] -pRec[x]);iCount[iClassIdx]++;
//if (iSaoType == EO_0  || iSaoType == EO_1 || iSaoType == EO_2 || iSaoType == EO_3)
{
//if (iSaoType == EO_0)
{
if( m_saoLcuBasedOptimization && m_saoLcuBoundary )
{
numSkipLine = iIsChroma? 1:3;
numSkipLineRight = iIsChroma? 3:5;
}
iStats = m_iOffsetOrg[iPartIdx][SAO_EO_0];
iCount = m_iCount    [iPartIdx][SAO_EO_0];

pOrg = getPicYuvAddr(m_pcPic->getPicYuvOrg(), iYCbCr, iAddr);
pRec = getPicYuvAddr(m_pcPic->getPicYuvRec(), iYCbCr, iAddr);

iStartX = (uiLPelX == 0) ? 1 : 0;
iEndX   = (uiRPelX == iPicWidthTmp) ? iLcuWidth-1 : iLcuWidth-numSkipLineRight;
for (y=0; y<iLcuHeight-numSkipLine; y++)
{
iSignLeft = xSign(pRec[iStartX] - pRec[iStartX-1]);
for (x=iStartX; x< iEndX; x++)
{
iSignRight =  xSign(pRec[x] - pRec[x+1]);
uiEdgeType =  iSignRight + iSignLeft + 2;
iSignLeft  = -iSignRight;

iStats[m_auiEoTable[uiEdgeType]] += (pOrg[x] - pRec[x]);
iCount[m_auiEoTable[uiEdgeType]] ++;
}
pOrg += iStride;
pRec += iStride;
}
}

//if (iSaoType == EO_1)
{
if( m_saoLcuBasedOptimization && m_saoLcuBoundary )
{
numSkipLine = iIsChroma? 2:4;
numSkipLineRight = iIsChroma? 2:4;
}
iStats = m_iOffsetOrg[iPartIdx][SAO_EO_1];
iCount = m_iCount    [iPartIdx][SAO_EO_1];

pOrg = getPicYuvAddr(m_pcPic->getPicYuvOrg(), iYCbCr, iAddr);
pRec = getPicYuvAddr(m_pcPic->getPicYuvRec(), iYCbCr, iAddr);

iStartY = (uiTPelY == 0) ? 1 : 0;
iEndX   = (uiRPelX == iPicWidthTmp) ? iLcuWidth : iLcuWidth-numSkipLineRight;
iEndY   = (uiBPelY == iPicHeightTmp) ? iLcuHeight-1 : iLcuHeight-numSkipLine;
if (uiTPelY == 0)
{
pOrg += iStride;
pRec += iStride;
}

for (x=0; x< iLcuWidth; x++)
{
m_iUpBuff1[x] = xSign(pRec[x] - pRec[x-iStride]);
}
for (y=iStartY; y<iEndY; y++)
{
for (x=0; x<iEndX; x++)
{
iSignDown     =  xSign(pRec[x] - pRec[x+iStride]);
uiEdgeType    =  iSignDown + m_iUpBuff1[x] + 2;
m_iUpBuff1[x] = -iSignDown;

iStats[m_auiEoTable[uiEdgeType]] += (pOrg[x] - pRec[x]);
iCount[m_auiEoTable[uiEdgeType]] ++;
}
pOrg += iStride;
pRec += iStride;
}
}
//if (iSaoType == EO_2)
{
if( m_saoLcuBasedOptimization && m_saoLcuBoundary )
{
numSkipLine = iIsChroma? 2:4;
numSkipLineRight = iIsChroma? 3:5;
}
iStats = m_iOffsetOrg[iPartIdx][SAO_EO_2];
iCount = m_iCount    [iPartIdx][SAO_EO_2];

pOrg = getPicYuvAddr(m_pcPic->getPicYuvOrg(), iYCbCr, iAddr);
pRec = getPicYuvAddr(m_pcPic->getPicYuvRec(), iYCbCr, iAddr);

iStartX = (uiLPelX == 0) ? 1 : 0;
iEndX   = (uiRPelX == iPicWidthTmp) ? iLcuWidth-1 : iLcuWidth-numSkipLineRight;

iStartY = (uiTPelY == 0) ? 1 : 0;
iEndY   = (uiBPelY == iPicHeightTmp) ? iLcuHeight-1 : iLcuHeight-numSkipLine;
if (uiTPelY == 0)
{
pOrg += iStride;
pRec += iStride;
}

for (x=iStartX; x<iEndX; x++)
{
m_iUpBuff1[x] = xSign(pRec[x] - pRec[x-iStride-1]);
}
for (y=iStartY; y<iEndY; y++)
{
iSignDown2 = xSign(pRec[iStride+iStartX] - pRec[iStartX-1]);
for (x=iStartX; x<iEndX; x++)
{
iSignDown1      =  xSign(pRec[x] - pRec[x+iStride+1]) ;
uiEdgeType      =  iSignDown1 + m_iUpBuff1[x] + 2;
m_iUpBufft[x+1] = -iSignDown1;
iStats[m_auiEoTable[uiEdgeType]] += (pOrg[x] - pRec[x]);
iCount[m_auiEoTable[uiEdgeType]] ++;
}
m_iUpBufft[iStartX] = iSignDown2;
ipSwap     = m_iUpBuff1;
m_iUpBuff1 = m_iUpBufft;
m_iUpBufft = ipSwap;

pRec += iStride;
pOrg += iStride;
}
}
//if (iSaoType == EO_3  )
{
if( m_saoLcuBasedOptimization && m_saoLcuBoundary )
{
numSkipLine = iIsChroma? 2:4;
numSkipLineRight = iIsChroma? 3:5;
}
iStats = m_iOffsetOrg[iPartIdx][SAO_EO_3];
iCount = m_iCount    [iPartIdx][SAO_EO_3];

pOrg = getPicYuvAddr(m_pcPic->getPicYuvOrg(), iYCbCr, iAddr);
pRec = getPicYuvAddr(m_pcPic->getPicYuvRec(), iYCbCr, iAddr);

iStartX = (uiLPelX == 0) ? 1 : 0;
iEndX   = (uiRPelX == iPicWidthTmp) ? iLcuWidth-1 : iLcuWidth-numSkipLineRight;

iStartY = (uiTPelY == 0) ? 1 : 0;
iEndY   = (uiBPelY == iPicHeightTmp) ? iLcuHeight-1 : iLcuHeight-numSkipLine;
if (iStartY == 1)
{
pOrg += iStride;
pRec += iStride;
}

for (x=iStartX-1; x<iEndX; x++)
{
m_iUpBuff1[x] = xSign(pRec[x] - pRec[x-iStride+1]);
}

for (y=iStartY; y<iEndY; y++)
{
for (x=iStartX; x<iEndX; x++)
{
iSignDown1      =  xSign(pRec[x] - pRec[x+iStride-1]) ;
uiEdgeType      =  iSignDown1 + m_iUpBuff1[x] + 2;
m_iUpBuff1[x-1] = -iSignDown1;
iStats[m_auiEoTable[uiEdgeType]] += (pOrg[x] - pRec[x]);//记录
iCount[m_auiEoTable[uiEdgeType]] ++;
}
m_iUpBuff1[iEndX-1] = xSign(pRec[iEndX-1 + iStride] - pRec[iEndX]);

pRec += iStride;
pOrg += iStride;
}
}
}
}


(转载请注明出处)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息