因为霍夫曼编码以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. Mongodb入门1

    学如逆水行舟不进则退 | 心似平原走马易放难追 MongoDB是一个NOSQL类型的数据库,关于SQL与NOSQL的区别不是本文的重点,不在此进行记录. MongoDB安装 然后再将MongoDB的安 ...

  2. CSP-S 2023

    T1 直接 \(10^{5}\) 枚举状态就过了,合法的非零差分数量只可能为 \(1,2\)(\(0\) 相当于没转,按照题意 "都不是正确密码" 是不符的) 需要注意的是形如 0 ...

  3. [Tkey] CodeForces 1267G Game Relics

    太神了这题,膜拜出题人 orz. 思考一 首先是大家都提到的一点,先抽卡再买.这里来做个数学分析. 假设我们还剩 \(k\) 种没有买,其实我们是有式子来算出它的花费期望的.WIKI 上提到,假设一个 ...

  4. Linux部署东方通TongWeb7

    一.软件版本 操作系统: CentOS 7.5.1804 JDK:1.8_201 东方通:TongWeb7.0.4.2 二.部署流程 2.1 安装JDK 2.1.1 下载JDK并发明回到/opt下解压 ...

  5. 7.flask 源码解析:session

    目录 一.flask 源码解析:session 1.1 session 简介 1.2 解析 1.2.1 请求过程 1.2.2 session 对象 1.2.3 签名算法 1.2.4 应答过程 1.3 ...

  6. jenkins Publish over SSH 的配置与使用

    一.安装Publish over SSH 插件 二.在Configure System 配置Publish over SSH属性 Passphrase:如果私钥设置了密码就是私钥的密码,私钥没设置密码 ...

  7. KubeKey 离线部署 KubeSphere v3.4.1 和 K8s v1.26 实战指南

    作者:运维有术 前言 知识点 定级:入门级 了解清单 (manifest) 和制品 (artifact) 的概念 掌握 manifest 清单的编写方法 根据 manifest 清单制作 artifa ...

  8. Kubernetes 边缘节点抓不到监控指标?试试这个方法!

    KubeSphere v3.1.0 通过集成 KubeEdge,将节点和资源的管理延伸到了边缘,也是 KubeSphere 正式支持边缘计算的第一个版本. 笔者也第一时间搭建和试用了边缘节点相关的功能 ...

  9. 修改Linux服务的文件打开句柄数

    在bash中,有个ulimit命令,提供了对shell及该shell启动的进程的可用资源控制.主要包括打开文件描述符数量.用户的最大进程数量.coredump文件的大小等. 在centos 5/6 等 ...

  10. Windows 非服务器版本永久关闭命令行“快速编辑模式”

    使用管理员权限运行cmd.exe 运行如下命令: reg add HKEY_CURRENT_USER\Console /v QuickEdit /t REG_DWORD /d 00000000 /f