컴퓨터 비전

PCA(주성분 분석)를 통해 이미지에서 특성 추출하기 - (1)

kdjames0930 2025. 11. 14. 00:02

 PCA(주성분 분석)는 고차원 데이터를 더 낮은 차원으로 축소하면서도, 데이터가 가진 중요한 특성을 최대한 보존할 수 있도록 해주는 기법이다. 이미지의 경우 각 픽셀이 하나의 차원으로 간주되기 때문에, 예를 들어 64×64 픽셀 이미지라면 무려 4096차원의 데이터가 된다. 하지만 PCA를 적용하면 이러한 고차원 이미지 데이터를 비교적 낮은 차원에서 효율적으로 표현할 수 있으며, 그 과정에서 이미지의 주요 특징을 효과적으로 추출해 활용할 수 있다.

 

 

그러면 직접 여러개의 이미지 데이터에서 PCA 기법을 이용해 특성을 추출해보도록 하겠다!

 

 

데이터는 Kaggle에 있는 "Cat & Dog Face 64x64 pixel" 데이터셋을 활용했다.

출처) https://www.kaggle.com/datasets/alessiosanna/cat-dog-64x64-pixel

 

Cat & Dog Face 64x64 pixel

55 images of Cats & 55 images of Dogs

www.kaggle.com

 


 

from PIL import Image
import numpy as np
import os
import matplotlib.pyplot as plt
from google.colab import drive
drive.mount('/content/drive')

img_directory = "/content/drive/MyDrive/Dataset/Cats And Dogs Faces 64x64"

img_files = [f for f in os.listdir(img_directory) if os.path.isfile(os.path.join(img_directory, f))]

imgs = []
imgs2 = []

for file_name in img_files:
  img_path = os.path.join(img_directory, file_name)
  img = Image.open(img_path)
  img_np = np.array(img)
  imgs.append(img)
  imgs2.append(img_np)

 

 

우선, Google Drive에 미리 저장해 둔 이미지 데이터를 불러온다.
이때 이미지를 두 가지 형태로 저장하는데, 첫 번째는 PIL 라이브러리를 이용하는 방법이다.
Image.open() 함수에 불러올 이미지의 경로를 인자로 전달하면, 해당 이미지를 PIL 이미지 객체로 반환받을 수 있다.
두 번째는 이미지를 NumPy 배열 형태로 변환하여 저장하는 방법으로, np.array() 함수를 통해 각 이미지를 배열로 바꾼 뒤 리스트에 저장한다. 오늘은 차원 축소를 직접 구현할 것이기에 NumPy배열 형태로 변환한 버전을 사용하겠다.

 

gray = np.dot(imgs2[0][..., :3], [0.2989, 0.5870, 0.1140])
imgs_gray = []
for img in imgs2:
  gray = np.dot(img[..., :3], [0.2989, 0.5870, 0.114])
  imgs_gray.append(gray)

plt.subplot(1, 2, 1)
plt.imshow(imgs_gray[10], cmap='gray') # 흑백 ver.
plt.subplot(1, 2, 2)
plt.imshow(imgs[10]) # 컬러 ver.

 

 사용할 이미지는 RGB로 구성되어 있어, 각 픽셀은 세 개의 채널 값을 갖는 3차원 데이터이다.
이후 계산의 편의성과 직관적인 분석을 위해 이러한 RGB 이미지를 Grayscale(흑백) 형태로 변환하여, 각 픽셀을 하나의 값으로 표현된 1차원 데이터로 사용하겠다.

 

 

그림1. 흑백, 컬러 출력 결과

 

 

PCA를 수행하기 위해 먼저 이미지 데이터의 공분산 행렬(Covariance Matrix)을 계산한다.
그 후 공분산 행렬의 고유벡터(Eigenvector)와 고유값(Eigenvalue)을 구하고, 고유값이 큰 순서대로 상위 고유벡터를 선택하여 이를 새로운 축으로 삼아 차원 축소를 진행할 것이다.

 

imgs_flatten = np.array([img.reshape(-1) for img in imgs_gray])

def center_data(M):
  center_data = np.mean(imgs_flatten, axis=0)
  center_matrix = np.repeat(center_data, 110)
  center_matrix = np.reshape(center_matrix, (110, 4096), order='F')
  X = M - center_matrix
  return X
  
X = center_data(imgs_flatten)
plt.imshow(X[12].reshape(64, 64))

def get_cov_matrix(X):
  cov_matrix = X.T @ X
  cov_matrix = cov_matrix / (X.shape[0] - 1)
  return cov_matrix

cov_matrix = get_cov_matrix(X)
print(f"shape of covariance matrix: {cov_matrix.shape}")

 

(64×64) 형태의 이미지를 벡터로 변환한 후, 110개의 이미지에서 각 차원(픽셀)에 대한 평균값을 계산한다.
그 다음, 각 이미지 벡터에서 해당 픽셀의 평균값을 빼 중심화된 데이터 X를 만든다. 이후 X의 Transpose Matrix와 X의 dot product를 계산하고 이를 (이미지의 수-1) 만큼 나눠주면 공분산 행렬을 구할 수 있다!

그림 2. Centered Image

 

import scipy.sparse.linalg

eigenvals, eigenvecs = scipy.sparse.linalg.eigsh(cov_matrix, k=110)
print(f'Ten largest eigenvalues: \n{eigenvals[-10:]}')

'''
출력 결과
Ten largest eigenvalues: 
[ 347635.61269444  359681.87486088  379914.99476597  454250.38220337
  626348.16100618  774121.43931159 1096787.74147599 1395346.16071232
 2314761.77294897 3674745.46755588
 '''

 

 

이제 Eigenvector, Eigenvalue를 계산해보자. 직접 계산을 할 필요 없이 여러 라이브러리를 사용할 수 있다. 

scipy.sparse.linalg.eigsh 함수에 앞서 구한 공분산 행렬과 구하고자 하는 고유벡터의 수(k=110)를 파라미터로 넘겼다. 

 

eigenvals = eigenvals[::-1]
eigenvecs = eigenvecs[:,::-1]

print(f'Ten largest eigenvalues: \n{eigenvals[:10]}')

'''
출력결과
Ten largest eigenvalues: 
[3674745.46755588 2314761.77294897 1395346.16071232 1096787.74147599
  774121.43931159  626348.16100618  454250.38220337  379914.99476597
  359681.87486088  347635.61269444]
'''

 

PCA를 수행하기 위해 먼저 Eigenvalue를 큰 순서대로 재정렬한다. 주성분 분석에서는 Eigenvalue 값이 큰 Eigenvector를 선택하여 데이터를 투영해야 한다.
이는 고유값이 큰 방향으로 데이터를 축소하면, 데이터의 분산이 가장 크게 유지되기 때문이며, 결과적으로 원본 데이터의 정보가 최대한 보존된다는 의미이다.

 

fig, ax = plt.subplots(4,4, figsize=(20,20))
for n in range(4):
    for k in range(4):
        ax[n,k].imshow(eigenvecs[:,n*4+k].reshape((64, 64)))
        ax[n,k].set_title(f'component number {n*4+k+1}')

 

앞서 구한 Eigenvector들이 이미지의 어떠한 특성을 담고 있는지 출력해서 확인해 보았다. 결과는 다음과 같다. 

 

그림 3. 이미지로 부터 추출한 특성 1~9 - Eigenvalue 값 높은 순

 

어느정도 고양이와 강아지의 모습을 담아내고 있다는 것을 확인할 수 있다. Component Number 2의 경우 눈이 또렷하게 보이는 것으로 봐서 "눈"이라는 특징이 추출된 게 아닐까 싶다. 뒤로 갈수록 Eigenvalue값이 낮아짐에 따라 출력된 결과 역시 약간식 흐릿해지는 점도 확인할 수 있었다. Eigenvalue값이 가장 낮은 Eigenvector들을 출력하면 다음과 같은 결과가 나온다. 

 

그림 4. 이미지로 부터 추출한 특성 1~9 - Eigenvalue값 낮은 순

 

자세히 들여다보면 고양이와 강아지 얼굴 형상 비슷한 무언가 보이는 듯 싶기도 하지만... Eigenvalue값이 큰 것들과 비교해보면 차이가 확연하다. 

 

다음에는 PCA 결과를 plot 해보고, 축소된 벡터를 다시 이미지로 복원(Image Reconstruction)시켜 보겠다!

반응형