§6.5. Картинки

Рассмотрим ещё один интересный класс задач — работу с картинками. Вы уже знаете, что картинки — это бинарные файлы, поэтому просто так их не поредактируешь и не «прочитаешь». На самом деле, именно этого позволяет достичь библиотека Pillow — практически Photoshop для программистов. С ней мы и будем знакомиться. Перед началом, установите её: pip3 install pillow.

Базовые операции

Скачайте себе это изображение к себе, а затем запустите IPython из терминала в той же директории. Давайте попробуем немного поиграть с сохранённым файлом. Для начала, подключим библиотеку:

In [1]: from PIL import Image

Теперь загрузим картинку, используя функцию Image.open():

In [2]: img = Image.open("lena.png")

В img будет сохранён объект, через который загруженное изображение можно менять, сохранять в другой файл и прочее. Например, вы сразу можете посмотреть на него, используя метод .show():

In [3]: img.show()

Откроется окошко с картинкой. Таким образом вы всегда можете посмотреть, что у вас получилось.

Давайте попробуем выполнить несколько простых операций. Преобразуем картинку в оттенки серого:

In [4]: img.convert("L").show()

Результат отобразится на экране.

Мы воспользовались методом .convert(), который принимает в качестве аргумента режим работы, и возвращает новое, преобразованное изображение. "L" обозначает «изображение в оттенках серого». Другой интересный режим — "1", который преобразует картинку в чёрно-белую, но с применением специального алгоритма преобразования — так, что на глаз не сразу заметишь, что в ней используется всего два цвета.

Теперь поработаем с размерами картинки. Получить размеры можно, используя атрибут .size.

In [5]: a.size
Out [5]: (220, 220)

Размер хранится парой — это ещё один тип данных в Python, который позволяет держать вместе два любых значения. В данном случае первый элемент пары — это размер картинки в пикселях по горизонтали, а второй — по вертикали. Значения из пары можно получить, распаковав её в две отдельные переменные:

In [6]: x, y = a.size

In [7]: x
Out [7]: 220

In [8]: y
Out [8]: 220

Теперь давайте увеличим размер исходной картинки, скажем, в два раза. Для этого мы можем использовать метод .resize(), используя только что полученные значения исходного размера. Для этого нужно будет также создать новую пару:

In [9]: bigger_size = (2 * x, 2 * y)

In [10]: bigger_size
Out [10]: (440, 440)

In [11]: bigger_img = img.resize(bigger_size)

Как мы видим, некоторые функции и методы в Python также принимают пары как аргументы. Полученную картинку мы также можем посмотреть с помощью .show(). В Pillow можно найти функции для обработки изображений на все случаи жизни — обратитесь к документации объекта Image. Давайте изучим ещё один полезный метод, .save(), который используется для сохранения результата вашей работы в другой файл. Формат сохраняемого изображения будет определяться расширением, которое вы используете. Например, давайте сохраним картинку в формате JPEG:

In [12]: img.save("new_lena.jpg")

В директории, где вы открыли интерпретатор, появится новый файл, который вы можете открыть обычным системным просмотрщиком.

Собираем коллажи

Pillow позволяет работать не только с существующими файлами, но и начинать с чистого холста. Давайте создадим пустое изображение, но пусть в этот раз оно будет вдвое больше шире, чем прошлое:

In [13]: new_img = Image.new("RGB", (2 * img.width, img.height))

Здесь мы воспользовались функцией Image.new(), которая принимает два аргумента: режим изображения и его размер в виде пары. Режим здесь совпадает с аргументом, который мы раньше передавали в .convert()"RGB" означает «цветное изображение» (соответственно, можно было бы передать "L", и получить изображение в оттенках серого). Размеры исходного изображения мы получаем из атрибутов .width и .height; в них содержится тот же размер, что и в .size, но раздельно.

Что мы можем сделать с полученным изображением? Одна из часто используемых операций — вставка, когда мы вставляем исходное изображение в какое-либо место конечного, — опять же, прямо как в графическом редакторе. Чтобы чуть разнообразить результат, воспользуемся также методом .transpose(), который позволяет поворачивать изображение или отражать его по вертикали и горизонтали. Для начала давайте получим отражённую по горизонтали фотографию Лены (так зовут модель на фотографии):

In [14]: flipped_img = img.transpose(Image.FLIP_LEFT_RIGHT)

Как аргумент .transpose() принимает одну из констант, определённую в модуле Image. Это — заранее заданные значения, обозначающие ту или иную операцию. Кроме Image.FLIP_LEFT_RIGHT, которое обозначает нужную нам операцию, определено например Image.ROTATE_90, обозначающее «выполнить поворот на 90 градусов по часовой стрелке». Полный список поддерживаемых операций можно посмотреть в документации.

Теперь давайте склеим два изображения — исходное и отражённое — по горизонтали, вставив их в новое изображение. Здесь пригодится метод .paste(), который вызывается для изображения-получателя. Ему передаётся два аргумента: вставляемое изображение и координаты вставки.

Чтобы осуществить задуманное, первое изображение следует вставить по координатам (0, 0), а второе — в (img.width, 0):

In [15]: new_img.paste(img, (0, 0))

In [16]: new_img.paste(flipped_img, (img.width, 0))

In [17]: new_img.show()

В результате выйдет широкое изображение, в которое вставлены два предыдущих.

Рисуем по пикселям

Наконец, в Pillow есть модуль для рисования двухмерных изображений, который называется ImageDraw. Он позволяет рисовать точки, линии, круги, добавлять надписи — в общем, всё, что может пригодиться для простых задач. Давайте попробуем создать несколько рисунков. Для начала загрузим библиотеку и созданим контекст — объект, позволяющий проводить операции над изображением. Также создадим новое пустое изображение-холст (которое по умолчанию будет залито чёрным цветом):

In [18]: from PIL import ImageDraw

In [19]: result_img = Image.new("RGB", (200, 200))

In [20]: ctx = ImageDraw.Draw(result_img)

Все операции рисования теперь доступны через объект ctx. В любой момент можно посмотреть текущее изображение, выполнив result_img.show().

Начнём с классической задачи — нарисуем квадрат. Мы будем использовать для этого метод .line(), позволяющий рисовать ломаные линии. Кроме координат точек, на которых надо построить ломаную, этот метод принимает аргумент fill, определяющий цвет линии. Для изображений в формате RGB цвет задаётся интенсивностью трёх цветовых каналов — красного, зелёного и синего. Про это подробнее описывалось в параграфе о стеганографии в изображениях. В Pillow это значение описывается тройкой, например (255, 255, 0) обозначает жёлтый цвет. Давайте нарисуем жёлтый квадрат стороной 100 пикселей в центре изображения:

In [21]: ctx.line([(50, 50), (150, 50), (150, 150), (50, 150), (50, 50)], fill=(255, 255, 0))

Это действие можно также выполнить проще, используя метод .rectangle(). Он принимает всего две точки — две стороны прямоугольника, Этот метод также позволяет закрасить внутренности прямоугольника, используя параметр fill, и сделать обводку отдельным цветом через параметр outline:

In [22]: ctx.rectangle([(75, 75), (125, 125)], fill=(0, 255, 255), outline=(255, 255, 0))

Последний и самый универсальный метод, который мы рассмотрим — .putpixel(). Этот метод объекта изображения позволяет закрашивать заданные пиксели изображения. Давайте, используя его, создадим изображение полностью из случайных пикселей. Такой код удобнее будет писать в отдельном файле — создадим новый файл в текстовом редакторе. Итак, применим здесь наши знания из предыдущих глав, а также изучим новый модуль random. Этот модуль позволяет генерировать случайные числа, и мы будем использовать его для выбора цвета каждого пикселя. Для этой задачи подойдёт функция random.randrange(). Она принимает число stop, и возвращает такое случайное целое число n, что 0 <= n < stop. Итак, приведём здесь код:

from PIL import Image
import random

img = Image.new("RGB", (200, 200))
for x in range(img.width):
    for y in range(img.height):
        r = random.randrange(256)
        g = random.randrange(256)
        b = random.randrange(256)
        img.putpixel((x, y), (r, g, b))
img.save("random.png")

Попробуйте исполнить его на своём компьютере.

Выводы

  1. В Python есть удобное средство работы с изображениями — библиотека Pillow.

  2. В библиотеке есть много методов по преобразованию изображений — перевода в другую цветовую схему. изменения размера, сохранения в других форматах и многое другое. Эти функции содержатся в модуле PIL.Image.

  3. Мы можем создавать изображения сами, собирая их из комбинаций существующих. Используя модуль PIL.ImageDraw, можно рисовать геометрические фигуры и даже надписи.

  4. Метод .putpixel() позволяет нам закрашивать каждый пиксель изображения по отдельности.

Задача А. Отдохните! ⟶