一、熵编码概念:

熵越大越混乱

信息学中的熵:

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

信源编码定理:

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

熵与混乱程度:

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

熵编码基本思想:

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

常用的熵编码算法:

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

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

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. 微信小程序开发官方文档解读

    创建页面 在这个教程里,我们有两个页面,index 页面和 logs 页面,即欢迎页和小程序启动日志的展示页,他们都在 pages 目录下.微信小程序中的每一个页面的[路径+页面名]都需要写在 app ...

  2. 高性能网络编程(1)—accept建立连接‍(转载,作者:陶辉)

    编 写服务器时,许多程序员习惯于使用高层次的组件.中间件(例如OO(面向对象)层层封装过的开源组件),相比于服务器的运行效率而言,他们更关注程序开发 的效率,追求更快的完成项目功能点.希望应用代码完全 ...

  3. load和DOMContenLoaded的区别

    load和DOMContentLoaded的作用就是当页面加载完成的时候自动执行,但他们执行的时间点是不一样的. DOM文档加载步骤: (1)解析html结构 (2)加载外部脚本和样式表文件 (3)解 ...

  4. textarea自适应高度,div模仿textarea可编辑实现自适应高度,placeholder使用图标

    1.textarea自适应高度,placeholder使用图标 自适应高度,有很多种办法: 1)jq: $("textarea").on("input",fun ...

  5. 逢三退一(boolean数组的使用)

    package com.hanqi.count; // 逢三退一 输出留到最后值的索引; public class Count1 { //主方法 public static void main(Str ...

  6. SQL用了Union后的排序问题

    最近使用SQL语句进行UNION查询,惊奇的发现:SQL没问题,UNION查询也没问题,都可以得到想要的结果,可是在对结果进行排序的时候,却出问题了. 1.UNION查询没问题 SELECT `id` ...

  7. dedecms_

    2012-7-5(no1)当我们点击检索结果的某个电影超链接时,如何跳转到对应的内容页[本资源由www.qinglongweb.com搜集整理] dedelist标签 --可以嵌套 项目移植: mys ...

  8. 【Java框架型项目从入门到装逼】第九节 - 数据库建表和CRUD操作

    1.新建学生表 这节课我们来把和数据库以及jdbc相关的内容完成,首先,进行数据库建表.数据库呢,我们采用MySQL数据库,我们可以通过navcat之类的管理工具来轻松建表. 首先,我们得建一个数据库 ...

  9. mysql 两个时间段的差,可以是秒,天,星期,月份,年...

    SELECT TIMESTAMPDIFF(SECOND, now(), "2012-11-11 00:00:00") 语法为:TIMESTAMPDIFF(unit,datetime ...

  10. Powerdesigner+PostgreSQL

    1.准备软件 Powerdesigner PostgreSQL PostgreSQL ODBC驱动程序: psqlODBC,网址:http://www.postgresql.org/ftp/odbc/ ...