§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")
Попробуйте исполнить его на своём компьютере.
Выводы
В Python есть удобное средство работы с изображениями — библиотека Pillow.
В библиотеке есть много методов по преобразованию изображений — перевода в другую цветовую схему. изменения размера, сохранения в других форматах и многое другое. Эти функции содержатся в модуле
PIL.Image
.Мы можем создавать изображения сами, собирая их из комбинаций существующих. Используя модуль
PIL.ImageDraw
, можно рисовать геометрические фигуры и даже надписи.Метод
.putpixel()
позволяет нам закрашивать каждый пиксель изображения по отдельности.