【CSP试题回顾】202212-3-JPEG 解码

CSP-202212-3-JPEG 解码

关键点:Z字扫描

在JPEG压缩中,Z字形扫描是一种将8x8块的数据按照Z字形(或之字形)顺序重新排列的过程。这样做的目的是为了将相似的数据(尤其是零值)放置在一起,从而提高后续压缩步骤的效率。

1.方法一:任意大小矩阵的Z字扫描

zScan函数实现了Z字形扫描的过程。它按照特定的顺序遍历8x8块,这个顺序开始于左上角,然后沿着块的对角线向上移动,达到边界后转向并沿着另一条对角线向下移动,依此类推。在每一步中,该函数检查是否还有待填充的系数(来自数组 m),如果有,则将当前系数填入对应的位置;如果没有更多的系数,则在当前位置填充零。该过程的关键在于管理遍历的方向和处理边界情况——例如,当扫描达到矩阵的上边界或右边界时,需要正确改变方向

void zScan(vector<vector<int>>& M, vector<int>& m) {
    int size = 8; // 矩阵大小为8x8
    vector<int> result;
    int i = 0, j = 0, mi = 0;
    bool up = true; // 用于标识当前移动方向,初始为向上

    for (int k = 0; k < size * size; ++k) {
        if (mi < m.size())
        {
            M[i][j] = m[mi];
            mi++;
        }
        else M[i][j] = 0;

        if (up) { // 如果当前方向是向上
            if (j == size - 1) { // 如果触及右边界
                ++i; // 向下移动
                up = false; // 改变方向
            }
            else if (i == 0) { // 如果触及上边界
                ++j; // 向右移动
                up = false; // 改变方向
            }
            else { // 否则,继续向上和向右移动
                --i;
                ++j;
            }
        }
        else { // 如果当前方向是向下
            if (i == size - 1) { // 如果触及底边界
                ++j; // 向右移动
                up = true; // 改变方向
            }
            else if (j == 0) { // 如果触及左边界
                ++i; // 向下移动
                up = true; // 改变方向
            }
            else { // 否则,继续向下和向左移动
                ++i;
                --j;
            }
        }
    }
}

2.方法二:固定大小矩阵下标驱动的Z字形扫描

另一种实现Z字形扫描的方法是使用一个预定义的索引矩阵(如你所示的 idx 矩阵)。这个矩阵直接定义了从Z字形位置到矩阵线性位置的映射。在这种方法中,你不需要通过逐步遍历来确定每个元素的Z字形顺序,而是直接使用索引矩阵来查找每个元素在Z字形顺序中的位置。

int idx[8][8] = {
        {0, 1, 5, 6, 14, 15, 27, 28},
        {2, 4, 7, 13, 16, 26, 29, 42},
        {3, 8, 12, 17, 25, 30, 41, 43},
        {9, 11, 18, 24, 31, 40, 44, 53},
        {10, 19, 23, 32, 39, 45, 52, 54},
        {20, 22, 33, 38, 46, 51, 55, 60},
        {21, 34, 37, 47, 50, 56, 59, 61},
        {35, 36, 48, 49, 57, 58, 62, 63}
    };
  
void zScanByIndex(vector<vector<int>>& M, vector<int>& m) {
    for (int i = 0; i < 8; i++) { // 填充M矩阵,使用idx矩阵中的值作为m向量的索引
        for (int j = 0; j < 8; j++) {
            if (idx[i][j] < m.size()) { // 检查idx[i][j]是否在m向量的大小范围内
                M[i][j] = m[idx[i][j]];
            } else {
                M[i][j] = 0; // 如果索引超出了m的大小,就将M的当前元素设置为0
            }
        }
    }
}

解题思路

这段代码回答了JPEG压缩的关键步骤:Z字形扫描、量化和离散余弦变换。这个过程有助于理解JPEG压缩如何减少图像数据的大小,同时保留了相对较高的图像质量。代码中每一步都对应JPEG压缩的一部分。

1.初始化和数据输入

代码开始时,创建了几个二维数组来存储量化矩阵 Q、初始块 M、变换后的块 M1,以及其他变量 nT

  • vector<vector<int>>Q(8, vector<int>(8)): 8x8的量化矩阵。
  • vector<vector<int>>M(8, vector<int>(8)): 存储初始的8x8块,或是在Z字扫描后存储系数。
  • vector<vector<double>>M1(8, vector<double>(8)): 存储离散余弦变换(DCT)后的数据。

main 函数中读取这些值,其中 Q 是量化矩阵,n 是Z字形扫描输入系数的数量,T 是用户选择的压缩步骤标识。

2.Z字形扫描

zScan 函数将一维数组 m (这是按Z字形顺序排列的系数)填入8x8块 M 中。这模仿了JPEG编码过程中将量化后的DCT系数按Z字形排列的过程,这一步骤有助于后续的编码效率,因为它通常会将许多零值放置在块的末尾。

3.量化

multiply 函数通过将8x8的DCT系数块 M 与量化矩阵 Q 逐元素相乘,来实现量化过程。在实际JPEG压缩中,这会降低图像的质量,但同时大大减少所需的数据量。

4.离散余弦变换(DCT)

discreteCos 函数计算了DCT的逆过程,将量化后的系数转换回像素值。这里使用的是二维DCT公式,通常用于JPEG的解码过程。函数计算了8x8块中每个位置的值,根据DCT的公式,使用cosine函数根据频率变化的系数。

5.最终阶段和显示结果

main函数的最后部分,根据用户选择的处理阶段(通过变量 T 控制),程序可以仅执行Z字形扫描、执行扫描和量化或执行全部步骤(包括DCT)。最终,display函数用于输出处理后的8x8块,显示压缩过程中的中间或最终结果。

完整代码

#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
using namespace std;

vector<vector<int>>Q(8, vector<int>(8));
vector<vector<int>>M(8, vector<int>(8));
vector<vector<double>>M1(8, vector<double>(8));
int n, T;

// Z字扫描如果大小是固定的,可以直接存下标矩阵
void zScan(vector<vector<int>>& M, vector<int>& m) {
    int size = 8; // 矩阵大小为8x8
    vector<int> result;
    int i = 0, j = 0, mi = 0;
    bool up = true; // 用于标识当前移动方向,初始为向上

    for (int k = 0; k < size * size; ++k) {
        if (mi < m.size())
        {
            M[i][j] = m[mi];
            mi++;
        }
        else M[i][j] = 0;

        if (up) { // 如果当前方向是向上
            if (j == size - 1) { // 如果触及右边界
                ++i; // 向下移动
                up = false; // 改变方向
            }
            else if (i == 0) { // 如果触及上边界
                ++j; // 向右移动
                up = false; // 改变方向
            }
            else { // 否则,继续向上和向右移动
                --i;
                ++j;
            }
        }
        else { // 如果当前方向是向下
            if (i == size - 1) { // 如果触及底边界
                ++j; // 向右移动
                up = true; // 改变方向
            }
            else if (j == 0) { // 如果触及左边界
                ++i; // 向下移动
                up = true; // 改变方向
            }
            else { // 否则,继续向下和向左移动
                ++i;
                --j;
            }
        }
    }
}

void multiply(vector<vector<int>>& M, vector<vector<int>>& Q) {
    for (size_t i = 0; i < 8; i++)
    {
        for (size_t j = 0; j < 8; j++)
        {
            M[i][j] *= Q[i][j];
        }
    }
}

double pai = acos(-1);

double alpha(int u) {
    if (u == 0)return sqrt(0.5);
    return 1;
}

double discreteCos(vector<vector<int>>& M, int i, int j) {
    double MM = 0;
    for (size_t u = 0; u < 8.0; u++) {
        for (size_t v = 0; v < 8.0; v++) {
            MM += alpha(u) * alpha(v) * M[u][v] * cos((pai / 8.0) * (i + 0.5) * u) * cos((pai / 8.0) * (j + 0.5) * v);
        }
    }
    MM = MM / 4;
    return MM; 
}

void display(vector<vector<int>>& M) {
    for (auto& it : M) {
        for (auto& jt : it) {
            cout << jt << " ";
        }
        cout << endl;
    }
}

int main() {
    for (auto& it : Q) {
        for (auto& jt : it) {
            cin >> jt;
        }
    }
    cin >> n >> T;
    vector<int>m(n);
    for (size_t i = 0; i < n; i++)
    {
        cin >> m[i];
    }

    zScan(M, m);
    if (T == 0)
    {
        display(M);
        return 0;
    }

    multiply(M, Q);
    if (T == 1)
    {
        display(M);
        return 0;
    }

    for (size_t i = 0; i < M.size(); i++)
    {
        for (size_t j = 0; j < M[i].size(); j++)
        {
            M1[i][j] = discreteCos(M, i, j);
        }
    }

    for (size_t i = 0; i < M.size(); i++)
    {
        for (size_t j = 0; j < M[i].size(); j++)
        {
            M[i][j] = round(M1[i][j] + 128);
            if (M[i][j] > 255) M[i][j] = 255;
            if (M[i][j] < 0) M[i][j] = 0;
        }
    }

    display(M);
    return 0;
}

请添加图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值