이 장은 합성곱 신경망에 관해 설명합니다. 이미 잘 알려진 합성곱 신경 알고리즘들에 관해서도 설명합니다.
합성곱 신경망(CNN : Convolution Neural Network)은 어떤 입력 데이터에 대해 필터를 적용하여 새로운 결과(합성곱 된 결과)를 만들어줍니다.
1.1 합성곱
1) 이동평균
- 합성곱(컨볼루션; Convolution)을 설명하기 위해 이동평균(Moving Average)을 먼저 설명해 보겠습니다. 이동평균은 데이터를 따라가며 평균을 구하는 것을 의미합니다.

- 데이터를 3개씩 묶어 평균을 구해 나간 후 원본 데이터와 이동 평균 데이터를 그래프로 그려보겠습니다. 원본 데이터는 파란색 실선으로 표시했고, 이동평균 데이터는 초록색 점선으로 표시했습니다.
data = [3,4,1,5,7,9,6,2]
mvg = [None, 8/3, 10/3, 13/3, 21/3, 22/3, 17/3, None]
import matplotlib.pyplot as plt
plt.plot(data, 'bo-', label= 'Input Data')
plt.plot(mvg, 'go:', label= 'Averaged Data')
plt.legend()

2) 가중치를 갖는 이동평균
- 데이터를 따라가며 평균을 구할 때 데이터마다 같은 가중치를 주지 않을 수 있습니다. 다음 그림처럼 현재 주목하는 데이터에는 1/2의 가중치를 주고 그 앞/뒤 데이터에는 1/4 가중치를 주고 평균을 구할 수 있습니다.

3) 합성곱 연산
- 가중치를 정의하고 이동하면서 이를 이용해 합성곱(Convolution) 연산을 수행할 수 있습니다. 연산에 사용할 가중치를 정의한 배열을 필터 커널(Filter Kernel)이라고 부릅니다. 줄여서 필터 또는 커널이라고 부르기도 합니다.
- 만일 커널 h = [1/4 1/2 1/4]에 대한 x 데이터에 대한 합성곱 연산은 다음과 같이 정의됩니다.
- y[n] = h[-1] * x[n-1] + h[0] * x[n] + h[1] * x[n+1]
- 일반적으로 커널 h[-(K-1)/2] ~ h[(K-1)/2]에 대해서 y[n]은 다음과 같이 정의됩니다.
- y[n] = ∑h[k] * x[n+k]
- 다음 코드는 이동평균과 가중치를 지정하고 합성곱을 한 결과를 시각화합니다. 이동평균 결과는 점(dotted)선, 가중치를 이용한 합성곱 결괄는 대시(dashed)선으로 표시했습니다.

4) 합성곱의 활용
- 합성곱은 영상 신호처리에 자주 사용됩니다. 주된 용도는 필터링입니다. 예를 들면, 다음과 같이 필터 커널과 데이터를 줬을 경우 값의 차이가 있는 곳(예를 들면 데이터의 3번째와 4번째는 차이가 10)에서의 합성곱 결과가 그렇지 않은 곳에 비해 크게 움직이는 것을 볼 수 있습니다. 영상처리에서 화소의 차이가 있는 곳은 주로 경계(Edge)에 해당하고, 기울기가 큰 값을 찾는 엣지 필터입니다.

1.2 이미지 합성곱
- 이미지는 2차원이므로 적용하는 필터 또한 2차원입니다. 이미지 합성곱은 필터를 적용할 수 있는 영역에 대해서 현재 주목 화소와 주위 화소의 배열을 각 요소끼리 모두 곱한 후 모든 항목을 더합니다.
- 합성곱에 사용하는 필터 커널(Filter Kernel)의 크기는 정해진 것은 없습니다. 일반적으로 현재 화소와 주위의 화소에 3x3 또는 5x5 크기의 필터를 적용해서 영상을 처리합니다. 필터는 원본 이미지에 적용 되는 작은 크기( 3x3 또는 5x5 )의 배열을 의미합니다. 만일 3x3 필터를 적용하는 합성곱에서 현재 주목 화서가 p5이고 출력 결과가 h[i, j]라면 h[i, j]는 다음 식으로 계산됩니다.
- h[i , j] = A*p1 + B*p2 + C*p3 + D*p4 + E*p5 + F*p6 + G*p7 + H*p8 + I*p9
1) 이미지 필터 적용
- 이미지에 3x3 크기 평균값 필터를 적용하는 것을 보겠습니다.

- 만일 위 이미지와 같은 필터를 적용한다고 하면 현재 화소와 주위 화소는 1/9 가중치를 갖게 됩니다. 다시 말하면 현재 화소와 주위 화소의 값을 모두 더한 후 9로 나누게 되는데 이렇게 하면 영상이 흐릿하게 되는 효과가 있습니다. 이를 흐림(Blur) 효과라고 합니다.
- 필터의 갯수(n=9) 만큼 나눈 필터는 흐린 효과를 주는 필터 중에서 평균값 필터에 해당합니다.

- 다음 5x5 이미지 배열에 3x3 크기 필터를 이용해서 합성곱을 수행하면 img[1,1]의 출력 화소의 값은 다음 식을 통해 계산됩니다.
- img[1,1] = (1*0) + (3*1) + (3*0) + (0*1) + (3*0) + (1*1) + (0*1) + (0*1) + (2*0) = 4
- 바깥 화소의 경우는 필터를 적용할 수 없으므로 0을 출력합니다.
- img[1,2] 화소는 다음 식을 통해 구해진 값이 출력됩니다.
- img[1,2] = (3*0) + (3*1) + (0*0) + (3*1) + (1*0) + (0*1) + (0*1) + (2*1) + (1*0) = 8

- img[1,3] 화소는 다음 식을 통해 구해진 값이 출력됩니다.
- img[1,3] = (3*0) + (0*1) + (0*0) + (1*1) + (0*0) + (0*1) + (2*1) + (1*1) + (1*0) = 4
- 이런 방법으로 모든 화소에 대해 합성곱을 수행하면 필터를 적용한 결과를 얻게 되는 것입니다.
2) 이미지 필터 적용 예
- 영상처리 필터를 적용하면 원 영상의 이미지에서 경계선을 더 뚜렷하게 하거나 아니면 더 흐리게 처리할 수 있습니다.

- 위의 그림은 원본 영상에 필터 처리를 한 영상들의 예를 보여줍니다. 때로는 x축과 y축에 약간 다른 필터를 적용하여 영상에서 경계선(에지) 영역만 추출할 수도 있습니다. 이렇게 필터를 적용하면 필터 행렬의 특성에 따라 원본 이미지로부터 특성이 강조된 이미지를 얻을 수 있습니다.

- 필터가 적용되어 영상이 어떻게 바뀌는지 보여주기 위해 3x3 필터를 예로 설명하겠습니다. 이 필터는 수직 성분 윤곽선을 검출하기 위한 필터입니다. 현재 위치 (x,y)의 화소 값에서 (x+1, y) 위치 화소의 값을 빼고 그 결과게 절댓값을 취해 출력 영상의 (x', y') 화소의 값으로 합니다.
- 위의 그림은 MNIST 이미지 데이터의 첫 번째 데이터인 5에 적용하기 전 화소의 값(왼쪽)과 적용 후의 화소 값(오른쪽)을 보여줍니다. 필터를 적용한 이후의 화소의 값이 크게 변하는 부분에서 큰 값을 가집니다.
3) 합성곱 함수 구현하기
- 합성곱 함수가 있다면 다양한 필터를 여러분이 테스트해 볼 수 있을 것입니다. 다음은 이미지에 필터를 적용하는 함성곱 함수를 구현합니다. 함수의 padding 매개변수가 same이면 필터의 크기에 의해 계산되지 않는 바깥 테두리의 0화소가 생기지 않게 해줍니다.
import numpy as np
def Conv2D(img, kernel=None, padding="valid"):
if kernel is not None :
h, w = img.shape[0:2]
kh, kw = kernel.shape
sy, sx = int(kh/2), int(kw/2)
if padding=="same":
new_shape = (h+sy, w+sx)
img_out = np.zeros(new_shape, dtype=np.uint8)
elif padding=="valid":
img_out = np.zeros(img.shape, dtype=np.uint8)
height, width = img_out.shape
for y in range(sy, height-2*sy):
for x in range(sx, width-2*sx):
roi = img[y-sy:y+sy+1, x-sx:x+sx+1]
filtered = roi * kernel
conv_value = np.abs(np.sum(filtered))
img_out[y, x] = np.uint8(conv_value)
return img_out[sy:-sy, sx:-sx]
else:
print("Kernel array not found!")
- 다음 코드는 합성곱 함수를 이용해서 필터를 적용하는 예입니다.
import cv2 # pip install opencv-python
img = cv2.imread("house.jpg", cv2.IMREAD_GRAYSCALE)
kernel = np.array([[0,0,0],[0,1,-1],[0,0,0]])
output = Conv2D(img, kernel=kernel, padding="same")
cv2.imshow("House", img)
cv2.imshow("Conv2D", output)
cv2.waitKey()
cv2.destroyAllWindows()
1.3 풀링
- CNN에서 합성곱 계층을 통과한 데이터는 차원축소 과정을 통해 이미지의 크기를 줄이게 됩니다. 입력 이미지를 차원 축소해서 크기를 줄이는 것을 풀링(Pooling) 이라고 합니다.

- 이미지의 크기를 줄이는 과정에서 줄여야 할 대상 영역의 화소 중에서 어떤 화소를 출력 이미지의 화소로 선택할 지 결정해야 합니다.
- 풀링(Pooling) 방법으로는 다음 3가지를 주로 사용합니다.
- Max Pooling : 대상 화소 중에서 가장 큰 화소 값을 선택합니다.
- Mean Pooling(Average Pooling) : 대상 화소의 평균값을 선택합니다.
- Min Pooling : 대상 화소중에서 가장 작은 값을 선택합니다.
- 풀링을 위해 대상 영역이 이동하는 크기를 스트라이드(stride)라고 합니다. 만일 2x2 풀링 시 스트라이드가 2이면 가로/세로 방향으로 각각 2화소씩 이동하며 풀링하므로 이미지가 1/4 크기로 줄어듭니다.
1) 맥스 풀링
- Max Pooling의 경우 아래와 같이 영상의 크기를 1/2로 줄여야 하면 2x2화소들 중에서 가장 큰 화소의 값을 대상 화소의 값으로 선택하는 것을 의미합니다.

- 인공 신경망 CNN에서 특징이 더 강조된 영상을 얻기 위해 해당 화소 중에서 가장 큰 화소의 값을 출력하는 맥스 풀링을 선호합니다. 그 이유는 이미지의 특징을 가장 잘 표현할 수 있는 값이 가장 큰 값이고, 이것은 그 구간에서의 패턴의 민감성을 나타내 주기 때문입니다.
2) 맥스 풀링 구현
- 다음은 2x2화소에서 가장 큰 값을 추출하는 맥스 풀링의 예입니다.

import numpy as np
def maxpool2d(img):
height, width = img.shape
img_ = np.zeros((int(height/2), int(width/2)), dtype=np.uint8)
for y in range(int(height/2)):
for x in range(int(width/2)):
try:
img_[y, x] = np.max(img[2*y:2*y+2, 2*x:2*x+2])
except:
pass
return img_
- 맥스 풀링은 그레이스케일 영상의 경우 전체적으로 이미지가 밝아지는 현상이 있습니다.
import cv2
img = cv2.imread("lena.jpg", cv2.IMREAD_GRAYSCALE)
cv2.imshow("Nearst 0.5", cv2.resize(img, dsize=(0,0), fx=0.5, fy=0.5))
cv2.imshow("Max Pooling", maxpool2d(img))
cv2.waitKey()
cv2.destroyAllWindows()

- 맥스 풀링은 엣지(Edge, 경계선 또는 윤곽선) 영상 등 특징을 추출해 저장한 이미지의 경우 엣지가 더 뚜렷해지는 효과가 있습니다. 그래서 CNN에서 차원을 줄이고 특징을 강조하기 위해 사용합니다.
import cv2
img = cv2.imread("lena.jpg", cv2.IMREAD_GRAYSCALE)
cv2.imshow("Lena", img)
kernel = np.array([[0,0,0], [0,1,-1], [0,0,0]])
edge = cv2.filter2D(img, -1, kernel)
cv2.imshow("Egde", edge)
cv2.imshow("resize", cv2.resize(img, dsize=(0,0), fx=0.5, fy=0.5))
cv2.imshow("Max Pooling", maxpool2d(img))
cv2.imshow("edge resize", cv2.resize(edge, dsize=(0,0), fx=0.5, fy=0.5))
cv2.imshow("edged max pool", maxpool2d(edge))
cv2.waitKey()
cv2.destroyAllWindows()
1.4 스트라이드와 제로 패딩
1) 스트라이드
- 합성곱 또는 차원 풀링 과정에서 필터 커널이 원 이미지에 적용된 후 움직이는 화소의 크기를 스트라이드(stride)라고 합니다.

- 일반적으로 3x3 필터를 합성곱 할 때 모든 이미지에 필터가 처리되고 같은 크기의 출력이 되어야 하므로 스트라이드는 1을 사용합니다. 그리고 2x2 풀링의 경우 가로와 세로를 1/2로 줄이려면 스트라이드의 크기를 2로 합니다. 스트라이드가 2라면 필터를 적용한 후 필터가 적용되기 위해 이동시키는 화소의 수가 2개라는 의미입니다. 풀링 크기가 3x3인데 스트라이드가 2이면 중첩 풀링(Overlapping Pooling)이 됩니다.
2) 제로 패딩


- 제로 패딩(Zero Padding)은 합성곱 또는 풀링 연산 시 이미지의 크기가 줄어들지 않도록 원본 이미지 테두리 화소를 추가하여 연산하는 것을 의미합니다.
- 합성곱 연산 또는 풀링 연산 후 이미지의 크기가 작아질 수 있습니다. 예를 들어 합성곱 연산의 경우 필터 커널의 크기에 따라 원본 이미지의 바깥 테두리가 무조건 0으로 채워질 수 있습니다. 그렇게 되면 필터 커널을 적용한 후의 이미지의 크기가 줄어들어 데이터 손실이 있을 수 있습니다.
- 합성곱 연산에서 사라지는 테두리 화소 수는 상/하/좌/우 각각 floor(filter_size/2)개씩입니다. 만일 3x3 크기 필터 커널을 적용하면 바깥 테두리 1화소가 사라집니다. 다음 그림은 원 영상에 5x5 크기 메디안 필터를 적용하기 전(왼쪽)과 후(오른쪽)입니다. 테두리 2픽셀은 연산에서 제외됩니다.
- 텐서플로우 또는 케라스 등에서 zero padding 관련 옵션을 선택할 때 'same' 또는 'valid'를 사용합니다. 'same' 옵션이 연산 시 모자라는 바깥 화소를 0으로 채우고 연산을 해서 원본의 크기를 그대로 유지하라는 'zero padding'을 의미합니다.
1.5 CNN 모델
1) 합성곱 계층과 풀링 계층
- CNN은 영상을 분류하기 위한 인공신경망 모델입니다. CNN의 핵심은 영상의 특징을 찾기 위해 필터 커널을 적용한 전처리와 차원축소에 있습니다. 영상에서 특징을 파악하기 위해 영상에 필터를 적용하여 얻은 영상을 이용합니다.
- CNN은 입력 이미지 전체가 은닉 계측의 뉴런에 모두 연결되어 있지 않습니다. CNN은 기본적으로 합성곱 계층과 풀링 계층을 가집니다.

- 합성곱 계층은 영상의 특징을 추출하기 위한 계층입니다. 합성곱 계층의 입력 영상은 필터를 거치게 됩니다. 필터 처리된 데이터를 특징맵(Feature Map)이라고 부르며, 몇 개의 필터를 사용하느냐에 따라 특징맵의 개수도 달라집니다. 이렇게 필터를 거쳐 특징맵들로 구성된 계층을 합성곱 계층이라고 부릅니다.
- 풀링 계층은 합성곱 계층의 입력을 받아 차원을 축소하는 계층입니다. 이 계층은 합성곱 계층 뒤에 따라옵니다. 풀링의 방법은 최댓값을 남기는 Max Pooling, 평균값을 남기는 Mean Pooling, 그리고 합을 구하여 남기는 Sum Pooling이 있습니다. 보통 입력층 데이터에서 2x2 크기의 윈도우 영역을 기준으로 가장 큰 값을 남기는 맥스 풀링을 많이 사용합니다. CNN은 이렇게 합성곱 계층과 풀링 계층을 통해 만들어진 계층의 출력이 DNN 입력으로 사용됩니다.
2) 전체 연결 계층
- 합성곱 계층과 풀링 계층의 최종 출력은 분류를 위한 전체 연결 계층(Fully Connected Layer)으로 전달됩니다. 이 계층에서 선형 결합과 소프트맥스에 의해 이미지의 분류가 이루어집니다.

- 합성곱 계층과 풀링 계층에서의 필터는 이미지의 특징을 더 잘 표현할 수 있는 방향으로 학습됩니다. 다시 말하면 인공신경망의 학습 과정에서 입력 이미지의 특징을 더 잘 표현할 수 있도록 필터가 학습으로 만들어진다는 의미입니다. 그래서 합성곱과 풀링을 포함한 계층을 특징 추출(Feature extraction) 계층이라고 합니다.
3) CNN을 이용한 MNIST 데이터 모델링 구성도
- 다음 그림은 MNIST 데이터를 분류하기 위한 CNN 모델링 구성도 입니다. 모든 선을 표시하면 구성도가 너무 복잡해지므로 이전 층의 출력이 다음 층의 입력으로 표현하는 선의 일부를 생략했습니다.

- 28x28 숫자 이미지 하나가 특징 추출 계층(Feature exetraction layer)의 첫 번째 층에 전달되면 conv2d 함수에 의해 필터(w, 임의의 값)가 적용되고, ReLU 활성화 함수를 통해 특징을 갖는 h_conv1 이미지가 출력됩니다. 이것은 다시 max_pool 함수에 의해 크기가 14x14로 줄어듭니다. 14x14 특징 이미지는 특징 추출 층의 두 번째 특징 추출 층의 두 번째 층에 전달되고, ReLU 활성화 함수를 통해 특징을 갖는 h_conv2 이미지가 출력됩니다. 이미지가 얼마만큼씩 줄어들게 할 것인지는 max_pool 함수의 스트라이드(stride)의 크기를 통해 결정합니다. 맥스 풀링 시 스트라이드가 이 예제에서처럼 [2, 2] 크기이면 이미지는 1/4로 줄어들고, [3, 3] 크기이면 이미지는 1/9 크기로 줄어듭니다.
- 특징 추출 층의 출력은 7x7 이미지 64개 입니다. 이들 이미지의 화소 들은 7*7*64개의 변수가 되고 이것은 분류를 위해 전체 연결 층(Fully connected layer)의 입력으로 전달되어 분류를 위한 학습을 진행하게 됩니다. 그림에서 전체 연결 층의 가중치는 입력하는 7x7 크기 이미지 64개의 화소 수와 같습니다. 이곳에서의 가중치는 임의의 수로 할당되고 학습이 진행되면서 출력 h가 정답에 가까워지도록 갱신됩니다. 전체 연결 층의 입력은 가중치와 선형 결합한 후 ReLU 함수가 적용되어 출력층으로 전달됩니다. 출력층의 가중치는 이전 층의 출력의 개수인 1,024개 입니다. 이곳에서는 분류를 위해 소프트 맥스 함수를 사용합니다.
- CNN에 적용되는 합성곱 계층의 입력과 출력, 그리고 풀링 계층의 입력과 출력에 대한 이해를 돕기 위해 필터 처리와 풀링을 통한 차원 축소에 대해 알아봤습니다. 그렇다고 해서 우리가 CNN을 이용해 영상을 학습시킬 때 필터를 지정해 주지는 않습니다. 우리의 목적은 필터를 찾는 것이 아닌 영상을 분류하기 위한 것입니다. 필터는 학습의 과정에서 인공신경망 학습으로 만들어집니다.
1.6 Conv2D
- 케라스의 Conv2D 클래스는 2D 이미지를 합성곱하기 위한 레이어를 만들어줍니다.

- Conv2D 클래스를 이용하여 입력 데이터가 28x28 크기 그레이스케일 이미지이며, 뉴런이 32개이고, 필터 커널의 크기가 3x3이며, 제로 패딩을 사용하고 활성화 함수가 ReLU인 층과 2x2 맥스 풀링 층을 추가하려면 다음과 같이 합니다.
from tensorflow.keras import Sequential, layers
model = Sequential([
layers.Input(shape=(28, 28, 1)),
layers.Conv2D(32, (3, 3), activation='relu',),
layers.MaxPooling2D(pool_size=(2, 2))
])
model.summary()
- 이 코드는 padding 매개변수를 설정하지 않았으므로 기본값인 'valid'가 적용되어 입력 이미지 28x28 크기가 Conv2D에 의해 합성곱 연산할 때 테두리 화소는 연산에 사용되지 않으므로 출력되는 모양은 26x26이며 그로 인해 맥스 풀링의 결과는 13x13이 됩니다.
- padding='same'을 사용하면 Conv2D에 의해 합성곱 연산할 때 이미지의 바깥 화소를 연산하기 위해서 주위에 0화소를 두어 연산합니다. 이것은 제로 패딩(Zero Padding)이라고 부릅니다.
'HRDI_AI > [AI특화] AI 기반 실시간 객체탐지 모델 구현' 카테고리의 다른 글
| 4. 합성곱 신경망 - (2) 손글씨 숫자 인식을 위한 CNN 구현 (0) | 2026.01.20 |
|---|---|
| 3. 인공신경망 모델 최적화 - (1) 과적합(Overfitting) (0) | 2025.12.16 |
| 3. 인공신경망 모델 최적화 (0) | 2025.12.16 |