2017年的第一篇博文。
本文主要有以下三部分内容:
介绍了Golomb编码,及其两个变种:Golomb-Rice和Exp-Golomb的基本原理
C++实现了一个简单的BitStream库,能够方便在bit流和byte数字之间进行转换
C++实现了Golomb-Rice和Exp-Golomb的编码,并进行了测试。
在文章的最后提供了本文中的源代码下载。
Golomb编码的基本原理
Golomb编码是一种无损的数据压缩方法,由数学家Solomon W.Golomb在1960年代发明。Golomb编码只能对非负整数进行编码,符号表中的符号出现的概率符合几何分布(Geometric Distribution)时,使用Golomb编码可以取得最优效果,也就是说Golomb编码比较适合小的数字比大的数字出现概率比较高的编码。它使用较短的码长编码较小的数字,较长的码长编码较大的数字。
Golomb编码是一种分组编码,需要一个正整数参数m,然后以m为单位对待编码的数字进行分组,如下图:
对于任一待编码的非负正整数N,Golomb编码将其分为两个部分:所在组的编号GroupID以及分组后余下的部分,GroupID实际是待编码数字N和参数m的商,余下的部分则是其商的余数,具体计算如下:
\[ \begin{array}{c}
q = N / m \\ \\
r = N \% m
\end{array}
\]
对于得到的组号q使用一元编码(Unary code),余下部分r则使用固定长度的二进制编码(binary encoding)。
一元编码(Unary coding)是一种简单的只能对非负整数进行编码的方法,对于任意非负整数num,它的一元编码就是num个1后面紧跟着一个0。例如:
num
Unary coding
0
0
1
10
2
110
3
1110
4
11110
5
111110
其编解码的伪代码如下:
UnaryEncode(n) {
while (n > 0) {
WriteBit(1);
n--;
}
WriteBit(0);
}
UnaryDecode() {
n = 0;
while (ReadBit(1) == 1) {
n++;
}
return n;
}
使用一元编码编码组号也就是商q后,对于余下的部分r则有根据编码数字大小的不同有不同的处理方法。
如果参数m是2的次幂(这也是下面将要介绍的Golomb-Rice编码),则使用取r的二进制表示的低\(\log_2(m)\)位,作为r的码字
如果参数m不是2的次幂,如果m不是2的次幂,设\(b = \lceil{\log_2(m)}\rceil\)
如果\(r < 2^b - m\),则使用b-1位的二进制编码r。
如果\(r \geqq 2^b -m\),则使用b位二进制对\(r+2^b-m\)进行编码
总结,设待编码的非负整数为N,Golomb编码流程如下:
初始化正整数参数m
取得组号q以及余下部分r,计算公式为:\(q = N / m,r = N \% m\)
使用一元编码的方式编码q
使用二进制的方式编码r,r所使用位数的如下:
如果参数m是2的次幂(这也是下面将要介绍的Golomb-Rice编码),则使用取r的二进制表示的低\(\log_2(m)\)位,作为r的码字。
如果参数m不是2的次幂,如果m不是2的次幂,设\(b = \lceil{\log_2(m)}\rceil\)
如果\(r < 2^b - m\),则使用b-1位的二进制编码r。
如果\(r \geqq 2^b -m\),则使用b位二进制对\(r+2^b-m\)进行编码
说明:
\(\lceil a \rceil\) 大于a的最小整数 ceil运算
\(\lfloor a \rfloor\) 小于a的最大整数 floor运算
Golomb-Rice 编码
Golomb-Rice是Golomb编码的一个变种,它给Golomb编码的参数m添加了个限制条件:m必须是2的次幂。这样有两个好处:
不需要做模运算即可得到余数r,r = N & (m - 1)
对余数r编码更为简单,只需要取r二进制的低\(\log_2(m)\)位即可。
则Golomb-Rice的编码过程更为简洁:
初始化参数m,m必须为2的次幂
计算q和r,q = N / m ; r = N & (m - 1)
使用一元编码编码q
取r的二进制位的低\(\log_2(m)\)位作为r的码字。
解码过程如下:
bool b;
uint64_t unary = 0;
b = bitStream.getBit();
while (b)
{
unary++;
b = bitStream.getBit();
}
std::bitset<64> bits;<