JPEG——分割,DCT,量化流程

JPEG——分割,DCT,量化流程

一. 前言#

近期在Coursera上了一门图像处理的课程 ”Image and Video Processing: From Mars to Hollywood with a Stop at the Hospital“
目前在学习第二章图像压缩的相关内容,其中比较详细的讲到了JPEG标准的处理流程,于是自己就尝试实现用matlab了一下。

二. 流程#

jpeg流程图

(一)灰度图像压缩#

要对一个仅有灰度通道的图片进行JPEG标准压缩,首先需要先进行预处理:
将图片以8*8的形式进行分割,若原图片尺寸不为8的倍数,则进行补全。接下来对图片的处理,都是以8*8的小图片为单位进行处理的。

接下来是3大步:

1. 对8*8进行DCT变换(离散余弦变换)#

DCT变换目的是将图像从空间域变换到频域的方法。在课程当中还提及,使用DCT变换实际上是一种退让。最理想的变换方式是KLT变换,然而这种变换矩阵不是一个常数,需要根据图片本身进行计算,代价昂贵。因此在JPEG标准中使用了DCT变换作为一种近似。

在进行DCT变换后,其左上角至右下角的元素所包含的信息量是逐步递减的。也就是说,如果在DCT变换后,只使用左上角的部分元素,剩下元素填补为0;在进行解压缩的时候,也可以得到某种令人满意的结果。(在接下来的实验当中,将展现这一点)

2. 量化#

量化的目的是为变长编码进行服务的。如哈夫曼编码(变长编码的一种),只有当某些数字以某种较高的频率出现的时候,才能达到压缩的目的。当每个数字出现的频率都相等的时候,是使用变长编码进行压缩效果最差的时候。

量化可以分为均匀量化非均匀量化两种:

  • 均匀量化:floor ( data / n) * n ,这种量化方法使每n个数字都变为同一个数字。比如说:当n取2的时候
    一串数字:1,2,3,4,5,6,7
    量化为:0,2,2,4,4,6,6
  • 非均匀量化:将量化前的值以x表示,量化后的值以y表示,那么x和y的关系就是不均匀的分段函数。比如当x在[0,3)区间时,y为0,当x在[3,9)区间时,y为3.

JPEG标准中使用的是均匀量化,但是,对于不同位置的值,使用不同的量化系数n。
对于灰度图像使用矩阵:

Qy = [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];

对于色彩通道使用矩阵:

Qc = [17,18,24,47,99,99,99,99;
        18,21,26,66,99,99,99,99;
        24,26,56,99,99,99,99,99;
        47,66,99,99,99,99,99,99;
        99,99,99,99,99,99,99,99;
        99,99,99,99,99,99,99,99;
        99,99,99,99,99,99,99,99;
        99,99,99,99,99,99,99,99;];

4. 变长编码#

变长编码以哈夫曼为例,某个数值存储时用到的长度,是与其在图片中出现的概率有关的。出现概率大的,其存储所用空间就小。这样整个图片存储所用的空间大小,就会小于等长编码的大小。

(二)色彩图像压缩#

JPEG在处理色彩图像时,使用的不是RGB通道,而是YCbCr通道。(两者之间的转换有一个特征矩阵)
对于色彩图像处理,就相当于把3个通道分开都分别当作3个灰度图片进行压缩。唯一的区别就在于,对于Y和两个色彩通道进行量化时,其量化矩阵不同。

(三)图像解压缩#

图像解压缩与图像压缩的步骤是正好相反的,解码-量化-逆DCT变换。
最后还要记得把图片之前为了补齐8的倍数的像素舍弃掉。

三. 代码#

首先定义变量矩阵

global Qy;
Qy = [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];
global Qc;
Qc = [17,18,24,47,99,99,99,99;
        18,21,26,66,99,99,99,99;
        24,26,56,99,99,99,99,99;
        47,66,99,99,99,99,99,99;
        99,99,99,99,99,99,99,99;
        99,99,99,99,99,99,99,99;
        99,99,99,99,99,99,99,99;
        99,99,99,99,99,99,99,99;];
global Qn;
Qn = ones(8,8);
Qn = Qn*16;

% Qy = Qn;
% Qc = ones(8,8)*64;


global mask1;
global mask2;
global mask3;
mask1=[1 1 1 1 1 0 0 0
        1 1 1 1 0 0 0 0
        1 1 1 0 0 0 0 0
        1 1 0 0 0 0 0 0
        1 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];
mask2=[1 1 1 1 0 0 0 0
        1 1 1 0 0 0 0 0
        1 1 0 0 0 0 0 0
        1 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];
mask3=[1 1 0 0 0 0 0 0
        1 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 0 0 0];

接下来是运行的主函数部分,rgb_jpg函数即对色彩图像进行压缩算法,其中用到的转换函数选项为“dct”,“fft”,“none”,分别表示进行DCT变换,FFT(快速傅立叶变换),以及不进行变换直接进行量化。
error为不同方法下的MSE误差

img = imread("15.jpeg");
[r,c] = size(img);
rgb_dct = rgb_jpg(img,"dct");
rgb_fft = rgb_jpg(img,"fft");
rgb_none = rgb_jpg(img,"none");
error_dct = (1/(r*c))*sum(sum(sum((img-rgb_dct).^2)));
error_fft = (1/(r*c))*sum(sum(sum((img-rgb_fft).^2)));
error_none = (1/(r*c))*sum(sum(sum((img-rgb_none).^2)));
subplot(331)
imshow(img);
subplot(332)
imshow(rgb_dct);
subplot(333)
imshow(img-rgb_dct);
subplot(334)
imshow(img);
subplot(335)
imshow(rgb_fft);
subplot(336)
imshow(img-rgb_fft);
subplot(337)
imshow(img);
subplot(338)
imshow(rgb_none);
subplot(339)
imshow(img-rgb_none);

disp(error_dct);
disp(error_fft);
disp(error_none);

接下来是压缩函数:

function rgb = rgb_jpg(img,function_name)

    YCbCr = rgb2ycbcr(img);
    Y = YCbCr(:,:,1);
    Cb = YCbCr(:,:,2);
    Cr = YCbCr(:,:,3);
    [img_com,r_flag,c_flag] = jpeg_com_sum(Y,0,function_name);
    Y_j = jpeg_decom_sum(img_com,r_flag,c_flag,0,function_name);
    [img_com,r_flag,c_flag] = jpeg_com_sum(Cb,1,function_name);
    Cb_j = jpeg_decom_sum(img_com,r_flag,c_flag,1,function_name);
    [img_com,r_flag,c_flag] = jpeg_com_sum(Cr,1,function_name);
    Cr_j = jpeg_decom_sum(img_com,r_flag,c_flag,1,function_name);
    YCbCr_j = cat(3,Y_j,Cb_j,Cr_j);
    rgb = ycbcr2rgb(YCbCr_j);

end


function [img_com,r_flag,c_flag] = jpeg_com_sum(img,flag,function_name)
    n = 8;
    [row,col] = size(img);
    % divide to 8*8
    % 1 fill
    r = ceil(row/n)*n;
    c = ceil(col/n)*n;
    r_flag = r-row;
    c_flag = c-col;
    img_fill = zeros(r,c);
    img_fill(1:row,1:col) = img;
    % 2 divide and process(dct & quantize)
    img_com = zeros(r,c);
    for i = 1:8:r-7
        for j = 1:8:c-7
            img_com(i:i+7,j:j+7) = compress_small_img(img_fill(i:i+7,j:j+7),flag,function_name);
        end
    end
    % variable-length coding
    
end

function img = compress_small_img(img,flag,function_name)
    % transform
    if(function_name == "dct")
        img = dct2(img);
    elseif(function_name == "fft")
        img = fft2(img);
    end
    
    % preserve left-top corner
    global mask1;
    img = img.* mask1;
    
    % quantize
    if(flag == 0)
        global Qy;
        Q = Qy;
    else
        global Qc;
        Q = Qc;
    end
    img = floor(img./Q);
    
end

解压缩函数:

% jpeg 总解压缩
function img_decom = jpeg_decom_sum(img,r_flag,c_flag,flag,function_name)
    n = 8;
    [row,col] = size(img);
    img_decom = zeros(row,col);
    % 1 divide and process(dequantize & dct)
    for i = 1:8:row-7
        for j = 1:8:col-7
            img_decom(i:i+7,j:j+7) = decompress_small_img(img(i:i+7,j:j+7),flag,function_name);
        end
    end
    % 2 cut
    img_decom = img_decom(1:row-r_flag,1:col-c_flag);
    img_decom = uint8(img_decom);
end
% quantize,dct 解压缩
function img = decompress_small_img(img,flag,function_name)
    % dequantize
    if(flag == 0)
        global Qy;
        Q = Qy;
    else
        global Qc;
        Q = Qc;
    end
    img = img .* Q;
    % transform
    if(function_name == "dct")
        img = idct2(img);
    elseif(function_name == "fft")
        img = real(ifft2(img));
    end
end

四. 效果#

左侧是原图,中间是压缩后再解压缩的,右侧是差别图
第一行是DCT,第二行是FFT,第三行是None
MSE分别为:69.9985;41.8939;185.8945
此时用的量化矩阵为JPEG的标准量化矩阵;8*8进行变化后所取的像素为:

mask1=[1 1 1 1 1 0 0 0
        1 1 1 1 0 0 0 0
        1 1 1 0 0 0 0 0
        1 1 0 0 0 0 0 0
        1 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];

在这里插入图片描述
更换所取元素为mask2:
MSE分别为:58.0195,41.8039,188.7869

mask2=[1 1 1 1 0 0 0 0
        1 1 1 0 0 0 0 0
        1 1 0 0 0 0 0 0
        1 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];

在这里插入图片描述
更换所取元素为mask3:
MSE分别为:55.5431,46.4918,194.1467

可以看见,当所取元素越少时,DCT的误差是在稳步减小,而FFT和None两种,误差都是稳步增大的。
此外,即使在FFT的误差比DCT的误差小的时候,图片呈现出来的效果也是有一种“重影”的感觉。
这就解释了,为什么选择了DCT进行变换,而不是FFT。(从函数上看,DCT是连续周期函数,而FFT是分段周期函数。从现实角度来说,临近像素肯定具有相似的特征,而DCT的“连续”更复合这一点)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值