ITや趣味など気軽に投稿しています。

【Python】OpenCVで画像をぼかす

今回は画像のフィルタリングで画像をぼかしてみる。

フィルタリングって、何でしたっけ?

フィルタリングをすることで、物体検出などの精度を高めることができる場合がある。

なるほど、是非やりましょう。

フィルタリングといってもいろいろな方法があるから、主要なものを紹介していく。

事前準備

import cv2
import matplotlib.pyplot as plt
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)))
キツネ
fox.jpg

フィルタとは

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

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

ほ、ほう??

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

自作カーネルによる画像処理

まだイメージが湧かないので、具体例とかありますか??

そうだな。これからいくつかカーネルを使った例を見せてみよう。

自作カーネルによる画像処理に使う関数

自作カーネルでフィルタリングするときはcv2.filter2D()を使う。

cv.filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]])

引数

共通的な引数以外の引数は以下の通りだ。

引数説明
ddepth出力画像の深度(データ型)。通常は -1 を指定し、入力画像と同じ深度とする。
kernelフィルタ(カーネル)。NumPy配列。
dst出力画像。
anchorカーネルのアンカーポイント。フィルタの中心を指定する。デフォルトは (-1, -1) で、カーネルの中心がアンカーポイントとなる。
deltaフィルタリング結果に加算する値。
borderType画像の端の処理方法。

Identity Kernel(恒等写像)

まずIdentity Kernel(恒等写像)だが、これは元の値と同じ値を返す。

{\displaystyle {\begin{bmatrix}\ \ 0&\ \ 0&\ \ 0\\\ \ 0&\ \ 1&\ \ 0\\\ \ 0&\ \ 0&\ \ 0\end{bmatrix}}}
Identity Kernel

つまり、変換前後で変わらないってことですね。

#Identity Kernel作成
kernel1 = np.array([[0, 0, 0],
                    [0, 1, 0],
                    [0, 0, 0]])

#カーネル適用
identity = cv2.filter2D(src=image, ddepth=-1, kernel=kernel1)

#表示
plt.figure(figsize=(10, 6))

plt.subplot(1, 2, 1)
plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))  
plt.axis("off")
plt.title("Original")

plt.subplot(1, 2, 2)
plt.imshow(cv2.cvtColor(identity, cv2.COLOR_BGR2RGB))  
plt.title("Identity")
plt.tight_layout()
plt.axis("off")

plt.show()
Identity Kernel適用結果

Box blur(ぼかし)

次は画像をぼかしてみる。変更箇所は、kernel1作成の部分だけだ。

#blur Kernel作成
kernel1 = np.ones((5, 5), np.float32) / 25

#カーネル適用
blur = cv2.filter2D(src=image, ddepth=-1, kernel=kernel1)

#表示
plt.figure(figsize=(10, 6))

plt.subplot(1, 2, 1)
plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))  
plt.axis("off")
plt.title("Original")

plt.subplot(1, 2, 2)
plt.imshow(cv2.cvtColor(blur, cv2.COLOR_BGR2RGB))  
plt.title("Blur")
plt.tight_layout()
plt.axis("off")

plt.show()
Blur Kernel適用結果

【おまけ】Sharpen(先鋭化)

次は画像をシャープにしてみる。

シャープにするって、どんなです?

輪郭をくっきりさせるといったところだな。

#sharpen Kernel作成
kernel1 = np.array([[0, -1, 0],
                    [-1, 5, -1],
                    [0, -1, 0]])

#カーネル適用
sharpen = cv2.filter2D(src=image, ddepth=-1, kernel=kernel1)

#表示
plt.figure(figsize=(10, 6))

plt.subplot(1, 2, 1)
plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))  
plt.axis("off")
plt.title("Original")

plt.subplot(1, 2, 2)
plt.imshow(cv2.cvtColor(sharpen, cv2.COLOR_BGR2RGB))  
plt.title("Sharpen")
plt.tight_layout()
plt.axis("off")

plt.show()
Sharpen Kernel適用結果

確かに、毛並みが際立ってます。

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

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

これまでカーネルを自分で定義していたが、実はOpenCVにはフィルタリング用の関数が既に用意されている。

なんだ、早くいってくださいよ。

先に便利なものを教えると、理論も理解せずすぐサボるだろう?

はうっ、、

ぼかし(cv2.blur())

cv2.blur()を使うことでぼかしを入れることができる。この関数はあくまで単純な平均化フィルタであり、画像全体を均一にぼかす。

cv.blur(src, ksize[, dst[, anchor[, borderType]]])

引数

引数説明
ksizeカーネルのサイズ
dst出力画像。
anchorカーネルのアンカーポイント。フィルタの中心を指定する。デフォルトは (-1, -1) で、カーネルの中心がアンカーポイントとなる。
borderType画像の端の処理方法。

実装

#ぼかし処理
blur = cv2.blur(src=image, ksize=(5,5))
cv2.blur(src=image, ksize=(5,5))

あらま、簡単ですね。

ガウシアンぼかし(cv2.GaussianBlur())

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

、要は、どういうことです?

cv2.blur()が均一的なぼかしだったのに対して、こちらは重みづけでぼかし量が変わるため、見た目自然なぼかしになるのが特徴だ。

じゃあ、cv2.GaussianBlur()だけでよくないですか?

重みづけをするということは当然計算量が多くなり、処理が遅くなる。それが許容できない場合、cv2.GaussianBlur()は推奨できない。

cv.GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType[, hint]]]])

引数

引数説明
ksizeカーネルのサイズ
sigmaXX軸方向に対するガウス関数の標準偏差
sigmaYY軸方向に対するガウス関数の標準偏差
hintガウシアンぼかしのアルゴリズム選択
デフォルトはOpenCV が最適なアルゴリズムを自動的に選択する。

実装

#ガウシアンぼかし処理
blur = cv2.GaussianBlur(src=image, ksize=(5,5), sigmaX=0, sigmaY=0)
cv2.GaussianBlur(src=image, ksize=(5,5), sigmaX=0, sigmaY=0)

普通のぼかしとの違いが、、よく分からない。

顔あたりは比較的はっきり見えてるんじゃないか?知らんけど。

博士、もしかして関西人ですか?

つ、次へいこう。

中央値ぼかし(cv2.medianBlur())

次は中央値ぼかしだ。この手法では、元画像の各ピクセルがカーネルの画像ピクセルの中央値に置き換えられる。

中央値ぼかしはcv2.medianBlur()で適用できる。

cv.medianBlur(src, ksize[, dst])

引数

引数説明
ksizeカーネルのサイズ

実装

#中央値ぼかし処理
median = cv2.medianBlur(src=image, ksize=5)
median = cv2.medianBlur(src=image, ksize=5)

ガウシアンぼかしよりぼけ具合が大きいですかね??

そうだな。中央値ぼかしは、一般にごま塩ノイズと呼ばれるような画像のざらつきを排除する場合などに使われる。

Bilateral フィルタ(cv2.bilateralFilter())

ここまで3種類のぼかし技法を紹介したな。

通常のぼかし、ガウシアンぼかし、中央値ぼかしですね。

それらはいずれも、輪郭などのエッジ部分もぼけてしまう。エッジを極力残しながら画像をぼかすにはBilateralフィルタを使う。

cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace[, dst[, borderType]])

引数

引数説明
dフィルタリングに使用するピクセル近傍の直径
sigmaColor色空間におけるフィルタの標準偏差
sigmaSpace座標空間におけるフィルタの標準偏差

sigmaの引数はいずれも数値で指定するが、値が10未満の場合はぼかし効果をあまり得られない。

一方、150以上にするとぼかし効果が強くなり、漫画風のテイストになる。

実装

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

たしかに、ボケながらも輪郭はくっきりですね!

まとめ

ぼかし技法をいろいろと紹介したが、複雑な処理ほど処理速度は低下する。どこまでの精度で実施するかは、実際の状況を考慮して決めるようにしてほしい。

試してみて、決めていくのがいいんですかね。

かもしれんな。今日はここまでにしよう。

ありがとうございました~~♪

【ポイント】
・cv2.blur()は単純な平均化フィルタであり、画像全体を均一にぼかす。
・cv2.GaussianBlur()はカーネルの中心からの距離に基づいて重みづけされ、中心に近いほど加重平均の影響が大きくなる。
・cv2.medianBlur()は元画像の各ピクセルがカーネルの画像ピクセルの中央値に置き換えられる。
・cv2.bilateralFilter()はエッジを極力残しながら画像をぼかす