이미지에서 노이즈 제거하는 디노이징 알고리즘, NLmeans 설명 (python 코드 포함)


728x90

가장 간단하게 이미지에서 노이즈를 제거하는 방법은 예전에 소개해드렸던 Gaussian 필터, Median 필터, Bilateral 필터를 사용하는 것입니다. Gaussian, Median, Bilateral 필터 처리는 모두 이웃한 주변 픽셀들의 값을 가지고 현재 픽셀의 값을 바꿔준다는 특징을 갖고 있습니다. 예를 들어, Median 필터는 현재 픽셀의 주변 이웃들의 중앙값(median)을 구해서 그것을 현재 픽셀의 값으로 삼아줍니다. 

 

미디안 필터 설명.

 

그런데 이러한 Gaussian, Median, Bilateral 필터처리를 이용해서 노이즈를 제거하는 것은 한계가 많습니다. median 필터만 해도 salt-and-pepper와 같이 위치 상 떨어져 있는 픽셀들에 노이즈가 낀 경우에만 성공적일 뿐 노이즈가 여러 픽셀에 걸쳐있는 경우에는 제대로 노이즈를 제거해내지 못합니다.

 

그래서 더 나은 노이즈 제거 방법을 찾기 위해 연구자들은 계속해서 연구를 해왔습니다. 오늘은 이 세상에 존재하는 수많은 디노이징 방법 중에 하나를 소개해드리려고 합니다. 이름은 NLmeans (Non-Local Means Denoising)입니다. 이 방법은 opencv 라이브러리에도 포함되어 있습니다. 함수명은 cv2.fastNlMeansDenoising()입니다.

 

NLmeans는 다음과 같이 작동합니다. 여기서부터 중요하니 집중해서 읽어주세요.^^

 

1) 현재 수정할 픽셀을 중간 위치에 포함하고 있는 패치와 각 픽셀을 중간 위치에 포함하고 있는 패치들의 유사도를 비교합니다. 어떤 패치들은 현재 수정할 픽셀을 담고 있는 패치와 유사도가 클 것이고, 어떤 패치들은 유사도가 작을 것입니다. 유사도가 크다는 말은 서로 닮았다는 뜻입니다.

2) 유사도가 큰 패치의 픽셀에 더 큰 가중치를 줘서, 각 픽셀의 값을 더한 것을 현재 픽셀의 값으로 삼아줍니다.

 

아래 그림을 가지고 설명을 해보겠습니다. 우선 이 그림에서 파란색 패치들은 서로 닮았습니다. 반면 파란색 패치들은 연두색 패치들과는 별로 닮지 않았습니다. 또한 초록색 패치들과도 닮지 않았습니다. 따라서, 파란색 패치의 중간에 위치한 픽셀의 값을 수정할 때는 파란색 패치들의 중간에 위치한 픽셀들에 더 큰 가중치를 줘서, 각 픽셀의 값을 더해줍니다. 가중평균(weighted mean)을 낸다는 뜻입니다. 또한, 초록색 패치 중간에 위치한 픽셀의 값을 수정할 때는 초록색 패치 중간에 위치한 픽셀들에 더 큰 가중치를 줘서 평균을 냅니다. 그것이 초록색 패치 중간에 위치한 픽셀의 값이 되는 것입니다. 

 

이미지 출처: [1]

 

좀 더 이해를 돕기 위해 다른 그림을 하나 더 보여드리겠습니다. 지금 p 픽셀의 값을 수정할 차례라고 가정해보겠습니다. q1, q2 픽셀을 담고 있는 패치들은 p 픽셀을 담고 있는 패치와 꽤 닮았습니다. 반면 p 픽셀을 담고 있는 패치와 q3 픽셀을 담고 있는 패치는 별로 안 닮았습니다. 따라서 p 픽셀의 값은 0.005*q1 + 0.005*q2 + 0.0000001*q3 + ...  이런식으로 결정됩니다. q1, q2에 부여된 가중치가 q3에 부여된 가중치보다 훨씬 크죠? 만약 이미지내 픽셀의 갯수가 240000(600x400)개가 있다면, p = 0.005*q1 + 0.005*q2 + 0.0000001*q3 + ... + 0.0000002*q239999와 같이 계산될 것입니다. 모든 픽셀의 값을 수정하려면, 이러한 연산이 총 240000번 반복되어야 합니다. 이것이 바로 NLmeans의 원리입니다. 

 

이미지 출처: [2]

 

간단히 지역적으로(locally) 주변 픽셀들을 참고했던 Gaussian, Median, Bilateral 필터처리와 달리 이 방법은 Non-local, 즉 지역을 벗어나서 전체를 고려해서 현재 픽셀의 값을 수정해줍니다. 이웃 픽셀들이 아닌 전체 픽셀들의 가중평균으로 각 픽셀의 값을 구해주기 때문에 NLmeans라는 이름이 붙은 것입니다. NLmeans는 Gaussian, Median, Bilateral 방법에 비해 훨씬 무거운 방법(시간 비용이 큰 방법)이긴 하지만, 성능은 그만큼 좋은 편입니다. 실시간 작업에는 사용하기 어렵지만, 정확도를 추구하는 작업에는 쓸만합니다.  

 

파이썬 코드 실습

opencv 패키지의 cv2.fastNlMeansDenoising() 함수를 이용해서 다음 이미지의 노이즈를 제거해보도록 하겠습니다. 

 

 

필요한 파이썬 코드는 다음과 같습니다. 

 

1
2
3
4
5
6
7
8
9
10
11
12
import cv2
 
img = cv2.imread("test1.jpg", cv2.IMREAD_COLOR)
 
'노이즈 제거'
denoised_img = cv2.fastNlMeansDenoisingColored(img, None1515510)
 
cv2.imshow("before", img)
cv2.imshow("after", denoised_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
 
cs

 

코드에서 가장 중요한 부분은 바로 6번째 행입니다. 

 

 

컬러이미지에서 노이즈를 제거하는 경우 사용자가 설정해줘야할 파라미터가 4개가 있습니다. 그 4개 값을 적절히 설정해주면 됩니다. 그러면 위 코드를 실행해보겠습니다. 

 

 

보시다시피 NLmeans는 꽤 노이즈를 잘 제거해냄을 알 수 있습니다. 물론 이미지에서 지워지지 말아야할 디테일한 부분들도 지워지긴 했지만요. 동일한 이미지에 대해 Gaussian, Median, Bilateral 필터를 적용하면 어떤 결과가 나오는지도 확인해보겠습니다. 사용한 파이썬 코드와 실행 결과는 각각 다음과 같습니다. 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import cv2
 
img = cv2.imread("test1.jpg", cv2.IMREAD_COLOR)
 
'노이즈 제거'
denoised_img1 = cv2.fastNlMeansDenoisingColored(img, None1515510# NLmeans
denoised_img2 = cv2.GaussianBlur(img, (55), 0# Gaussian
denoised_img3 = cv2.medianBlur(img, 5# Median
denoised_img4 = cv2.bilateralFilter(img, 55050# Bilateral
 
cv2.imshow("before", img)
cv2.imshow("after(NLmeans)", denoised_img1)
cv2.imshow("after(Gaussian)", denoised_img2)
cv2.imshow("after(Median)", denoised_img3)
cv2.imshow("after(Bilateral)", denoised_img4)
 
cv2.waitKey(0)
cv2.destroyAllWindows()
cs

 

 

Gaussian, Median, Bilateral 필터들은 노이즈를 제대로 제거해내지 못했습니다. 오히려 이미지에서 중요한 부분들을 많이 지워버렸습니다. 이 이미지에 대해서는 NLmeans가 훨씬 더 만족스럽게 노이즈를 제거해낸다는 것을 명확하게 알 수 있습니다. 

 

누군가에게 이 글이 조금이나마 도움이 되었길 바라며, 글을 맺겠습니다. 혹여나 제 설명이 틀린 부분이 있다면 알려주시면 감사하겠습니다.^^ 

 

 

b스카이비전의 추천글

[Learn opencv by examples] 6. Gaussian 필터, Bilateral 필터, Median 필터

자료를 대표하는 숫자, 대표값: 평균, 중앙값, 최빈값

 

 

<참고자료>

[1] opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_photo/py_non_local_means/py_non_local_means.html, OpenCV, "Image Denoising"

[2] Buades, Antoni, Bartomeu Coll, and J-M. Morel. "A non-local algorithm for image denoising." 2005 IEEE Computer Society Conference on Computer Vision and Pattern Recognition (CVPR'05). Vol. 2. IEEE, 2005.