파이썬 Pillow로 이미지 일괄 처리하기 (리사이즈와 워터마크)
지금까지 우리가 다뤄온 건 대부분 글자였어요. 텍스트 파일, 엑셀 데이터, 뉴스 요약 같은 거요. 그런데 컴퓨터 안에 쌓인 파일들 보면, 사실 이미지가 어마어마하게 많잖아요.
상품 사진 100장을 블로그 규격에 맞게 크기 줄이고, 거기에 회사 로고 워터마크까지 하나씩 박아야 한다고 생각해보세요. 포토샵 켜서, 한 장 열고, 크기 조정하고, 로고 붙이고, 저장하고... 이걸 100번. 저도 예전에 이런 걸 손으로 하다가 밤 11시까지 회사에 남아본 적이 있거든요.
그때 이런 생각 해보신 적 있으세요? "이거 그냥 똑같은 작업 100번 반복하는 거잖아. 이걸 왜 내가 손으로 하고 있지?"
맞아요. 그래서 오늘은 그 반복 노동을 파이썬한테 통째로 넘겨버릴 거예요.
Pillow는 자동화되는 포토샵이에요
파이썬으로 이미지를 다루려면 Pillow(필로우)라는 도구를 씁니다.
포토샵은 사람이 마우스로 한 장씩 만지는 도구잖아요. Pillow는 좀 달라요. "가로 800으로 줄이고, 오른쪽 아래에 로고 박고, 저장해" 같은 지시를 코드로 한 번 적어두면, 그 지시를 사진 몇 백 장에 똑같이 자동으로 적용해주는 포토샵이에요. 손이 아니라 명령서로 일하는 거죠.
먼저 설치부터 할게요. 터미널(명령 프롬프트)에 이렇게 칩니다.
pip install Pillow
자, 여기서 첫 번째 함정이 나와요. 아주 많은 분들이 여기서 처음 좌절하시거든요.
설치는 분명 Pillow라고 했는데, 코드에서 불러올 땐 이렇게 씁니다.
from PIL import Image # 설치는 Pillow, 불러올 땐 PIL !
Pillow가 아니라 PIL이에요. 왜 이러냐면, Pillow가 옛날에 있던 PIL이라는 라이브러리의 후속작이라 그래요. 그 시절 코드들이 계속 돌아가게 하려고 부르는 이름은 옛날 이름 PIL을 그대로 물려받은 거예요.
혹시 워드 문서 만드는 편 기억나세요? 그때도 python-docx를 설치하고 코드에선 import docx라고 썼잖아요. 딱 그거랑 똑같은 함정이에요. "설치명이랑 부르는 이름이 다를 수 있다"는 걸 여기서 한 번 더 만나는 거죠.
import Pillow라고 썼다가 ModuleNotFoundError: No module named 'Pillow' 에러 보고 당황하지 마시고, from PIL import Image 이렇게 쓰시면 됩니다.
이미지 열고 크기 확인하기
일단 사진 한 장을 열어볼게요.
from PIL import Image
img = Image.open("photo.jpg") # 이미지 파일을 파이썬으로 열기
print(img.size) # (가로, 세로) 픽셀 크기
print(img.format) # 파일 형식
# 실행 결과:
# (4000, 3000)
# JPEG
Image.open()은 사진을 파이썬 손에 쥐여준 상태예요. 아직 화면에 뜨는 건 아니고, 메모리에 열어둔 거죠.
img.size는 (가로, 세로) 순서로 나와요. 이 사진은 가로 4000, 세로 3000픽셀이네요. 요즘 폰으로 찍으면 이 정도로 큽니다. 블로그에 올리기엔 너무 크죠. 그래서 줄여야 해요.
이미지 크기 줄이기 — 파이썬 이미지 일괄 리사이즈의 핵심
크기를 줄이는 방법은 두 가지가 있어요. 근데 이 둘의 차이를 모르면 사진이 이상하게 찌그러집니다.
resize — 딱 정해진 크기로
small = img.resize((800, 600)) # 가로 800, 세로 600으로 강제 변경
resize는 내가 지정한 크기로 딱 맞춰줘요. 문제는, 원본 비율이랑 안 맞는 크기를 넣으면 사진이 고무줄처럼 늘어나거나 눌린다는 거예요. 4:3 사진에 16:9 크기를 억지로 넣으면 사람 얼굴이 옆으로 쭉 늘어나는 참사가 벌어집니다.
thumbnail — 비율을 알아서 지켜줌 (이게 사실상 정답)
from PIL import Image
img = Image.open("photo.jpg")
img.thumbnail((800, 800)) # 비율 유지하며 긴 변이 800 넘지 않게 축소
img.save("photo_small.jpg") # 다른 이름으로 저장 → 원본 보존
# photo_small.jpg 가 새로 생김 (가로세로 비율 그대로 줄어든 채)
thumbnail은 "이 상자 안에 들어가도록 알아서 비율 맞춰서 줄여줘"예요. 가로세로 중에 긴 쪽을 기준으로 계산하니까 찌그러질 걱정이 없어요. 블로그 썸네일이나 상품 사진 줄일 때는 사실상 이걸 쓰시면 됩니다.
정확한 규격이 필요하면 resize, 그냥 적당히 줄이고 싶으면(대부분 이 경우예요) thumbnail. 이렇게 기억하시면 편해요.
그런데 여기 두 번째 함정이 숨어 있어요. thumbnail은 이렇게 쓰면 안 됩니다.
small = img.thumbnail((800, 800)) # ← 이렇게 하면 small은 None(빈 값)이 돼요!
thumbnail은 결과를 새로 만들어서 돌려주는 게 아니라, 원본 img 자체를 그 자리에서 바꿔버려요. 그래서 굳이 변수에 담을 필요 없이 img.thumbnail(...) 하고 그냥 img.save(...) 하면 됩니다. 위처럼 담으면 small이 텅 빈 값이 돼서, 나중에 small.save() 할 때 AttributeError: 'NoneType' object has no attribute 'save' 에러가 나요. resize는 새 이미지를 돌려주는데 thumbnail은 정반대라, 이거 처음에 진짜 많이 헷갈리시더라고요.
포토샵 없이 사진 포맷 변환 (PNG → JPG)
이번엔 파일 형식을 바꿔볼게요. Pillow는 정말 똑똑해서, 저장할 때 확장자만 보고 알아서 형식을 바꿔줍니다.
from PIL import Image
img = Image.open("logo.png") # PNG 파일 열기
img.save("logo.jpg") # 확장자를 .jpg로만 주면 자동으로 JPG 변환·저장
별도의 변환 명령 같은 거 필요 없어요. 그냥 저장할 이름의 확장자를 .jpg로 주면 끝이에요.
근데 여기서 세 번째 함정. 투명 배경이 있는 PNG를 이렇게 JPG로 저장하면 에러가 나요.
OSError: cannot write mode RGBA as JPEG
이게 왜 그러냐면요. PNG는 투명한 부분을 담을 수 있어요. 그래서 색 정보가 빨강·초록·파랑에 투명도까지 더해서 RGBA 네 가지예요. 그런데 JPG는 투명을 아예 못 담아요. 색만 담는 RGB 세 가지죠. 그러니 투명 정보를 어떻게 처리할지 몰라서 파이썬이 손을 드는 거예요.
해결은 딱 한 줄이에요. 투명 정보를 버리고 RGB로 바꿔주면 됩니다.
from PIL import Image
img = Image.open("logo.png") # 투명 배경 PNG (RGBA 모드)
img = img.convert("RGB") # 투명 정보를 버리고 RGB로 변환
img.save("logo.jpg", quality=90) # 이제 JPG 저장 OK (quality는 화질, 1~95)
"JPG로 바꿀 땐 convert('RGB')를 습관처럼" 이렇게 외워두시면 나중에 안 당황해요. 그리고 quality 값을 70~80 정도로 낮추면 화질은 거의 그대로인데 용량이 확 줄어서, 사진 여러 장 웹에 올릴 때 업로드가 훨씬 빨라집니다.
사진에 워터마크 넣기 (텍스트와 로고)
이제 내 사진임을 표시하는 워터마크를 박아볼게요. 워터마크는 사진에 찍는 반투명 서명 도장 같은 거예요.
글자 워터마크
from PIL import Image, ImageDraw, ImageFont
img = Image.open("photo.jpg")
draw = ImageDraw.Draw(img) # 이 사진을 도화지 삼아 그릴 붓 준비
# 한글 폰트 파일 경로를 직접 지정 (윈도우 맑은고딕)
font = ImageFont.truetype("C:/Windows/Fonts/malgun.ttf", 40)
draw.text((30, 30), "ⓒ 우리회사", font=font, fill="white") # (x,y) 위치에 글자
img.save("photo_wm.jpg")
ImageDraw.Draw(img)는 사진을 도화지로 삼아서 그 위에 글씨를 그릴 붓을 쥐는 거예요. draw.text((x, y), 글자, ...)로 원하는 위치에 글자를 얹습니다.
여기서 네 번째 함정, 이거 한글 쓰시는 분들은 거의 다 한 번씩 겪어요. 폰트를 따로 지정 안 하면 기본 폰트가 한글을 몰라서 워터마크가 □□□ 네모로 깨져서 나와요. 그래서 ImageFont.truetype("폰트경로", 크기)로 한글 폰트를 직접 알려줘야 합니다. 윈도우 맑은고딕은 C:/Windows/Fonts/malgun.ttf에 있어요.
이때 경로를 틀리게 쓰면 OSError: cannot open resource 에러가 나니까, 파일 경로가 진짜 맞는지 확인해주세요.
한 가지 더. 이미지에서 좌표 (x, y)는 왼쪽 위가 (0, 0)이에요. 오른쪽으로 갈수록 x가 커지고, 아래로 갈수록 y가 커집니다. 학교에서 배운 그래프는 위로 갈수록 y가 커졌잖아요? 그거랑 반대라서 위치 잡을 때 헷갈리실 수 있어요.
로고 이미지 워터마크
글자 대신 로고 그림을 오른쪽 아래 구석에 스티커처럼 붙일 수도 있어요.
from PIL import Image
img = Image.open("photo.jpg").convert("RGBA") # 밑바탕 사진
logo = Image.open("logo.png").convert("RGBA") # 투명 배경 로고
x = img.width - logo.width - 20 # 오른쪽 끝에서 20px 안쪽
y = img.height - logo.height - 20 # 아래 끝에서 20px 안쪽
img.paste(logo, (x, y), logo) # 세 번째 logo = '투명 마스크'
img.convert("RGB").save("photo_logo.jpg")
paste(붙일이미지, 위치, 마스크)는 사진 위에 로고를 스티커처럼 붙이는 거예요. 여기서 진짜 중요한 게 세 번째 인자예요. 여기에 로고 자신을 한 번 더 넣어주면, 로고의 투명한 부분은 안 붙고 그림 부분만 딱 얹혀요. 이걸 빼먹으면 로고 배경이 흰 네모로 붙어버려서 사진을 다 가려요. 이것도 처음에 정말 많이 놓치는 부분이에요.
참고로 img.rotate(90)으로 90도 돌리거나 img.crop((좌, 상, 우, 하))로 원하는 부분만 오려내는 것도 됩니다. 스캔 문서 방향 돌릴 때 같은 데 쓰면 편해요.
폴더 통째로 한 번에 처리하기
자, 여기가 오늘의 진짜 목적지예요. 지금까지 배운 부품들(열기 + 리사이즈 + 워터마크 + 저장)을 하나로 합쳐서, 폴더 안 사진 전부에 도장 찍듯 자동으로 처리할 거예요.
기억하시죠? 예전에 폴더 안 파일들을 glob으로 하나씩 순회하는 법을 배웠고, 그 결과를 다른 폴더에 저장하는 습관도 익혔잖아요. 그거의 이미지 버전이 바로 이거예요.
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
src = Path("원본사진") # 원본 폴더 (절대 건드리지 않음)
dst = Path("완성본") # 결과 저장 폴더 (반드시 따로!)
dst.mkdir(exist_ok=True) # 결과 폴더 없으면 새로 만들기
font = ImageFont.truetype("C:/Windows/Fonts/malgun.ttf", 40)
for file in src.glob("*.jpg"): # 원본 폴더 안 jpg 전부 순회
img = Image.open(file)
img.thumbnail((1000, 1000)) # 비율 유지하며 축소
draw = ImageDraw.Draw(img)
draw.text((20, 20), "ⓒ 우리회사", font=font, fill="white") # 워터마크
img.save(dst / file.name, quality=85) # 같은 이름으로 '완성본' 폴더에 저장
print(f"{file.name} 처리 완료")
print("전부 끝!")
# 실행 결과:
# 사진1.jpg 처리 완료
# 사진2.jpg 처리 완료
# ... (폴더 안 사진 수만큼 반복)
# 전부 끝!
이 반복문 하나면 사진이 100장이든 500장이든 몇 초 만에 끝나요. 포토샵으로 한 장씩 하던 그 밤샘 노동이 통째로 사라지는 거죠. 이 쾌감, 한번 맛보면 못 잊어요.
여기서 두 가지만 꼭 기억해주세요.
첫째, 원본 폴더에서 읽어서 결과는 완성본 폴더에 저장했어요. dst / file.name으로 원래 파일명 그대로 다른 폴더에 넣는 거죠. 이게 왜 중요하냐면, 리사이즈나 워터마크는 되돌릴 수가 없어요. 원본 위에 그대로 덮어써 버리면 축소되고 워터마크 박힌 버전으로 영영 바뀝니다. 원본 화질은 다시 못 살려요. 요리할 때 재료는 그대로 두고 완성한 요리만 새 접시에 담는 거랑 똑같아요.
둘째, src.glob("*.jpg")는 소문자 .jpg만 걸려요. 사진 파일이 대문자 .JPG면 하나도 안 잡히니까, 아무것도 처리가 안 되면 확장자 대소문자를 확인해보세요. png도 같이 처리하려면 순회를 한 번 더 돌리면 됩니다.
아, 그리고 아이폰으로 찍은 .heic 사진은 Pillow가 기본으로는 못 열어요. pillow-heif 같은 플러그인을 따로 깔아야 하는데, 이건 나중에 필요할 때 찾아보시고 지금은 jpg랑 png로 연습하세요.
처음에 자주 하는 실수들
정리 삼아, 다들 한 번씩 걸려 넘어지는 지점만 모아봤어요.
import Pillow했다가 에러 → 설치는Pillow, 부를 땐from PIL import Image.- 사진이 찌그러짐 →
resize에 비율 안 맞는 크기를 줬을 때. 웬만하면thumbnail로. thumbnail결과가 None →thumbnail은 원본을 제자리에서 바꿔요. 변수에 담지 말고 그냥img.thumbnail(...)후 저장.- 투명 PNG를 JPG 저장 시 에러 → 저장 전에
img.convert("RGB")한 줄. - 한글 워터마크가 네모로 깨짐 →
ImageFont.truetype로 한글 폰트 경로 지정. - 원본이 덮어써짐 → 결과는 무조건 다른 폴더에. 이게 제일 아픈 실수예요.
오늘 배운 걸로 해볼 것
백문이 불여일견이니까, 직접 해보시는 게 제일 빨라요.
- 본인 사진이 든 폴더를 하나 골라서,
완성본폴더에 비율 유지 축소본을 한꺼번에 만들어보세요. (위 마지막 예제를 거의 그대로 쓰면 돼요.) - 거기에 본인 이름이나 회사명으로 텍스트 워터마크까지 넣어서 폴더째 처리해보세요.
힌트를 드리면, 마지막 예제 코드에서 src와 dst 폴더 이름만 본인 폴더에 맞게 바꾸면 거의 끝이에요. 워터마크 글자도 "ⓒ 우리회사" 부분만 원하는 걸로 바꾸시면 되고요. 여유가 되면 투명 PNG 로고를 하나 구해서 오른쪽 아래에 박아보는 것까지 도전해보세요.
이렇게 폴더째 자동으로 사진을 처리하는 것도, 앞에서 만들었던 뉴스봇이나 요약봇도, 사실 공통점이 하나 있어요. 전부 내 컴퓨터가 켜져 있어야 돌아간다는 거예요. 근데 컴퓨터를 끄면요? 노트북 들고 나가면요?
다음 편에서는 이 봇들을 내 컴퓨터 대신 클라우드에 올려서, 컴퓨터를 꺼도 24시간 알아서 돌아가게 만드는 걸 해볼 거예요. 지금까지 만든 자동화들의 마지막 퍼즐 조각인 셈이죠.
제 글이 도움이 되셨다면 댓글 & 공감 부탁드려요 😀
'Application > Python' 카테고리의 다른 글
| [Python] 26. 파이썬 자동화 봇 24시간 돌리기 (무료 클라우드 호스팅) (0) | 2026.08.18 |
|---|---|
| [Python] 24. 파이썬 로깅과 에러 알림: 봇이 죽지 않게 지키기 (0) | 2026.08.13 |
| [Python] 23. 파이썬 스케줄링: schedule 모듈로 정기 실행하기 (0) | 2026.08.10 |
| [Python] 22. 파이썬으로 AI 문서 요약과 번역 봇 만들기 - 긴 문서도 통째로 (0) | 2026.08.07 |
| [Python] 21. 파이썬으로 ChatGPT API 호출하기 - AI에게 텍스트 처리 자동으로 시키기 (0) | 2026.08.04 |