【H.264/AVC视频编解码技术详解】七、 熵编码算法(1):基础知识

简介: 《H.264/AVC视频编解码技术详解》视频教程已经在“CSDN学院”上线,视频中详述了H.264的背景、标准协议和实现,并通过一个实战工程的形式对H.

《H.264/AVC视频编解码技术详解》视频教程已经在“CSDN学院”上线,视频中详述了H.264的背景、标准协议和实现,并通过一个实战工程的形式对H.264的标准进行解析和实现,欢迎观看!

“纸上得来终觉浅,绝知此事要躬行”,只有自己按照标准文档以代码的形式操作一遍,才能对视频压缩编码标准的思想和方法有足够深刻的理解和体会!

链接地址:H.264/AVC视频编解码技术详解

GitHub代码地址:点击这里

本节视频免费

1. 熵编码概念

“熵”这一概念原本来自于化学和热力学,用于度量能量退化的指标,即熵越高,物体或系统的做功能力越低。后来香农将这一概念引入到信息论中,用于表示消息的平均信息量。信源的熵通常可以表示信源所发出信息的不确定性,即越是随机的、前后不相关的信息,其熵越高。

在信息论中,香农提出了信源编码定理。该定理说明了香农熵与信源符号概率之间的关系,说明信息的熵为信源无损编码后的平均码字长度的下限。任何的无损编码方法都不可能使编码后的平均码长小于香农熵,只能使其尽量接近。

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

在实际使用中,常用的熵编码主要有变长编码和算术编码等方法。其中变长编码相对于算术编码较为简单,但平均压缩比可能略低。常见的变长编码方法有哈夫曼编码和香农-费诺编码等。

2. 熵编码的简单实现——哈夫曼编码

戴维·哈夫曼(David·A·Huffman)于1952年在麻省理工学院的罗伯特·费诺的指导下攻读博士学位时,发明了一种基于有序频率二叉树的编码方法,该方法的编码效率超过了他的导师和信息论之父香农的研究成果(香农-费诺编码),因此又称作“最优编码方法”。

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

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

在使用哈夫曼编码执行对码元的实际编码过程时,码元的权值可设置为其概率值,那么可以根据其权值来构建哈夫曼树。我们假设使用哈夫曼编码对以下概率的码字进行编码:

码字 概率
A 0.1
B 0.1
C 0.15
D 0.2
E 0.2
F 0.25

根据概率表构建哈夫曼树的过程如下图所示:

最终我们可以得到如下图所示的哈夫曼树:

在哈夫曼树构建完成后,便可以得到每一个码元的哈夫曼编码的码字。具体方法是:从哈夫曼树的根节点开始遍历,直至每一个终端节点,当访问某个节点的左子树时赋予码字0,访问右子树时赋予一个码字1(反之亦可),直到遍历到终端节点时这一路径所代表的0和1的串便是该码元的哈夫曼编码码字。

例如上图的哈夫曼树,根节点访问左子树ABCF,赋予码字0;然后再访问左子树ABC,赋予码字0,此时整个码字为00,然后访问右子树得到终端节点C,赋予码字1,此时便可以得到C的哈夫曼编码码字001。以此规律,整个六个元素的码元集合的编码码表为:

  • A: 0000
  • B: 0001
  • C: 001
  • D: 10
  • E: 11
  • F: 01

从这个码表中还可以看出另外一个规律:哈夫曼编码的任意一个码字,都不可能是其他码字的前缀。因此通过哈夫曼编码的信息可以紧密排列连续传输,而不用担心解码时的歧义性。

3. 哈夫曼树的构建Demo

下面的程序段给出一个构建哈夫曼树,并生成对应码元的哈夫曼编码的过程:

#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;

static bool open_input_file(ifstream &input, const char *inputFileName)
{
    input.open(inputFileName);
    if (!input.is_open())
    {
        return false;
    }
    return true;
}

struct MinHeapNode
{
    char data;
    unsigned int freq;
    MinHeapNode *left, *right;
    MinHeapNode(char data, unsigned freq)
    {
        left = right = NULL;
        this->data = data;
        this->freq = freq;
    }
};
typedef struct MinHeapNode MinHeapNode;

struct compare
{
    bool operator()(MinHeapNode* l, MinHeapNode *r)
    {
        return (l->freq > r->freq);
    }
};

static void get_huffman_code(MinHeapNode *root, string code)
{
    if (!root)
    {
        return;
    }

    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();
    CharNode nodeArr[256] = { { 0, 0 } };
    while (inputFile.good())
    {
        cout << buf;
        nodeArr[buf].character = buf;
        nodeArr[buf].frequency++;
        buf = inputFile.get();
    }
    cout << endl;

    priority_queue<MinHeapNode*, vector<MinHeapNode*>, compare>  minHeap;
    for (int idx = 0; idx < 256; idx++)
    {
        if (nodeArr[idx].frequency > 0)
        {
            cout << "Node " << idx << ": [" << nodeArr[idx].character << ", " << nodeArr[idx].frequency << "]" << endl;
            minHeap.push(new MinHeapNode(nodeArr[idx].character, nodeArr[idx].frequency));
        }
    }

    MinHeapNode *leftNode = NULL, *rightNode = NULL, *topNode = NULL;
    while (minHeap.size() != 1)
    {
        leftNode = minHeap.top();
        minHeap.pop();

        rightNode = minHeap.top();
        minHeap.pop();

        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;
}

程序的详细解释过程请到视频中观看。

目录
相关文章
|
4月前
|
机器学习/深度学习 算法 数据可视化
深度解读DBSCAN聚类算法:技术与实战全解析
深度解读DBSCAN聚类算法:技术与实战全解析
466 0
|
6月前
|
数据采集 机器学习/深度学习 人工智能
决策树C4.5算法的技术深度剖析、实战解读
决策树C4.5算法的技术深度剖析、实战解读
207 0
|
7月前
|
缓存 算法 网络协议
【网络编程】第2章(3) 客户软件的设计算法和实现技术
【网络编程】第2章(3) 客户软件的设计算法和实现技术
|
7月前
|
算法 搜索推荐 Shell
python技术面试题(十五)--算法
python技术面试题(十五)--算法
|
19天前
|
存储 算法 JavaScript
Java入门高频考查算法逻辑基础知识3-编程篇(超详细18题1.8万字参考编程实现)
解决这类问题时,建议采取下面的步骤: 理解数学原理:确保你懂得基本的数学公式和法则,这对于制定解决方案至关重要。 优化算法:了解时间复杂度和空间复杂度,并寻找优化的机会。特别注意避免不必要的重复计算。 代码实践:多编写实践代码,并确保你的代码是高效、清晰且稳健的。 错误检查和测试:要为你的代码编写测试案例,测试标准的、边缘情况以及异常输入。 进行复杂问题简化:面对复杂的问题时,先尝试简化问题,然后逐步分析和解决。 沟通和解释:在编写代码的时候清晰地沟通你的思路,不仅要写出正确的代码,还要能向面试官解释你的
32 0
|
1月前
|
机器学习/深度学习 算法 计算机视觉
利用深度学习算法实现图像风格转换技术探究
本文将通过深入分析深度学习算法在图像处理领域的应用,探讨如何利用神经网络实现图像风格转换技术。通过研究不同风格迁移算法的原理和实现方式,揭示其在艺术创作、图像编辑等领域的潜在应用和挑战。
|
2月前
|
存储 算法 搜索推荐
掌握数据结构与算法,成为技术大牛
数据结构与算法是计算机科学中至关重要的基础知识,它们的应用贯穿于各种编程语言和技术领域。本文将介绍数据结构与算法的基本概念及其在实际开发中的应用,帮助读者打下坚实的技术基础,成为真正的技术大牛。
37 4
|
3月前
|
存储 安全 算法
保护数据隐私的安全加密算法:技术守护个人信息安全的利器
在数字化时代,个人信息安全日益受到威胁。本文将深入探讨安全加密算法的重要性,以及如何利用先进的技术保护个人数据的隐私。从对称加密到非对称加密,再到现代密码学的发展,我们将一一解析这些技术的原理和应用。通过了解安全加密算法,我们可以更好地保护个人数据隐私,确保信息的安全传输和存储。
51 3
|
4月前
|
编解码 算法
网易云音乐音视频算法处理技术
网易云音乐音视频算法处理技术
67 0
|
4月前
|
消息中间件 存储 算法
【云计算与大数据技术】数据编码LZSS算法、Snappy压缩库及分布式通信系统的讲解(图文解释 超详细)
【云计算与大数据技术】数据编码LZSS算法、Snappy压缩库及分布式通信系统的讲解(图文解释 超详细)
73 0