PHP imagick の convolveImage は画像の畳み込み処理するメソッドです。
この convolveImage は、imagick 3.3.0 => 3.4.0 を境に受け取る引数の形式が変わりましたが、公式マニュアルが分かりにくいのでメモを作りました。
convolveImage の機能
画像に対して畳み込みの処理ができます。
例えば、以下のような畳み込みカーネルを与えるとブラー効果を得られます。
\frac{1}{16}
\begin{vmatrix}
1 & 2 & 1 \\
2 & 4 & 2 \\
1 & 2 & 1 \\
\end{vmatrix}
元画像 | convoveImage 適用後 |
---|---|
この 3x3 で 9個のカーネル係数を引数に渡す方法が imagick や ImageMagick のバージョンによって異なります。
バージョンによる差異
imagick 3.3.0 以前
カーネル係数を一次元の配列で渡します。個数は N^2 に限定されます。以下の例だと 3^2 = 3x3 で 9 個の要素を持つ配列です。
$kernel = [1/16, 2/16, 1/16,
2/16, 4/16, 2/16,
1/16, 2/16, 1/16];
$image->convolveImage($kernel);
convolveImage は渡された配列を正方行列にして処理します。
今回は 9個の配列を渡したので、3x3 行列として処理します。
25個を渡すと 5x5 です。つまり縦長や横長のカーネルは使えません。
imagick 3.4.0 以降
2次元の配列を ImageKernel オブジェクトで渡します。
$matrix = [ [1/16, 2/16, 1/16],
[2/16, 4/16, 2/16],
[1/16, 2/16, 1/16] ];
$kernel = \ImagickKernel::fromMatrix($matrix);
$image->convolveImage($kernel);
ImageMagick 6
ImageMagick 6 では imagick 3.4.0 以降だとしても imagick 3.3.0 以前の引数形式のままになります。
$kernel = [1/16, 2/16, 1/16,
2/16, 4/16, 2/16,
1/16, 2/16, 1/16];
$image->convolveImage($kernel);
バージョン分岐
新旧のバージョンに対応する場合、以下のように分岐すると良いでしょう。
$imVersion = \Imagick::getVersion();
if (($imVersion["versionNumber"] < 0x700) || (phpversion("imagick") < "3.4.0")) { // 古い方
$kernel = [1/16, 2/16, 1/16,
2/16, 4/16, 2/16,
1/16, 2/16, 1/16];
$image->convolveImage($kernel);
} else { // 新しい方
$matrix = [ [1/16, 2/16, 1/16],
[2/16, 4/16, 2/16],
[1/16, 2/16, 1/16] ];
$kernel = \ImagickKernel::fromMatrix($matrix);
$image->convolveImage($kernel);
}
バージョンを間違えた場合
以下のようなエラーメッセージが出るはずです。
Imagick::convolveImage() expects parameter 1 to be object, array given
Imagick::convolveImage() expects parameter 1 to be array, object given
何故 ImageMagick 6 だと古いままなのか
ImageMagick 6 の convolve API が正方行列しか対応していないためです。
- ImageMagick 7 用 imagick 実装
kernel = Z_IMAGICKKERNEL_P(objvar);
IMAGICK_KERNEL_NOT_NULL_EMPTY(kernel);
status = MagickConvolveImageChannel(intern->magick_wand, channel, kernel->kernel_info);
- ImageMagick 6 用 imagick 実装
order = (unsigned long) sqrt(num_elements);
status = MagickConvolveImageChannel(intern->magick_wand, channel, order, kernel);
efree(kernel);
サイズ情報として rank(整数値) しか渡してないので、縦と横を別にできません。
備考
マニュアルの記述
マニュアルが間違えているのは imagick 実装の PHPDoc コメントによるところもあるので、先月(2022/1/10)に修正 PR を送ってあります。なお、今のところ反応はありません。
- fixing to doc comment proto convolveImage #534
セパラブルフィルタ
ImageMagick 7, imagick 3.4.0 以降の引数形式だと、縦と横のサイズを任意にできるので、例えば以下のような横だけブラーが最小限の処理で可能です。
$matrix = [ [1/4, 2/4, 1/4] ]; // horizontal blur
$kernel = \ImagickKernel::fromMatrix($matrix);
$image->convolveImage($kernel);
\frac{1}{4}
\begin{vmatrix}
1 & 2 & 1 \\
\end{vmatrix}
元画像 | 横ブラー |
---|---|
横でブラーをかけた後、更に縦ブラーもかければ、当初の 3x3 行列と同等の結果が得られます。
\frac{1}{4}
\begin{vmatrix}
1 \\
2 \\
1 \\
\end{vmatrix}
$matrix1 = [ [1/4, 2/4, 1/4] ]; // horizontal blur
$matrix2 = [ [1/4],
[2/4],
[1/4] ]; // vertical blur
$kernel1 = \ImagickKernel::fromMatrix($matrix1);
$kernel2 = \ImagickKernel::fromMatrix($matrix2);
$image->convolveImage($kernel1); // horizontal blur
$image->convolveImage($kernel2); // vertical blur
元画像 | 横ブラー |
---|---|
縦ブラー | 横-縦ブラー |
元画像 | 横ブラー |
---|---|
縦ブラー | 横-縦ブラー |
3x3 だと微妙ですが、それより巨大になればなるほど、縦と横で分けた方が高速化できます。セパラブルフィルタと呼ばれます。
1次元配列の N^2 縛り
古い引数の取り方だと、1次元配列で N^2 に個数が限定されると説明しました。
これには少し注意が必要で、imagick 実装を見てみると、
order = (unsigned long) sqrt(num_elements);
status = MagickConvolveImageChannel(intern->magick_wand, channel, order, kernel);
なので、N^2 に合わない場合はエラーにならず、後ろの方の要素を切り捨てるようです。
例えば 10個要素の配列を渡した場合は 9 つだけ使われるように思われます。(未検証)
まとめ
- imagick 3.3.0 以前、又は ImageMagick 6 だと、1次元配列を引数にとる。正方行列として解釈される。
- imagick 3.4.0 以前、かつ ImageMagick 7 だと 2次元配列を包んだ ImageKernel を引数に取る。任意の形の行列を渡せるのでセパラブルフィルタに便利。
- ImageMagick 6 を使っている場合、出来れば 7 にあげましょう。