256级灰度图像进行霍夫曼编码、解码

// Huffman2005.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "Windows.h"
#include "math.h"
#include <time.h>

//几个全局变量,存放读入图像的位图数据、宽、高、颜色表及每像素所占位数(比特) 
//此处定义全局变量主要为了后面的图像数据访问及图像存储作准备
unsigned char *pBmpBuf;//读入图像数据的指针
int bmpWidth;//图像的宽
int bmpHeight;//图像的高
int imgSpace;//图像所需空间
RGBQUAD *pColorTable;//颜色表指针
int biBitCount;//图像类型
char str[100];//文件名称 
int Num[300];//各灰度值出现的次数 
float Feq[300];//各灰度值出现的频率 
unsigned char *lpBuf;//指向图像像素的指针
unsigned char *m_pDib;//存放打开文件的DIB

int NodeNum;	//Huffman树总节点个数
int NodeStart;	//Huffman树起始节点
struct Node{		//Huffman树节点
	int color;		//记录叶子节点的灰度值(非叶子节点为 -1)
	int lson,rson;	//节点的左右儿子(若没有则为 -1)
	int num;		//节点的数值(编码依据)
	int mark;		//记录节点是否被用过(用过为1,没用过为0)
}node[600];
char CodeStr[300][300];	//记录编码值
int CodeLen[300];		//编码长度
bool ImgInf[8000000];	//图像信息
int InfLen;				//图像信息长度


/***********************************************************************
* 函数名称:
* readBmp()
*
*函数参数:
*  char *bmpName -文件名字及路径
*
*返回值:
*   0为失败,1为成功
*
*说明:给定一个图像文件名及其路径,读图像的位图数据、宽、高、颜色表及每像素
*      位数等数据进内存,存放在相应的全局变量中
***********************************************************************/
bool readBmp(char *bmpName)
{
	//二进制读方式打开指定的图像文件
	FILE *fp=fopen(bmpName,"rb");
	if(fp==0)
	{ 
		printf("未找到指定文件!\n");
		return 0;
	}


	//跳过位图文件头结构BITMAPFILEHEADER
	fseek(fp, sizeof(BITMAPFILEHEADER),0);


	//定义位图信息头结构变量,读取位图信息头进内存,存放在变量head中
	BITMAPINFOHEADER head;  
	fread(&head, sizeof(BITMAPINFOHEADER), 1,fp); 

	//获取图像宽、高、每像素所占位数等信息
	bmpWidth = head.biWidth;
	bmpHeight = head.biHeight;
	biBitCount = head.biBitCount;

	//定义变量,计算图像每行像素所占的字节数(必须是4的倍数)
	int lineByte=(bmpWidth * biBitCount/8+3)/4*4;

	//灰度图像有颜色表,且颜色表表项为256
	if(biBitCount==8)
	{
		//申请颜色表所需要的空间,读颜色表进内存
		pColorTable=new RGBQUAD[256];
		fread(pColorTable,sizeof(RGBQUAD),256,fp);
	}

	//申请位图数据所需要的空间,读位图数据进内存
	pBmpBuf=new unsigned char[lineByte * bmpHeight];
	fread(pBmpBuf,1,lineByte * bmpHeight,fp);

	//关闭文件
	fclose(fp);

	return 1;
}

/***********************************************************************

保存信息

***********************************************************************/
//二进制转十进制
int Change2to10(int pos){
	int i,j,two = 1;
	j = 0;
	for(i = pos + 7;i >= pos;i --){
		j += two * ImgInf[i];
		two *= 2;
	}
	return j;
}

//保存Huffman编码树
int saveInfo(char *writePath,int lineByte){
	int i,j,k;

	FILE *fout;
	fout = fopen(writePath,"w");
	fprintf(fout,"%d %d %d\n",NodeStart,NodeNum,InfLen);//输出起始节点、节点总数、图像所占空间
	for(i = 0;i < NodeNum;i ++){		//输出Huffman树
		fprintf(fout,"%d %d %d\n",node[i].color,node[i].lson,node[i].rson);
	}

	/*for(i = 0;i < InfLen;i ++){
	fprintf(fout,"%d",ImgInf[i]);
	}
	fprintf(fout,"\n");*/

	fclose(fout);

	return 0;
}

//保存文件
bool saveBmp(char *bmpName, unsigned char *imgBuf, int width, int height, 
			 int biBitCount, RGBQUAD *pColorTable)
{
	//如果位图数据指针为0,则没有数据传入,函数返回
	if(!imgBuf)
		return 0;

	//颜色表大小,以字节为单位,灰度图像颜色表为1024字节,彩色图像颜色表大小为0
	int colorTablesize=0;
	if(biBitCount==8)
		colorTablesize=1024;

	//待存储图像数据每行字节数为4的倍数
	int lineByte=(width * biBitCount/8+3)/4*4;

	//以二进制写的方式打开文件
	FILE *fp=fopen(bmpName,"wb");
	if(fp==0) return 0;

	//申请位图文件头结构变量,填写文件头信息
	BITMAPFILEHEADER fileHead;
	fileHead.bfType = 0x4D42;//bmp类型

	//bfSize是图像文件4个组成部分之和
	fileHead.bfSize= sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)
		+ colorTablesize + lineByte*height;
	fileHead.bfReserved1 = 0;
	fileHead.bfReserved2 = 0;

	//bfOffBits是图像文件前三个部分所需空间之和
	fileHead.bfOffBits=54+colorTablesize;

	//写文件头进文件
	fwrite(&fileHead, sizeof(BITMAPFILEHEADER),1, fp);

	//申请位图信息头结构变量,填写信息头信息
	BITMAPINFOHEADER head; 
	head.biBitCount=biBitCount;
	head.biClrImportant=0;
	head.biClrUsed=0;
	head.biCompression=0;
	head.biHeight=height;
	head.biPlanes=1;
	head.biSize=40;
	head.biSizeImage=lineByte*height;
	head.biWidth=width;
	head.biXPelsPerMeter=0;
	head.biYPelsPerMeter=0;
	//写位图信息头进内存
	fwrite(&head, sizeof(BITMAPINFOHEADER),1, fp);

	//如果灰度图像,有颜色表,写入文件 
	if(biBitCount==8)
		fwrite(pColorTable, sizeof(RGBQUAD),256, fp);

	//写位图数据进文件
	//fwrite(imgBuf, height*lineByte, 1, fp);
	fwrite(imgBuf, InfLen / 8, 1, fp);


	//关闭文件
	fclose(fp);

	return 1;
}


/*********************

Huffman编码图像解码

*********************/
//读入编码图像
bool readHuffman(char *Name)
{
	int i,j;
	char NameStr[100];
	//读取Huffman编码信息和编码树
	strcpy(NameStr,Name);
	strcat(NameStr,".bpt");
	FILE *fin = fopen(NameStr,"r");
	if(fin == 0){
		printf("未找到指定文件!\n");
		return 0;
	}
	fscanf(fin,"%d %d %d",&NodeStart,&NodeNum,&InfLen);
	//printf("%d %d %d\n",NodeStart,NodeNum,InfLen);
	for(i = 0;i < NodeNum;i ++){
		fscanf(fin,"%d %d %d",&node[i].color,&node[i].lson,&node[i].rson);
		//printf("%d %d %d\n",node[i].color,node[i].lson,node[i].rson);
	}

	//二进制读方式打开指定的图像文件
	strcpy(NameStr,Name);
	strcat(NameStr,".bhd");
	FILE *fp=fopen(NameStr,"rb");
	if(fp==0){ 
		printf("未找到指定文件!\n");
		return 0;
	}

	//跳过位图文件头结构BITMAPFILEHEADER
	fseek(fp, sizeof(BITMAPFILEHEADER),0);

	//定义位图信息头结构变量,读取位图信息头进内存,存放在变量head中
	BITMAPINFOHEADER head;  
	fread(&head, sizeof(BITMAPINFOHEADER), 1,fp); 

	//获取图像宽、高、每像素所占位数等信息
	bmpWidth = head.biWidth;
	bmpHeight = head.biHeight;
	biBitCount = head.biBitCount;

	//定义变量,计算图像每行像素所占的字节数(必须是4的倍数)
	int lineByte=(bmpWidth * biBitCount/8+3)/4*4;

	//灰度图像有颜色表,且颜色表表项为256
	if(biBitCount==8){
		//申请颜色表所需要的空间,读颜色表进内存
		pColorTable=new RGBQUAD[256];
		fread(pColorTable,sizeof(RGBQUAD),256,fp);
	}

	//申请位图数据所需要的空间,读位图数据进内存
	pBmpBuf=new unsigned char[lineByte * bmpHeight];
	fread(pBmpBuf,1,InfLen / 8,fp);

	//关闭文件
	fclose(fp);

	return 1;
}

void HuffmanDecode()
{
	//获取编码信息
	int i,j,tmp;
	int lineByte=(bmpWidth * biBitCount/8+3)/4*4;
	for(i = 0;i < InfLen / 8;i ++)
	{
		j = i * 8 + 7;
		tmp = *(pBmpBuf + i);
		while(tmp > 0)
		{
			ImgInf[j] = tmp % 2;
			tmp /= 2;
			j --;
		}
	}
	/*for(i = 0;i < InfLen;i ++)
	printf("%d",ImgInf[i]);
	printf("\n");*/

	//解码
	int p = NodeStart;	//遍历指针位置
	j = 0;
	i = 0;
	do
	{
		if(node[p].color >= 0)
		{
			*(pBmpBuf + j) = node[p].color;
			//printf("%d ",*(pBmpBuf + j));
			j ++;
			p = NodeStart;
		} 
		if(ImgInf[i] == 1)
			p = node[p].lson;	
		else if(ImgInf[i] == 0)
			p = node[p].rson;
		i ++;
	}while(i <= InfLen);
	//printf("\nj: %d\n",j);
}


/*********************

Huffman编码

*********************/
//Huffman编码初始化
void HuffmanCodeInit()
{
	int i;
	for(i = 0;i <256;i ++)//灰度值记录清零
		Num[i] = 0;
	//初始化哈夫曼树
	for(i = 0;i < 600;i ++)
	{
		node[i].color = -1;
		node[i].lson  = node[i].rson = -1;
		node[i].num   = -1;
		node[i].mark  = 0;
	}
	NodeNum = 0;
}

//深搜遍历Huffman树获取编码值
char CodeTmp[300];
void dfs(int pos,int len)
{
	//遍历左儿子
	if(node[pos].lson != -1)
	{
		CodeTmp[len] = '1';
		dfs(node[pos].lson,len + 1);
	}
	else
	{
		if(node[pos].color != -1)
		{
			CodeLen[node[pos].color] = len;
			CodeTmp[len] = '\0';
			strcpy(CodeStr[node[pos].color],CodeTmp);
		}
	}

	//遍历右儿子
	if(node[pos].lson != -1)
	{
		CodeTmp[len] = '0';
		dfs(node[pos].rson,len + 1);
	}
	else{
		if(node[pos].color != -1)
		{
			CodeLen[node[pos].color] = len;
			CodeTmp[len] = '\0';
			strcpy(CodeStr[node[pos].color],CodeTmp);
		}
	}
}

//寻找值最小的节点
int MinNode()
{
	int i,j = -1;
	for(i = 0;i < NodeNum;i ++)
		if(!node[i].mark)
			if(j == -1 || node[i].num < node[j].num)
				j = i;
	if(j != -1)
	{
		NodeStart = j;
		node[j].mark = 1;
	}
	return j;
}

//编码主函数
void HuffmanCode()
{
	int i,j,k,a,b;

	for(i = 0;i < 256;i ++)
	{//创建初始节点
		Feq[i] = (float)Num[i] / (float)(bmpHeight * bmpWidth);//计算灰度值频率
		if(Num[i] > 0)
		{
			node[NodeNum].color = i;
			node[NodeNum].num = Num[i];
			node[NodeNum].lson = node[NodeNum].rson = -1;	//叶子节点无左右儿子
			NodeNum ++;
		}
	}

	while(1)
	{	//找到两个值最小的节点,合并成为新的节点
		a = MinNode();
		if(a == -1)
			break;
		b = MinNode();
		if(b == -1)
			break;

		//构建新节点
		node[NodeNum].color = -1;
		node[NodeNum].num = node[a].num + node[b].num;
		node[NodeNum].lson = a;
		node[NodeNum].rson = b;
		NodeNum ++;
		//node[a].mark = node[b].mark = 1;
	}

	//根据建好的Huffman树编码(深搜实现)
	dfs(NodeStart,0);

	//屏幕输出编码
	int sum = 0;
//	printf("Huffman编码信息如下:\n");
	for(i = 0;i < 256;i ++)
		if(Num[i] > 0)
		{
			sum += CodeLen[i] * Num[i];
//			printf("灰度值:%3d  频率: %f  码长: %2d  编码: %s\n",i,Feq[i],CodeLen[i],CodeStr[i]);
		}
// 		printf("原始总码长:%d\n",bmpWidth * bmpHeight * 8);
// 		printf("Huffman编码总码长:%d\n",sum);
// 		printf("压缩比:%.3f : 1\n",(float)(bmpWidth * bmpHeight * 8) / (float)sum);

		//记录图像信息
		InfLen = 0;
		int lineByte=(bmpWidth * biBitCount/8+3)/4*4;
		for(i = 0;i < bmpHeight;i ++)
			for(j = 0;j < bmpWidth;j ++)
			{
				lpBuf = (unsigned char *)pBmpBuf + lineByte * i + j;
				for(k = 0;k < CodeLen[*(lpBuf)];k ++)
				{
					ImgInf[InfLen ++] = (int)(CodeStr[*(lpBuf)][k] - '0'); 
				}
			}

			//再编码数据
			j = 0;
			for(i = 0;i < InfLen;)
			{
				*(pBmpBuf + j) = Change2to10(i);
				i += 8;
				j ++;
			}
}

/******************************

主函数

******************************/

int main(int argc, char** argv)
{

	int ord;//命令 
	char c;
	int i,j;
	clock_t start,finish;
	int total_time;

//	CString str;

	while(1)
	{
		printf("本程序提供以下功能\n\n\t1.256色灰度BMP图像Huffman编码\n\t2.Huffman编码BMP文件解码\n\t3.退出\n\n请选择需要执行的命令:");
		scanf("%d%c",&ord,&c);

		if(ord == 1)
		{ 
			printf("\n---256色灰度BMP图像Huffman编码---\n");
			printf("\n请输入要编码图像名称:");
			scanf("%s",str);

			//读入指定BMP文件进内存
			char readPath[100];
			strcpy(readPath,str);
			strcat(readPath,".bmp");
			if(readBmp(readPath))
			{
				//输出图像的信息
				//printf("\n图像信息:\nwidth=%d,height=%d,biBitCount=%d\n",bmpWidth,bmpHeight,biBitCount);

				int lineByte=(bmpWidth * biBitCount/8+3)/4*4;

				if(biBitCount==8)
				{
					//编码初始化
											
					HuffmanCodeInit();

					//计算每个灰度值出现的次数 
					for(i = 0;i < bmpHeight;i ++)
						for(j = 0;j < bmpWidth;j ++)
						{
							lpBuf = (unsigned char *)pBmpBuf + lineByte * i + j;
							Num[*(lpBuf)] += 1;
						}

						//调用编码
						start=clock(); 	
						HuffmanCode();
						finish=clock();
						total_time=(finish-start);
						printf("识别一张耗时: %d毫秒",total_time);

						//将图像数据存盘
						char writePath[100];
						//保存编码后的bmp
						strcpy(writePath,str);
						strcat(writePath,"_Huffman.bhd");
						saveBmp(writePath, pBmpBuf, bmpWidth, bmpHeight, biBitCount, pColorTable);
						//保存Huffman编码信息和编码树
						strcpy(writePath,str);
						strcat(writePath,"_Huffman.bpt");
						saveInfo(writePath,lineByte);
						printf("\n编码完成!编码信息保存在 %s_Huffman 文件中\n\n",str);
				}
				else
				{
					printf("本程序只支持256色BMP编码!\n");
				}



				//清除缓冲区,pBmpBuf和pColorTable是全局变量,在文件读入时申请的空间
				delete []pBmpBuf;
				if(biBitCount==8)
					delete []pColorTable;
			}

			printf("\n-----------------------------------------------\n\n\n");
		}
		else if(ord == 2)
		{
			printf("\n---Huffman编码BMP文件解码---\n");
			printf("\n请输入要解码文件名称:");
			scanf("%s",str);

			//编码解码初始化
			HuffmanCodeInit();

			if(readHuffman(str))
			{	//读取文件
				HuffmanDecode();	//Huffman解码

				//将图像数据存盘
				char writePath[100];
				//保存解码后的bmp
				strcpy(writePath,str);
				strcat(writePath,"_Decode.bmp");
				InfLen = bmpWidth * bmpHeight * 8;
				saveBmp(writePath, pBmpBuf, bmpWidth, bmpHeight, biBitCount, pColorTable);		
				system(writePath);

				printf("\n解码完成!保存为 %s_Decode.bmp\n\n",str);
			}
			printf("\n-----------------------------------------------\n\n\n");

		}
		else if(ord == 3)
			break;
	}

	return 0;   /* ANSI C requires main to return int. */
}
注:此程序是对灰度图像进行的编码、解码

你可能感兴趣的:(256级灰度图像进行霍夫曼编码、解码)