python

画像処理・解析その2

画像の平滑化

画像の平滑化の種類には有名どころとして、前回行った平均化フィルタとガウシアンフィルタがあります。
その他にも、中央値フィルターとバイラテラルフィルターについても紹介していきます。

平均化フィルタ

前回行った、3*3の1/9フィルターを簡単に行うための関数が提供されています。

import cv2
import matplotlib.pyplot as plt
%matplotlib inline

img = cv2.imread('Lenna_(test_image).png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# ブラー処理=>前回1/9と同じ
img_blur = cv2.blur(img, (3, 3))

fig = plt.figure()
plt.gray()
fig.add_subplot(1,2,1)
plt.imshow(img)

plt.gray()
fig.add_subplot(1,2,2)
plt.imshow(img_blur)

ガウシアンフィルター

ガウシアンフィルターの特徴として、カーネルサイズを奇数に設定する必要があります。
また、ぼかし度合いとして標準偏差を大きくすれば、大きくなります。逆に小さくすれば、小さくなります。
内部の処理としては、ガウス分布にしたがってカーネル内の重みを計算します。

平均化フィルタとの違いは、中央の注目画素の重みが最も大きくなり、外側に行くにつれて重みが小さくなります。これにより、中央の情報を残しながら画像をぼかすことが可能になります。

# ガウシアンフィルター
# ガウシアンフィルターはフィルター数は奇数出ないと駄目
# 与える標準偏差が大きくなればぼかし度合いも大きくなる
img_ga = cv2.GaussianBlur(img, (9, 9), 2) 

fig = plt.figure()
plt.gray()
fig.add_subplot(1,2,1)
plt.imshow(img)

plt.gray()
fig.add_subplot(1,2,2)
plt.imshow(img_ga)

メディアンフィルター

メディアンフィルターは中央値をすべて塗りつぶすので、他のフィルターより変化が分かりやすいとお思います。

# メディアンフィルター
# 5*5の画像サイズの中央値をすべて塗りつぶす
img_me = cv2.medianBlur(img, 5)

fig = plt.figure()
plt.gray()
fig.add_subplot(1,2,1)
plt.imshow(img)

plt.gray()
fig.add_subplot(1,2,2)
plt.imshow(img_me)

バイラテラルフィルター

バイラテラルフィルターはガウシアンフィルターを2つ使用します。色空間に着目したものと距離に着目したものになります。この2つを考慮してフィルタリングが行われます。
これにより、今までのフィルタだとエッジの部分もぼけてしまうが、輝度の変化が急激な部分(エッジ)は残すことができます。

# 今までのフィルタだとエッジの部分もぼけてしまうが、輝度の変化が急激な部分(エッジ)は残す
# バイラテラルフィルター
# カーネルのサイズは20を指定し、次の30はそれぞれ色空間の標準偏差、距離の標準偏差
img_bi = cv2.bilateralFilter(img, 20, 30, 30)

fig = plt.figure()
plt.gray()
fig.add_subplot(1,2,1)
plt.imshow(img)

plt.gray()
fig.add_subplot(1,2,2)
plt.imshow(img_bi)

画像の微分

画像を微分するとエッジが検出できます。
傾き = 隣の画素 – 現在の画素値
下記のような画像で、微分をすれば赤枠の部分を検出でき、黒と白の境界線だけが残ります。

エッジの検出(Sobel・Laplacian・Canny)

Sobelフィルター

Sobelフィルターは微分する方向を自分で選ぶことができるので、縦方向・横方向それぞれにエッジ検出をすることができます。

import cv2
import matplotlib.pyplot as plt
%matplotlib inline

img = cv2.imread('Lenna_(test_image).png', 0)

# sobelフィルター
img_sobel_x = cv2.Sobel(img, cv2.CV_32F, 1, 0, ksize=3) # x方向への微分
img_sobel_y = cv2.Sobel(img, cv2.CV_32F, 0, 1, ksize=3) # y方向への微分

# 負の値が入っているので、0-255の値に直します
img_sobel_x
# array([[   0.,   -4.,   -8., ...,  -64., -148.,    0.],
#        [   0.,   -4.,   -8., ...,  -64., -148.,    0.],
#        [   0.,   -4.,   -8., ...,  -64., -148.,    0.],
#        ...,
#        [   0.,   26.,   23., ...,   -4.,   -2.,    0.],
#        [   0.,   39.,   30., ...,    7.,   14.,    0.],
#        [   0.,   44.,   32., ...,   12.,   20.,    0.]], dtype=float32)

img_sobel_x = cv2.convertScaleAbs(img_sobel_x)
img_sobel_y = cv2.convertScaleAbs(img_sobel_y)

fig = plt.figure(figsize=(15, 9))
plt.gray()
fig.add_subplot(1,3,1)
plt.imshow(img)

plt.gray()
fig.add_subplot(1,3,2)
plt.imshow(img_sobel_x)

plt.gray()
fig.add_subplot(1,3,3)
plt.imshow(img_sobel_y)

Laplacianフィルター

sobelフィルターと違う点は、微分方向の指定がないため全てのエッジが検出可能ですが、値は小さくなります。

# Laplacianフィルター
# 2次の微分:方向指定はなし
img_lap = cv2.Laplacian(img, cv2.CV_32F)
img_lap = cv2.convertScaleAbs(img_lap)

fig = plt.figure(figsize=(15, 9))
plt.gray()
fig.add_subplot(1,2,1)
plt.imshow(img)

plt.gray()
fig.add_subplot(1,2,2)
plt.imshow(img_lap)

エッジの検出が見づらい時は、検出した値を2倍にする方法があります。
ただし、余計なノイズが入り込みます。

img_lap *= 2
fig = plt.figure(figsize=(15, 9))
plt.gray()
fig.add_subplot(1,2,1)
plt.imshow(img)

plt.gray()
fig.add_subplot(1,2,2)
plt.imshow(img_lap)

Laplacian Gaussian

先程のLaplacianフィルターを2倍したやり方だと、余計なノイズが入ってしまったので、それを取り除いた状態にするのが、Laplacian Gaussianです。

# Laplacian Gaussian
img_blur = cv2.GaussianBlur(img, (3, 3), 2)
img_lap3 = cv2.Laplacian(img_blur, cv2.CV_32F)
img_lap3 = cv2.convertScaleAbs(img_lap3)
img_lap3 *= 2

fig = plt.figure(figsize=(15, 9))
plt.gray()
fig.add_subplot(1,2,1)
plt.imshow(img_lap2)

plt.gray()
fig.add_subplot(1,2,2)
plt.imshow(img_lap3)

Canny

Cannyはノイズを上手く取り除いて、エッジのみを検出します。
Laplacianは二次微分を使用している事もあり、輝度に対して敏感です。
対して、Cannyは輝度の変化が激しいところに反応します。
Cannyのアルゴリズムは大きく分けて4つに分類されます。
1、ガウシアンフィルターでぼかす
2、Sobelフィルターで微分(x軸、y軸どちらも行い、2乗和の平方根をとる)
3、極大点を探す
4、2段階の閾値処理でエッジを残す

import cv2
import matplotlib.pyplot as plt
%matplotlib inline

img = cv2.imread('Lenna_(test_image).png', 0)

# 閾値設定が重要 => エッジを残せるかどうかに関わってくる
img_canny = cv2.Canny(img, 10, 180) # 下限が10, 上限が180
img_canny2 = cv2.Canny(img, 100, 200)

fig = plt.figure(figsize=(15, 9))
plt.gray()
fig.add_subplot(1,2,1)
plt.imshow(img_canny)

plt.gray()
fig.add_subplot(1,2,2)
plt.imshow(img_canny2)