| 一、圖像存儲為了有效的傳輸和存儲圖像,需要對圖像數(shù)據(jù)進(jìn)行壓縮。依據(jù)圖像的保真度,圖像壓縮可分為無損壓縮和有損壓縮。 1. 無損壓縮無損壓縮的基本原理是相同的顏色信息只需保存一次。無損壓縮保證解壓以后的數(shù)據(jù)和原始數(shù)據(jù)完全一致,壓縮時(shí)去掉或減少數(shù)據(jù)中的冗余,解壓時(shí)再重新插到數(shù)據(jù)中,是一個(gè)可逆過程。無損壓縮算法一般可以把普通文件的數(shù)據(jù)壓縮到原來的1/2-1/4。 2. 有損壓縮有損壓縮方式在解壓后圖像像素值會發(fā)生改變,解壓以后的數(shù)據(jù)和原始數(shù)據(jù)不完全一致,是不可逆壓縮方式。在保存圖像時(shí)保留了較多的亮度信息,將冗余信息合并,合并的比例不同,壓縮的比例也就不同。由于信息量減少了,所以壓縮比可以很高,圖像質(zhì)量也會下降。 二、圖像格式常見有損的圖像格式有:JPEG、WebP,常見無損的圖像格式有:PNG、BMP、GIF。 通常以文件的后綴名來區(qū)分圖片的格式,但有時(shí)并不準(zhǔn)確。實(shí)際的圖片格式可通過查看圖片數(shù)據(jù)來確定(查看方式:Notepad 打開圖片,選擇“插件”->“插件管理”,安裝“HEX-Editor”,安裝后再次選擇“插件”->“HEX-Editor”->“View in HEX”)。 以JPEG和PNG圖像格式為例。JPEG格式以0xFF D8開頭,以0xFF D9結(jié)尾。PNG格式以0x89 50 4E 47 0D 0A 1A 0A開頭,其中50 4E 47是英文字符串“PNG”的ASCII碼,以00 00 00 00 49 45 4E 44 AE 42 60 82結(jié)尾,標(biāo)志著PNG數(shù)據(jù)流結(jié)束。  三、JPEG壓縮上文的圖例是圖像文件實(shí)際保存的數(shù)據(jù),也就是圖像壓縮后的數(shù)據(jù)。本文以JPEG格式為例講解圖像壓縮的過程。JPEG的文件格式一般有兩種文件擴(kuò)展名:.jpg和.jpeg,這兩種擴(kuò)展名的實(shí)質(zhì)是相同的,我們可以把.jpg的文件改名為.jpeg,而對文件本身不會有任何影響。嚴(yán)格來講,JPEG的文件擴(kuò)展名應(yīng)該為.jpeg,由于DOS時(shí)代的8.3文件名命名原則,就使用了.jpg的擴(kuò)展名。 下文以小狗圖像為例,詳述圖片壓縮具體過程,圖像分辨率是320x264。首先看下圖:  通常我們看到的彩色圖像是三通道或四通道圖像。三通道圖像是指有RGB三個(gè)通道,R:紅色,G:綠色,B:藍(lán)色。四通道圖像是在三通道的基礎(chǔ)上加了Alpha通道,Alpha通道用來衡量一個(gè)像素的透明度。當(dāng)Alpha為0時(shí),該像素完全透明;當(dāng)Alpha為255時(shí),該像素完全不透明。四通道圖像只有PNG格式支持。 圖中小狗是三通道圖像,有320x264個(gè)像素點(diǎn),每個(gè)像素點(diǎn)由三個(gè)值表示,如上圖右側(cè)小狗眼睛部分,黑色區(qū)域每個(gè)通道的像素值較小如(3,2,11),白點(diǎn)部分像素值較高如(114,116,117)。圖中共84480個(gè)像素,每個(gè)像素用24位表示,若直接存儲需要占用84480*24/8/1024=247.5KB,為了有效地傳輸和存儲圖像,有必要對圖像做壓縮。JPEG壓縮步驟如下。 1. 色彩空間轉(zhuǎn)換JPEG采用YUV顏色空間,“Y”表示明亮度,也就是灰度值;“U”和“V”表示色度,用于描述圖像色彩和飽和度。因?yàn)槿搜蹖α炼缺容^敏感,而對于色度不那么敏感,可以在UV維度大量縮減信息,所以先將RGB的數(shù)據(jù)轉(zhuǎn)換到Y(jié)UV色彩空間。轉(zhuǎn)換公式: 
 python 實(shí)現(xiàn) import cv2import numpy as np# opencv 讀取的圖片是BGR順序image = cv2.imread('data/dog.jpg')h, w, c = image.shape# 色彩空間轉(zhuǎn)換 BGR -> YUVimage_yuv = np.zeros_like(image, dtype=np.uint8)for line in range(h):    for row in range(w):        B = image[line, row, 0]        G = image[line, row, 1]        R = image[line, row, 2]        Y = np.round(0.299*R   0.587*G   0.114*B)        U = np.round(0.5*R - 0.4187*G - 0.0813*G   128)        V = np.round(-0.1687*R - 0.3313*G   0.5*B   128)        image_yuv[line, row, :] = (Y, U, V)# 保存圖像cv2.imwrite('Y.png', image_yuv[:,:, 0])cv2.imwrite('U.png', image_yuv[:,:, 1])cv2.imwrite('V.png', image_yuv[:,:, 2])     cv2.imwrite('YUV.png', image_yuv) 結(jié)果展示  2. 降采樣由于人眼對色度不敏感,直接將U、V分量進(jìn)行色度采樣,JPEG壓縮算法采用YUV 4:2:0的色度抽樣方法。4:2:0表示對于每行掃描的像素,只有一種色度分量以2:1的抽樣率存儲,也就是說每隔一行/列取值,偶數(shù)行取U值,奇數(shù)行取V值,UV通道寬度和高度分別降低為原來的1/2。  python 實(shí)現(xiàn) 結(jié)果展示  3. 離散余弦變換(DCT)人類視覺對高頻信息不敏感,利用離散余弦變換可分析出圖像中高低頻信息含量,進(jìn)而壓縮數(shù)據(jù)。 JPEG中將圖像分為8*8的像素塊,對每個(gè)像素塊利用離散余弦變換進(jìn)行頻域編碼,生成一個(gè)新的8*8的數(shù)字矩陣。對于不能被8整除的圖像大小,需對圖像填充使其可被8整除,通常使用0填充。由于離散余弦變換需要定義域?qū)ΨQ,所以先將矩陣中的數(shù)值左移128,使值域范圍在[-128, 127]。 二維離散余弦變換公式為:   python 實(shí)現(xiàn) import mathdef alpha(u):    if u==0:        return 1/np.sqrt(8)    else:        return 1/2def block_fill(block):    block_size = 8    dst = np.zeros((block_size, block_size), dtype=np.uint8)    h, w = block.shape    dst[:h, :w] = block         return dstdef DCT_block(img):    block_size = 8    img = block_fill(img)    img_fp32 = img.astype(np.float32)    img_fp32 -= 128    img_dct = np.zeros((block_size, block_size), dtype=np.float32)    for line in range(block_size):        for row in range(block_size):            n = 0            for x in range(block_size):                for y in range(block_size):                    n  = img_fp32[x,y]*math.cos(line*np.pi*(2*x 1)/16)*math.cos(row*np.pi*(2*y 1)/16)            img_dct[line, row] = alpha(line)*alpha(row)*n    return np.ceil(img_dct)    def DCT(image):    block_size = 8    h, w = image.shape    dlist = []    for i in range((h   block_size - 1) // block_size):        for j in range((w   block_size - 1) // block_size):            img_block = image[i*block_size:(i 1)*block_size, j*block_size:(j 1)*block_size]            # 處理一個(gè)像素塊            img_dct = DCT_block(img_block)            dlist.append(img_dct)      return dlistimg_dct = DCT(image_y)結(jié)果展示  4. 量化每個(gè)8*8的像素塊經(jīng)離散余弦變換后生成一個(gè)8*8的浮點(diǎn)數(shù)矩陣,量化的過程則是去除矩陣中的高頻信息,保留低頻信息。JPEG算法提供了兩張標(biāo)準(zhǔn)化系數(shù)矩陣,分別處理亮度數(shù)據(jù)和色差數(shù)據(jù),表示 50% 的圖像質(zhì)量。  量化的過程:使用DCT變換后的浮點(diǎn)矩陣除以量化表中數(shù)值,然后取整。量化表是控制JPEG壓縮比的關(guān)鍵,可以根據(jù)輸出圖片的質(zhì)量來自定義量化表,通常自定義量化表與標(biāo)準(zhǔn)量化表呈比例關(guān)系,表中數(shù)字越大則質(zhì)量越低,壓縮率越高。 python 實(shí)現(xiàn) 結(jié)果展示  5. ZIGZAG排序排序規(guī)則如圖:  python 實(shí)現(xiàn) def zigzag(blocks):    block_list = []    for block in blocks:        zlist = []        w, h = block.shape        if w != h:            return None        max_sum = w   h - 2        for _s in range(max_sum   1):            if _s % 2 == 0:                for i in range(_s, -1, -1):                    j = _s - i                    if i >= w or j >= h:                        continue                    zlist.append(block[i,j])            else:                for j in range(_s, -1, -1):                    i = _s - j                    if i >= w or j >= h:                        continue                    zlist.append(block[i,j])        block_list.append(zlist)    return block_listzglist = zigzag(img_quan)結(jié)果展示 [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. 差分脈沖編碼調(diào)制(DPCM)對直流系數(shù)(DC)編碼對像素矩陣做DCT變換,相當(dāng)于將矩陣的能量壓縮到第一個(gè)元素中,左上角第一個(gè)元素被稱為直流(DC)系數(shù),其余的元素被稱為交流(AC)系數(shù)。JPEG將量化后的頻域矩陣中的DC系數(shù)和AC系數(shù)分開編碼。使用DPCM技術(shù),對相鄰圖像塊量化DC系數(shù)的差值進(jìn)行編碼;使用行程長度編碼(RLE)對AC系數(shù)編碼。需要注意的一點(diǎn)是,對AC系數(shù)的的RLE編碼是在8x8的塊內(nèi)部進(jìn)行的,而對DC系數(shù)的DPCM編碼是在整個(gè)圖像上若干個(gè)8x8的塊之間進(jìn)行的。 差值編碼原理:樣值與前一個(gè)(相鄰)樣值的差值,則這些差值大多數(shù)是很小的或?yàn)榱?,可以用短碼來表示;而對于出現(xiàn)幾率較差的差值,用長碼表示,這樣可以使總體碼數(shù)下降;采用對相鄰樣值差值進(jìn)行變字節(jié)長編碼的方式稱為差值編碼,又稱為差分脈碼調(diào)制(DPCM)。 8x8的圖像塊經(jīng)過DCT變換后,得到的直流系數(shù)特點(diǎn): 
 python 實(shí)現(xiàn) 結(jié)果展示 [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系數(shù)中間格式JPEG中為了更進(jìn)一步節(jié)約空間,不直接保存數(shù)據(jù)的具體數(shù)值,而是將數(shù)據(jù)按照位數(shù)分為16組,保存在表里面。這也就是所謂的變長整數(shù)編碼VLI。編碼VLI表如下:  以第一個(gè)block和第二個(gè)block為例,DPCM結(jié)果是50,通過查找VLI編碼表該值位于VLI表格的第6組,因此可以寫成(6)(50)的形式,即為DC系數(shù)的中間格式。 8. 行程長度編碼(RLC)對交流系數(shù)(AC)編碼具有相同顏色并且是連續(xù)的像素?cái)?shù)目稱為行程長度。RLC編碼簡單直觀,編碼/解碼速度快。例如,字符串AAABCDDDDDDDDBBBBB 利用RLE原理可以壓縮為3ABC8D5B。在JPEG編碼中,使用的數(shù)據(jù)對是(兩個(gè)非零AC系數(shù)之間連續(xù)0的個(gè)數(shù),下一個(gè)非零AC系數(shù)的值)。注意,如果AC系數(shù)之間連續(xù)0的個(gè)數(shù)超過16,則用一個(gè)擴(kuò)展字節(jié)(15,0)來表示16連續(xù)的0。 python 實(shí)現(xiàn) def rlc(zglist):    res_ac = []    for i in range(len(zglist)):        ac = []        zg = zglist[i]        zero_num = 0        for k in range(1, len(zg)):            if zg[k] != 0:                ac.append((zero_num, zg[k]))                zero_num = 0            else:                zero_num  = 1        if zero_num:            ac.append((0, 0))        res_ac.append(ac)    return res_acres_ac = rlc(zglist)結(jié)果展示 zigzag結(jié)果:[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編碼結(jié)果:[(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系數(shù)中間格式RLC編碼結(jié)果:[(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)] 對每組數(shù)據(jù)第二個(gè)數(shù)進(jìn)行VLI編碼,(0, -2.0)第二個(gè)數(shù)是-2.0,查找VLI編碼表是第2組,所以可將其寫(0, 2), -2.0。同理,AC系數(shù)中間格式可寫成以下形式: (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基本系統(tǒng)規(guī)定采用Huffman編碼。Huffman編碼時(shí)DC系數(shù)與AC系數(shù)分別采用不同的Huffman編碼表,對于亮度和色度也采用不同的Huffman編碼表。因此,需要4張Huffman編碼表才能完成熵編碼的工作。具體的Huffman編碼采用查表的方式來高效地完成。 上文中8x8像素塊的中間格式: 
 因此,這個(gè)8x8的亮度像素塊信息壓縮后的數(shù)據(jù)流為1110110010,0101,10110010,100000,0100,11000,11000,0101,11000,11000,11000,1010??偣?5比特,壓縮比為(64*8-65)/(64*8)*100%=87.3% 以上是JPEG壓縮的整個(gè)過程,最終將所有編碼結(jié)果整合并按JPEG規(guī)范格式存儲,即可得到j(luò)pg格式的圖像文件。  | 
|  |