利用opencv与python3 JPEG压缩与解压实现

由于内容是从写好的word文件中复制过来,可能排版等会有各种问题,建议直接看github中的pdf

另外由于我写这份作业的时候还不熟悉py3,因此实际上由很多可以优化的地方,比如数组强烈建议使用numpy而不是此处的列表

github地址:https://github.com/c980129/JPEG

 

 

JPEG压缩实现(Python3)

  1. RGB转YUV

JPEG会将彩色图像执行YUV或YIQ的颜色空间转换,二次采样JPEG采用4:2:0,所以这里使用YUV420的颜色空间。在JPEG中使用的颜色模型是YCbCr(由YUV调整而来)。对于一个2*2的块,我们会保存4个Y值,1个Cb值(取0行0列的Cb)与1个Cr值(取1行0列的Cr),6个值保存信息,因此Cb与Cr有一定损失。

其中我们用函数rgb2yuv(在RGB2YUV.py)中来实现颜色模型转换与二次采样。并分别用三个二维数组保存采取的Y、U、V值。于是我们能得到三张分别用Y、U、V生成的灰度图(由于U和V损失为原来的1/4,因此其图像的长宽也分别为原来的1/2)(为了减少绝对值将Y值减去128):

 

  1. 图像边长填充为8的倍数并等分

用DCT.fill(img)函数对二次采样得到的Y、U、V图像分别用0填充知道其矩阵的height和width都是8的倍数,因为DCT函数的参数是一个8*8的矩阵。同样用DCT.split(img)函数将图像以左到右,上到下的顺序分成多个8*8矩阵,并返回这些矩阵连成的数组。

 

 

  1. 离散余弦变换

用DCT.FDCT(block)函数对一个8*8矩阵进行二维离散余弦变换,保存得到的矩阵。

 

  1. 量化

在类Quantization中保存成员变量table0与table1作为亮度和色度的量化表,调用Quantization.quanY(img)与Quantization.quanUV(img)分别用于对Y图像与U、V图像量化。

 

  1. AC系数

用AC类中的ZScan(img)对一个图像进行Z型扫描,得到一个长度为63的数组(图像第一个像素并不需要,它将在DC系数中保存)。之后通过RLC(array)对上面得到的数组array进行RLC得到他们的游长编码。

 

 

  1. DC系数

用DC类中的DPCM(blocks)函数对所有图像的DC系数提取并返回它们的DPCM编码数组。

 

  1. 熵编码

压缩部分是无损压缩,对DC系数(一个数组)采用可变字长整数编码。将一个DC系数分成size和amplitude两部分,配合VLI(num)和toB(num)函数将一个DC系数转换为一个[size(num), s(string(B))]。

这里的s已经是二进制串了(暂且用字符串存储方便操作),size需要用哈夫曼编码压缩。这里使用JPEG推荐的哈夫曼编码(亮度与色度两个表)。

对于AC系数,我们知道AC系数采用有偿编码,由两个数runlength与value组成。先将value如同DC系数一样采用可变字长整数编码拆分成size与amplitude,然后runlength与size合并为symbol1,smplitude独立为symbol2。对于runlength大于15的数情况,在symbol1添加(15,0)表示(为了解码时能识别,应在前端添加)。然后对symbol1采用哈夫曼编码,对于symbol2则直接用上文可变字长整数编码得到的二进制码。Symbol1采用的哈夫曼编码同样用JPEG推荐的哈夫曼编码表,由于过长不在报告中贴出。最终可用函数AllCompressY与AllcompressUV(未贴出)将一个表格的数据转换为二进制字符串。参数为DC系数(一个)与AC系数(数组)。

 

在Test.py中将对所有函数测试(实际上每个类的python文件的下方注释部分都是对这个类中函数的单元测试,由于不方便在报告中列出因此采用这种方式)。

这是Y图像的第一个8*8矩阵:

这是该矩阵的DCT变换结果(取整):

量化结果(取整):

 

Z字形扫描,DC系数,AC系数(太长未列全),二进制字符串,串长:

可以看到此处二进制长217位,意思是已经将一个8*8*8(512)bit的图像压缩为217bit。

 

对于译码,我们需要事先保存图片的长宽(正如图片位流里会保存一样),以此计算出Y,U,V图像的矩阵数,才能对整个二进制流正确分割(在译码过程中分割)。

在Compress类的encoding函数中,参数是位流(字符串形式)与宽,高(整型)。我们根据宽高得到Y、U、V图像的8*8矩阵的数量(我们知道U和V是一样多的),然后从位流头部开始移动两个指针。我们需要先得到上面使用的四个哈夫曼编码表的反向映射(这里用字典)。我们知道两个指针之间的二进制码的含义必定在几个状态之间转换:读取DC系数的size(通过不断比较两个指针之间的位流是否为字典的key,是的话得到其value(这里指字典的value),即size,不是则移动尾指针);通过size得到新的头尾指针,得到amplitude;然后开始读取AC系数的(runlength,size),如同上面得到DC系数的size一样,获得size后以此得到amplitude;循环读取AC系数直到读取翻译到的(runlength, value)为(0,0)或得到63个AC系数为止,将DC系数与AC系数都加进各自的列表中(这两列表将存储全部Y矩阵的DC系数与AC系数)。U与V同理,最终我们得到Y、U、V的DC系数与AC系数的数据(由于这段代码比较长不贴出,在Compress.py中)

然后通过DC类中的DPCM2(DC)函数,传入各自的DC系数,得到三个其元素是一个矩阵的列表,每个矩阵的[0][0]都通过DC系数还原为编码前

然后对每个矩阵分别通过对应的DC系数还原为63个数(AC.RLE(array))

再Z形填进矩阵中(AC.Z2Tab函数)(这段较长不贴出)

 

 

 

 

 

分别逆量化(Quantization.reY(img)与Quantization.reUV(img))(因为亮度与色度量化表不一样因此要分别操作)

再二维逆离散余弦变换(DCT.IDCT(img)。

这段操作每个函数的对象都是单个8*8矩阵

通过对所有矩阵进行同样的操作我们能得到所有当初刚分割完的8*8矩阵(不算损失的话)。

同样通过对这些矩阵进行合拼并将当初填充的0割掉,得到Y、U、V图像(这段代码较长不贴出,在DCT.py的DCT.merge()中,注意需要图像的长宽作为参数)

经此我们能得到YUV420的原图像。

 

关于测试代码,可以直接运行Test.py,会打印得到对每个函数的测试,或者运行Main.py,将执行对图像从压缩到解压的全部操作。Main.py会打印压缩得到的位流长,并在代码的上一层目录中创建一个txt.txt文件用字节形式保存每个位的数据(主要是用于测试解压代码时可以免去压缩过程迅速开始测试)。

运行Main.py会打印位流的长度,图片的高度与宽度(因此推荐在终端窗口中运行而不是直接运行py文件),并显示解压后的图片

我们直到682*1024个像素,如果直接用RGB的24位保存将要16760832bit

而这里压缩后得到的位流是1388439bit(虽然这里是字符串形式而非位流形式,但为了方便省去了以位流写文件的步骤,我们直接通过字符串的长度来对比)。压缩率是8.3%。

然而压缩的代价也十分明显(如图)。其中黑色变为其他颜色的点主要是YUV420中大量色度损失导致的(直接将图片从RGB转为YUV420再直接转为RGB就会有这些点的损失,因此如此推断),颜色也同样有不少损失,远处的云能看到明显的格子化。

解压后:

原图:

 

③ 结果对比:

       压缩率约为8.3%的JPEG图(忽略文件信息等真正JPEG文件可能会造成的误差)

体积大约为170KB。与原文件接近,或许是我二度采样哪里理解错了或者写错了导致失真严重。

       用PS使压缩的GIF也接近170KB(调整损失),得到的图像明显颜色(只有256)不足导致颜色接近的区块容易模糊融在一起。但整体色彩比起jpeg更接近原图,但是缺少细节。因此像照片这种细节多的图片,在体积相同的情况下还是选择JPEG更合适。或许是我的代码或者算法不够好导致的损失,也可能是我的二度采样是在原JPEG已经压缩过的二度采样的基础上的再次二度采样导致的损失,实际上原图jpg也是170KB体积,但表现效果十分好,比起我的压缩解压以及PS的转换为GIF都是如此。

      

 

 

GIF与JPEG文件保存在根目录下。代码保存在py文件夹中。

发布了31 篇原创文章 · 获赞 13 · 访问量 1万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览