Библиотека Python для работы с изображениями — пакет, который читает файл картинки в память, дает менять пиксели и размеры и сохраняет результат обратно в файл. Основных инструментов четыре: Pillow, OpenCV, scikit-image и imageio. Ниже — по какому признаку выбирать, рабочий пример на Pillow и ошибки, на которых спотыкаются чаще всего.
Содержание
- Три понятия, которые путают
- Какую библиотеку выбрать под задачу
- Минимальный пример на Pillow: превью для сайта
- OpenCV: порядок каналов BGR и молчаливый None
- scikit-image и imageio: следить за типом данных
- Если картинки приходят от пользователей
- Что стало с библиотеками из старых обзоров
- Если не получилось
- Выводы
- Где применяется / связь с практикой
- FAQ
Все примеры прогнаны 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, чтобы один битый файл не останавливал всю пачку.



