【Python】OpenCVで画像をフィルタリングする

今回はOpenCVで画像にフィルタリング処理を施す方法を解説します。

画像のフィルタリングとは、画像から不要なもの(ノイズなど)を取り除き、目的の情報を明確にするための処理です。フィルタリングをすることによって、物体検出などの精度を高めることができたりします。

フィルタリングの方法は1つではなく、状況に応じて様々な方法を使い分けます。今回は、各フィルタの特徴とPythonでの実装方法を紹介していきます。

今回使用する画像

今回はキツネの画像を使っていきます。

import cv2
import numpy as np

image = cv2.imread('input/fox.jpg')

height, width = image.shape[:2]
image = cv2.resize(image,(round(width/4), round(height/4)))
オリジナルの画像

フィルタの正体

画像処理におけるフィルタの正体は奇数の二次元正方行列です(3×3, 5×5など)。カーネルとも呼ばれます。

\ begin {equation *} \ begin {bmatrix} 1&1&1 \\ 1&1&1 \\ 1&1&1 \ end {bmatrix} \ end {equation *}

これを画像の各ピクセルに対して行列演算(畳み込み)することで、画像をぼかしたり鮮明にしたりすることができます。

ざっくり言うと、カーネルの値が変わることで画像をシャープにしたりぼかしたりできます。

カーネルの例

カーネルの種類は無数にありますが、その中からいくつか実践してみようと思います。

Identity Kernel

恒等写像(Identity Kernel)は元の値と同じ値を返します。つまり、変換前と変換後で同じ画像となります。

{\displaystyle {\begin{bmatrix}\ \ 0&\ \ 0&\ \ 0\\\ \ 0&\ \ 1&\ \ 0\\\ \ 0&\ \ 0&\ \ 0\end{bmatrix}}}
Identity Kernel
kernel1 = np.array([[0, 0, 0],
                    [0, 1, 0],
                    [0, 0, 0]])

identity = cv2.filter2D(src=image, ddepth=-1, kernel=kernel1)

cv2.imshow('Original', image)
cv2.imshow('Identity', identity)

cv2.waitKey()

cv2.destroyAllWindows()

実行すると、画像に違いはありません。

filter2Dの引数ddepthは出力画像の型です。-1とすることで、入力画像と同じ型を返します。

Box blur

つぎは画像をぼかしてみます。以下のカーネルを使えば、画像をぼかすことができます。

kernel2 = np.ones((5, 5), np.float32) / 25

Blur = cv2.filter2D(src=image, ddepth=-1, kernel=kernel2)

cv2.imshow('Original', image)
cv2.imshow('Blur', Blur)

cv2.waitKey()
cv2.destroyAllWindows()

右側の画像はオリジナルの画像よりもぼかしが入っているのがわかります。

Sharpen

画像をシャープにして輪郭を際立たせてみます。

kernel1 = np.array([[0, -1, 0],
                    [-1, 5, -1],
                    [0, -1, 0]])

Shapen = cv2.filter2D(src=image, ddepth=-1, kernel=kernel1)

cv2.imshow('Original', image)
cv2.imshow('Shapen', Shapen)

cv2.waitKey()

cv2.destroyAllWindows()

右側の画像はより輪郭がくっきりと浮かび上がりました。

このように、カーネルを定義して畳み込みをすることで、画像のフィルタリングが可能になります。

OpenCVの関数でフィルタリングする

これまでカーネルを自作して画像をぼかしたりシャープ化してきました。しかし、実際の場面でいちいちカーネルを自作するのは面倒です。

OpenCVでは、フィルタリングするための関数が用意されているので、以降はそれを使ってフィルタリングをしていきます。

ぼかし

カーネルを特に指定する必要がない場合は、blur()を使うことで簡単に画像をぼかすことができます。

ksizeはカーネルのサイズです。

blur = cv2.blur(src=image, ksize=(5,5))

cv2.imshow('Original', image)
cv2.imshow('blur', blur)

cv2.waitKey()

cv2.destroyAllWindows()

ガウシアンぼかし

ガウシアンぼかしは重みづけによる加重平均を用いたフィルタリングです。カーネルの中心からの距離に基づいて重みづけされ、中心に近いほど加重平均の影響が大きくなります。

ksizeはカーネルのサイズです。sigmaXとsigmaYは、X軸方向とY軸方向の標準偏差です。

sigmaXを0に指定すると、標準偏差はカーネルサイズから算出されます。sigmaYのデフォルトは0です。各値は、正の値で指定します。

gaussian_blur = cv2.GaussianBlur(src=image, ksize=(5,5),sigmaX=0, sigmaY=0)

cv2.imshow('Original', image)
cv2.imshow('gaussian_blur', gaussian_blur)

cv2.waitKey()

cv2.destroyAllWindows()

中央値ぼかし

中央値ぼかし(median blur)は、元画像の各ピクセルがカーネルの画像ピクセルの中央値に置き換えられます。

ksizeはカーネルのサイズです。正の奇数を指定します。

median = cv2.medianBlur(src=image, ksize=5)

cv2.imshow('Original', image)
cv2.imshow('median', median)

cv2.waitKey()
cv2.destroyAllWindows()

カーネルサイズが同じ場合、中央値ぼかしの方がガウシアンぼかしよりもぼかし効果が大きいです。中央値ぼかしは、ごま塩ノイズと呼ばれるような画像のざらつきを排除する場合などに使われます。

Bilateral フィルタ

これまでのぼかしでは、輪郭等エッジもぼかされてしまっていました。Bilateralフィルタを使えば、エッジを極力残しながら画像をぼかすことができます。

パラメータのdはフィルタリングに使用するピクセル近傍の直径です。

sigmaColorは1次元のガウス分布を定義します。sigmaSpaceはx方向とy方向のカーネル空間範囲を指定します。

bilateral_filter = cv2.bilateralFilter(src=image, d=9, sigmaColor=75, sigmaSpace=75)

cv2.imshow('Original', image)
cv2.imshow('bilateral_filter', bilateral_filter)

cv2.waitKey()
cv2.destroyAllWindows()

輪郭を残しながらぼかしが効いていることがわかります。

OpenCVの学習におすすめ書籍

PythonでOpenCV始めてみようという方におすすめなのが以下書籍です。実装例も豊富なので、1からコードを書かずとも学習を進めることができます。

オライリーの1冊は読み物というより辞書としての利用におすすめです。お値段結構しますが、細かい情報までしっかりと詰め込まれています。

まとめ

OpenCVで画像をフィルタリングする方法を紹介しました。

単純に画像をぼかす、シャープ化するといっても様々な方法あります。どの手法が適切かは、使用する画像によって異なります。

まずはいろいろ試してみて、各手法でどのような効果があるかを確認してみてください。

OpenCVの基礎を身につけるためのロードマップは以下を参考にしてください。

ではでは👋