Автор телегам-канала CODE BLOG — Вадим Шванов написал для нашего блога статью о работе с изображениями c использованием Python и Pillow.
Сегодня мы рассмотрим основы работы с изображениями в Python, а также создадим пару фильтров для фотографий.
Использовать для этого мы будем Pillow, NumPy (т. к. мы будем преобразовывать изображение в массив) и matplotlib для настраиваемого вывода результата на экран.
Установка пакетов
NumPy и matplotlib можно легко установить с помощью пакетного менеджера pip, просто запустив следующие команды в терминале:
pip install numpy
pip install matplotlib
С Pillow всё тяжелее. Python версии => 3.6 поддерживает установку этого пакета через pip. Однако при возникновении ошибки при других версиях Pillow и «питона» проверяем соответствие версий здесь.
Также я предлагаю изучить ответы на вопрос по поводу установки pillow, а также как установить .whl файл с помощью pip.
В качестве среды разработки был выбран всем известный Jupyter Notebook.

Начало работы
Для начала импортируем все необходимые нам модули, а также загрузим изображение в объект Python:

Примечание. Никто не заставляет вас импортировать модули с помощью конструкции import … as …, давая им такие же имена, как и я. Однако почти во всех «литературных» источниках NumPy импортируется с псевдонимом np, а matplotlib.pyplot — plt.
По поводу кода не беспокойтесь, в конце статьи будет указана ссылка на .ipynb документ, который вы сможете легко загрузить и изменить под своё усмотрение.
Загрузка изображения происходит с помощью функции open() под-модуля Image:

Jupyter Notebook выводит изображения просто по их имени. Вот то самое изображение, путь к которому я передал в функцию:

Однако далее мы создадим свою функцию, чтобы у нас была возможность добавить надпись над фотографией (заголовок) или поменять её размер.
Вывод изображений с matplotlib

Вот функция, которую мы будем использовать далее. Она принимает два параметра: img — массив, который мы получили из исходной фотографии и title — заголовок, который по умолчанию имеет значение None. Мы изменяем размеры фигуры на 6×6, чтобы сделать вывод изображения больше, и выводим его на экран. Также мы проверяем, был ли указан заголовок, и в таком случае устанавливаем его с помощью функции title(). Выключим пометки на осях, указав ‘off’ в функцию axis().

Преобразование в «черный-белый»
Сейчас мы будем преобразовывать изображение в черно-белые тона. Пока что мы не будем преобразовывать загруженное изображение в массив, ибо здесь мы будем использовать метод convert() классаизображения PIL.
Функция выглядит очень просто:

Немного про само изображение. Мы представили его в RGB формате, то есть каждый пиксель имеет три значения — красный, зелёный и синий. Оттенки серого получаются, когда все эти три значения равны. Изначальная идея состояла в том, чтобы просто взять среднее арифметическое трёх значений. Однако позже я вспомнил об одной известной формуле яркости пикселя:

Мне подсказали хорошую статью насчет «правильности» этой формулы, и я с радостью поделюсь ею с вами: «Об относительной яркости, или насколько живучим бывает легаси».
PIL использует свои коэффициенты, потому просто давайте просмотрим результат:


Да, мы немного потеряли в качестве из-за того, что растянули изображение с помощью matplotlib. Однако инструмент действительно работает (для сравнения можете реализовать мою первоначальную идею про среднее арифметическое).
Что мы хотим? Массив!
Далее мы будем использовать инструменты NumPy, потому желательно конвертировать изображение в np.array:

Да, конструктор массива принимает изображение в качестве аргумента и создаёт вот такой массив. Узнаем форму массива с помощью поля shape.

224 x 225 — размеры нашего изображения. 3 появляется из-за того, что каждый пиксель представлен тремя значениями.
Негатив
Значения цветов могут меняться от 0 до 255. Негатив — эффект «переворачивания» цветов. Достигается он довольно просто: от 255 мы просто отнимаем значение цвета. Т. к. все операции над массивом numpy выполняются векторизированно (поэлементно), то мы просто отнимем от 255 текущий массив:

С помощью нашей функции show() оценим результат:


Swap «left-right»
Следующей задачей у нас является «разворот» изображения слева направо (т. е. правая и левая стороны изображения меняются местами). В NumPy есть замечательная функция fliplr и её «близкий друг» flipud.
Про их применение можно найти информацию здесь.
Нас интересует только функция fliplr, в которую мы и передадим исходный массив:

Данный фрагмент кода не изменяет массив, а возвращает новый, что нам и нужно. Оценим результата работы:


Добавим синего
Последний эффект я случайно обнаружил у себя в телефоне, после чего мне показалось хорошей идеей реализовать его. Идея проста — нам нужно сделать изображение как можно синее. Для этого я предлагаю заменить каждое третье «значение пикселя» на максимальное — 255.

Обратите внимание. Здесь нам нужно было поменять сам массив, однако я не хотел терять исходное изображение. Потому мы копируем последовательность с помощью np.copy().
Также имеет место быть другая конструкция:
arr[:, :, 2] = 255
И, по традиции, выводим результат:


Заключение
Под конец хочется сказать, что это далеко не конец возможностей Python в плане обработки изображений, и нас ждёт еще много чего интересного. Мы создали несколько различных эффектов, которые используются в камерах современных мобильных телефонов, а также разобрались с парой других проблем.
Весь код из статьи вы сможете найти в этом документе.
Больше статей автора на сайте shwanoff.ru
Также рекомендуем подписаться на группу ВКонтакте, Телеграм и YouTube-канал. Там еще больше полезного и интересного для разработчиков.