在視頻編碼中,在算術編碼之前,要對需要傳輸的符號進行二值化,今天就來學習一下常用的二值化方法。
常見的二值化編碼算法有:一元碼、截斷一元碼、截斷萊斯二值化,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 );
}