基于离散余弦变换的图片压缩算法 C语言实现DCT

该文章介绍了使用离散余弦变换(DCT)实现图像压缩的原理和过程,包括DCT编码、量化、熵编码等步骤。作者分享了使用C/C++编写的代码,通过8x8块进行DCT变换、量化和反量化,最后进行逆DCT变换来压缩和解压缩图像。文中提到,对于彩色图像,仅计算亮度(Y)值导致压缩后变为黑白。解决方案是为每个通道(RGB)分别处理和存储。
摘要由CSDN通过智能技术生成

       本次的课设呢是写一个基于离散余弦变换的图片压缩算法,要求用C/C++实现,总是面向CSDN编程的我呢找了一圈没找到,甚至Github上面也没找到什么自己满意的代码,好吧那就把我这次完成的课设分享出来,为我后面的学弟学妹谋福利。

先简单说一下原理吧

可以直接观看B站的视频介绍,我觉得讲的挺好的【中英双字】JPEG算法原理 jpeg图片是如何压缩的?_哔哩哔哩_bilibili

      DCT编码属于正交变换编码。这类算法通常是将空间域上的图像经过正交变换映射到系数空间,使变换后的系数直接相关性降低。图像变换本身并不能压缩数据,但变换后图像大部分能量集中到了少数几个变换系数上,再采用适当的量化和熵编码使可以有效地压缩图像。信息论的研究表明,正交变换不改变信源的熵值,变换前后图像的信息量并无损失,完全可以通过反变换得到原来的图像值。但图像经过正交变换后,把原来分散在原空间的图像数据在新的坐标空间中得到集中,对于大多数图像而言,大量的变换系数很小,只要删除接近于0的系数,并对较小的系数进行粗量化,而保留包含图像主要信息的系数,以此进行压缩编码。在重建图像进行解码(逆变换)时,所损失的将是些不重要的信息,几乎不会引起图像失真,图像的变换编码就是利用这些来压缩图像并得到很高的压缩比。

用DCT压缩图像的过程为

        (1)首先将输入图像分解为8×8或16×16的块,然后对每个子块进行二维DCT变换。

        (2)将变换后得到的量化的DCT系数进行编码和传送,形成压缩后的图像格式。

用DCT解压的过程为:

        (1)对每个8×8或16×16块进行二维DCT反变换。

        (2)将反变换的矩阵的块合成一个单一的图像。

        余弦变换具有把高度相关数据能量集中的趋势,DCT变换后矩阵的能量集中在矩阵的左上角,右下的大多数的DCT系数值非常接近于0。对于通常的图像来说,舍弃这些接近于0的DCT的系数值,并不会对重构图像的画面质量带来显著的下降。所以,利用DCT变换进行图像压缩可以节约大量的存储空间。压缩应该在最合理地近似原图像的情况下使用最少的系数。使用系数的多少也决定了压缩比的大小。

                            

(3) 量化矩阵

    {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}

为了简单,此次课设我只按照如下步骤对图片进行了处理

        流程中的图像预处理呢就是将目的路径的图片读取到程序进行处理,读取图片的高度、宽度和通道数,然后创建一个新的图像数据数组等于原图片的长××通道数,通道数是用来决定处理的图片是黑白图片还是彩色图片,黑白图片的通道数是1,而彩色图片的通道数分为RGB3通道数,不同的通道数进行不同的处理。将图像根据长宽划分为8×8像素的小块,然后对每一个小块进行处理,在处理每一个小块的时候用两个for循环嵌套遍历小块的每一个像素进行处理,这样就可以处理完整个图像。

好了,废话不多说,我们直接上代码。

        程序需要用到两个头文件,分别是stb_image.h和stb_image_write.h用来处理图片,由于这两个头文件代码比较长,就不放在这里了,可自行到Github上面下载,下载地址为stb/stb_image.h at ster · nothings/stb · GitHub

stb/stb_image_write.h at master · nothings/stb · GitHub

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

#define BLOCK_SIZE 8
#define PI 3.14159265358979323846

// 量化矩阵
int quantizationMatrix[BLOCK_SIZE][BLOCK_SIZE] = {
    {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}
};

// 对8x8的块进行离散余弦变换
void performDCT(float block[BLOCK_SIZE][BLOCK_SIZE])
{
    float coefficient;
    float sum;
    float alpha_u, alpha_v;
    float dctBlock[BLOCK_SIZE][BLOCK_SIZE];

    // 计算离散余弦变换系数
    for (int u = 0; u < BLOCK_SIZE; u++) {
        for (int v = 0; v < BLOCK_SIZE; v++) {
            if (u == 0)
                alpha_u = sqrt(1.0 / BLOCK_SIZE);
            else
                alpha_u = sqrt(2.0 / BLOCK_SIZE);

            if (v == 0)
                alpha_v = sqrt(1.0 / BLOCK_SIZE);
            else
                alpha_v = sqrt(2.0 / BLOCK_SIZE);

            coefficient = (alpha_u * alpha_v) / 4.0;

            dctBlock[u][v] = 0.0;

            // 执行离散余弦变换
            for (int x = 0; x < BLOCK_SIZE; x++) {
                for (int y = 0; y < BLOCK_SIZE; y++) {
                    dctBlock[u][v] += block[x][y] * cos(((2 * x + 1) * u * PI) / (2.0 * BLOCK_SIZE)) *
                                     cos(((2 * y + 1) * v * PI) / (2.0 * BLOCK_SIZE));
                }
            }

            dctBlock[u][v] *= coefficient;
        }
    }

    // 将离散余弦变换结果复制回原始块
    for (int i = 0; i < BLOCK_SIZE; i++) {
        for (int j = 0; j < BLOCK_SIZE; j++) {
            block[i][j] = dctBlock[i][j];
        }
    }
}

// 将一个8x8的DCT系数块进行逆DCT变换,得到原始的像素块 
void performIDCT(float block[BLOCK_SIZE][BLOCK_SIZE])
{
    float result[BLOCK_SIZE][BLOCK_SIZE];

    for (int u = 0; u < BLOCK_SIZE; u++) {
        for (int v = 0; v < BLOCK_SIZE; v++) {
            float sum = 0.0;
            float alpha_u, alpha_v;

            for (int x = 0; x < BLOCK_SIZE; x++) {
                for (int y = 0; y < BLOCK_SIZE; y++) {
                    if (x == 0)
                        alpha_u = sqrt(1.0 / BLOCK_SIZE);
                    else
                        alpha_u = sqrt(2.0 / BLOCK_SIZE);

                    if (y == 0)
                        alpha_v = sqrt(1.0 / BLOCK_SIZE);
                    else
                        alpha_v = sqrt(2.0 / BLOCK_SIZE);

                    float coefficient = (alpha_u * alpha_v) / 4.0;

                    sum += coefficient * block[x][y] * cos(((2 * u + 1) * x * PI) / (2.0 * BLOCK_SIZE)) *
                           cos(((2 * v + 1) * y * PI) / (2.0 * BLOCK_SIZE));
                }
            }

            result[u][v] = sum;
        }
    }

    // 将逆DCT变换结果复制回原始块
    for (int i = 0; i < BLOCK_SIZE; i++) {
        for (int j = 0; j < BLOCK_SIZE; j++) {
            // 提高亮度,可以乘以一个增益因子
            result[i][j] *= 12; // 可以根据需要进行调整

            block[i][j] = result[i][j];
        }
    }
}

// 对块进行量化,将DCT系数除以量化矩阵中对应位置的值,并四舍五入到最近的整数 
void performQuantization(float block[BLOCK_SIZE][BLOCK_SIZE])
{
    for (int i = 0; i < BLOCK_SIZE; i++) {
        for (int j = 0; j < BLOCK_SIZE; j++) {
            block[i][j] = round(block[i][j] / quantizationMatrix[i][j]);
        }
    }
}

// 对块进行反量化,反量化操作将量化后的系数乘以量化矩阵中对应位置的值 
void performDequantization(float block[BLOCK_SIZE][BLOCK_SIZE])
{
    for (int i = 0; i < BLOCK_SIZE; i++) {
        for (int j = 0; j < BLOCK_SIZE; j++) {
            block[i][j] = block[i][j] * quantizationMatrix[i][j];
        }
    }
}

int main()
{
    // 读取图像数据
    int width, height, channels;
    unsigned char* image = stbi_load("input.jpg", &width, &height, &channels, 0);

    if (!image) {
        printf("图片加载出错,请检查路径或者命名\n");
        return 1;
    }
	printf("图片正在压缩中,请不要关闭界面...\n");
	
    // 计算图像块的行和列数
    int rows = height / BLOCK_SIZE;
    int cols = width / BLOCK_SIZE;

    // 创建新的图像数据数组
    unsigned char* newImage = (unsigned char*)malloc(width * height * channels * sizeof(unsigned char));

    // 对每个8x8块进行处理
    for (int row = 0; row < rows; row++) {
        for (int col = 0; col < cols; col++) {
            // 获取当前块的起始像素位置
            int startRow = row * BLOCK_SIZE;
            int startCol = col * BLOCK_SIZE;

            // 创建当前块的像素块
            float block[BLOCK_SIZE][BLOCK_SIZE];

            // 对每一个块的每一个像素值转换为浮点数,并将其存储在像素块中
            for (int i = 0; i < BLOCK_SIZE; i++) {
                for (int j = 0; j < BLOCK_SIZE; j++) {
                	//将二维图像坐标转换为一维数组索引再乘以通道数,以考虑每个像素可能包含的颜色通道数 
                    int pixelIndex = ((startRow + i) * width + (startCol + j)) * channels;
					//pixelIndex 就是当前块中每个像素在图像数据数组中的索引位置
					
					//单通道图像(灰度图像),则每个像素的数值直接转换为浮点数并存储在像素块中
                    if (channels == 1) {
                        block[i][j] = (float)image[pixelIndex];
                    } 
					//图像是三通道图像(RGB图像),则将RGB像素值转换为亮度(Y)值
					else if (channels == 3) {
                        //标准的亮度转换公式,即使用红、绿、蓝通道的权重系数加权求和来计算亮度值
                        block[i][j] = 0.299 * (float)image[pixelIndex] + 0.587 * (float)image[pixelIndex + 1] + 0.114 * (float)image[pixelIndex + 2];
                    }
                }
            }

            // 进行DCT变换
            performDCT(block);
			
            // 对块进行量化
            performQuantization(block);

            // 对块进行反量化
            performDequantization(block);

            // 进行逆DCT变换
            performIDCT(block);

            // 将处理后的像素块存储在新图像数据数组中
            for (int i = 0; i < BLOCK_SIZE; i++) {
                for (int j = 0; j < BLOCK_SIZE; j++) {
                    int pixelIndex = ((startRow + i) * width + (startCol + j)) * channels;

                    if (channels == 1) {
                        newImage[pixelIndex] = (unsigned char)block[i][j];
                    } else if (channels == 3) {
                        // 将亮度(Y)值转换回RGB像素值
                        newImage[pixelIndex] = (unsigned char)block[i][j];
                        newImage[pixelIndex + 1] = (unsigned char)block[i][j];
                        newImage[pixelIndex + 2] = (unsigned char)block[i][j];
                    }
                }
            }
        }
    }

    // 保存处理后的图像,100:表示JPEG图像的压缩质量,范围从0到100,其中100表示最高质量的无损压缩。 
    stbi_write_jpg("output.jpg", width, height, channels, newImage, 100);
	printf("图片压缩成功!\n请回目录下查看\n");

    // 释放内存
    stbi_image_free(image);
    free(newImage);
    
    return 0;
}

注意!!!上述代码需要将两个头文件stb_image.h和stb_image_write.h以及要压缩的图片放在同一个文件夹下面,并且图片命名要为“input.jpg”(只要跟程序里面命名一致即可),压缩成功后的图片也在这个文件夹里面查看。

大概像这样

压缩示例

                                                             这是原图input.jpg

                                                      这是压缩后的图片output.jpg

                   

        显然被干成黑白色的了,因为上述代码对通道数为3的彩色图片没有处理好,在处理彩色图片时是根据RGB图像的三个通道值计算亮度(Y)值,它使用了标准的亮度转换公式。亮度转换是将彩色图像转换为灰度图像的一种常见方法,其中每个像素的亮度值被用于表示整个像素的亮度。在处理3通道彩色图片代码中,RGB通道的值按照一定的权重(0.299、0.587、0.114)进行加权求和,得到一个亮度值。因此,即使处理的是彩色图像,它也会将RGB像素值压缩为一个亮度值,从而在某种程度上模拟了灰度图像的效果。

解决办法是创建3个通道分别存储R、G、B颜色,只需要改动主函数中的部分,代码将在下一篇博客中提供。我们还需要完成可视化界面,也在下一篇中。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值