作者: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.補充一個calcSaoStatsCuOrg在calcSaoStatsCu中被調用
/** 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;
}
}
}
}
(轉載請註明出處)