視頻編碼中常用的二值化方法

在視頻編碼中,在算術編碼之前,要對需要傳輸的符號進行二值化,今天就來學習一下常用的二值化方法。

常見的二值化編碼算法有:一元碼、截斷一元碼、截斷萊斯二值化,K階指數哥倫布編碼,定長編碼,這裏定長編碼就不做介紹了。

一元碼

參考:
https://en.wikipedia.org/wiki/Unary_coding

一元碼是一種十分簡單的二值化方法。對於非負整數N,其一元碼錶示爲N個1加1個0。
例如N=5,一元碼錶示爲111110(5個1加1個0),N=0,一元碼即爲0。

HEVC代碼(實際該函數在代碼中沒有被調用):

//uiSymbol:待編碼符號
Void TEncSbac::xWriteUnarySymbol( UInt uiSymbol, ContextModel* pcSCModel, Int iOffset )
{
    //使用常規編碼器進行編碼,uiSymbol爲0,編碼0,否則編碼1
	m_pcBinIf->encodeBin( uiSymbol ? 1 : 0, pcSCModel[0] );
 
    //uiSymbol爲0,結束編碼()
	if( 0 == uiSymbol)
	{
		return;
	}
    
    //編碼(uiSymbol-1)個1加1個0(加上一開始編碼的1,即編碼了uiSymbol個1加1個0)
	while( uiSymbol-- )
	{
		m_pcBinIf->encodeBin( uiSymbol ? 1 : 0, pcSCModel[ iOffset ] );
	}
 
	return;
}

VVC代碼中也沒有使用一元碼,去掉了該函數。

截斷一元碼

參考:
https://blog.51cto.com/7335580/2070466

截斷一元碼是一元碼的一種變體。在已知待編碼符號的最大值Nmax的情況下,假設當前待編碼符號爲非負整數N,若N<Nmax,截斷一元碼即爲一元碼,若N=Nmax,截斷一元碼爲N個1。
例如:已知Nmax=5,N=3,截斷一元碼爲1110,N=5,截斷一元碼爲11111。

HEVC代碼(mvp-index、delta-qp等語法元素編碼中使用了截斷一元碼):

//uiSymbol:待編碼符號
//uiMaxSymbol:已知待編碼的語法元素的最大值
Void TEncSbac::xWriteUnaryMaxSymbol( UInt uiSymbol, ContextModel* pcSCModel, Int iOffset, UInt uiMaxSymbol )
{
	if (uiMaxSymbol == 0)
	{
		return;
	}
 
	 //使用常規編碼器進行編碼,uiSymbol爲0,編碼0,否則編碼1
	m_pcBinIf->encodeBin( uiSymbol ? 1 : 0, pcSCModel[ 0 ] );
	
    //uiSymbol爲0,結束編碼()
	if ( uiSymbol == 0 )
	{
		return;
	}
 
	// 判斷是否需要截斷
	Bool bCodeLast = ( uiMaxSymbol > uiSymbol );
 
	//編碼(uiSymbol-1)個1
	while( --uiSymbol )
	{
		m_pcBinIf->encodeBin( 1, pcSCModel[ iOffset ] );
	}
	
	// 不需要截斷,直接在後面添加0
	if( bCodeLast )
	{
		m_pcBinIf->encodeBin( 0, pcSCModel[ iOffset ] );
	}
 
	return;
}

VVC代碼中對函數進行了改造:

//symbol:待編碼符號
void CABACWriter::unary_max_symbol( unsigned symbol, unsigned ctxId0, unsigned ctxIdN, unsigned maxSymbol )
{
  //symbol>maxSymbol會拋出異常,正常情況應該是symbol<=maxSymbol
  CHECK( symbol > maxSymbol, "symbol > maxSymbol" );
  
  //寫出bin的總個數,當symbo<maxSymbol,截斷一元碼寫出symbo+1個bin;當symbo=maxSymbol,截斷一元碼寫出maxSymbol個bin。
  const unsigned totalBinsToWrite = std::min( symbol + 1, maxSymbol );
  
  //對Symbol使用截斷一元碼二值化並編碼。當symbol=maxSymbol時,totalBinsToWrite=symbol,會輸出symbol個1;當symbol<maxSymbol0時,totalBinsToWrite=symbol + 1,會輸出symbol個1加1個0。
  for( unsigned binsWritten = 0; binsWritten < totalBinsToWrite; ++binsWritten )
  {
    const unsigned nextBin = symbol > binsWritten;
    m_BinEncoder.encodeBin( nextBin, binsWritten == 0 ? ctxId0 : ctxIdN );
  }
}

截斷萊斯碼

參考:
https://blog.csdn.net/u012803718/article/details/16992915

截斷萊斯碼包括前綴prefix和後綴suffix兩部分,已知最大上限值cMax,萊斯參數cRice,對待編碼符號cVal進行編碼:
1.計算prefix:
prefix = cVal >> cRice,對prefix以cMax>>cRice爲上限制使用截斷一元碼二值化。

2.計算suffix:
如果cVal<cMax:suffix = cVal - prefix <<cRice。
如果cVal>=cMax:無後綴suffix。
suffix按定長碼計算,位數爲cRice。

可以看到,當cRice=0時,截斷萊斯碼實際就是截斷一元碼。

代碼中截斷萊斯碼只有在係數編碼時用到,沒有獨立函數實現。

k階指數哥倫布碼

參考:
https://baike.baidu.com/item/%E6%8C%87%E6%95%B0%E5%93%A5%E4%BC%A6%E5%B8%83%E7%A0%81/22742504?fr=aladdin
https://www.cnblogs.com/wangguchangqing/p/6297792.html

k階指數哥倫布碼也是由前綴和後綴組成,編碼方法如下:
1.將數字X以二進制形式寫出,去掉最低的k個比特,之後加1,求得T。
2.計算T的比特數,前面需要補T-1個零。
3.將去掉的最低k個比特位補回比特串尾部。

前綴就是補的T-1個零加1個1(在步驟1中加了1,因此T大於等於1,一定能找到這個1),剩下的是後綴。也就是說前後綴分界是第一個1,這個1與前面所有的0是前綴,這個1之後是後綴。
這裏前綴後綴沒有詳細解釋,推薦看https://www.cnblogs.com/wangguchangqing/p/6297792.html

例如
X=4,k=1階指數哥倫布碼爲:
1.4的二進制碼爲100,去掉1位之後加1,T=11。
2.T有2位,前面補1個零。
3.在T後補上去掉的1個0,即0110,其中01爲前綴,10爲後綴。

在HEVC、VVC中一般使用1階指數哥倫布碼,需要注意的是實際使用的k階指數哥倫布碼有所不同,這裏給draft給出的算法說明:
在這裏插入圖片描述
按照draft算法,嘗試計算4的1階指數哥倫布碼,最終碼字爲:1010,而按照之前原理給出的方法結果爲0110,是不同的。draft中對此沒有單獨說明,我比對了多組結果:

3 0 理論值:00100 HEVC:11000
4 0 理論值:00101 HEVC:11001
4 1 理論值:0110 HEVC:1010
5 1 理論值:0111 HEVC:1011
5 2 理論值:01001 HEVC:10001

觀察前綴(已加粗),HEVC編碼結果與理論值的前綴相反,這一點個人認爲是爲了便於存儲和表示。代碼中,編碼結果還是以整形數存儲的,高位爲0是無法存儲和表示的,因此對前綴取反,這樣高位一定爲1,就可以記錄編碼結果了,很巧妙。

HEVC代碼如下:

// uiSymbol:待編碼符號
// uiCount:階數K
Void TEncSbac::xWriteEpExGolomb( UInt uiSymbol, UInt uiCount )
{
	UInt bins = 0;
	Int numBins = 0;
 
	while( uiSymbol >= (UInt)(1<<uiCount) )
	{
		bins = 2 * bins + 1;
		numBins++;
		uiSymbol -= 1 << uiCount;
		uiCount  ++;
	}
	bins = 2 * bins + 0;
	numBins++;
	bins = (bins << uiCount) | uiSymbol;
	numBins += uiCount;
	assert( numBins <= 32 );
	m_pcBinIf->encodeBinsEP( bins, numBins );
}

VVC代碼稍有改動,用移位替代了乘2操作:

void CABACWriter::exp_golomb_eqprob( unsigned symbol, unsigned count )
{
  unsigned bins    = 0;
  unsigned numBins = 0;
  while( symbol >= (unsigned)(1<<count) )
  {
    bins <<= 1;
    bins++;
    numBins++;
    symbol -= 1 << count;
    count++;
  }
  bins <<= 1;
  numBins++;
  bins = (bins << count) | symbol;
  numBins += count;
  CHECK(!( numBins <= 32 ), "Unspecified error");
  m_BinEncoder.encodeBinsEP( bins, numBins );
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章