Huffman图像压缩

1、实现基于Huffman编码的图像压缩

实现大体思路遵循上述的原理,关于比特位的处理,如果直接用位运算的话,编写起来较复杂。于是我改用0和1的字符串来逐位表示一个个比特。也就是说,编码过程中经过像素值转成,再由字符串转成比特位,解码过程中经过比特位转成字符串,再由字符串转成像素值。通过字符串作为桥梁,可以避免复杂的位运算,而运行效率也不会下降多少。而至于像素值和字符串怎样转换,还要用到现成的bitset<32>类型。

c++代码如下:

HuffmanEncode.cpp:

HuffmanEncode函数对外使用。调用函数HuffmanEncode,输入图像的二维矩阵,图像的宽高,以及另存的压缩后的文件名,执行后得到压缩后的文件以及头部信息文件。

在HuffmanEncode函数内,findMinNode函数用于找到当前最少出现的节点;buildTree函数用于构建哈夫曼树;每个节点的下标为像素值,存有出现次数,指向左右子节点的下标,以及用来判断是否已删除的位;buildTable函数用于建立像素值和编码值的映射关系。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <bitset>using namespace std;struct Node {int count;int left;int right;int removed;
};int findMinNode(Node* nodes, int length) {int index = -1;for (int i = 0; i < length; i++) {if ((index == -1 || nodes[i].count < nodes[index].count) && !nodes[i].removed && nodes[i].count > 0) {index = i;}}if (index != -1) {nodes[index].removed = 1;}return index;
}int buildTree(Node* nodes, int* counts) {for (int i = 0; i < 256; i++) {nodes[i].left = -1;nodes[i].right = -1;nodes[i].count = counts[i];nodes[i].removed = 0;}int length = 256;while (1) {int l = findMinNode(nodes, length);if (l == -1) {break;}int r = findMinNode(nodes, length);if (r == -1) {break;}nodes[length].left = l;nodes[length].right = r;nodes[length].count = nodes[l].count + nodes[r].count;nodes[length].removed = 0;length++;}return length;
}void buildTable(Node* nodes, int pos, string bits, string * table) {int l = nodes[pos].left;int r = nodes[pos].right;if (nodes[pos].left == -1 && nodes[pos].right == -1) {table[pos] = bits;return;}buildTable(nodes, r, bits + "1", table);buildTable(nodes, l, bits + "0", table);
}void HuffmanEncode(unsigned char ** data, int height, int width, const char *writepath) {FILE* fp;int counts[256];memset(counts, 0, sizeof(int) * 256);for (int i = 0; i < height; i++) {for (int j = 0; j < width; j++) {counts[data[i][j]]++;}}Node nodes[256 * 2];int length = buildTree(nodes, counts);string table[256];buildTable(nodes, length - 1, "", table);string table_path = "";table_path = table_path + writepath + "_table";fp = fopen(table_path.c_str(), "w");for (int i = 0; i < 256; i++) {if (table[i].size() == 0) {fprintf(fp, "2\n");} else  {fprintf(fp, "%s\n", table[i].c_str());}}fclose(fp);int total_bit_length = 0;for (int i = 0; i < 256; i++) {total_bit_length += counts[i] * table[i].size();}char * str = new char[total_bit_length];int cur = 0;for (int i = 0; i < height; i++) {for (int j = 0; j < width; j++) {for (int k = 0; k < table[data[i][j]].size(); k++) {str[cur] = table[data[i][j]][k];cur++;}}}fp = fopen(writepath, "wb");int times = total_bit_length / 32 + 1;string total = "";total = total + str;for (int i = 0; i < 32 * times - total_bit_length; i++) {total = total + "0";}fwrite(&total_bit_length, sizeof(int), 1, fp);for (int i = 0; i < times; i++) {bitset<32> byte(total.substr(32 * i, 32));unsigned long tmp = byte.to_ulong();fwrite(&tmp, sizeof(int), 1, fp);}fclose(fp);
}

函数HuffmanDecode直接调用,传入字符类型的数组指针作为解码后数据用于返回到主函数,图像的宽高以及编码文件的路径名。

decodeString函数用于解析由比特位转成的字符串,并将之解析为最终用于返回的解码图像矩阵。其中,传入的map类型参数是读取编码映射表后建立的编码值和像素值的解码映射表,map类型可以极大地减少查表的时间,降低时间复杂度。由于编码值的前缀唯一不会是其它的编码值,所以不会出现错误识别编码字符串的情况。

HuffmanDecode.cpp:

#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <bitset>
#include <map>using namespace std;void decodeString(string & total, map<string, int> & table_map, int total_bit_length, unsigned char * data) {int index = 0;int cur = 1;int head = 0;while (head < total_bit_length) {if (total[head] != '1' && total[head] != '0') {head++;cur = head + 1;continue;}if (table_map.count(total.substr(head, cur - head))) {data[index++] = table_map[total.substr(head, cur - head)];head = cur;cur = head + 1;} else {cur++;}}
}void HuffmanDecode(unsigned char * decoded_data, int height, int width, const char *readpath) {FILE* fp;string table[256];string path = "";path = path + readpath + "_table";fp = fopen(path.c_str(), "rb");for (int i = 0; i < 256; i++) {char tmp[30];fscanf(fp, "%s", tmp);table[i] = table[i] + tmp;}fclose(fp);map<string, int> table_map;for (int i = 0; i < 256; i++) {table_map[table[i]] = i;}fp = fopen(readpath, "rb");int total_bit_length;fread(&total_bit_length, sizeof(int), 1, fp);int times = total_bit_length / 32 + 1;string total = "";char * str = new char[total_bit_length];int *words = new int[times];fread(words, sizeof(int), times, fp);int cur = 0;for (int i = 0; i < times; i++) {bitset<32> bits(words[i]);string tmp = bits.to_string();for (int j = 0; j < 32; j++) {str[cur] = tmp[j];cur++;}}fclose(fp);total = total + str;decodeString(total, table_map, total_bit_length, decoded_data);
}

这个文件装的是主函数,以及主函数调用的关于压缩文件文件、头部信息文件和映射表文件等的读写处理的函数。

HuffmanMain.cpp

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include "HuffmanEncode.cpp"
#include "HuffmanDecode.cpp"using namespace std;struct ImageHeader {BITMAPFILEHEADER bf;    BITMAPINFOHEADER bi;int rgb[256];
};int ReadImage(string path, ImageHeader & ih, unsigned char ** & data) {FILE * fp;fp = fopen(path.c_str(), "rb");if (fp == NULL) {return 0;} fread(&ih.bf, sizeof(BITMAPFILEHEADER), 1, fp);fread(&ih.bi, sizeof(BITMAPINFOHEADER), 1, fp);fread(&ih.rgb, sizeof(int), 256, fp);if (ih.bi.biBitCount != 8) {printf("Gray image only!\n");return 0;}data = new unsigned char*[ih.bi.biHeight];int row_width = ih.bi.biWidth + (4 - ih.bi.biWidth % 4);for (int i = 0; i < ih.bi.biHeight; i++) {data[i] = new unsigned char[ih.bi.biWidth];}for (int i = ih.bi.biHeight - 1; i >= 0; i--) {for (int j = 0; j < ih.bi.biWidth; j++) {fread(&data[i][j], 1, 1, fp);}if (ih.bi.biWidth % 4 > 0) {fseek(fp, 4 - ih.bi.biWidth % 4, SEEK_CUR);}}fclose(fp);return 1;
}int CopyHeader(string path, ImageHeader & ih) {FILE * fp;fp = fopen(path.c_str(), "wb");if (fp == NULL) {return 0;}fwrite(&ih.bf, sizeof(BITMAPFILEHEADER), 1, fp);fwrite(&ih.bi, sizeof(BITMAPINFOHEADER), 1, fp);fwrite(&ih.rgb, sizeof(int), 256, fp);fclose(fp);return 1;
}int ReaderHeader(string path, ImageHeader & ih2) {FILE * fp;fp = fopen(path.c_str(), "rb");if (fp == NULL) {return 0;}fread(&ih2.bf, sizeof(BITMAPFILEHEADER), 1, fp);fread(&ih2.bi, sizeof(BITMAPINFOHEADER), 1, fp);fread(&ih2.rgb, sizeof(int), 256, fp);fclose(fp);return 1;
}int WriteImage(string path, ImageHeader & ih2, unsigned char * & decoded_data) {FILE * fp;fp = fopen(path.c_str(), "wb");if (fp == NULL) {return 0;}fwrite(&ih2.bf, sizeof(BITMAPFILEHEADER), 1, fp);fwrite(&ih2.bi, sizeof(BITMAPINFOHEADER), 1, fp);fwrite(&ih2.rgb, sizeof(int), 256, fp);for (int i = ih2.bi.biHeight - 1; i >= 0; i--) {fwrite(&decoded_data[i * ih2.bi.biWidth], ih2.bi.biWidth, 1, fp);char tmp = 0;if (ih2.bi.biWidth % 4 > 0) {fwrite(&tmp, 1, 4 - ih2.bi.biWidth % 4, fp);}}fclose(fp);return 1;
}int main() {char readpath[50];printf("BMP format image name:");scanf("%s", readpath);ImageHeader ih;unsigned char ** data;string path = "";path = path + readpath + ".bmp";if (ReadImage(path, ih, data)) {printf("Image %s read successful.\n", readpath);} else {printf("Image %s reading failed.\n", readpath);return 0;}path = "";path = path + readpath + "_head";if (CopyHeader(path, ih)) {printf("Header file copied.\n");} else {printf("Header file copying failed.\n");return 0;}path = "";path = path + readpath;HuffmanEncode(data, ih.bi.biHeight, ih.bi.biWidth, path.c_str());printf("Image encoded.\n");path = "";path = path + readpath + "_head";ImageHeader ih2;if (ReaderHeader(path, ih2)) {printf("Header file read successful.\n");} else {printf("Header file reading failed.\n");return 0;}path = "";path = path + readpath;unsigned char * decoded_data = new unsigned char[ih2.bi.biHeight * ih2.bi.biWidth];HuffmanDecode(decoded_data, ih2.bi.biHeight, ih2.bi.biWidth, path.c_str());printf("Image decoded.\n");path = "";path = path + readpath + "_decode.bmp";if (WriteImage(path, ih2, decoded_data)) {printf("Decoded image saved successful.\n");} else {printf("Decoded image saving failed.\n");return 0;}
}

2、实验结果

原图像test.bmp:

处理过程:

原图像文件test.bmp与压缩后文件test的大小对比:

压缩率约为15.27%。

解码后的图像test_decode.bmp:

原图像文件test.bmp与解码后图像test_decode.bmp的大小对比:

可以看到,解码后图像能一个字节都不差地被恢复出来。因为是无损压缩,失真率为0。

另再测试原图像test1.bmp:

处理过程:

原图像文件test1.bmp与压缩后文件test1的大小对比:

压缩率约为6.19%。

解码后的图像test1_decode.bmp:

原图像文件test1.bmp与解码后图像test1_decode.bmp的大小对比:

可以看到,解码后图像还是能一个字节都不差地被恢复出来。因为是无损压缩,失真率为0。

3、总结

这次作业,一开始我并没打算纯用C/C++来实现Huffman压缩图像,因为原先不清楚bmp格式的文件怎么读取。但Matlab可以调用函数直接获得图像矩阵,这样不不用去处理文件头部信息了。但是对于Matlab很高级的编程工具,要做到Huffman建树、建表,以及按位来写文件的话却并不知道如何着手,反而是用惯了的C/C++更适合这种工作。于是我开始寻求两种优势兼具的解决方法:先用Matlab读取出图像矩阵,再将图像矩阵通过面向C语言的接口传给C代码压缩处理,最后返回解码后的图像矩阵到Matlab,让Matlab封装图像矩阵成bmp格式文件。这样一来,我既能不去考虑图像文件头部处理,又能用C/C++来从底层实现编码压缩和解码。

通过网上查阅资料,我学到了Matlab的C语言接口mex的基本使用,包括如何在Matlab传递参数,如何在C代码内解析参数。解析的过程有点绕,不过熟练掌握指针的知识的话也能正确处理好参数。

这份代码能正确进行Huffman编码和解码,不过最后我还是改用C++重新实现了一次,因为绕了这么久好像也就差个bmp格式文件的读写罢了,干脆再差点关于bmp的资料,把这个问题也用C语言解决了罢。

以上的代码我花了两天时间来编写,测试起来很痛苦,因为用对着图像矩阵检查像素值是否算对,甚至还要用BinaryViewer来检查二进制位是否正确,如果不正确又会是哪里出错了。每晚都要debug知道三四点,那几天感觉严重缺乏睡眠。

但总体来说,这次大作业,我能应用学到的多媒体技术知识来解决问题并温习了学过的知识,虽然过程不轻松,但最终能正确丝毫无误地解码出图像时,成就感还是满满的,结果还是很让自己满意,并且编程也算是能练练手了。

以下是我一开始用这种方法实现的代码:

Matlab脚本Huffman.m:

img = imread('test.bmp');
%img = rgb2gray(img);
[height, width] = size(img);mex HuffmanEncode.cpp
HuffmanEncode(int32(img), 'test.txt');mex HuffmanDecode.cpp
mat = uint8(HuffmanDecode('test.txt', height, width));
imwrite(mat, 'test_decoded.bmp', 'bmp');

HuffmanEncode.cpp:

#include "mex.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <string>
#include <bitset>using namespace std;struct Node {int count;int left;int right;int removed;
};int findMinNode(Node* nodes, int length) {int index = -1;for (int i = 0; i < length; i++) {if ((index == -1 || nodes[i].count < nodes[index].count) && !nodes[i].removed && nodes[i].count > 0) {index = i;}}if (index != -1) {nodes[index].removed = 1;}return index;
}int buildTree(Node* nodes, int* counts) {for (int i = 0; i < 256; i++) {nodes[i].left = -1;nodes[i].right = -1;nodes[i].count = counts[i];nodes[i].removed = 0;}int length = 256;while (1) {int l = findMinNode(nodes, length);if (l == -1) {break;}int r = findMinNode(nodes, length);if (r == -1) {break;}nodes[length].left = l;nodes[length].right = r;nodes[length].count = nodes[l].count + nodes[r].count;nodes[length].removed = 0;length++;}return length;
}void buildTable(Node* nodes, int pos, string bits, string * table) {int l = nodes[pos].left;int r = nodes[pos].right;if (nodes[pos].left == -1 && nodes[pos].right == -1) {table[pos] = bits;return;}buildTable(nodes, r, bits + "1", table);buildTable(nodes, l, bits + "0", table);
}void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) {double* dataCursor = mxGetPr(prhs[0]);int height = mxGetM(prhs[0]);int width = mxGetN(prhs[0]);char *writepath = mxArrayToString(prhs[1]);FILE* fp;int * data = (int *)dataCursor;int counts[256];memset(counts, 0, sizeof(int) * 256);for (int i = 0; i < height; i++) {for (int j = 0; j < width; j++) {counts[data[i + height * j]]++;}}Node nodes[256 * 2];int length = buildTree(nodes, counts);string table[256];buildTable(nodes, length - 1, "", table);string table_path = "table_of_";table_path = table_path + writepath;fp = fopen(table_path.c_str(), "w");for (int i = 0; i < 256; i++) {if (table[i].size() == 0) {fprintf(fp, "2\n");} else  {fprintf(fp, "%s\n", table[i].c_str());}}fclose(fp);int total_bit_length = 0;for (int i = 0; i < 256; i++) {total_bit_length += counts[i] * table[i].size();}char * str = new char[total_bit_length];int cur = 0;for (int i = 0; i < width; i++) {for (int j = 0; j < height; j++) {for (int k = 0; k < table[data[height * i + j]].size(); k++) {str[cur] = table[data[height * i + j]][k];cur++;}}}fp = fopen(writepath, "wb");int times = total_bit_length / 32 + 1;string total = "";total = total + str;for (int i = 0; i < 32 * times - total_bit_length; i++) {total = total + "0";}fwrite(&total_bit_length, sizeof(int), 1, fp);for (int i = 0; i < times; i++) {bitset<32> byte(total.substr(32 * i, 32));unsigned long tmp = byte.to_ulong();fwrite(&tmp, sizeof(int), 1, fp);}fclose(fp);
}

HuffmanDecode.cpp:

#include "mex.h"
#include <stdlib.h>
#include <stdio.h>
#include <string>
#include <bitset>
#include <map>using namespace std;void decodeString(string & total, map<string, int> & table_map, int total_bit_length, double* res) {int index = 0;int cur = 1;int head = 0;while (head < total_bit_length) {if (total[head] != '1' && total[head] != '0') {head++;cur = head + 1;continue;}if (table_map.count(total.substr(head, cur - head))) {res[index++] = table_map[total.substr(head, cur - head)];head = cur;cur = head + 1;} else {cur++;}}
}void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) {const char *readpath = mxArrayToString(prhs[0]);int height = mxGetScalar(prhs[1]);int width = mxGetScalar(prhs[2]);FILE* fp;string table[256];string table_path = "table_of_";table_path = table_path + readpath;fp = fopen(table_path.c_str(), "rb");for (int i = 0; i < 256; i++) {char tmp[30];fscanf(fp, "%s", tmp);table[i] = table[i] + tmp;}fclose(fp);map<string, int> table_map;for (int i = 0; i < 256; i++) {table_map[table[i]] = i;}fp = fopen(readpath, "rb");int buffer;int total_bit_length;fread(&total_bit_length, sizeof(int), 1, fp);int times = total_bit_length / 32 + 1;string total = "";char * str = new char[total_bit_length];int *words = new int[times];fread(words, sizeof(int), times, fp);int cur = 0;for (int i = 0; i < times; i++) {bitset<32> bits(words[i]);string tmp = bits.to_string();for (int j = 0; j < 32; j++) {str[cur] = tmp[j];cur++;}}fclose(fp);total = total + str;double *res;plhs[0] = mxCreateDoubleMatrix(height, width, mxREAL);res = mxGetPr(plhs[0]);decodeString(total, table_map, total_bit_length, res);
}  

Huffman图像压缩相关推荐

  1. matlab霍夫曼图像压缩,用matlab仿真huffman编码在jpg图像压缩中的应用崔微微

    <用matlab仿真huffman编码在jpg图像压缩中的应用崔微微>由会员分享,可在线阅读,更多相关<用matlab仿真huffman编码在jpg图像压缩中的应用崔微微(3页珍藏版 ...

  2. 基于霍夫曼(Huffman)图像编码的图像压缩和重建-含Matlab代码

    目录 一.引言 二.霍夫曼Huffman编码 2.1 霍夫曼编码流程 2.2 输入数据的编码 三.霍夫曼解码 四.实验结果 五.参考文献 六.Matlab代码(GUI界面)获取 一.引言 随着通信与信 ...

  3. 基于MATLAB的图像压缩感知设计(含源文件)

    欢迎添加微信互相交流学习哦! 项目源码:https://gitee.com/oklongmm/biye 名称    基于MATLAB的图像压缩感知 目录 目录    I 第1章 绪论    3 1.1 ...

  4. 【图像处理】MATLAB:图像压缩

    图像压缩背景知识   图像冗余包括编码冗余.像素间冗余.心理视觉冗余. 编码冗余 减少编码冗余 function h = entropy(x, n) % 熵error(nargchk(1, 2, na ...

  5. 基于DCT变换的JPEG图像压缩原理

    1.为什么要进行图像压缩 众所周知,当今人类社会具有三大支柱,即物质.能量.信息.当下已由物质过渡到信息,从农业现代化到工业现代化,再到当今的信息化时代.信息具有通用性.抽象性.无限性.其通用性表现在 ...

  6. 【图像压缩】DCT图像无损压缩【含GUI Matlab源码 726期】

    ⛄一.DCT图像无损压缩简介 1 图像压缩 图像压缩按照压缩过程中是否有信息的损失以及解压后与原始图像是否有误差可以分为无损压缩和有损压缩两大类.无损压缩是指不损失图像质量的压缩,它是对文件的存储方式 ...

  7. 数字图像处理 第八章 图像压缩

    第八章 图像压缩 一.基础知识 1.1编码冗余 1.2空间和时间冗余 1.3不相关的信息 1.4图像信息的度量 1.5保真度准则 1.6图像压缩模型 二.一些基本的压缩方法 2.1霍夫曼编码 Pyth ...

  8. 基于DCT变换的JPEG图像压缩

    基于DCT变换的JPEG图像压缩 摘 要:图像和视频通常在计算机中表示会占用非常大的空间,而出于节省硬盘空间的考虑,往往要进行压缩.而随着网络的发展,图像压缩技术越来越被人所重视.DCT变换是图像压缩 ...

  9. 基于MATLAB的图像压缩感知 算法的实现

    摘要 获取项目源文件,联系Q:1415736481,可指导毕设,课设 数据压缩技术是提高无线数据传输速度的有效措施之一.传统的数据压缩技术是基于奈奎斯特采样定律进行采样,并根据数据本身的特性降低其冗余 ...

最新文章

  1. java condition_死磕 java同步系列之ReentrantLock源码解析(二)
  2. ‘mmdet\ops\nms\src/soft_nms_cpu.pyx‘ doesn‘t match any files
  3. The pc Register(程序计数器)
  4. learn_Day14 内置函数补充、反射、初识面向对象
  5. 训练的神经网络不工作?一文带你跨过这37个坑
  6. 基于弹性计算的AI推理
  7. 计算机语言分类:机器语言、汇编语言、标记语言、脚本语言、编程语言
  8. hdu P3374 String Problem
  9. 装机人员常用软件工具大全
  10. 开源的仿真软件HOPSAN
  11. win7美化_为Windows笔记本外接显示器!附实用工具/桌面美化折腾指南
  12. Nginx配置多个二级域名和多个CA证书
  13. win10中运行LoadRunner:Initialization failed; communication error. Error (-81024): LR_VUG: The ‘QTWeb‘ t
  14. python抢红包脚本_Python自动抢视频红包,仅供学习!
  15. 远程桌面连接管理 工具使用说明
  16. NXP JN5169 UART波特率问题
  17. 静态工作点 (直流偏置点)
  18. LOG,Harris,SUSAN角点及边缘检测原理和代码实现
  19. 「前端」微信获取openId,静默授权与非静默授权
  20. 10.Volumetric Shaders

热门文章

  1. 用div来代替table
  2. Debian10开启路由转发以及配置dhcp中继
  3. 微信小程序数据 \n 换行符失效解决办法
  4. C 语言实例 - 输入n个整数,使其从大到小输出
  5. 论文阅读笔记(1):Deep Animation Video Interpolation in the Wild——野外深度动画视频插值(2021CVPR)
  6. 数据嗨客 | 第3期:朴素贝叶斯和垃圾邮件过滤 机器学习 2016-11-01 0 摘要:概率论只不过是把常识用数学公式表达了出来。 概率论只不过是把常识用数学公式表达了出来。 —
  7. 一扩四(​FE8.1)USB转接和一扩七(FE2.1)USB原理图和PCB分享
  8. c语言编写邮箱注册登录的程序,c语言实现邮箱地址验证
  9. 【CodeForces】CF13C Sequence(配数学证明)
  10. inb inw inl outb outw outl:端口操作