基于离散余弦变换实现 JPEG 压缩

680 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第27天,点击查看活动详情

前言

离散余弦变换一节中,我们已经学习了如何实现 2D-DCTIDCT,在本节中,我们将使用这两种技术执行图像压缩操作,完成 JPEG 格式图像压缩。JPEG 压缩基本操作步骤如下所示:

  • 将图像分割为 8x8 的图像块
  • 在每个图像块上(从左到右,从上到下)应用 DCT
  • 对每个图像块使用量化压缩
  • 将压缩后的图像块存储在较小的空间中,从而达到较高的压缩效果

下图显示了在 8x8 图像块的 64DCT 基函数,对于图像中的每个块,64DCT 系数是基函数的线性组合以生成图像块系数:

image.png

接下来,我们将实现简化版 JPEG 压缩,以确保仅需少数 DCT 系数可以表示图像,而不会导致图像质量有过多的视觉失真。在本节中,我们将直接在输入图像像素上计算 8x8 图像块的 DCT

此外,我们将使用全局阈值来清零大部分 DCT 系数,而不是通过用量化矩阵(对于给定的压缩级别)分割 DCT 矩阵来进行量化。

最后,我们将跳过霍夫曼 (Huffman) 编码,并存储(压缩)/读取(解压缩)图像。

JPEG 压缩

(1) 导入所需库,读取输入图像,并将图像分割为 8x8DCT 块:

import scipy.fftpack as fp
from skimage.io import imread
from skimage.color import rgb2gray, gray2rgb
from skimage.draw import rectangle_perimeter
import numpy as np
import matplotlib.pylab as plt
from mpl_toolkits.mplot3d import Axes3D  # noqa: F401 unused import
from mpl_toolkits.axes_grid1 import make_axes_locatable
from matplotlib.ticker import LinearLocator, FormatStrFormatter
from scipy.fftpack import dct, idct

def dct2(a):
    return dct(dct(a, axis=0, norm='ortho'), axis=1, norm='ortho')

def idct2(a):
    return idct(idct(a, axis=0, norm='ortho'), axis=1, norm='ortho')

im = rgb2gray(imread('1.png'))

dct_coeffs = np.zeros(im.shape)

for i in range(0, im.shape[0], 8):
    for j in range(0, im.shape[1], 8):
        dct_coeffs[i:(i+8),j:(j+8)] = dct2(im[i:(i+8),j:(j+8)])

(2) 从图像中提取一个图像块,并显示该块的 DCT 系数。

index = 112
plt.figure(figsize=(10,6))
plt.gray()
plt.subplot(121), plt.imshow(im[index:index+8,index:index+8]), plt.title( "An 8x8 Image block", size=10)
plt.subplot(122), plt.imshow(dct_coeffs[index:index+8,index:index+8], vmax= np.max(dct_coeffs)*0.01, vmin = 0, extent=[0, np.pi, np.pi, 0])
plt.title("An 8x8 DCT block", size=10)
plt.show()

输出结果如下所示:

Figure_5.png

(3) 使用全局阈值对 DCT 系数进行阈值设置,将值小于阈值乘以最大系数值的所有 DCT 系数置为 0,打印应用阈值后剩余的非零 DCT系数的百分比。

thresh = 0.03
dct_thresh = dct_coeffs * (abs(dct_coeffs) > (thresh*np.max(dct_coeffs)))
percent_nonzeros = np.sum( dct_thresh != 0.0 ) / (im.shape[0]*im.shape[1])
print ("Keeping only {}% of the DCT coefficients".format(percent_nonzeros*100.0))
# Keeping only 3.45875% of the DCT coefficients

从以上输出可以看出,应用阈值后,只有约 3.5%DCT 系数为非零值。

(4) 可视化原始 DCT 系数以及阈值处理后获得的系数:

plt.figure(figsize=(12,7))
plt.gray()
plt.subplot(121), plt.imshow(dct_coeffs,cmap='gray',vmax = np.max(dct_coeffs)*0.01,vmin = 0), plt.axis('off')
plt.title("8x8 DCTs of the image", size=10)
plt.subplot(122), plt.imshow(dct_thresh, vmax = np.max(dct_coeffs)*0.01, vmin = 0), plt.axis('off')
plt.title("Thresholded 8x8 DCTs of the image", size=10)
plt.tight_layout()
plt.show()

Figure_6.png

(5) 最后,仅使用 3.5% 非零 DCT 系数,使用 8x8IDCT 重建压缩图像:

im_out = np.zeros(im.shape)
for i in range(0, im.shape[0], 8):
    for j in range(0, im.shape[1], 8):
        im_out[i:(i+8),j:(j+8)] = idct2( dct_thresh[i:(i+8),j:(j+8)])

(6) 绘制原始图像和 DCT 压缩图像,并对比原始图像和重建(压缩)图像:

plt.figure(figsize=(15,7))
plt.gray()
plt.subplot(121), plt.imshow(im), plt.axis('off'), plt.title('original image', size=10)
plt.subplot(122), plt.imshow(im_out), plt.axis('off'), plt.title('DCT compressed image', size=10)
plt.tight_layout()
plt.show()

Figure_7.png

从以上图像可以看到,仅通过有效地存储大约 3.5%DCT 系数,我们便可以高图像质量的重建图像,而不会过多的降低图像质量上,从而实现较高压缩比,并计算 MSE/PSNR 以进行定量测量图像间的差距。