【视频编解码·学习笔记】7. 熵编码算法:基础知识 & 哈夫曼编码
一、熵编码概念:
熵越大越混乱
信息学中的熵:
- 用于度量消息的平均信息量,和信息的不确定性
- 越是随机的、前后不相关的信息,其熵越高
信源编码定理:
- 说明了香农熵越信源符号概率之间的关系
- 信息的熵为信源无损编码后平均码长的下限
- 任何的无损编码方法都不可能使编码后的平均码长小于香农熵,只能使其尽量接近
熵与混乱程度:
混乱度越高的信源,越难以被压缩,需要更大量的信息来表示其排列顺序
熵编码基本思想:
是使其前后的码字之间尽量更加随机,尽量减小前后的相关性,更加接近其信源的香农熵。这样在表示同样的信息量时所用的数据长度更短。
常用的熵编码算法:
- 变长编码:哈夫曼编码 和 香农-费诺编码。运算复杂度低,但同时编码效率也低。
- 算术编码:运算复杂,但编码效率高
二、哈夫曼编码基本原理:
1. 哈夫曼树简单介绍:
- 哈夫曼编码是变长编码方法的一种,该方法完全依赖于码字出现的概率来构造整体平均长度最短的编码
- 关键步骤:建立符合哈夫曼编码规则的二叉树,该树又称作哈夫曼树
哈夫曼树:
- 一种特殊的二叉树,其终端节点的个数与待编码的码元的个数等同,而且每个终端节点上都带有各自的权值
- 每个终端节点的路径长度乘以该节点的权值的总和称为整个二叉树的加权路径长度
- 在满足条件的各种二叉树中,该路径长度最短的二叉树即为哈夫曼树
2. 哈夫曼树构建过程:
- 将所有左,右子树都为空的作为根节点。
- 在森林中选出两棵根节点的权值最小的树作为一棵新树的左,右子树,且置新树的附加根节点的权值为其左,右子树上根节点的权值之和。注意,左子树的权值应小于右子树的权值。
- 从森林中删除这两棵树,同时把新树加入到森林中。
- 重复2,3步骤,直到森林中只有一棵树为止,此树便是哈夫曼树。
下面是构建哈夫曼树的图解过程:
3. 哈夫曼编码:
利用哈夫曼树求得的用于通信的二进制编码称为哈夫曼编码。树中从根到每个叶子节点都有一条路径,对路径上的各分支约定指向左子树的分支表示”0”码,指向右子树的分支表示“1”码,取每条路径上的“0”或“1”的序列作为各个叶子节点对应的字符编码,即是哈夫曼编码。
以上图例子来说:
A,B,C,D对应的哈夫曼编码分别为:111,10,110,0
用图说明如下:
4. 重要特点:
哈夫曼编码的任意一个码字,都不可能是其他码字的前缀。因此通过哈夫曼编码的信息可以紧密排列连续传输,而不用担心解码时的歧义性。
三、哈夫曼树的构建程序:
新建一个VS工程,起名为Huffman
。这个程序用来将一个英文短文中的字母,按照出现次数多少,进行哈夫曼编码。(需自备一个英语短文,保存为txt格式,放到工程子目录下:xxx\Huffman\Huffman)
1. 首先编写打开和读取文件内容部分:
#include "stdafx.h"
#include <iostream>
#include <fstream>
using namespace std;
static bool open_input_file(ifstream &input, const char *inputFileName)
{
input.open(inputFileName);
if (!input.is_open())
{
return false;
}
return true;
}
int _tmain(int argc, _TCHAR* argv[])
{
ifstream inputFile;
if (!open_input_file(inputFile, "input.txt"))
{
cout << "Error: opening input file failed!" << endl;
return -1;
}
char buf = inputFile.get();
while (inputFile.good())
{
cout << buf;
buf = inputFile.get();
}
inputFile.close();
return 0;
}
2. 统计字符出现频次:
创建一个结构体,保存字符及其频次:
typedef struct
{
unsigned char character;
unsigned int frequency;
} CharNode;
在之前的代码中添加统计部分:
char buf = inputFile.get();
// 下面直接用字符的ascii码作为索引,一个ascii码占一个字节,共256种可能
CharNode nodeArr[256] = { {0,0} };
while (inputFile.good())
{
cout << buf;
nodeArr[buf].character = buf;
nodeArr[buf].frequency++;
buf = inputFile.get();
}
cout << endl << endl;
for (int i = 0; i < 256; i++)
{
if (nodeArr[i].frequency > 0)
{
cout << "Node " << i << ": [" << nodeArr[i].character << ", " << nodeArr[i].frequency << "]" << endl;
}
}
输出如下(Node 10那块换行是因为,ascii中10对应的是“换行”):
3. 根据频次对字符排序:
首先添加几个需要用到的库:
#include <queue>
#include <vector>
#include <string>
排序用到了priority_queue 及 重载运算符 方面的知识,对此不了解的可以看以下几篇讲解:
http://blog.csdn.net/keshacookie/article/details/19612355
http://blog.csdn.net/xiaoquantouer/article/details/52015928
https://www.cnblogs.com/zhaoheng/p/4513185.html
然后定义一个哈夫曼树节点,并重载比较运算符:
// 哈夫曼树节点
struct MinHeapNode
{
char data; //字符
unsigned freq; //频次(权值)
MinHeapNode *left, *right; //左右子树
MinHeapNode(char data, unsigned freq) //构造函数
{
left = right = NULL;
this->data = data;
this->freq = freq;
}
};
typedef MinHeapNode MinHeapNode;
struct compare
{
// 重载()运算符,定义youxai
bool operator()(MinHeapNode* l, MinHeapNode* r)
{
// 由小到大排列采用">"号,如果要由大到小排列,则采用"<"号
return (l->freq > r->freq);
}
};
将节点放入到优先级队列中(会自动排序):
// 创建优先级队列,由小到大排列
priority_queue<MinHeapNode*, vector<MinHeapNode*>, compare> minHeap;
for (int i = 0; i < 256; i++)
{
if (nodeArr[i].frequency > 0)
{
minHeap.push(new MinHeapNode(nodeArr[i].character, nodeArr[i].frequency));
}
}
4. 构建哈夫曼树,并进行哈夫曼编码:
用排好的队列构建哈夫曼树:
//用排好的队列实现哈夫曼树
MinHeapNode *leftNode = NULL, *rightNode = NULL, *topNode = NULL;
while (!minHeap.size != 1)
{
leftNode = minHeap.top();
minHeap.pop();
rightNode = minHeap.top();
minHeap.pop();
// 将合并节点的data设置为-1
topNode = new MinHeapNode(-1, leftNode->freq + rightNode->freq);
topNode->left = leftNode;
topNode->right = rightNode;
minHeap.push(topNode);
}
新建函数,对构建好的哈夫曼树进行哈夫曼编码:
static void get_huffman_code(MinHeapNode *root, string code)
{
if (!root)
{
return;
}
// 由于之前设置了合并节点的data为-1,因此检测到不是-1时,即为叶子结点,进行输出
if (root->data != -1)
{
cout << root->data << ": " << code << endl;
}
// 递归调用
get_huffman_code(root->left, code + "0");
get_huffman_code(root->right, code + "1");
}
最后在主函数中调用此函数即可:
get_huffman_code(topNode, "");
编译运行程序,输出如下:
由于文本不同,每个字符出现的频率不同,因此对于不同文本的编码也不同。这就要求在解码的时候,也需要有编码表才行。
完整程序如下:
#include "stdafx.h"
#include <iostream>
#include <fstream>
#include <queue>
#include <vector>
#include <string>
using namespace std;
// 每一个符号(字母和标点等)定义为一个结构体,包括字符和出现频次
typedef struct
{
unsigned char character;
unsigned int frequency;
} CharNode;
// 哈夫曼树节点
struct MinHeapNode
{
char data; //字符
unsigned freq; //频次(权值)
MinHeapNode *left, *right; //左右子树
MinHeapNode(char data, unsigned freq) //构造函数
{
left = right = NULL;
this->data = data;
this->freq = freq;
}
};
typedef MinHeapNode MinHeapNode;
struct compare
{
// 重载()运算符来定义优先级
bool operator()(MinHeapNode* l, MinHeapNode* r)
{
// 由小到大排列采用">"号,如果要由大到小排列,则采用"<"号
return (l->freq > r->freq);
}
};
static bool open_input_file(ifstream &input, const char *inputFileName)
{
input.open(inputFileName);
if (!input.is_open())
{
return false;
}
return true;
}
static void get_huffman_code(MinHeapNode *root, string code)
{
if (!root)
{
return;
}
// 由于之前设置了合并节点的data为-1,因此检测到不是-1时,即为叶子结点
if (root->data != -1)
{
cout << root->data << ": " << code << endl;
}
// 递归调用
get_huffman_code(root->left, code + "0");
get_huffman_code(root->right, code + "1");
}
int _tmain(int argc, _TCHAR* argv[])
{
ifstream inputFile;
if (!open_input_file(inputFile, "input.txt"))
{
cout << "Error: opening input file failed!" << endl;
return -1;
}
char buf = inputFile.get();
// 下面直接用字符的ascii码作为索引,一个ascii码占一个字节,共256种可能
CharNode nodeArr[256] = { {0,0} };
while (inputFile.good())
{
cout << buf;
nodeArr[buf].character = buf;
nodeArr[buf].frequency++;
buf = inputFile.get();
}
cout << endl << endl;
// 创建优先级队列,由小到大排列
priority_queue<MinHeapNode*, vector<MinHeapNode*>, compare> minHeap;
for (int i = 0; i < 256; i++)
{
if (nodeArr[i].frequency > 0)
{
cout << "Node " << i << ": [" << nodeArr[i].character << ", " << nodeArr[i].frequency << "]" << endl;
minHeap.push(new MinHeapNode(nodeArr[i].character, nodeArr[i].frequency));
}
}
//用排好的队列实现哈夫曼树
MinHeapNode *leftNode = NULL, *rightNode = NULL, *topNode = NULL;
while (minHeap.size() != 1)
{
leftNode = minHeap.top();
minHeap.pop();
rightNode = minHeap.top();
minHeap.pop();
// 将合并节点的data设置为-1
topNode = new MinHeapNode(-1, leftNode->freq + rightNode->freq);
topNode->left = leftNode;
topNode->right = rightNode;
minHeap.push(topNode);
}
get_huffman_code(topNode, "");
inputFile.close();
return 0;
}
【视频编解码·学习笔记】7. 熵编码算法:基础知识 & 哈夫曼编码的更多相关文章
- 【视频编解码·学习笔记】8. 熵编码算法:基本算法列举 & 指数哥伦布编码
一.H.264中的熵编码基本方法: 熵编码具有消除数据之间统计冗余的功能,在编码端作为最后一道工序,将语法元素写入输出码流 熵解码作为解码过程的第一步,将码流解析出语法元素供后续步骤重建图像使用 在H ...
- 【视频编解码·学习笔记】11. 提取SPS信息程序
一.准备工作: 回到之前SimpleH264Analyzer程序,找到SPS信息,并对其做解析 调整项目目录结构: 修改Global.h文件中代码,添加新数据类型UINT16,之前编写的工程中,UIN ...
- 【视频编解码·学习笔记】6. H.264码流分析工程创建
一.准备工作: 新建一个VS工程SimpleH264Analyzer, 修改工程属性参数-> 输出目录:$(SolutionDir)bin\$(Configuration)\,工作目录:$(So ...
- 【视频编解码·学习笔记】12. 图像参数集(PPS)介绍
一.PPS相关概念: 除了序列参数集SPS之外,H.264中另一重要的参数集合为图像参数集Picture Paramater Set(PPS). 通常情况下,PPS类似于SPS,在H.264的裸码流中 ...
- 【视频编解码·学习笔记】3. H.264视频编解码工程JM的下载与编解码
一.下载JM工程: JM是H.264标准制定团队所认可的官方参考软件.网址如下 http://iphome.hhi.de/suehring/tml/ 从页面中可找到相应的工程源码,本次选择JM 8.6 ...
- 【视频编解码·学习笔记】2. H.264简介
一.H.264视频编码标准 H.264视频编码标准是ITU-T与MPEG合作产生的又一巨大成果,自颁布之日起就在业界产生了巨大影响.严格地讲,H.264标准是属于MPEG-4家族的一部分,即MPEG- ...
- 【视频编解码·学习笔记】13. 提取PPS信息程序
PPS结构解析 与之前解析SPS方式类似 一.定义PPS类: 在3.NAL Unit目录下,新建PicParamSet.cpp和PicParamSet.h,在这两个文件中写入类的定义和函数实现. 类定 ...
- 【视频编解码·学习笔记】5. NAL Unit 结构分析
在上篇笔记中通过一个小程序,可以提取NAL Unit所包含的的字节数据.H.264码流中的每一个NAL Unit的作用并不是相同的,而是根据不同的类型起不同的作用.下面将对NAL Unit中的数据进行 ...
- 【视频编解码·学习笔记】10. 序列参数集(SPS)介绍
一.SPS 相关概念: SPS即 "Sequence Paramater Set",又称作序列参数集. SPS中保存了一组编码视频序列(Coded video sequence)的 ...
随机推荐
- Genymotion的安装与使用(附百度云盘下载地址,全套都有,无需注册Genymotion即可使用)
http://blog.csdn.net/scythe666/article/details/70216144 附百度云盘下载地址 :http://pan.baidu.com/s/1jHPG7h8 1 ...
- UEP-时间
时间戳转化为Date(or String) SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ...
- [国嵌笔记][019][Eclipse集成开发环境]
Eclipse集成开发环境的作用 可以编译程序,也可以对程序进行在线调试 集成开发环境 1.JLink连接开发板的JTAG 2.JLink连接PC的USB 3.eclipse软件 4.gdb serv ...
- webpack的安装与使用
在安装 Webpack 前,你本地环境必须已安装nodejs. 可以使用npm安装,当然由于 npm 安装速度慢,也可以使用淘宝的镜像及其命令 cnpm,安装使用介绍参照:使用淘宝 NPM 镜像. 使 ...
- CSS中的定位与浮动
CSS中的定位与浮动 本文主要讲述CSS中的三种定位样式static.relative和absolute的区别以及浮动元素的特征. 定位样式 CSS中定位样式position的取值有三个,默认值:st ...
- dig(域信息搜索器)命令
dig命令 dig命令是常用的域名查询工具,可以用来测试域名系统工作是否正常. 语法 dig(选项)(参数) 选项 @<服务器地址>:指定进行域名解析的域名服务器: -b<ip地 ...
- angular 4 http 之web api 服务
Angular Http是获取和保存数据的.主要是为了取到我json文件里的数据. 直接上代码吧: 1. 先介绍Promise模式的:(直接代码) heroes.json: 1 2 3 4 5 6 ...
- Thrift compiler代码生成类解析
代码生成类解析: Thrift--facebook RPC框架,介绍就不说了,百度,google一大把,使用也不介绍,直接上结构和分析吧. Hello.thrift文件内容如下: namespace ...
- 借助 Vue 来构建单页面应用
原文: https://github.com/MeCKodo/vue-tutorial 主题 Vue.js (1/2)Vue构建单页应用最佳实战 前言 我们将会选择使用一些vue周边的库 1.使用no ...
- shopnc IM配置过程
im配置windows下,修改chat下和data下的config,安装node,覆盖node下文件即可