Библиотеки Python для работы с изображениями: что выбрать и как начать

Библиотеки Python для работы с изображениями: что выбрать и как начать Полезное

Библиотека Python для работы с изображениями — пакет, который читает файл картинки в память, дает менять пиксели и размеры и сохраняет результат обратно в файл. Основных инструментов четыре: Pillow, OpenCV, scikit-image и imageio. Ниже — по какому признаку выбирать, рабочий пример на Pillow и ошибки, на которых спотыкаются чаще всего.

Все примеры прогнаны 23.09.2026 на Python 3.14 с Pillow 12.3.0, OpenCV 5.0.0 (пакет opencv-python-headless), scikit-image 0.26.0, imageio 2.37.4 и NumPy 2.5.3. В других версиях вывод может немного отличаться.

Три понятия, которые путают

Библиотеки по-разному хранят картинку в памяти, отсюда большинство ошибок новичков.

Понятие Что это Где встречается
Объект Image Обертка с форматом, режимом (RGB, RGBA, L) и методами Pillow
Массив NumPy Таблица чисел формы (высота, ширина, каналы) OpenCV, scikit-image, imageio
Порядок каналов В какой последовательности лежат цвета пикселя RGB у большинства, BGR у OpenCV
Тип данных (dtype) uint8 — целые 0-255, float64 — обычно дроби 0-1 scikit-image часто возвращает float

Цепочка на одном пикселе: красная точка -> в Pillow (255, 0, 0) в режиме RGB -> в OpenCV массив [0, 0, 255] (BGR) -> в scikit-image после перевода в float — 1.0, 0.0, 0.0.

Какую библиотеку выбрать под задачу

Задача Что взять Когда иначе
Превью, обрезка, водяной знак, конвертация форматов для сайта Pillow Нужны тысячи файлов в секунду — смотреть на OpenCV
Компьютерное зрение: видео с камеры, поиск лиц и контуров, геометрия OpenCV Нужна понятная научная обработка — scikit-image
Научный анализ: сегментация, фильтры, измерение объектов scikit-image Большие 3D-объемы (КТ, МРТ) — специализированные SimpleITK или SciPy ndimage
Прочитать или записать файл в массив NumPy, GIF, видеокадры imageio Нужна правка картинки, а не только ввод-вывод — Pillow

Упрощение: границы размыты, таблица говорит, с чего начать, а не что запрещено.

Библиотека pip-пакет Импорт Представление Сильная сторона
Pillow pillow from PIL import Image Объект Image Форматы, EXIF, простые операции
OpenCV opencv-python или opencv-python-headless import cv2 Массив, BGR Скорость, видео, CV-алгоритмы
scikit-image scikit-image import skimage Массив, часто float Научные алгоритмы, документация с примерами
imageio imageio import imageio.v3 as iio Массив Единый ввод-вывод многих форматов

Сборка headless — OpenCV без оконных функций вроде cv2.imshow, для серверов и контейнеров.

Минимальный пример на Pillow: превью для сайта

Сценарий: из большой фотографии сделать JPEG не больше 800 пикселей по длинной стороне. Установка — python -m pip install pillow; демо-картинку photo.png скрипт создаст сам.

from pathlib import Path
from PIL import Image, ImageDraw, ImageOps

def make_preview(src, dst, max_side=800):
    with Image.open(src) as im:
        print("исходник:", im.format, im.size, im.mode)
        im = ImageOps.exif_transpose(im)       # учесть поворот из EXIF
        im.thumbnail((max_side, max_side))     # уменьшить, сохранив пропорции
        if im.mode != "RGB":                   # JPEG не хранит прозрачность:
            im = im.convert("RGBA")            # кладем картинку на белый фон
            bg = Image.new("RGB", im.size, "white")
            bg.paste(im, mask=im.getchannel("A"))
            im = bg
        im.save(dst, "JPEG", quality=85, optimize=True)
        print("превью:", im.size, im.mode, Path(dst).stat().st_size, "байт")

if not Path("photo.png").exists():             # демо-картинка, если своей нет
    demo = Image.new("RGBA", (3000, 2000), (30, 120, 200, 255))
    ImageDraw.Draw(demo).ellipse((1000, 500, 2000, 1500), fill=(250, 200, 0, 255))
    demo.save("photo.png")

make_preview("photo.png", "preview.jpg")

В консоли появится (размер файла зависит от содержимого картинки):

исходник: PNG (3000, 2000) RGBA
превью: (800, 533) RGB 7632 байт

Разбор:

  • ImageOps.exif_transpose поворачивает кадр по тегу ориентации: камера пишет пиксели в одном положении, а поворот — отдельной меткой, и без этого фото может лечь боком.
  • thumbnail меняет картинку на месте, сохраняет пропорции и только уменьшает. Точный размер или увеличение — resize((w, h)), пропорции тогда считаете сами.

Ошибка: сохранить RGBA в JPEG

from PIL import Image
im = Image.open("photo.png")
im.save("broken.jpg")

Фактический результат — исключение:

OSError: cannot write mode RGBA as JPEG

Исправление — перевести в RGB до сохранения. Простой im.convert("RGB") отбросит альфа-канал, и прозрачные места могут стать черными, поэтому в примере выше картинка кладется на белый фон.

OpenCV: порядок каналов BGR и молчаливый None

Две ловушки видно на одном пикселе. Сохраняем красную точку через Pillow и читаем через OpenCV:

import cv2
from PIL import Image

Image.new("RGB", (2, 1), (255, 0, 0)).save("red.png")   # красный пиксель
arr = cv2.imread("red.png")
print(arr[0, 0])                                          # BGR
print(Image.fromarray(arr).getpixel((0, 0)))              # ошибка: синий
rgb = cv2.cvtColor(arr, cv2.COLOR_BGR2RGB)
print(Image.fromarray(rgb).getpixel((0, 0)))              # верно: красный
print(cv2.imread("net-takogo-fajla.png"))

Вывод:

[  0   0 255]
(0, 0, 255)
(255, 0, 0)
None

Первая ловушка: OpenCV хранит каналы в порядке BGR. Если отдать такой массив в Pillow или Matplotlib без cv2.cvtColor, красное станет синим.

Вторая: cv2.imread на несуществующий или битый файл возвращает None вместо исключения (плюс предупреждение в stderr), и ошибка всплывет позже. Проверяйте сразу: if arr is None: raise FileNotFoundError(path).

scikit-image и imageio: следить за типом данных

imageio читает файл в массив NumPy, scikit-image обрабатывает. Пример на той же photo.png — выделение границ фильтром Собеля:

import imageio.v3 as iio
from skimage import color, filters, util

img = iio.imread("photo.png")
print(img.shape, img.dtype)
gray = color.rgb2gray(img[..., :3])          # отбросить альфа, в оттенки серого
print(gray.dtype, gray.min(), gray.max())
edges = filters.sobel(gray)
print(edges.dtype, round(float(edges.max()), 3))
iio.imwrite("edges.png", util.img_as_ubyte(edges / edges.max()))
print(iio.imread("edges.png").dtype)

Вывод:

(2000, 3000, 4) uint8
float64 0.4182078431372549 0.7694313725490196
float64 0.278
uint8

На входе uint8 (0-255), после rgb2gray — дроби от 0 до 1. В прогоне запись такого массива в PNG напрямую упала с OSError: cannot write mode F as PNG, а «починка» через .astype("uint8") превращает дроби в 0 и 1 — картинка почти черная. Правильно — util.img_as_ubyte, обратно — util.img_as_float.

Если картинки приходят от пользователей

Файл изображения — такие же недоверенные данные, как текст формы. Три меры, которые дает сама Pillow:

  • Ограничение пикселей. Маленький сжатый файл может распаковаться в гигантский массив и съесть память. Pillow предупреждает выше Image.MAX_IMAGE_PIXELS (по умолчанию 89 478 485) и бросает DecompressionBombError при двукратном превышении: при лимите 50 млн картинка 20000×10000 дала Image size (200000000 pixels) exceeds limit of 100000000 pixels.
  • Белый список форматов: Image.open(path, formats=["JPEG", "PNG", "WEBP"]). PNG при списке только из JPEG и WEBP отклонен с UnidentifiedImageError.
  • Метаданные. EXIF может содержать координаты съемки и модель камеры. В Pillow 12.3 пересохранение JPEG без аргумента exif= метаданные не переносит (в прогоне теги ориентации и камеры пропали). Если передаете exif=, фильтруйте теги сами.

Это базовая защита, а не полная: еще нужны лимит размера файла до чтения, хранение загрузок вне публичного каталога сайта, случайные имена и обновления Pillow — уязвимости в декодерах находят регулярно.

Что стало с библиотеками из старых обзоров

  • NumPy и SciPy — основа под OpenCV и scikit-image; scipy.ndimage полезен для n-мерных данных (фильтры, морфология).
  • SimpleITK — интерфейс к ITK для медицинских снимков (КТ, МРТ). Для фото избыточен.
  • PyCairo — рисование векторной 2D-графики, то есть генерация изображений, а не обработка фото.
  • Mahotas, pgmagick, SimpleCV — из обзоров прошлых лет. SimpleCV писали под Python 2, для новых проектов его не берут; у остальных смотрите дату последнего релиза на PyPI.

Если не получилось

Симптом Причина Что делать
ModuleNotFoundError: No module named 'PIL' Пакет не установлен в текущий интерпретатор python -m pip install pillow тем же python, которым запускаете скрипт
No module named 'cv2' Ставили не тот пакет python -m pip install opencv-python-headless
Цвета перепутаны, красное стало синим BGR из OpenCV cv2.cvtColor(arr, cv2.COLOR_BGR2RGB)
'NoneType' object has no attribute 'shape' cv2.imread не нашел файл Проверить путь и if arr is None
Фото с телефона лежит боком Не учтен EXIF-поворот ImageOps.exif_transpose
cannot write mode F as PNG или черная картинка float 0-1 вместо uint8 util.img_as_ubyte

Выводы

  • Для задач сайта (превью, обрезка, конвертация) начинайте с Pillow; для компьютерного зрения и видео — с OpenCV; для научного анализа — со scikit-image; imageio — для чтения и записи в массивы.
  • Pillow работает с объектом Image, остальные — с массивами NumPy; между ними переходят через Image.fromarray и numpy.asarray.
  • OpenCV хранит каналы как BGR и возвращает None вместо исключения на нечитаемый файл.
  • scikit-image часто отдает float 0-1: перед записью переводите в uint8 явно.
  • Для чужих файлов включайте лимит пикселей, белый список форматов и контроль EXIF.

Где применяется / связь с практикой

Обработка изображений нужна в веб-разработке, автоматизации и анализе данных, и везде опирается на базу языка: файлы и пути, исключения, окружения и пакеты.

Освойте тему на практике

Ее системно дает курс Python Developer. Basic. Если хочется сначала посмотреть на формат занятий, загляните в открытые уроки Otus.

FAQ

Чем Pillow отличается от PIL?
PIL — исходная библиотека, ее развитие остановилось, последний выпуск был в 2009 году. Pillow — ее поддерживаемый форк: импорт остался прежним (from PIL import Image), а ставится пакет pillow.

Как обработать сразу папку картинок?
Обойти файлы через Path("папка").glob("*.jpg") и вызвать для каждого функцию вроде make_preview, оборачивая вызов в try/except, чтобы один битый файл не останавливал всю пачку.

OTUS Журнал
Скидка 5% 12-19 октября на курсы (popup)
Бесплатные открытые уроки (поп-ап)