因为霍夫曼编码以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)霍夫曼解码的更多相关文章

  1. JPEG解码——(4)霍夫曼解码

    本篇是该系列的第四篇,主要介绍霍夫曼解码相关内容. 承接上篇,文件头解析完毕后,就进入了编码数据区域,即SOS的tag后的区域,也是图片数据量的大头所在. 1. 解码过程规则描述 a)从此颜色分量单元 ...

  2. C# 霍夫曼二叉树压缩算法实现

    知道有的人比较懒,直接贴全部代码. 一开始一次性Code完了压缩部分代码.只调试了2,3次就成功了. 一次性写150行代码,没遇到什么bug的感觉还是蛮爽的. 写解压代码,才发现压缩代码有些细节问题. ...

  3. 基于python的二元霍夫曼编码译码详细设计

    一.设计题目 对一幅BMP格式的灰度图像(个人证件照片)进行二元霍夫曼编码和译码 二.算法设计 (1)二元霍夫曼编码: ①:图像灰度处理: 利用python的PIL自带的灰度图像转换函数,首先将彩色图 ...

  4. 霍夫曼编码(Huffman Coding)

    霍夫曼编码(Huffman Coding)是一种编码方法,霍夫曼编码是可变字长编码(VLC)的一种. 霍夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编码表是通过一种评估来源符 ...

  5. c++实现哈夫曼树,哈夫曼编码,哈夫曼解码(字符串去重,并统计频率)

    #include <iostream> #include <iomanip> #include <string> #include <cstdlib> ...

  6. Java数据结构(十二)—— 霍夫曼树及霍夫曼编码

    霍夫曼树 基本介绍和创建 基本介绍 又称哈夫曼树,赫夫曼树 给定n个权值作为n个叶子节点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称为最优二叉树 霍夫曼树是带权路径长度最短的树,权值较 ...

  7. 赫夫曼\哈夫曼\霍夫曼编码 (Huffman Tree)

    哈夫曼树 给定n个权值作为n的叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree).哈夫曼树是带权路径长度最短的树,权值较大的结点离 ...

  8. word2vec 中的数学原理二 预备知识 霍夫曼树

    主要参考:    word2vec 中的数学原理详解                 自己动手写 word2vec 编码的话,根是不记录在编码中的 这一篇主要讲的就是霍夫曼树(最优二叉树)和编码.  ...

  9. CF 463A && 463B 贪心 && 463C 霍夫曼树 && 463D 树形dp && 463E 线段树

    http://codeforces.com/contest/462 A:Appleman and Easy Task 要求是否全部的字符都挨着偶数个'o' #include <cstdio> ...

  10. 采用霍夫曼编码(Huffman)画出字符串各字符编码的过程并求出各字符编码 --多媒体技术与应用

    题目:有一个字符串:cabcedeacacdeddaaaba,问题: (1)采用霍夫曼编码画出编码的过程,并写出各字符的编码 (2)根据求得的编码,求得各编码需要的总位数 (3)求出整个字符串总编码长 ...

随机推荐

  1. musl libc 与 glibc 在 .NET 应用程序中的兼容性

    musl Linux 和 glibc 是两种不同的 C 标准库实现,它们在多个方面存在显著差异. 历史和使用情况: glibc 是较早且广泛使用的 C 标准库实现,具有较长的开发历史和广泛的社区支持. ...

  2. git 回退之前某次提交

    git 版本回退,用于误提交或者版本回退 一.回滚到之前的某次版本,且该版本后的提交都不要(用于版本回退) 1).首先查看版本号,用 git log 命令查看要回退的版本对应的commit Id co ...

  3. Mmdetection dataset pipline

    数据的加载顺序是上图(来自mmdetection官网)中的顺序进行,上图中只有一次padding,但是其实dataloader一共有两次padding,一次是pad,另外一次就是collect后,给模 ...

  4. PyCharm 的一些基本设置&&常用插件&&快捷键

    PyCharm一些基本设置   1.主题色彩   2.添加设置:Ctrl+鼠标滚轮上下调节字体大小           3. 中文语言包   4.翻译插件 5.快捷键

  5. gadget驱动框架(一)

    之前在linux移植udc驱动的时候,没有深入的理解整个gadget驱动框架,现在重新再屡屡gadget驱动,以便后期再次学习.本系列的文章以虚拟串口进行分析,相关源码均是基于linux4.19.12 ...

  6. 对于 Serverless, DevOps, 多云及边缘可观察性的思考与实践

    从单体到微服务,再到 Serverless,应用在逐渐地轻量化.有人可能会有疑问,微服务都还没有顺畅的搭建起来,现在又出了 Serverless,应该怎么办? 其实两者之间并不是一个相互替代的关系.我 ...

  7. POI方式实现Excel表格数据导出

    Excel表格导出 1.添加pom依赖 1 <!-- office 操作工具 --> 2 <dependency> 3 <groupId>org.apache.po ...

  8. 云电脑玩游戏挑选标准,ToDesk实测体验

    大家玩游戏还在攒机吗?与其花费时间精力在组装游戏电脑上,不如用上最近兴起的云电脑软件.无需额外配备硬件设备,旧电脑原地变身成高性能电脑,随时随地享受游戏乐趣. 但市面上众多的云电脑软件,该怎么选择才能 ...

  9. git知识点,常用命令

    git理论知识 git的服务器端(remote)端包含多个repository,每个repository可以理解为一个项目. 而每个repository下有多个branch."origin& ...

  10. 使用FastAPI整合Gradio和Django

    大家好,我是每天分享AI应用的萤火君! 经常接触机器学习的同学可能都接触过Gradio这个框架,Gradio是一个基于Python的专门为机器学习项目创建的快速开发框架,可以让开发者快速发布自己的模型 ...