多元Huffman树——对BMP灰度图像进行n元哈夫曼编码

本文介绍了如何使用N元哈夫曼编码对BMP灰度图像进行编码和解码,详细阐述了从读取图像、统计像素频率、构建哈夫曼树到编码和解码的全过程。作者通过C++实现了这一功能,同时讨论了代码中的问题和限制,如不能处理超过10元的编码。
摘要由CSDN通过智能技术生成

在刚刚结束的信息论与编码的课程设计中,我选了对灰度图像进行N元哈夫曼编码这道题。一开始觉得不就是一个哈夫曼树的构建吗有什么难的,数据结构里早就学过了,应该一两个小时就可以写完,但是事实上遇到的问题远远超过我的想象,N元哈夫曼编码写了两天才勉勉强强实现了编码和译码的功能。嗯我知道如果搜博客搜到了我的博客,肯定是冲着代码去的,没错我自己 搜行博客的时候也是抱着这种心态,但是直接抄代码是不好的习惯,在贴代码之前我还是先把整体思路介绍一下。代码仅做参考。这个代码是有小问题的,没时间修了,但是基本的功能还是可以实现。

首先对信源进行N元哈夫曼编码。N哈夫曼编码肯定要构造哈夫曼树。哈夫曼树的构造方式如下:

(1)将所有结点放入一个集合T中

(2)将N个结点组合,变成一个新的结点,这N个结点接在新结点的孩子结点上,从集合中删除这N个结点,并将新结点加入集合T

(3)重复以上步骤直达T中只剩下一个结点,这个结点就是哈夫曼树的根节点

写好哈夫曼树的构造后,就是对信源进行编码了。但是对信源进行编码前还有一件事:哈夫曼编码要知道信源的概率并且从大到小排序。那么对图片进行编码也应当就是先读取图像的所有像素,然后每个像素作为一个信源符号,统计好每个像素(信源符号)的概率(频数),然后排好序,再对每个像素(信源符号)进行编码。读取BMP最开始我考虑了openCV库,但是这个库太大了,配置来配置去肯定又要消耗时间,而且我仅仅只用里面读取图像的像素并变成数组这一个功能,安装一个好几百兆的库肯定是大材小用。那有没有vs上可以直接调用的实现对图像进行读取的库呢?还真让我找到了,GUI+库,具体的代码实现参考别人的博客然后进行了一部分修改,原博客找不到了,实在实在不好意思,如果原作者看到这段代码记得和我联系。统计频率的方法为:先从图片读取像素,然后把每个像素输入到txt中,然后对txt进行遍历,每输入一个像素就查找这个像素是否被统计过,如果没有就新建一个用于统计此像素的结点,如果已经有了,那就把这个像素的频数+1.

概率统计好之后输出到文件,输出的格式为形如:

255(像素值) 1010(频数)

192  1239 

然后在构建哈夫曼编码前将信源符号和频数一个个读入,存入集合T中。统计出信源的个数。记得进行N元哈夫曼编码时要满足一个公式


如果信源个数少了就要生成空的信源。

对图片构造好哈夫曼编码后就可以进行编码了。方法是每读一个像素就查找这个像素的编码(在代码中coderules这个vector存储 了每个信源符号对应的编码),然后将编码输出。

解码的时候有两种方式,一种是匹配,每读入一个符号就放入一个string,然后查找整个编码表有没有这个string对应的信源符号,如果有的话就输出信源符号,并将string置空,如果没有就继续读入一个。

写BMP的方式也是借鉴的别人代码,同样我也找不到原作者了,非常非常抱歉。

有一个很重要的问题,GDI+读图像的时候似乎是从最后开始往前读的,所以读出来的像素顺序是反的,所以由像素矩阵写入的图片也会是反的(稍微修改过后是轴对称的,但是依然没有变成原本的样子)

计算信源熵和平均码长这些东西就不多介绍了,公式书上有,如果你弄懂了我的代码的话,也可以很容易写出来。

好的下面是重头戏了:

HuffmanTree.h

#pragma once
#ifndef _INC_HUFFMANTREE
#define _INC_HUFFMANTREE




/*n元霍夫曼编码,需要有的数据有:自身符号,自身的权值(概率),指向孩子的指针数组(如果是二元的话)直接左孩子右孩子就可以了,但是
由于是n元,无法确定n的大小,所以采用指针数组的方式,动态生成所需要的数组,在数组中由左至右依次是1孩子2孩子3孩子......
(是否需要一个往上指的指针?)还需要有自身的编码*/
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <fstream>
#include <windows.h>
#include <gdiplus.h>
#pragma comment(lib, "gdiplus.lib")
using namespace std ;
using namespace Gdiplus;
struct MessageSource//用来表达信源符号的,第一个是信源符号,第二个是对应的频率
{
int symbol = 0;
unsigned int frequence = 0;
};
class Node
{
public:
Node(int c) {
n = c;
childs= new Node*[n];
for (int i = 0; i < n; i++)
{
childs[i] = nullptr;
}
}
Node() {
n = 2;
childs = new Node*[n];
for (int i = 0; i < n; i++)
{
childs[i] = nullptr;
}
}
string symbol;
unsigned long int weight = 0;//权值
Node** childs;
vector<string>encode;
void showChild() {
for (int i=0;i<4;i++)
{
childshow[i] = *(childs + i);//只是用来显示用
}
}
private:
int n=2;
Node* childshow[4];//只是用来显示用
};


//操作符重载,为了实现两个节点的比较
bool operator>(const Node &a, const Node &b);
bool operator<(const Node &a, Node &b);


/*如果是n元哈夫曼树,如果把这n个结点全部传入Add函数?如果用一个个传参的方法肯定不行,必须得重载两个Node相加直到n个Node相加的函数
可以把这些结点放入一个vector,然后依次把这些结点加起来,同时把孩子指针指向他们。为了方便孩子从左到右的顺序,在放入vector时就应该从
大到小的顺序排好,这里写一个降序排序的方法,将vector排序,这个函数用在sort中,使用方法sort(a.begin(),a.end(),Greatorsort)*/
bool Greatorsort(Node* a, Node* b);


class Code
{
public:
Code() {};
~Code() {};


int symbol;
string code;
unsigned long int weight = 0;
};




class HuffmanTree//n元霍夫曼编码,需要有的数据有:自身的权值(概率),指向孩子的指针数组(如果是二元的话)
{
public:
HuffmanTree(int c) {
n = c;
}
HuffmanTree() {
n = 2;
}


public:
/*一个集合T,用来存放结点,包括新组成的结点*/
vector<Node*>nodeset;
Node* root;//根节点
void init();
void buildHaffmanTree();
Node* Add(vector<Node *>t);//在Add之前要把需要相加的结点放入vector中
void travel(Node* p);
void encode(Node* p);
vector<Code*>coderule;
void setCodeRules(Node* pNode); 
void readBMP();
void culculateFrequency();
vector<MessageSource*>List;//用来储存信源和对应的频率(频数),比如255 200
int find(int c);//用来查找c这个信源有没有统计过,如果有频率+1,如果没有就新建
void EncodeBMP();
string findCode(int c);
UINT height = 0;//图片高度和宽度
UINT width = 0;
int sourcecount = 0;
bool isLeaf(Node* p);//用来判断某个结点是不是叶子节点,如果是叶子结点则说明可以解码,如果不是则继续

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值