데이터 전처리/python

이미지 전처리1 - RGB, HSV, resizing

JSMATH 2024. 5. 15. 05:45

이미지 전처리 하기에 앞서 

컴퓨터에서 이미지를 읽는 방법을 알아야합니다.

이미지 데이터를 픽셀이란 작은 점들의 집합으로 컴퓨터는 읽습니다.

각 픽셀마다 색과 밝기의 정보가 있는것이죠.

 

게임으로 예시를 들어볼까요?

서든어택이라는 게임 많이 아실겁니다. 이 게임은 게임자체에서 각 해상도별로 화면을 지원하지만 마우스 dpi 폴링레이트 지원률, fps 특성상 한 화면에 다 보여야 빠른반응이 가능하기에 여러 해상도가 있지만 제일 작은 해상도인 800*600을 사용합니다. 이미지가 뭉개져서 보이긴 하나 위의 장점이 더 크기에 800*600을 사용하는 것이죠.

 

이때 800*600을 가로 800픽셀, 세로600픽셀을 의미합니다.

 

이제 이미지처리하는데 있어 필요한 패키지를 설치해주어야 합니다.

주피터 노트북을 사용하시는 분들은 cmd 환경설정 파이썬 버전이 3.11.3인지 확인해주세요.

#name에 원하는 환경이름 적어주세요.
conda create -n name python=3.11.3

#환경진입
activate name

#버전이 잘 설치되었는지 확인
python 

pip install jupyter notebook 
pip install ipykernel

python -m ipykernel install --user --name=커널이름 --display-name'이름'

pip install opencv-python
pip install matplotlib
pip install pandas

환경과 커널을 만드셨다면 들어가셔서 아래 패키지를 임포트 해주세요.

import numpy as np
import pandas as pd
import cv2
import matplotlib.pyplot as plt

이제 아무 이미지나 하나 만들어봅시다. 

#균일분포로 난수 생성. 
#randint(0,256)은 0포함 256제외 최대 255까지.
#(100,100)은 사이즈 형태임.
#uint8은 8비트를 양의정수로 표현해준다.
random_image = np.random.randint(0,256,(100,100),dtype=np.uint8)

Uniform dist로 난수를 생성합니다.

8비트를 양의정수로 표현하는데

최소비트는 00000000부터 최대비트는 11111111 입니다.

왜그럴까요?

컴퓨터는 2진수로 데이터를 읽습니다. 0과1로만 데이터를 읽는 것이죠

예로들어 4라는 수는 컴퓨터가 00000100=2^2으로 읽는것이죠.

6이라는 수는 00000110=2^2+2^1 이런식으로..

그럼 11111111=2^7+2^6+..2^0 그럼 얼마일까요?

https://ko.wikipedia.org/wiki/%EB%93%B1%EB%B9%84%EC%88%98%EC%97%B4

 

등비수열 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 이 문서는 공비에 관한 것입니다. 다른 뜻에 대해서는 공비 문서를 참고하십시오. 등비수열(等比數列, 문화어: 같은비수렬, 영어: geometric sequence) 또는 기하수열

ko.wikipedia.org

등비수열의 합(출처:위키피디아)

 

위의 공식에 의해 2^7-1 =255가 되겠습니다.

그렇기에,

random_image = np.random.randint(0,256,(100,100),dtype=np.uint8)에서 [0,256) 이라는 구간을 정한이유가 양의 8비트를 나타내기 위함임을 알 수 있습니다. 1픽셀에 1byte=8bit를 요구하는데 8비트 8비트 하는 이유가 1픽셀의 1byte단위이기 때문이죠. 뭐 아무튼 말이 길어졌는데,

random_image.shape
#(100, 100)

np.max(random_image)
#255

random_image
#array([[150, 188, 223, ...,  65,  36,  16],
#       [246,  20,  42, ..., 204, 180,  64],
#       [132,  74, 208, ..., 246, 106, 161],
#       ...,
#       [147, 144,   5, ..., 200, 227,  23],
#       [166, 218,   7, ..., 120,  39, 161],
#       [111,  26, 143, ..., 208, 182,  33]], dtype=uint8)

잘 나왔음을 확인 할 수 있습니다. 실제 난수이미지는 어떨까요?

plt.figure(figsize=(12, 4))
plt.imshow(random_image)

난수 이미지

 

그럼 실제 이미지로 해보겠습니다. 아무 이미지나 가능합니다. 

요근래에 GPT4o가 나온 것을 알고계신가요? 갑자기 생각난김에 저는 GPT에 구글에서 긁은 이미지를 넣어 생성이미지로 작업해보겠습니다.

 

이미지는 파이썬 디렉토리에 넣어주셔야 이미지를 읽어 올 수 있습니다.

 

image = cv2.imread('image.png')

image.shape
#(1024, 1024, 3)

#색상,명도,채도를 분리하지 못함->계산이 빠르다.
#R,G,B 각각 255가지 색을 표현가능. 총 255^3
plt.figure(figsize=(12, 4))
plt.imshow(image)

RGB_image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)
RGB_image.shape

 

BGR로 읽어온 이미지

 

RGB로 읽어온 이미지

기본적으로 이미지를 BGR로 읽어옵니다.

BGR로 읽은 이미지는 뭔가 많이 이상해보입니다. RGB로 읽은 이미지는 정상이미지로 보입니다.

실제로 우리 시각에 보이는 색상 형태는 RGB입니다. 그렇기에 컴퓨터가 기본적으로 BGR로 읽어오지만 컨버트를 시켜주어 RGB로 변환해주어야 우리의 눈에 익숙한 이미지가 나오는 것이죠.

 

RGB(Red,Green,Blue)의 각 채널을 분리하여 한번 보면 다음과 같습니다.

#RGB 채널 분리.
R,G,B = cv2.split(RGB_image)

G.shape
#(1024, 1024)

#각 채널을 이미지로 표시
plt.figure(figsize=(12,4))
#빨간 채널
plt.subplot(1,4,1)
plt.imshow(R,cmap='Reds')
plt.title('Red Channel')

#초록 채널
plt.subplot(1,4,2)
plt.imshow(G,cmap='Greens')
plt.title('Green Channel')

#파란 채널
plt.subplot(1,4,3)
plt.imshow(B,cmap='Blues')
plt.title('Blue Channel')

#총 채널
plt.subplot(1,4,4)
plt.imshow(RGB_image)
plt.title('Total Channel')

*subplot(start,end,order)로 구성.

 

우선 G.shape를 보아하니 기본적인 형태는 같습니다. 이로써 위의 image.shape =(1024,1024,3)에서 3이 RGB임을 유추 할 수 있습니다. RGB_image[0], RGB_image[1], RGB_image[2] 가 각각 R,G,B를 뜻하는 것을 알 수 있다는 것이죠.

RGB 색상분리

cmap은 각 이미지별 색상을 뜻합니다.  만약 cmap을 모두 제거한다면 다음과 같이 나옵니다.

cmap X

cmap이 제거된 모습에서 각 이미지가 비슷하나 강조된 부분이 조금씩 다름을 알 수 있습니다. 당연하게도

R,G,B 코드를 눌러보면 배열이 조금씩 다르기 때문입니다.

 

별개로 cmap 색기능도 여러가지 있습니다. 기본적으로 적지 않으면 'viridis'이며,

 

#각 채널을 이미지로 표시
plt.figure(figsize=(12,4))
#빨간 채널
plt.subplot(1,3,1)
plt.imshow(R,cmap='plasma')
plt.title('Red Channel')

#초록 채널
plt.subplot(1,3,2)
plt.imshow(G,cmap='magma')
plt.title('Green Channel')

#파란 채널
plt.subplot(1,3,3)
plt.imshow(B)
plt.title('Blue Channel')

여러 cmap별 이미지

 

하지만 우리는 RGB만으로 색을 제대로 구별하기 어렵습니다. 실제로 색이 얼마나 강렬한지 밝기는 어디서 더 밝고 어두운지 우리의 시각으로 구별해내죠. 변수가 더 들어가기에 계산은 좀 더 RGB보다 복잡해집니다.

 

#HSV(색상,채도,명도)
#색상:RGB, 채도:색의 강렬함, 명도:밝기
#아래는 이미지를 BGR -> RGB -> HSV로 변환하는 과정
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image_hsv = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2HSV)
image_hsv.shape
#(1024, 1024, 3)

#위의 RGB분리처럼 HSV도 분리하자!
H,S,V = cv2.split(image_hsv)
# 각 채널을 그레이스케일 이미지로 표시
plt.figure(figsize=(12, 4))
plt.subplot(1, 4, 1)
plt.imshow(H, cmap='hsv')
plt.title('Hue Channel')

plt.subplot(1, 4, 2)
plt.imshow(S, cmap='gray')
plt.title('Saturation Channel')

plt.subplot(1, 4, 3)
plt.imshow(V, cmap='gray')
plt.title('Value Channel')

plt.subplot(1, 4, 4)
plt.imshow(RGB_image)
plt.title('Base')

HSV

왼쪽의 첫 이미지는 RGB로 색상을 표현했습니다. 파란색이 많은 부분은 파란색으로 다 칠해져있고 좀 밝은색 부분은 붉은색이 칠해져있습니다.

두번째 이미지(채도)는 어두운 부분은 좀 더 밝은 색이고 밝은 부분은 더 어두운 색입니다. 

 

 

 

이런 이미지들을 계산한다면 변수량이 많아져서 처리속도가 많이 느려질 것입니다.

그렇기에 특징을 가진 부분만 사이즈를 다시 조절하여 계산속도를 상승시키면 성능이 올라 갈 여지가 높습니다.

#이미지 리사이징
#단순 이미지 리사이징
resized_image = cv2.resize(RGB_image, (64, 64))
#이미지 보간 리사이징
resized_image = cv2.resize(RGB_image, (64, 64), 
interpolation=cv2.INTER_AREA)

그냥 이미지 리사이즈는 cv2.resize로 어느 사이즈로 리사이징할지 정해주면 되고,

interpolation(보간) 각 픽셀에 연결지점을 어떻게 연결시켜줄지의 방법을 정해줄려면 interpolation을 적용시켜주면 됩니다.

interpolation이 없는 리사이징
interpolation이 적용된 리사이징

interpolation이 적용 전,후를 비교해보면 좀 더 픽셀 연결지점이 부드러워짐을 알 수 있습니다.