ffmpegでとにかく綺麗なGIFを作りたい

何かを説明するときにGIFってわかりやすいですよね。

![output-palette-sierra2.gif](https://qiita-image-store.s3.amazonaws.com/0/16700/c316d45d-056c-08de-507c-03ca7cec4c20.gif)

友達が[meeemo](https://meeemo.space/)というサービスを作りそれのプレビュー動画を撮るお手伝いをしたのですが、より綺麗な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は[サポートしているフォーマット](https://www.ffmpeg.org/general.html#File-Formats)がかなり多いので、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はアスペクト比率を維持する指定
- パッとみてノイズが目立ちますね。

![output.gif](https://qiita-image-store.s3.amazonaws.com/0/16700/d52ef636-e7ed-2552-3e8c-32ffba913efe.gif)

### 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はアスペクト比率を維持する指定

![output-palette.gif](https://qiita-image-store.s3.amazonaws.com/0/16700/f4bd56d9-a838-5505-687f-d05bd70f3acc.gif)

### 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とほぼ変わらないクオリティ

![output-multiple-palette.gif](https://qiita-image-store.s3.amazonaws.com/0/16700/246c5a56-d1d9-e16d-ce52-ec5aec3ae4e9.gif)

### 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](https://qiita.com/yusuga/items/ba7b5c2cac3f2928f040#2-グローバルパレットを使用して最適化-838kb)よりノイズが目立っているが画像容量は下がっている (838KB → 574KB)
- オプションで `bayer_scale` も指定可能ですが今回は無指定のデフォルト値2

![output-palette-bayer.gif](https://qiita-image-store.s3.amazonaws.com/0/16700/911c668b-860b-2bac-02c0-0d91afcdfd7b.gif)

### 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](https://qiita.com/yusuga/items/ba7b5c2cac3f2928f040#2-グローバルパレットを使用して最適化-838kb)とほぼ同じ見た目で画像容量が下がっている (838KB → 743KB)

![output-palette-hackbert.gif](https://qiita-image-store.s3.amazonaws.com/0/16700/e8fa762b-a551-0fdd-fd46-6274f874f5c9.gif)

### 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](https://qiita.com/yusuga/items/ba7b5c2cac3f2928f040#2-グローバルパレットを使用して最適化-838kb)とほぼ同じ見た目で画像容量が下がっている (838KB → 697KB)

![output-palette-floyd_steinberg.gif](https://qiita-image-store.s3.amazonaws.com/0/16700/59b4270d-6ba2-f688-b3f9-1ef6911eaf15.gif)

### 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](https://qiita.com/yusuga/items/ba7b5c2cac3f2928f040#2-グローバルパレットを使用して最適化-838kb)とほぼ同じ見た目で画像容量が下がっている (838KB → 557KB)

![output-palette-sierra2.gif](https://qiita-image-store.s3.amazonaws.com/0/16700/55f0ac58-d375-0d97-54f7-ae14d39b3c78.gif)

### 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](https://qiita.com/yusuga/items/ba7b5c2cac3f2928f040#2-グローバルパレットを使用して最適化-838kb)とほぼ同じ見た目で画像容量が下がっている (838KB → 448KB)

![output-palette-none.gif](https://qiita-image-store.s3.amazonaws.com/0/16700/61ddc196-e6fb-e586-eb13-1219bd6e0248.gif)

# 結果

## 多少の綺麗さを犠牲にして軽量なGIFを作りたい

**[1. 通常](1. 通常)**

> ffmpeg -i input.mov -r 10 -vf scale=640:-1 -f gif output.gif

- パレットを使用しない方が容量は削減できました。ただ個人的には結構ノイズが気になります。

## 手軽に綺麗なGIFを作りたい

**[2. グローバルパレットを使用して最適化](https://qiita.com/yusuga/items/ba7b5c2cac3f2928f040#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

## 画像容量は無視してとにかく綺麗な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. 通常](https://qiita.com/yusuga/items/ba7b5c2cac3f2928f040#1-通常-341kb) | 341KB | × |
| [2. グローバルパレットを使用して最適化](https://qiita.com/yusuga/items/ba7b5c2cac3f2928f040#2-グローバルパレットを使用して最適化-838kb) | 838KB | ○ |
| [3. 1フレーム毎にパレットを使用して最適化](https://qiita.com/yusuga/items/ba7b5c2cac3f2928f040#3-1フレーム毎にパレットを使用して最適化-65mb) | 6.5MB | ○ |
| [4. グローバルパレットの最適化 + ディザリングをbayer](https://qiita.com/yusuga/items/ba7b5c2cac3f2928f040#4-グローバルパレットの最適化--ディザリングをbayer-574kb) | 574KB | △ |
| [5. グローバルパレットの最適化 + ディザリングをheckbert](https://qiita.com/yusuga/items/ba7b5c2cac3f2928f040#5-グローバルパレットの最適化--ディザリングをheckbert-743kb) | 743KB | ○ |
| [6. グローバルパレットの最適化 + ディザリングをfloyd_steinberg](https://qiita.com/yusuga/items/ba7b5c2cac3f2928f040#6-グローバルパレットの最適化--ディザリングをfloyd_steinberg-697kb) | 697KB | ○ |
| [7. グローバルパレットの最適化 + ディザリングをsierra2](https://qiita.com/yusuga/items/ba7b5c2cac3f2928f040#7-グローバルパレットの最適化--ディザリングをsierra2-557kb) | 557KB | ○ |
| [8. グローバルパレットの最適化 + ディザリングなし](https://qiita.com/yusuga/items/ba7b5c2cac3f2928f040#8-グローバルパレットの最適化--ディザリングなし-448kb) | **448KB** | ○ |

- [High quality GIF with FFmpeg](http://blog.pkh.me/p/21-high-quality-gif-with-ffmpeg.html)によると `floyd_steinberg` が最も人気があるディザリングアルゴリズムのようです。また `sierra2_4a` (デフォルト設定なので2が該当する) は `sierra2` の改良版でより処理が早くより小さい画像容量になるらしいのですが、今回の場合は `sierra2` の方が画像容量が小さいという結果になりました。やはりソースによって結果はかわりそうなので、突き詰めたい場合は目視確認するしかなさそうです。
- 綺麗さは1, 4以外はほぼ同じで十分綺麗です。画像容量は[8. グローバルパレットの最適化 + ディザリングなし](https://qiita.com/yusuga/items/ba7b5c2cac3f2928f040#8-グローバルパレットの最適化--ディザリングなし-448kb)が一番小さく綺麗さも問題ないという結果になりましたが、おそらく今回のソースにとっては **たまたまディザリングなしが最適だった** だけかと思われます。

## ffmpeg以外のツールも使う

- gifを一枚ずつImageMagickで最適化してからgifアニメーションに戻す方法も考えられます。ただ [How do I convert a video to GIF using ffmpeg, with reasonable quality?](https://superuser.com/a/556031) の回答で比較されていましたが、ffmpegのパレットを使った方が軽量化されていたのでどっちが最適かは一概に言えないのかもしれません。

# 参照

- [How to make GIFs with FFMPEG](https://engineering.giphy.com/how-to-make-gifs-with-ffmpeg/)
- [High quality GIF with FFmpeg](http://blog.pkh.me/p/21-high-quality-gif-with-ffmpeg.html)
- [FFmpeg Filters Documentation - 10.125 paletteuse](https://ffmpeg.org/ffmpeg-filters.html#paletteuse)
- [ディザ](https://ja.wikipedia.org/wiki/%E3%83%87%E3%82%A3%E3%82%B6)