當前位置:
首頁 > 知識 > 為提高用戶體驗,Yelp 是如何無損壓縮圖片的

為提高用戶體驗,Yelp 是如何無損壓縮圖片的

(點擊

上方藍字

,快速關注我們)




編譯:伯樂在線 - iScream


如有好文章投稿,請點擊 → 這裡了解詳情




Yelp 擁有超過 1 億張由用戶生成的照片,這些照片從晚餐、理髮,到我們最新的功能之一:yelfies。這些圖像佔據了用戶 APP 和網站的大部分帶寬,這意味著存儲和傳輸的巨大成本。為了向用戶提供最好的體驗,我們努力優化這些圖片並將其平均大小縮小了 30%。這樣節省了用戶的時間和帶寬,並降低了為這些圖像提供服務的成本。哦,我們這樣做都沒有降低圖像的質量呢!






背景




Yelp 已經幫用戶存儲上傳的照片超過 12 年了。我們那些將無損格式的圖片(PNG,GIF)保存成 PNG 格式,而所有其他的格式的圖片則保存成 JPEG 格式。我們保存圖片用的是 Python 和 Pillow,並使用如下代碼片段,開啟了我們照片上傳的故事:





# do a typical thumbnail, preserving aspect ratio


new_photo

=

photo

.

copy

()


new_photo

.

thumbnail

(


    

(

width

,

height

),

    

resample

=

PIL

.

Image

.

ANTIALIAS

,


)


thumbfile

=

cStringIO

.

StringIO

()


save_args

=

{

"format"

:

format

}

if

format

==

"JPEG"

:


    

save_args

[

"quality"

]

=

85


new_photo

.

save

(

thumbfile

,

**

save_args

)




以此作為起點,我們開始研究潛在的文件大小優化方法,可以讓我們在不損失質量的前提下使用。




優化




首先,我們不得不決定是自己來處理這個問題,還是讓 CDN 提供商用他們的魔力來神奇地改變我們的照片。在優先保證高質量的內容前提下,去評估眾多選項並使得潛在的尺寸與質量相互抵消是有意義的。我們先行調查了當前照片文件大小縮小的現狀 – 可以做出什麼改變,以及與每個改變相關聯的大小/質量下降。隨著這項調查的完成,我們決定從三大類方法來著手。這篇文章剩下的部分解釋了我們做了什麼,並在每項優化中獲得了多少收益。




1.Pillow中的改變






  • 優化標誌



  • 漸進的JPEG




2.應用照片邏輯的更改






  • 大型 PNG 格式圖的檢測



  • 動態 JPEG 質量




3.JPEG編碼器更改






  • Mozjpeg(網格量化,自定義量化矩陣)




Pillow 中的改變




標誌位優化




這是我們最簡單的更改之一:啟用 Pillow 中的設置,以 CPU 耗時為代價(optimize = True)來節約更多的文件大小。 由於該權衡的本質,這樣做並不會影響圖像質量。




對於 JPEG 格式的圖片,該標誌位會指示編碼器,通過掃描每張圖片做一次附加的遍歷,來找到最佳霍夫曼編碼。對每張圖片的第一遍掃描,我們不是寫入文件,而是計算每個值的出現統計,這是計算理想編碼所需要信息。PNG 內部使用 zlib,因此在這種情況下,標誌位優化能有效地指示編碼器使用 gzip -9,而不是 gzip -6。




這是一項簡單的改進,但事實證明它並非靈丹妙藥,只減少百分之幾的文件大小。




漸進式 JPEG




將圖像保存為 JPEG 格式時,你可以選擇幾種不同的類型:






  • 從上到下載入的 JPEG 標準圖像。



  • 漸進式 JPEG 圖像,從較模糊載入到較不模糊。Pillow(progressive = True)可以輕鬆地啟用漸進式選項。 因而,人類肉眼可感知到性能有了提升(也就是說,當圖像部分缺失時,更容易注意到它不是完全銳利的)。




此外,漸進式文件打包方式通常會使得文件尺寸減少。如維基百科文章中所詳盡解釋的那樣,JPEG 格式在 8×8 像素塊上使用 Z 字形模式進行熵編碼。當這些像素塊的值被解包並按順序排列時,我們通常首先得到的是非零數字,然後是 0 的序列,用該模式對於圖像中的每個 8×8 塊進行重複和交織。在使用漸進編碼時,解開的像素塊的順序發生了改變。每個塊值較高的數字會首先出現文件中(它給出了漸進式圖像最早掃描到的不同阻塞),並且那些能增加出色細節的小數字(包括更多的 0),它們的跨度將更長,並一直持續到末位。圖像數據的這種重新排序不會改變圖像本身,而是增加了在一行中的可能存在的 0 的數量(這樣可以更容易地壓縮)。




與由用戶貢獻的美味甜甜圈圖像做比較(點擊查看大圖):







(1)標準 JPEG 格式圖渲染的模擬







(2)漸進式 JPEG 格式圖渲染的模擬。




應用照片邏輯的改變




大型 PNG 格式圖片的檢測




Yelp 為用戶生成的內容提供了兩種圖像格式 –  JPEG 和 PNG。JPEG 是一種很好的照片格式,但通常會與高對比度設計內容(如 logo)相結合。相比之下,PNG 是完全無損的,非常適合圖形,但對於那些小失真不可見的照片來說佔用的空間太大了。在用戶上傳實際照片是 PNG 格式的情況下,我們識別這些文件並將其另存為 JPEG 格式,可節省下大量的存儲空間。Yelp 上一些常見的 PNG 照片來源於移動設備由和應用程序拍攝的截圖,這些照片是通過添加特效或邊框修飾了的。







(左)一個典型的具有 logo 和邊框的複合 PNG 格式的上傳圖。 (右)一個典型的來自於屏幕截圖的 PNG 格式的上傳圖。




我們想減少這些不必要的 PNG 格式的圖片數量,但重要的是要避免矯枉過正或降低了 logo 和圖形等的質量。我們應該如何區分出一張圖片中屬於照片的部分呢?從像素層面可以做到嗎?




使用了 2500 張圖片作為實驗樣本後,我們發現文件大小和獨特像素的結合可以很好地區分照片。我們用我們最大的解析度生成候選縮略圖,看看輸出PNG文件是否大於 300 KiB。如果是,我們還將檢查圖像內容,查看是否有超過 2^16 種獨特顏色(Yelp 將 RGBA 格式的上傳圖片轉成 RGB 格式,但如果沒這樣做的話,我們也會檢查)。




在實驗數據集中,那些手動調整閾值來定義成「 bigness 」佔了有可能縮小的文件大小的 88%(即如果我們要轉換所有圖像,我們預期的文件大小的縮小),這是在不會導致轉換後圖形的任何假陽性的前提下進行的。




動態的 JPEG 質量




縮小 JPEG 文件大小的第一個也是最為人所知的一種方式是稱為質量的設置。許多能保存 JPEG 格式圖片的應用程序會將質量指定為一個數字。




質量這個詞有點抽象。事實上,一幅 JPEG 格式的圖像的每個顏色通道都有單獨的質量。質量等級從 0 到 100 映射到顏色通道的不同量化表,取決於丟失了多少數據(通常是高頻成分)。信號域中的量化是 JPEG 編碼過程中丟失信息的步驟之一。




減小文件大小的最簡單的方法是降低圖像的質量,這會引入更多的雜訊。不是每張圖片在一個給定的相同質量水平上都會丟失相同的信息量。




我們可以為每張圖片動態地選擇一種質量優化設置,找到質量和存儲大小之間的理想平衡。有兩種方法可以做到這一點:






  • 自下而上:這些是通過處理 8 x 8 像素塊級別的圖像生成調諧量化表的演算法。它們計算出丟失了多少理論上的質量,以及丟失的數據如何放大或消除,對人眼可見失真的多或少。



  • 自上而下的:這些演算法是將整張圖片與其原始無失真版本進行比較,檢測丟失了多少信息。通過迭代生成具有不同質量設置的候選圖像,我們可以選擇其中一張圖片,作為滿足某種評估演算法的最低評估水平的圖像。




我們評估了一種自下而上的演算法,在實驗中,在我們希望使用的質量範圍的上限內沒有產生合適的結果(儘管它似乎仍然具有中檔圖像質量的潛力, 這時編碼器可以開始更冒險地去丟棄它的位元組)。關於這一策略的眾多學術論文在90年代初就有發表,該方法對計算能力要求很高,並且走了選項 B 地址這類的捷徑,例如不評估塊之間的交互。




所以我們採取了第二種方法:使用對分的演算法生成不同質量水平的候選圖像,並通過使用 pyssim 函數來計算其結構相似性度量(SSIM),來評估每個候選圖像的質量下降,直到該值處於可配置但閾值也還處於靜態。這允許我們選擇性地降低文件的平均大小(和平均質量),僅僅針對那些已經開始產生肉眼可識別的質量下降的圖像。




在下圖中,我們繪製了用3種不同質量方法生成的 2500 張圖像的SSIM值的表。




1、用一種質量為 85 的初始方法作出原始圖像,使用藍線來繪製。




2、一種能降低文件大小,質量改為 80 的替代方法,用紅線來繪製。




3、我們在最後選用了一種方法,使用橙色繪製動態質量的 SSIM 為 80 – 85 的圖,即選擇質量為 80 到 85(含)的圖像,這是基於一種滿足或超過的 SSIM 比率:該比率是預先計算的、使轉換髮生在圖像範圍中間的某個靜態值。這樣我們可以降低文件的平均大小,而不會降低那些質量最差的圖片的質量。





3 種不同質量策略下 2500 張圖片的 SSIM 值圖




SSIM?




有不少圖像質量演算法試圖模仿人類視覺系統。我們已經評估過其中的許多演算法,並認為 SSIM 雖然較為古老,但基於以下幾個特徵是最適合於迭代優化的:




1、對 JPEG 量化誤差敏感


2、快速,簡單的演算法


3、可以直接對 PIL 原生圖像對象進行計算,而不是將圖像轉換為 PNG 格式並將其傳遞給 CLI 應用程序(參見#2)




動態質量的代碼示例:





import cStringIO


import

PIL

.

Image


from ssim import compute_ssim




def get_ssim_at_quality

(

photo

,

quality

)

:


    

"""Return the ssim for this JPEG image saved at the specified quality"""


    

ssim_photo

=

cStringIO

.

StringIO

()


    

# optimize is omitted here as it doesn"t affect


    

# quality but requires additional memory and cpu


    

photo

.

save

(

ssim_photo

,

format

=

"JPEG"

,

quality

=

quality

,

progressive

=

True

)


    

ssim_photo

.

seek

(

0

)


    

ssim_score

=

compute_ssim

(

photo

,

PIL

.

Image

.

open

(

ssim_photo

))


    

return

ssim_score




def _ssim_iteration_count

(

lo

,

hi

)

:


    

"""Return the depth of the binary search tree for this range"""


    

if

lo

>=

hi

:


        

return

0


    

else

:


        

return

int

(

log

(

hi

-

lo

,

2

))

+

1




def jpeg_dynamic_quality

(

original_photo

)

:


    

"""Return an integer representing the quality that this JPEG image should be


    saved at to attain the quality threshold specified for this photo class.



    Args:


        original_photo - a prepared PIL JPEG image (only JPEG is supported)


    """


    

ssim_goal

=

0.95


    

hi

=

85


    

lo

=

80



    

# working on a smaller size image doesn"t give worse results but is faster


    

# changing this value requires updating the calculated thresholds


    

photo

=

original_photo

.

resize

((

400

,

400

))



    

if

not

_should_use_dynamic_quality

()

:


        

default_ssim

=

get_ssim_at_quality

(

photo

,

hi

)


        

return

hi

,

default_ssim



    

# 95 is the highest useful value for JPEG. Higher values cause different behavior


    

# Used to establish the image"s intrinsic ssim without encoder artifacts


    

normalized_ssim

=

get_ssim_at_quality

(

photo

,

95

)


    

selected_quality

=

selected_ssim

=

None



    

# loop bisection. ssim function increases monotonically so this will converge


    

for

i

in

xrange

(

_ssim_iteration_count

(

lo

,

hi

))

:


        

curr_quality

=

(

lo

+

hi

)

// 2


        

curr_ssim

=

get_ssim_at_quality

(

photo

,

curr_quality

)


        

ssim_ratio

=

curr_ssim

/

normalized_ssim



        

if

ssim_ratio

>=

ssim_goal

:


            

# continue to check whether a lower quality level also exceeds the goal


            

selected_quality

=

curr_quality


            

selected_ssim

=

curr_ssim


            

hi

=

curr_quality


        

else

:


            

lo

=

curr_quality



    

if

selected_quality

:


        

return

selected_quality

,

selected_ssim


    

else

:


        

default_ssim

=

get_ssim_at_quality

(

photo

,

hi

)


        

return

hi

,

default_ssim




還有一些關於這種技術的其他博客文章,這裡是柯爾特·麥卡尼斯(Colt Mcanlis)的一篇博文。當我們發布這篇博客的時候,Etsy 已經發表了一篇了!擊掌吧,更快的網路!




JPEG 編碼器的更改




Mozjpeg 是 libjpeg-turbo 的一個開源分支,它採取以時間換空間的方法,通過更長時間的運算換取更加優化的文件尺寸。這種方法很好地與離線批處理方法結合以重新生成圖像。一些更昂貴的演算法用了比 libjpeg-turbo 多出 3 – 5 倍的時間,使得圖像更小了一些!




mozjpeg 的區別之一是使用一種替代量化表。如上所述,質量是用於每個顏色通道的量化表的抽象。所有符號都指向默認的 JPEG 量化表,因而它很容易被擊敗。用這個詞來說,




JPEG spec:





這些表僅作為示例,並不一定適用於任何特定應用。




那麼自然地,不要驚訝於你知道這些表是大多數編碼器使用的默認值…




Mozjpeg 經歷了為我們的替代表做基準測試的麻煩時期,並在之後成為了它所創建的圖像中,能用到的性能最好的通用替代品。




Mozjpeg + Pillow




大多數的 Linux 發行版都默認安裝了 libjpeg。所以在 Pillow 下使用 mozjpeg 在默認情況下不起作用,但配置起來也不是很困難。當您構建 mozjpeg 時,請使用–with-jpeg8 標誌位,並確保可以通過 Pillow 鏈接到它。如果您使用Docker,您可能會有一個 Dockerfile,如:





FROM

ubuntu

:

xenial



RUN

apt

-

get

update


&&

DEBIAN_FRONTEND

=

noninteractive

apt

-

get

-

y

--

no

-

install

-

recommends

install


# build tools


nasm


build

-

essential


autoconf


automake


libtool


pkg

-

config


# python tools


python


python

-

dev


python

-

pip


python

-

setuptools


# cleanup


&&

apt

-

get

clean


&&

rm

-

rf

/

var

/

lib

/

apt

/

lists

/* /

tmp

/* /

var

/

tmp

/*



# Download and compile mozjpeg


ADD

https

:

//github.com/mozilla/mozjpeg/archive/v3.2-pre.tar.gz /mozjpeg-src/v3.2-pre.tar.gz


RUN

tar

-

xzf

/

mozjpeg

-

src

/

v3

.

2

-

pre

.

tar

.

gz

-

C

/

mozjpeg

-

src

/


WORKDIR

/

mozjpeg

-

src

/

mozjpeg

-

3.2

-

pre


RUN

autoreconf

-

fiv


&&

.

/

configure

--

with

-

jpeg8


&&

make install

prefix

=/

usr

libdir

=/

usr

/

lib64


RUN

echo

"/usr/lib64n"

> /

etc

/

ld

.

so

.

conf

.

d

/

mozjpeg

.

conf


RUN

ldconfig



# Build Pillow


RUN pip install

virtualenv


&&

virtualenv

/

virtualenv_run


&& /

virtualenv_run

/

bin

/

pip

install

--

upgrade

pip


&& /

virtualenv_run

/

bin

/

pip

install

--

no

-

binary

=:

all

:

Pillow

==

4.0.0




以上!使用它,您將可以在正常圖片工作流程中,使用由 mozjpeg 支持的 Pillow。




影響




這些改進中的每一項對我們的提升分別是多少呢?我們隨機抽取 2,500 張 Yelp 上的商業照片開始了此項研究,測試照片大小在經過我們的處理流程後有怎樣的變化。




1、通過改變 Pillow 的設置,可以將圖片大小減小 4.5%


2、大型 PNG 格式圖片檢測可以將圖片大小減小 6.2%


3、動態質量可以將圖片大小減小 4.5%


4、切換到 mozjpeg 編碼器可以將圖片大小減小 13.8%




這些改進使得圖像文件的平均大小減少了約 30%,並被我們應用到我們最大和最常見的圖像解析度上,使網站能為用戶提供更快速的服務,並且每天在數據傳輸上節省了 TB 級別的容量。按照 CDN 來衡量:







從 CDN(結合非圖像靜態內容)測量的平均文件大小。




我們還沒做的




本節旨在向您介紹可能能夠做出的一些常見改進,有的是因為它們和 Yelp 使用的工具並無關係,有些是因為我們權衡後決定不做。




子採樣




子採樣是決定網頁圖像的質量和文件大小的主要因素。對於子抽樣的更詳盡的描述可以在網上找到,但在這個博客文章我們只要說以 4:1:1(這是 Pillow 的默認值,沒有指定任何其他內容)進行子採樣就夠了,所以我們無法了解能進一步縮小多少。




有損的 PNG 編碼




在了解我們對 PNG 格式圖片做了什麼之後,用例如 pngmini 這樣的有損編碼器,選擇將這些圖片中一部分保留為 PNG 格式,可能是有意義的,但是我們選擇將其重新保存成 JPEG 格式。這是一個結果看來合理的替代選項,根據作者的說法,未修改的 PNG 格式圖片的文件大小縮小了 72-85 %。




動態內容類型




為更多的現代化內容類型提供支持,如 WebP 或 JPEG2k 肯定是我們的未來要做的。而一旦這個假設的項目實行了,就會有大量的用戶要求為現有已優化的 JPEG / PNG 圖像做這些,這將繼續使該項工作非常值得做。




SVG




我們在網站上的許多地方使用 SVG,就像我們設計師創建的靜態資產,這已成為我們的指導風格。雖然這種格式和優化工具(如 svgo)有助於減少網站頁面的開銷,但它與我們在這裡所做的工作無關。




供應商的魔術




能提供圖像傳送/調整大小/裁剪/轉碼服務的供應商太多,包括開源的 thumbor。也許這是支持響應式圖像,動態內容類型的最簡單方法,並且在將來仍然能讓我們保持在技術前沿。而現在我們的解決方案仍然是獨立的。




拓展閱讀




這裡列出的兩本書在這個領域歷史上是絕對能站穩腳跟的,強烈建議您進一步閱讀這些書籍。






  • High Performance Images



  • Designing for Performance




看完本文有收穫?請轉

發分享給更多人


關注「P

ython開發者」,提升Python技能


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python開發者 的精彩文章:

BAT 面試官帶你刷真題、過筆試
K-means 在 Python 中的實現
選擇一個 Python Web 框架:Django vs Flask vs Pyramid

TAG:Python開發者 |