动态规划在图像压缩中的应用

动态规划在图像压缩中的应用

一、基本原理

图像压缩是一种降低图像文件大小的技术,目的是为了节省存储空间或加快传输速度。

图像表示

在计算机中,图像通常用一系列的像素点来表示,每个像素点有一个灰度值,范围通常是0到255,所以需要8位来表示一个像素点。

图像压缩的存储格式

图像压缩的目的是减少存储或传输图像所需的位数。这段内容描述的一种方法是将图像分割成若干个连续的像素段,每个段中的所有像素都用相同数量的位来表示。每个像素段还需要一些额外的信息来存储,比如用多少位来表示这个段中的像素,以及这个段有多少个像素等。

存储空间计算

对于每个像素段,需要存储的位数是这个段中像素数量乘以每个像素的位数,再加上一些额外的位数。整个图像所需的存储空间是所有像素段所需存储空间的总和。

动态规划求解

动态规划是一种解决问题的方法,它将问题分解成一系列相互关联的子问题,先解决子问题,然后用子问题的解来构造原问题的解。
在这个图像压缩问题中,定义 s [ i ] s[i] s[i] 为前 i i i 个像素的最优分段所需的存储位数。最优分段意味着所需存储位数最少。这个问题满足最优子结构性质,即整个问题的最优解可以通过组合其子问题的最优解来获得。

具体来说,对于前 i i i 个像素的最优分段,可以通过考虑前 i − k i-k ik 个像素的最优分段,再加上从第 i − k + 1 i-k+1 ik+1 个像素到第 i i i 个像素的一个段来获得。通过遍历所有可能的 k k k 值,可以找到使得总存储位数最小的分段方式。

具体计算公式

s [ i ] = min ⁡ k { s [ i − k ] + k ⋅ b max ( i − k + 1 , i ) + 11 } s[i] = \min_k \{ s[i-k] + k \cdot b_{\text{max}}(i-k+1, i) + 11 \} s[i]=kmin{s[ik]+kbmax(ik+1,i)+11}

其中, b max ( i − k + 1 , i ) b_{\text{max}}(i-k+1, i) bmax(ik+1,i)表示从第 i − k + 1 i-k+1 ik+1 个像素到第 i i i个像素这个段中最大灰度值对应的位数,加上一些额外的位数来存储必要的信息。

通过这种方法,可以逐步计算出整个图像的最优分段,从而实现图像压缩。

二、主要实现代码(参考计算机算法设计与分析教材)

1、各个基本功能函数
  1. int length(int i);

    • 功能:该函数计算数字i在二进制表示下所需要的位数。
    • 参数:一个整数i
    • 返回值:一个整数,表示i在二进制下所需要的位数。
  2. void Compress(int n, int p[], int s[], int l[], int b[]);

    • 功能:该函数根据给定的图像灰度数组p,计算出图像的最优压缩方式。它会填充s, l, 和 b数组,其中s表示每个像素位置的累计最小存储空间,l表示每个像素位置的最优段长度,b表示每个像素位置的最大位数。
    • 参数
      • n:灰度数组p的长度。
      • p[]:图像的灰度数组。
      • s[]:存储每个像素位置的累计最小存储空间。
      • l[]:存储每个像素位置的最优段长度。
      • b[]:存储每个像素位置的最大位数。
    • 返回值:无。
  3. void Traceback(int n, int& i, int s[], int l[]);

    • 功能:该函数通过回溯法找到图像的最优分段位置。
    • 参数
      • n:需要回溯的最后一个像素位置。
      • i:引用参数,用于记录分段的数量。
      • s[]:存储每个像素位置的累计最小存储空间。
      • l[]:存储每个像素位置的最优段长度。
    • 返回值:无。
  4. void Output(int s[], int l[], int b[], int n);

    • 功能:该函数输出图像的压缩结果,包括每个段的长度和所需存储位数。
    • 参数
      • s[]:存储每个像素位置的累计最小存储空间。
      • l[]:存储每个像素位置的最优段长度。
      • b[]:存储每个像素位置的最大位数。
      • n:灰度数组的长度。
    • 返回值:无。
2、具体代码实现
// 导入标准输入输出库
#include <iostream>
// 使用标准命名空间,这样我们就可以直接使用cout, cin等,而不需要写std::cout, std::cin。
using namespace std;

// 定义N为7,表示图像的灰度序列长度。
const int N = 7;

// 函数声明
int length(int i);
void Compress(int n,int p[],int s[],int l[],int b[]);
void Traceback(int n,int& i,int s[],int l[]);
void Output(int s[],int l[],int b[],int n);

int main()
{
    // 定义图像的灰度数组p,下标从1开始计数。
    int p[] = {0,10,12,15,255,1,2};
    // 初始化存储空间数组s,分段长度数组l,每段所需存储位数数组b。
    int s[N],l[N],b[N];

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

    // 输出原始的灰度序列
    for(int i=1; i<N; i++)
    {
        cout << p[i] << " ";
    }
    cout << endl;

    // 调用压缩函数
    Compress(N-1, p, s, l, b);
    // 输出压缩后的结果
    Output(s, l, b, N-1);
    return 0;
}

void Compress(int n, int p[], int s[], int l[], int b[])
{
    // 这里我们设置了两个常数:每段的最大像素数量为256,每段的头部信息需要11位来存储。
    int Lmax = 256, header = 11;
    
    // 我们设置s[0]为0,这表示开始时,没有任何像素,所以需要的存储空间为0。
    s[0] = 0;
    
    // 这个循环表示我们要查看每一个像素,从第一个开始。
    for(int i=1; i<=n; i++)
    {
        // 对于每个像素,我们要计算它需要多少位来表示。这个数字存储在b数组中。
        b[i] = length(p[i]);
        
        // 为了方便计算,我们假设当前像素是最需要位数的像素。
        int bmax = b[i];
        
        // 初始假设:如果只有这一个像素作为一个段,那么所需的存储空间就是前面所有的加上这个像素的。
        s[i] = s[i-1] + bmax;
        
        // 并且我们先假设这个像素单独是一个段。
        l[i] = 1;

        // 然后我们要考虑把更多的像素加入这个段,看看是否能够更节省空间。
        for(int j=2; j<=i && j<=Lmax; j++)
        {
            // 我们检查加入新像素后,是否有新的像素需要的位数更多。
            if(bmax < b[i-j+1])
            {
                // 如果是,我们更新这个最大的位数。
                bmax = b[i-j+1];
            }
            
            // 接下来我们要看,如果加入了j个像素作为一个段,是否能节省存储空间。
            if(s[i] > s[i-j] + j*bmax)
            {
                // 如果能,我们就更新需要的存储空间和这个段的长度。
                s[i] = s[i-j] + j*bmax;
                l[i] = j;
            }
        }
        
        // 最后,加上每个段开始时的那11位的头部信息。
        s[i] += header;
    }
}


// 计算数字i的二进制表示需要的位数
int length(int i)
{
    // 这里,我们初始化k为1。因为任何非零数字至少需要1位来表示。
    int k = 1;

    // 我们将i除以2,因为我们想知道这个数字能够被多少次2整除。
    // 这是因为每次除以2,我们其实就是在去掉这个数字二进制表示的最后一位。
    i = i/2;

    // 当i大于0时,我们继续执行循环体。
    while(i > 0)
    {
        // 每次循环,我们都将k增加1,因为我们又去掉了i的一个二进制位。
        k++;

        // 继续除以2,为下一次循环做准备。
        i = i/2;
    }

    // 最后,我们返回k,这是数字i需要的二进制位数。
    return k;
}


// 回溯找到最优分段的具体位置
void Traceback(int n, int& i, int s[], int l[])
{
    if(n == 0)
        return;
    Traceback(n-l[n], i, s, l);
    // 存储分段位置
    s[i++] = n-l[n];
}

void Output(int s[], int l[], int b[], int n)
{
    // 输出最小的存储空间
    cout << "图像压缩后的最小空间为:" << s[n] << endl;
    int m = 0;
    Traceback(n, m, s, l);
    s[m] = n;
    cout << "将原灰度序列分成" << m << "段序列段" << endl;

    // 输出每段的长度和所需存储位数
    for(int j=1; j<=m; j++)
    {
        l[j] = l[s[j]];
        b[j] = b[s[j]];
    }
    for(int j=1; j<=m; j++)
    {
        cout << "段长度:" << l[j] << ",所需存储位数:" << b[j] << endl;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值