libwce:小波编码器中的熵层,独立版本

Lobsters Hottest 工具

摘要

libwce 是一个简洁且无专利问题的 Rust 库,实现了用于小波编码器的比特平面计数(BPC)熵层,提供一个无状态、无依赖的熵编码模块。

<p><a href="https://lobste.rs/s/d3uwf1/libwce_entropy_layer_wavelet_codec_on_its">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/05/24 14:57

# 小波编解码器的熵层,独立呈现 来源:https://yogthos.net/posts/2026-05-24-libwce.html 大多数你熟悉的图像编解码器,比如 JPEG、JPEG 2000、JPEG XS、WebP,都像是千层蛋糕。顶层是变换,底层是熵编码,码率控制浮动在中间。外面还包裹着一层元数据。有趣的部分都藏在大量的框架代码、参数解析器和标准规范之下。如果你只想看看小波系数是如何变成比特的,就必须深入编解码器的内部。 我编写了 `libwce`,一个极简的实现,仅包含一个 500 行的 `lib.rs` 文件。它只实现了一个概念干净的位平面计数(BPC)风格熵层,遵循 JPEG XS 的精神,并且没有其他多余的东西。没有样板代码或依赖项,这个库只依赖标准库。 ### BPC 编码速成 原始视频流基本上是一个像素网格,其中大多数像素与其相邻像素共享非常相似的颜色和亮度值。单独存储每个像素会浪费大量带宽,因为流中存在大量重复数据。编解码器通过将图像从空间域变换到频率域来压缩这些信息。编解码器不是追踪单个像素,而是使用数学频率来描述整个图像中的颜色变化。像标准 JPEG 这样的旧格式最终会将图像切成方块,并应用离散余弦变换,导致我们熟知且喜爱的块状伪影。 小波是一种较新的方法,它通过一次性对整个图像应用变换过程来解决这个问题,将信号分成多个尺度上的低频结构数据和高频细节数据。小波变换后,你会得到一个二维有符号整数系数数组,其中大多数接近零,并带有长拉普拉斯拖尾。熵层的目的是将这个数组压缩成少量有效比特。 BPC 编码每次处理四个系数组成的一组。对于每组,你需要确定最小的 `bpc`,使得每个系数都能被容纳。这就是位平面计数,表示该组中所有系数比特都为零的索引之上的一位。在 libwce 中,所有 `bpc` 值首先被写入单个比特流,然后对于每组,四个系数按照系数主序发出。这些是每个系数的幅度比特,紧接着当该系数非零时,是一个符号比特。这就是你需要做的所有数据处理。然后,当你去编码这些 `bpc` 值时,就进入了实际的压缩环节。相邻的组往往大小相似,因此不是将每个 `bpc` 写为原始的 6 位数字,而是可以根据其邻居进行估算,然后只写入一个很小的残差,这个残差通常非常小。 这里,libwce 使用 RUNNING(DPCM 差值相对于前一组 `bpc`,按之字形映射并采用 Rice 编码)和 ZERO(相对于 `lossy_bits` 的无符号残差)预测器,它们可以与可选的每 8 组一个位的稀疏块标志结合,该标志会短路所有死区块。这样你就有四种预测器 × 标志组合,编码器会在这四种组合内遍历七个 Rice-k 值,通过单次成本搜索为每个子带选择最佳组合。所有组合都会产生相同的解码结果,但它们生成的比特流类型不同。每种组合最适合不同的像素分布,例如纹理区域、平坦部分或大部分为零的子带。 ### 使用示例 下面是一个子带的完整解码器: ``` let mut coeffs = vec![0i32; N]; let lossy_bits = decode(buf, &mut coeffs).unwrap(); dequantize_optimal(&mut coeffs, lossy_bits, scale_b); ``` 这个库本身是无状态的,只处理你提供的缓冲区。它不使用 I/O 或隐藏的全局变量,完全通过调用者拥有的缓冲区工作(内部会分配一个小的 BPC 暂存缓冲区)。 ### 端到端图像压缩 该仓库包含 3 个演示。最有趣的是 `image_compress`,它是一个构建在 libwce 之上的完整编解码器。它输入时使用 Haar 小波,中间使用 libwce,输出时使用逆 Haar,并在四个质量预设下运行。 ``` 预设 lossy_bits 载荷 .wce 文件 压缩比 PSNR LL HL LH HH 字节 字节 近无损 2 4 4 5 146537 146597 1.52x 49.06 dB 平衡 4 6 6 7 92631 92691 2.40x 37.54 dB 激进 6 8 8 9 49516 49576 4.48x 28.79 dB 非常损耗 8 10 10 11 21923 21983 10.11x 21.62 dB ``` 整个流程包括 DWT、子带编码、量化和写入容器,代码不到 500 行。如果你把四个重构后的 PGM 文件并排打开,会看到随着压缩率增加,质量逐渐下降。在 q1 时,图像与原图无法区分;q2 在平坦区域有轻微平滑;q3 开始出现明显的小波振铃伪影;q4 则呈现出可识别的小波块状,看起来有点神秘但依然可读。 第二个演示 `mode_shootout` 通过每个预测器 × 标志组合运行一个合成的拉普拉斯子带,并显示胜出者。 ``` 模式 总大小 压缩比 通过 -------------- ----- ------ -- RUN, flag=off 658 12.45x Y RUN, flag=on 666 12.30x Y ZERO, flag=off 652 12.56x Y ZERO, flag=on 660 12.41x Y 自动选择 612 13.39x Y 最佳强制模式:ZERO, flag=off (652 字节) 自动选择比最佳强制模式少 40 字节(更好的 rice_k)。 ``` 这正是那种在完整编解码器范围内做起来很麻烦的事情,你需要摆弄内部检测、禁用码率控制,然后模拟框架层。而在 libwce 中,模式比较就是 API 的工作方式。你可以将同一个子带依次通过 `encode_with_options`(每个预测器 × 标志组合),然后计算字节数并选择胜出者,这正是 `encode` 自身内部所做的。 第三个演示 `stream_surgery` 对编码比特流执行 256 次随机比特翻转和 256 次随机字节混淆,300 个覆盖每个 4 字节前缀的截断点,以及一组对抗性案例,包括全一的“一元炸弹”以及精心构造的错误头部。 ``` 比特翻转(任意位置) : 256/256 返回,平均 36 / 最多 1024 个系数不同(共 1024) 随机字节(任意位置) : 256/256 返回,无崩溃 截断(每个前缀) : 300/300 前缀长度返回 对抗性(炸弹 + 错误头部) : 7 个案例干净返回 ``` 该演示展示了每个案例都能成功解码,没有任何挂起或崩溃。 ### 它不是什么 最后,值得重申的是,我故意没有把 libwce 写成完整的编解码器实现,因为那需要添加容器格式、码率控制和其他管道代码。它的设计目的是说明中间层编解码器中最概念上有趣的层是如何工作的,并且在没有完整编解码器负担的情况下更容易研究和修改。你得到的只是熵层,可以将其接入你自己的流程中。 仓库位于 https://github.com/yogthos/libwce。克隆并玩一玩。它是为可读性而编写的。

相似文章

将代码置于显微镜下:面向LLM的小波上下文

Reddit r/LocalLLaMA

WaveScope 是一个 MCP 服务器,它应用小波变换对代码库进行处理,为 LLM 提供多分辨率结构上下文,以改进代码理解和编辑,解决上下文退化与结构性感知问题。

Lobsters Hottest

一篇技术博文,探讨随机性、Linux熵以及构建一个名为morerandom的工具,该工具使用WASM插件来为系统熵池提供熵。

wio:窗口化输入/输出

Lobsters Hottest

wio 是一个 Zig 平台抽象库,负责处理窗口管理、事件、剪贴板、音频以及图形上下文创建(OpenGL、Vulkan),支持 Windows、macOS、Linux、Android 和 WebAssembly。