Библиотека 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).

Работа с изображениями через PIL - comments

En
Python pil image (python)