寻找更好的压缩技术

8
我正在压缩由数据包组成的二进制流。
每个数据包由256个32位整数(样本)组成。问题在于,大多数整数与前一个整数仅有少量位不同(通常仅有0-4位是不同的)。
以下是一个例子:
3322 2222 2222 1111 1111 1110 0000 0000    BIT POSITIONS
1098 7654 3210 9817 6543 2109 8765 4321
--------------------------------------------------------
1100 1001 1110 0010 0001 0101 0110 1101    Sample 1  
               *                   * 
1100 1001 1110 1010 0001 0101 0110 0101    Sample 2     changes: bit 19, 4

1100 1001 1110 1010 0001 0101 0110 0101    Sample 3     changes: none
     *            *            *   
1100 0001 1110 1011 0001 0101 0010 0101    Sample 4     changes: bit 27, 17, 7
...

我的当前无损压缩方案基于四位字节。基本上,我使用一个控制字节,在其中编码 - 使用单个位 - 前一个样本中哪些四位字节发生了改变; 如果有变化,我将修改后的四位字节包含在压缩流中,否则它们将在解压缩时从前一个样本中重建。

这是我提供的示例流如何被压缩:

Control Byte: 11111111     // all nibbles change, since this is first sample
Data:         1100 1001 1110 0010 0001 0101 0110 1101 // data for all nibbles
Control Byte: 00010001     // only nibbles 3 and 7 have changes
Data:         1010 0101    // data for nibbles 3 and 7
Control Byte: 00000000     // no nibbles are changing
Data:                      // no data is required
Control Byte: 01010010     // nibbles 1, 3 and 6 have changes
Data:         0001 1011 0010   // nibbles 1, 3 and 6
...

使用这种方案,我们有256字节(控制字节)的固定开销,平均可变压缩数据长度为260字节(从样本到样本改变的半字节)。考虑到未压缩数据包的长度为1024字节,这实际上给了我们一个50%的平均压缩率。
这并不差,但我的直觉是还有更好的方法。是否有人知道一种更好的压缩策略可以利用样本之间非常少的位数变化?损失压缩是一个选择,只要解压缩后的比特误差率很小(小于3%),对于这个特定的数据流,比特位置的数字权重是无关紧要的,因此高位发生的错误根本不重要。
提前感谢大家!

数据包中样本的顺序是否重要?如果不重要,您可以在每个数据包内进行排序,以最小化控制字节数量。 - cmh
@cmh,好建议 - 不幸的是,样本的顺序很重要 :( - user1222021
5个回答

6

您最好使用现有的技术(例如,Lempel-Ziv-Welch;flate),或在此类方法之前加上差分编码(可能更好)。通过差分编码,您将用该字节与前一个字节之间的差替换每个字节(除第一个字节外)。现在,您应该得到许多零和少量杂乱的小值。 Huffman编码或类似LZW的编码将对大部分零的字符串进行深度压缩。


@RVic:+1 这看起来非常有前途。使用差分编码,我们最终得到的是一个几乎全为零的比特串。我们一定会尝试的。=) - user1222021
1
哇,使用我的原始技术,24小时的流量大约为14MB。使用您的差异编码建议,然后是LZMA。一个24小时的文件只有37KB!我感到非常高兴! - user1222021
非常感谢您的建议@DRVic。同时也感谢所有提供如此聪明建议的人,大家普遍提出的差异编码(xor)的想法在此之前我并未想过。 - user1222021
1
我完全被搞糊涂了,第一次测试结果是使用差异编码的文本表示。一个包含XOR结果的二进制文件从29MB压缩到了5KB! - user1222021

6
如果您将第一个整数未压缩发送,并为其他 255 个整数计算与前一个整数之间的 XOR,您将得到一个比特流,其中非零比特非常稀少。该比特流可以使用算术编码进行编码。
如果在计算相邻值之间的XOR后,我们有一个比特流,其中每个“0”或“1”比特的概率相互独立(独立于整数中的比特位置和数据包中的整数位置),则算术编码保证最佳无损压缩率。

5

您可以在输入数据上执行异或操作。因为只有少量位发生变化,这将给您带来主要由0组成的结果,其中夹杂着一些1

1100 1001 1110 0010 0001 0101 0110 1101    Sample 1  
1100 1001 1110 1010 0001 0101 0110 0101    Sample 2     
1100 1001 1110 1010 0001 0101 0110 0101    Sample 3     
1100 0001 1110 1011 0001 0101 0010 0101    Sample 4     

在起始值之后,这将产生一个序列。
0b0000 0000 0000 1000 0000 0000 0001 0000, 
0b0000 0000 0000 0000 0000 0000 0000 0000, 
0b0000 1000 0000 0010 0000 0000 1000 0000

您现在可以使用各种标准的压缩算法。其中包括8字节序列的哈夫曼编码,LZW或熵编码,但一个好的尝试可能是简单的位运行长度编码,从位位置0开始计算每个1位之间的零位数量:

4, 14, 51, 9, 9

如果您将运行长度限制为30,并选择一个转义符号31,表示“将31添加到下一个运行长度”,则会得到以下结果:
4, 14, 31, 20, 9, 9

整个序列需要6*5位。你现在可以对此进行哈夫曼编码...


1
从您的示例中,似乎变化的几个位不总是相同的(例如始终是最低的4位)。因此,我建议对转置数组上的位进行简单的运行长度编码。如果没有您的数字/数据分布,我建议从长度为4位开始,但您可以尝试一些示例输入。
压缩的伪代码如下:
 for bitpos = 0 to 31
     for datapos = 0 to 255 
         BitString.append(getbit(data[datapos], bitpos);
     endfor
 endfor

 result="";
 pos = 0;
 while (notEndOfString)
     # count 1s
     count = 0;
     while (pos < 32*256 AND count < 16 AND BitString[pos]==1)
         count++;
         pos++;
         endwhile
     result.append4BitNumber(count);
     # count 0s
     count = 0;
     while (pos < 32*256 AND count < 16 AND BitString[pos]==0)
         count++;
         pos++;
         endwhile
     result.append4BitNumber(count);
 endwhile

也许可以通过后续应用Lempel-Ziv或Huffman编码来增加压缩率,但是如果没有更多关于输入数据分布的信息,就无法说更多(这适用于一般的问题 - 如果有更好的输入数据信息,就可以为其量身定制某种压缩)。
编辑:另一种简单的方法是对变化位位置进行编码: 您从初始32位字开始,然后为每个数据字存储3位,定义有多少位发生变化(即0..7),然后您存储0..7次4位,其中4位编码了变化位的位置。这意味着当平均有2位发生变化时,您需要为您的32 * 256位数据包使用32 + 255 *(3 + 8)= 2837 =>大约为其原始大小的35%。
如果您经常有相同数量的位发生变化,则其中一些4位模式会非常频繁地出现,而其他则根本不会=>在这些4位组上进行Huffman编码将使其最优化(如果您知道这些模式概率永远不会改变,甚至可以制作静态Huffman树,因此您不必存储它)。

1

我的想法与Evgeny Kluev的类似。 第一个整数发送未压缩的,其余的变成自身和前一个整数的异或。

1100 1001 1110 0010 0001 0101 0110 1101    Sample 1  
               *                   * 
0000 0000 0000 1000 0000 0000 0000 1000    Sample 2

0000 0000 0000 0000 0000 0000 0000 0000    Sample 3
     *            *            *   
0000 1000 0000 0001 0000 0000 0100 0000    Sample 4

现在,我不再将稀疏数据分成块并在此执行算术编码,而是进一步转换数据。因为实际上,算术编码是基于数据频率不相等的。看着这个,你觉得呢?
0000 0000 0000 1000 0000 0000 0000 1000

将会比...更频繁地出现

0000 1000 0000 0001 0000 0000 0100 0000

还是反过来吗?

好的,这里是我将进一步转换数据的方法。 让剩下的数据成为描述连续零的数字序列。 例如,数据变成:

1100 1001 1110 0010 0001 0101 0110 1101    Sample 1  followed by decimals
12, 15, 39, 10, 9, 6

现在您可以对这些尾数执行算术编码。 这次频率将有意义! 因为您在问题中说有很少的变化,这意味着 连续零的数量越多,它们出现的次数就越多。
编辑:这个答案与hirschhornsalz的答案完全相同。 除了他还提到您可以对最大零数设置限制并将它们拆分...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接