歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> 使用FFmpeg處理高質量GIF圖片

使用FFmpeg處理高質量GIF圖片

日期:2017/2/27 15:54:29   编辑:Linux教程

大約2年前,我試著使用FFmpeg來提升GIF圖片質量,這就要求在GIF編碼器中加入了透明機制。在大多數情況下因為圖片源並不可控,所以這些嘗試僅僅是避免編碼器損失太多信息而已。

最近在Stupeflix,我們需要為 Legend app 提供高質量的 GIF,所以我決定再試試。

在這篇微博中提到的所有特性都可以在 FFmpeg 2.6 中找到,他們也會被應用到Legend app的下一個版本(下一個版本可能在3月26號左右)。

TL;DR:跳轉到 Usage 段看怎麼使用。

初始提升(2013)

讓我們看下2013年引入GIF編碼器的透明機制的作用:

% ffmpeg -v warning -ss 45 -t 2 -i big_buck_bunny_1080p_h264.mov -vf scale=300:-1 -gifflags -transdiff -y bbb-notrans.gif
% ffmpeg -v warning -ss 45 -t 2 -i big_buck_bunny_1080p_h264.mov -vf scale=300:-1 -gifflags +transdiff -y bbb-trans.gif
% ls -l bbb-*.gif
-rw-r--r-- 1 ux ux 1.1M Mar 15 22:50 bbb-notrans.gif
-rw-r--r-- 1 ux ux 369K Mar 15 22:50 bbb-trans.gif

這個選項默認生效,當你的圖片是高度運動的或者色彩變化強烈,你應該關閉它。

另一個實現的壓縮機制是剪切,剪切是僅僅重繪GIF圖中一個子矩形但不改變其他地方的基本手段。當然在影片中,這麼做用處不大,我們後面再談。

除了上述機制,之前我沒有再取得什麼進展。也可能是其他措施不太明顯,總之,在圖片質量上還存在不少缺陷。


256色的局限性

你可能知道,GIF 是受限於256色調色板。並且默認情況下,FFmpeg 只使用一個通用調色版去嘗試覆蓋所有的顏色區域,以此來支持含有大量內容的文件:


有序抖動和誤差擴散
使用抖動來避免陷入這個問題(256色限制),在上面的邦尼大熊兔GIF中,應用了有序Bayer抖動。通過它的8x8網狀圖案可以很輕易地辨認出來。盡管它不是最好的方式,但是它同樣具有很多優點,例如生動,快速,實際上能夠防止帶條效應和類似的視覺小毛病。

你將會發現大多數的其它抖動方法都是基於誤差的,原理是:一個單色誤差(從調色板中挑選的顏色與想要的顏色之間的差異)將會傳播到整個畫面上。引起幀之間的一種”群集效應“,甚至是幀之間完全相同的源的區域,然而這經常會提供一個更好的質量,因為它完全抹去了GIF的壓縮:

% ffmpeg -v warning -ss 45 -t 2 -i big_buck_bunny_1080p_h264.mov -vf scale=300:-1:sws_dither=ed -y bbb-error-diffusal.gif
% ls -l bbb-error-diffusal.gif
-rw-r--r-- 1 ux ux 1.3M Mar 15 23:10 bbb-error-diffusal.gif


更好的調色板
提高GIF圖片質量的第一步就是定義一個更好的調色板。GIF格式存儲了一個全局調色板,但你可以對一張圖片(或者是子畫面;覆蓋在前一幀上的後一幀,但它可以覆蓋在一個特定的偏移位置上以獲得一個更小的尺寸)重新定義一個調色板。每一幀的調色板都可以取代全局調色板來只對一幀起作用。一旦你停止定義一個調色板,它將會回落到全局調色板。這意味著你不能對一系列的幀定義一個調色板,而這恰恰是你想做的。(典型的做法是在每個場景變化的時候定義一個新的調色板)。

所以,換句話說,你需要遵守這樣的模式:一個全局調色板,或者,每幀一個調色板。

每幀一個調色板 (未實施)

我最初開始於實施一個每幀調色板的計算,但它具有如下缺點:

  • 開銷:一個256色調色板是768B大小,而且它不是LZW壓縮算法的一部分,所以它不會被壓縮。而且它被存儲在每一幀裡,這就意味著一個25FPS的連續鏡頭就需要150kbits/秒的開銷。然而大部分是可以忽略的。
  • 我最初始測試由於調色板的變化產生了一個亮度閃爍效果,這一點也不好。

這就是我之所以沒有使用這種方法而是選擇計算一個全局調色板來代替的兩個原因。現在我回想起來,它可能與重試這種方法有關,因為在某種程度上,現在的色彩量化比我當初測試的時候的狀態要好一些。

對於一系列的幀的每一幀都使用相同的調色板(典型的做法是在場景變化時,就像前面提到的那樣)也是可能的。或者,更好的做法是:只在子矩形變化時使用。

所有的這些都當作一個練習留給讀者吧。歡迎補充,如果你對這個感興趣的話可以隨時和我聯系。

一個全局調色板(已實施)

具有一個全局調色板意思是一個2-pass(二次驗碼)壓縮方式(除非你願意把所有的視頻幀都存儲在內存裡)。

第一遍是對整個圖片計算一個調色板,這就是新的palettegen濾波器參與進來的地方。這個濾波器對每一幀的所有顏色制作一個直方圖,並且基於這些生成一個調色板。

在技術層面上還存在一些瑣事:這個濾波器實現了Paul Heckbert的這篇Color Image Quantization for Frame Buffer Display (1982)論文中的算法的一個變種。這裡是我記得的一些不同之處(或者說是關於論文中未定義的行為的特異性):

  • 它使用一個全解析度的色彩直方圖。而不是論文中作為關鍵建議使用的下采樣 RGB 5:5:5 直方圖,這個濾波器對1600萬種可能的 RGB 8:8:8 色彩使用了一個哈希表。

  • 對方格的分割任然是在中點上進行的,對要分割的方格的選擇是根據方格中的顏色方差來進行的(一個帶有大的色彩方差的方格將會優先截掉)。

  • 對方格中的顏色求平均值取決於顏色的重要性,就我而言,這在論文中並沒有定義。

  • 當沿著一個維度(紅,綠或藍)進行分割方格時,假如相等,綠色是優先於紅色的,然後再是藍色。

所以不管怎樣,這個濾波器都是在做色彩量化,並且生成一個調色板(通常保存在一個PNG文件裡)。

它通常看起來像這個樣子(upscaled):


顏色映射與抖動

第二遍(驗碼)是通過paletteuse濾波器完成的,就跟它的名字一樣,它將會使用這個調色板來生成最終的量化顏色流,它的任務是在生成的調色板中找出最合適的顏色來表示輸入的顏色。這也是你可以選擇使用哪種抖動方法的地方。

這裡同樣有一些技術側面上的小問題:

  • 濾波器實現了五種抖動方法,然而最初的論文中只提出了一種方法。

  • 就像palettegen一樣,色彩分辨率(將24-bit輸入顏色映射到一個調色板條目上)的完成沒有破壞輸入。它是通過一個K-d Tree(當k = 3時,很明顯,每一個維度都是RGB的組成部分)的迭代實現和一個緩存系統來達到這一目標的。

使用這兩個濾波器可以讓你將GIF編碼成這樣(單全局調色板,無抖動):



用法

使用相同參量手動運行兩遍(驗碼)是有點討厭,還要對每一遍的參數進行調整。所以我推薦寫一個簡單的腳本,如下:

#!/bin/sh
 
palette="/tmp/palette.png"
 
filters="fps=15,scale=320:-1:flags=lanczos"
 
ffmpeg -v warning -i $1 -vf "$filters,palettegen" -y $palette
ffmpeg -v warning -i $1 -i $palette -lavfi "$filters [x]; [x][1:v] paletteuse" -y $2

...可以這樣使用:

% ./gifenc.sh video.mkv anim.gif

filters變量包括:

  • 一個幀率的調整(減小到15會使畫面看起來不平穩,但可以使最終的GIF體積更小)

  • 一個取代默認(目前是bilinear)定標器的lanczos定標器縮放比例。推薦它的原因是你使用lanczos或bicubic來縮放畫面要比bilinear優越的多。如果你不這樣做的話,你的輸入會模糊的多。


提取:僅作示例

不見得你會編碼一部完整的影片,所以你可能會對使用-ss和-t選項來選擇一個片段感興趣。如果你真是這樣,那麼就要確保將它作為輸入選項加入(在-i選項之前),例如:

#!/bin/sh
 
start_time=12:23
duration=35
 
palette="/tmp/palette.png"
 
filters="fps=15,scale=320:-1:flags=lanczos"
 
ffmpeg -v warning -ss $start_time -t $duration -i $1 -vf "$filters,palettegen" -y $palette
ffmpeg -v warning -ss $start_time -t $duration -i $1 -i $palette -lavfi "$filters [x]; [x][1:v] paletteuse" -y $2

如果不是,那麼至少在第一遍它就會導致沒有多個幀輸出(調色板),所以不會做你想要的。

一個可供選擇的是在流復制中預提取你想要編碼的片段,看起來是這樣:

% ffmpeg -ss 12:23 -t 35 -i full.mkv -c:v copy -map 0:v -y video.mkv

如果流復制不夠精確,你可以添加一個trim濾波器。例如:

filters="trim=start_frame=12:end_frame=431,fps=15,scale=320:-1:flags=lanczos"

獲得最好的調色板輸出Getting the best out of the palette generation

現在我們可以開始看有趣的一部分了,在palettegen濾波器中,主要的和能讓你感興趣去嘗試的大概就是stats_mode選項了。

這個選項的主要作用就是允許你指明在整部視頻中你需要東西,或者只是移動的物體。如果你使用stats_mode=full(默認),所有的像素將會是顏色統計的一部分。如果你使用stats_mode=diff,只有與前一幀不同的像素會被計入。

注意:向一個濾波器中添加選項,需要這樣做:filter=opt1=value1:opt2=value2

下面是一個例子來說明它是怎樣影響最終的輸出的:

第一張GIF圖片使用stats_mode=full(默認)。在整個展示過程中背景都沒有變化,結果是天空因為明智的顏色得到了更多的關注。另一方面,作為結果,文本的淡出遭到了破壞:

另一方面,第二張GIF圖片是使用stats_mode=diff,這對移動物體很有幫助。事實上,文本淡出的表現更好,代價是天空的抖動產生了點小問題:


獲得最佳的顏色映射輸出

paletteuse濾波器具有稍微多點的選項來操作。最明顯的是抖動(dither選項)。唯一可有效預測的抖動是Bayer抖動,其它所有的抖動都是基於誤差擴散。

如果你真的希望使用Bayer(因為你有速度或尺寸的限制),你可以使用bayer_scale選項來減小或增加它的方格圖案。 pattern.

當然你同樣可以通過使用dither=none來完全禁用抖動。

關於誤差擴散抖動,你將會希望使用floyd_steinberg,sierra2和sierra2_4a。關於這些的詳細信息,我將你重定向到這裡DHALF.TXT.

對於懶惰的人,floyd_steinberg是最受歡 迎的了,而sierra2_4a是sierra2(這個才是默認的)的一個快速/小型化的版本,擴散的原理是用三個像素來代替七個像素。heckbert 是記錄在我前面提到的論文中的一個,但只是作為參考文獻引入的(你大概沒想到)。

這裡是不同的抖動模式的一個小預覽:

原始的 (31.82K) :

dither=bayer:bayer_scale=1(132.80K):

dither=bayer:bayer_scale=2(118.80K):

dither=bayer:bayer_scale=3(103.11K):

dither=floyd_steinberg(101.78K):

dither=sierra2(89.98K):

dither=sierra2_4a(109.60K):

dither=none(73.10K):

最後,玩過的抖動,你可能會對學習diff_mode選項感興趣,引用文檔中的一段話:

只有矩形的改變才會被再處理。這與GIF的 cropping/offsetting壓縮原理相似。如果圖片只有一部分變化,那麼對於速度來說這個選項就很有用,因而被用於這些情況,例如:對於作為 移動場景邊界的矩形的有限范圍的誤差擴散抖動(如果場景的變化不大,這種方式會導向更確定的輸出。而且,作為結果,減小了移動噪聲和得到更好的GIF壓 縮)。

或者換句話說:如果你想在你的圖片的背景上使用誤差擴散抖動,甚至他靜態的,激活這個選項來限制誤差傳播到整個圖片上。這裡有一個典型的相關情況:

只有當上面和底部的文本同時移動的時候(也就是最後幾幀),注意猴子的臉部的顏色抖動是怎樣的。
譯文:www.oschina.net/translate/high-quality-gif-with-ffmpeg

Copyright © Linux教程網 All Rights Reserved