一、图像存储 为了有效的传输和存储图像,需要对图像数据进行压缩。依据图像的保真度,图像压缩可分为无损压缩和有损压缩。1。无损压缩 无损压缩的基本原理是相同的颜色信息只需保存一次。无损压缩保证解压以后的数据和原始数据完全一致,压缩时去掉或减少数据中的冗余,解压时再重新插到数据中,是一个可逆过程。无损压缩算法一般可以把普通文件的数据压缩到原来的1214。2。有损压缩 有损压缩方式在解压后图像像素值会发生改变,解压以后的数据和原始数据不完全一致,是不可逆压缩方式。在保存图像时保留了较多的亮度信息,将冗余信息合并,合并的比例不同,压缩的比例也就不同。由于信息量减少了,所以压缩比可以很高,图像质量也会下降。二、图像格式 常见有损的图像格式有:JPEG、WebP,常见无损的图像格式有:PNG、BMP、GIF。 通常以文件的后缀名来区分图片的格式,但有时并不准确。实际的图片格式可通过查看图片数据来确定(查看方式:Notepad打开图片,选择插件插件管理,安装HEXEditor,安装后再次选择插件HEXEditorViewinHEX)。 以JPEG和PNG图像格式为例。JPEG格式以0xFFD8开头,以0xFFD9结尾。PNG格式以0x89504E470D0A1A0A开头,其中504E47是英文字符串PNG的ASCII码,以0000000049454E44AE426082结尾,标志着PNG数据流结束。 三、JPEG压缩 上文的图例是图像文件实际保存的数据,也就是图像压缩后的数据。本文以JPEG格式为例讲解图像压缩的过程。JPEG的文件格式一般有两种文件扩展名:。jpg和。jpeg,这两种扩展名的实质是相同的,我们可以把。jpg的文件改名为。jpeg,而对文件本身不会有任何影响。严格来讲,JPEG的文件扩展名应该为。jpeg,由于DOS时代的8。3文件名命名原则,就使用了。jpg的扩展名。 下文以小狗图像为例,详述图片压缩具体过程,图像分辨率是320x264。首先看下图: 通常我们看到的彩色图像是三通道或四通道图像。三通道图像是指有RGB三个通道,R:红色,G:绿色,B:蓝色。四通道图像是在三通道的基础上加了Alpha通道,Alpha通道用来衡量一个像素的透明度。当Alpha为0时,该像素完全透明;当Alpha为255时,该像素完全不透明。四通道图像只有PNG格式支持。 图中小狗是三通道图像,有320x264个像素点,每个像素点由三个值表示,如上图右侧小狗眼睛部分,黑色区域每个通道的像素值较小如(3,2,11),白点部分像素值较高如(114,116,117)。图中共84480个像素,每个像素用24位表示,若直接存储需要占用844802481024247。5KB,为了有效地传输和存储图像,有必要对图像做压缩。JPEG压缩步骤如下。1。色彩空间转换 JPEG采用YUV颜色空间,Y表示明亮度,也就是灰度值;U和V表示色度,用于描述图像色彩和饱和度。因为人眼对亮度比较敏感,而对于色度不那么敏感,可以在UV维度大量缩减信息,所以先将RGB的数据转换到YUV色彩空间。转换公式:Y0。299R0。587G0。114BU0。5R0。4187G0。0813G128V0。1687R0。3313G0。5B128 python实现importcv2importnumpyasnpopencv读取的图片是BGR顺序imagecv2。imread(datadog。jpg)h,w,cimage。shape色彩空间转换BGRYUVimageyuvnp。zeroslike(image,dtypenp。uint8)forlineinrange(h):forrowinrange(w):Bimage〔line,row,0〕Gimage〔line,row,1〕Rimage〔line,row,2〕Ynp。round(0。299R0。587G0。114B)Unp。round(0。5R0。4187G0。0813G128)Vnp。round(0。1687R0。3313G0。5B128)imageyuv〔line,row,:〕(Y,U,V)保存图像cv2。imwrite(Y。png,imageyuv〔:,:,0〕)cv2。imwrite(U。png,imageyuv〔:,:,1〕)cv2。imwrite(V。png,imageyuv〔:,:,2〕)cv2。imwrite(YUV。png,imageyuv) 结果展示 2。降采样 由于人眼对色度不敏感,直接将U、V分量进行色度采样,JPEG压缩算法采用YUV4:2:0的色度抽样方法。4:2:0表示对于每行扫描的像素,只有一种色度分量以2:1的抽样率存储,也就是说每隔一行列取值,偶数行取U值,奇数行取V值,UV通道宽度和高度分别降低为原来的12。 python实现色彩空间转换BGRYUV4:2:0defRGB2YUV420(image):h,w,cimage。shapeimageynp。zeros((h,w),dtypenp。uint8)imageunp。zeros(((h1)21,(w1)21),dtypenp。uint8)imagevnp。zeros(((h1)21,(w1)21),dtypenp。uint8)forlineinrange(h):forrowinrange(w):Bimage〔line,row,0〕Gimage〔line,row,1〕Rimage〔line,row,2〕Ynp。round(0。299R0。587G0。114B)imagey〔line,row〕Yifline20androw20:Unp。round(0。5R0。4187G0。0813G128)imageu〔line2,row2〕Uifline21orlineh1:Vnp。round(0。1687R0。3313G0。5B128)imagev〔line2,row2〕Vreturnimagey,imageu,imagev 结果展示 3。离散余弦变换(DCT) 人类视觉对高频信息不敏感,利用离散余弦变换可分析出图像中高低频信息含量,进而压缩数据。 JPEG中将图像分为88的像素块,对每个像素块利用离散余弦变换进行频域编码,生成一个新的88的数字矩阵。对于不能被8整除的图像大小,需对图像填充使其可被8整除,通常使用0填充。由于离散余弦变换需要定义域对称,所以先将矩阵中的数值左移128,使值域范围在〔128,127〕。 二维离散余弦变换公式为: python实现importmathdefalpha(u):ifu0:return1np。sqrt(8)else:return12defblockfill(block):blocksize8dstnp。zeros((blocksize,blocksize),dtypenp。uint8)h,wblock。shapedst〔:h,:w〕blockreturndstdefDCTblock(img):blocksize8imgblockfill(img)imgfp32img。astype(np。float32)imgfp32128imgdctnp。zeros((blocksize,blocksize),dtypenp。float32)forlineinrange(blocksize):forrowinrange(blocksize):n0forxinrange(blocksize):foryinrange(blocksize):nimgfp32〔x,y〕math。cos(linenp。pi(2x1)16)math。cos(rownp。pi(2y1)16)imgdct〔line,row〕alpha(line)alpha(row)nreturnnp。ceil(imgdct)defDCT(image):blocksize8h,wimage。shapedlist〔〕foriinrange((hblocksize1)blocksize):forjinrange((wblocksize1)blocksize):imgblockimage〔iblocksize:(i1)blocksize,jblocksize:(j1)blocksize〕处理一个像素块imgdctDCTblock(imgblock)dlist。append(imgdct)returndlistimgdctDCT(imagey) 结果展示 4。量化 每个88的像素块经离散余弦变换后生成一个88的浮点数矩阵,量化的过程则是去除矩阵中的高频信息,保留低频信息。JPEG算法提供了两张标准化系数矩阵,分别处理亮度数据和色差数据,表示50的图像质量。 量化的过程:使用DCT变换后的浮点矩阵除以量化表中数值,然后取整。量化表是控制JPEG压缩比的关键,可以根据输出图片的质量来自定义量化表,通常自定义量化表与标准量化表呈比例关系,表中数字越大则质量越低,压缩率越高。 python实现defquantization(blocks,Q):imgquan〔〕forblockinblocks:imgquan。append(np。round(np。pide(block,Q)))returnimgquanimgquanquantization(imgdct,Qy) 结果展示 5。ZIGZAG排序 排序规则如图: python实现defzigzag(blocks):blocklist〔〕forblockinblocks:zlist〔〕w,hblock。shapeifw!h:returnNonemaxsumwh2forsinrange(maxsum1):ifs20:foriinrange(s,1,1):jsiifiworjh:continuezlist。append(block〔i,j〕)else:forjinrange(s,1,1):isjifiworjh:continuezlist。append(block〔i,j〕)blocklist。append(zlist)returnblocklistzglistzigzag(imgquan) 结果展示 〔39。0,4。0,4。0,0。0,0。0,2。0,2。0,1。0,1。0,1。0,0。0,0。0,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,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〕6。差分脉冲编码调制(DPCM)对直流系数(DC)编码 对像素矩阵做DCT变换,相当于将矩阵的能量压缩到第一个元素中,左上角第一个元素被称为直流(DC)系数,其余的元素被称为交流(AC)系数。JPEG将量化后的频域矩阵中的DC系数和AC系数分开编码。使用DPCM技术,对相邻图像块量化DC系数的差值进行编码;使用行程长度编码(RLE)对AC系数编码。需要注意的一点是,对AC系数的的RLE编码是在8x8的块内部进行的,而对DC系数的DPCM编码是在整个图像上若干个8x8的块之间进行的。 差值编码原理:样值与前一个(相邻)样值的差值,则这些差值大多数是很小的或为零,可以用短码来表示;而对于出现几率较差的差值,用长码表示,这样可以使总体码数下降;采用对相邻样值差值进行变字节长编码的方式称为差值编码,又称为差分脉码调制(DPCM)。 8x8的图像块经过DCT变换后,得到的直流系数特点:系数值较大;相邻图像块的系数值变换不大。 python实现defDPCM(zglist):resdpcm〔〕foriinrange(len(zglist)):ifi0:resdpcm。append(zglist〔i〕〔0〕)continueresdpcm。append(zglist〔i〕〔0〕zglist〔i1〕〔0〕)returnresdpcmresdpcmDPCM(zglist) 结果展示 〔50。0,2。0,13。0,7。0,3。0,0。0,1。0,0。0,1。0,2。0,0。0,1。0,0。0,1。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〕7。DC系数中间格式 JPEG中为了更进一步节约空间,不直接保存数据的具体数值,而是将数据按照位数分为16组,保存在表里面。这也就是所谓的变长整数编码VLI。编码VLI表如下: 以第一个block和第二个block为例,DPCM结果是50,通过查找VLI编码表该值位于VLI表格的第6组,因此可以写成(6)(50)的形式,即为DC系数的中间格式。8。行程长度编码(RLC)对交流系数(AC)编码 具有相同颜色并且是连续的像素数目称为行程长度。RLC编码简单直观,编码解码速度快。例如,字符串AAABCDDDDDDDDBBBBB利用RLE原理可以压缩为3ABC8D5B。在JPEG编码中,使用的数据对是(两个非零AC系数之间连续0的个数,下一个非零AC系数的值)。注意,如果AC系数之间连续0的个数超过16,则用一个扩展字节(15,0)来表示16连续的0。 python实现defrlc(zglist):resac〔〕foriinrange(len(zglist)):ac〔〕zgzglist〔i〕zeronum0forkinrange(1,len(zg)):ifzg〔k〕!0:ac。append((zeronum,zg〔k〕))zeronum0else:zeronum1ifzeronum:ac。append((0,0))resac。append(ac)returnresacresacrlc(zglist) 结果展示 zigzag结果:〔50。0,2。0,13。0,7。0,3。0,0。0,1。0,0。0,1。0,2。0,0。0,1。0,0。0,1。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〕 RLC编码结果:〔(0,2。0),(0,13。0),(0,7。0),(0,3。0),(1,1。0),(1,1。0),(0,2。0),(1,1。0),(1,1。0),(1,1。0),(0,0)〕9。AC系数中间格式 RLC编码结果:〔(0,2。0),(0,13。0),(0,7。0),(0,3。0),(1,1。0),(1,1。0),(0,2。0),(1,1。0),(1,1。0),(1,1。0),(0,0)〕 对每组数据第二个数进行VLI编码,(0,2。0)第二个数是2。0,查找VLI编码表是第2组,所以可将其写(0,2),2。0。同理,AC系数中间格式可写成以下形式: (0,2),2。0,(0,4),13。0,(0,3),7。0,(0,2),3。0,(1,1),1。0,(1,1),1。0,(0,2),2。0,(1,1),1。0,(1,1),1。0,(1,1),1。0,(0,0)10。熵编码 JPEG基本系统规定采用Huffman编码。Huffman编码时DC系数与AC系数分别采用不同的Huffman编码表,对于亮度和色度也采用不同的Huffman编码表。因此,需要4张Huffman编码表才能完成熵编码的工作。具体的Huffman编码采用查表的方式来高效地完成。 上文中8x8像素块的中间格式:DC:(6)(50),数字6查DC亮度Huffman编码表是1110,数字50查VLI编码表是110010。AC:(0,2),2。0,(0,4),13。0,(0,3),7。0,(0,2),3。0,(1,1),1。0,(1,1),1。0,(0,2),2。0,(1,1),1。0,(1,1),1。0,(1,1),1。0,(0,0),(0,2)查AC亮度Huffman编码表是01,2。0查VLI编码表是01。 因此,这个8x8的亮度像素块信息压缩后的数据流为1110110010,0101,10110010,100000,0100,11000,11000,0101,11000,11000,11000,1010。总共65比特,压缩比为(64865)(648)10087。3 以上是JPEG压缩的整个过程,最终将所有编码结果整合并按JPEG规范格式存储,即可得到jpg格式的图像文件。 智驱力科技驱动生产力