1. 程式人生 > >質量監控-圖片減包

質量監控-圖片減包

文章連結

經過多個版本迭代,專案在release配置下的打包體積依舊輕鬆破百,應用體積過大導致的問題包括:

  • 更長的構建時間,換個詞就是加班
  • TEXT段體積過大會導致稽核失敗
  • 使用者不願意下載應用

通常來說,資原始檔能在應用體積包中佔據1/3或者更多的體積,相比起程式碼(5kb/千行)的平均佔用來說,對圖片進行減包是最直接高效的手段,對圖片資源的處理方式包括四種:

  1. 通過請求下載大圖
  2. 使用工具壓縮圖片
  3. 查詢刪除重複圖片
  4. 查詢複用相似圖片

考慮到由於專案開發分工的問題,方式1需要推動落地,所以本文不討論這種處理方式。其他三種都能通過編寫指令碼實現自動化處理

圖片壓縮

圖片壓縮分為有失真壓縮無失真壓縮兩類,有失真壓縮放棄了一部分圖片的質量換取更高的壓縮比。網上主流的壓縮工具有tinypngpngquantImageAlphaImageOptim等,分別採用了一種或者多種壓縮技術完成圖片壓縮

為什麼png能夠無失真壓縮

由於png格式的靈活性,同一張圖片可以使用多種方式進行表示,不同方式佔用的大小不一樣。一般的軟體會採用效率更高的方式來表示圖片,所以這種情況下png圖片存在巨大的優化空間。通常來說,從png檔案中能去除的資料包括:

  • iTXttEXtzTXt這些可以儲存任意文字的資料區段
  • iCCP資料區段儲存的profile
    等等
  • photoshop匯出的png圖片存在大量的額外資訊

png圖片有兩種型別的資料塊,一種是必不可缺的資料塊稱為關鍵資料塊。另一種叫做輔助資料塊png檔案格式規範指定的輔助資料塊包括:

  • 背景顏色資料塊bKGD
  • 基色和白色資料塊cHRM
  • 影象γ資料塊gAMA
  • 影象直方圖資料塊hIST
  • 物理畫素尺寸資料塊pHYs
  • 樣本有效位資料塊sBIT
  • 文字資訊資料塊tEXt
  • 影象最後修改時間資料塊tIME
  • 影象透明資料塊tRNS
  • 壓縮文字資料塊zTXt

其中tEXtzTXt資料段中存在的資料包括:

關鍵字
Title 影象名稱
Author 影象作者
Description 影象說明
Copyright 版權宣告
CreationTime 原圖創作時間
Software 創作影象使用的軟體
Disclaimer 棄權
Warning 影象內容警告
Source 創作影象使用的裝置
Comment 註釋資訊

由上可見,輔助資料塊在png檔案中可能佔據了極大的篇幅,正是這些資料塊構成了png的無失真壓縮條件

tinypng

tinypng採用了一種稱作Quantization的壓縮技術,通過合併圖片中相似的顏色,將24bit的圖片檔案壓縮成8bit圖片,同時去除圖片中不必要的元資料,圖片最高能達到70%以上的壓縮率。截止文章完成之前,tinypng僅提供了線上壓縮功能,暫未提供工具下載

pngquant

根據官方介紹,pngquant24bit以上的圖片轉換成8bit的保留透明度通道的壓縮圖片,壓縮演算法的壓縮比非常顯著,通常都能減少70%的大小。pngquant提供了命令列工具來完成解壓任務:

pngquant --quality=0-100 imagepath
複製程式碼

命令列更多除錯引數可以在官網參閱

ImageAlpha

ImageAlpha是一個macOS系統下的有損圖片壓縮工具,內建了pngquantpngnq-s9等多個壓縮工具,多數情況下通過將圖片降至8bit來獲取高壓縮比。由於ImageAlpha的視覺化介面無法批量處理圖片,直接使用提供的命令工具可以實現批量壓縮圖片:

for file in $(ls $1); do
    imagepath=$1"/"$file
    if [ -d imagepath ]
    then
        /// 路徑為資料夾
    else
        if [[ $file == *.png ]]
        then
            beforeSize=`ls -l $imagepath | awk '{print $5}'`
            /Applications/ImageAlpha.app/Contents/MacOS/pngquant $imagepath
            afterSize=`ls -l ${imagepath/.png/-fs8.png} | awk '{print $5}'`
            
            if [[ $afterSize -lt $beforeSize]]
            then
                mv ${imagepath/.png/-fs8.png} $imagepath
            fi
        fi
    fi
done
複製程式碼

使用ImageAlpha需要注意兩點:

  1. 壓縮後的圖片命名會自動新增-fs8字尾,需要使用mv命令實現替換
  2. 有失真壓縮會修改關鍵資料塊,可能導致壓縮圖片尺寸增大,需要過濾

在使用有失真壓縮時需要注意單張png圖片是可以被多次壓縮的,但這會導致圖片的清晰度和色彩都受到影響,不建議對圖片超過一次以上的有失真壓縮

ImageOptim

ImageOptim是介紹的四種工具中唯一的無失真壓縮,它採用了包括去除exif資訊重新排列畫素儲存方式等手段實現圖片的壓縮。無損代表著一張圖片被ImageOptim壓縮後,後續無法再次進行壓縮,同時它的壓縮比往往比不上其他的有失真壓縮方案,但最大程度上保證了圖片的原始清晰度和色彩

for file in $(ls $1); do
    imagepath=$1"/"$file
    if [ -d imagepath ]
    then
        /// 路徑為資料夾
    else
        if [[ $file == *.png ]]
        then
            /Applications/ImageOptim.app/Contents/MacOS/ImageOptim $imagepath
        fi
    fi
done
複製程式碼

ImageOptim同樣存在視覺化的工具並且支援批量壓縮圖片

多方案對比

考慮到ImageAlpha幾乎都是使用pngquant作為壓縮工具,因此只列出三種壓縮工具的對比:

原始尺寸 壓縮工具 壓縮後尺寸 壓縮比
319.5KB tinypng 120.5KB 62%
319.5KB ImageAlpha-pngquant 395KB -24%
319.5KB ImageOptim 252KB 21%

測試圖片採用qq聊天截圖生成的pngtinypng壓縮率非常高,而pngquant的表現不盡人意

刪除重複圖片

通常來說,出現重複圖片的原因包括模組間需求開發沒有打通或是缺少統一的圖片命名規範。通過圖片MD5摘要是識別重複圖片的最快方法,以python為例,匹配重複圖片的程式碼如下:

md5list = {}
for file in files:
    if os.path.isdir(file.path):
        continue
        
    md5obj = hashlib.md5()
    fd = open(file.path, 'rb')
    while True:
        buff = fd.read(2048)
        if not buff:
            break
        md5obj.update(buff)
    fd.close()
    
    filemd5 = str(md5obj.hexdigest()).lower()
    if filemd5 in md5list:
        md5list[filemd5].add(file.path)
    else:
        md5list[filemd5] = set([file.path])
        
for key in md5list:
    list = md5list[key]
    if len(list) > 1:
        print (list)
複製程式碼

在遍歷中以檔案MD5字串作為key,維護具備相同MD5的圖片路徑,最後遍歷這個map查詢存在一個以上路徑的陣列並且輸出

尋找相似圖片

相似圖片在圖片內容、色彩上都十分的接近,多數時間可以考慮複用這些圖片,但相似圖片的問題在於無法通過MD5直接匹配。為了確認兩個圖片是否相似,要使用簡單的一個數學公式來幫忙查詢:

方差。在概率論和統計學中,一個隨機變數的方差描述的是它的離散程度,也就是該變數離其期望值的距離

舉個例子,甲同學五次成績分別是65, 69, 81, 89, 96,乙同學五次成績是82, 80, 77, 81, 80,兩個人平均成績都是80,但是引入方差公式計算:

甲: ((65-80)^2 + (69-80)^2 + (81-80)^2 + (89-80)^2 + (96-80)^2) / 5 = 136.8
乙: ((82-80)^2 + (80-80)^2 + (77-80)^2 + (81-80)^2 + (80-80)^2) / 5 = 2.8
複製程式碼

平均值相同的情況下,方差越大,說明資料偏離期望值的情況越嚴重。方差越接近的兩個隨機變數,他們的變化就越加趨同,獲取方差程式碼如下:

def getVariance(nums):
    variance = 0
    average = sum(nums) / len(nums)
    for num in nums:
        variance += (num - average) * (num - average) / len(nums)
    return variance
複製程式碼

因此將圖片劃分成連串的一維資料,以此計算出圖片的方差,通過方差匹配可以實現一個簡單的圖片相似度判斷工具,實現前還要注意兩點:

  1. 圖片RGB色彩值會導致方差的計算變得複雜,所以轉成灰度圖可以降低難度
  2. 不同尺寸需要縮放到相同尺寸進行計算

最終將圖片轉換成一維資料列表的程式碼如下:

def getAverageList(img):
    commonlength = 30
    img = cv2.resize(img, (commonlength, commonlength), interpolation=cv2.INTER_CUBIC)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    res = []
    for idx in range(commonlength):
        average = sum(gray[idx]) / len(gray[idx])
        res.append(average)
複製程式碼

將圖片轉成灰度圖後,仍然可能存在RGB色值不同但灰度值相同的情況導致判斷失準,可以考慮兩種方案提高演算法的檢測準確率:

  1. 在不修改以灰度值計算方差的方案下,構建以列平均畫素值為單位的一維列表計算另一個方差,兩個方差值一併做判斷
  2. 摒棄灰度值方差方案,每一行分別生成RGB三種色彩平均值的一維列表,計算出三個方差進行匹配檢測

效果

經過兩輪圖片減包處理後,整個專案資源產生的減包量約有20M,其中通過文中的三種手段產生的減包量在6.5M左右,整體上來看產出還是比較可觀的

關注我的公眾號獲取更新資訊