这是场景:
我有一个嵌入式系统,其中 ADC每 2 秒记录一次 16 位恒电位仪样本。系统连续数周记录数据,这使得内存使用成为一个问题。
我如何在记录信号时无损压缩信号,而不是为数百万个样本中的每一个使用 16 位存储空间?理想情况下,我想将信号分解为 15 分钟的压缩段。我敢肯定有一个标准,但我找不到。
如果有帮助,信号通常在样本之间具有非常低的增量(低频)。
这是场景:
我有一个嵌入式系统,其中 ADC每 2 秒记录一次 16 位恒电位仪样本。系统连续数周记录数据,这使得内存使用成为一个问题。
我如何在记录信号时无损压缩信号,而不是为数百万个样本中的每一个使用 16 位存储空间?理想情况下,我想将信号分解为 15 分钟的压缩段。我敢肯定有一个标准,但我找不到。
如果有帮助,信号通常在样本之间具有非常低的增量(低频)。
压缩就是找到数据中的冗余并删除它们。由于您似乎无法告诉我们太多有关您的实际数据集的信息,因此此答案必须非常通用。
我收集到“恒电位仪”数据是连续的,通常变化缓慢,但样本之间的偏差可能很小。在 15 分钟(450 个样本)块中对此进行编码的一种好方法是将一阶、二阶或三阶(或更多,取决于数据的一般性质)多项式拟合到要捕获的数据块它的整体形状。1
然后,该块将被编码为该多项式的参数(可能是四个 16 位数字),加上与该多项式的各个样本偏差,这可能是小得多的数字——可能是 450 个 3 或 4 位数字,对于一个总共 1414 或 1864 位,而不是原来的 7200 位——压缩比大约为 4 或 5 比 1。
如果您发现无法为样本增量使用固定宽度,请考虑使用Huffman 编码来表示它们 - 小值得到短代码,而可能稀有的大值得到更长的代码。您应该仍然能够获得大约 3 比 1 的压缩比,这取决于您的数据。
1如果事实证明数据具有循环分量,则使用自回归或傅立叶分析来识别关键的周期性分量(频率、相位和幅度)可能更有用,然后从定义的函数中记录单个样本增量那些参数。
您的数据可能有两个组成部分:
随机变化很可能会呈现数据中的大部分熵。您可以通过过采样来减少它:例如,一次取 4 个样本并将结果除以 4 会将噪声减半。
ADC 样本的无损压缩没有广泛流行的压缩格式。有时会使用免费无损音频编解码器,但它的计算量很大,可能不适合您的系统。DEFLATE和LZ4等通用无损压缩格式占用的计算资源较少,但除非添加自定义预处理步骤,否则压缩率不会很好。所以最好设计一个自定义的压缩方案。
压缩方法的通用结构通常由两部分组成:
我成功使用的一种自定义格式是计算连续样本之间的增量,并使用Elias gamma coding对差异进行编码。它的优点是易于实现,并且对于缓慢变化的信号,每个样本编码少于 1 个字节。
使用的最佳编码很大程度上取决于样本的分布。您已经告诉我们增量大部分都非常小,这意味着您的第一步几乎肯定是增量编码(将每个值转换为与前一个值的差异。)
另一个限制是您正在对其进行编码的系统——您已经说过它是“嵌入式”的,但这涵盖了相当多的功能。您还说过 SD 卡超出了范围,并且您一次只能在 RAM 中缓冲 450 个样本,这表明确实是一个非常小的系统。在这种情况下,为了简单性和节省 CPU/RAM 进行优化似乎是有序的。
如果最常见的 delta 值正好是 0——也就是说,很多样本与前一个样本相同——那么首先“运行长度编码”那些 0 值的运行可能是一个好主意。(即只存储连续有多少个。)
其余的进一步取决于值的分布情况。为了练习,我假设它们几乎都在 -64 < x < 63 的范围内(即 7 位有符号整数)。我还假设使用字节而不是位是最容易的(例如,如果您正在编写 C,这可能是正确的) - 如果这不是真的,请参阅按位方案的答案的最底部。一个非常简单的字节编码可能看起来像这样:
0b0xxxxxxx- 在“xxxxxxx”部分中表示为 7 位有符号整数的文字值 (delta)。(值从 -64 到 63。)
0b10xxxxxx- 一系列零(增量),长度由“xxxxxx”表示(6 位无符号最多可以表示 63,如果我们需要更多,我们可以添加另一个条目。)
0b110xxxxx 0byyyyyyyy- 在“xxxxxyyyyyyyy”部分中表示为 13 位有符号整数的文字值 (delta)。
0b11111111 0bxxxxxxxx 0byyyyyyyy- 表示为 16 位有符号整数的文字值 (delta)。这是一种非常低效的编码(显然),因为它将 16 位值转换为 3 字节表示。它不必要地浪费空间以保持输出字节对齐。这种方案只有在如此大的增量非常罕见时才有意义。(每个非平凡的压缩方案都会有一些输入,其结果输出实际上更大;这是信息论的定理。)
(上述方案的灵感来自 Unicode 的 UTF-8 编码。)
与霍夫曼代码(在另一个答案中提到)不同,假定的值分布是预先固定的。这是一个优点,因为它使事情变得简单,并且避免了在每个样本块的开头增加开销;这是一个缺点,因为更具适应性的方案不需要手动调整分布。
如果比 -64 到 63 小得多的 delta 很常见,则比上述更好的逐字节编码将需要一次处理多个样本,以便获得优于 2:1 的压缩(即多于一个每个输出字节的样本。)
如果按位编码没问题,那么一个更易于描述的方案如下:仍然先进行增量编码,然后按如下方式编码。一个 0 位后跟一个可变长度的正整数,编码后面的零个数;一个 1 位后跟一个符号位,然后是一个可变长度的正整数,一起编码下一个 (delta) 值。可变长度正整数可以使用https://en.wikipedia.org/wiki/Universal_code_(data_compression)中的代码之一进行编码,例如 Elias 代码之一。(哪种编码最好将再次取决于数据的分布,但可能它们中的任何一个都会做得很好。)
为了扩展 Dave 的想法,如果值随时间缓慢变化,另一种存储 15 分钟块的方法是仅存储更改。
您将从完整的 16 位数字开始,然后确定需要多少位来存储两个样本之间的最大变化。例如,如果最大的变化是从 1050 变为 1013,则二进制补码中的差值为 -37 或 1011011。每个样本需要 7 个字节,而不是 16 个。