JPEG图像压缩解压算法——C++实现

兹于2017年11月,应《多媒体技术基础》课程实验的要求,本人就基于JPEG图像压缩解压算法做了较为深入的理解,用C++语言实现JPEG图像压缩解压算法。



JPEG图像压缩解压算法


一、实验目的

1.掌握JPEG的压缩原理;

2.熟知FDCT变换方法;

3.掌握JPEG所采用的行程编码、霍夫曼编码、差分编码等压缩技术;

4.掌握JPEG解压原理。


二、实验设备与环境

Windows 7 操作系统,Dev-C ++,MATLAB


三、实验内容、程序清单及运行结果

实验要求:

根据书本P76-P83内容,用Dev-C ++编写JPEG压缩与解压算法。(没有《多媒体技术基础(第三版)》,可以在这里下载PDF电子书,也可在中国知网查阅类似文章如:《用查表法快速实现二维8×8离散余弦逆变换的研究——纪秀花 张彩明 韩慧健》和《基于JPEG标准的静态图像压缩算法研究——张元伟 刘彦隆》等)

1. 实现JPEG压缩编码程序。

2. 实现JPEG解压程序。


实验操作和步骤:

1.打开Dev-C ++,进入编程环境,新建一个源代码文件,文件名任意;

2.将下面我所编写的代码拷贝到所新建的源代码文件中;

3.点击编译运行。


算法简介:

该算法操作是把一个单独的彩色图像分量分成8×8的图像块,即是对源图像的一个分量样本分成8×8的数据,然后进行压缩编码和解码。


自创C++源代码

/**
*	作者:戴文治
*	时间:2017年11月17日
*	描述:JPEG压缩与解压算法
*	测试环境:Dev-C++ 5.9.2
*/

#include<iostream>
#include<string>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#define MAX 100
#define N 8 	//N为每个图像分量的矩阵大小
using namespace std; 

/*亮度量化值表*/
struct  BrightnessQuantizedValueTable{
	int Q[N][N];
	BrightnessQuantizedValueTable(){
		int x[N][N]={16,11,10,16,24,40,51,61,
					 12,12,14,19,26,58,60,55,
					 14,13,16,24,40,57,69,56,
					 14,17,22,29,51,87,80,62,
					 18,22,37,56,68,109,103,77,
					 24,35,55,64,81,104,113,92,
					 49,64,78,87,103,121,120,101,
					 72,92,95,98,112,100,103,99};
		for(int i=0;i<N;i++){
			for(int j=0;j<N;j++){
				Q[i][j] = x[i][j];
			}
		} 
	}
};
BrightnessQuantizedValueTable brightnessQuantizedValueTable;//定义一个亮度量化值表

/*亮度DC差值码表*/
struct BrightnessDC_DifferenceTableList{
	string brightnessDC_DifferenceTable[12];//亮度DC差值码表  类别(数组下标)与码字映射
	BrightnessDC_DifferenceTableList(){
		brightnessDC_DifferenceTable[0]="00";
		brightnessDC_DifferenceTable[1]="010";
		brightnessDC_DifferenceTable[2]="011";
		brightnessDC_DifferenceTable[3]="100";
		brightnessDC_DifferenceTable[4]="101";
		brightnessDC_DifferenceTable[5]="110";
		brightnessDC_DifferenceTable[6]="1110";
		brightnessDC_DifferenceTable[7]="11110";
		brightnessDC_DifferenceTable[8]="111110";
		brightnessDC_DifferenceTable[9]="1111110";
		brightnessDC_DifferenceTable[10]="11111110";
		brightnessDC_DifferenceTable[11]="111111110";
	}
};
BrightnessDC_DifferenceTableList brightnessDC_DifferenceTableList;//定义一个亮度DC差值码表


/*AC系数熵编码时的中间符号*/
struct AC_EntropyCoding_MiddleSymbol{
	string R_S;
	int temp; 
};
AC_EntropyCoding_MiddleSymbol ac_EntropyCoding_MiddleSymbol[N*N];//由于用函数返回结构体数组,里面的字符串会出现一些无法处理的乱码,故定义为全局变量 

/*熵编码时的编码输出*/
struct EntropyCoding{
	string strTemp1;
	string strTemp;
};
EntropyCoding ac_EntropyCodingStr[N*N];//由于用函数返回结构体数组,里面的字符串会出现一些无法处理的乱码,故定义为全局变量 


/* R/S与码字映射结点*/
struct StringMap{
	string key;
	string value;
}; 

/*亮度AC码表  R/S与码字映射表*/
/**特别注意,以下的亮度AC码表只适用于给出的测试数据,如要其它的测试数据,则必须补全亮度AC码表**/
//网上找到一个fantasy 的博客http://menmory.blog.163.com/blog/static/12690012620114535032530/ 这里面有比较详细的亮度AC码表等 
struct StringMapList{
	StringMap stringMap[N*N];
	int partNum;	//该亮度AC码表中的条数 
	
	StringMapList(){
		//部分常用亮度AC码表
		stringMap[0].key = "0/0(EOB)";
		stringMap[0].value = "1010";
		stringMap[1].key = "0/1";
		stringMap[1].value = "00"; 
		stringMap[2].key = "1/1";
		stringMap[2].value = "1100";
		stringMap[3].key = "1/2";
		stringMap[3].value = "11011";
		stringMap[4].key = "2/1";
		stringMap[4].value = "11100";
		stringMap[5].key = "3/2";
		stringMap[5].value = "111110111";
		stringMap[6].key = "F/0(ZRL)";
		stringMap[6].value = "11111111001";
		stringMap[7].key = "F/F";
		stringMap[7].value = "1111111111111110";
		partNum = 8;
	}
};
StringMapList stringMapList;//定义一个 部分常用亮度AC码表


/*DC差值范围表,本人通过对表找规律发现如下规律 */
int DC_Difference(int temp){
	int temp1;
	if(temp == 0){
		 temp1 = 0;
	}else{
		for(int i=1;i<=11;i++){
			if(abs(temp)<pow(2,i)){
				temp1 = i;
				break;
			}
		}
	}
	return temp1;
}


/*AC系数范围表,本人通过对表找规律发现如下规律*/
int AC_Difference(int temp){
	int temp1;
	if(temp == 0){
		 temp1 = 0;
	}else{
		for(int i=1;i<=10;i++){
			if(abs(temp)<pow(2,i)){
				temp1 = i;
				break;
			}
		}
	}
	return temp1;
}


/*将正整数十进制转换成二进制*/ 
string TenToTwo(int temp){
	string strTemp="";
	//旧方法 
//	for(int k=abs(temp);k>0;k=k/2){
//		strTemp = strTemp + (k%2==1?'1':'0');
//	}
//	//倒置 
//	int len = strTemp.length();
//	for(int k=0;k<len/2;k++){
//		char t = strTemp[k];
//		strTemp[k] = strTemp[len-1-k];
//		strTemp[len-1-k] = t;
//	}
	//新方法 
	char str[N*N];
	itoa(temp,str,2);
	strTemp = str; 
	return strTemp; 
}


/*将正整数二进制转换成十进制*/ 
int TwoToTen(string strTemp){
	int temp=0;
	for(int i=0;i<strTemp.length();i++){
		temp = temp*2+strTemp[i]-'0';
	}
	return temp;
} 

/*将一个负数的二进制串逐位取反*/ 
string ConvertToComplement(string strTemp){
	string str = "";
	for(int i=0;i<strTemp.length();i++){
		str = str + (strTemp[i]=='1'?'0':'1');
	}
	return str;
} 


/*DC系数编码*/
EntropyCoding DC_EntropyCoding(int &temp,int &temp1){
	//对DC系数生成中间符号(中间符号(temp1,temp))
	
	//查DC差值表
	temp1 = DC_Difference(temp);
	
	/*测试*/
//	cout<<temp1<<endl;
	
	//对中间符号通过查表进行符号编码 
	//对 temp1通过查亮度DC差值码表进行熵编码
	EntropyCoding dc_EntropyCodingStr; 
	dc_EntropyCodingStr.strTemp1 = brightnessDC_DifferenceTableList.brightnessDC_DifferenceTable[temp1];

	//对 temp进行转换成补码 
	//先将 temp转换成二进制串
	dc_EntropyCodingStr.strTemp = TenToTwo(abs(temp));
	//转换成补码
	if(temp<0){
		dc_EntropyCodingStr.strTemp = ConvertToComplement(dc_EntropyCodingStr.strTemp);	
	}
	
	/*测试*/
//	cout<<dc_EntropyCodingStr.strTemp1<<"\t"<<dc_EntropyCodingStr.strTemp<<endl;
	
	return dc_EntropyCodingStr;
}


/*AC系数编码*/
bool AC_EntropyCoding(int F_[N][N],int &index){
	//对AC系数生成中间符号中/后的部分 
	int SSSS[N][N];
	
	//查AC系数范围表,本人通过对表找规律发现如下规律 
	for(int i=0;i<N;i++){
		for(int j=0;j<N;j++){
			SSSS[i][j] = AC_Difference(F_[i][j]);
		}
	}
	
	/*测试*/
//	for(int i=0;i<N;i++){
//		for(int j=0;j<N;j++){
//			cout<<SSSS[i][j]<<" ";
//		}
//		cout<<endl;
//	}
	
	//Z字形编码
	int count = 0;//计算0的个数
	int i,j,t; 
	for(i=0,j=1,t=1;t<=N-2;t++){//以下语句设为一个周期,大概要执行N-2个周期(这里N=8,通过观察发现每一下一上为一周期,则有6个周期+半段) 
		//向左下方向 
		for(;i<N&&j>=0;i++,j--){
			if(F_[i][j]==0){
				count++;
			}else{
				char countString[N*N];
				itoa(count,countString,10);//将整数count转换为字符串并保存在countString(以10进制方式,也可指定2、8、10、16等进制实现进制转换,进制转换新玩法) 
				string strTemp = "/";
				strTemp = countString + strTemp;
				//cout<<"--"<<strTemp<<"--"<<endl;
				char SSSS_String[N*N];
				itoa(SSSS[i][j],SSSS_String,10);
				strTemp = strTemp + SSSS_String;
				//cout<<"**"<<strTemp<<"**"<<endl;
				
				//中间符号 
				ac_EntropyCoding_MiddleSymbol[index].R_S = strTemp;
				ac_EntropyCoding_MiddleSymbol[index].temp = F_[i][j];
				index++;
				count = 0;//置为0 ,计算下个不为0的数前面0的个数 
			}
		}
		if(i>=N&&j<0){//当出现正中间往下时,挪回正规 
			i--;
			j = j+2;
		}else if(i>=N){//当出现往下突出时,挪回正规 
			i--;
			j = j+2;
		}else if(j<0){//当出现往左突出时,挪回正规
			j++; 
		}
		
		//向右上方向 
		for(;i>=0&&j<N;i--,j++){
			if(F_[i][j]==0){
				count++;
			}else{
				char countString[N*N];
				itoa(count,countString,10);
				string strTemp = "/";
				strTemp = countString + strTemp;
				//cout<<"--"<<strTemp<<"--"<<endl;
				char SSSS_String[N*N];
				itoa(SSSS[i][j],SSSS_String,10);
				strTemp = strTemp + SSSS_String;
				//cout<<"**"<<strTemp<<"**"<<endl;
				
				//中间符号 
				ac_EntropyCoding_MiddleSymbol[index].R_S = strTemp;
				ac_EntropyCoding_MiddleSymbol[index].temp = F_[i][j];
				index++;
				count = 0;//置为0 ,计算下个不为0的数前面0的个数
			}
		}
		if(i<0&&j>=N){//当出现正中间往上时,挪回正规 
			j--;
			i = i+2;	
		}else if(i<0){//当出现往上突出时,挪回正规 
			i++;
		}else if(j>=N){//当出现往右突出时,挪回正规 
			j--;
			i = i+2;
		}
	}
	
	//剩下半个周期的编码 
	//向左下方向 
	for(;i<N&&j>=0;i++,j--){
		if(F_[i][j]==0){
			count++;
		}else{
			char countString[N*N];
			itoa(count,countString,10);
			string strTemp = "/";
			strTemp = countString + strTemp;
			//cout<<"--"<<strTemp<<"--"<<endl;
			char SSSS_String[N*N];
			itoa(SSSS[i][j],SSSS_String,10);
			strTemp = strTemp + SSSS_String;
			//cout<<"**"<<strTemp<<"**"<<endl;
			
			//中间符号 
			ac_EntropyCoding_MiddleSymbol[index].R_S = strTemp;
			ac_EntropyCoding_MiddleSymbol[index].temp = F_[i][j];
			index++;
			count = 0;//置为0 ,计算下个不为0的数前面0的个数
		}
	}
	if(i>=N){//当出现往下突出时,挪回正规
		i--;
		j = j+2;
	}
	if(F_[i][j]==0){//最后一个点
		count++;
		ac_EntropyCoding_MiddleSymbol[index].R_S = "0/0(EOB)";
		ac_EntropyCoding_MiddleSymbol[index].temp = INT_MAX;
		index++; 
	}else{
		char countString[N*N];
		itoa(count,countString,10);
		string strTemp = "/";
		strTemp = countString + strTemp;
		//cout<<"--"<<strTemp<<"--"<<endl;
		char SSSS_String[N*N];
		itoa(SSSS[i][j],SSSS_String,10);
		strTemp = strTemp + SSSS_String;
		//cout<<"**"<<strTemp<<"**"<<endl;
		
		//中间符号 
		ac_EntropyCoding_MiddleSymbol[index].R_S = strTemp+"(EOB)";
		ac_EntropyCoding_MiddleSymbol[index].temp = F_[i][j];
		index++;
	}
	
	/*测试*/
//	for(int k=0;k<index;k++){
//		cout<<ac_EntropyCoding_MiddleSymbol[k].R_S<<"\t"<<ac_EntropyCoding_MiddleSymbol[k].temp<<endl;
//	}
	
	
	//对中间符号进行符号编码 
	//对R/S通过查亮度AC码表进行熵编码
	for(int u=0;u<index;u++){
		for(int v=0;v<stringMapList.partNum;v++){
			if(ac_EntropyCoding_MiddleSymbol[u].R_S == stringMapList.stringMap[v].key){
				ac_EntropyCodingStr[u].strTemp1 = stringMapList.stringMap[v].value;
			}
		}
		//对 temp进行转换成补码 
		//先将 temp转换成二进制串
		if(ac_EntropyCoding_MiddleSymbol[u].R_S!="0/0(EOB)"){
			ac_EntropyCodingStr[u].strTemp = TenToTwo(abs(ac_EntropyCoding_MiddleSymbol[u].temp));
			//转换成补码
			if(ac_EntropyCoding_MiddleSymbol[u].temp<0){
				ac_EntropyCodingStr[u].strTemp = ConvertToComplement(ac_EntropyCodingStr[u].strTemp);	
			}
			
			/*测试*/
//			cout<<"**********"<<ac_EntropyCodingStr[u].strTemp<<endl;
		}else{
			ac_EntropyCodingStr[u].strTemp = ""+'\0';
			
			/*测试*/
//			cout<<"**********+"<<ac_EntropyCodingStr[u].strTemp<<endl;
		}
	}
	
	/*测试*/
//	for(int k=0;k<index;k++){
//		cout<<ac_EntropyCodingStr[k].strTemp1<<" "<<ac_EntropyCodingStr[k].strTemp<<endl;
//	}
	return true;
}

int main(){
	cout<<"压缩编码过程:"<<endl; 
	/*压缩编码*/
	const double PI = acos(-1);
//	double f[N][N]={139,144,149,153,155,155,155,155,
//					144,151,153,156,159,156,156,156,
//					150,155,160,163,158,156,156,156,
//					159,161,162,160,160,159,159,159,
//					159,160,161,162,162,155,155,155,
//					161,161,161,161,160,157,157,157,
//					162,162,161,163,162,157,157,157,
//					162,162,161,161,163,158,158,158};
	double f[N][N];
	double ff[N][N],F[N][N];
	
	/*输入图像的一个分量样本*/
	for(int i=0;i<N;i++){
		for(int j=0;j<N;j++){
			cin>>f[i][j];
		}
	}
	
	cout<<"源图像的一个分量样本:"<<endl; 
	/*输出—源图像的一个分量样本*/
	for(int i=0;i<N;i++){
		for(int j=0;j<N;j++){
			cout<<f[i][j]<<"\t";
		}
		cout<<endl;
	}
	
	//图像的一个分量样本-128后 
	for(int i=0;i<N;i++){
		for(int j=0;j<N;j++){
			ff[i][j] = f[i][j]-128;
		}
	}
	
	cout<<"源图像的一个分量样本-128后:"<<endl; 
	/*输出—图像的一个分量样本-128后*/
	for(int i=0;i<N;i++){
		for(int j=0;j<N;j++){
			cout<<ff[i][j]<<"\t";
		}
		cout<<endl;
	}
	
	//由公式计算DCT变化后的系数矩阵
	for(int u=0;u<N;u++){
		for(int v=0;v<N;v++){
			double temp = 0.0;
			for(int i=0;i<N;i++){
				for(int j=0;j<N;j++){
					temp = temp + ff[i][j]*cos((2*i+1)*u*PI*1.0/16)*cos((2*j+1)*v*PI*1.0/16);
				}
			}
			F[u][v] = 1.0/4*(u==0?1.0/sqrt(2):1)*(v==0?1.0/sqrt(2):1)*temp;
		}
	}
	
	/*输出—DCT变化后的系数矩阵*/
	//DCT变化后的系数矩阵
	cout<<"DCT变化后的系数矩阵:"<<endl;
	for(int u=0;u<N;u++){
		for(int v=0;v<N;v++){
			printf("%.1f\t",F[u][v]);
			//cout<<F[u][v]<<" ";
		}
		cout<<endl;
	}
	
	//利用公式将DCT变化后的系数矩阵转换为规格化量化系数矩阵 
	int F_[N][N];//规格化量化系数矩阵 
	for(int i=0;i<N;i++){
		for(int j=0;j<N;j++){//二维数组Q 为亮度量化值表 
			F_[i][j] = (int)((F[i][j]/brightnessQuantizedValueTable.Q[i][j])>0.0)?floor((F[i][j]/brightnessQuantizedValueTable.Q[i][j]) + 0.5) : ceil((F[i][j]/brightnessQuantizedValueTable.Q[i][j]) - 0.5);//进行量化,然后进行四舍五入 
		}
	}
	
	/*输出—规格化量化系数矩阵*/
	//规格化量化系数矩阵 
	cout<<"规格化量化系数:"<<endl;
	for(int u=0;u<N;u++){
		for(int v=0;v<N;v++){
			cout<<F_[u][v]<<"\t";
		}
		cout<<endl;
	}
	 
	//对DC系数生成中间符号(temp1,temp)
	int temp = F_[0][0];
	int temp1;
	EntropyCoding dc_EntropyCodingStr = DC_EntropyCoding(temp,temp1);
	
	cout<<"中间符号:"<<endl; 
	/*输出—DC系数中间符号*/
	cout<<temp1<<"\t"<<temp<<endl; 
	
	
	int index=0; //AC系数生成中间符号的个数 
	//对AC系数生成中间符号
	AC_EntropyCoding(F_,index);
	
	/*输出—AC系数中间符号*/
	for(int k=0;k<index;k++){
		cout<<ac_EntropyCoding_MiddleSymbol[k].R_S<<"\t";
		if(ac_EntropyCoding_MiddleSymbol[k].R_S!="0/0(EOB)"){
			cout<<ac_EntropyCoding_MiddleSymbol[k].temp;
		}
		cout<<endl;
	}
	
	cout<<"熵编码输出:"<<endl; 
	/*输出—DC系数熵编码输出*/
	cout<<dc_EntropyCodingStr.strTemp1<<"\t"<<dc_EntropyCodingStr.strTemp<<endl;
	
	/*输出—AC系数熵编码输出*/
	for(int k=0;k<index;k++){
		cout<<ac_EntropyCodingStr[k].strTemp1<<"\t"<<ac_EntropyCodingStr[k].strTemp<<endl;
	}
	
	
	cout<<"----------------------------------------------------------------------"<<endl<<endl;
	cout<<"解码过程:"<<endl; 
	//下面的解码过程只用到了压缩过程传输过来的熵编码和熵编码中AC系数的个数index(也可以通过一个循环来计算出这个index) 
	
	cout<<"待解码的熵编码:"<<endl;
	/*输出—DC系数熵编码输出*/
	cout<<dc_EntropyCodingStr.strTemp1<<"\t"<<dc_EntropyCodingStr.strTemp<<endl;
	
	/*输出—AC系数熵编码输出*/
	for(int k=0;k<index;k++){
		cout<<ac_EntropyCodingStr[k].strTemp1<<"\t"<<ac_EntropyCodingStr[k].strTemp<<endl;
	}
	
	/*解码*/
	//将DC系数熵编码的编码转换为中间符号
	//对strTemp1进行反向查找亮度DC差值码表得到temp1
	int Itemp1;
	for(int i=0;i<11;i++){
		if( dc_EntropyCodingStr.strTemp1 == brightnessDC_DifferenceTableList.brightnessDC_DifferenceTable[i]){
			Itemp1 = i;
		}
	}
	//对strTemp进行反向补码得到temp,可通过查找规律发现若是负数转换为补码后首个数字必为0,正数必不为0 
	int Itemp;
	if(dc_EntropyCodingStr.strTemp[0]=='0'){//为负数,先取反 
		string tempStr = ConvertToComplement(dc_EntropyCodingStr.strTemp);
		//转换为10进制
		Itemp = TwoToTen(tempStr);
		//加负号
		Itemp = -Itemp;
	} else{//为正数,直接转换为10进制即可 
		Itemp = TwoToTen(dc_EntropyCodingStr.strTemp);
	}
	
	cout<<"中间符号:"<<endl; 
	/*输出—DC中间符号*/
	cout<<Itemp1<<"\t"<<Itemp<<endl;
	
	
	//将AC系数熵编码的编码转换为中间符号
	AC_EntropyCoding_MiddleSymbol Iac_EntropyCoding_MiddleSymbol[N*N];
	//遍历所有的AC系数熵编码的编码对strTemp1进行反向查找亮度AC码表得到R_S
	for(int i=0;i<index;i++){
		for(int u=0;u<stringMapList.partNum;u++){
			if(ac_EntropyCodingStr[i].strTemp1==stringMapList.stringMap[u].value){
				Iac_EntropyCoding_MiddleSymbol[i].R_S = stringMapList.stringMap[u].key;
			}
		}
		//对strTemp进行反向补码得到temp,可通过查找规律发现若是负数转换为补码后首个数字必为0,正数必不为0
		if(ac_EntropyCodingStr[i].strTemp[0]=='0'){//为负数,先取反 
			string tempStr = ConvertToComplement(ac_EntropyCodingStr[i].strTemp);
			//转换为10进制
			Iac_EntropyCoding_MiddleSymbol[i].temp = TwoToTen(tempStr);
			//加负号
			Iac_EntropyCoding_MiddleSymbol[i].temp = -Iac_EntropyCoding_MiddleSymbol[i].temp;
		} else{//为正数,直接转换为10进制即可 
			Iac_EntropyCoding_MiddleSymbol[i].temp = TwoToTen(ac_EntropyCodingStr[i].strTemp);
		}
	}
	
	/*输出—AC中间符号*/
	for(int i=0;i<index;i++){
		cout<<Iac_EntropyCoding_MiddleSymbol[i].R_S<<"\t";
		if(Iac_EntropyCoding_MiddleSymbol[i].R_S!="0/0(EOB)"){
			cout<<Iac_EntropyCoding_MiddleSymbol[i].temp;
		}
		cout<<endl;
	} 
	
	//规格化量化系数 
	int IF_[N][N];
	
	//初始化矩阵 
	for(int u=0;u<N;u++){
		for(int v=0;v<N;v++){
			IF_[u][v]=0; 
		}
	}
	
	//DC系数
	//还原编码 
	IF_[0][0] = Itemp;
	//AC系数
	//Z字形还原编码
	int count;//计算0的个数
	int a=0,b=1; //初始位置
	for(int h=0;h<index;h++){
		//将/前的字符串转换为整数
		count = 0;
		for(int w=0;Iac_EntropyCoding_MiddleSymbol[h].R_S[w]!='/';w++){
			count = count*10 + Iac_EntropyCoding_MiddleSymbol[h].R_S[w]-'0';
		} 
		/*测试*/
//		cout<<"count:"<<count<<endl; 
		
		while(Iac_EntropyCoding_MiddleSymbol[h].R_S!="0/0(EOB)"){
			//根据查找规律发现,当行+列为奇数时向左下方向,当行+列为偶数时为右上方向 
			if((a+b)%2==0){//偶数,向右上方向 
				for(;count>=0&&a>=0&&b<N;a--,b++){
					if(count==0){//此时放temp 
						IF_[a][b] = Iac_EntropyCoding_MiddleSymbol[h].temp;
						count--;
						break; 
					}else{//此时放0 
						IF_[a][b] = 0;
						count--;
					}
				}
				if(count<0){//向右上移动 
					a--;
					b++;
				}
				if(a<0&&b>=N){//当出现正中间往上时,挪回正规  
					b--;
					a = a+2;
				}else if(a<0){//当出现往上突出时,挪回正规
					a++; 
				}else if(b>=N){//当出现往右突出时,挪回正规 
					b--;
					a = a+2;
				}
				if(count<0){//跳出到第一层循环 
					break; 
				}
			} else{//奇数,向左下方向 
				for(;count>=0&&a<N&&b>=0;a++,b--){
					if(count==0){//此时放temp 
						IF_[a][b] = Iac_EntropyCoding_MiddleSymbol[h].temp;
						count--;
						break; 
					}else{//此时放0 
						IF_[a][b] = 0;
						count--;
					}
				}
				if(count<0){//向左下移动 
					a++;
					b--;
				}
				if(a>=N&&b<0){//当出现正中间往下时,挪回正规 
					a--;
					b = b+2;
				}else if(a>=N){//当出现往下突出时,挪回正规 
					a--;
					b = b+2;
				}else if(b<0){//当出现往左突出时,挪回正规
					b++;
				}
				if(count<0){//跳出到第一层循环 
					break; 
				}
			}
		}
	}
	
	/*输出—规格化量化系数矩阵*/
	//规格化量化系数矩阵 
	cout<<"规格化量化系数:"<<endl;
	for(int u=0;u<N;u++){
		for(int v=0;v<N;v++){
			cout<<IF_[u][v]<<"\t";
		}
		cout<<endl;
	}
	
	
	//利用公式将规格化量化系数矩阵转换为逆量化后的系数矩阵  
	double IF[N][N];//逆量化后的系数矩阵
	for(int i=0;i<N;i++){
		for(int j=0;j<N;j++){//二维数组Q 为亮度量化值表
			IF[i][j] = 1.0*IF_[i][j]*brightnessQuantizedValueTable.Q[i][j];
		}
	}
	
	/*输出—逆量化后的系数矩阵*/
	cout<<"逆量化后的系数矩阵:"<<endl;
	for(int i=0;i<N;i++){
		for(int j=0;j<N;j++){
			cout<<IF[i][j]<<"\t";
		}
		cout<<endl;
	}
	
	//由公式计算IDCT变化后的系数矩阵
	double Iff[N][N];
	for(int i=0;i<N;i++){
		for(int j=0;j<N;j++){
			double sum = 0.0;
			for(int u=0;u<N;u++){
				for(int v=0;v<N;v++){
					sum = sum + (u==0?1.0/sqrt(2.0):1.0)*(v==0?1.0/sqrt(2.0):1.0)*IF[u][v]*cos((2*i+1)*u*PI*1.0/16)*cos((2*j+1)*v*PI*1.0/16);	
				}
			}
			Iff[i][j] = 1.0/4*sum;
		}
	}
	
	/*输出—IDCT变化后的系数矩阵*/
	cout<<"IDCT变化后的系数矩阵:"<<endl;
	for(int i=0;i<N;i++){
		for(int j=0;j<N;j++){
			//cout<<Iff[i][j]<<"\t";
			printf("%.0f\t",Iff[i][j]);
		}
		cout<<endl;
	}

	
	cout<<"源图像的一个分量样本的重构图像:"<<endl; 
	/*IDCT变化后的系数矩阵+128后变成源图像的一个分量样本的重构图像*/
	double If[N][N]; 
	for(int i=0;i<N;i++){
		for(int j=0;j<N;j++){
			If[i][j] = Iff[i][j]+128;
		}
	}
	
	/*输出—源图像的一个分量样本的重构图像*/
	for(int i=0;i<N;i++){
		for(int j=0;j<N;j++){
			//cout<<If[i][j]<<"\t";
			printf("%.0f\t",If[i][j]);
		}
		cout<<endl;
	}
	return 0;
} 


测试用例(书本P83):

139 144 149 153 155 155 155 155

144 151 153 156 159 156 156 156

150 155 160 163 158 156 156 156

159 161 162 160 160 159 159 159

159 160 161 162 162 155 155 155

161 161 161 161 160 157 157 157

162 162 161 163 162 157 157 157

162 162 161 161 163 158 158 158

 

压缩编码:


解码:


利用MATLAB将测试用例的数据生成jpeg格式图片


将得到后的图片放大:


再将经过JPEG压缩解压后的数据生成jpeg格式图片

 

将得到后的图片放大:

                          

 

四、实验结论、实验体会

    本次实验算是我目前“算法”编码生涯中编码最长的一次,一共700多行的代码。在编码途中我遇到了非常多的问题。

1. 系统问题:在我初次编完750行左右代码的时候,代码突然抽疯,丢失了很多代码,只剩下600行左右,而且有些后面的代码跑到中间去了。点返回键显示返回栈错误,此时我的内心是崩溃的,但是没办法只能重新编写丢失的代码,并对部分代码进行优化修改。然后每写完一段代码,马上新建一个TXT文件保存代码,以防再次发生这种情况。

2. 编程语言问题:在这次编码过程,我愈发的感觉C++/C 类库真的太少了,不利于快速编程。例如:

2.1 string的字符串操作能力及其低下,很多都要自己编写,甚至进行字符串连接时也是非常苛刻,不能将两个常量字符串进行连接、其它类型的数据不能直接跟字符串连接,可用操作函数极少等等,与JAVA语言完全没得比。其次,C++语言的矩阵操作能力也是比较低下的,与MATLAB语言完全没得比,而且我觉得此次的实验更应该用MATLAB语言实现,这样会节省大量的工作;

2.2 用函数返回结构体数组,里面的字符串会出现一些无法处理的乱码,最后不得不将其定义为全局变量;

3. 参照表格繁多复杂、C++/C语言数据类型较为贫乏:有一些可以通过观察找到规律编写,而有一些则是另外算法得到的结果,因此为方便起见,将一些找不到规律的表直接用映射方法进行存储。

4. 课本出错问题:课本上的IDCT逆变换公式有问题,通过在网上查找正确的公式进行编码;其次课本上给出的测试用例也有一些问题;

5. 编码问题:Z字形编码,编码时通过模拟其轨迹,以及查找规律;

6. 编码问题:代码命名重复,导致一些结果不正确或编译出问题,要时刻明白每个变量的生命周期,尽最大努力缩短变量的生命周期,减少不必要的冲突;

7. 编码问题:对变量的数据类型要时刻有个清晰的认识;

8. 编码规范问题:尽量多的注释,以及对不同类别注释的区分;

9. 不同平台不兼容问题:本次实验我在Dev-C ++开发,而当我把代码放到VS2010时却出现了许多编译错误,需要进行适当修改,VS对语法检查非常苛刻。

    最后,在此次编码过程中我收获颇多,编写了很多非常巧妙的算法,精简了很多常用算法,使得代码效率更高、更简洁、更方便。比如:十进制转换为其它进制的算法,只需要几条非常简短的代码就能实现,根本不用自己编写繁琐的代码,函数原型为char*itoa(int value,char*string,int radix); int value 被转换的整数,char *string 转换后储存的字符数组,int radix 转换进制数,如2,8,10,16 进制等;功能:将任意类型的数字转换为字符串,使用时必须包含<cstdlib>头文件,开启进制转换新玩法。而至于二进制转换为十进制以及二进制串逐位取反的算法也进行了巧妙的设计。

    本来想直接将一张JPEG格式的图片进行压缩编码,然后解压输出,但是经过尝试发现需要编写更庞大的代码,对代码要进行一些修改、增加更多的数据,在时间上是不太允许的,故而没有实现这个想法。


写的不是很好,望各位大神多多指正,不喜勿喷。

已标记关键词 清除标记
相关推荐
项目:JPEG图片压缩程序 v2.0 更新:2004/5/23 -------------   这是一个比较完整的JPEG压缩程序,支持 彩色/灰度、Y分量水平/垂直采 样律、图像质量 调整。使用的是最简单的基线系统(BaseLine)压缩方式, 量化表及Huffman表都是与ACDSee一致的,没有提供自适应Huffman表压缩功能。 速度测试 ~~~~~~~~ CPU:赛杨733 内存:128MB SDRAM 操作系统:Windows 98 SE (请运行编译后的exe!在VB开发环境下是解析运行,大规模数据处理速度很慢) (单位:毫秒ms) ┏━━━━┳━━━━━━┯━━━━━┯━━━━━┓ ┃    ┃512*512 Lena│ 800*600 │ 1024*768 ┃ ┣━━━━╋━━━━━━┿━━━━━┿━━━━━┫ ┃ 灰度 ┃ 376.9725 │ 597.6534 │ 950.3084 ┃ ┠────╂──────┼─────┼─────┨ ┃彩色 1:1┃ 676.0799 │1097.7514 │1655.6605 ┃ ┠────╂──────┼─────┼─────┨ ┃彩色 2:1┃ 587.0741 │ 921.2441 │1384.4843 ┃ ┠────╂──────┼─────┼─────┨ ┃彩色 1:2┃ 574.2990 │ 905.8943 │1394.3513 ┃ ┠────╂──────┼─────┼─────┨ ┃彩色 2:2┃ 509.0129 │ 806.8808 │1251.1223 ┃ ┗━━━━┻━━━━━━┷━━━━━┷━━━━━┛ 注:   “彩色 x:y”表示Y分量的水平/垂直采样滤。     1:1:YUV 111     2:1:YUV 422(水平)     1:2:YUV 422(垂直)     2:2:YUV 411 提示 ~~~~ JPEG压缩分为三个部分: 1.JPEG压缩原理及算法(RGB/YCbCr,DCT/IDCT,量化,Z字型编码,用DPCM对DC/用RLE对AC编码/Huffman编码) 2.JPEG标记格式 3.JPEG图片文件的组织形式(位于SOS后的图像压缩数据是由MCU块怎样排列的,特别是使用了DRI之后) 细节问题 ~~~~~~~~ 1.解码过程中遇到“FF 00”当“FF”处理,否则忽略“FF” 2.进行DCT前YCbCr要减128,进行IDCT前要加128(相当于Y减128,CbCr不需要加128) 3.每个标记中数据可以不止一个(去掉长度的部分就是一段) 参考文献 ~~~~~~~~ 1.何斌《VC++数字图像处理》人民邮电出版社, 2001.4 2.张益贞《VC++实现JPEG/MPEG编解码技术》人民邮电出版社, 2002.11 3.杨淑莹《VC++图像处理程序设计》清华大学出版社/北方交通大学出版社, 2003.11(SOF0中,宽度/高度 写反了) 4.网络上的一些文章,比如 云风的“JPEG 简易文档 V2.12”…… VB不适合编写高速JPEG压缩程序的原因 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1.JPEG压缩需要大量的位运算,而VB中只能靠乘除来移位,效率太低了 2.在高级语言中确定一个整数占用的位数需要一大堆if,可在汇编中一条位扫描指令就行了 3.浮点DCT变换太慢,而 AAN快速DCT变换算法MMX优化版 早就公布n年了
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页