大約兩年前, 我嘗試改進FFmpeg對GIF編碼的支持,至少要很體面。尤其是要在GIF編碼器中加入透明機制。然而你寫的代碼並不總是能使其達到最優,這種情況非常常見。但這仍然只是阻止編碼器陷入尴尬的嘗試。
不過最近在 Stupeflix,我們需要一個方法給 Legend app 生成高質量的 GIF,所以我決定在這上面再花些功夫。
所有在這篇博文 FFmpeg
2.6 中列舉的特性都是可用的,並且在Legend app的下一版本中將使用這些特性 (大概在3月26號左右)。
文章太長不要去讀:在Usage 部分關注怎麼使用即可。
讓我們看下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圖中一個子矩形但不改變其他地方的基本手段。當然在影片中,這麼做用處不大,我們後面再談。
除了上述機制,之前我沒有再取得什麼進展。也可能是其他措施不太明顯,總之,在圖片質量上還存在不少缺陷。
你可能知道,GIF 是受限於256色調色板。並且默認情況下,FFmpeg 只使用一個通用調色版去嘗試覆蓋所有的顏色區域,以此來支持含有大量內容的文件:
使用抖動來避免陷入這個問題(256色限制),在上面的邦尼大熊兔GIF中,應用了有序Bayer抖動。通過它的8×8網狀圖案可以很輕易地辨認出來。盡管它不是最好的方式,但是它同樣具有很多優點,例如生動,快速,實際上能夠防止帶條效應和類似的視覺小毛病。
你將會發現大多數的其它抖動方法都是基於誤差的,原理是:一個單色誤差(從調色板中挑選的顏色與想要的顏色之間的差異)將會傳播到整個畫面上。引起幀之間的一種”群集效應“,甚至是幀之間完全相同的源的區域,然而這經常會提供一個更好的質量,因為它完全抹去了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格式存儲了一個全局調色板,但你可以對一張圖片(或者是子畫面;覆蓋在前一幀上的後一幀,但它可以覆蓋在一個特定的偏移位置上以獲得一個更小的尺寸)重新定義一個調色板。每一幀的調色板都可以取代全局調色板來只對一幀起作用。一旦你停止定義一個調色板,它將會回落到全局調色板。這意味著你不能對一系列的幀定義一個調色板,而這恰恰是你想做的。(典型的做法是在每個場景變化的時候定義一個新的調色板)。
所以,換句話說,你需要遵守這樣的模式:一個全局調色板,或者,每幀一個調色板。
我最初開始在每幀中計算出一個調色板,但是我發現了這樣存在如下缺陷:
這就是我之所以沒有使用這種方法而是選擇計算一個全局調色板來代替的兩個原因。現在我回想起來,它可能與重試這種方法有關,因為在某種程度上,現在的色彩量化比我當初測試的時候的狀態要好一些。
對於一系列的幀的每一幀都使用相同的調色板(典型的做法是在場景變化時,就像前面提到的那樣)也是可能的。或者,更好的做法是:只在子矩形變化時使用。
所有的這些都當作一個練習留給讀者吧。歡迎補充,如果你對這個感興趣的話可以隨時和我聯系。
具有一個全局調色板意思是一個2-pass(二次驗碼)壓縮方式(除非你願意把所有的視頻幀都存儲在內存裡)。
第一遍是對整個圖片計算一個調色板,這就是新的palettegen濾波器參與進來的地方。這個濾波器對每一幀的所有顏色制作一個直方圖,並且基於這些生成一個調色板。
在技術層面上還存在一些瑣事:這個濾波器實現了Paul Heckbert的這篇Color Image Quantization for Frame Buffer
Display (1982)論文中的算法的一個變種。這裡是我記得的一些不同之處(或者說是關於論文中未定義的行為的特異性):
所以不管怎樣,這個濾波器都是在做色彩量化,並且生成一個調色板(通常保存在一個PNG文件裡)。
它通常看起來像這個樣子(upscaled):
第二遍(驗碼)是通過paletteuse濾波器完成的,就跟它的名字一樣,它將會使用這個調色板來生成最終的量化顏色流,它的任務是在生成的調色板中找出最合適的顏色來表示輸入的顏色。這也是你可以選擇使用哪種抖動方法的地方。
這裡同樣有一些技術側面上的小問題:
使用這兩個濾波器可以讓你將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變量包括:
不見得你會編碼一部完整的影片,所以你可能會對使用-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"
現在我們可以開始看有趣的一部分了,在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的錯切/偏移壓縮機制比較類似。如果僅僅圖像中的一部分在改變,這個選項有利於編碼加速,並且該選項還通過一些方法限制誤差擴散抖動的范圍在矩形區域內,該矩形區域即是移動場景的邊界 (如果場景變動不大,這更利於確定的輸出同時,減小了移動噪聲和輸出更好地GIF壓縮).
換句話說:如果想在圖像裡對背景使用誤差擴散抖動,雖然背景是靜態的,但可以通過這個選項限制誤差在整幅圖像的擴散。如下是與之相關的典型例子:
注意僅當頂部和底部的文本同時在動的時候,猴子的臉部圖像是怎麼抖動的。 (注意最後面幾幀).
Linux下編譯FFmpeg之下載源文件並編譯 http://www.linuxidc.com/Linux/2012-02/54565.htm
Linux 編譯升級 FFmpeg 步驟 http://www.linuxidc.com/Linux/2013-08/88190.htm
CentOS 5.6 上安裝 FFMPEG http://www.linuxidc.com/Linux/2011-09/42793.htm
在Ubuntu下安裝FFmpeg http://www.linuxidc.com/Linux/2012-12/75408.htm
Ubuntu 12.04下編譯ffmpeg http://www.linuxidc.com/Linux/2013-02/78857.htm
Ubuntu 14.04下PPA安裝FFmpeg 2.2.2 http://www.linuxidc.com/Linux/2014-05/101322.htm
FFmpeg 的詳細介紹:請點這裡
FFmpeg 的下載地址:請點這裡