JPEG编解码(python实现)

多媒体通信技术大作业(2021.04.08)

题目:选一张图片,然后用JPEG编码方法对其进行编码(语言不限)。

JPEG编解码基本流程:

JPEG编解码(python实现)_第1张图片

其中编码:

在这里插入图片描述
解码:

JPEG编解码(python实现)_第2张图片

JPEG概念

JPEG是Joint Photographic Exports Group的英文缩写,中文称之为联合图像专家小组。该小组隶属于ISO国际标准化组织,主要负责定制静态数字图像的编码方法,即所谓的JPEG算法。JPEG专家组开发了两种基本的压缩算法、两种熵编码方法、四种编码模式。如下所示:

压缩算法:

  • 有损的离散余弦变换DCT
  • 无损的预测压缩技术

熵编码方法:

  • Huffman编码
  • 算术编码

编码模式:

  • 基于DCT的顺序模式:编码、解码通过一次扫描完成
  • 基于DCT的渐进模式:编码、解码需要多次扫描完成,扫描效果由粗到精,逐级递增
  • 无损模式:基于DPCM,保证解码后完全精确恢复到原图像采样值
  • 层次模式:图像在多个空间分辨率中进行编码,可以根据需要只对低分辨率数据做解码,放弃高分辨率信息

在实际应用中,JPEG图像编码算法使用的大多是离散余弦变换、Huffman编码、顺序编码模式。这样的方式,被人们称为JPEG的基本系统。基本系统的JPEG压缩编码算法一共分为11个步骤:颜色模式转换、采样、分块、离散余弦变换(DCT)、Zigzag 扫描排序、量化、DC系数的差分脉冲调制编码、DC系数的中间格式计算、AC系数的游程长度编码、AC系数的中间格式计算、熵编码。

下面通过使用python对一幅图像进行JPEG编码。

  1. 图像分割
    JPEG算法的第一步,图像被分割成大小为8X8的小块,这些小块在整个压缩过程中都是单独被处理的。

JPEG编解码(python实现)_第3张图片

  1. 颜色空间转换RGB->YCbCr
    所谓“颜色空间”,是指表达颜色的数学模型,比如我们常见的“RGB”模型,就是把颜色分解成红绿蓝三种分量,这样一张图片就可以分解成三张灰度图,数学表达上,每一个8X8的图案,可以表达成三个8X8的矩阵,其中的数值的范围一般在[0,255]之间。不同的颜色模型各有不同的应用场景,在JPEG压缩算法中,需要把图案转换成YCbCr模型,其中Y表示亮度(Luminance),Cb和Cr分别表示绿色和红色的色差值。最终可以得到RGB转换为YCbCr的数学公式:
    在这里插入图片描述
    有损压缩首先要做的事情就是“把重要的信息和不重要的信息分开”,YCbCr恰好能做到这一点。对于人眼来说,图像中明暗的变化更容易被感知到,这是由于人眼的构造引起的。视网膜上有两种感光细胞,能够感知亮度变化的视杆细胞,以及能够感知颜色的视锥细胞,由于视杆细胞在数量上远大于视锥细胞,所以我们更容易感知到明暗细节。如下图所示:
    JPEG编解码(python实现)_第4张图片
    JPEG编解码(python实现)_第5张图片
    可以明显看到,亮度图的细节更加丰富。JPEG把图像转换为YCbCr之后,就可以针对数据的重要成都做不同的处理。这就是为什么JPEG使用这种颜色空间的原因。
    代码如下所示:
  def __Rgb2Yuv(self, r, g, b):
        # 从图像获取YUV矩阵
        y = 0.299 * r + 0.587 * g + 0.114 * b
        u = -0.1687 * r - 0.3313 * g + 0.5 * b + 128
        v = 0.5 * r - 0.419 * g - 0.081 * b + 128
        return y, u, v
  1. 离散余弦变换(DCT)
    JPEG算法中的核心内容为离散余弦变换,即DCT。离散余弦变换属于傅里叶变换的另外一种形式,当我们要处理的不再是函数,而是一堆离散的数据时,并且这些数据是对称的话,那么傅里叶变化出来的函数只含有余弦项,这种变换称为离散余弦变换。
    经过DCT,数据中隐藏的规律被发掘了出来,杂乱的数据被转换成几个工整变化的数据。DCT转换后的数组中第一个是一个直线数据,因此又被称为“直流数据”,简称DC,后面的数据被称为“交流数据”,简称AC。
    在JPEG压缩过程中,经过颜色空间的转换,每一个8X8的图像块,在数据上表现为3个8X8的矩阵,紧接着我们对这三个矩阵做一个二维的DCT转换,二维的DCT转换公式为:
    JPEG编解码(python实现)_第6张图片
    在实际的JPEG压缩过程中,由于图像本身的连贯性,一个8X8的图像中的数值一般不会出现大的跳跃,经过DCT转换后,左上角的直流分量保存了一个大的数值,其他分量都接近于0。
    代码如下所示:
def __init__(self):
        # 初始化DCT变换的A矩阵
        self.__dctA = np.zeros(shape=(8, 8))
        for i in range(8):
            c = 0
            if i == 0:
                c = np.sqrt(1 / 8)
            else:
                c = np.sqrt(2 / 8)
            for j in range(8):
                self.__dctA[i, j] = c * np.cos(np.pi * i * (2 * j + 1) / (2 * 8))

def __Dct(self, block):
        # DCT变换
        res = np.dot(self.__dctA, block)
        res = np.dot(res, np.transpose(self.__dctA))
        return res

  1. 数据量化
    经过颜色空间转换和离散余弦变换,每一个8X8的图像块都变成了三个8X8的浮点数矩阵,分别表示Y,Cr,Cb数据。数据量化在可以损失一部分精度的情况下,用更少的空间存储这些浮点数。JPEG提供的量子化算法如下:
    在这里插入图片描述其中G是需要处理的图像矩阵,Q称作量化系数矩阵(Quantization matrices),JPEG算法提供了两张标准的量化系数矩阵,分别用于处理亮度数据Y和色差数据Cr以及Cb。
    JPEG编解码(python实现)_第7张图片
    经过量化以后,一大部分数据变成了0,这非常有利于后面的压缩存储。
    代码如下所示:
 def __Quantize(self, block, tag):
        res = block
        if tag == self.__lt:
            res = np.round(res / self.__lq)
        elif tag == self.__ct:
            res = np.round(res / self.__cq)
        return res
  1. 编码DCT量化系数
    其中DC系数采用预测编码,采用以前块的DC值预测当前块的DC值,再采用Huffman编码编码预测误差。AC系数采用游程编码,在少数低频系数后存在大量的高频0系数。代码如下所示:
def __Zig(self, blocks):
        ty = np.array(blocks)
        tz = np.zeros(ty.shape)
        for i in range(len(self.__zig)):
            tz[:, i] = ty[:, self.__zig[i]]
        tz = tz.reshape(tz.shape[0] * tz.shape[1])
        return tz.tolist()

原图如下所示:
JPEG编解码(python实现)_第8张图片
经过JPEG压缩后再解压缩处理后的图像如下所示:

JPEG编解码(python实现)_第9张图片
对比可得,处理前的图像大小为168KB,经过JPEG压缩后图片的大小为21KB。压缩比大致为8:1。

在这里插入图片描述
参考资料:

  1. JPEG原理详解
  2. JPEG图片压缩的python实现

你可能感兴趣的:(多媒体通信技术,算法)