본문 바로가기

컴퓨터 비전/실습

Bilateral Filter 직접 구현하기(python)

Bilateral Filter란

이번 포스팅에서는 가우시안 노이즈가 낀 사진을 필터링해주는 Bilateral Filter를 opencv를 사용하지 않고

python으로 직접 구현해 보는것 위주로 다루겠습니다.

Bilateral Filter는 기존 Gaussian Filter와는 다르게 단순히 가우시안 분포(거리에 따라 가중치가 작아짐)로

사진을 복원하는것이 아니라 현재 관심있는 구역의 상태에 따라 동적으로 반영하여 계속 달라지는 필터로 사진을 복원합니다.

예를 들어 원본이 256 * 256 Gray 사진이라면 Gaussian Filter는 Filter가 딱 한개면 되는것에 비해 Bilateral Filter는 무려 약 65536개 만큼 필터를 개별 맞춤 제작해주어야 합니다. 그렇기 때문에 시간이 오래 걸리는데요. 하지만 엣지에서 효과가 Gaussian Filter보다 더 선명하여 단점을 보안한 필터라고 할수 있겠습니다. 코드와 주석을 보시겠습니다.

 

Code

import cv2
from matplotlib.style import library
import numpy as np
import math
from matplotlib import pyplot as plt
from copy import deepcopy
import random

def G_noise(img, s):
    m, n = img.shape
    noise = np.random.normal(0, s**2, m * n)
    noise = noise.reshape(m, n) # make Gaussian noise ( m * n ) 
    return noise
    
def Psnr(img1, img2):
    m, n = img1.shape
    
    mse = 0

    for i in range(m):
        for j in range(n):
            mse = mse + ((img1[i][j] - img2[i][j]) ** 2.0) # sum of square errors 
    mse = mse / (m * n) # definition of mean square error
	return 20 * math.log10(255 / math.sqrt(mse)) # scaling mse to log scale

def G_filter(k, sigma, v):

    assert k % 2 == 1, 'kernel_size must be odd' # filter must be odd_size
    
    n = int((k - 1) / 2) # number to get range of filter
    l = []
    
    for i in range(-1 * n, n + 1):
        for j in range(-1 * n, n + 1):
            c = -1 * ( ( i**2 + j**2 ) / (2 * sigma**2) ) 
            # definition of Gaussian filter
            l.append(c)
               
    gaussian = np.exp(l).reshape(-1, k) # take exponential
    sum = gaussian.sum() # normalization factor

    if v == 1:
        return gaussian / sum # version #1
    if v == 2:
        return gaussian # version #2

def padding(img, n):

    x, y = img.shape # original image size

    padding_img = np.zeros((x+2*n, y+2*n)) # consider up,down,left,right

    padding_img[n:x+n, n:y+n] = img # zero padding

    return padding_img

def conv(img, filter):
   
    k = len(filter) # kernel size
    n = int((k - 1) / 2) # padding number
    x, y = img.shape # original image size

    a = padding(img, n)

    for i in range(n, x+n):
        for j in range(n, y+n):
            a[i][j] = np.multiply(a[i-n:i+n+1, j-n:j+n+1], filter).sum() # convolution operation
     
    return a[n:x+n, n:y+n] # return result image ,except the padding part

def Bi_filtered(img, k, sigma_s, sigma_r):
    
    n = int((k - 1) / 2) # padding number
    x, y = img.shape

    G = G_filter(k, sigma_s, 2) # get Gaussian filter

    a = padding(img, n) # padding

    for i in range(n, x+n):
        for j in range(n, y+n):

            b = Bi(a, k, sigma_r, i, j) # get Bilateral filter at present position

            filter = np.multiply(G, b) # multiply Gaussian and Bilateral
            filter = filter / filter.sum() # normalization

            a[i][j] = np.multiply(a[i-n:i+n+1, j-n:j+n+1], filter).sum() # convolution operation
    
    return a[n:x+n, n:y+n]

def Bi(img, k, sigma_r, x, y):

    n = int((k - 1) / 2) # number to get range of filter    

    l = []

    for i in range(-1 * n, n + 1):
        for j in range(-1 * n, n + 1):
            c = -1 * ( ((img[x][y] - img[x+i][y+j])**2) / (2 * (sigma_r)**2) ) 
            # definition of Bilateral
            l.append(c)

    bilateral = np.exp(l).reshape(-1, k)
    
    return bilateral

def conv(img, filter):
   
    k = len(filter) # kernel size
    n = int((k - 1) / 2) # padding number
    x, y = img.shape # original image size

    a = padding(img, n)

    for i in range(n, x+n):
        for j in range(n, y+n):
            a[i][j] = np.multiply(a[i-n:i+n+1, j-n:j+n+1], filter).sum() # convolution operation
     
    return a[n:x+n, n:y+n] # return result image ,except the padding part


imageFile = '/home/lks9909/image_project1/lena.bmp'
img1 = cv2.imread(imageFile, 0)

noise = G_noise(img1, 5)

img2 = img1 + noise

img3 = conv(img2, G_filter(7,5,1))

img4 = Bi_filtered(img2,7,50,50)



plt.subplot(2,2,1)
plt.imshow(img1, vmin=0, vmax=255, cmap='gray')


plt.subplot(2,2,2)
plt.imshow(img2, vmin=0, vmax=255, cmap='gray')


plt.subplot(2,2,3)
plt.imshow(img3, vmin=0, vmax=255, cmap='gray')

plt.subplot(2,2,4)
plt.imshow(img4, vmin=0, vmax=255, cmap='gray')

plt.show()

print('Gaussian psnr:', Psnr(img1,img3))
print('Bilateral psnr:', Psnr(img1,img4))

 

결과 비교

원본 원본 + Gaussian Noise
Gaussian Filter 적용 Bilateral Filter 적용

Gaussian Filter는 노이즈가 줄어들긴 했으나 원본에서 블러(blur)가 많이된 것에 비해

Bilateral Filter는 노이즈 제거와 엣지부분에서 선명함을 확인할수 있습니다.

 

역시 PSNR(최대 신호 대 잡음비)도 더 높음을 확인할수 있습니다.

PSNR

감사합니다.

'컴퓨터 비전 > 실습' 카테고리의 다른 글

prewitt, sobel, canny 구현(python)  (0) 2023.02.10
mean shift 직접 구현하기(python)  (0) 2023.01.31
kmeans 직접 구현하기(python)  (1) 2023.01.25
SIFT 직접 구현하기(python)  (0) 2023.01.25