파이썬 강의/openCV

파이썬 openCV 10. 가우시안 노이즈(Gaussian Noise)

마리사라 2020. 11. 22. 22:42
반응형

파이썬 openCV 10번째 강의는 가우시안 노이즈(Gaussian Noise)입니다.

 

이번 강의에서는 가우시안 노이즈를 생성하는 방법과, 이미지 평균 연산을 통한 가우시안 노이즈 제거 방법을 동시에 알려드리겠습니다.


0. 가우시안 노이즈?

우선 가우시안 노이즈가 무엇인지 알려드리겠습니다.

 

가우시안 노이즈가 첨가된 lenna

이런 식으로 사진이 지지직 거리는 느낌의 이미지를 보신 적이 있으실 겁니다. 이런 잡음을 가우시안 노이즈라고 합니다. 이름이 가우시안 노이즈인 이유는, 이름처럼 가우스 함수에 따른 분포(가우시안 분포)를 따르고 있기 때문에 가우시안 노이즈라고 이름 붙여졌습니다.

 

가우시안 분포

가우시안 분포는 위의 공식으로 나타냅니다. 하지만 우리는 이런 공식 따위 알 필요는 없고, 그저 저런 공식에 따르는 잡음이라고만 생각하셔도 좋을 것 같습니다.

 

가우시안 잡음은 보통 이미지의 압축, 전송 등의 과정에서 일어납니다. 이미지가 압축되면서 이미지가 줄어들게 되고, 이후 다시 복구하는 과정에서 여러 가지 원인으로 인해 원래의 화소 값이 아닌, 오차가 생긴 값이 들어갈 수가 있습니다. 그렇게 생기는 노이즈가 가우시안 노이즈입니다.

 

이미지가 전송될 때에도, 압축을 한 이후 전송을 하게 됩니다. 이때는 압축과 복원을 하는 과정에는 아무 문제가 없더라도, 전송 과정에서 오류가 발생하면 가우시안 노이즈가 생성될 수 있는 것입니다.

 

 

이러한 가우시안 노이즈를 제거하는 방법은 여러 가지가 있지만, 이번 강의에서는 노이즈 이미지의 평균을 통해 노이즈를 제거해보겠습니다.


1. openCV에서의 가우시안 노이즈

그렇다면 파이썬에서도 저러한 공식을 이용해서 가우시안 노이즈를 만들거나, 일부러 오류를 내서 만들어야 하는가? 라면 NO입니다.

 

어차피 가우시안 분포를 따른다는 것은, 정규 분포를 따른다는 뜻입니다. 그리고 우리는 정규 분포를 따르는 랜덤 한 숫자를 만드는 함수 random이 있습니다.

 

 

def make_noise(std, gray):

우선 먼저 할 것은 가우시안 노이즈를 만드는 함수를 정의합니다. 필요한 변수는 std와 gray입니다. gray는 제 강의를 처음부터 보신 분들은 눈치채셨겠지만, 그레이 스케일(흑백) 영상입니다. 

std는 잡음의 크기입니다. std값이 크면 클수록 잡음이 크게 생성됩니다.

 

이제 make_noise의 내용입니다

 

height, width = gray.shape

잡음을 만들기 위해 중첩 for문을 사용할 예정입니다. 그래서 이미지의 크기를 불러와 줍니다.

 

img_noise = np.zeros((height, width), dtype=np.float)

이제 노이즈를 만들 빈 이미지를 생성해줍니다. 이때 dtype는 내용물의 타입을 설정해 주는 옵션으로, 실수로 정의해 주겠습니다.

 

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

이제 중첩 for문을 이용해서 가우시안 노이즈를 만들어 보겠습니다.

우선 우선 make_noise에 정규분포를 따르는(normal) 랜덤(random)한 숫자를 넣어줍니다.

이후 set_noise에 make_noise와 외부에서 입력받은 std값을 곱하고, 조금 전에 만들었던 img_noise라는 빈 이미지에 원래 화소 + set_noise값을 하여 넣어줍니다.

이렇게 하면 노이즈가 추가된 이미지가 만들어집니다.

 

return img_noise

이후 이렇게 만들어진 이미지를 return 해주면 끝입니다.

 

 

자 이제 가우시안 노이즈를 생성하는 방법은 배웠습니다. 그렇다면 가우시안 노이즈를 없애는 방법을 알려드리겠습니다.

 

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

늘 그렇듯, 이미지를 불러오고, 그레이 스케일로 만들어주고, 그 크기를 구해줍니다.

 

std = 15

이제 std값을 설정해 줍니다. 이 부분은 input을 통해 입력받도록 하셔도 되고, 저처럼 미리 설정해두셔도 됩니다.

 

img_noise = make_noise(std, gray)
img_noise2 = make_noise(std, gray)
img_noise3 = make_noise(std, gray)
img_noise4 = make_noise(std, gray)

이제 가우시안 노이즈가 첨가된 이미지를 총 4개 만들어 줍니다. 이미지의 평균으로 노이즈를 제거할 것이기 때문에, 같은 이미지로는 평균을 해도 원래의 이미지만 나오게 됩니다. 따라서 조금씩 노이즈가 다른 이미지가 필요한 것입니다.

 

out2 = np.zeros((height, width), dtype=np.float)
out3 = np.zeros((height, width), dtype=np.float)
out4 = np.zeros((height, width), dtype=np.float)

결과물이 저장될 빈 이미지를 생성해줍니다. out2에는 2개의 이미지의 평균, out3에는 3개의 이미지의 평균, out4에는 4개의 이미지의 평균이 들어갈 예정입니다.

 

    for i in range(height):
        for j in range(width):
            if (img_noise[i][j] + img_noise2[i][j]) / 2 > 255:
                out2[i][j] = 255
            else:
                out2[i][j] = (img_noise[i][j] + img_noise2[i][j]) / 2
                
            if (img_noise[i][j] + img_noise2[i][j] + img_noise3[i][j]) / 3 > 255:
                out3[i][j] = 255
            else:
                out3[i][j] = (img_noise[i][j] + img_noise2[i][j] + img_noise3[i][j]) / 3
                
            if (img_noise[i][j] + img_noise2[i][j] + img_noise3[i][j] + img_noise4[i][j]) / 4 > 255:
                out4[i][j] = 255
            else:
                out4[i][j] = (img_noise[i][j] + img_noise2[i][j] + img_noise3[i][j] + img_noise4[i][j]) / 4

또다시 중첩 for문을 통한 작업입니다. 이때 각 if문은 오버플로우를 방지하는 구문이며, 그 외에는 평균값을 이미지에 저장합니다.

 

 

이제 작업이 모두 끝났습니다. 완성된 이미지를 한번 보겠습니다.

 

원본
노이즈가 낀 이미지
이미지 2개의 평균
이미지 3개의 평균
이미지 4개의 평균

 

결과를 보면, 노이즈도 잘 생성되었고, 평균으로 들어가는 이미지의 개수가 많아질수록 원본 이미지에 가깝게 나타나는 것을 볼 수 있습니다.


2. 마치며

가우시안 노이즈는 요즘에는 워낙 기술이 좋아져서 잘 보기 힘든 노이즈입니다. 하지만 아예 없는 것은 아니라서, 이러한 기법을 알아두면 언젠가는 쓸모가 있을 것이라 생각합니다.

 

 

import cv2
import numpy as np


def make_noise(std, gray):
    height, width = gray.shape
    img_noise = np.zeros((height, width), dtype=np.float)
    for i in range(height):
        for a in range(width):
            make_noise = np.random.normal()  # 랜덤함수를 이용하여 노이즈 적용
            set_noise = std * make_noise
            img_noise[i][a] = gray[i][a] + set_noise
    return img_noise


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

    std = 15
    img_noise = make_noise(std, gray)
    img_noise2 = make_noise(std, gray)
    img_noise3 = make_noise(std, gray)
    img_noise4 = make_noise(std, gray)

    out2 = np.zeros((height, width), dtype=np.float)
    out3 = np.zeros((height, width), dtype=np.float)
    out4 = np.zeros((height, width), dtype=np.float)
    # 평균 계산
    for i in range(height):
        for j in range(width):
            if (img_noise[i][j] + img_noise2[i][j]) / 2 > 255:
                out2[i][j] = 255
            else:
                out2[i][j] = (img_noise[i][j] + img_noise2[i][j]) / 2

            if (img_noise[i][j] + img_noise2[i][j] + img_noise3[i][j]) / 3 > 255:
                out3[i][j] = 255
            else:
                out3[i][j] = (img_noise[i][j] + img_noise2[i][j] + img_noise3[i][j]) / 3

            if (img_noise[i][j] + img_noise2[i][j] + img_noise3[i][j] + img_noise4[i][j]) / 4 > 255:
                out4[i][j] = 255
            else:
                out4[i][j] = (img_noise[i][j] + img_noise2[i][j] + img_noise3[i][j] + img_noise4[i][j]) / 4

    cv2.imshow("original", gray)
    cv2.imshow('noise', img_noise.astype(np.uint8))
    cv2.imshow('avr2', out2.astype(np.uint8))
    cv2.imshow('avr3', out3.astype(np.uint8))
    cv2.imshow('avr4', out4.astype(np.uint8))
    cv2.waitKey(0)


run()

 

반응형