Heesung Yang
컴퓨터는 이미지를 어떻게 저장하고 표현할까?
이미지 들여다보기
멋드러지게 사진을 찍고 있는 한 남자의 흑백 사진(이미지)를 보자.
그냥 딱 흑백 사진이다. 이상해 보이지 않는다. 이 이미지를 확대해보자.
약간 이상해졌다. 모자이크처럼 보인다. 조금 더 확대해보자.
이건 … 도통 뭔지 모르겠다. 그냥 작은 네모칸들의 집합이다. (눈썰미 좋은 사람은, 사진 속 남자의 눈 부분이라는걸 알 수도…?)
이게 바로 이미지의 실체다. (음!?)
네모칸마다 색이 칠해져 있고, 이 네모칸들이 가로/세로로 배열되어 있다. 이 내용을 약간의 전문용어(?)로 정리해보자.
- 각 네모칸은 픽셀(Pixel)이라고 부른다.
- 가로/세로 각각 몇 개의 픽셀이 있느냐 = 이미지의 해상도가 얼마냐와 같은 말이다.
- 해상도 800x600 인 이미지는 가로 픽셀 800개, 세로 픽셀 600개인 이미지이다.
- 모니터에도 같은 개념이 적용된다. 요즘 핫한(?) 4K 모니터는 가로 3840개, 세로 2160개까지 표현할 수 있다.
- 각 픽셀마다 표현할 수 있는 색이 몇 개냐에 따라 8비트 이미지, 16비트 이미지, 32비트 이미지라고 부른다.
자, 위 개념을 컴퓨터에서 어떻게 구현하는지 몇 가지 예를 통해 알아보자.
색칠 공부
위 그림을 누구나 그릴 수 있도록 만드려면 어떻게 해야 할까?
-
우선, 정사각형 가로/세로 16개를 그린다.
-
칠해야 하는 색깔별로 숫자를 정한다. (팔레트를 만든다.)
-
각 칸마다 숫자를 적는다.
-
팔레트에 적힌 숫자와 색을 맞춰 색을 칠한다.
어렸을 때 했던 색칠 공부가 떠오르지 않는가? (십자수를 해봤던 사람이라면 십자수 도안이 떠올랐을 수도 있고) 컴퓨터도 동일한 원리로 이미지를 저장하고 표현한다.
컴퓨터에서 숫자와 색깔의 관계
위 오리 그림은 5개의 색상을 사용했다. 그리고 각 색상을 숫자 0부터 4로 표현했다. 컴퓨터는 아래와 같은 규칙으로 숫자로 색을 표현한다. 먼저 흑백 이미지부터 알아보자.
흑백 이미지
흑백 이미지는 검은색과 흰색을 256단계로 나눠서 표현한다. (숫자 0부터 255까지) 검은색은 0, 흰 색을 255로 표현하며 1~254 사이의 숫자로 검은색과 흰색의 중간을 표현한다.
아래는 오리 그림을 0~255 사이의 적당한 숫자를 사용하여 흑백으로 표현해 본 그림이다.
그리고 아래는 이 글 중간에 나왔던 확대 이미지를, 마찬가지로 0~255 사이의 숫자로 표현한 그림이다.
컬러 이미지
혹시 RGB라는 단어를 본 적이 있는가? 포토샵 또는 이미지 편집 프로그램을 사용해봤다면 아마 많이 봤던 단어일 것이다. RGB는 Red/Green/Blue 의 약자로, 이 세가지 색을 조합하여 컬러 이미지를 나타낼 수 있다.
실제로는 더 다양한 표현 방법이 있지만(CMYK, HSV, YUV, YCbCr) 여기서는 개념 이해를 위해 RGB에 대한 설명만 진행한다.
흑백 이미지와 마찬가지로 각각의 색은 0~255까지의 숫자로 색의 농도를 표현할 수 있고, 항상 이 3가지 색의 조합으로 표현해야 한다. RGB라는 이름에 볼 수 있듯이 Red,Green,Blue 순서로 표현해야 한다. 예를 들어 보자.
Red : (255,0,0)
Green : (0,255,0)
Blue : (0,0,255)
Black : (0,0,0)
White : (255,255,255)
White 부분이 조금 이상하게 생각될 수 있다.(내가 그랬다.) 모든 색을 최고 농도로 설정하면 하얀색이 된다고…? 찾아보면 가산혼합과 감산혼합이라는 어려운 용어가 나오는데, 일단 그냥 외우자. 컴퓨터에서 RGB는 가산혼합 방식이며, 색을 섞을 수록 점점 더 밝아진다. 그래서 모든 색을 다 섞으면 가장 밝은 흰색이 된다. 끝.
자, 우리의 오리
를 다시 보자. 위 예에서는 임의로 0,1,2,3,4 의 숫자로 색을 표현했지만 이번에는 컴퓨터가 사용하는 RGB 값으로 표현해보자.
와우, 뭔가 더 복잡해 보인다. 흑백 이미지는 픽셀 하나를 표현할때 숫자 하나만 사용했다. RGB는 픽셀마다 숫자 3개씩 사용해야 한다. 그래서 같은 이미지를 흑백,컬러로 저장하면 컬러 이미지 용량이 3배 더 크다! (실제로는 이미지 압축을 하기때문에 좀 덜 크지만 압축을 배제했을 때)
Python을 이용하여 숫자로 이미지 그리기
자, 이론을 배웠으니 실제로 한번 만들어보자. 사용할 프로그래밍 언어는 Python3
다. 위 예시에서 보았던 흑백 오리 그림부터 그려보자.
# 필요한 패키지 설치
pip install numpy Pillow
from PIL import Image
import numpy as np
# 16x16 모눈종이
duck = np.array([
[255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
[255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255],
[255,255,255,255,201,201,201,201,201,255,255,255,255,255,255,255],
[255,255,255,201,201,201,201,201,201,201,255,255,255,255,255,255],
[255,255,255,150,201, 0,201,201,201,201,255,255,255,255,255,255],
[255,150,150,150,201,201,201,201,201,201,255,255,255,255,255,255],
[255,255,150,150,150,201,201,201,201,255,255,255,255,255,255,255],
[255,255,255,255,201,201,201,201,255,201,201,201,255,201,255,255],
[255,255,255,201,201,201,201,201,201,201,201,201, 0,201,255,255],
[255,255,201,201,201,201,201, 0,201,201,201, 0,201,201,255,255],
[255,255,201,201,201,201,201,201, 0, 0, 0,201,201,201,255,255],
[ 50, 50,201,201,201,201,201,201,201,201,201,201,201,201, 50, 50],
[ 50, 50, 50,201,201,201,201,201,201,201,201,201,201, 50, 50, 50],
[ 50, 50, 50, 50,201,201,201,201,201,201,201,201, 50, 50, 50, 50],
[ 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50],
[ 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50],
], dtype=np.uint8)
img = Image.fromarray(duck)
img.show()
이미지 크기가 매우 작기 때문에(16x16) 크게 확대했다.
다음은 컬러 오리다!
from PIL import Image
import numpy as np
duck = np.array([
[[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255]],
[[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255]],
[[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255]],
[[255,255,255],[255,255,255],[255,255,255],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255]],
[[255,255,255],[255,255,255],[255,255,255],[255,153, 0],[255,229,153],[ 0, 0, 0],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255]],
[[255,255,255],[255,153, 0],[255,153, 0],[255,153, 0],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255]],
[[255,255,255],[255,255,255],[255,153, 0],[255,153, 0],[255,153, 0],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255]],
[[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,255,255],[255,229,153],[255,229,153],[255,229,153],[255,255,255],[255,229,153],[255,255,255],[255,255,255]],
[[255,255,255],[255,255,255],[255,255,255],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[ 0, 0, 0],[255,229,153],[255,255,255],[255,255,255]],
[[255,255,255],[255,255,255],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[ 0, 0, 0],[255,229,153],[255,229,153],[255,229,153],[ 0, 0, 0],[255,229,153],[255,229,153],[255,255,255],[255,255,255]],
[[255,255,255],[255,255,255],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[ 0, 0, 0],[ 0, 0, 0],[ 0, 0, 0],[255,229,153],[255,229,153],[255,229,153],[255,255,255],[255,255,255]],
[[ 74,134,232],[ 74,134,232],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[ 74,134,232],[ 74,134,232]],
[[ 74,134,232],[ 74,134,232],[ 74,134,232],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[ 74,134,232],[ 74,134,232],[ 74,134,232]],
[[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[255,229,153],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232]],
[[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232]],
[[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232],[ 74,134,232]],
], dtype=np.uint8)
img = Image.fromarray(duck)
img.show()
마찬가지로 크게 확대했다.
마치며
지금까지 이미지 처리를 위한 기본적인 내용을 살펴보았다. 이미지가 숫자로 이뤄졌기 때문에 해볼 수 있는 재밌는 것들이 꽤 있다. 이미지 밝기를 조절한다던가, 이미지 명도를 조절한다던가 혹은 최근 스마트폰 앱에서 많이 하는 필터링/보정 같은 것들도 결국 숫자를 어떻게 변화시키냐의 얘기다. 다음 글에서는 해당 내용에 대해 다뤄보겠다.
Previous post
AWS CLI [EC2] - 생성하기Next post
스마트폰 앱은 어떻게 이미지를 보정하는 걸까?