一、熵编码概念:

熵越大越混乱

信息学中的熵:

  • 用于度量消息的平均信息量,和信息的不确定性
  • 越是随机的、前后不相关的信息,其熵越高

信源编码定理:

  • 说明了香农熵越信源符号概率之间的关系
  • 信息的熵为信源无损编码后平均码长的下限
  • 任何的无损编码方法都不可能使编码后的平均码长小于香农熵,只能使其尽量接近

熵与混乱程度:

混乱度越高的信源,越难以被压缩,需要更大量的信息来表示其排列顺序

熵编码基本思想:

是使其前后的码字之间尽量更加随机,尽量减小前后的相关性,更加接近其信源的香农熵。这样在表示同样的信息量时所用的数据长度更短。

常用的熵编码算法:

  • 变长编码:哈夫曼编码 和 香农-费诺编码。运算复杂度低,但同时编码效率也低。
  • 算术编码:运算复杂,但编码效率高

二、哈夫曼编码基本原理:

1. 哈夫曼树简单介绍:

  • 哈夫曼编码是变长编码方法的一种,该方法完全依赖于码字出现的概率来构造整体平均长度最短的编码
  • 关键步骤:建立符合哈夫曼编码规则的二叉树,该树又称作哈夫曼树

哈夫曼树:

  • 一种特殊的二叉树,其终端节点的个数与待编码的码元的个数等同,而且每个终端节点上都带有各自的权值
  • 每个终端节点的路径长度乘以该节点的权值的总和称为整个二叉树的加权路径长度
  • 在满足条件的各种二叉树中,该路径长度最短的二叉树即为哈夫曼树

2. 哈夫曼树构建过程:

  1. 将所有左,右子树都为空的作为根节点。
  2. 在森林中选出两棵根节点的权值最小的树作为一棵新树的左,右子树,且置新树的附加根节点的权值为其左,右子树上根节点的权值之和。注意,左子树的权值应小于右子树的权值。
  3. 从森林中删除这两棵树,同时把新树加入到森林中。
  4. 重复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. 熵编码算法:基础知识 & 哈夫曼编码的更多相关文章

  1. 【视频编解码·学习笔记】8. 熵编码算法:基本算法列举 & 指数哥伦布编码

    一.H.264中的熵编码基本方法: 熵编码具有消除数据之间统计冗余的功能,在编码端作为最后一道工序,将语法元素写入输出码流 熵解码作为解码过程的第一步,将码流解析出语法元素供后续步骤重建图像使用 在H ...

  2. 【视频编解码·学习笔记】11. 提取SPS信息程序

    一.准备工作: 回到之前SimpleH264Analyzer程序,找到SPS信息,并对其做解析 调整项目目录结构: 修改Global.h文件中代码,添加新数据类型UINT16,之前编写的工程中,UIN ...

  3. 【视频编解码·学习笔记】6. H.264码流分析工程创建

    一.准备工作: 新建一个VS工程SimpleH264Analyzer, 修改工程属性参数-> 输出目录:$(SolutionDir)bin\$(Configuration)\,工作目录:$(So ...

  4. 【视频编解码·学习笔记】12. 图像参数集(PPS)介绍

    一.PPS相关概念: 除了序列参数集SPS之外,H.264中另一重要的参数集合为图像参数集Picture Paramater Set(PPS). 通常情况下,PPS类似于SPS,在H.264的裸码流中 ...

  5. 【视频编解码·学习笔记】3. H.264视频编解码工程JM的下载与编解码

    一.下载JM工程: JM是H.264标准制定团队所认可的官方参考软件.网址如下 http://iphome.hhi.de/suehring/tml/ 从页面中可找到相应的工程源码,本次选择JM 8.6 ...

  6. 【视频编解码·学习笔记】2. H.264简介

    一.H.264视频编码标准 H.264视频编码标准是ITU-T与MPEG合作产生的又一巨大成果,自颁布之日起就在业界产生了巨大影响.严格地讲,H.264标准是属于MPEG-4家族的一部分,即MPEG- ...

  7. 【视频编解码·学习笔记】13. 提取PPS信息程序

    PPS结构解析 与之前解析SPS方式类似 一.定义PPS类: 在3.NAL Unit目录下,新建PicParamSet.cpp和PicParamSet.h,在这两个文件中写入类的定义和函数实现. 类定 ...

  8. 【视频编解码·学习笔记】5. NAL Unit 结构分析

    在上篇笔记中通过一个小程序,可以提取NAL Unit所包含的的字节数据.H.264码流中的每一个NAL Unit的作用并不是相同的,而是根据不同的类型起不同的作用.下面将对NAL Unit中的数据进行 ...

  9. 【视频编解码·学习笔记】10. 序列参数集(SPS)介绍

    一.SPS 相关概念: SPS即 "Sequence Paramater Set",又称作序列参数集. SPS中保存了一组编码视频序列(Coded video sequence)的 ...

随机推荐

  1. kali中的webshell

    webacoo -g 生成一句话 -o 输出文件 -r 不混淆代码 -t 连接模式 -u 制定URL 生成一句话 webacoo -g -o a.php 连接一句话 webacoo -t -u htt ...

  2. 配置web.xml文件用于配置tomcat

    <Context path = "/myweb" docBase = "E:\workspace1\myweb\WebRoot" reloadable = ...

  3. [国嵌攻略][142][LCD驱动程序架构]

    LCD裸机驱动回顾 1.LCD初始化 1.1.控制器初始化 1.2.端口初始化 1.3.指明了帧缓冲 2.LCD图形显示 2.1.将图形数据写入帧缓冲 Linux帧缓冲体验 把图片转换成开发板屏对应的 ...

  4. [国嵌笔记][007][Linux网络配置]

    Vmware网络设置 1.bridged(桥接模式) 如果网络中能提供多个IP地址,则使用桥接方式.虚拟机与主机的IP地址彼此独立. 2.NAT(网络地址转换模式) 如果只能提供一个IP地址,则使用N ...

  5. webpack打包非模块化js

    本文主要记录了非模块化js如何使用webpack打包 模块化打包实现方式 webpack是模块打包工具,通过入口文件递归遍历其依赖图谱,绝对是打包神器. bar.js export default f ...

  6. 小白的Python之路 day5 configparser模块的特点和用法

    configparser模块的特点和用法 一.概述 主要用于生成和修改常见配置文件,当前模块的名称在 python 3.x 版本中变更为 configparser.在python2.x版本中为Conf ...

  7. 【JavaScript_轮播图】

    今天给大家带来的是我自己做的一个轮播图效果,让我们一起来学习一下吧. 这是我的页面所有代码: <!DOCTYPE html> <html> <head> <m ...

  8. linux下 ls -l 命令显示结果每一列代表什么意思

    第一个栏位,表示文件的属性.Linux的文件基本上分为三个属性:可读(r),可写(w),可执行(x).但是这里有十个格子可以添(具体程序实现时,实际上是十个bit位).第一个小格是特殊表示格,表示目录 ...

  9. 查看三种MySQL字符集的方法

    查看MySQL字符集的命令是我们经常会使用到的,下文就介绍了其中的三种查看MySQL字符集的命令,供您参考学习. 作者:佚名来源:互联网|2010-10-09 11:36 移动端 收藏 分享 CTO训 ...

  10. 宝塔服务器管理助手Linux面版-使用教程

    在顺利安装宝塔服务器linux面板之后,我们打开这个面板,UI界面设计的很简介,所有命令一看就知道是干什么用的,和我们以前用过的虚拟主机管理后台是很像的. 方法/步骤 1 使用方法如下: 面板地址:h ...