영상 데이터

소개

이미지를 저장하는 형식은 jpg, png, tif, gif등 여러 가지가 있다. 이미지는 픽셀(화소)로 구성된다. 다음 설명은 파이썬 모듈 Pillow에서 사용되는 형식이다. 이미지를 다루는 도구마다 약간씩 차이가 있다.

크기와 좌표(coordinate system)

  • 이미지의 크기는 너비(또는 가로, width), 높이(또는 세로, height)로 구성된다. 너비와 높이는 픽셀의 갯수로 결정된다.

  • 픽셀은 2차원 좌표로 표현할 수 있다. 이미지의 왼쪽 위가 (0,0)이고, 각 픽셀은 좌표 (x, y)로 나타낸다. 첫번째 성분 x는 너비를 나타내고 두번째 성분 y는 높이를 나타낸다. x는 왼쪽에서 오른쪽으로 증가하고, y는 위에서 아래로 증가한다.

  • 이미지의 부분을 표현하기 위해서 사각형을 이용한다. 사각형은 (x, y, z, w)로 표현하며 x, y는 각각 부분 이미지의 왼쪽, 위의 좌표를 나타내고, z, w는 각각 부분 이미지의 오른쪽, 아래의 좌표를 나타낸다. z, w는 포함되지 않는다. (0, 0, 800, 600)은 800x600 크기를 표현한다.

모드(mode)

모드는 픽셀값을 구성하는 방법이다.

  • 이진 영상(binary image)는 픽셀이 1 비트로 구성되어 있어서 검은색은 0, 흰색은 1로 표현한다.

  • 흑백 영상(gray-scale image) 흑백의 표현을 0 - 255의 숫자로 표현한다. 가장 어두운 것은 0, 가장 밝은 것은 255로 표현한다. 하나의 픽셀을 표현하기 위해 8비트가 사용된다.

  • 컬러 영상(color image)은 하나의 픽셀을 표현하기 위해서 3개의 색 빨간(R), 초록(G), 파란(B)색을 사용한다. 각각의 R, G, B는 8비트로 이루어져 있으며 0 - 255의 숫자로 표현한다. 숫자가 커지면 각각의 색이 짙어진다. 빚의 삼원색 원리가 적용되어 (255, 255, 255)는 흰색을 나타내고 (0, 0, 0)은 검은색을 나타낸다. 뿐만 아니라 투명도를 표현하기 위해서 Alpha 채널을 추가하여, (R, G, B, A) 4개의 채널을 사용하기도 한다. alpha 채널에서 0는 투명, 255는 불투명을 의미한다. 이외에도 HSV(Hue, Saturation, Intensity), CMYK, YCbCr 모델등 여러 가지가 있다.

밴드(band)

밴드 또는 채널(channel)이라고 하며 픽셀을 구성하는 방법으로 흑백은 1 채널을 가지고 있고 컬러 영상은 R, G, B 3개의 채널(밴드)를 가지고 있다. 또는 R, G, B, A이면 4개의 밴드를 가지고 있다.

준비

이미지 처리를 하기 위한 모듈 Pillow를 설치한다. Pillow 는 다양한 형식의 이미지를 읽고 쓸 수 있으며 크기 조절, 회전, 임의의 affine 변환을 할 수 있다.

pip install Pillow

우선 필요한 이미지를 웹사이트에서 다운받아 images 폴더를 만들고 그 안에 저장한다.

import requests
import os
from io import BytesIO

directory = 'images'
if not os.path.exists(directory):
    os.makedirs(directory)

url = 'https://cdn.pixabay.com/photo/2017/11/03/17/21/koala-2914975_640.jpg'
response = requests.get(url)
img = Image.open(BytesIO(response.content))

filepath = directory + '/koala.jpg'
if not os.path.exists(filepath):
    img.save(filepath)

# logo_url = 'https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/Python.svg/48px-Python.svg.png'
# response = requests.get(logo_url)
# logo_img = Image.open(BytesIO(response.content))

# logo_img.save('images/logo.png')

# jisung_url = 'http://i.imgur.com/pbgsMRV.jpg'
# response = requests.get(jisung_url)
# jisung_img = Image.open(BytesIO(response.content))

# jisung_img.save('images/jisung.png')

이미지 읽기

from PIL import Image
import matplotlib.pyplot as plt

# 이미지 열기
코알라 = Image.open('images/koala.jpg')

# 이미지 정보 출력
print(코알라.format, 코알라.size, 코알라.mode)
JPEG (640, 425) RGB

이미지 보기

pyplot.imshow() 메소드를 이용해서 이미지를 볼 수 있다.

# 이미지 보기
plt.imshow(코알라)
plt.show()
_images/ImageData_6_0.png

또한 Image.show() 메소드를 이용해서도 볼 수 있는데 이것은 시스템 기본 프로그램을 이용해서 그림을 본다.

코알라.show()

저장

save(파일이름) 메소드를 이용해서 파일 이름을 지정하면 파일 확장자 형식에 맞추어 저장을 한다.

# 이미지 JPG로 저장
코알라.save('images/koala.jpg')

잘라내기/붙이기

  • 잘라내기

crop(사각형튜플) 메소드를 이용해서 원래 이미지의 부분을 잘라낼 수 있다. 사각형튜플(왼쪽위x, 왼쪽위y, 오른쪽아래x, 오른쪽아래y)와 같이 구성된다.

코알라 = Image.open('images/koala.jpg')
사각 = (138, 50, 455, 400)
코알라만 = 코알라.crop(사각)
# cropImage.save('images/lenna-crop.jpg')
plt.imshow(코알라만)
plt.show()
# 코알라만.show() # 윈도우즈 기본 그림 보기 프로그램 실행
_images/ImageData_12_0.png
  • 붙이기

paste(region, box)를 이용해서 region이미지를 직사각형 box 위치에 붙여 넣을 수 있다. region의 크기와 box의 크기가 같아야 한다.

img1 = 코알라.copy()
box = (50, 50, 350, 350)
cropImage = img1.crop(box)
region = cropImage.rotate(180)
img1.paste(region, box)
plt.axis("off")
plt.imshow(img1)
plt.show
<function matplotlib.pyplot.show>
_images/ImageData_14_1.png
  • 파이썬 로고 붙이기

img = Image.open('images/lenna.png')
logo = Image.open('images/logo.png')
img1 = img.copy()
position = ((img1.width - logo.width), (img1.height - logo.height))
img1.paste(logo, position)
img1.paste(logo, position, logo)
img1.save('images/lenna-logo.jpg')

paste 함수의 3번째 인수는 mask이고 mask가 될 수 있는 이미지의 모드는 ‘1’(이진 영상), ‘L’(8비트 흑백 영상), ‘RGBA’이다. ‘RGBA’ 영상을 사용하면 alpha 채널의 값을 mask로 사용하게 된다. 알파 채널에서 0은 투명을 의미하고 255는 불투명을 의미한다.

print("logo의 모드:", logo.mode, "(0,0)에서의 픽셀(r,g,b,a):", logo.getpixel((0,0)))
logo의 모드: RGBA (0,0)에서의 픽셀(r,g,b,a): (0, 0, 0, 0)

logo 이미지의 픽셀 (0,0) 위치에서 alpha 밴드의 값이 0(투명)이므로, 기존 이미지 lenna.png의 픽셀을 그대로 보여주게 된다.

회전 및 크기 변경

썸네일(thumnail)

thumnail(크기) 메소드를 이용해 원래 이미지보다 작거나 같은 크기로 변경한다. 크기 = (가로, 세로) 튜플 형태를 가지며 가로, 세로 중 작은 것을 기준으로 원래 이미지와 비율을 맞추어 줄인다. thumnail() 메소드는 자신의 이미지 크기를 변경하므로 원하지 않으면 copy() 메소드를 이용해 사본을 만들어 사용한다.

# Thumbnail 이미지 생성
크기 = (128, 128)
코알라1 = 코알라.copy() # 코알라 이미지 사본
코알라1.thumbnail(크기)
plt.imshow(코알라1)
plt.show()
print('이미지 크기: {}'.format(코알라1.size))
_images/ImageData_20_0.png
이미지 크기: (128, 85)

크기 변경

from PIL import Image
코알라 = Image.open('images/koala.jpg')

# 크기를 400x400 으로
코알라1 = 코알라.resize((400, 400))
코알라1.show()
# 코알라1.save('images/koala_400.jpg')
print('변경된 이미지 크기: {}'.format(코알라1.size))
변경된 이미지 크기: (400, 400)

회전

# 시계 반대 방향으로 90도 회전
img3 = img.rotate(90)
# img3.save('images/lenna-rotate.jpg')

plt.imshow(img3)
plt.show()
_images/ImageData_24_0.png
img4 = img.rotate(18)
plt.imshow(img4)
<matplotlib.image.AxesImage at 0x2425ab82a20>
_images/ImageData_25_1.png

expand=True 선택을 하면 전체 그림이 보이며 새로 만들어진 이미지의 크기는 증가한다.

img5 = img.rotate(18, expand=True)
plt.imshow(img5)
<matplotlib.image.AxesImage at 0x2425c2425f8>
_images/ImageData_27_1.png

필터링

filter() 메소드와 BLUR, CONTOUR, DETAIL, EDGE_ENHANCE, EDGE_ENHANCE_MORE, EMBOSS, FIND_EDGES, SMOOTH, SMOOTH_MORE, SHARPEN 등의 필터들을 사용하여 이미지를 변형한다.

  • 블러링

from PIL import Image, ImageFilter

img = Image.open('images/koala.jpg')
blurImage = img.filter(ImageFilter.BLUR)
plt.imshow(blurImage)
plt.show()
_images/ImageData_29_0.png

직접하기

  • 각각의 필터링을 적용해보자.

PIL.ImageFilter.RankFilter(size, rank), PIL.ImageFilter.GaussianBlur(radius=2), PIL.ImageFilter.Kernel(size, kernel, scale=None, offset=0) 등의 클래스를 이용할 수 있다.

PIL.ImageFilter.RankFilter(size, rank)에서 size는 창의 크기로 홀수만 가능하다. rank는 창 안에 있는 값들을 순서대로 배열했을 때 순서를 의미한다. 0은 가장 작은 값, size * size -1은 가장 큰 값이다.

from PIL import Image, ImageFilter

코알라 = Image.open('images/koala.jpg')
코순서 = 코알라.filter(ImageFilter.RankFilter(5, 24))
plt.imshow(코순서)
plt.show()
_images/ImageData_32_0.png

transpose

transpose(method) 함수와 method 인자을 이용하여 이미지를 대칭 또는 90도 단위로 회전할 수 있다. method가 될 수 있는 것은 PIL.Image.FLIP_LEFT_RIGHT, PIL.Image.FLIP_TOP_BOTTOM, PIL.Image.ROTATE_90, PIL.Image.ROTATE_180, PIL.Image.ROTATE_270 or PIL.Image.TRANSPOSE이 있다.

img = Image.open('images/koala.jpg')
flipImage = img.transpose(Image.FLIP_LEFT_RIGHT)

plt.imshow(flipImage)
plt.show()
_images/ImageData_34_0.png

점(픽셀) 연산

point() 메소드를 이용하여 각 픽셀에 대한 연산을 수행하여 픽셀의 값을 변경할 수 있다. point() 메소드는 이미지 데이터 형의 최대값을 넘으면 모두 최대값으로 변경한다.

# 각 픽셀에 2를 곱한다. 전체적으로 이미지가 밝아지는 효과.
out = img.point(lambda i: i * 2)
plt.imshow(out)
plt.show()
_images/ImageData_36_0.png
  • 채널에 대한 연산을 수행

# 각각의 채널로 이미지를 분리한다.
source = img.split()

R, G, B = 0, 1, 2

# 빨간색이 100 보다 크면 255, 그렇지 않으면 0
mask = source[R].point(lambda i: i > 100 and 255)

# 녹색 채널의 값을 줄인다.
out = source[G].point(lambda i: i * 0.7)

# 빨간색이 100보다 큰 부분은 녹색값이 줄어들고 그렇지 않으면 원래값을 갖는다.
source[G].paste(out, None, mask)

# build a new multiband image
img1 = Image.merge(img.mode, source)

plt.imshow(img1)
plt.show()
_images/ImageData_38_0.png

x and y 연산은 x가 거짓(0)이면 결과값은 x(0)이고 참이면 결과값은 y가 된다.

히스토그램

histogram() 메소드를 이용하여 히스토그램 정보를 리스트로 돌려받는다. RGB 모드이면 256 x 3 = 768개의 리스트를 반환한다.

import numpy as np

hist = img.histogram()
print(len(hist))
hista = np.reshape(hist, (3, 256))
plt.plot(hista[0], 'rs-', hista[1], 'go-', hista[2], 'bx-')
plt.show()
768
_images/ImageData_41_1.png

numpy array를 이용

numpy.array() 또는 numpy.asarray()를 이용해서 numpy array 객체로 변경한 다음 연산을 한 후 Image.fromarray() 메소드를 이용해서 이미지 객체로 변경하여 사용할 수 있다.

import numpy as np
from PIL import Image

arr = np.array(img)
print(arr.shape)

# arr[:, :, 0] * 1.2
(425, 640, 3)

point() 메소드와 달리 넘파이 연산은 데이터 형 최대값을 넘으면 (최대값 + 1)로 나눈 나머지가 저장된다. 따라서 원하지 않는 결과가 나올 수 있으므로 주의해야 한다.

opencv

opencv 패키지를 이용해서 이미지를 다룰 수 있다. 이것은 다음 기회로 미룬다. 파이썬 opencv 문서는 https://docs.opencv.org/3.3.1/d6/d00/tutorial_py_root.html를 참조한다.

참고 사이트