何かを説明するときにGIFってわかりやすいですよね。
友達がmeeemoというサービスを作りそれのプレビュー動画を撮るお手伝いをしたのですが、より綺麗なGIFに変換できないのかなと思いいろいろ調べた結果です。
ささっと結果を知りたい人用
- これで汎用的に綺麗なGIFが作れます。ソースによっては調整すればより軽量で綺麗なものが作成できる可能性があります。
ffmpeg -i input.mov -filter_complex "[0:v] fps=10,scale=640:-1,split [a][b];[a] palettegen [p];[b][p] paletteuse" output-palette.gif
環境
- ffmpeg 3.4.2, 4.0.1 で動作することは確認しました。
インストール
- ffmpegはmacOSならHomebrew経由でのインストールが一番楽だと思います。
brew install ffmpeg
ソース
- QuickTimeで画面収録したmov (3624 x 2537, 45秒, 36.1MB)
※ ffmpegはサポートしているフォーマットがかなり多いので、movに限らずmp4とかほぼなんでも変換させることができます。
処理
動画の再生速度を速くする
- 本題からは少しずれますが、今回は説明動画なので少し早回しにして短時間でサービスの雰囲気が伝わるように4倍速に変更しました。
ffmpeg -i input.mov -filter:v setpts=PTS/4.0 output.mov
mov to GIF
- 共通設定として10FPS、横幅を640pxにしています。細かい設定は適宜変えるといいと思います。
1. 通常 (341KB)
ffmpeg -i input.mov -r 10 -vf scale=640:-1 -f gif output.gif
-
-r 10
はFPSの指定 -
-vf scale=640:-1
はwidthを640pxでheightはアスペクト比率を維持する指定 - パッとみてノイズが目立ちますね。
2. グローバルパレットを使用して最適化 (838KB)
ffmpeg -i input.mov -filter_complex "[0:v] fps=10,scale=640:-1,split [a][b];[a] palettegen [p];[b][p] paletteuse" output-palette.gif
-
fps=10
はFPSの指定 -
scale=640:-1
はwidthを640pxでheightはアスペクト比率を維持する指定
3. 1フレーム毎にパレットを使用して最適化 (6.5MB)
ffmpeg -i input.mov -filter_complex "[0:v] fps=10,scale=w=640:h=-1,split [a][b];[a] palettegen=stats_mode=single [p];[b][p] paletteuse=new=1" output-multiple-palette.gif
- 動画全体で色々なシーンがあって色が多く使われている場合に綺麗
- 今回のソースの場合には2とほぼ変わらないクオリティ
4. グローバルパレットの最適化 + ディザリングをbayer (574KB)
ffmpeg -i input.mov -filter_complex "[0:v] fps=10,scale=640:-1,split [a][b];[a] palettegen [p];[b][p] paletteuse=dither=bayer" output-palette-bayer.gif
- 2よりノイズが目立っているが画像容量は下がっている (838KB → 574KB)
- オプションで
bayer_scale
も指定可能ですが今回は無指定のデフォルト値2
5. グローバルパレットの最適化 + ディザリングをheckbert (743KB)
ffmpeg -i input.mov -filter_complex "[0:v] fps=10,scale=640:-1,split [a][b];[a] palettegen [p];[b][p] paletteuse=dither=heckbert" output-palette-hackbert.gif
- 2とほぼ同じ見た目で画像容量が下がっている (838KB → 743KB)
6. グローバルパレットの最適化 + ディザリングをfloyd_steinberg (697KB)
ffmpeg -i input.mov -filter_complex "[0:v] fps=10,scale=640:-1,split [a][b];[a] palettegen [p];[b][p] paletteuse=dither=floyd_steinberg" output-palette-floyd_steinberg.gif
- 2とほぼ同じ見た目で画像容量が下がっている (838KB → 697KB)
7. グローバルパレットの最適化 + ディザリングをsierra2 (557KB)
ffmpeg -i input.mov -filter_complex "[0:v] fps=10,scale=640:-1,split [a][b];[a] palettegen [p];[b][p] paletteuse=dither=sierra2" output-palette-sierra2.gif
- 2とほぼ同じ見た目で画像容量が下がっている (838KB → 557KB)
8. グローバルパレットの最適化 + ディザリングなし (448KB)
ffmpeg -i input.mov -filter_complex "[0:v] fps=10,scale=640:-1,split [a][b];[a] palettegen [p];[b][p] paletteuse=dither=none" output-palette-none.gif
- 2とほぼ同じ見た目で画像容量が下がっている (838KB → 448KB)
結果
多少の綺麗さを犠牲にして軽量なGIFを作りたい
[1. 通常](1. 通常)
ffmpeg -i input.mov -r 10 -vf scale=640:-1 -f gif output.gif
- パレットを使用しない方が容量は削減できました。ただ個人的には結構ノイズが気になります。
手軽に綺麗なGIFを作りたい
ffmpeg -i input.mov -filter_complex "[0:v] fps=10,scale=640:-1,split [a][b];[a] palettegen [p];[b][p] paletteuse" output-palette.gif
画像容量は無視してとにかく綺麗なGIFを作りたい
[3. 1フレーム毎にパレットを使用して最適化](3. 1フレーム毎にパレットを使用して最適化)
ffmpeg -i input.mov -filter_complex "[0:v] fps=10,scale=w=640:h=-1,split [a][b];[a] palettegen=stats_mode=single [p];[b][p] paletteuse=new=1" output-multiple-palette.gif
- 画像容量が6.5MBと特出して大きくなりましたが、一つのグローバルパレットだけの最適化だと足りない場合があるのでいろんなシーン(色)がある場合には有用になるかもしれません。
画像容量と綺麗さを両立したGIFを作りたい
2, 4〜8 を出力し目視確認
形式 | 画像容量 | 主観的綺麗さ |
---|---|---|
1. 通常 | 341KB | × |
2. グローバルパレットを使用して最適化 | 838KB | ○ |
3. 1フレーム毎にパレットを使用して最適化 | 6.5MB | ○ |
4. グローバルパレットの最適化 + ディザリングをbayer | 574KB | △ |
5. グローバルパレットの最適化 + ディザリングをheckbert | 743KB | ○ |
6. グローバルパレットの最適化 + ディザリングをfloyd_steinberg | 697KB | ○ |
7. グローバルパレットの最適化 + ディザリングをsierra2 | 557KB | ○ |
8. グローバルパレットの最適化 + ディザリングなし | 448KB | ○ |
-
High quality GIF with FFmpegによると
floyd_steinberg
が最も人気があるディザリングアルゴリズムのようです。またsierra2_4a
(デフォルト設定なので2が該当する) はsierra2
の改良版でより処理が早くより小さい画像容量になるらしいのですが、今回の場合はsierra2
の方が画像容量が小さいという結果になりました。やはりソースによって結果はかわりそうなので、突き詰めたい場合は目視確認するしかなさそうです。 - 綺麗さは1, 4以外はほぼ同じで十分綺麗です。画像容量は8. グローバルパレットの最適化 + ディザリングなしが一番小さく綺麗さも問題ないという結果になりましたが、おそらく今回のソースにとっては たまたまディザリングなしが最適だった だけかと思われます。
ffmpeg以外のツールも使う
- gifを一枚ずつImageMagickで最適化してからgifアニメーションに戻す方法も考えられます。ただ How do I convert a video to GIF using ffmpeg, with reasonable quality? の回答で比較されていましたが、ffmpegのパレットを使った方が軽量化されていたのでどっちが最適かは一概に言えないのかもしれません。