JPEG格式研究——(3)霍夫曼解码
因为霍夫曼编码以bit为单位,长度又不确定,读取时无法区分,JPEG采用了范式霍夫曼编码。
读取并生成霍夫曼表
JPEG中DC系数和AC系数是分别进行编码将霍夫曼表保存在DQT中。
直接上代码解释可能更直接:
let mut code = 0usize;
let mut length = [0; 16];
for i in 0..16 {
length[i] = data[offset + i + 1];
for j in 0..length[i] as usize {
if code >= (1 << (i + 2)) {
return Err(HuffmanErrorType::InvalidCode(data[off + j], code));
}
map.insert(Binary::new(code, i + 1), data[off + j]);
code += 1;
}
off += length[i] as usize;
code <<= 1;
}
首先定义了一个变量code,用于计算对应的编码,根据范式霍夫曼编码的计算方法从0开始。因为霍夫曼编码的长度最大为16bit,所以用了长16字节的数组,保存了不同长度的编码个数
let mut code = 0usize;
let mut length = [0; 16];
然后依次读取不同长度的编码个数
for i in 0..16 {
length[i] = data[offset + i + 1];
...
}
然后根据编码长度进行循环计算,相同长度的编码每计算出一个就将code+1。这里为了后面解码方便直接用map保存键值对。
Binary
是一个保存了值和二进制长度的结构体,用于区分不同长度的二进制串
for j in 0..length[i] as usize {
// 错误处理部分省略
map.insert(Binary::new(code, i + 1), data[off + j]);
code += 1;
}
然后在编码长度+1时将code左移一位
code <<= 1;
霍夫曼解码
选择霍夫曼表
霍夫曼表数量=颜色分量数*2,比如RGB和YCbCr都是3个颜色分量,而灰度则是1个颜色分量,颜色分量数以及每一个颜色分量的DC、AC系数解码所需的霍夫曼表编号都保存在SOF中。
选择出需要的DC、AC系数的霍夫曼表后,就可以开始解码了
解码
JPEG中将图像按8x8大小进行分块,所以都是以64个数为一组进行解码的,其中第一个数是DC系数,其余的63个则是AC系数
DC系数
DC系数需要首先读取一个经过霍夫曼编码的数据,这个数表示需要读取的bit长度。再以这一长度读取一个二进制串(未经过霍夫曼编码),如果长度为0则表示这里的数据就是0。
这一个二进制串的最高位是符号位,为1表示正数,为0表示负数,如果只有1位那就是只有符号位。然后要对符号位之外表示的数按位取反,按符号位得出正负得到DPCM编码。
DPCM编码实际上也很简单,就是加上上一个DC系数就好了(如果是第一个DC系数则不用加或者加0)
代码如下:
let codeval = dc.huff.decode(bs)?;
let len = codeval as usize;
if len == 0 {
code[0] = last_dc;
} else if len == 1 {
code[0] = last_dc + bs.read(len)? as isize * 2 - 1; // 0 -> -1, 1 -> 1
} else {
let sign = bs.read(1)?;
let num = sign << (len - 1) | bs.read(len - 1)?;
let result;
if sign == 0 {
result = -(((!num) & ((1 << len) - 1)) as isize); // Rust中按位取反是!有点不适应
} else {
result = num as isize;
}
code[0] = result + last_dc;
}
AC系数
先用霍夫曼解码出一个数,这个数的高4位表示0的个数,而低4位后面的数据的bit长度。其中有两种特殊情况:全为0则是EOB(End Of Block),直接结束AC系数解码,剩余的部分用0填充;高4位为1,低4为0则表示有连续16个0.
后面读取出的数据和DC系数解码的方式一样,先是1位符号位,后面跟着剩余位的数据。
代码如下:
let mut i = 1;
while i < 64 {
let codeval = ac.huff.decode(bs)?;
let zero = codeval >> 4;
let len = (codeval & 0x0f) as usize;
if len == 0 {
if codeval == 0xf0 { // 连续16个0
i += 16;
continue;
} else { // End Of Block,直接结束
break;
}
} else if len == 1 {
i += zero as usize;
code[i] = bs.read(len)? as isize * 2 - 1; // 0 -> -1, 1 -> 1
} else {
let sign = bs.read(1)?;
let num = sign << (len - 1) | bs.read(len - 1)?;
let result;
if sign == 0 {
result = -(((!num) & ((1 << len) - 1)) as isize);
} else {
result = num as isize;
}
i += zero as usize;
code[i] = result;
}
i += 1;
}
参考资料
博客园博客:JPEG解码——(4)霍夫曼解码 - OnlyTime_唯有时光 - 博客园 (cnblogs.com)
JPEG标准:Microsoft Word - T081E.DOC (w3.org)
一个Rust写的JPEG解码器:MROS/jpeg_tutorial: 跟我寫 JPEG 解碼器 (Write a JPEG decoder with me) (github.com)
友情链接
我学习过程中写的JPEG图片查看器:Ryan1202/my-tiny-jpeg-viewer: A Tiny Jpeg Viewer (github.com)
JPEG格式研究——(3)霍夫曼解码的更多相关文章
- JPEG解码——(4)霍夫曼解码
本篇是该系列的第四篇,主要介绍霍夫曼解码相关内容. 承接上篇,文件头解析完毕后,就进入了编码数据区域,即SOS的tag后的区域,也是图片数据量的大头所在. 1. 解码过程规则描述 a)从此颜色分量单元 ...
- C# 霍夫曼二叉树压缩算法实现
知道有的人比较懒,直接贴全部代码. 一开始一次性Code完了压缩部分代码.只调试了2,3次就成功了. 一次性写150行代码,没遇到什么bug的感觉还是蛮爽的. 写解压代码,才发现压缩代码有些细节问题. ...
- 基于python的二元霍夫曼编码译码详细设计
一.设计题目 对一幅BMP格式的灰度图像(个人证件照片)进行二元霍夫曼编码和译码 二.算法设计 (1)二元霍夫曼编码: ①:图像灰度处理: 利用python的PIL自带的灰度图像转换函数,首先将彩色图 ...
- 霍夫曼编码(Huffman Coding)
霍夫曼编码(Huffman Coding)是一种编码方法,霍夫曼编码是可变字长编码(VLC)的一种. 霍夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编码表是通过一种评估来源符 ...
- c++实现哈夫曼树,哈夫曼编码,哈夫曼解码(字符串去重,并统计频率)
#include <iostream> #include <iomanip> #include <string> #include <cstdlib> ...
- Java数据结构(十二)—— 霍夫曼树及霍夫曼编码
霍夫曼树 基本介绍和创建 基本介绍 又称哈夫曼树,赫夫曼树 给定n个权值作为n个叶子节点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称为最优二叉树 霍夫曼树是带权路径长度最短的树,权值较 ...
- 赫夫曼\哈夫曼\霍夫曼编码 (Huffman Tree)
哈夫曼树 给定n个权值作为n的叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree).哈夫曼树是带权路径长度最短的树,权值较大的结点离 ...
- word2vec 中的数学原理二 预备知识 霍夫曼树
主要参考: word2vec 中的数学原理详解 自己动手写 word2vec 编码的话,根是不记录在编码中的 这一篇主要讲的就是霍夫曼树(最优二叉树)和编码. ...
- CF 463A && 463B 贪心 && 463C 霍夫曼树 && 463D 树形dp && 463E 线段树
http://codeforces.com/contest/462 A:Appleman and Easy Task 要求是否全部的字符都挨着偶数个'o' #include <cstdio> ...
- 采用霍夫曼编码(Huffman)画出字符串各字符编码的过程并求出各字符编码 --多媒体技术与应用
题目:有一个字符串:cabcedeacacdeddaaaba,问题: (1)采用霍夫曼编码画出编码的过程,并写出各字符的编码 (2)根据求得的编码,求得各编码需要的总位数 (3)求出整个字符串总编码长 ...
随机推荐
- .NET 9 中 LINQ 新增的功能
LINQ介绍 语言集成查询 (LINQ) 是一系列直接将查询功能集成到 C# 语言的技术统称.数据查询历来都表示为简单的字符串,没有编译时类型检查或 IntelliSense 支持.此外,需要针对每种 ...
- fluent python-chap3-1
class collections.OrderedDict([items]) 返回一个 dict 子类的实例,它具有专门用于重新排列字典顺序的方法. """ move_t ...
- 【论文解读】System 2 Attention提高大语言模型客观性和事实性
一.简要介绍 本文简要介绍了论文"System 2 Attention (is something you might need too) "的相关工作.基于trans ...
- Figma 学习笔记 – Auto Layout
用途 Auto Layout 有点像 CSS 的 Flex, 它还带有 responsive 的概念.使用它以后可以替代掉不少 constraints 的写法. 用法 一个 parent 抱着多个 c ...
- 还在苦于密码太弱?教你3招用Linux生成高强度密码
各位好啊,我是会编程的蜗牛,作为java开发者,我们平常肯定会接触Linux操作系统,其实除了一般的部署应用外,它还可以帮助我们生成密码.解决我们平常自己想各种复杂密码的烦恼,以后我会讲一讲如何安全地 ...
- foobar2000 v1.6.13 汉化版(更新于2022.11.22)
foobar2000 v1.6.13 汉化版 -----------------------[软件截图]---------------------- -----------------------[软 ...
- WPF中的ListBox怎么添加删除按钮并删除所在行
直接上代码: 第一步:创建测试类 public class BeautifulGirl { public string Name { get; set; } } 第二步:创建viewmodel和数据源 ...
- nestjs 登录和验证码结合验证 svgCaptcha 包 session 会话标识
// ps: 现在用户验证使用 token jwt 了 代替了 session // session 是服务器为每个用户建立的唯一标识 以区分用户 会话标识 // session 是express中的 ...
- 别人可以访问本项目的ip地址
.markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...
- 云原生周刊:Istio 加入 Phippy 家族 | 2024.3.18
开源项目推荐 ko "ko" 是一个用于构建和部署 Go 应用程序的简单.快速的容器镜像构建工具.它适用于那些镜像中只包含单个 Go 应用程序且没有或很少依赖于操作系统基础镜像的情 ...