2009年08月14日

B-Splineフィルタが軽い理由

真・東方縦画面化ツールに搭載したフィルタの中で、(3次の)B-Splineフィルタがもっとも軽くなっています。

しかし、実際に3次のB-Splineとバイキュービックを書いたことがある人なら分かると思いますが、この2つは各項にかかる係数が違うだけで、それ以外は全く同じ式です。では、なぜB-Splineフィルタが軽いのか…というのが今回の話。

各フィルタの補間関数をグラフ化すると次のようになります。

plot

Bi-Cubic1はシャープネス0、Bi-Cubic2はシャープネス1に設定したときのバイキュービックフィルタです。(シャープネスの値は一般的なものではなく、私が実用的と判断した範囲に設定した値です)

この補間関数は、フィルタのインパルス応答に相当し、入力信号が出力にどのように影響するかを表しています。例えばバイキュービックフィルタでは、入力画素値と同じ位置では1.0(元の値と全く同じ)で、そこから離れるにしたがって影響力が減っていき、ちょうど1画素分離れた位置では影響が0になります。更にそこから2画素分離れた位置まではマイナスの影響をします。

※ここでの説明は全て1次元ですが、今回使用しているフィルタは全て「分離型フィルタ」というもので、2次元のフィルタをX方向とY方向の1次元フィルタに分けることができます。このようなフィルタでは、X方向に1次元フィルタをかけた後に、Y方向に1次元フィルタをかけることで、2次元のフィルタと同じ結果を得ることができます。

ただし、この概念は入力信号中心の考え方ですが、実際のプログラムでは出力信号中心の考え方が必要になります。そのためには、「畳み込み」という計算を行います。

畳み込みは、フィルタ関数の定義域の全ての値を取得し、フィルタ関数を掛けて足し合わせるという処理になります。今回のフィルタは全て定義域が-2〜2で幅が4なので、入力画像から4つのピクセルをサンプリングすることになります。連続する4点をサンプリングし、補間関数のちょうど1ずつ離れた位置の値を掛けて足し合わせます。

さて、この4つの曲線のうち、B-Splineだけがマイナスの領域がないことが分かると思います。B-Splineの軽さの秘密は、ここにあります。

GPUには基本機能としてバイリニアフィルタが搭載されています。バイリニアフィルタはX方向とY方向にそれぞれ2点ずつ合計4つのピクセルを、距離に応じた比例配分で混合します。比例配分は、p1*a+p2*(1.0-a) のような計算で、必ず係数の合計が1.0になります。この処理はよく使うので、テクスチャユニットに組み込まれていて、非常に少ないコストで利用することができます。(ちなみにバイリニアフィルタも分離型フィルタ)

畳み込み処理で2つのピクセルに注目したとき、例えばそれぞれに掛ける係数が0.1と0.4だったとすると、次のように変形できます。

p1*0.1+p2*0.4

= (p1*0.2+p2*0.8)*0.5

= (p1*0.2+p2*(1.0-0.2))*0.5

= (p1*a+p2*(1.0-a))*0.5 [a=0.2]

これはバイリニアフィルタでサンプリングした結果に、係数の合計値を掛ければ畳み込み処理の2点分を処理できることを表します。

現在のGPUは、計算は速いがテクスチャの読み込みは遅いという性質があります。そのため、多少計算が複雑になっても、テクスチャ読み込み回数を減らした方が速くなります。B-Splineフィルタはこのテクニックを使って、16点フィルタを4回のテクスチャフェッチで処理しています。

ただし、バイリニアフィルタの係数は0.0〜1.0に限定されるので、負の係数が混ざっているとこの方法は使えません。

3次のB-SplineフィルタのX/Yフィルタの符号を表にすると次のようになります。

+ / +

+ / +

+ / +

+ / +

+ / +

+ / +

+ / +

+ / +

+ / +

+ / +

+ / +

+ / +

+ / +

+ / +

+ / +

+ / +

全ての領域で符号が同じなので、この手法が使えることが分かると思います。

一方、バイキュービックフィルタやLanczos2は次のようになります。

- / -

+ / -

+ / -

- / -

- / +

+ / +

+ / +

- / +

- / +

+ / +

+ / +

- / +

- / -

+ / -

+ / -

- / -

正負の符号が混ざっているために、この手法が使えません。

2点に掛ける係数がどちらも負の値の場合は、係数の符号を反転してバイリニアフィルタで読み込み、その結果を再び反転することで同じ結果が得られます。四隅を省略して12点補間とした簡易版バイキュービックフィルタではこの方法を使い、符号が揃っている中央の4点、上下左右の2点ずつをそれぞれバイリニアフィルタで読み込み、計5回のテクスチャフェッチで処理しています。

簡易版バイキュービックフィルタがこの方法で軽量化に成功してしまったために、軽いフィルタが作れると思って実装したB-Splineとの差がほとんど無くなってしまいました。

ちなみに、修正版バイキュービックフィルタではこのような最適化は行っておらず、16回テクスチャを読み込んでいます。

ところで、上のグラフでBi-Cubic1とLanzos2がほとんど同じ曲線ですよね。もう少しパラメータを変えるともっと近くなります。出力画像を見てこの2つのフィルタの違いがよく分からないと思っていたのですが、グラフを描いてみて納得しました。

Lanzosを使うなら2ではなく3以上を使わないと意味がなさそうです。Lanczos3になると36点補間となり、一気に倍以上の処理量になるので、私のツールで採用する気はありません。(費用対効果が悪過ぎる)

Posted by 新坂 | Comment(0) | TrackBack(0) | プログラミング | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前: [必須入力]

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

この記事へのトラックバックURL

※ブログオーナーが承認したトラックバックのみ表示されます。

※言及リンクのないトラックバックは受信されません。


この記事へのトラックバック