6. 합성곱 연산 함수 구현 - 인공지능 고급(시각) 강의 복습

안상훈·2024년 6월 4일
0

인공지능-시각

목록 보기
6/54
post-thumbnail

개요

본 블로그 포스팅은 수도권 ICT 이노베이션 스퀘어에서 진행하는 인공지능 고급-시각 강의의 CNN알고리즘 강좌 내용을 필자가 다시 복기한 내용에 관한 것입니다.


1. 합성곱 연산 함수 구현

이번에는 위 사진처럼 원본 이미지에 Kernel 통하여 합성곱 연산을 수행하는 함수를 직접 구현해 보고자 한다.

import numpy as np

def Conv2D(img, kernel=None):
    #이미지랑 커널의 높이, 너비 추출
    h, w = img.shape
    kh, kw = kernel.shape

    output_h = h - kh + 1
    output_w = w - kw + 1 #결과 이미지의 높이/너비 계산
    
    #결과 이미지 초기화 하기
    img_out = np.zeros((output_h, output_w), dtype=np.uint8)

    for y in range(output_h):
        for x in range(output_w):
            #관심영역 추출 region of interest
            roi = img[y:y+kh, x:x+kw]

            filtered = roi * kernel #합성곱의 '곱'
            conv_value = np.abs(np.sum(filtered))  #'곱'한거 '합성'

            # 결과를 0-255 범위로 클립하고 uint8로 변환
            img_out[y, x] = np.clip(conv_value, 0, 255).astype(np.uint8)
            #실제 conv를 돌릴 때는 float32 데이터타입을 주로 씀

    return img_out

코드 리뷰를 하자면 아래와 같다.

#이미지랑 커널의 높이, 너비 추출

h, w = img.shape
kh, kw = kernel.shape

output_h = h - kh + 1
output_w = w - kw + 1 #결과 이미지의 높이/너비 계산

위 부분은 Input_img, Kernel, Output_img의 높이/너비 를 계산하기 위한 함수로
그림으로 이해하면 편할 듯 하다.

여기서 h가 5, kh가 3이니 output_h는 5-3+1 = 3이 된다.

그리고 이 값을 바탕으로

img_out = np.zeros((output_h, output_w), dtype=np.uint8)

결과 이미지를 담을 img_out 행렬을 생성한다.

for y in range(output_h):
	for x in range(output_w):

이 이중 for문이 수행하는건 아래와 같은 연산을 반복하는 것이다.

위 사진처럼
Input_img위를 Kernel이 한칸 한칸씩 훑어나감
그리고 훑어 나갈 때마다 합성곱 연산을 수행하는데
합성곱의 연산을 수행 후 다음 합성연산을 수행하여, Output_img 매트릭스의 인자값을 채워나간다.

라고 보면 된다.

import cv2

#합성곱 연산 대상 이미지 불러오기
img_file = 'opencv/mountain.jpg'
img = cv2.imread(img_file, cv2.IMREAD_GRAYSCALE)

#커널을 만들기 -> 커널은 edge 검출 커널임
kernel = np.array([[0,0,0], 
				  [-1,2,-1],
                  [0,0,0]])

#합성곱 연산함수를 불러와서 출력 이미지 생성
output_img = Conv2D(img, kernel=kernel)
#원본 이미지와 합성곱 연산결과 이미지 출력
cv2.imshow('origin', img)
cv2.imshow('res', output_img)

cv2.waitKey(0)
cv2.destroyAllWindows()

다음으로 설계한 함수로 Input_img에 대하여

kernel = np.array([[0,0,0], 
				  [-1,2,-1],
                  [0,0,0]])

위 설계한 커널을 활용하여 합성곱 연산을 수행하면, 아래의 그림처럼 합성곱 연산의 결과 Output_img를 얻을 수 있다.


2. 합성곱 연산 심화 - Stride, Padding

다음은 위 사진처럼 Stride, Padding개념을 도입하여 합성곱 연산을 수행하는 애니메이션이다.

파란색 ROI 영역이 2칸씩 넘어가고 있으며 이는 Stride = 2
하늘색 Input_img 바깥에 0으로 채워진 픽셀들이 1줄씩 있으며, 이는 Padding = 1로 볼 수 있다.

Stride, Padding 인자값의 조절됨에 따라 합성곱의 연산 결과인 Output_img의 크기가 조절 될 수 있으며,
이는 아래의 수식으로 정리 할 수 있다.

위 수식을 활용하여 합성곱 연산(Stide, Padding)인자가 포함된 함수의 구현은 아래와 같다.

def Conv2D(img, kernel=None, stride=1, padding=1):
    
    h, w = img.shape
    kh, kw = kernel.shape

    #-----padding을 적용하는 부분-------#
    padded_h = h + 2*padding
    padded_w = w + 2*padding

    #원본이미지에 padding붙이기
    padded_img = np.zeros((padded_h, padded_w)).astype(np.uint8)
    padded_img[padding:h + padding, padding:w + padding] = img
    #-----padding을 적용하는 부분-------#

    #-----stride을 적용하는 부분-------#
    output_h = ((padded_h - kh) // stride) + 1
    output_w = ((padded_w - kw) // stride) + 1
    #-----stride을 적용하는 부분-------#
    
    img_out = np.zeros((output_h, output_w), dtype=np.uint8)
    #출력 Feature Map(출력이미지) 초기화

    for y in range(output_h):
        for x in range(output_w):
            #관심영역 추출 region of interest
            step_y, step_x = y*stride, x*stride
            roi = padded_img[step_y:step_y+kh, step_x:step_x+kw]

            filtered = roi * kernel #합성곱의 '곱'
            conv_value = np.abs(np.sum(filtered))  #'곱'한거 '합성'

            # 결과를 0-255 범위로 클립하고 uint8로 변환
            img_out[y, x] = np.clip(conv_value, 0, 255).astype(np.uint8)
            #실제 conv를 돌릴 때는 float32 데이터타입을 주로 씀

    return img_out, padded_img

이 함수를 바탕으로 원본 이미지, Padding이 적용된 이미지, 출력 이미지를 시각화 한다면 아래와 같다.

img_file = 'opencv/mountain.jpg'
img = cv2.imread(img_file, cv2.IMREAD_GRAYSCALE)

kernel = np.array([[0,0,0], 
                   [-1,2,-1],
                   [0,0,0]]) #커널 만들기

output, padded_img = Conv2D(img, kernel=kernel, stride=2, padding=1)
cv2.imshow('origin', img)
cv2.imshow('ped_img', padded_img)
cv2.imshow('res', output)

cv2.waitKey(0)
cv2.destroyAllWindows()

지금은

stride=2, padding=1

만을 적용하였지만 좀 극단적인 값을 적용하면 아래와 같아진다

stride=3, padding=20

profile
자율차 공부중

0개의 댓글