因为霍夫曼编码以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. WebShell流量特征检测_哥斯拉篇

    80后用菜刀,90后用蚁剑,95后用冰蝎和哥斯拉,以phpshell连接为例,本文主要是对这四款经典的webshell管理工具进行流量分析和检测. 什么是一句话木马? 1.定义 顾名思义就是执行恶意指 ...

  2. OCR技术的昨天今天和明天!2023年最全OCR技术指南!

    OCR是一项科技革新,通过自动化大幅减少人工录入的过程,帮助用户从图像或扫描文档中提取文字,并将这些文字转换为计算机可读格式.这一功能在许多需要进一步处理数据的场景中,如身份验证.费用管理.自动报销. ...

  3. Windows NoiLinux

    在 Windows 下使用 NoiLinux ubuntu-noi-v2.0.iso 下载 ubuntu-noi-v2.0.iso 打开 VMWare,创建新的虚拟机 -> 自定义(高级)-&g ...

  4. 解决 SHADERed无法打开,报错 缺失XINPUT1_4.DLL 文件的办法

    起因: 在给某个不知名的同事安装软件时遇到的一个问题,安装完成后打开软件报错:SHADERed无法打开,报错 缺失XINPUT1_4.DLL ,C++依赖项均已打上,卸载C++插件后依然报错只能找缺失 ...

  5. 【赵渝强老师】什么是Redis Cluster

    (一)什么是Redis Cluster? Redis Cluster是Redis的分布式解决方案,在Redis 3.0版本正式推出的,有效解决了Redis分布式方面的需求.当遇到单机内存.并发.流量等 ...

  6. .NET 代码混淆工具-JIEJIE.NET

    前言 JIEJIE.NET是一款强大的开源.NET程序集混淆工具.它利用深度加密技术和多样化的混淆策略,有效地保护了.NET软件的版权和源代码安全,防止未经授权的访问和篡改. 项目介绍 JIEJIE. ...

  7. 1.2 HELLO 三角形

    这一节,我觉得是相当有难度的.渲染一个三角形,就需要介绍GLSL语言,图形渲染管线(Graphics Pipeline)以及着色器(Shader),标准化设备坐标(NDC)等诸多概念. 图形渲染管线和 ...

  8. 通过脚手架 npx 创建 react 项目

    npx create-react-app create-react --template typescript 使用 ts 模板

  9. 创建一个简单的基于MyBatis的项目

  10. excel导出功能的实现流程说⼀下?

    导出的话,我们因为到处的数据量不⼤,所以直接采取的时候前端主导的⽅案,参考的现成⽅案实现的 导出 ⼤概得流程就是 1. 调⽤后端接⼝得到要导出的数据 2. 把数据简单处理⼀下转化成导出插件需要的格式 ...