< 9-1. Image Denoising >
이번 섹션에서는
이미지 내에서 노이즈를 제거하기 위한 “Non-local Means Denoising” 알고리즘에 대해 배워 볼 것이다. 그리고cv2.fastNIMeansDenoising(), cv2.fastNIMeansDenoisingColored() 등의 함수에 대해 알아 볼 것이다.
Theory
이전 챕터에서, Gaussian Blurring, Median Blurring 등과 같은 다양한 이미지 스무딩(smoothing) 기술을 봤었다. 이 기법들은 작은 수의 노이즈들을 제거하는데 좋은 성능을 보였다. 이러한 기법들은, 픽셀 주변의 이웃 픽셀을 조금 골라서, gaussian weighted average, median of the values등을 적용시켜서 중앙 요소를 대체하는 것이다. 요약하자면, 이웃 픽셀에 대해 지역적인 노이즈 제거법이라는 것이다.
일반적으로 노이즈는 평균이 0인 랜덤 변수라고 말한다. 노이즈 픽셀을 생각해 보면,
간단한 설정을 통해 이를 증명할 수 있다. 정적인 카메라를 설치해두고 2초동안 촬영하면 대량의 프레임 혹은 같은 장면인 여러 이미지들을 얻을 수 있다. 그리고 영상내의 모든 프레임의 평균을 구하는 코드를 짜보자. 그리고 최종 결과와 첫 프레임을 비교해보자. 노이즈가 줄어든 것을 볼 수 있다. 불행하게도 이러한 간단한 방법은 카메라와 현장 상황에 대해 매우 영향을 많이 받는다. 또한 종종 노이즈 이미지가 하나밖에 없을 수도 있다.
아이디어가 심플하기에, 노이즈를 평균내기 위해 비슷한 이미지들로 이루어진 세트가 필요하다. 이미지내에서 5x5크기의 창을 생각해보자. 동일한 패치가 이미지의 다른 어딘가에 있을 가능성이 크다. 이 유사한 패치들을 함께 사용해서 그들의 평균을 찾는게 어떨까? 특정 창에 대해서 잘 작동한다. 아래 이미지를 보면서 이해하는 것이 편할 것이다.
이미지내의 파란색 패치들은 유사해보인다. 초록색 패치들또한 유사해보인다. 그래서 이 픽셀들을 가지고, 모든 패치들을 평균하고 결과로 얻은 값으로 픽셀을 대체한다. 이 방법은 “Non-Local Means Denoising”이라고 불린다. 앞선 blurring 기법들 보다는 시간이 더 걸리지만, 결과는 매우 좋다!
색상 이미지에 대해서, 이미지는 CIELAB 색공간으로 변환되고, 이는 개별적으로 L과 AB 성분을 디노이징한다.
Image Denoising in OpenCV
OpenCV는 이 기법의 4개의 변형을 제공한다.
- cv2.fastNIMeansDenoising() - 단일 흑백 이미지에 대해 작동한다.
- cv2.fastNIMeansDenoisingColored() - 컬러 이미지에 대해 작동한다.
- cv2.fastNIMeansDenoisingMulti() - 짧은 시간동안 연속적으로 캡쳐된 이미지에 대해서 작동한다(흑백)
- cv2.fastNIMeansDenoisingColoredMulti() - 위와 같지만, 컬러 이미지에 대한 것!
공통 인자(arg)들은 다음과 같다 :
- h : 필터의 강도를 결정하는 파라미터이다. h값이 높을수록 노이즈들 더 잘 지우지만, 이미지의 디테일도 지워지게 된다. (10이 적당하다)
- hForColorComponents : h와 같지만, 컬러 이미지에만 적용된다.
- templateWindowSize : 홀수여야 한다. (7 추천)
- searchWindowSize : 홀수여야 한다. (21 추천)
이 섹션에서는 2,3 기법에 대해서 해볼 것이다!
1. cv2.fastNIMeansDenoisingColored()
위에서 말했던 것 처럼 컬러 이미지에서 노이즈들 제거할 수 있다. (노이즈는 가우시안 일거라 예상한다.)
import cv2
import numpy as np
import matplotlib.pyplot as plt
orig_img = cv2.imread('./images/pills.jpg')[...,::-1]
dst = cv2.fastNlMeansDenoisingColored(orig_img,None,10,10,7,21)
plt.figure(figsize=(12,8))
plt.subplot(121), plt.imshow(orig_img),plt.title("Original Image")
plt.subplot(122), plt.imshow(dst), plt.title("Denoised Image")
plt.show()
필터 강도는 다 똑같이 10이다. 값을 더 주게 되면 더 smoothing 된 이미지를 얻을 수 있다.
2. cv2.fastNIMeansDenoisingMulti()
이제는 같은 방법을 영상에 대해 적용해 볼 것이다. 첫 인자(입력 변수)는 노이즈가 낀 프레임이다. 두 번째 인자는imgToDenoiseIndex 는 어느 프레임이 디노이징 되어야하는지를 정해주기에, 입력 리스트에 프레임의 인덱스를 넣어줘야 한다. 세번째는 temporalWindowSize로 주위 몇개의 프레임들이 디노이징에 사용될 것 인지를 정한다. 이는 홀수여야 한다. 총 temporalWindowSize 프레임은 중앙 프레임이 디노이징되는 프레임이길 원할 때 사용된다. 예를 들어서, 5개 프레임이 들어있는 리스트를 입력으로 넣었다고 생각해보자. imgToDenoiseIndex =2 이고 temporalWindowSize = 3으로 설정하자. 그러면 프레임-1, 프레임-2, 프레임-3은 프레임-2를 디노이징하기 위해 사용된다! (Index가 2번이기에 2번째, 총 사이즈가 3) 예시를 보자!
cap = cv2.VideoCapture('./videos/landing.mp4')
# 첫 5 프레임을 리스트에 넣기
img = [cap.read()[1] for i in range(5)]
# 흑백스케일로 변환
gray = [cv2.cvtColor(i,cv2.COLOR_BGR2GRAY) for i in img]
# 전부 float64로 변환
gray = [np.float64(i) for i in gray]
# 분산이 25인 노이즈를 생성하기
noise = np.random.randn(*gray[1].shape)*10
# 이미지에 노이즈 더하기
noisy = [i+noise for i in gray]
# 다시 uint8로 변환
noisy = [np.uint8(np.clip(i,0,255)) for i in noisy]
# 전체 5개 프레임 중 3번째 프레임을 디노이징하자 여기선 idx가 0,1,2
# h가 filter strength이기에 노이즈를 더 지우고 싶다면 값을 크게 주면 된다.
dst = cv2.fastNlMeansDenoisingMulti(noisy,2,5,None,10,7,35)
plt.figure(figsize=(20,20))
plt.subplot(311), plt.imshow(gray[2],'gray'), plt.title("Original Image")
plt.subplot(312), plt.imshow(noisy[2],'gray'), plt.title("Noisy")
plt.subplot(313), plt.imshow(dst,'gray'), plt.title("Result")
plt.show()
결과는 다음과 같다.
이는 연산에 상당한 시간이 소요된다. 결과로, 처음 이미지는 원본 프레임, 두 번째는 노이즈를 추가한 이미지, 세 번째는 디노이징 된 이미지이다.