【算法小记】DP之图像压缩

问题描述: 图片在计算机存储的是图片中的像素序列{p1,p2,p3......pn},也就是像素的灰度值(pi)。灰度值的范围是0~255。因此需要8位二进制表示一个像素。能否用更少的位数表示灰度值呢?即是图像压缩的问题。

问题分析:

可要求像素序列进行分段,使得最后所需要的位数最少(储存空间)。引入两个固定位数的值来表示:

1.  3位数表示当前段的每位像素的位数(000----表示像素位数为1);

2.  8位数表示当前段的像素个数。因为最多存储0~255个数字,一个灰度值最多2^3位(00000000----表示1个)

算法设计:

例子:{6, 5, 7,5, 245, 180, 28,28,19, 22, 25,20}一组灰度值序列。按默认需要12*8=96位

分为3段:第一段:最大为7,可用3位表示......

位数结果为:11*3(固定的11位数)+4*3+2*8+6*5=91<96,优化了!!

代码设计:

如果最优结果把像素序列分为m段(0<=m<=n)

DP法就需要一个数组记录每步最优值:S[i]记录{p1,p2,......,pi}的最优处理方式得到的最优解(存储位数);

假设每段由L[i]个像素点(0<=L[i]<=255):L[1]+L[2]+......+L[m]=n(所有段的像素点数量相加等于总的像素数量)--------8位表示

每段每个像素的存储位数肯定是一样的(否则这些像素不可能分配在同一段中),用b[i]来表示-----------3位表示

                                                      每段中有l[i]个像素点,每个像素点的灰度值用b[i]位存储

                                          每段:  l[i]*b[i](位存储每段的像素点的灰度值)+11 (每段至少需要11位)

递推公式:

s[i]表示前i个像素点{p1,p2,...,pi}所需要的总的存储位数

                     S[i]=min{ S[i-k]+k*bmax(i-k+1,i)+1},1<=k<=min{i,256}

#include<iostream>
using namespace std;

//求对数操作: 十进制0~1---1位表示;2~3---2位表示;4~7--3位表示;8~15--4位表示 
int length(int i){
	int k=1;
	while(i>=2){
		i=i/2;
		k++;
	}
	return k;
} 
/*
p[]:像素点的灰度序列,下标从1开始(p1,p2,....) 
s[i]:前i个像素点的最优分段所需的储存位数,s[0]=0 
*/
void compress(int n,int p[],int s[],int l[],int b[]){
	int Lmax=256,header=11;
//限制每段最多有256(2^8)个像素点;每段至少需要储存的固定11位 
 	s[0]=0;
	for(int i=0;i<=n;i++)
	{
		int bmax;
		b[i]=length(p[i]);
		bmax=b[i];
		s[i]=s[i-1]+bmax;
		l[i]=1;//先初始化为1,若找到更好的分段再替换
		//k代表从i开始往前数k个分成一块,从i开始往前数1,2,……分成一块,并求出其最小值
		for(int k=2;k<=i && k<=Lmax;k++)
		{
			if(bmax<b[i-k+1])	bmax=b[i-k+1];
			if(s[i]>=s[i-k]+k*bmax)
			{
				s[i]=s[i-k]+k*bmax;
				l[i]=k;//记录划分的位置 
			}
		 } 
		 s[i]+=header; 
	}
} 

void Traceback(int n,int& i,int s[],int l[]){
	if(n==0)	return ;
	Traceback(n-l[n],i,s,l); //最后一段有l[n]个像素,依次向前类推。
	s[i++]=n-l[n];//重新为数组s赋值,存储分段位置,最终m为共分了多少段
} 

void Output(int s[],int l[],int b[],int n){
	 //输出s[n]存储位数后,s[]被重新赋值,用来存储分段位置。
    cout<<"图像压缩后的最小空间为:"<<s[n]<<endl;
    int m=0;
    Traceback(n,m,s,l);
    s[m]=n;         //m为总段数,设s[m]=n,分割位置为第n个像素。
     cout<<"将原灰度序列分为"<<m<<"段序列"<<endl;
    for(int i=1;i<=m;i++)
    {
        l[i]=l[s[i]];
        b[i]=b[s[i]];       
    }
    for(int i=1;i<=m;i++)
    {
        cout<<"段长度:"<<l[i]<<",所需存储位数:"<<b[i]<<endl;
    }
}

int main()
{
    int p[]={6, 5, 7,5, 245, 180, 28,28,19, 22, 25,20};
    int N=13;//数组长度,因为下标从1开始 
    int s[N],l[N],b[N];
    cout<<"图像序列灰度值为:"<<endl;
    for(int i=1;i<N;i++)
        printf("%d ",p[i]);
    cout<<endl;
    compress(N-1,p,s,l,b);
    Output(s,l,b,N-1);
    return 0;
}

上述的Output、Traceback函数运用递归进行,比较难理解。

下面是老师讲解比较容易理解的方法

int Maxb(int b[],int i,int j )//找该段最大的位数 
{
	int bmax=0;
	bmax=b[i];
	for(int k=i+1;k<=j;k++)
	{
		if(bmax<b[k])
		   bmax=b[k];
	}	
	return bmax;	
}

void Output(int s[],int l[],int b[],int n)
{
	cout<<"图像压缩后的最小空间为:"<<s[n]<<endl;
	int m = 0,t;
	t=n;//序列长度 
    while(t)
    {
    	m++;
    	t=t-l[t];
    }//m:记录共分为几段 
	cout<<"将原灰度序列分成"<<m <<"段序列段"<<endl;
	
	int *length=new int[m+1];//存储每段的长度值 
	int *bmax=new int[m+1];//存储每段的灰度值占用的位数

    t=n;
	for(int j=m; j>=1; j--)
	{
		length[j] = l[t];//第j段的灰度值长度		
		bmax[j]= Maxb(b,(t-l[t]+1),t);//修改后才是正确的!!! 
		t=t-l[t];
	}
	for(int j=1; j<=m; j++)
	{
		cout<<"段长度:"<<length[j]<<",所需存储位数:"<<bmax[j]<<endl;
	}

}

 

完整代码展示 

//动态规划 图像压缩问题
#include <iostream> 
#include<stdlib.h>
using namespace std; 

const int N = 7;

int length(int i);
void Compress(int n,int p[],int s[],int l[],int b[]);
void Tracebace(int n,int& i,int s[],int l[]);
void Output(int s[],int l[],int b[],int n);//非教材版本的输出 

int main()
{
//	int p[] = {0,10,12,15,255,2,1};//图像灰度数组 下标从1开始计数
//	int p[] = {0,10,12,15,255,1,2};//图像灰度数组 下标从1开始计数
// int p[] = {0,230,222,1,255,121,2};//图像灰度数组 下标从1开始计数
// int p[] = {0,116,2,1,81,88,0};//图像灰度数组 下标从1开始计数
	int p[] = {0,230,222,15,255,1,2};//图像灰度数组 下标从1开始计数
	int s[N],l[N],b[N];
	/*
		s[]
		l[]
		b[]
	*/
   

	cout<<"图像的灰度序列为:"<<endl;

	for(int i=1;i<N;i++)
	{
		cout<<p[i]<<" ";
	}
	cout<<endl;

	Compress(N-1,p,s,l,b); 
	//************************************************
	cout<<"b[]:"<<endl;
	for(int i=1;i<=N-1;i++)
	{
		cout<<b[i]<<"  ";
	}
	cout<<endl;
	for(int i=1;i<=N-1;i++)
	{
		cout<<"s["<<i<<"]="<<s[i]<<"  "<<"l["<<i<<"]="<<l[i]<<endl;
	}
	//************************************************
	Output(s,l,b,N-1);
	system("pause");
	return 0;
}

void Compress(int n,int p[],int s[],int l[],int b[])
{//s[i]:1<=i<=n,是像素序列{p1,...,pi}的最优分段所需存储位数。
// l[i]:1<=i<=n,是像素序列{p1,...,pi}的最优分段的最后一段的长度。
//b[i]:一段每个像素的表示 
//递归公式:s[i]=min(1<=k<=min(i,256)) (s[i-k]+k*bmax(i-k+1,i)+11.(k:表示像素序列{p1,...,pi}的最优分段的最后一段的长度) 
	int Lmax = 256,header = 11,bmax;//*************************
	s[0] = 0; //初始值
	for(int i=1; i<=n; i++)//按照s[1] -> s[2] ->....s[n]的顺序求解,s[n]即最终的最优解。
	{
		b[i] = length(p[i]);//计算像素点p需要的存储位数
		bmax = b[i];
		
		s[i] = s[i-1] + bmax;//当k=1的情况下的值
		l[i] = 1;

		for(int k=2; k<=i && k<=Lmax;k++) //求k取2--->min(i,256)时是否有更小的值,有更小的则更新s[i]。 
		{
			if(bmax<b[i-k+1])
			{
				bmax = b[i-k+1];
			}
			if(s[i]>s[i-k]+k*bmax)
			{
				s[i] = s[i-k] + k*bmax;
				l[i] = k;
			}
		}
	  	s[i] += header;
     	
	
	}
}

int length(int i)
{//返回灰度值i需要的存储位数 。(例如:灰度值是0--1则返回1,灰度值是2--3则返回2,灰度值是4--7则返回3,... 灰度值是128--255则返回8)
	int k=1;
	i = i/2;
	while(i>0)
	{
		k++;
		i=i/2;
	}
	return k;
}

//********************增加这个函数后才是正确的!**********************************
int Maxb(int b[],int i,int j )//找该段最大的位数 
{
	int bmax=0;
	bmax=b[i];
	for(int k=i+1;k<=j;k++)
	{
		if(bmax<b[k])
		   bmax=b[k];
	}	
	return bmax;	
}

void Output(int s[],int l[],int b[],int n)
{
	cout<<"图像压缩后的最小空间为:"<<s[n]<<endl;
	int m = 0,t;
	t=n;//序列长度 
    while(t)
    {
    	m++;
    	t=t-l[t];
    }//m:记录共分为几段 
	cout<<"将原灰度序列分成"<<m <<"段序列段"<<endl;
	
	int *length=new int[m+1];//存储每段的长度值 
	int *bmax=new int[m+1];//存储每段的灰度值占用的位数

    t=n;
	for(int j=m; j>=1; j--)
	{
		length[j] = l[t];//第j段的灰度值长度		
		bmax[j]= Maxb(b,(t-l[t]+1),t);//修改后才是正确的!!! 
		t=t-l[t];
	}
	for(int j=1; j<=m; j++)
	{
		cout<<"段长度:"<<length[j]<<",所需存储位数:"<<bmax[j]<<endl;
	}

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值