화소 입장에서 바라본 영상 처리 연산이란 화소가 새로운 값을 받는 과정입니다.
새로운 값을 어디에서 받느냐에 따라 점 연산, 영역 연산, 기하 연산의 세 종류로 구분할 수 있습니다.
점 연산은 자기 자신으로부터 값을 받고, 영역 연산은 이웃 화소의 값을 보고 새로운 값을 결정합니다.
기하 연산에서는 기하학적 변환에 따라 다른 곳으로부터 값을 받습니다.
컨볼루션(convolution)
컨볼루션은 입력 영상의 각 화소에 필터를 적용해 곱의 합을 구하는 연산입니다.
필터를 입력 영상의 1, 2, 3, ..., 8 위치에 차례로 씌우고 곱의 합을 구해 출력 영상에 씌웁니다.
출력 영상에 씌우는 이유는 - 연상 도중 본래 화소 값이 바뀌면 다음 화소 처리를 할 때 오류가 발생하기 때문입니다.
(a) 그림은 위치 3에 필터를 씌우고 곱의 합을 계산하는 과정을 보여줍니다.
곱의 합은 해당하는 화소끼리 곱한 다음 결과를 더합니다. 즉 (2 x 1) + (1 x 2) + (3 x 1) = 7을 출력 영상에 씁니다.
이 예에서 필터 크기는 3이고 필터를 구성하는 화소의 인덱스는 중앙을 0, 왼쪽을 -1, 오른쪽을 1로 표시 합니다.
(b) 그림은 필터가 2차원인 컨볼루션 과정입니다.
필터를 1행의 (1, 1) 위치에서 시작해 오른쪽으로 이동하며 (1, 6) 위치까지 적용합니다.
1행을 마치면 2행으로 내려와 (2, 1)에서 시작해 오른쪽으로 이동하며 (2, 6) 까지 적용합니다.
2차원 필터의 크기를 h x w 로 표시하는데, 보통 h와 w는 같게 하고 대칭성을 위해 홀수를 사용합니다.
필터를 가장자리 화소에 씌우면 필터의 일부가 밖으로 나가기 때문에 적용을 할 수 없습니다.
따라서 위 그림의 출력 영상의 가장자리는 - 로 표시했습니다.
가장자리에 적용하려면 원래 영상(입력)에 덧대기를 하면 됩니다.
0 덧대기 (0 padding) 는 필요한 만큼 가장자리를 확장한 후 '0' 으로 채우고, 복사 덧대기는 가장자리 화소 값으로 채웁니다.
컨볼루션 자체는 특정한 목적이 없는 일반 연산(generic operation) 입니다. 필터가 정해지면 목적이 정해집니다.
이번엔 각 상황에 맞는 필터를 다뤄보겠습니다.
스무딩 필터(smoothing filter) - 잡음 제거
스무딩 필터는 영상의 잡음을 누그러뜨릴 수 있습니다.
밝은 영역에 어두운 작은 반점이 여기저기 나타나는 경우가 잡음의 예입니다.
왼쪽 필터는 크기가 3 x 3 이지만 모든 화소에 1 / 9를 처리하기 때문에 입력 영상의 근처 9개 화소의 평균을 취하는 셈입니다.
따라서 어떤 점의 값이 주위에 비해 아주 낮을 때 자신을 커지고 주위는 작아져서 잡음을 누그러뜨리는 효과가 있습니다.
오른쪽 필터는 크기가 5 x 5 인 가우시안 함수의 의해 제작된 스무딩 필터입니다.
중심에서 멀어질 수록 적용되는 가중치 값이 낮아지므로 해당 화소가 흐려집니다.
가우시안 개발공범 정리본 (작성자에게 동의 구했습니다)
import cv2 as cv
import numpy as np
img = cv.imread('soccer.jpg')
img = cv.resize(img, dsize=(0,0), fx=0.4, fy=0.4)
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
img를 불러와 스무딩 필터와 엠보싱 필터를 적용해 보겠습니다.
GaussianBlur( 영상, 필터크기, 표준편차 )
GaussianBlur 함수는 영상의 잡음을 제거하거나 영상을 부드럽게 만들기 위해 사용됩니다.
가우시안 필터를 적용하여 영상을 흐리게 만듭니다.
gus_low = cv.GaussianBlur(gray, (5, 5), 0.0) # 약하게
gus_medium = cv.GaussianBlur(gray, (9, 9), 0.0) # 적당히
gus_high = cv.GaussianBlur(gray, (15, 15), 0.0) # 세게
smooth = np.hstack((gus_low, gus_medium, gus_high)) # 이어붙이기
cv.imshow('Smooth', smooth)
첫 번째 인수는 스무딩을 적용할 영상이고, 두 번째 인수는 필터의 크기입니다.
세 번째 인수는 가우시안에 적용될 표준편차인데 0.0 으로 설정하면 필터 크기를 보고 자동으로 추정합니다.
엠보싱 필터(embossing filter) - 입체 형상
엠보싱 필터는 영상에 입체적인 효과를 주는 필터입니다. 이 필터는 영상의 주변 픽셀과 중심 픽셀 간의 밝기 차이를 감지하여 입체적인 형상을 부각시키는 효과를 만듭니다.
엠보싱 필터는 오른쪽 아래 화소에서 왼쪽 위 화소를 빼는 연산 이므로 - 255 ~ 255 사이의 값이 발생할 수 있습니다.
따라서 컨볼루션 결과를 그냥 저장하면 문제가 생깁니다. uint8 형의 변수는 0 ~ 255 값만 가질 수 있기 때문입니다.
- opencv에서는 한 화소의 명암을 uint8 타입으로 표현합니다.
import numpy as np
a = np.array([-3, -2, -1, 0, 1, 254, 255, 256, 257, 258], dtype = np.uint8)
print(a)
따라서 엠보싱 필터를 사용하기 위해서는 np.ciip 함수를 사용하여 값의 범위를 0 ~ 255 로 지정해야 합니다.
np.clip(a + 128, 0, 255) 에서 첫 번째 인수로 데이터를 받고 두 번째 인수로 최솟값, 세 번째 인수로 최댓값을 지정합니다.
만일 최솟값, 최댓값을 넘어가면 지정한 값으로 바뀝니다.
filter2D( 영상, 깊이, 필터 )
filter2D 함수는 커널(필터)을 사용하여 입력 이미지에 2D 필터링을 적용합니다.
첫 번째 인수로 처리할 영상, 두 번째 인수로 출력 이미지 깊이( -1 은 원본과 동일), 세 번째 인수로 적용할 필터
femboss = np.array([[-1.0, 0.0, 0.0],
[ 0.0, 0.0, 0.0], # 필터 생성
[ 0.0, 0.0, 1.0]])
gray16 = np.int16(gray) # 원본 이미지 int16 자료형으로 변경
# 화소값 int16 적용 X, clip() 적용 X
emboss_bad = cv.filter2D(gray, -1, femboss)
# clip() 적용 X
emboss_normal = np.uint8( cv.filter2D(gray16, -1, femboss) + 128 )
# 다 적용
emboss_good = np.uint8( np.clip( cv.filter2D(gray16, -1, femboss) + 128,
0,
255))
cv.imshow('bad', emboss_bad)
cv.imshow('normal', emboss_normal)
cv.imshow('good', emboss_good)
가장 먼저 이해해야 할 것은 왜 gray 이미지를 int16 자료형으로 만들었냐 입니다.
위에서 설명한 것처럼 이미지의 각 화소는 uint8 값 이기 때문에 음수의 값이 나올 수 없습니다.
따라서 엠보싱 연산(뺄셈)을 수행할 때 올바른 값이 나올 수 없기 때문에
자료형을 일단 int(음수 가능)형으로 만들어 놓은 다음, 연산이 완료된 후(또는 연산중) 다시 uint8 로 만듭니다.
emboss_bad는 연산을 시작할 때부터 이미 int16이 아니기 때문에 음수 적용이 안됐습니다 - clip()을 쓸 필요도 없습니다.
emboss_normal는 음수 적용이 됐지만, clip()으로 올바른 값을 지정하지 않았습니다.
emboss_good는 음수를 적용하고 처리값을 clip()으로 양수값 범위로 묶은 다음 다시 uint8로 만들었습니다.
'인공지능 > 컴퓨터 비전' 카테고리의 다른 글
[컴퓨터 비전] 1 영상의 형태 (0) | 2023.04.22 |
---|---|
[컴퓨터 비전] 3 점 연산 (0) | 2023.04.22 |