c++实现几种模式的算术编码的编码和译码(固定模式,自适应模式,基于上下文的多阶自适应算术编码,完全统计模型)(信息论基础与编码)

算术编码(Arithmetic Coding)

算术编码是图像压缩的主要算法之一。 是一种无损数据压缩方法,也是一种熵编码的方法。和其它熵编码方法不同的地方在于,其他的熵编码方法通常是把输入的消息分割为符号,然后对每个符号进行编码,而算术编码是直接把整个输入的消息编码为一个数,一个满足(0.0 ≤ n < 1.0)的小数n。[^1]
算术编码是将一个符号序列表示成0和1之间的一个间隔(interval),并用该间隔内的一个浮点小数表示,再将该小数转换成为二进制数,符号序列越长,对应的间隔越小,表示这一间隔的二进制位数就越多。

固定模式AC

题目:设信源可能输出的符号是26个字母,且每个字母出现的概率为:a, b, c, d, e, f 均为0.1,其它是等概的,试编写程序可以对任意字母序列(如presentation)进行固定模式的算术编码,并进行相应的译码。

自适应算术编码

操作方法

自适应算术编码即每输入一个字符就实时调整一次字符出现概率,按照此概率进行算术编码,循环。

步骤:
想象一个袋子里装着一些小球,每种字符相当于一个不同颜色的小球,最初,每种小球被摸中的概率都相等:
1.从输入流中每读取一个字符 ,就对该符号进行算术编码;
2.向袋中投入代表该种字符的小球,小球总数增加1,重新计算频率;
3.循环下去,每读一个字符就更新一次频率,进行算术编码。

问题和解答

1.wireeeee和wireless计算出来的编码长度哪个更短?为什么?
答:wireeeee更短。输入的字符串排列越有个性,编码越短。
2. 讲讲decord函数是怎么实现的。
答:先将编码出来的字符串转换为十进制数,然后概率初始化,再搜索该数所在区间,计算出新的区间,将temp转换成字母,译出字母再存入数组,循环就能实现译码。
3.为什么分数组序号=0和!=0的情况?
答:用io输入输出流的getline()获取的数组首位元素不能用于正则表达式,a[0]=‘ ’,必须分开讨论;也有其他解决办法,使用其他获取数组元素的方式。

算法流程图

自适应算术编码流程图

代码

题目要求:设信源可能输出的符号是26个字母,且每个字母出现的概率未知,试编写程序可以对任意字母序列(如presentation)进行自适应模式的算术编码,并进行相应的译码。(采用 8个字符一组,用空格或者回车作为间隔。即输入的字符串超过8位时,以8个为一组进行编码,相邻两组之间的记忆性取消。)

/****************** 自适应算术编码 **********************/
#include<cmath>
#include<string>
#include<iostream>
#include<iomanip>
using namespace std;
double proc[] = { 1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,
				1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,
				1.0,1.0,1.0,1.0,1.0,1.0 };
double proc_sum = 26;
double probability[] = { 1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0 };
double  areaBegin, areaEnd;
int cord[1000];
double cordLength;
char str[1000], newstr[1000];
int strLength = 0;  //字符串总长度
int num = 0;  //计数
int rowLength = 0;  //每行长度
double result;

bool readdat()
{
	cout<<"*********** 自适应模式 ***********\n";
	cout<<"请输入字符串(a--z): \n";

	//%s格式用来输入一个字符串,通过空格和换行来识别一个字符串的结束。也就是说使用%s格式输入并保存到字符数组中的字符串是不含空格的。
	//scanf("%s", str);
//	cin.getline(str, 1000, '\n');

	cin.getline(str, 1000);
	while (str[strLength] != '\0')
		strLength++;
	cout << "字符总长度:"<<strLength << endl;
	for (int i = 0; i < strLength; i++)   // 输入是否合法
		if ((str[i] > 'z' || str[i] < 'a')&& str[i] != ' ') return 1;
	return 0;
}

void encord()
{
	cout<<"  编  码  :"<< endl;
	double w = 0.0, len;
	areaBegin = 0.0, areaEnd = 1.0;

	//测试
	//cout << "rowLength: "<<rowLength << endl<<"num: "<<num<<endl;
	double proc[] = { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 };  // 概率初始化
	double proc_sum = 26;
	double probability[] = { 1.0 / 26.0,1.0 / 26.0,1.0 / 26.0,1.0 / 26.0,1.0 / 26.0,
							1.0 / 26.0,1.0 / 26.0,1.0 / 26.0,1.0 / 26.0,1.0 / 26.0,
							1.0 / 26.0,1.0 / 26.0,1.0 / 26.0,1.0 / 26.0,1.0 / 26.0,
							1.0 / 26.0,1.0 / 26.0,1.0 / 26.0,1.0 / 26.0,1.0 / 26.0,
							1.0 / 26.0,1.0 / 26.0,1.0 / 26.0,1.0 / 26.0,1.0 / 26.0,
							1.0 / 26.0 };

	for (int i = num;i < num+rowLength;i++)
	{
		int n = str[i] - 'a'; 
		w = 0.0; int k;
		for (k = 0; k < n; k++)
			w += probability[k];   // 累加计算所在区间
		len = areaEnd - areaBegin;				// 计算新的区间
		areaEnd = areaBegin + len * (w + probability[k]);
		areaBegin += len * w;

		int j; 
		proc_sum += 1;	// 调整概率分布
		for (j = 0; j < 26; j++)
			if (j == n) proc[j] += 1;           //向袋子里加小球
		for (j = 0; j < 26; j++)
			probability[j] = proc[j] / proc_sum;  //计算现有概率

	}
//显示过程
	cout <<"区间:["<<setprecision(20)<<areaBegin<<","<< setprecision(20) << areaEnd<<")"<<endl;      // Change to fixed notation
	result = areaBegin * (double)0.01 + areaEnd * (double)0.99; // 选择适当的点
	cout << "选点:" << result << endl;
	cordLength = ceil(-log(areaEnd - areaBegin)/log(2));    //运用公式求码长
	cout << "编码位数: " << cordLength << endl;

	cout << "编码结果: ";
	double temp1 = result; int temp2;
	for (int j = 0; j < cordLength; j++)  // 十进制转换成二进制(小数乘二取整)
	{
		temp1 *= 2;
		temp2 = int(temp1);
		temp1 -= temp2;

		cord[j] = temp2;   //编的码顺着存入数组
		cout<<temp2;
	}
	cout << endl;

}

void decord()
{
	cout<<"  译  码  :"<<endl;
	result = 0.0; double wei = 0.5;
	for (int i = 0; i < cordLength; i++, wei *= 0.5)  // 二进制转换成十进制
		result += wei * cord[i];
	cout << "译码选取的数:" << setprecision(20) << result << endl<<"译码结果:";

	double proc[] = { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 };  // 概率初始化
	double proc_sum = 26;
	double probability[] = { 1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,
							1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,
							1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,
							1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,
							1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,1.0/26.0,
							1.0/26.0 };

	areaBegin = 0.0, areaEnd = 1.0; wei = 0.0;
	int temp;  double len;
	for (int j = num; j < (num+rowLength); j++)
	{
		temp = 0; wei = 0.0; len = areaEnd - areaBegin;
		while (result - areaBegin > wei*len)
		{
			wei += probability[temp++];
		}  // 搜索所在区间
		temp--;
		areaEnd = areaBegin + wei * len;  // 计算新的区间
		areaBegin = areaBegin + (wei - probability[temp])*len;
		cout << (char)(temp + 97);    //转换成字母
		newstr[j]=(char)(temp+97);    //译出字母存入数组

		int m;
		proc_sum += 1;	// 调整概率分布
		for (m = 0; m < 26; m++)
			if (m == temp) proc[m] += 1;
		for (m = 0; m < 26; m++)
			probability[m] = proc[m] / proc_sum;
	}
	cout << endl;
}

int main() {
	if (readdat())
		cout<<"字符输入错误!!!\n";
	else {
		while (str[num] != '\0') {

			if (num == 0) {
//				cout << rowLength << " " << num << endl;
				while (str[num + rowLength] != ' '&&str[num + rowLength] != '\0') {
					rowLength++;           //数出每组多少个
				}
//				cout << rowLength << " " << num << endl;

				encord();
				decord();
				cout << endl;
				num = num + rowLength;
				rowLength = 0;
			}
			else {
//			cout << rowLength << " " << num << endl;
			if (str[num] = ' ') { num=num+1; }         //新的开始
			while (str[num+rowLength] != ' '&&str[num + rowLength] != '\0') {
				rowLength++;           //数出每组多少个
			}
//			cout << rowLength << " " << num << endl;

			encord();
			decord();
			cout << endl;
			num = num + rowLength ;
			rowLength = 0;
			}
		}
	}
	cout << "最终译码结果:" << endl;
	for (int i = 0; i < strLength; i++)
		cout << newstr[i] ;
	return 0;
}

心得

1.对程序运行顺序感到迷惑时,列表一步步计算变量或可以画流程图。
2.将代码多细分成几个模块,可以使代码更清晰,可读性更强,哪一部分有问题修改也不会影响其他模块。
3.由于算术编码需要计算[0,1)的小数,所以对数的精度可能会有要求,需要注意数据类型的统一,本题中采用double类型,注意小数精度的设置。

基于上下文的多阶自适应算术编码(CABAC,H.264编码标准)

题目要求:设信源可能输出的符号是a, b, c 三个字母,构成一个二阶Markov信源,且各阶条件概率如下,试编写程序可以对任意字母序列(如abbcabcb)进行基于上下文的自适应算术编码,并进行相应的译码。

零阶条件概率:
p(a)=1/3; p(b)=1/3; p©=1/3;
一阶条件概率:
p(a/a)=1/2; p(b/a )=1/4; p(c/a)=1/4;
p(a/b)=1/4; p(b/b)=1/2; p(c/b)=1/4;
p(a/c)=1/4; p(b/c)=1/4; p(c/c)=1/2;
二阶条件概率:
p(a/aa)=3/5; p(b/aa)=1/5; p(c/aa)=1/5;
p(a/ab)=1/4; p(b/ab)=1/4; p(c/ab)=1/2;
p(a/ac)=1/4; p(b/ac)=1/4; p(c/a2)=1/2;
p(a/ba)=1/2; p(b/ba)=1/4; p(c/ba)=1/4;
p(a/bb)=1/5; p(b/bb)=3/5; p(c/bb)=1/5;
p(a/bc)=1/4; p(b/bc)=1/4; p(c/bc)=1/2;
p(a/ca)=1/2; p(b/ca)=1/4; p(c/ca)=1/4;
p(a/cb)=1/2; p(b/cb)=1/4; p(c/cb)=1/4;
p(a/cc)=1/5; p(b/cc)=1/5; p(c/cc)=3/5;

完全统计模型算术编码

操作方法

原理:算术编码是将一个符号序列表示成0和1之间的一个间隔(interval),并用该间隔内的一个浮点小数表示,再将该小数转换成为二进制数,符号序列越长,对应的间隔越小,表示这一间隔的二进制位数就越多。完全统计模型算术编码是对已经输入进去的所有字符进行概率统计,按照这些概率来划分区间并编译码。
而香农编码属于不等长编码,通常将经常出现的消息变成短码,不经常出现的消息编成长码,从而提高通信效率。 香农编码严格意义上来说不是最佳码,它是采用信源符号的累计概率分布函数来分配码字。按照以下公式可计算其码长。
香农编码码长计算
步骤:想象一个空袋子,每种字符相当于一个不同颜色的小球,输入一个字符串相当于将所有对应的小球全部投入袋子。先计算每种小球出现频率,按照已经计算好的频率进行算术编码。然后按照该频率用公式计算香农编码的码长。

代码

题目要求:设信源可能输出的符号是26个字母,且每个字母出现的概率未知,试编写程序可以对任意字母序列(如presentation)进行完全统计模型的算术编码,并与香农编码进行码长比较(比值)。(采用 8个字符一组,用空格或者回车作为间隔。即输入的字符串超过8位时,以8个为一组进行编码,相邻两组之间的记忆性取消。)

/****************** 完全统计模型算术编码 **********************/
#include<cmath>
#include<string>
#include<iostream>
#include<iomanip>
using namespace std;
double proc[] = { 0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
				0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
				0.0,0.0,0.0,0.0,0.0,0.0 };
double probability[] = { 0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0 };
double  areaBegin, areaEnd;
int cord[1000];
double cordLength = 0;
double mycordLength = 0;
double ShanLength = 0;
char str[1000], newstr[1000];
int strLength = 0;  //字符串总长度
int num = 0;  //计数
int rowLength = 0;  //每行长度
int sumLength = 0;  //不含空格的长度
double result;


bool readdat()
{
	cout << "*********** 完全统计 ***********\n";
	cout << "请输入字符串(a--z): \n";

	//%s格式用来输入一个字符串,通过空格和换行来识别一个字符串的结束。也就是说使用%s格式输入并保存到字符数组中的字符串是不含空格的。
	//scanf("%s", str);
//	cin.getline(str, 1000, '\n');

	cin.getline(str, 1000);
	while (str[strLength] != '\0')
		strLength++;
//	cout << strLength << endl;
	for (int i = 0; i < strLength; i++)   // 输入是否合法
		if ((str[i] > 'z' || str[i] < 'a') && str[i] != ' ') return 1;
	return 0;
}

void Shannon() {
	double d;

	for (int j = 0; j < 26; j++) {
		if (probability[j] == 0) {
			continue;
		}
		d = proc[j] * ceil(-log(probability[j]) / log(2));
		//cout << probability[j] << " " << endl;    //测试
		ShanLength += d;	
	}
	cout << "香农编码码长为:" << ShanLength<<endl;

}

void encord()
{
	cout << "  编  码  :" << endl;
	double w = 0.0, len;
	areaBegin = 0.0, areaEnd = 1.0;

	//测试
	//cout << "rowLength: "<<rowLength << endl<<"num: "<<num<<endl;
	for (int i = num; i < num + rowLength; i++)
	{
		int n = str[i] - 'a';
		w = 0.0; int k;
		for (k = 0; k < n; k++)
			w += probability[k];   // 累加计算所在区间
		len = areaEnd - areaBegin;				// 计算新的区间
		areaEnd = areaBegin + len * (w + probability[k]);
		areaBegin += len * w;

	}
	//显示过程
	cout << "区间:[" << setprecision(20) << areaBegin << "," << setprecision(20) << areaEnd << ")" << endl;      // Change to fixed notation
	result = areaBegin * (double)0.000000000001 + areaEnd * (double)0.999999999999; // 选择适当的点
	cout << "选点:" << result << endl;
	cordLength = ceil(-log(areaEnd - areaBegin) / log(2));    //运用公式求码长
	cout << "编码位数: " << cordLength << endl;
	mycordLength += cordLength;
	cout << "编码结果: ";
	double temp1 = result; int temp2;
	for (int j = 0; j < cordLength; j++)  // 十进制转换成二进制(小数乘二取整)
	{
		temp1 *= 2;
		temp2 = int(temp1);
		temp1 -= temp2;

		cord[j] = temp2;   //编的码顺着存入数组
		cout << temp2;
	}
	cout << endl;
}

void decord()
{

	cout << "  译  码  :" << endl;
	result = 0.0; double wei = 0.5;
	for (int i = 0; i < cordLength; i++, wei *= 0.5)  // 二进制转换成十进制
		result += wei * cord[i];

	cout << "译码选取的数:" << setprecision(20) << result << endl << "译码结果:";

	areaBegin = 0.0, areaEnd = 1.0; wei = 0.0;
	int temp;  double len;
	for (int j = num; j < (num + rowLength); j++)
	{
		temp = 0; wei = 0.0; len = areaEnd - areaBegin;
		while (result - areaBegin > wei*len)
		{
			wei += probability[temp++];
		}  // 搜索所在区间
		temp--;
		areaEnd = areaBegin + wei * len;  // 计算新的区间
		areaBegin = areaBegin + (wei - probability[temp])*len;
		cout << (char)(temp + 97);    //转换成字母
		newstr[j] = (char)(temp + 97);    //译出字母存入数组
	}
	cout << endl;
}

int main() {
	if (readdat())
		cout << "字符输入错误!!!\n";
	else {
		while (str[num] != '\0') {
			//计算总的概率
			if (num == 0) {
				//cout << rowLength << " " << num << endl;//测试
				int j;
				for (j = 0; j < 26; j++)
					if (j == str[num + rowLength] - 'a') proc[j] += 1;       //向对应位置加小球

				rowLength++;           //数出每组多少个
				while (str[rowLength] != ' '&&str[rowLength] != '\0') {
					int j;
					for (j = 0; j < 26; j++)
						if (j == str[rowLength] - 'a') proc[j] += 1;           //向袋子里加小球

					cout << endl;
					rowLength++;           //数出每组多少个
				}

				num = num + rowLength;
				sumLength += rowLength;
				rowLength = 0;
			}
			else {

				if (str[num] = ' ') { num = num + 1; }
/*				for (int s=0;s<26;s++) //重新赋值
				{
					proc[s] = 0.0;
				}
*/				while (str[num + rowLength] != ' '&&str[num + rowLength] != '\0') {
					int j;
					for (j = 0; j < 26; j++)
						if (j == str[num + rowLength] - 'a') proc[j] += 1;           //向袋子里加小球

					rowLength++;           //数出每组多少个
				}


				num = num + rowLength;
				sumLength += rowLength;
				rowLength = 0;
			}
		}

		for (int j = 0; j < 26; j++) {
			probability[j] = proc[j] / (double)sumLength;  //计算现有概率
			cout << setprecision(20) << probability[j] << " ";
		}
		cout << endl;

		num = 0;
		while (str[num] != '\0') {
			//编码译码
			if (num == 0) {
				//cout << rowLength << " " << num << endl;//测试
/*					int j;
					for (j = 0; j < 26; j++)
						if (j == str[num + rowLength] - 'a') proc[j] += 1;           //向对应位置加小球
					for (j = 0; j < 26; j++) //测试
						cout << proc[j] << " ";
					cout << endl;
*/					rowLength++;           //数出每组多少个
				while (str[rowLength] != ' '&&str[rowLength] != '\0') {
/*					int j;
					for (j = 0; j < 26; j++)
						if (j == str[rowLength]-'a') proc[j] += 1;           //向袋子里加小球
					for (j = 0; j < 26; j++) //测试
						cout << proc[j] << " ";
					cout << endl;
*/					rowLength++;           //数出每组多少个
				}
				//测试

				//cout << rowLength << " " << num << endl;
				encord();
				decord();
				cout << endl;
				num = num + rowLength;
				rowLength = 0;
			}
			else {
				//cout << rowLength << " " << num << endl;
				
				if (str[num] = ' ') { num = num + 1; }
/*				for (int s=0;s<26;s++) //重新赋值
				{
					proc[s] = 0.0;
				}
*/				while (str[num + rowLength] != ' '&&str[num + rowLength] != '\0') {
/*					int j;
					for (j = 0; j < 26; j++)
						if (j == str[num + rowLength] - 'a') proc[j] += 1;           //向袋子里加小球
					for (int j = 0; j < 26; j++) //测试
						cout << proc[j] << " ";
					cout << endl;
*/					rowLength++;           //数出每组多少个
				}
/*				for (int j = 0; j < 26; j++) {//测试
					probability[j] = proc[j] / (double)rowLength;  
					cout << setprecision(20) << probability[j] << " ";
				}
				cout << endl;
*/			//cout << rowLength << " " << num << endl;//测试
				encord();
				decord();
				cout << endl;
				num = num + rowLength;
				rowLength = 0;

			}
		}
	}
	cout << "完全统计模型编码总长:" << mycordLength << endl;
	Shannon();
	cout << "完全统计模型算术编码和香农编码码长比值为:" << mycordLength / ShanLength << endl;
	cout << "最终译码结果:" << endl;
	for (int i = 0; i < strLength; i++)
		cout << newstr[i];
	return 0;
}

心得

1.%s格式用来输入一个字符串,通过空格和换行来识别一个字符串的结束。也就是说使用%s格式输入并保存到字符数组中的字符串是不含空格的。而使用cin.getline()得到的数组由于io输入流得到数组的第一个元素无法使用正则表达式,所以时常出错显示为空格‘ ’。处理数据时将num分为0和非0的两种情况避开了这点。
2.完全统计模型算术编码是对已经输入进去的所有字符进行概率统计,按照这些概率来划分区间并编译码。
3.将经常需要用到的步骤编写成一个函数如Shannon(),可以使代码更简洁优雅。

算法流程图

完全统计模型算术编码流程图

  • 1
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Howdareyou!

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值