본문 바로가기
Developer/OpenCV

[OpenCV] Blur을 통한 이미지 노이즈 제거 (컨볼루션, 가우시안, 미디언)

by Doony 2020. 8. 16.

이번 포스팅에서는 이미지를 흐릿하게 만드는 Blur 효과에 대해 알아보겠습니다. 본 포스팅은 이세우님의 '파이썬으로 만드는 openCV 프로젝트' 책을 참고했습니다.
아래와 같이 보통 blur 효과를 사용하면, 이미지가 흐릿해집니다.


좌측은 원본 이미지이고, 우측은 블러링 효과를 준 이미지입니다. 포토샵 등의 툴에서도 흔히 사용하는 기능이며, 주요 정보를 가리고 싶을 때 많이들 사용하는데요. blur가 들어가는 원리를 이해하면, 가리는 것(흐려지게 하는 것) 뿐만 아니라 노이즈 제거도 할 수 있다는 것을 알 수 있습니다.


blur의 기본 원리

이미지 또는 영상을 흐릿하게 만드는 원리는, 다음과 같은 kernel를 전체 이미지에 convolution을 돌림으로써, 커널 사이즈에 맞게 픽셀 평균값을 대입하는 것입니다.
즉, 특정 픽셀의 주변 값의 평균(또는 임의의 array 연산)값을 해당 픽셀에 넣는 것입니다.
이미지

(출처: https://developer.apple.com/documentation/accelerate/blurring_an_image)


2D Convolution kernel의 원리는 딥러닝에서도 CNN이라는 유명 알고리즘에 사용되는 동일한 방식입니다. 그 만큼 많이 쓰이면서도 단순한 원리입니다.


2D Convolution Kernel

kernelwindow, filter, mask 등 다양한 이름으로 불리고 있는데요. 컨볼루션 연산을 openCV에서는 아래와 같이 제공하고 있습니다.

  • cv2.filter2D(src, ddepth, kernel)

전체 코드는 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import cv2
import numpy as np
 
img = cv2.imread('./noise.jpg')
 
= np.array([[1,1,1],
               [1,1,1],
               [1,1,1]]) * (1/9)
blur = cv2.filter2D(img, -1, k)
 
 
merged = np.hstack((img, blur))
cv2.imshow('blur', merged)
cv2.waitKey(0)
cv2.destroyAllWindows()
cs

결과 이미지는 다음과 같습니다.


좌측은 원본, 우측은 컨볼루션 필터를 먹인 이미지입니다. 약간 흐릿해진 것을 볼 수 있습니다. (3,3) 형태의 커널에 모두 동일한 값을 넣고, 픽셀 값의 수준을 유지하기 위해 1/9로 나눠준 것입니다.
이렇게 동일한 값으로 이루어진 커널로 blur를 넣는 것을 평균 블러링이라고 합니다.


커널이 꼭 같은 값이어야하는 것은 아닙니다. 각 행렬에 따라 가우시안 분포를 가지는 커널을 넣을 수도 있는데, 이를 가우시안 블러링이라고 합니다.
가우시안 분포는 정규분포의 다른 이름으로, 쉽게 이해하자면 중간값이 가장 크고(많고), 끝으로 갈수록 적어지는 분포입니다.
가우시안분포
(출처: 위키피디아)


코드에 적용하면 다음과 같습니다. 컨볼루션을 돌리되, 커널에 가우시안 분포를 적용했습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import cv2
import numpy as np
 
img = cv2.imread('./noise.jpg')
 
= np.array([[1,2,1],
               [2,4,2],
               [1,2,1]]) * (1/16)
blur = cv2.filter2D(img, -1, k)
 
 
merged = np.hstack((img, blur))
cv2.imshow('blur', merged)
cv2.waitKey(0)
cv2.destroyAllWindows()
cs

결과 이미지는 다음과 같습니다.

커널값을 보시면, 각 행과 열이 중간값이 가장 크게 지정된 것을 알 수 있습니다. 또한 픽셀의 값 수준 유지를 위해 전체 합인 16으로 나눠주었습니다.
이렇게 직접 가우시안 분포를 따르는 커널을 만들 수도 있지만, openCV에서는 함수로 가우시안 블러링을 제공하고 있습니다.


코드는 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import cv2
import numpy as np
 
img = cv2.imread('./noise.jpg')
 
k2 = cv2.getGaussianKernel(30)
blur2 = cv2.filter2D(img, -1, k2*k2.T)
 
blur3 = cv2.GaussianBlur(img, (9,9), 0)
 
merged = np.hstack((img, blur2, blur3, blur3))
 
cv2.imshow('blur', merged)
cv2.waitKey(0)
cv2.destroyAllWindows()
cs

크게 2가지 방식이 있습니다.

  • getGaussianKernel(ksize, sigma): 커널만 생성하는 함수입니다. 1차원이기 때문에, 2차원으로 변환하기 위해서는 transpose된 행렬과의 곱셈으로 대입해야 합니다.
  • GaussianBlur(src, ksize, sigmaX): 이미지에 가우시안 처리까지 한방에 해줍니다. 커널 사이즈와 시그마만 입력하면 자동으로 이미지 혹은 영상을 변환합니다.

결과는 다음과 같습니다.

좌측은 원본, 중간은 1차원 커널(3,3), 마지막은 (9,9) 가우시안 블러링을 사용했습니다. 차이점이 보이시나요?
범위가 넓어질수록 당연히 더 블러링 효과가 넓어지고, 노이즈가 제거되는 것을 볼 수 있습니다.

실제 이미지의 노이즈 제거를 위해서는 적당한 커널과 시그마를 찾는 과정이 필요할 것 같습니다. 과도하게 노이즈를 줄이다가는 위 사진처럼 원본 이미지 자체가 너무 흐릿해지는 부작용이 발생할 수 있습니다.


미디언 블러링

가우시안 블러링과 다르게, 커널 영역의 한 픽셀을 선택해서 그대로 넣는다는 데서 차이점이 있습니다. 즉, 기존의 블러링을 말그대로 픽셀값의 평균(또는 유사한 연산)을 통해 결과물을 얻었다면, 미디언은 중간값에 해당하는 픽셀값을 선택하는 개념입니다.

코드와 결과는 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import cv2
import numpy as np
 
img = cv2.imread('./noise.jpg')
 
blur3 = cv2.GaussianBlur(img, (9,9), 0)
 
 
blur4 = cv2.medianBlur(img, 5)
 
merged = np.hstack((img, blur3, blur4))
 
cv2.imshow('blur', merged)
cv2.waitKey(0)
cv2.destroyAllWindows()
cs


좌측부터 원본, 가우시안 블러링, 미디언 블러링입니다.
미디언의 경우 실제 픽셀 값 하나를 선택해 넣은 것이기 때문에 가우시안보다 노이즈는 제거되면서 좀 더 선명한 것을 볼 수 있습니다.
즉, salt-and-pepper 즉 소금 후추를 뿌린 것 같은 잡음 제거에는 미디언 블러링이 보다 효과적입니다.


바이레터럴 필터 Bilateral filter

위에서 설명한 블러링은 어쨌든 경계가 흐릿해지는 문제가 있습니다. 가우시안 필터와 경계 필터 2가지를 사용한다고 하는데, 그런만큼 연산량이 많아 속도가 느려진다는 단점이 있습니다.

  • cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace)

코드는 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import cv2
import numpy as np
 
img = cv2.imread('./noise.jpg')
 
 
blur4 = cv2.medianBlur(img, 5)
 
blur5 = cv2.bilateralFilter(img, 57575)
 
merged = np.hstack((img, blur4, blur5))
 
cv2.imshow('blur', merged)
cv2.waitKey(0)
cv2.destroyAllWindows()
cs

좌측부터 원본, 미디언 블러링, 바이레터럴 필터입니다. 바이레터럴 필터가 노이즈도 잡으면서 경계도 더 선명한 것을 볼 수 있습니다.
바이레터럴 필터에서 사용되는 경계 필터에 대해서는 다음에 상세히 다뤄보도록 하겠습니다.

댓글