【问题标题】:What would be the contents of canonical Huffman encoded bit stream?规范霍夫曼编码比特流的内容是什么?
【发布时间】:2017-01-20 23:53:27
【问题描述】:

让我们考虑以下带有符号-代码长度-规范代码数据的示例。

A - 2 - 00
B - 2 - 01
D - 2 - 10
C - 3 - 110 
E - 3 - 111

我想知道编码比特流的内容是什么?是 00 01 10 110 111 (基本上所有代码)还是二进制等效的 2,2,2,3,3 作为相应的代码长度?我想在这里补充一点,一些资源说只是将代码作为编码比特流传输,而其他一些资源很少谈论将代码从编码比特流中丢弃并仅传输代码长度数据。

【问题讨论】:

    标签: algorithm compression gzip huffman-code encoder


    【解决方案1】:

    编码比特流

    代码是:

    00 01 10 110 111
    

    请注意,如果我们发送 2、2、2、3、3 的代码,则无法确定输入是 AAACC 还是 BBBEE(或许多其他等效选项)。

    因为霍夫曼码是prefix code,这意味着我们可以明确地解码比特流,尽管不知道空格在哪里。

    换句话说,当给定输出 000110110111 时,我们可以将其唯一地解码为 ABDCE。

    发射码表

    我认为混淆可能是因为您需要拥有两件事来解码比特流:

    1. 编码的比特流
    2. 查找表

    这两件事通常以非常不同的方式编码。

    在许多情况下,查找表是预先固定的,因此不需要传输。

    但是,如果概率可以改变,那么我们需要告诉接收者使用什么代码表。在这种情况下,我们可以只传输每个码字的长度,这为接收器提供了足够的信息来构建规范的 Huffman 码。替代方案也是可能的,例如我们可以发送每个码字长度的数量,然后是值。 JPEG 使用了这种替代方法,并在下面进行了详细说明。

    示例

    JPEG 图像编解码器使用 Huffman 表。通常使用一些默认表,但可以通过传输自定义 Huffman 代码来优化图像的大小。关于这个的教程是here

    霍夫曼表传输方式的另一种描述是here。代码长度(以字节为单位)后跟代码值(同样以字节为单位)发送。

    读取它的代码(取自链接)是:

    // Next sixteen bytes are the counts for each code length
    u8 counts[16];
    for (i = 0; i < 16; i++) {
        counts[i] = fgetc(fp);
        ctr++;
    }
    
    // Remaining bytes are the data values to be mapped
    // Build the Huffman map of (length, code) -> value
    for (i = 0; i < 16; i++) {
        for (j = 0; j < counts[i]; j++) {
            huffData[table][huffKey(i + 1, code)] = fgetc(fp);
            code++;
            ctr++;
        }
        code <<= 1;
    }
    

    【讨论】:

    • 感谢您的回答。然而,正如一些资源所说,我们在常规霍夫曼的规范霍夫曼的情况下获得的最大优势是不将代码作为编码比特流发送。通过这种方式,我们使比特流的大小更小。正如你所说的查找表,你能告诉我那个查找表的结构,需要发送吗?是symbol-codelength-code的形式吗?
    • @beginner 我添加了更多关于如何发送查找表的说明
    • 有趣的是,该方案无法编码 256 个 8 位代码的简单平面代码,因为代码长度的最大计数为 255。
    【解决方案2】:

    您要问的是如何将代码的描述发送给接收者,以便接收者知道如何解码以下代码值。

    有多种方法可以实现不同程度的复杂程度,具体取决于您要投入多少精力来压缩代码描述。 Peter de Rivaz 描述了 JPEG 使用的一种简单方法,即发送 16 个计数的每个长度的代码数量,然后是每个符号的字节值。因此,对于您的代码(十六进制):

    00 03 02 00 00 00 00 00 00 00 00 00 00 00 00 00 41 42 43 44 45

    这不是非常紧凑,它不能代表可能的代码之一,即 256 个 8 位代码,因为每个长度的计数限制为 255。

    当你有一个完整的代码时,你可以做的第一件事就是切断代码长度。计算剩下多少个代码模式很容易,在这种情况下,您可以在没有剩余代码模式时简单地结束它。跟随符号。然后你有:

    00 03 02 41 42 43 44 45

    每个计数不需要 8 位,因为它们受到这些计数的限制。例如,您不能有两个以上的一位代码。所以我们可以用更少的位对这些进行编码,例如n+1 位用于 n 个代码。所以两位,三位,位,等等,直到代码完成。对于您的代码,现在是二进制:

    00 011 0010

    后跟字节41 42 43 44 45,在位流中适当偏移。现在计数列表采用 9 位而不是 24 位。由于我们知道只能有 256 个符号,我们可以将每个计数的位数限制为 9,允许计数 256,解决之前的问题能够表示平面代码。然后,如果代码长度限制为 16 位(对于 JPEG 也是如此),则计数所需的最大字节数为 14.5,小于原来的 16。通常计数将在 14.5 字节之前结束。

    您可以变得更加复杂,注意在每个代码长度下,由于使用向上模式的代码长度较短,您对该长度的代码的可能计数有限制。然后每个计数的位数可以根据有多少可能的值而变化。那么计数描述将是:

    00 011 10,然后是八位值41 42 43 44 45

    由于我们没有用完长度为 1 和 2 的先前模式,因此它们仍然需要分别为 2 位和 3 位。但是,对于长度 3,我们现在只剩下三种可能性:计数 0、1 或 2。计数为 3 会超额订阅代码。所以我们可以为最后一个使用两位。现在是 7 位而不是 9 位,这大大减少了使用较长代码长度的代码的计数位数。

    一种完全不同的方案是 deflate 格式使用的方案(用于 zip、gzip、zlib、png 等)。首先发送要遵循的代码长度数,然后是每个符号的代码长度,直到最后一个。符号本身由代码长度位置隐含。这会产生很多零,以表示不存在的符号。因此,对于您的代码,将有一个 70 上升到符号 69(“E”),然后是 65 个零,然后是 2 2 2 3 3。这看起来非常长,而且确实如此。放气然后运行长度和霍夫曼编码该长度列表,以压缩它。长的零字符串被压缩为几位,而短的长度也只有几位。因此,您必须先发送代码长度代码长度代码(!)的描述,以便您可以对其进行解码。

    您可以阅读deflate specification 以获取有关该方案的更多信息。 brotli 使用类似的方案,但更复杂。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-17
      • 1970-01-01
      • 2011-01-13
      • 1970-01-01
      相关资源
      最近更新 更多