目次
概要
画像処理の空間フィルタリングについて解説し、OpenCV の filter2D を使ったやり方を紹介します。
空間フィルタリング
入力画像の各画素に対して、対象の画素及びその近傍の画素の画素値の重み付き総和を計算し、出力画像の画素値を計算する処理を空間フィルタリング (spacial filtering) または畳み込み (convolution) といいます。 フィルタリングを行う際の重みはカーネル (kernel) またはフィルタ (filter) といいます。
入力画像の画素を $\text{src}(x, y)$、サイズが $N \times N$ のフィルタの値を $f(i, j)$ としたとき、出力画像の画素 $\text{dst}(x, y)$ は次のように計算できます。
$$ \text{dst}(x, y) = \sum_{i = 0}^N \sum_{j = 0}^N \text{src}(x + i, y + j) \times f(i, j) $$フィルタリングの例
入力画像とカーネルは以下とします。
このとき、出力画像の各画素値はフィルタをスライドさせながら、カーネルと重なっている画素とカーネルの要素同士を乗算し、総和を取って計算します。
cv2.filter2D
OpenCV では、cv2.filter2D() でフィルタリングが行えます。
dst = cv2.filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]])
引数
名前 | 型 | デフォルト値 |
---|---|---|
src | ndarray | |
入力画像 | ||
ddepth | int | |
出力画像の型。-1 の場合は入力画像と同じ型を使用。 | ||
kernel | ndarray | |
カーネル | ||
anchor | tuple of 2-ints | (-1, -1) |
フィルタを表す行列のアンカー成分。(-1, -1) の場合は行列の中心。 | ||
delta | scalar | 0 |
重み付き総和を計算したあと、この delta を足す | ||
borderType | BorderTypes | cv2.BORDER_DEFAULT |
パディング方法 |
返り値
名前 | 説明 | ||
---|---|---|---|
dst | 出力画像 |
サンプルコード
In [1]:
import cv2
import numpy as np
from IPython.display import Image, display
def imshow(img):
"""ndarray 配列をインラインで Notebook 上に表示する。
"""
ret, encoded = cv2.imencode(".jpg", img)
display(Image(encoded))
# 画像を読み込む。
img = cv2.imread("sample.jpg", cv2.IMREAD_GRAYSCALE)
# 微分フィルタ
kernel = np.array([[0, 0, 0], [0, -1, 1], [0, 0, 0]])
# フィルタリングを行う。
dst = cv2.filter2D(img, -1, kernel)
imshow(dst)
ddepth – 出力画像の型
総和演算の結果は $[0, 255]$ の範囲に収まらない可能性があり、ddepth
で指定した型で表せる範囲を超えた値はクリップされます。
値 | 意味 |
---|---|
-1 | 入力画像と同じ型 |
cv2.CV_8U | 符号なし8ビット |
cv2.CV_8S | 符号あり8ビット |
cv2.CV_16U | 符号なし16ビット |
cv2.CV_16S | 符号あり16ビット |
cv2.CV_32S | 符号あり32ビット |
cv2.CV_32F | 32ビット浮動小数点数 |
cv2.CV_64F | 64ビット浮動小数点数 |
In [2]:
# エンボスフィルタ
kernel = np.array([[1, 0, 0], [0, 0, 0], [0, 0, -1]])
# ddeeth=-1
dst = cv2.filter2D(img, -1, kernel)
print("ddepth=-1 (cv2.CV_8U)", dst.min(), dst.max(), dst.dtype)
# ddeeth=cv2.CV_16S
dst = cv2.filter2D(img, cv2.CV_16S, kernel)
print("ddepth=-1 (cv2.CV_16S)", dst.min(), dst.max(), dst.dtype)
ddepth=-1 (cv2.CV_8U) 0 239 uint8 ddepth=-1 (cv2.CV_16S) -223 239 int16
delta – オフセット
重み付き総和を計算したあとに delta を足します。
In [3]:
# エンボスフィルタ
kernel = np.array([[1, 0, 0], [0, 0, 0], [0, 0, -1]])
dst = cv2.filter2D(img, -1, kernel)
imshow(dst)
dst = cv2.filter2D(img, -1, kernel, delta=128)
imshow(dst)
borderType – パディング方法
フィルタリングを行う際に、入力画像の端では画素が存在しない部分が出てくるので、その補完方法を指定します。
値 | 意味 |
---|---|
cv2.BORDER_CONSTANT | 0でパディングする |
cv2.BORDER_REPLICATE | 一番端の値を繰り返してパディングする |
cv2.BORDER_REFLECT | 端で値を反転させてパディングする |
cv2.BORDER_DEFAULT | cv2.BORDER_REFLECT と同じ |
入力画像
1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 |
- borderType=cv2.BORDER_CONSTANT
端は0でパディングします。(例: abcde -> 00|abcde|00)
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 1 | 2 | 3 | 4 | 5 | 0 | 0 |
0 | 0 | 6 | 7 | 8 | 9 | 10 | 0 | 0 |
0 | 0 | 11 | 12 | 13 | 14 | 15 | 0 | 0 |
0 | 0 | 16 | 17 | 18 | 19 | 20 | 0 | 0 |
0 | 0 | 21 | 22 | 23 | 24 | 25 | 0 | 0 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
- borderType=cv2.BORDER_REPLICATE
端の画素を繰り返してパディングします。(例: abcde -> aa|abcde|ee)
1 | 1 | 1 | 2 | 3 | 4 | 5 | 5 | 5 |
1 | 1 | 1 | 2 | 3 | 4 | 5 | 5 | 5 |
1 | 1 | 1 | 2 | 3 | 4 | 5 | 5 | 5 |
6 | 6 | 6 | 7 | 8 | 9 | 10 | 10 | 10 |
11 | 11 | 11 | 12 | 13 | 14 | 15 | 15 | 15 |
16 | 16 | 16 | 17 | 18 | 19 | 20 | 20 | 20 |
21 | 21 | 21 | 22 | 23 | 24 | 25 | 25 | 25 |
21 | 21 | 21 | 22 | 23 | 24 | 25 | 25 | 25 |
21 | 21 | 21 | 22 | 23 | 24 | 25 | 25 | 25 |
- borderType=cv2.BORDER_REFLECT
端で折り返してパディングします。(例: abcde -> ba|abcde|ed)
7 | 6 | 6 | 7 | 8 | 9 | 10 | 10 | 9 |
2 | 1 | 1 | 2 | 3 | 4 | 5 | 5 | 4 |
2 | 1 | 1 | 2 | 3 | 4 | 5 | 5 | 4 |
7 | 6 | 6 | 7 | 8 | 9 | 10 | 10 | 9 |
12 | 11 | 11 | 12 | 13 | 14 | 15 | 15 | 14 |
17 | 16 | 16 | 17 | 18 | 19 | 20 | 20 | 19 |
22 | 21 | 21 | 22 | 23 | 24 | 25 | 25 | 24 |
22 | 21 | 21 | 22 | 23 | 24 | 25 | 25 | 24 |
17 | 16 | 16 | 17 | 18 | 19 | 20 | 20 | 19 |
コメント