このページでは、Inkscapeのフィルタの機能をより深く理解するために、特に畳み込み演算を使ったフィルタの性質について、いろいろ調べてまとめていきたいと思います。
(参考:フィルタとは何か)
(2023.06.01更新)
SVGのフィルタと畳み込みの関係
SVG形式の中でフィルタ要素(<filter>)を記述するときに、そのフィルタ要素の部品として使える各種フィルタプリミティブ要素の中に、feConvolveMatrixという要素があります。
この要素は、カーネルと呼ばれる行列をパラメータとして指定すると、そのカーネルを使ってフィルタ適用対象のオブジェクト全体に畳み込み演算を行って、その結果を出力する(典型的には画面に表示する)というものです。
(参考:フィルタプリミティブ要素:feConvolveMatrix)
畳み込み演算とはどんな演算か?
畳み込み演算そのものは数学上の一般的な式として表されていて、どんな式なのか簡単な言葉でむりやり理解しようとすれば、関数fを計算することに替えて、関数fの形を、変換式を定義した関数gを一定区間(例えば-∞から∞)にわたって掛けて足す(積分する)ことで、関数f´の形に変換してから、関数f´を計算するというものだということになるかと思います。
数式で書けば、こんな感じです。
これをfeConvolveMatrixの機能に沿って言い直すと、オブジェクトの画像の各ピクセルとカーネルの行列の各要素を順番に掛け合わせ、それを足し合わせる(そうするとフィルタ適用後のピクセル値が1つ求まる)ことを画像全体に渡って繰り返すものだということになります。とても単純なものです。(実際は画像の端っこのところの辻褄合わせや、繰り返し方にも細かい違いがありますが、おおざっぱに理解すればこうなるはず)
数式を使って、もう少し具体的に理解します。
変換元の画像は次のようにピクセル値が並んでいるとします。
カーネルの行列も次のようになっているとします。典型的なカーネルである3行3列の場合です。
そして、このカーネルを使ったフィルタを適用することにより得られる変換結果の画像が、次のようなピクセルの並びになるとします。
このとき、畳み込み演算によって、変換後のピクセル1つ q i,j をどうやって計算するかというと、次のような計算式になるそうです。
計算したいq i,jを囲う正方形状のピクセル群と行列の要素とを掛けて足しています。
なお、カーネルの行列は画像のピクセルの並びとは逆に、右下から計算に用いるところがポイントです。
演算手順はわかったけれど・・・
しかし、この単純に思える演算(わずかに3行3列だけ!)がいったいどんな性質を備えているのか(そして、なぜ右下から計算するのか?)を理解しておかないと、実際に具体的な値が並んだ行列をカーネルとして使ったときにどんな変換結果が得られるのかが想像できないということがわかってきました。
そこで今さらですが、付け焼刃でもいいので、畳み込み演算とそれによって変換される画像の性質について、少しでも理解できるよう調べたことをまとめていってみようと思います。
畳み込みにはどんな意味があるのか?
いろいろな解説コンテンツを読んでみて、それらを頭の中でマージした結果、こんな風に理解すればいいんじゃないかなというイメージはまとまってきました。もちろんいろんなことを端折った説明なので、正確な理解ではないことは承知の上で。
次のように、一連のデータを入力すると、何かしら目に見えない一定の法則みたいなものがあって、その法則にのっとって変化したデータが出力されるような状況というのはよくあります。

例えば、ある一連の音を鳴らす(入力A)と、部屋の中で壁の形にしたがって反響することで、耳にはちょっと違った音で聞こえる(出力B)というケースや、ある画像の一連のピクセル値を与える(入力A)と、それを画面に表示する際の何らかのゆがみの影響で、画面にはちょっと違ったピクセル列で表示される(出力B)といったケースです。
その「一定の法則」をあらかじめ調べておいて、任意の入力Aがあったときの出力Bの形を推定できればいろんな応用が効きそうです。
ここでは、画像のピクセル値の例で考えてみます。ある位置(x=a)に入力されたピクセル値p(a)が、出力時にはどんなピクセル値qになるかを出力先の位置(x)ごとに調べます。

そして、調べた結果として、次のように出力する位置(Δxだけ離れた位置)に応じて異なる係数(k)を掛けた関係になっていることがわかったとします。

この係数群を次のように羅列することで、入力値と出力値の関係をモデル化します。カーネルに相当するものです。

そして、このkを使って、出力全体の形を推定するには、今度は反対に、ある位置(b)での出力値を計算して推定値にします。どうやるかというと、同じ位置bの入力値とその周囲の入力値のそれぞれに対して、各々の位置xに対応するk(x)を掛け、それらを足し合わせます。

数式で書くと次のようになります。

この計算の様子が「kとpとの掛け算と足し算を1つのq(b)に向かって畳み込むように行っている」ように見えるので、「畳み込み」と呼ばれるようになったんじゃないかという説明がどこかのサイトでありました。
そして、このq(b)の計算式と、k(Δx)の並びとを比べてみると、k(Δx)の並びとは逆に計算していることが分かります。これがカーネルの行列を反転して計算することに相当します。
別の言い方をすると、カーネルの並びは入力側の1つのデータを中心に複数の出力値ごとの係数を並べたものであるのに対して、畳み込みの演算は出力側の1つのデータをターゲットとして複数の入力値にわたってカーネルの値を係数として掛けるという計算になるので、まるでカーネルの並びを反転させて使っているようにみえるということなのだと思います。
画像フィルタのカーネルは畳み込みのためのもの?
画像フィルタのカーネルは、畳み込み演算に用いられているというべきなのか?という点が気になるところです。
カーネルの行列が反転されてから画像に適用されている、という事実からすると、このカーネルは畳み込みカーネルであると理解すべきなのかもしれません。
でも、ある位置のピクセル値と隣に位置するピクセル値との間に画像の内容とは無関係な何らかの法則があるとは思えないので、ある位置のピクセル値を求めるために周囲のピクセル値を畳み込んでいるのだという説明はしっくりきません。
どちらかというと、カーネルを相関カーネルとして用いて、あるピクセル値とその周囲のピクセル値との並びと、相関カーネルの行列値の並びとをマッチングして、並びの相関の大きさに応じた色に変換しているのだ、と理解したほうがフィルタの役割や使用目的に沿っているように思えます。
これは全く想像ですが、相関カーネルの行列値とピクセル値とを掛けて足し合わせるという演算をひとことで表すときに、畳み込み演算と似ている(単に行列を反転させているだけ)ことから、「畳み込み」と呼ぶことにしただけなんじゃないか?と思います。でも「畳み込み」と呼ぶ以上は、カーネルの行列も反転させなければ一般的な定義と整合しなくなってしまうので、ほんとうは必要ないのだけれど「画像フィルタのカーネルの行列は反転させて用いることにしよう」となったのではないかと。
ほんとうは畳み込みを行うためのカーネルじゃないのだけれど、「畳み込み」と呼んだほうが座りがいいので、「畳み込み」と呼ぶと同時に呼び名に整合するように行列を反転することとしたのではないかと。
勝手な想像ですが、こう考えたら、本来は畳み込みのためのカーネルではないものを畳み込みのためのカーネルかのように扱う理由が腑に落ちたような気がします。
現実には、カーネルの行列を掛けて足し合わせることそれ自体を畳み込み演算だと理解してしまっても(そういう解説をしているコンテンツや本もたくさんあります)本質的な違いはないのでしょうし、多くの代表的なカーネルの例でも対称な行列を用いているので、畳み込みカーネルと理解しようが相関カーネルと理解しようが結果は同じなのだけれど、フィルタのパラメータとして指定するカーネルの行列を(Inkscapeもそうであるように)あらかじめ反転させた状態で指定しないといけないという妙な制約のウラは分かっていた方が良いかと思います。
また、相関カーネルとはいっても、典型的なカーネルが3行3列という小さなもので、統計的に意味のある演算にはなってなさそうなので、相関の大きさを求めていると理解するよりも、単に変換対象のピクセルとその周囲のピクセルについて重み付き和を取ることで、小さい範囲での色の変化を強調したり緩和したりする変換だ(特に重みにマイナス値を使うことで「強調」が可能になっている)と捉えたほうが直感的に分かりやすいように思います。3行3列のカーネルでそこそこのフィルタが実現できるとなった時点(それがどういう歴史なのか調べてませんが)で、その計算手順が畳み込み演算なのか相関を求めているのか重み付き平均なのかは単なる呼び名の問題になってしまったように感じられます。(余計なニュアンスが加わって分かりにくくならないように新しい名前を採用したほうがいい面もあるかもしれないなと思ったり)