ffmpeg コマンドラインツール入門 第3回

こんにちは、シニアリサーチャーの佐藤です。ffmpeg の記事はこれが最終回です。1回目の記事はこちら、2回目の記事はこちらです。

前回フィルタ処理の概略を説明したので、今回は具体的にどんなフィルタがあるのか、それらを組み合わせることでどんな処理が可能なのか、話したいと思います。では最後までお付き合いください。

動画フィルタ

主要フィルタ解説

前回説明した要領で異なるフィルタを組み合わせれば、様々な画像処理をコマンドラインから実行することが可能になる。主要な個々のフィルタについて詳しく解説したいところだが、あまりにもフィルタとオプションの数が多いので、いくつかのフィルタと主なオプションを紹介するに留める。公式ドキュメントにはフィルタが網羅されているので、詳しく知りたい場合はそちらを参照。

なお、(in:N/out:M) の表記は、必要な入力と出力の数を表している。また、

[in1][in2]filter[out]

とある場合、[in1] を入力動画1、[in2] を入力動画2と呼ぶことにする。入力が3つ以上になった場合も同様に入力動画3等と呼ぶ。サイズや座標の単位はピクセル。

キャンバスサイズ変更: pad (in:1/out:1)

動画を拡大するのではなく、パディングしてキャンバスを広げる。2つの動画を左右に並べて比較したいときなどに使用。よく使うオプションと定数は以下の通り。

# オプション名 説明 デフォルト
1 width / w 新しいキャンバスサイズ 入力動画と同じ
2 height / h 新しいキャンバスサイズ 入力動画と同じ
3 x 入力動画左上隅の新キャンバス内での位置 0
4 y 入力動画左上隅の新キャンバス内での位置 0
定数名 説明
in_w / iw 入力動画サイズ
in_h / ih 入力動画サイズ

オーバーレイ: overlay (in:2/out:1)

入力動画1の上に入力動画2を重ね合わせる。比較動画作成や、動画にロゴを入れる際に使用。

# オプション名 説明 デフォルト
1 x 入力動画1のこの位置に入力動画2の左上隅がくるように重ねる 0
2 y 入力動画1のこの位置に入力動画2の左上隅がくるように重ねる 0
定数名 説明
main_w / W 入力動画1のサイズ
main_h / H 入力動画1のサイズ
overlay_w / w 入力動画2のサイズ
overlay_h / h 入力動画2のサイズ

サイズ変更: scale (in:1/out:1)

動画のサイズを変更する。

# オプション名 説明 デフォルト
1 width / w 変更後のサイズ 入力動画と同じ
2 height / h 変更後のサイズ 入力動画と同じ
定数名 説明
in_w / iw 入力動画サイズ
in_h / ih 入力動画サイズ

このフィルタは、画素のアスペクト比を変更することで、入力のアスペクト比を維持しようとする。入力と異なるアスペクト比を出力に設定する場合は、これに注意しなければならない。アスペクト比の維持を避けるには、

scale=w=1920:h=1080:force_original_aspect_ratio=disable

のように force_original_aspect_ratio=disable を設定する。

クロップ: crop (in:1/out:1)

フレームをクロップする。例でも説明したが、オプションを絞って再掲する。

# オプション名 説明 デフォルト
1 w / out_w クロップサイズ 入力動画と同じ
2 h / out_h クロップサイズ 入力動画と同じ
3 x クロップ枠左上隅の位置 クロップ枠が入力動画の中心に来るように調整
4 y クロップ枠左上隅の位置 クロップ枠が入力動画の中心に来るように調整
定数名 説明
in_w / iw 入力動画サイズ
in_h / ih 入力動画サイズ
out_w / ow クロップサイズ
out_h / oh クロップサイズ
n フレームカウント
t タイムスタンプ(秒)

回転: rotate (in:1/out:1)

動画を時計回りに回転させる。

# オプション名 説明 デフォルト
1 a / angle 回転角度(ラジアン) 0
2 out_w / ow 出力動画サイズ 入力動画と同じ
3 out_h / oh 出力動画サイズ 入力動画と同じ
定数名 説明
in_w / iw 入力動画サイズ
in_h / ih 入力動画サイズ
n フレームカウント
t タイムスタンプ(秒)

このフィルタは内容だけを回すので、例えば90度回転させたい場合は、動画サイズも入れ替えないと意図した結果が得られないかもしれない。

上下反転: vflip (in:1/out:1)

動画の上下を反転させる。オプションはない。

左右反転: hflip (in:1/out:1)

動画の左右を反転させる。オプションはない。

転置: transpose (in:1/out:1)

動画を転置させる。もしくは、転置に加えて反転を行う。

# オプション名 説明 デフォルト
1 dir 転置方式の指定 0
定数名 説明
0 / 4 / cclock_flip 左上起点の転置
1 / 5 / clock 時計回りに90度回転
2 / 6 / cclock 半時計回りに90度回転
3 / 7 / clock_flip 右上起点の転置

ブレンド: blend (in:2/out:1)

これも例で簡単に説明したが、2つの入力動画をブレンドする。

# オプション名 説明 デフォルト
15 all_expr ブレンド方法の指定 -
定数名 説明
X 座標
Y 座標
W 入力動画サイズ
H 入力動画サイズ
TOP / A 入力動画1の画素値
BOTTOM / B 入力動画2の画素値
N フレームカウント
T タイムスタンプ(秒)

all_expr はオプション1ではないので、これを使う場合は blend=all_expr= とオプション名を指定して設定した方がよい。ブレンド方法は式として指定する。定数に座標やタイムスタンプがあるので、動画中の位置や時間によってブレンド方法を変化させることが可能。なお、他のオプションを使えばチャンネルごとにブレンド方法を変えることもできる。

画素値の変更: lut (in:1/out:1)

各チャンネルごとに画素値を変更する。

# オプション名 説明 デフォルト
1 c0 第1チャンネルの値 -
2 c1 第2チャンネルの値 -
3 c2 第3チャンネルの値 -
4 c3 第4チャンネルの値 -
8 a アルファ値 -
9 y Y の値 -
10 u U の値 -
11 v V の値 -
定数名 説明
w 入力動画サイズ
h 入力動画サイズ
val 当該チャンネルの画素値
maxval 当該チャンネルの最大値
minval 当該チャンネルの最小値

これも blend のように式を設定する。例えば

lut='min(2*val,maxval)'

とすれば、luma の値を2倍にすることができる。c0, c1, c2, c3y, u, v, a は好きな方を使えばよい。ただし、各色がどういう順番で並んでいるのかはピクセルフォーマットに依存していることに注意。

チャンネルの入れ替え: mergeplanes (in:1-4/out:1)

複数入力の Y, U, V チャンネルを自由に入れ替えて、出力を作る。

# オプション名 説明 デフォルト
1 mapping チャンネルの組み合わせ方 0
2 format 出力動画のピクセルフォーマット yuva444p

mapping の指定方法にはクセがあり、16進数で指定する。例えば出力が3チャンネル動画の場合、

0xaAbBcC

と6桁の数字を用いる。aA が出力の第1チャネルを、bB が出力の第2チャネルを、cC が出力の第3チャネルを指定しており、a, b, c はどの入力から持ってくるか、A, B, C は当該入力のどのチャンネルを使うかを表している。例えば、YUV420 PLANAR 動画の U, V チャンネルを入れ替えるには、

mergeplanes=0x000201:yuv420p

とする。Y を入力動画1から、UV を入力動画2から持ってきたい場合は、

mergeplanes=0x001112:yuv420p

でよい。この例について少し解説する。最初の2桁は 00 であり、これは入力動画1の第1チャンネルを表している。これを出力動画の第1チャンネルに設定することになるので、出力の Y には入力動画1の Y が使われる。次は 11 で、入力動画2の第2チャンネルを出力動画の第2チャンネルに設定するという意味である。つまり、出力の U には入力動画2の U が使われる。残る 12 については省略する。

4チャンネル動画の場合は、同じ要領で8桁の16進数を用いる。入力動画の数は、最大4つまで設定できる。出力のピクセルフォーマットに対応したサイズのデータを、それぞれのチャンネルごとに用意する必要がある、という点に注意。例えば、YUV420 PLANAR 動画の Y と U を単純に入れ替えるとエラーがでる。

万能フィルタ: geq (in:1/out:1)

1入力1出力のフィルタであれば、実はこの geq だけでほぼ全て解決する。ただし、重くて使いにくい。

# オプション名 説明 デフォルト
1 lum_expr / lum Y チャンネルの評価式 -
2 cb_expr / cb U チャンネルの評価式 -
3 cr_expr/ cr V チャンネルの評価式 -
4 alpha_expr / a アルファチャンネルの評価式 -
定数、関数名 説明
X 処理している画素の座標
Y 処理している画素の座標
W 動画サイズ
H 動画サイズ
N フレームカウント
T タイムスタンプ(秒)
p(x, y) 処理しているチャンネルの画素値
lum(x, y) Y チャンネルの画素値
cb(x, y) U チャンネルの画素値
cr(x, y) V チャンネルの画素値
alpha(x, y) アルファチャンネルの画素値

このフィルタ専用の特殊な関数がいくつか用意されている。周辺画素の値にも違うチャンネルの値にもアクセスできるので、かなり広範囲のフィルタを記述できる。例として何も変更しないフィルタを載せておく。

geq='p(X,Y)':'p(X,Y)':'p(X,Y)'

フレームの複製: split (in:1/out:N)

1つのフレームを任意の数複製することができる。

[in]split[out1][out2]...[outN];

という要領で使う。出力は何個あってもよい。

例えば完全に無意味な処理として、1つのフレームを2つに分割してそれをまたブレンドするというものを考える。

ffmpeg -i input.mp4 -vf "split[tmp1][tmp2]; [tmp1][tmp2]blend=all_expr='(A+B)/2'" output.mp4

ここで注意すべきなのは、途中で複数のタグを使うようなフィルタであっても、入力が1つであれば -vf オプションで対応できるという点である。

フレーム情報の取得: showinfo (in:1/out:1)

以下で紹介するフィルタは、フレームを解析するタイプのフィルタである。

まず showinfo であるが、これを作用させるとフレームごとの詳細情報を表示させることができる。フレームそのものには何も手を加えず、入力をそのまま出力に渡す。

SSIM の計算: ssim (in:2/out:1)

フレームごとの SSIM (Structual Similarity Metric) を計算し、入力動画1をそのまま出力に渡す。

# オプション名 説明 デフォルト
1 stats_file / f 出力ファイル名の指定 出力しない

出力ファイルを指定する代わりに - を設定して ssim=- とすると、標準出力に結果が出力される。

PSNR の計算: psnr (in:2/out:1)

フレームごとの MSE (Mean Squared Error) や PSNR (Peak Signal-to-Noise Ratio) を計算し、入力動画1をそのまま出力に渡す。基本的なオプションと使い方は ssim と同様。

Canny エッジ検出: edgedetect (in:1/out:1)

Canny のアルゴリズムでエッジ検出を行う。出力としては、エッジを最大値(白)とした2値画像が得られる。

# オプション名 説明 デフォルト
1 low 閾値(低)の設定 20/255
2 high 閾値(高)の設定 50/255

閾値の設定は [0,1] で行う仕様である。

その他

他にもできることの例を、フィルタ名だけ挙げておく

  • ノイズ除去: bm3d 等
  • 畳み込み: convolution
  • インターレース解除: yadif 等
  • 逆テレシネ: detelecine
  • グリッドの描画: drawgrid
  • 文字列の描画: drawtext
  • インターレース動画をフィールドに分解: field
  • ヒストグラムの計算: histogram
  • レンズ歪み補正: lenscorrection
  • メディアンフィルタ: median
  • ノイズ付加: noise
  • タイムスタンプの書き換え: setpts

各フィルタの解説はここに、オプションの指定等で使用可能な数学関数や定数の一覧はここにあるので、適宜参照のこと。

実際にフィルタを書く際、毎回エンコードしてそれを再生して確認するというのは面倒なので、ffplay というツールを使うのを勧める。これは ffmpeg のオプションが使える動画プレイヤーで、フィルタを作用させた結果が再生される。例えば

ffplay -i input.mp4 -vf hflip

とすれば、左右反転した動画が再生される。フィルタ効果の確認がすぐ行えるので、効率が上がる。ただし、-filter_complex を用いた複数入力フィルタの場合は使えない。

また、-filter_script:v-filter_complex_script オプションを使えば、別ファイルに記述しておいたフィルタを呼び出せるので、複雑なフィルタを再利用する場合に有効である。

コマンド例

以下では入力、出力へのオプションは指定していない場合が多いが、必要であれば適宜指定すればよい。

左右比較動画の作成

ffmpeg -i input1.mp4 -i input2.mp4 -filter_complex "[0:v]pad=2*iw[tmp]; [tmp][1:v]overlay=w, scale=iw/2:ih/2" output.mp4

入力動画1の右側に空白を挿入して横幅を2倍にした後、その部分に入力動画2をオーバーレイしている。最後に、見やすさのために全体を1/2に縮小。なお、タイムスタンプや音声は入力動画1のものが使用される。

動画にロゴを入れる

動画の右下にロゴ画像を追加したいことがある。アルファチャンネルが設定されている画像であれば、動画と同様に読み込んでオーバーレイするだけで実現できる。

ffmpeg -i input.mp4 -i logo.png -filter_complex "[1:v]scale=256:256[tmp]; [0:v][tmp]overlay=W-w:H-h" output.mp4

時計回りに90度回転させた動画を作る

ffmpeg -i input.mp4 -vf "transpose=clock" output.mp4

別の書き方もできる。

ffmpeg -i input.mp4 -vf "rotate=PI/2:ih:iw" output.mp4

動画の幅と高さを入れ替えているのに注意。

動画をぐるぐる回転させる

ffmpeg -i input.mp4 -vf "rotate=PI/30*n" output.mp4

こういう無意味な遊びもできる。回転の速度は PI/30*n の部分で調整化。何を言っているのか意味不明な可能性もあるので、結果動画を貼っておく。

鏡面エフェクトをかける

簡単なエフェクトをかけることもできる。

ffmpeg -i input.mp4 -vf "split[tmp1][tmp2]; [tmp2]vflip[tmp3]; [tmp1][tmp3]blend=all_expr='if(lt(Y,H/2),A,B)'" output.mp4

blend で使っている ltif

lt(a, b) = 1 (a < b), 0 (a >= b)
if(a, b, c) = b (a != 0), c (a == 0)

という関数である。つまりこのフィルタは、入力を分割して片方に上下反転をかけ、上反面は元のフレームを、下半面は反転したフレームを使うように合成するものである。

Star Wars 風画面切り替え

Star Wars 風というのは、ワイプでの画面切り替えのことである。

ffmpeg -i input1.mp4 -i input2.mp4 -filter_complex "blend=all_expr='if(lt(X,clip(W*(T-1.5)/1,0,W)),A,B)'" output.mp4

clip(x,a,b) という関数は、min(max(x,a),b) に等しい。つまり xa 以上 b 以下の値に抑える関数である。1.5秒から2.5秒にかけてワイプで画面を切り替えていることが分かる。

フレーム情報の取得

フレームのタイムスタンプ等の詳細情報を得ることができる。

ffmpeg -i input.mp4 -vf showinfo -f null tmp

最後の tmp だが、ffmpeg のインタフェース上出力が必要なので形式上付けているだけである。特に何もファイルは生成されないので、適当な名前を書いておけばよい。

SSIM と PSNR を同時に計算する

上の例と同様、出力フレームを捨てるタイプの処理である。

ffmpeg -i input1.mp4 -i input2.mp4 -vf "ssim=ssim.txt; [0:v][1:v]psnr=psnr.txt" -f null tmp

Canny エッジを入力動画に重ねて表示する

入力動画の V チャンネルを用いて Canny エッジ情報を可視化する。

ffmpeg -i input.mp4 -vf "split[tmp1][tmp2]; [tmp2]edgedetect, lut='min(val+128,255)'[tmp3]; [tmp1][tmp3]mergeplanes=0x001010:yuv444p, lut=u=128" -pix_fmt yuv420p output.mp4

mergeplanes の出力が yuv444p なのは、エッジ画像のサイズが入力動画と同じであり、それを U, V チャンネルに設定しているからである。yuv444p のままだと扱いづらいので、最後に yuv420p に変換した。若干複雑だと思うので、最後のピクセルフォーマット変換部分を除いた処理フローを載せておく。