LoginSignup
5
6

More than 3 years have passed since last update.

デジタル画像処理(空間フィルタリング)

Posted at

空間フィルタリング

領域に基づく濃淡変換(area to pixel operation)とのこと。
フィルタを各ピクセルに当てはめて、積和演算を実行して画像の平滑化(ノイズ除去)、エッジ検出を行う。
イメージはこの動画のように高速でフィルタをあてはめ、フィルタから得られた計算値を格画素に入力する。(畳み込みとも呼ばれる)

平滑化(画素値の凹凸を減少させる)

使用する画像

1_av_fil_before.jpg

使ったライブラリ

import numpy as np
from numpy.lib.stride_tricks import as_strided
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import axes3d
import matplotlib.gridspec as gridspec
import seaborn as sns
import math
import cv2

平均化フィルタ

領域内の画素値の平均を注目画素の値とする。

av_filter = np.array([[ 1/25,1/25,1/25,1/25,1/25,],
                      [ 1/25,1/25,1/25,1/25,1/25,],
                      [ 1/25,1/25,1/25,1/25,1/25,],
                      [ 1/25,1/25,1/25,1/25,1/25,],
                      [ 1/25,1/25,1/25,1/25,1/25,]], np.float32)

# ddepth = -1, means destination image has depth same as input image
dst1 = cv2.filter2D(image_1, -1, av_filter)
cv2.imwrite('2_av_fil.jpg', dst1)

↓全体的に平滑化さてている。

2_av_fil.jpg

加重平均フィルタ

重みつき平均化とも呼ばれ注目画素を中心に、外にいくにしたがって重みが減っているような分布

add_av_fil = np.array([[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]], np.float32)/256

dst1 = cv2.filter2D(res, -1, add_av_fil)
cv2.imwrite('3_add_av_fil.jpg', dst1)

↓比較的、エッジを保存しつつ平滑化さてている。

3_add_av_fil.jpg

ガウシアンフィルタ

ガウス分布に従って重みをつけている。

$$
h\left(x, y\right) = \frac{1}{2\pi\sigma^2}{\exp{\left(-\frac{x^2+y^2}{2\sigma^2}\right)}}\
$$

def gausian(X, y, siguma):
#     exp(-(x2 + y2)/2σ2)
    h2 = math.e**(-1*((X**2) + (y**2))/(2*(siguma**2)))
#     2πσ2
    h3 = 2*math.pi*(siguma**2)
    h = h2/h3
    return h

sns.set_style('ticks')

fig = plt.figure(figsize=(10,8))
ax = fig.add_subplot(111, projection='3d')

x = y = np.arange(-3,3,0.1)
X,Y = np.meshgrid(x,y)

ax.plot_surface(X,Y, gausian(X,Y,1),cmap='coolwarm')
plt.show()
plt.savefig("gausian_fig.jpg")

↓こういうイメージ

gausian_fig.jpg

手作り

def gausian_kernel(size):
    if size%2==0:
        print('kernel size should be odd')
        return
    sigma = (size-1)/2

#     [0,size]→[-sigma, sigma] にずらす
    x = y = np.arange(0,size) - sigma
    X,Y = np.meshgrid(x,y) 
    print(sigma)

    mat = gausian(X,Y,sigma)

    # 総和が1になるように
    kernel = mat / np.sum(mat)
    return mat

g_fil_hand = gausian_kernel(3)

#フィルタ形状
#[[0.05854983 0.09653235 0.05854983]
#[0.09653235 0.15915494 0.09653235]
#[0.05854983 0.09653235 0.05854983]]

dst1 = cv2.filter2D(res, -1, g_fil_hand)
cv2.imwrite('4_g_hand_fil.jpg', dst1)

4_g_hand_fil.jpg

opencv による

sigma = 2
blur = cv2.GaussianBlur(res, (0, 0), sigmaX = 3, sigmaY = 3)
cv2.imwrite('5_g_cv_fil.jpg', blur)

5_g_cv_fil.jpg

特定方向の平滑化

以下のようなフィルタを用いることで、特定方向にのみ平滑化が行われ、
横にぶれたような絵になる。

flat_d_fil = np.zeros([15, 15])

for i in range(15):
    flat_d_fil[i, i] = 1/15

dst1 = cv2.filter2D(res, -1, flat_d_fil)
cv2.imwrite('6_shaky_fil.jpg', dst1)

6_shaky_fil.jpg

微分フィルタ

縦エッジ用

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

dst1 = cv2.filter2D(res, -1, 8*diff_col)
cv2.imwrite('7_diff_col.jpg', dst1)

7_diff_col.jpg

横エッジ用

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

dst1 = cv2.filter2D(res, -1, 8*diff_row)
cv2.imwrite('8_diff_row.jpg', dst1)

8_diff_row.jpg

プリューウィット フィルタ

微分フィルタと平均フィルタの組み合わせ
横方向の画素値の勾配を拾う + 縦方向の画素値の勾配を平滑化

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

dst1 = cv2.filter2D(res, -1, 2*pulu_fil)
cv2.imwrite('9_pulu_fil.jpg', dst1)

9_pulu_fil.jpg

ソーベル フィルタ

微分フィルタと加重平均フィルタの組み合わせ 比較的にプリューウィットに比べ、滑らかなぼかし具合を実現する。 横方向の画素値の勾配を拾う + 縦方向の画素値の勾配を平滑化

sobel_fil = np.array([[1, 0, -1],
        [2, 0, -2],
        [1, 0, -1]])

dst1 = cv2.filter2D(res, -1, sobel_fil)
cv2.imwrite('10_sobel_fil.jpg', dst1)

10_sobel_fil.jpg

2次微分フィルタ

微分フィルタを二重にした感じ

todiff_fil = np.array([[0, 0, 0],
                      [1, -2, 1],
                      [0, 0, 0]])

dst1 = cv2.filter2D(res, -1, 2*todiff_fil)
cv2.imwrite('11_to_diff_fil.jpg', dst1)

ラプラシアン フィルタ

二次微分の縦+横をした感じ。

lap_fil = np.array([[0, 1, 0],
        [1, -4, 1],
        [0, 1, 0]])

dst1 = cv2.filter2D(res,ddepth=cv2.CV_16S,kernel=lap_fil)
cv2.imwrite('12_lap_hand_fil.jpg', dst1+100)

plt.plot(ds[400])
plt.savefig("lap_fig_1.jpg")

12_lap_fil.jpg

lap_fig_1.jpg

opencvで

ddepth = cv2.CV_16S
kernel_size = 3
dst = cv2.Laplacian(res, ddepth, ksize=kernel_size)
cv2.imwrite('13_lap_cv_fil.jpg', dst)

plt.plot(dst[400]+100)
plt.savefig("lap_fig_2.jpg")

13_lap_cv_fil.jpg

Log フィルタ(ラプラシアン ガウシアン)

$$
h\left(x, y\right) = \frac{x^2+y^2-2\sigma^2}{2\pi\sigma^6}{\exp{\left(-\frac{x^2+y^2}{2\sigma^2}\right)}}\
$$

手作り

def Log_fil(X, y, siguma):

#     X2+y2-2σ2
    h1 = ((X**2) + (y**2) - (2*(siguma**2))) 

#     exp(-(x2 + y2)/2σ2)
    h2 = math.e**(-1*((X**2) + (y**2))/(2*(siguma**2)))

#     2πσ6
    h3 = 2*math.pi*(siguma**6)

    h = h1*h2/h3
    return h

sns.set_style('ticks')

fig = plt.figure(figsize=(10,8))
ax = fig.add_subplot(111, projection='3d')

x = y = np.arange(-3,3,0.1)
X,Y = np.meshgrid(x,y)

ax.plot_surface(X,Y,Log_fil(X,Y,1),cmap='coolwarm')
plt.savefig("log_fig.jpg")
plt.show()

log_fig.jpg

def L_of_g_kernel(size, sigma):
    if size%2==0:
        print('kernel size should be odd')
        return
    ad_size = (size-1)/2

    # [0,size]→[-sigma, sigma] にずらす
    x = y = np.arange(0,size) - ad_size
    X,Y = np.meshgrid(x,y) 
    print(sigma)

    mat = Log_fil(X,Y,sigma)
    print(mat)

    # 総和が0になるように
    kernel = mat - np.sum(mat)/size**2

    return kernel

log_fil =  L_of_g_kernel(5, 1)

#フィルタ形状
#[[ 0.01749015  0.0391927   0.04307856  0.0391927   0.01749015]
# [ 0.0391927   0.         -0.09653235  0.          0.0391927 ]
# [ 0.04307856 -0.09653235 -0.31830989 -0.09653235  0.04307856]
# [ 0.0391927   0.         -0.09653235  0.          0.0391927 ]
# [ 0.01749015  0.0391927   0.04307856  0.0391927   0.01749015]]
dst1 = cv2.filter2D(res,ddepth=cv2.CV_16S,kernel=log_fil)
cv2.imwrite('14_log_fil_hand.jpg', dst1*20+200)
plt.savefig("log_fill_1.jpg")
plt.plot(dst1[400]*20+200)

14_log_fil_hand.jpg

log_fill_1.jpg

opencvによる

ddepth = cv2.CV_16S
kernel_size = 5

# Apply Gaussian Blur
blur = cv2.GaussianBlur(res,(5, 5), sigmaX = 1, sigmaY = 1)

# Apply Laplacian operator in some higher datatype
dst = cv2.Laplacian(blur, ddepth, ksize=kernel_size)
cv2.imwrite('15_log_fil_cv.jpg', dst+200)

plt.plot(dst[400])
plt.savefig("log_fill_2.jpg")

15_log_fil_cv.jpg

log_fill_2.jpg

0交差の抽出

def Zero_crossing(image, thresh):
    z_c_image = np.zeros(image.shape)

    # For each pixel, count the number of positive
    # and negative pixels in the neighborhood

    for i in range(1, image.shape[0] - 1):
        for j in range(1, image.shape[1] - 1):
            negative_count = 0
            positive_count = 0
            neighbour = [image[i+1, j-1],image[i+1, j],image[i+1, j+1],image[i, j-1],image[i, j+1],image[i-1, j-1],image[i-1, j],image[i-1, j+1]]
            d = max(neighbour)
            e = min(neighbour)
            for h in neighbour:
                if h>0:
                    positive_count += 1
                elif h<0:
                    negative_count += 1


            # If both negative and positive values exist in 
            # the pixel neighborhood, then that pixel is a 
            # potential zero crossing

            z_c = ((negative_count > 0) and (positive_count > 0) and (d-e > thresh))

            # Change the pixel value with the maximum neighborhood
            # difference with the pixel

            if z_c:
                z_c_image[i, j] = 200


    return z_c_image

dst2 = Zero_crossing(dst1, 10)
cv2.imwrite('16_log_fil_zero.jpg', dst2)

16_log_fil_zero.jpg

Cannyによる検出

1,平滑化

ガウシアンフィルタによる平滑化

2, 縦微分、横微分フィルタによる処理

sobelフィルタを使用している。

3,勾配量と勾配の方向の計算

$$
M(x,y) = \sqrt{G_x^2+G_y^2}
$$
$$
\Theta = atan2 \left(G_y/G_x \right)
$$

4, 閾値処理

誤検出分を取り除くために一定の範囲のみ検出するようにする。

class Canny_hand:
    def __init__(self, image, sigma, h_thresh, l_thresh):
        self.image = image.astype(np.float32)#データ型の指定
        self.h, self.w = image.shape
        self.sigma = sigma
        self.h_thresh = h_thresh
        self.l_thresh = l_thresh
        self.sobelx = np.array([[-1, 0, 1],
                          [-2, 0, 2],
                          [-1, 0, 1]])

        self.sobely = np.array([[-1, -2, -1],
                          [0,  0, 0],
                          [1, 2, 1]])

    def do(self,):
        blur = cv2.GaussianBlur(self.image,(0, 0),  sigmaX = self.sigma, sigmaY = self.sigma)
        Gx = cv2.filter2D(blur, -1, self.sobelx)
        Gy = cv2.filter2D(blur, -1, self.sobely)
        G_value = np.sqrt(np.square(Gy) + np.square(Gx)) 
        G_angle = np.arctan2(Gy, Gx)
        G_angle_set = self.set_angle(G_angle)
        G_max_group = self.extract_max(G_value, G_angle_set)
        result = self.thresh_hold(G_max_group, min=self.l_thresh, max=self.h_thresh)

        return result

    def set_angle(self, G_angle):
        pai = math.pi

        G_angle[np.where((G_angle >= -pai/8) & (G_angle < pai/8))] = 0
        G_angle[np.where((G_angle >= pai/8) & (G_angle < 3*pai/8))] = 45
        G_angle[np.where((G_angle >= 3*pai/8) & (G_angle < 5*pai/8))] = 90
        G_angle[np.where((G_angle >= 5*pai/8) & (G_angle < 7*pai/8))] = 135
        G_angle[np.where((G_angle >= 7*pai/8) & (G_angle < -7*pai/8))] = 180
        G_angle[np.where((G_angle >= -7*pai/8) & (G_angle < -5*pai/8))] = 225
        G_angle[np.where((G_angle >= -5*pai/8) & (G_angle < -3*pai/8))] = 270
        G_angle[np.where((G_angle >= -3*pai/8) & (G_angle < -pai/8))] = 315

        return G_angle

    def extract_max(self, G_value, G_angle_set):
        result = G_value.copy()

        for y in range(1, self.h - 1):
            for x in range(1, self.w - 1):

                if G_angle_set[y][x] == 0:
                    if (G_value[y][x] < G_value[y][x+1]):
                        result[y][x] = 0
                elif G_angle_set[y][x] == 45:
                    if (G_value[y][x] < G_value[y+1][x+1]):
                        result[y][x] = 0
                elif G_angle_set[y][x] == 90:
                    if (G_value[y][x] < G_value[y+1][x]):
                        result[y][x] = 0
                elif G_angle_set[y][x] == 135:
                    if (G_value[y][x] < G_value[y+1][x-1]):
                        result[y][x] = 0
                elif G_angle_set[y][x] == 180:
                    if (G_value[y][x] < G_value[y][x-1]):
                        result[y][x] = 0
                elif G_angle_set[y][x] == 225:
                    if (G_value[y][x] < G_value[y-1][x-1]):
                        result[y][x] = 0
                elif G_angle_set[y][x] == 270:
                    if (G_value[y][x] < G_value[y-1][x]):
                        result[y][x] = 0
                elif G_angle_set[y][x] == 315:
                    if (G_value[y][x] < G_value[y-1][x+1]):
                        result[y][x] = 0

        return result

    def thresh_hold(self, in_put, min=75, max=150, d=1):

        for y in range(0, self.h):
            for x in range(0, self.w):

                if in_put[y][x] >= max:
                    in_put[y][x] = 255

                elif in_put[y][x] < min:
                    in_put[y][x] = 0

                else:
                    if np.max(in_put[y-d:y+d+1, x-d:x+d+1]) >= max:
                        in_put[y][x] = 255
                    else:
                        in_put[y][x] = 0

        return in_put

canny = Canny_hand(res, 0.3,100, 200 )
can_do = canny.do()
cv2.imwrite('17_canny_hand.jpg', can_do)

17_canny_hand.jpg

opencvによる

edges = cv2.Canny(res,0.3,  100, 200)
cv2.imwrite('17_canny.jpg', dst2)

17_canny.jpg

鮮鋭化

$$
元の画像 + (元の画像 - 平滑化後の画像)×定数
$$

# Apply Gaussian Blur
blur = cv2.GaussianBlur(res,(0, 0), sigmaX = 3, sigmaY = 3)
cv2.imwrite('shap_blur.jpg', blur)

diff = res - blur
cv2.imwrite('18_shape_diff.jpg', diff)

shapen = res + diff*3
cv2.imwrite('19_shape_shapen.jpg', shapen)

shap_blur.jpg
18_shape_diff.jpg
19_shape_shapen.jpg

局所平均化

比較的エッジを残したまま平均化できる。
例として、以下の9パターンを計算する。
この中で最も分散が少ない物を選ぶ。

aria_ave.jpg.png

def vari(data):
    count = len(data)
    av = sum(data)/count
    void = []
    total = 0
    for d in data:
        diff = (d - av)**2
        total += diff
    pvariance = total/count

    return pvariance


def selective_ave(image):
    av_image = np.zeros(image.shape)

    for i in range(2, image.shape[0] - 2):
        for j in range(2, image.shape[1] - 2):

            center_neighbour = [image[i+1, j-1],image[i+1, j],image[i+1, j+1],image[i, j-1],
                image[i, j+1],image[i-1, j-1],image[i-1, j],image[i-1, j+1]]

            l_u_neighbour = [image[i-2, j-2],image[i-2, j-1],image[i-1, j-2],image[i-1, j-1],
                image[i-1, j],image[i, j-1],image[i, j]]

            up_neighbour = [image[i-2, j-1],image[i-2, j],image[i-2, j+1],
                image[i-1, j-1],image[i-1, j],image[i-1, j+1],image[i, j]]

            r_u_neighbour = [image[i+2, j+2],image[i+2, j+1],image[i+1, j+2],image[i+1, j+1],
                image[i+1, j],image[i, j+1],image[i, j]]

            right_neighbour = [image[i-1, j+2],image[i, j+2],image[i+1, j+2],
                image[i-1, j+1],image[i, j+1],image[i+1, j+1],image[i, j]]

            left_neighbour = [image[i-1, j-2],image[i, j-2],image[i+1, j-2],
                image[i-1, j-1],image[i, j-1],image[i+1, j-1],image[i, j]]

            l_d_neighbour = [image[i+2, j-2],image[i+2, j-1],image[i+1, j-2],
                image[i+1, j-1],image[i+1, j],image[i, j-1],image[i, j]]

            down_neighbour = [image[i+2, j-1],image[i+2, j],image[i+2, j+1],
                image[i+1, j-1],image[i+1, j],image[i+1, j+1],image[i, j]]

            r_d_neighbour = [image[i+2, j+2],image[i+2, j+1],image[i+1, j+2],
                image[i+1, j+1],image[i+1, j],image[i, j+1],image[i, j]]


            nb_list = [center_neighbour, l_u_neighbour, up_neighbour,
                r_u_neighbour, right_neighbour, left_neighbour,
                l_d_neighbour, down_neighbour, r_d_neighbour]

            number = 0
            vari_max = 0

            for nb in nb_list:
                pre_vari = vari(nb)

                if vari_max < pre_vari:
                    vari_max = pre_vari
                    max_index = number

                number += 1

            result = statistics.mean(nb_list[max_index])

            av_image[i, j] = result

    return av_image

select_av =  selective_ave(res)
cv2.imwrite('20_select_av.jpg', select_av)

20_select_av.jpg

バイラテラルフィルタ(二つのガウス分布を使ったフィルタ)

ガウスフィルタによるの平滑化の実施。
注目点との距離に応じて重みを分布させている(遠いほど軽くなる)
ここまでは通常のガウスフィルタと同じ。
ここでは、さらに、画素間の画素値の差にガウス分布の重みつけを採用している。
つまり(差が大きい(画素値が遠い)ほど、平滑化する重みが軽くなるので、エッジ部(画素値の差が大きい)は平滑化されにくくなり、
結果的にエッジが保存される。

bi = cv2.bilateralFilter(res,15, 20, 20)
cv2.imwrite('21_bilateral_fil.jpg', bi)

21_bilateral_fil.jpg

ノンローカルミーンフィルタ

バイラテラルフィルタが画素値の差に対して重み付をしているのに対して、
ここでは、他の小領域(周囲の画像領域)との類似度の差に対して、重み付をしている。
つまり周辺に似たような画像が多いと平滑化が重くなり、似てない場合は平滑化が軽くなる。

dst = cv2.fastNlMeansDenoising(res, None, 10, 7, 21)
cv2.imwrite('22_non_l_m_fil.jpg', dst)

22_non_l_m_fil.jpg

メディアンフィルタ

フィルタ処理の際に、注目領域の中央値を出力としている。
中央値とは、M×N画素の画像に対して、画素値の小さい画素から数えて、(M×N+1)/2
番目の画素の値のこと。
このフィルタはゴマを振りかけたようなスパイク状のノイズに有効である。

median = cv2.medianBlur(noicy_image, 3)
cv2.imwrite('24_median_fil.jpg', median)

ビフォー
23_spike_noise.jpg

アフター
24_median_fil.jpg

参考にしたサイト
http://optie.hatenablog.com/entry/2018/03/21/185647
https://theailearner.com/2019/05/25/laplacian-of-gaussian-log/
https://algorithm.joho.info/programming/python/opencv-canny-edge-detecte-py/

以上

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6