2013年7月21日日曜日

MotionMask

動画エンコードに手を出したばかりの頃「動き適応」(motion adaptive)という言葉によく頭を悩ませたものだった。

当時はAvisynth覚えたてでプラグイン漁りに夢中になっていたわけだが、しょっちゅう「動き適応ノイズ除去」とか「動き適応デインタレース」などと言う言葉に出くわすのである。
それまで筆者は「動き適応」なんていう珍妙な日本語は聞いたことがなかったので、一体どういう処理をしており、どういうメリットが有るのかもさっぱりわからなかった。
「動き適応」でググってみれば色々とそれらしき解説もあるにはあるが、読んでみても理解できないのである。

それでも長いこと続けていれば次第に知識や経験は増えていくわけで、かつては理解不能だったこともわかるようになっていくわけだが、「動き適応」を理解し処理内容をイメージできるようになったのは、プログラミングに手を出してデジタル映像の処理自体を稚拙ながらも自分で書くようになってからだった。

さて「動き適応」であるが、これは映像を動いている部分と動いていない部分に分け、それぞれに別の処理を行う(もしくは一方だけに処理を行う)ことを意味する。
そして、映像における動きとは、前か後ろ(もしくは両方)のフレームとの差分のことを意味する。
より具体的に言えば、あるフレームとその一つ前のフレームで引き算を行い、同一座標における二つのサンプルの差の絶対値を求める。そして、その値が閾値を超えていれば動いているとみなし、それ以外は静止しているとみなす。

この処理をavsで書くとすれば次のようになる。
threshold = 10 # 動き判定用閾値 前フレームとの差が10未満のサンプルは静止しているとみなす

src = something # ソースクリップ
prev = src.Loop(2, 0, 0) # ソースを先頭フレームだけ重複させ1フレームずらしたもの
diff = src.mt_lutxy(prev, "x y - abs", chroma="process") # srcとprevの各サンプルの差の絶対値を求める
mask = diff.mt_binarize(threshold, chroma="process") # 閾値で二値化(閾値未満は0、以上は255)

StackVertical(StackHorizontal(src.Subtitle("src"), prev.Subtitle("prev")),
\             StackHorizontal(diff.Subtitle("diff"), mask.Subtitle("mask")))
上記スクリプトのmaskクリップを一般にモーションマスクと呼ぶ(ちなみにmt_motion()は、上記の処理を行うための専用関数である)。

さて、このモーションマスクを使って「動き適応ぼかし」を行なってみよう。
動き適応ぼかしは静止している部分には手を加えず動きのある部分だけをぼかすことで、あまり劣化を目立たせずにエンコード時の圧縮率を稼ぐという、Xvid全盛期頃にはよく行われた処理である。
src = something
blur = src.Blur(1.0).Blur(1.0)
mask = src.mt_motion(10, chroma="process")
last = src.mt_merge(blur, mask)

StackVertical(StackHorizontal(src.Subtitle("src"), blur.Subtitle("blur")),
\             StackHorizontal(mask.Subtitle("mask"), last.Subtitle("last")))

モーションマスクによって、動きのない部分はsrcクリップ、動きのある部分はblurクリップになるように合成されているのがわかると思う。

Avisynthには、masktools2のmt_motion()の他にmvtools2のMMask()もある、
こちらはmt_motion()のような単純なモーションマスクではなくモーションベクトルの長さをマスクとして利用するもので、アルゴリズム的には優れているが計算量は半端無く跳ね上がるし、実装も難しい。
また、素材や行いたい処理によっては単純なフレーム間差分のほうが良い場合もあるので、使い分けは結構重要である。

0 件のコメント:

コメントを投稿