파이썬 강의/openCV

파이썬 openCV 20. 노이즈 제거 : 쿠와하라 필터(Kuwahara filter)

마리사라 2020. 12. 10. 20:03
반응형

파이썬 openCV 20번째 강의는 노이즈 제거의 방법인 Kuwahara 필터(Kuwahara filter)입니다. Kuwahara 필터는 일반 Kuwahara 필터, 일반화된 Kuwahara 필터, 적응형 Kuwahara 필터가 있지만 이번 강의는 일반 Kuwahara 필터만 사용하겠습니다,


0. Kuwahara?

Kuwahara 필터는 일반적으로는 잘 들어보시지 못한 필터일 것입니다. 한국어 웹사이트에서는 Kuwahara 필터를 잘 다루지 않습니다. Kuwahara 필터는 원래 의료용 영상에 적용되도록 만들어진 필터입니다.

 

저주파 통과 필터를 사용한 영상

일반적인 영상에서 노이즈를 제거하는 방법은 저주파 통과 필터입니다. 이러한 저주파 통과 필터는 효율적으로 노이즈를 제거할 수 있지만 가장자리를 흐리게 만들어 버립니다. 하지만 Kuwahara 필터는 가장자리를 흐리게 만들지 않으면서도 노이즈를 제거할 수 있는 필터입니다.

 

Kuwahara 필터 공식

Kuwahara 필터는 각각의 조건에 따라 결과 화소가 결정됩니다.

Kuwahara 필터 조건 결정 방법

Kuwahara 필터는 원래 화소를 기준으로 5 x 5의 명암을 구합니다. 그리고 각각의 사분면에 해당하는 값들의 분산값을 구합니다. 그 후 명암의 분산이 가장 작은 사분면의 화소들의 평균값이 출력 화소가 됩니다.

 

예를 들어 위 그림에서 a 사분면(2 사분면)의 명암 값들의 분산이 가장 작다고 하겠습니다. 그렇다면 필터를 거친 출력 값은 a 사분면의 화소들의 평균(컬러 영상이라면 각각의 R, G, B의 평균, 그레이 스케일이라면 명암의 평균)이 출력 화소가 됩니다.


1. openCV에서의 x

이제 코드를 알아보겠습니다.

img = cv2.imread('lenna.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
height, width = gray.shape

우선 이번 Kuwahara 필터는 그레이 스케일로 진행합니다. BGR 영상으로 진행하실 분들도 명암값의 분산을 구해야 하므로, 그레이 스케일 영상은 만들어 두서야 합니다.

 

img_noise = np.zeros_like(gray)
for i in range(height):
    for j in range(width):
        make_noise = np.random.normal()
        set_noise = 10 * make_noise
        img_noise[i][j] = gray[i][j] + set_noise

이제 영상에 가우시안 노이즈를 생성해 줍니다. 가우시안 노이즈는 위와 같은 중첩 for문으로 생성이 가능합니다.

 

temp = np.zeros((height + 4, width + 4), dtype=np.float)
temp[2: 2 + height, 2: 2 + width] = img_noise.copy().astype(np.float)
out = np.zeros_like(gray)

이제 출력 영상을 저장할 빈 배열을 만들어 줍니다. 이때 temp는 원래 영상의 크기보다 +4의 크기로 만들어주게 됩니다. 그 후 좌우 2칸씩 띄우고 노이즈 영상을 복사해 줍니다. 이렇게 하면 원본 영상 주변에 0의 값을 갖는 2칸씩의 빈 공간이 생기게 되는데, 이를 제로 패딩이라고 합니다.

 

for i in range(height):
    for j in range(width):
        point1 = temp[i:i + 3, j:j + 3]
        point2 = temp[i + 2:i + 5, j:j + 3]
        point3 = temp[i:i + 2, j + 2:j + 5]
        point4 = temp[i + 2:i + 5, j + 2:j + 5]

        if min(np.var(point1), np.var(point2), np.var(point3), np.var(point4)) == np.var(point1):
            out[i, j] = np.mean(temp[i:i + 3, j:j + 3])
        elif min(np.var(point1), np.var(point2), np.var(point3), np.var(point4)) == np.var(point2):
            out[i, j] = np.mean(temp[i + 2:i + 5, j:j + 3])
        elif min(np.var(point1), np.var(point2), np.var(point3), np.var(point4)) == np.var(point3):
            out[i, j] = np.mean(temp[i:i + 2, j + 2:j + 5])
        elif min(np.var(point1), np.var(point2), np.var(point3), np.var(point4)) == np.var(point4):
            out[i, j] = np.mean(temp[i + 2:i + 5, j + 2:j + 5])

이제 중첩 for문을 사용하여 화소값을 계산하게 됩니다. 이때 계산하는 계산량이 많아서 Kuwahara 필터는 다른 openCV프로그램들 보다 결과가 나오는데 시간이 걸립니다.

 

각각의 point들에 사분면들의 값을 저장합니다. 그 후 if문을 통해 각각의 포인트들의 분산(np.var = 분산을 구하는 함수)들의 최솟값들에 따라 결괏값을 저장합니다. 결괏값은 분산이 가장 작은 point의 평균(np.mean = 평균을 구하는 함수)이 들어가게 됩니다.

 

만약 Kuwahara 필터로 컬러 영상을 넣고싶으신 분들은 for문을 하나 더 하셔서 채널별(B, G, R)로 평균을 넣어주시면 됩니다.

 

 

이제 결과를 확인해보겠습니다.

 

원본 영상
가우시안 노이즈 생성 영상
Kuwahara 필터 적용 영상

결과 영상을 보시면 가우시안 노이즈는 거의 제거가 된 것을 볼 수 있습니다. 또한 저주파 필터와는 달리 영상이 흐려지는 부분도 없습니다. 하지만 유화 그림처럼 경계가 거의 없어진 것을 볼 수 있습니다. 이러한 결과가 Kuwahara 필터의 특징입니다.


2. 마치며

Kuwahara 필터는 실행시키고 매우 느리게 결과가 출력될 것입니다(평균 1분). 하지만 인내를 가지고 기다리셔야 결과를 확인하실 수 있으니, 한번 코드를 잘못 입력하시면 매우 답답하실 것입니다.

 

def kuwahara():
    img = cv2.imread('lenna.png')
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    height, width = gray.shape

    img_noise = np.zeros_like(gray)
    for i in range(height):
        for j in range(width):
            make_noise = np.random.normal()
            set_noise = 10 * make_noise
            img_noise[i][j] = gray[i][j] + set_noise

    temp = np.zeros((height + 4, width + 4), dtype=np.float)
    temp[2: 2 + height, 2: 2 + width] = img_noise.copy().astype(np.float)
    out = np.zeros_like(gray)

    for i in range(height):
        for j in range(width):
            point1 = temp[i:i + 3, j:j + 3]
            point2 = temp[i + 2:i + 5, j:j + 3]
            point3 = temp[i:i + 2, j + 2:j + 5]
            point4 = temp[i + 2:i + 5, j + 2:j + 5]

            if min(np.var(point1), np.var(point2), np.var(point3), np.var(point4)) == np.var(point1):
                out[i, j] = np.mean(temp[i:i + 3, j:j + 3])
            elif min(np.var(point1), np.var(point2), np.var(point3), np.var(point4)) == np.var(point2):
                out[i, j] = np.mean(temp[i + 2:i + 5, j:j + 3])
            elif min(np.var(point1), np.var(point2), np.var(point3), np.var(point4)) == np.var(point3):
                out[i, j] = np.mean(temp[i:i + 2, j + 2:j + 5])
            elif min(np.var(point1), np.var(point2), np.var(point3), np.var(point4)) == np.var(point4):
                out[i, j] = np.mean(temp[i + 2:i + 5, j + 2:j + 5])

    cv2.imshow('original', gray)
    cv2.imshow('noise', img_noise)
    cv2.imshow('out', out.astype(np.uint8))
    cv2.waitKey(0)

 

반응형