ImageMagick の Convolve (畳み込み) 処理

ImageMagick の畳み込みオプション -convolve と -morphology Correlate|Convolve の解説です。

- https://www.imagemagick.org/Usage/convolve/

# 畳み込みとは

画像の畳み込み処理は、雑にいうと周囲の色を混ぜて新しい色とする処理です。

![ダウンロード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/2a97b76d-8c1a-48d6-1332-d657d401feb9.png)

周囲の色をどんな割合で足すかの情報は畳み込みカーネルと呼びます。
このカーネルの種類によって、さまざまな画像変換エフェクトが得られます。

傾向として周囲のピクセルを + で取り込むとぼかし効果、- で取り込むと先鋭化(差異を強調)になります。

畳み込みの実際の例を紹介します。

# カーネル実例

## ぼかし(Blur)

主に、平滑化とガウシアンがあります。

### 平滑化 (smoothing)

```math
\frac{1}{9}
\begin{bmatrix}
1 & 1 & 1 \\
1 & 1 & 1 \\
1 & 1 & 1 \
\end{bmatrix}
```

平均化フィルタとも呼ばれます。以下のコマンドで実行できます。

```shell
% convert sample.png -convolve '1 1 1, 1 1 1, 1 1 1' smooth.png
```

, は読みやすいように便宜的につけました。全部スペース区切りでも OK です。

|sample | smooth.png|
|---|---|
|![sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/9334e104-ece3-4bb4-3c4e-21e8fc516815.png)| ![smooth.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/e8711776-5a5e-e20b-f45b-2930bf84da17.png)|

1/9 でなく １ を指定してうまく処理出来てますが、係数を全部足すと 1 になる正規化が内部で行われてます。

- morphology:showKernel

morphology:showKernel で実際に適用されたカーネルが確認できます。


```shell
% convert sample.png -define morphology:showKernel=1 \
          -convolve '1 1 1, 1 1 1, 1 1 1' smooth.png
Kernel "User Defined" of size 3x3+1+1 with values from 1 to 1
Forming a output range from 0 to 9 (Sum 9)
 0:  0.111111  0.111111  0.111111
 1:  0.111111  0.111111  0.111111
 2:  0.111111  0.111111  0.111111
```

- convolve:scale

9倍して元に戻したい場合は、convolve:scale が使えます

```shell
% convert sample.png -define morphology:showKernel=1 \
          -define convolve:scale=9 \
          -convolve '1 1 1, 1 1 1, 1 1 1' smooth9.png
Kernel "User Defined" of size 3x3+1+1 with values from 9 to 9
Forming a output range from 0 to 81 (Sum 81)
 0:         1         1         1
 1:         1         1         1
 2:         1         1         1
```

|sample.png | smooth9.png |
|---|---|
|![sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/9334e104-ece3-4bb4-3c4e-21e8fc516815.png)| ![smooth9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/3f91e000-3629-4e7c-93a8-22a6da23d835.png) |

輝度が9倍なので期待通り明るくなります。

### ガウシアン・ブラー (gaussian blur)

```math
\frac{1}{16}
\begin{bmatrix}
1 & 2 & 1 \\
2 & 4 & 2 \\
1 & 2 & 1 \\
\end{bmatrix}
```

ガウス分布(正規分布)を使った、より正確なブラーです。ぼかしが小さいうちは平滑化(smoothing)とほぼ同じ且つ微妙にボケが弱いですが、ボケの面積を増やすとガウシアンブラーの方が自然なボケ方をします。

```shell
% convert sample.png -convolve '1 2 1, 2 4 2, 1 2 1' gauss.png
```

|sample.png | gauss.png|
|---|---|
|![sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/9334e104-ece3-4bb4-3c4e-21e8fc516815.png)| ![gauss.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/6bc0abac-48e0-cc90-56bc-eae36e427191.png) |

数列はパスカルの三角形(Pascal's triangle)で算出できます。上の２つの数値を足して下の数値にするだけのルールです。

|https://app.awm.jp/image.js/pascal.html|
|---|
| ![oascal_triangle.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/52778441-00ad-d27d-7cce-1b9c2d735d4c.png) |



更に、{1 2 1} を縦と横で掛け算すれば {1 2 1, 2 4 2, 1 2 1} の先ほどの２次元カーネルが出てきます。
尚、先に示したガウシアンカーネルは正規分布 σ:0.8495 相当のようです。

```shell
% convert sample.png  -define morphology:showKernel=1 \
　　　　　　　　　　　　　　　　　　　　-morphology Convolve Gaussian:1x0.8495 gauss_0.8495.png
Kernel "Gaussian" of size 3x3+1+1 with values from 0 to 0.249927
Forming a output range from 0 to 1 (Normalized)
 0: 0.0625182     0.125 0.0625182
 1:     0.125  0.249927     0.125
 2: 0.0625182     0.125 0.0625182
```

## 先鋭化(Sharpen)

```math
\begin{bmatrix}
0 & -1 & 0 \\
-1 & 5 & -1 \\
0 & -1 & 0 \
\end{bmatrix}
```

```shell
% convert sample.png -convolve '0 -1 0, -1 5 -1, 0 -1 0' sharpen.png
```

| sample.png | sharpen.png| 
|---|---|
|![sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/9334e104-ece3-4bb4-3c4e-21e8fc516815.png)| ![sharpen.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/83884fce-18fc-fd9f-426c-634b910e9d67.png)|

画像がシャープになります。文字が読みやすくなるので是非、ご活用を。

## ラプラシアン

```math
\begin{bmatrix}
0 & 1 & 0 \\
1 & -4 & 1 \\
0 & 1 & 0 \
\end{bmatrix}
```

```shell
% convert sample.png -convolve '0 1 0, 1 -4 1, 0 1 0' laplacian.png
```

ラプラシアンは2次微分の事で、変化の強い場所が抽出されます。

|sample.png | laplacian.png|
|---|---|
|![sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/9334e104-ece3-4bb4-3c4e-21e8fc516815.png)| ![laplacian.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/8e087cb8-32af-2d0b-8b04-f5c8018af648.png) |

## エッジ

```math
\begin{bmatrix}
-1 & -1 & -1 \\
-1 &  8 & -1 \\
-1 & -1 & -1 \
\end{bmatrix}
```

エッジを抽出します。

```shell
% convert sample.png -convolve '-1 -1 -1, -1 8 -1, -1 -1 -1' edge.png
```

|sample.png | edge.png|
|---|---|
|![sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/9334e104-ece3-4bb4-3c4e-21e8fc516815.png)| ![edge.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/d82036ff-ec29-a6ec-c033-363b4062cb34.png) |

## ゾーベル

こちらもエッジ抽出に便利です。

```math
\begin{bmatrix}
-2 & -2 & 0 \\
-2 &  0 & 2 \\
 0 &  2 & 2 \
\end{bmatrix}
```

```shell
% convert sample.png -convolve '-2 -2 0, -2 0 2, 0 2 2' sobel.png
```

|sample.png | sobel.png|
|---|---|
|![sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/9334e104-ece3-4bb4-3c4e-21e8fc516815.png)| ![sobel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/aeed0a81-17eb-0ff2-bf52-e7ff70d1fe92.png) |

# -morphology Correlate

-morphology オプションでも畳み込み処理が出来ます。

## Correlate

-convolve は -morphology Convolve でなく -morphology Correlateと同じです。

```shell
% convert sample.png -morphology Correlate '-2 -2 0, -2 0 2, 0 2 2' sobel-mol.png
```

|sample.png | sobel-mol.png|
|---|---|
|![sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/9334e104-ece3-4bb4-3c4e-21e8fc516815.png)| ![sobel-mol.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/9fb8c02f-a412-f5d8-6300-259980df2fa2.png) |

-convolve はこの機能をそのまま使っています。

```c:MagickCore/effect.c
MagickExport Image *ConvolveImage(const Image *image,
  const KernelInfo *kernel_info,ExceptionInfo *exception)
{
  Image
    *convolve_image;
(略)
  convolve_image=MorphologyImage(image,ConvolveMorphology,1,kernel_info,
    exception);
  return(convolve_image);
}
```

## Convolve

Correlate はカーネルを素直に適用しますが、Convolve はカーネルを逆向きに適用します。
これは非対称なカーネルを使うと理由が想像つきます。試しに、F の字カーネルで畳み込んでみましょう。

```math
\begin{bmatrix}
1 & 1 & 1 & 1 & 1\\
1 & 0 & 0 & 0 & 0 \\
1 & 1 & 1 & 0 & 0 \\
1 & 0 & 0 & 0 & 0 \\
1 & 0 & 0 & 0 & 0 \\
\end{bmatrix}
```

しつこいですが、-morphology Correlate は -convolve とほぼ同じです。
ただし、-convolve と違ってカーネル正規化がデフォルトoffのようです。 -define convolve:scale=’!’ で正規化を有効に出来ます。

```shell
% convert star.png -define convolve:scale='!' -morphology Correlate \
    " 1 1 1 1 1 \
      1 0 0 0 0 \
      1 1 1 0 0 \
      1 0 0 0 0 \
      1 0 0 0 0 "  F.png
```

|star.png | F.png |
|---|---|
|![star.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/2377f5d6-2926-bdd1-96e2-e6df56ccc918.png) | ![F.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/93154bf3-16bd-0564-917f-891130b5d867.png)|

左上の一部を normalize した上でドット拡大してみます。

```
% convert F.png  -crop 40x32+0+0 -normalize F-crop.png
```

|F-crop.png| ドット化 |
|---|---|
|![F-crop.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/4d33d6dd-3034-636a-754c-4eecc8a803c9.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/958b8adc-c4d1-7514-4cac-c0008b10e55d.png) |

こんな感じに、カーネルを180度回転させた形で適用されます。なので -morphology Convolve はあえてカーネルを逆向きに適用します。


```shell
% convert star.png -define convolve:scale='!' -morphology Convolve \
    " 1 1 1 1 1 \
      1 0 0 0 0 \
      1 1 1 0 0 \
      1 0 0 0 0 \
      1 0 0 0 0 "  F-molconv.png
```

|star.png | F-melconv.png|
|---|---|
|![star.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/2377f5d6-2926-bdd1-96e2-e6df56ccc918.png) | ![F-molconv.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/5985deed-bdd4-caa0-03a5-0bb2e635592d.png) |

|F-molconv-crop.png| ドット化 |
|---|---|
| ![F-molconv-crop.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/4bccd9bc-3c2b-25c4-a5ca-a2f1e53f7b21.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/948bd8fa-6bf8-6ff4-13e0-2caa1e05a9a7.png) |

この方が直感に合うでしょう。
畳み込みでは対称な構造のカーネルを使う事が多くあまり問題になりませんが、モルフォロジーではこれが重要になります。

# カーネルサイズ指定

-convolve で並べる係数は 2*n (1, 4, 9, ...) 個並べると正方形のカーネルとして解釈されますが、縦と横のサイズを指定する事も出来ます。
例えば以下のようにして横方向だけブラーをかけられます。

```shell
% convert sample.png -define morphology:showKernel=1 \
          -convolve '5x1: 1 1 1 1 1' smooth5x1.png
Kernel "User Defined" of size 5x1+2+0 with values from 1 to 1
Forming a output range from 0 to 5 (Sum 5)
 0:       0.2       0.2       0.2       0.2       0.2
```

|sample.png | smooth5x1.png (横ブラー) |
|---|---|
|![sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/9334e104-ece3-4bb4-3c4e-21e8fc516815.png)| ![smooth5x1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/9d94adae-977b-8cca-23e2-c6784a1634d4.png) |

この機能は、セパラブルフィルタを処理するのに使えます。

## セパラブル・フィルタ (Separable Filter)

２次元カーネルを横1行、縦1列で分けて1次元カーネルの処理にできるフィルタが存在します。
ブラーはその典型です。2次元カーネルは処理が重たい事が多いので、この手法は実用的です。

### ガウシアン・ブラー

ガウシアンブラー 5x5 は以下のカーネルを持ちます。

```math
\frac{1}{256}
\begin{bmatrix}
1 &  4 &  6 &  4 & 1 \\
4 & 16 & 24 & 16 & 4 \\
6 & 24 & 36 & 24 & 6 \\
4 & 16 & 24 & 16 & 4 \\
1 &  4 &  6 &  4 & 1 \\
\end{bmatrix}
```

{ 1 4 6 4 1} の掛け算で表現できるので、以下のように分ける事ができます。

```math
\frac{1}{16}
\begin{bmatrix}
1 &  4 &  6 &  4 & 1 \\
\end{bmatrix}
```

```math
\frac{1}{16}
\begin{bmatrix}
1 \\
4 \\
6 \\
4 \\
1 \\
\end{bmatrix}
```

```shell
% convert sample.png -convolve '5x1: 1 4 6 4 1' gauss5x1.png
% convert sample.png -convolve '1x5: 1 4 6 4 1' gauss1x5.png
```

|sample.png | gauss5x1.png (横ブラー) |gauss1x5.png (縦ブラー)|
|---|---|---|
|![sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/9334e104-ece3-4bb4-3c4e-21e8fc516815.png)| ![gauss5x1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/45979f9a-96bf-6efc-b69a-d33f960304ff.png) | ![gauss1x5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/2b6fe757-f9e3-b4c1-6c1a-9d002643922e.png) |



```shell
% convert sample.png -convolve '5x1: 1 4 6 4 1' -convolve '1x5: 1 4 6 4 1' \
          gauss5x5.png
```

これで普通のブラーが実現できます。

|sample.png | gauss5x5.png|
|---|---|
|![sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/9334e104-ece3-4bb4-3c4e-21e8fc516815.png)| ![gauss5x5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/ca811906-0607-5e2d-62df-650f45e2139b.png) |

# おまけ

## ソフトブラー

昔のセル画でみかけたブラスチック越しで微妙にボケる味わいある効果は、元の画像と合成するだけで実現できます。ソフトブラー(softened Blurring)と呼ぶそうです。

- https://imagemagick.org/Usage/convolve/#soft_blur

```shell
% convert wakaru.png  -define convolve:scale=50,50% \
          -morphology Convolve Gaussian:0x2 \
          wakaru_soft_blur.png
```

|wakaru.png|wakaru_soft_blur.png|
|---|---|
| ![wakaru.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/28074c61-6648-53d0-672a-5e805ee57833.png) | ![wakaru_soft_blur.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/e06cc229-8a57-ce66-3956-f0939223fc18.png) |

Gaussian の数値は解像度に依存するので、大きめの画像では 0x3 以上に変えてみて下さい。あと、convolve:scale で元画像とブラーの混ぜ率、つまりエフェクトの効き具合を調整できます。0,100% が元画像のままで、100,0% だとただのボケ画像です。

# TODO

- ネームドコンボリューションカーネルの紹介

# 参考

- https://www.imagemagick.org/Usage/convolve/
- https://www.imagemagick.org/Usage/morphology/
- OpenCV - 画像の平滑化
   - http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_filtering/py_filtering.html
- OpenCV – 平均化フィルタとガウシアンフィルタについて
   - https://pystyle.info/opencv-mean-filter-and-gaussian-filter
   -