Библиотека Pillow: работа с графикой и фотографиями в Python
Основы работы с изображениями в Pillow
Библиотека Pillow (форк PIL) предоставляет мощные средства для обработки растровых изображений. Основной класс Image открывает, изменяет и сохраняет файлы. Рассмотрим базовые операции и различные подходы к решению типовых задач.
Загрузка, изменение размера и сохранение
Базовый сценарий: открыть изображение, изменить его размер с сохранением пропорций и сохранить в другом формате.
from PIL import Image
img = Image.open('source.jpg')
# Изменение размера без сохранения пропорций
img_resized = img.resize((800, 600))
# Сохранение результата
img_resized.save('output.jpg', quality=85)Python pil image (работа с изображениями через pil)
Здесь resize задает точный размер, что может исказить пропорции. Для сохранения пропорций используется thumbnail или вычисление соотношения вручную.
Типичные ошибки:
- FileNotFoundError – файл не найден. Проверьте путь.
- OSError: cannot identify image file – неподдерживаемый формат. Используйте
Image.verify()перед открытием. - Потеря качества при сжатии JPEG – регулируется параметром
quality(1-95).
Как изменить размер с сохранением пропорций и минимальным размером?
Метод thumbnail создает копию с максимальными размерами, не превышающими заданные, и сохраняет пропорции.
from PIL import Image
img = Image.open('photo.jpg')
img.thumbnail((400, 400))
img.save('thumb.jpg')
Изображение будет не больше 400 пикселей по ширине или высоте. В отличие от resize, thumbnail изменяет сам объект (inplace).
Если нужно именно уменьшить до точного квадрата с обрезкой, применяют ImageOps.fit или комбинацию resize+crop.
Как применить фильтры (размытие, резкость, контуры)?
Фильтры находятся в модуле ImageFilter. Можно применить встроенные или создать собственные.
from PIL import Image, ImageFilter
img = Image.open('photo.jpg')
blurred = img.filter(ImageFilter.BLUR)
sharpened = img.filter(ImageFilter.SHARPEN)
contour = img.filter(ImageFilter.CONTOUR)
blurred.save('blur.jpg')
Список фильтров: BLUR, CONTOUR, DETAIL, EDGE_ENHANCE, EMBOSS, SHARPEN, SMOOTH и другие. Для радиального или гауссова размытия с регулируемым радиусом используйте GaussianBlur.
from PIL import Image, ImageFilter
img = Image.open('photo.jpg')
blur = img.filter(ImageFilter.GaussianBlur(radius=5))
blur.save('gauss_blur.jpg')
Некоторые фильтры (например, EMBOSS) могут давать неожиданные цвета при работе с палитровыми изображениями. Рекомендуется конвертировать в RGB: img = img.convert('RGB').
Как работать с отдельными цветовыми каналами (RGB, RGBA)?
Изображение можно разбить на каналы, модифицировать их и собрать обратно.
from PIL import Image
img = Image.open('photo.png').convert('RGBA')
r, g, b, a = img.split()
# Увеличим красный канал
r = r.point(lambda i: min(255, i + 50))
new_img = Image.merge('RGBA', (r, g, b, a))
new_img.save('redder.png')
split возвращает кортеж каналов (как изображения в градациях серого). merge собирает их обратно. Для работы с альфа-каналом (прозрачностью) удобно использовать RGBA.
Если изображение палитровое, перед split его нужно конвертировать в RGB/RGBA. Иначе каналы могут быть некорректными.
Как нарисовать геометрические фигуры и текст поверх изображения?
Модуль ImageDraw позволяет рисовать линии, прямоугольники, эллипсы и добавлять текст.
from PIL import Image, ImageDraw
img = Image.new('RGB', (400, 300), 'white')
draw = ImageDraw.Draw(img)
draw.rectangle([50, 50, 150, 150], fill='red', outline='black')
draw.ellipse([200, 50, 300, 150], fill='blue', outline='green')
draw.line([0, 200, 400, 200], fill='gray', width=3)
img.save('drawing.jpg')
Для текста требуется шрифт (TrueType):
from PIL import Image, ImageDraw, ImageFont
img = Image.new('RGB', (300, 100), 'white')
draw = ImageDraw.Draw(img)
font = ImageFont.truetype('arial.ttf', 20)
draw.text((10, 40), 'Hello, Pillow!', fill='black', font=font)
img.save('text.jpg')
Если шрифт не найден, возникнет ошибка IOError. Укажите полный путь к файлу .ttf или используйте встроенный шрифт по умолчанию (без параметра font).
Как конвертировать изображения в разные форматы с управлением качеством?
Сохранение в JPEG, PNG, WebP, BMP и другие. Параметры различаются в зависимости от формата.
from PIL import Image
img = Image.open('photo.png')
# JPEG: quality (1-95), optimize, progressive
img.save('photo.jpg', 'JPEG', quality=80, optimize=True)
# PNG: compress_level (0-9)
img.save('photo.png', 'PNG', compress_level=6)
# WebP: quality, lossless
img.save('photo.webp', 'WebP', quality=80, lossless=False)
Параметр optimize для JPEG включает оптимизацию таблиц Хаффмана, что уменьшает размер без потери качества.
При сохранении в JPEG изображение с альфа-каналом будет конвертировано в RGB (прозрачность теряется). Для сохранения прозрачности используйте PNG.
Как обработать все изображения в папке (пакетная обработка)?
Совместное использование os и Pillow позволяет обрабатывать множество файлов.
import os
from PIL import Image
input_dir = 'originals'
output_dir = 'processed'
os.makedirs(output_dir, exist_ok=True)
for filename in os.listdir(input_dir):
if filename.lower().endswith(('.jpg', '.png', '.jpeg')):
path = os.path.join(input_dir, filename)
img = Image.open(path)
img = img.convert('RGB').resize((800, 600))
out_path = os.path.join(output_dir, filename)
img.save(out_path)
print(f'Processed {filename}')
В примере все изображения конвертируются в RGB (для безопасности), изменяются до фиксированного размера и сохраняются в другую папку.
Следует учитывать оперативную память: очень большие изображения могут вызвать MemoryError. Используйте thumbnail или загрузку частями (например, модуль ImageFile с параметром ImageFile.LOAD_TRUNCATED_IMAGES).
Создание коллажа из нескольких изображений
Склеивание нескольких картинок в одно полотно с отступами.
from PIL import Image
# Загрузка изображений
imgs = [Image.open(f'img{i}.jpg') for i in range(1, 5)]
# Предположим все одинакового размера
width, height = imgs[0].size
canvas_width = width * 2 + 10 # два столбца с промежутком
canvas_height = height * 2 + 10
canvas = Image.new('RGB', (canvas_width, canvas_height), 'white')
# Размещение
positions = [(0, 0), (width+10, 0), (0, height+10), (width+10, height+10)]
for img, pos in zip(imgs, positions):
canvas.paste(img, pos)
canvas.save('collage.jpg')
Создаётся файл collage.jpg размером (2*width+10, 2*height+10) с четырьмя изображениями в сетке 2x2 и белыми отступами 10px.
Автоматический поворот по данным EXIF
Камеры часто сохраняют ориентацию в метаданных EXIF, и изображение отображается перевёрнутым. Pillow позволяет прочитать тег ориентации и повернуть изображение.
from PIL import Image, ExifTags
img = Image.open('photo.jpg')
try:
exif = img._getexif()
if exif:
orientation = exif.get(0x0112)
if orientation == 3:
img = img.rotate(180, expand=True)
elif orientation == 6:
img = img.rotate(270, expand=True)
elif orientation == 8:
img = img.rotate(90, expand=True)
except (AttributeError, KeyError, IndexError):
pass
img.save('corrected.jpg')
Изображение корректируется в соответствии с EXIF-тегом Orientation.
Работа с изображениями из памяти (BytesIO)
Иногда нужно обработать изображение, не сохраняя его на диск, например, при скачивании из сети или передаче через API.
from PIL import Image
from io import BytesIO
import requests
response = requests.get('https://example.com/image.jpg')
img = Image.open(BytesIO(response.content))
# Применяем фильтр и сохраняем обратно в BytesIO
img = img.convert('RGB').filter(ImageFilter.SHARPEN)
output = BytesIO()
img.save(output, format='JPEG', quality=90)
# output.getvalue() - готовый байтовый поток
print(f'Размер обработанного изображения: {len(output.getvalue())} байт')
Изображение скачивается, обрабатывается и остаётся в оперативной памяти, без создания временных файлов.
Создание анимированного GIF из серии кадров
Pillow поддерживает создание и редактирование многослойных изображений, в том числе анимированных GIF.
from PIL import Image
frames = []
for i in range(5):
# Создаём кадр с изменяющимся текстом
img = Image.new('RGB', (200, 200), (255, 255, 255))
from PIL import ImageDraw, ImageFont
draw = ImageDraw.Draw(img)
draw.text((50, 80), f'Frame {i+1}', fill='black')
frames.append(img)
# Сохраняем как анимированный GIF (первый кадр, затем append_images)
frames[0].save('animation.gif',
save_all=True,
append_images=frames[1:],
duration=500,
loop=0)
Создаётся GIF-файл с пятью кадрами, сменяющимися каждые 500 мс, бесконечный цикл (loop=0).