콘텐츠로 건너뛰기
Home » Pillow 기본 사용법

Pillow 기본 사용법

PIL은 (아마 짐작이 가능하겠지만) Python Imaging Library의 약자로, 파이썬에서 여러 포맷의 이미지를 읽고 조작할 수 있게 하는 라이브러리이다. GUI 관련 라이브러리/프레임워크들이 일부 이미지 조작과 관련한 기능을 제공하긴 했지만, 2000년대 초반에 ‘파이썬스럽게’ 이미지를 처리할 수 있고, 가장 많은 기능을 사용할 수 있었다. 그러나 행복한 나날은 오래가지 못했는데, 2009년쯤해서 PIL은 더이상 새 버전이 릴리즈 되지 않았다.

이후 이 프로젝트를 포크한 Pillow 가 출범하여 명맥을 이어오고 있다. Pillow는 PIL에 대한 drop-in replacement를 목표로 하여, PIL을 사용했던 코드도 수정 없이 작동하도록 개발되었다. 따라서 만약 시스템에 오래된 PIL이 설치되어 있다면, Pillow를 설치하기 전에 먼저 PIL을 제거해야 한다.

설치하기

PIL은 pip가 보편화되기 이전의 물건이므로 setuptools의 규격을 따르지 않았다. 따라서 별도의 설치 패키지를 통해 설치하거나 따로 빌드해야 했는데, pillow는 이제 wheel 패키지를 제공하기 때문에 pip 명령만으로 간단하게 설치할 수 있다.

> pip install pillow

이미지 로드하기

pillow는 여러 개의 모듈로 나뉘어져 각각의 기능을 제공하는데, 기본적으로 이미지에 대한 입출력은 Image 모듈이 담당한다. Image.open() 함수를 사용해서 이미지 파일을 로드하여 이미지 객체를 만들 수 있다. 참고로 프로젝트 이름은 pillow로 구분되어 있지만, PIL을 완전히 대체하기 위한 용도로 제작되었스므로 패키지 이름은 PIL을 그대로 사용한다. (그래서 이전에 PIL이 설치된 환경에 설치하면 안된다)

로드된 이미지 객체는 show() 를 통해서 볼 수 있다. pillow는 GUI관련 코드를 포함하지 않으므로, 시스템에 설치된 기본 이미지 뷰어를 호출할 것이다. 만약 Jupyter 노트북이라면 노트북 상에서 이미지를 표시해준다.

from PIL import Image
image = Image.open('sample.jpg')
image.show()

이미지 객체는 그 외에도 여러 속성 및 메소드를 가지고 있어서 이미지에 대한 정보를 얻거나, 기본적인 변환을 할 수 있다.

  • image.width, image.height, image.size : 이미지의 크기 정보를 확인한다.
  • image.save() : 이미지를 주어진 이름이나 경로에 파일로 저장한다. 파일 확장자에 따라서 포맷이 자동으로 채택된다.
  • image.resize((w, h)) : 이미지를 특정 크기로 리사이징한다. 이 때, 크기 값은 가로, 세로를 각각 주는 것이 아니라 튜플로 넘겨주는 것에 주의하자. resample= 인자를 통해서 리샘플링 방법을 결정할 수 있다. 리샘플링 방법은 Image 내에 상수로 정의되어 있으며, 다음과 같은 것들이 있다. : NONE(=NEAREST), BOX, HAMMING, LINEAR (=BILINEAR), CUBIC (=BICUBIC), ANTIALIAS (=LANCZOS)
Resizing examples by resampling method
  • image.crop(box) : 특정 영역을 오려낸 사본을 만든다. box는 (left, upper, right, lower)로 구성된 튜플이다.
  • image.paste(im, box=None, mask=None) : 다른 이미지를 이미지 내 특정 영역이나 위치에 붙여넣는다. mask를 써서 붙이는 이미지에 마스크를 적용할 수 있다. 마스크는 이미지 외에 투명도값을 줄 수 있다.
  • image.filter() : 필터를 적용한다. 적용할 수 있는 필터는 ImageFilter 모듈에 정의되어 있다.
  • image.rorate(angle, resample=0, expand=0, center=None, translate=None) : 주어진 각도만큼 이미지를 회전한다. 각도는 도(degree)로 주어진다.
  • image.transform(size, method, data=None, resample=0, fill=1, fillcolor=None) : 이미지를 변형한다.
  • image.thumbnail(size) : 이미지를 썸네일로 변경한다. 사본을 만드는 것이 아님에 주의하자.

이미지에 필터를 적용하기

이미지 객체의 filter(flt) 메소드는 필터를 적용한 사본을 생성한다. 사용 가능한 필터들은 ImageFilter 모듈에 정의되어 있다. 다음은 몇 가지 필터를 적용한 예시이다. 필터들은 클래스로 정의돼 있으며, filter() 메소드에는 클래스를 넘기거나, 필터 인스턴스를 생성해서 전달할 수 있다. 인스턴스를 전달해야 하는 경우는 필터에 설정값이 있는 경우이다. (Blur의 경우 반경값 등)

Filter examples

이미지를 새로 만들기

Image.new(mode, size, color=0)을 사용해서 새 이미지를 생성한다. 생성한 이미지에는 다른 이미지를 paste 하거나 그외 다른 그리기를 할 수 있다. (이 포스팅의 예제 이미지들도 이런식으로 생성했다.) mode 값은 문자열로 색상 모드를 가리킨다. 통상 “RGB”를 사용하며, 투명한 배경을 갖는 이미지를 만들려면 “RGBA”를 사용하면 된다. size는 (width, height)로 구성되는 튜플이다.

이미지에 그리기

이미지 위에 텍스트나 그림을 그리기 위해서는 ImageDraw 모듈 내의 Draw 객체를 사용한다. Draw(im)으로 이미지를 넘겨서 Draw 객체를 생성하면, 이미지에 대한 드로잉 컨텍스트 객체를 얻을 수 있고, 이 객체의 그리기 메소드들을 사용해서 이미지 위에 점, 선 및 여러 도형이나 다른 이미지를 그릴 수 있다. 아래 코드는 왼쪽과 같은 사각형을 그리는 코드이다.

from PIL import Image, ImageDraw

# 흰색 캔버스 생성
canvas = Image.new("RGB", (300, 300), color="#fff")
d = ImageDraw.Draw(canvas)
# (10, 10) - (290, 10) - (290, 290) - (10, 290) - (10, 10)으로
# 선을 그려 사각형을 그린다.
d.line([10, 10, 290, 10, 290, 290, 10, 290, 10, 10], width=2, fill="#fa3")

텍스트 그리기

이미지에 글자 넣기 예제

그림 위에 텍스트를 그리기 위해서는 텍스트를 렌더링하는데 필요한 폰트 요소가 필요하다. Draw.text() 메소드를 사용하며, font= 파라미터에 트루타입/오픈타입 폰트 파일로부터 생성한 폰트 객체를 넘겨준다.

from PIL import Image, ImageDraw, ImageFont
im = Image.open("sample.jpg")
ft = ImageFont.truetype("somefont.ttf", size=20)
d = ImageDraw.Draw(im)
d.text((10, 10), "Draw this text", font=ft, fill="#ff3")
im.show()

텍스트를 그릴 때 지정해주는 좌표는 텍스트가 그려지는 바운딩 박스의 왼쪽 위 모서리의 좌표이다. 만약 텍스트를 이미지의 정중앙에 그리고 싶다면, 텍스트에 의해 그려지는 바운딩 박스의 크기를 알아야 한다. 이 값은 Draw.textsize(text, font)를 사용해서 미리 알아내어야 한다. 다음 예는 바로 위 예제에서 텍스트를 화면 가운데 그리도록 변경한 것이다.

# 그려질 텍스트의 크기를 구해 이미지 중앙에 텍스트 그리기
w, h = d.textsize("Draw this text", font=ft)
x, y = (im.width - w) // 2, (im.height - h) // 2
d.text((x, y),"Draw this text", font=ft, fill="#ff3")
im.show()

이미지 합성하기

이미지를 다른 이미지 위에 합성할 때, 특정한 영역만 필요하다면 해당 영역만큼 잘라내면(crop) 된다. 그런데 필요한 영역이 사각형이 아닌 원형이나 불특정한 모양인 경우에는 비트맵 마스크를 사용해서 합성할 수 있다. image.paste()의 세 번째 인자는 mask= 인데, 여기에 비트맵 마스크를 적용해서 이미지를 합성하는 것이 가능하다. 혹은 Image.composite(im1, im2, mask=None) 함수도 있는데, 이 함수는 이미지 1 을 이미지 2위에 합성하는데, mask 값이 있다면 마스크를 적용한다. 다만 모든 이미지가 ‘RGBA’ 모드여야 한다는 문제가 있다.

가장 간단한 방법은 image.paste()를 사용하는 것이다. 아래 이미지를 만들어보자.

# 이미지 로드 (800 * 600)
im = Image.open('sample.jpg')

# 같은 크기의 마스크 만들기. 중앙에 원을 그린다.
mask = Image.new('L', im.size, color=0)
dm = ImageDraw.Draw(mask)
x, y = (mask.width - 400) // 2, (mask.height - 400) // 2
dm.ellipse((x, y, x+400, y+400), fill='#fff', width=4)

# 캔버스를 생성해서, 원본, 마스크, 합성한 이미지를 각각 붙인다.
canvas = Image.new('RGBA', (am.width * 3, am.height), color="#ffffaa33")
canvas.paste(am)
canvas.paste(mask, (am.width, 0))
canvas.paste(am, (am.width * 2, 0), mask)
canvas.show()

Image Composite를 사용해서 합성하는 경우에는 드로잉 컨텍스트를 굳이 만들 필요가 없다는 정도의 잇점이 있을 수 있다. 원본이 모두 jpg 이미지이므로 convert()를 사용해서 RGBA 이미지로 변환해야 정상적으로 작동한다.