IDL/Image Processing

이미지에서 노이즈(Noise) 제거하기 [1]

이상우_idl 2021. 6. 15. 11:29
728x90
반응형

Noise라고 하는 것은 우리말로는 흔히 잡음이라고 부르기도 합니다. 넓은 의미로 본다면 우리가 데이터로서 얻게 되는 Signal(신호) 내에서 배경에 존재하는 말그대로 잡음과 같은 지저분한 형상들을 뜻합니다. 통상적으로 이러한 잡음은 제거해야 할 대상이 됩니다. 그래야 깨끗한 신호를 데이터로서 얻을 수 있기 때문입니다. 1차원적인 신호라 할 수 있는 음파(Sound Wave)의 예를 든다면 잡음이 많이 섞인 음성 신호에 대하여 잡음 제거 처리를 함으로써 좀 더 깨끗한 음질의 신호를 얻게 됩니다. 2차원적인 신호라고 할 수 있는 이미지(Image)의 경우도 마찬가지입니다. 우리가 이미지의 형태로 얻는 데이터를 보면 의미있는 형체 뿐 아니라 잡음(노이즈)과 같은 불필요하고 지저분한 형체들도 함께 뒤섞여있는 경우가 많습니다. 따라서 이미지 처리 분야에서는 이러한 노이즈를 이미지 상에서 효과적으로 제거하기 위한 여러가지 기법들이 존재합니다. 그리고 IDL에서도 이러한 노이즈 제거 처리를 위한 여러가지 기능 함수들이 내장되어 있습니다. 그래서 IDL에서 사용 가능한 여러가지 노이즈 제거 기법들에 대하여 예제와 함께 소개해보고자 합니다.

 

먼저 노이즈가 어느 정도 포함된 예제 이미지가 필요한데, 사실 그런 이미지를 실제로 찾기가 쉽지가 않습니다. 그래서 여기서는 멀쩡한 이미지에 인위적으로 노이즈를 추가하여 예제 이미지로 사용하고자 합니다. 물론 이미지 상에 존재하는 노이즈라는 것이 그 형태나 종류가 매우 다양하기 때문에, 여기서는 두가지 케이스를 가정하였습니다. 일단 기본이 되는 "멀쩡한" 이미지는 IDL의 설치 디렉토리에서 이미 제공되는 moon_landing.png라는 파일에서 가져오고, 여기에 인위적인 노이즈를 추가하는 방식으로 다음과 같이 생성합니다.

 

file = FILEPATH('moon_landing.png', SUBDIRECTORY=['examples', 'data'])
img0 = FLOAT(READ_PNG(file))
sz = SIZE(img0, /DIM)
noise = RANDOMN(-1, sz[0], sz[1], /UNIFORM)
img_a = img0
img_a[WHERE(noise GT .98)] = 255
xCoords = LINDGEN(sz[0], sz[1]) MOD sz[0]
yCoords = TRANSPOSE(xCoords)
noise = -SIN(xCoords*2)-SIN(yCoords*2)
img_b = img0+50*noise+100
HELP, img_a
PRINT, MIN(img_a), MAX(img_a)
HELP, img_b
PRINT, MIN(img_b), MAX(img_b)

 

이와 같이 img_a, img_b 두가지 이미지 데이터를 생성하였습니다. 각 데이터는 300x300의 실수형 2차원 배열이며, HELP 및 PRINT에 의하여 출력된 기본 정보는 다음과 같습니다.

 

IMG_A           FLOAT     = Array[300, 300]
      0.00000      255.000
IMG_B           FLOAT     = Array[300, 300]
    0.0714417      454.983

 

그러면 먼저 두 이미지의 모습을 다음과 같은 과정에 의하여 하나의 그래픽창 내에 나란히 표출해봅시다.

 

win1 = WINDOW(DIMENSIONS=[sz[0]*2, sz[1]], /NO_TOOLBAR)
im1_a = IMAGE(img_a, MARGIN=0, /CURRENT, LAYOUT=[2, 1, 1])
im1_b = IMAGE(img_b, MARGIN=0, /CURRENT, LAYOUT=[2, 1, 2])

 

표출된 모습은 다음 그림과 같습니다.

 

 

이와 같이 img_a는 마치 점과 같은 형체들이 이미지 전반에 걸쳐 노이즈처럼 퍼져있는 모습이고, img_b의 경우는 줄무늬와 같은 패턴의 노이즈가 이미지 전반에 걸쳐있는 모습입니다. 이제 이러한 두 종류의 이미지에 대하여 여러가지 노이즈 제거 기법들을 적용해봅시다. 제가 지금부터 소개할 기법들은 두가지 종류로 나눠보았습니다. 첫번째 카테고리는 컨볼루션(Convolution)을 기반으로 한 기법들이고 두번째 카테고리는 FFT(Fast Fourier Transform)를 기반으로 한 기법들입니다. 오늘은 먼저 첫번째 카테고리인 컨볼루션 기반의 기법들부터 살펴보겠습니다.

 

컨볼루션(Convolution)이라는 개념은 1차원이든 2차원이든 차원을 가리지않고 적용 가능한 기법이며, 특정한 크기의 커널(Kernel)이라고 하는 것을 데이터 전반에 걸쳐 적용하여 결과를 얻는 과정으로서 흔히 필터링(Filtering)이라고도 부릅니다. 이미지를 예로 들자면 원본 이미지에 대하여 블러링(Blurring) 또는 스무딩(Smoothing) 처리를 하여 화소값들을 뭉개는 경우, 샤프닝(Sharpening) 처리를 하여 화소값들간의 차이를 더 강조하는 경우, 화소값의 차이가 급격하고 변하는 부분을 일종의 경계선 또는 엣지(Edge)로 인식하는 경우 등 그야말로 무수히 많은 케이스들이 존재합니다. 따라서 컨볼루션이란 개념 자체는 훨씬 더 넓고 다양한 처리 기법들을 포함하지만, 여기서는 노이즈 제거와 관련된 것들만 몇가지 추려서 소개해보고자 합니다.

 

가장 먼저 소개할 기법은 앞서 잠시 언급된 블러링(Blurring) 또는 스무딩(Smoothing)이라고 부르는 처리 기법입니다. 이러한 기법에서는 3x3, 5x5 등과 같은 크기의 커널(Kernel)을 정의하여 이러한 커널이 이미지의 전 영역에 걸쳐 순차적으로 곱해지면서 결과를 산출하는 방식으로 처리됩니다. 이 과정을 제가 여기서 컨볼루션의 관점에서 다 설명하는 것은 너무 번거롭기 때문에서, 그냥 해당 기능 함수를 사용하여 결과를 얻는 과정으로 바로 넘어가겠습니다. IDL에서는 SMOOTH 함수를 사용하여 이러한 처리를 할 수 있는데, 두 이미지 각각에 대하여 다음과 같이 적용해봅시다.

 

img_f_a = SMOOTH(img_a, 3, /EDGE_TRUNCATE)
img_f_b = SMOOTH(img_b, 3, /EDGE_TRUNCATE)

HELP, img_f_a
PRINT, MIN(img_f_a), MAX(img_f_a)
HELP, img_f_b
PRINT, MIN(img_f_b), MAX(img_f_b)

 

여기서 3이라는 숫자는 3x3의 평균값 필터링 커널을 사용하라는 의미입니다. 즉 이미지 전 영역에 걸처 3x3의 섹터마다 평균 화소값을 산출하는 방식의 컨볼루션 처리를 하라는 것이기 때문에 원래의 화소값들에 비하여 더 뭉개진(?) 값들로 환산됩니다. 이러한 결과는 시각적으로는 이미지의 화소값들이 뭉개지면서 전반적으로 부드럽게 보이는 효과로 나타나게 되는데요. 노이즈에 해당되는 픽셀들은 주로 주변과의 화소값 차이가 큰 경우가 많습니다. 따라서 이러한 "뭉개기" 처리를 하면 노이즈 픽셀들의 튀는 화소값들을 완화시켜주는 효과를 얻게 됩니다. 위의 과정에 의하여 결과로 얻은 img_f_a, img_f_b 두 배열들을 다음과 같이 나란히 표출해봅시다.

 

win2 = WINDOW(DIMENSIONS=[sz[0]*2, sz[1]], /NO_TOOLBAR)
im2_a = IMAGE(img_f_a, MARGIN=0, /CURRENT, LAYOUT=[2, 1, 1])
im2_b = IMAGE(img_f_b, MARGIN=0, /CURRENT, LAYOUT=[2, 1, 2])

 

그 모습은 다음 그림과 같습니다.

 

 

이 모습을 보면 나름 나쁘지 않은 결과를 얻은 것 같기도 합니다. 물론 자세히 보면 약간의 차이는 있습니다. 특히 좌측 결과의 경우는 잔여 노이즈 형체들이 아직 남아있는 상태로서 완전히 다 제거되지는 못한 것으로 보입니다. 우측 결과의 경우는 의외로 제거가 그럭저럭 잘 된 것으로 보입니다. 이와 같이 SMOOTH 함수도 어느 정도의 노이즈 제거 효과는 있습니다. 물론 노이즈의 형태에 따라서 그 효과는 달라질 수 있습니다. 그리고 이런 결과를 얻을 때에는 각 결과 배열의 화소값 범위도 함께 확인해보는 것도 좋습니다. HELP 및 PRINT에 의하여 출력된 내용은 다음과 같습니다.

 

IMG_F_A         FLOAT     = Array[300, 300]
 -1.37289e-05      253.889
IMG_F_B         FLOAT     = Array[300, 300]
      69.8299      358.314

 

방금 사용한 SMOOTH 함수는 내부적으로 평균값 필터링(Mean Filtering) 알고리즘이 사용됩니다. 그런데 이러한 알고리즘이 사용되는 함수가 또 있는데 바로 MEAN_FILTER 함수입니다. 이 함수에 의한 기본적인 처리 방법은 다음과 같습니다.

 

img_f_a = MEAN_FILTER(img_a, 3)
img_f_b = MEAN_FILTER(img_b, 3)

 

이 과정은 3x3 크기의 커널을 사용하여 산술평균(Arithmetic Mean) 기반의 평균값 필터링에 의하여 결과를 산출하라는 의미입니다. 사실 이렇게 처리한 결과는 앞서 우리가 SMOOTH 함수를 사용한 경우와 거의 유사합니다. 그 대신 이번에는 다음과 같이 처리해봅시다.

 

img_f_a = MEAN_FILTER(img_a, 3, /GEOMETRIC)
img_f_b = MEAN_FILTER(img_b, 3, /GEOMETRIC)

 

여기서는 GEOMETRIC 키워드가 사용되었는데 이는 기하평균(Geometric Mean) 기반의 평균값 필터링에 의하여 결과를 산출하라는 의미입니다. 기하평균은 산술평균과는 계산 방식이 다르기 때문에 이 결과도 좀 다르게 나타납니다. 어차피 img_f_a, img_f_b를 표출하는 과정은 앞서 제시된 것과 동일하기 때문에 결과 그림을 바로 봅시다. 그 모습은 다음과 같습니다.

 

 

이 그림을 보면 좌측의 결과는 나름 나쁘지 않아 보이는 반면 우측의 결과에서는 줄무늬같은 노이즈의 형체가 그래도 아직 남아있는 것을 볼 수 있습니다. 이와 같이 처리 기법에 따라서 결과는 얼마든지 달라질 수 있습니다. 어쩌면 함수 내에서 키워드의 값 조정 또는 특정 키워드 사용 여부 등에 따라서도 달라질 수 있으므로, 이런 부분에 대해서는 여러분이 직접 테스트를 해보시기 바랍니다.

 

이번에는 평균값 필터링 대신 중간값 필터링(Median Filtering) 알고리즘을 적용해봅시다. 이를 위해서는 MEDIAN 함수를 사용하면 되며 그 과정은 다음과 같습니다.

 

img_f_a = MEDIAN(img_a, 3)
img_f_b = MEDIAN(img_b, 3)

 

여기서는 3x3의 커널을 적용하되 평균값 대신 중간값을 산출하게 됩니다. 중간값(Median)이라는 것은 크기 순으로 정렬했을 때 가운데 순위에 해당되는 값을 의미합니다. 따라서 평균의 경우에는 대상 배열 내의 모든 값을 더해서 갯수로 나누는 식으로 값을 "뭉개는" 방식인 반면, 중간값은 중간 순위에 해당되는 값을 가져오는 방식이기 때문에 어떻게든 원본 데이터에 존재하던 값이 선택됩니다. 따라서 중간값 필터링 처리의 결과는 "뭉개진" 느낌이 덜합니다. 위의 처리 결과들이 표출된 모습을 보면 다음 그림과 같습니다.

 

 

여기서 좌측의 결과를 보면 노이즈 픽셀들의 제거가 꽤 효과적으로 수행되었다는 느낌이 듭니다. 반면 우측의 결과는 어떻게 보면 상태가 더 안좋아진 것처럼 보일 정도로 결과가 안좋습니다. 이것도 결국 노이즈의 형태에 따른 차이입니다. 좌측에서와 같이 노이즈가 개별 픽셀로 존재할 경우에는 그 픽셀의 극단적인 값은 중간값 필터링에 의하여 효과적으로 걸러집니다. 하지만 우측에서와 같이 노이즈가 연속적인 패턴일 경우에는 이러한 처리는 별로 효과적이지 않습니다.

 

이번에는 Order Static이라는 알고리즘을 내부적으로 사용하는 ESTIMATOR_FILTER 함수를 사용해보겠습니다. 일단 그 과정은 다음과 같습니다.

 

img_f_a = ESTIMATOR_FILTER(img_a, 3)
img_f_b = ESTIMATOR_FILTER(img_b, 3)

 

여기서도 커널의 크기는 3x3으로 정의되었는데요. 사실 Order Static이라는 알고리즘에 대해서는 제가 자세히 설명드리긴 힘들지만, 커널의 크기만큼의 이웃 픽셀들 중에서 특정 순위에 해당되는 값을 가져오는 방법인 것으로 알고 있습니다. 따라서 어떻게 보면 앞서 소개한 MEDIAN의 경우과 크게 차이가 나지는 않습니다. 실제로 결과들을 표출해보면 그 모습은 다음과 같습니다.

 

 

이 모습을 보면 앞서 MEDIAN 함수를 적용했던 경우와 마찬가지로 좌측의 결과는 괜찮은 반면 우측의 결과는 좋지않습니다. 역시 이것도 기법 자체의 특성과 노이즈의 형태 사이의 상성의 문제라고 볼 수 있습니다.

 

이번에는 Lee 필터링이라는 알고리즘을 내부적으로 사용하는 LEEFILT 함수를 사용해보겠습니다. 일단 그 과정은 다음과 같습니다.

 

img_f_a = LEEFILT(img_a, 1)
img_f_b = LEEFILT(img_b, 1)

 

여기서 사용된 1이라는 숫자는 커널 또는 박스의 크기를 정의합니다. 이것은 2*N+1로 정의됩니다. 즉 숫자 1은 3x3의 박스 크기에 해당됩니다. 사실 Lee Filtering이라는 알고리즘에 대해서도 그냥 제가 아는 한도 내에서만 설명하자면, 박스 내 픽셀들에 대하여 표준편차 기반으로 결과값을 산출하는 일종의 적응형(Adaptive) 필터링 기법입니다. 이러한 기법은 노이지한 픽셀을 제거하면서도 샤프한 특성을 어느 정도 유지해주는 장점이 있는 것으로 알려져 있습니다. 하여간 결과를 보면 다음과 같습니다.

 

 

여기서 우측의 결과를 보면 줄무늬 패턴의 노이즈가 꽤 효과적으로 제거된 것을 볼 수 있습니다. 반면 좌측의 픽셀 단위 노이즈들에 대한 제거 효과를 그리 만족스럽지는 못합니다. 물론 박스의 크기를 조정하면 결과는 또 달라집니다. 또한 추가적으로 표준편차에 해당되는 인자까지 추가적으로 사용하는 것도 가능합니다. 그래서 만약 다음과 같이 2로 설정하여 박스 크기가 5x5가 되도록 하고 표준편차가 7이 되도록 설정하여 결과를 다시 얻어보면 다음과 같습니다.

 

img_f_a = LEEFILT(img_a, 2, 7)
img_f_b = LEEFILT(img_b, 2, 7)

 

 

여기서 좌측의 결과를 보면 픽셀 단위의 노이즈에 대한 제거 효과가 약간 더 좋아진 반면, 우측의 결과는 더 나빠진 것을 볼 수 있습니다. 물론 인자값들을 어떻게 조정하느냐에 따라서 결과는 또 달라질 수 있습니다.

 

여기까지 컨볼루션 기반의 노이즈 제거 기법 및 관련 예제들을 살펴보았습니다. 이미 앞서 언급했듯이 기법 자체의 알고리즘적 특성 그리고 노이즈의 형태에 따라서 결과의 만족도는 얼마든지 달라질 수 있습니다. 그리고 특정 기법에 해당되는 기능함수 내에서도 세부 인자 및 키워드들의 사용 방식에 따라서 결과는 얼마든지 달라질 수 있습니다. 결국 제거하고자 하는 노이즈의 형태를 파악하고 그에 맞는 기법을 적절하게 적용하는 것이 핵심이라는 것을 반드시 염두에 두셔야 합니다.

 

그러면 오늘은 여기까지 소개하기로 하고 다음 회차에서는 FFT 기반의 노이즈 제거 기법들을 소개해보도록 하겠습니다.

반응형