一、熵编码概念:

熵越大越混乱

信息学中的熵:

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

信源编码定理:

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

熵与混乱程度:

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

熵编码基本思想:

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

常用的熵编码算法:

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

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

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. 如何在VS2017中使用快捷键格式化代码?

    1.同时按住Ctrl键+A键,全选代码或要格式化的部分代码: 2.再按住Ctrl键,接着按一下K键,接着按一下F键.(注意:Ctrl键在按后面这2个键的时候一直是按着的,直到F键按完才松开).也就是俗 ...

  2. C#的LINQ

    在过去如果我们如果需要去查询某些集合或者数组里面的某些元素,我们需要写出大量的带有筛选的遍历集合的代码,但是有了Linq之后,我们就不用写出那些冗余麻烦的遍历代码,只需要关注其中的筛选,排列的函数就可 ...

  3. 解决Perhaps you are running on a JRE rather than a JDK?问题

    Maven-No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JD ...

  4. javascript 思维导图 总结

    项目接近尾声,闲暇时间对JavaScript的总结,包含数组的一些知识(创建.访问.关联数组,数组API,以及二维数组).js的内置对象.面向对象概念和特征.以及部分ES5特性. 大纲如图: 如需可下 ...

  5. 【Jsp/Servlet】获取客户端使用的ip

    一般使用jsp的时候大多数时间都可以使用request.getRemoteAddr() 来获取ip,但是这个前提是未经过反向代理等操作的原始地址,所以,需要在反向代理等操作之后还要获取客户端的ip变得 ...

  6. LNMP安装Let’s Encrypt 免费SSL证书方法:自动安装与手动配置Nginx

    前几天介绍了最新StartSSL免费SSL申请与配置,很多人看到部落介绍SSL证书安装时总是推荐了OneinStack,因为OneinStack提供了一键添加和配置Let's Encrypt 免费SS ...

  7. qq客服代码实现过程

    引入css,jsimages,将index.html中的qq聊天代码部分和返回顶部-部分放在head.html文件中, 将文中圈中部分删除,否则影响整个页面的样式:

  8. PHP 获得当前页面所有变量常量的值

    get_defined_vars() - 返回由所有已定义变量所组成的数组,这个函数在二次开发的时候用起来非常给力: get_defined_constants();可以返回当前的所有常量 zend的 ...

  9. Node.js进阶:5分钟入门非对称加密方法

    前言 刚回答了SegmentFault上一个兄弟提的问题<非对称解密出错>.这个属于Node.js在安全上的应用,遇到同样问题的人应该不少,基于回答的问题,这里简单总结下. 非对称加密的理 ...

  10. Java Draw

    简单绘画 直线 矩形 圆 根据矩阵画图 package com.zhoudm; import java.awt.*; import javax.swing.*; public class Draw e ...