Интернет теряет свой коллективный разум из-за маленького Йоды, и не зря. Я имею в виду, посмотри на него.

Недавно я помогал некоторым из моих студентов подготовиться к выпускному экзамену на Python и подумал, что было бы интересно изучить некоторые основы обработки изображений в популярной библиотеке Python Numpy, используя ребенка Йоды в качестве исходного материала.

Чтение изображения с помощью Numpy

Во-первых, как мы вообще читаем изображение с помощью Numpy? Оказывается, все довольно просто! Предположим, в вашем рабочем каталоге есть файл с именем baby_yoda.jpg, вот код, который вам нужен.

Мы должны увидеть нашего малыша Йоду:

Но как Python представляет изображения?

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

Изображение представляет собой большую прямоугольную сетку пикселей. И каждый пиксель полностью определяется тремя значениями: красным, зеленым и синим (RGB). Каждое из этих значений представляет собой целое число от 0 до 255. Чем выше значение, тем больше этого цвета присутствует в изображении.

С одной стороны, у нас есть пиксель со значениями RGB (0,0,0), что означает, что в этом пикселе нет абсолютно никакого цвета, он же черный. С другой стороны, у нас есть пиксель с максимальными значениями RGB (255,255,255), что означает, что в изображении есть как можно больше цвета, он же белый. Каждый цвет, который вы можете себе представить, находится где-то посередине.

Если ваше изображение имеет размер 100 на 200 пикселей, Python закодирует все изображение в трехмерный массив Numpy с размерами 100 на 200 на 3. Цифра 3 соответствует трем цветовым каналам, о которых мы упоминали ранее. Проявив творческий подход с этим трехмерным массивом Numpy, мы можем выполнять всевозможные классные преобразования нашего изображения!

1. Оттенки серого

Для начала мы возьмем наше цветное изображение малыша Йоды и преобразуем его в оттенки серого. Обратите внимание, что все цвета, которые мы называем «серыми», представляют собой просто комбинации RGB, в которых значения красного, зеленого и синего в точности одинаковы. Так, например, RGB (60,60,60) - это один оттенок серого, а (200,200,200) - другой.

Хм ... как бы нам взять пиксель со значениями RGB, такими как (60, 100, 200), и превратить его в серый пиксель? Что ж, одна из основных идей - просто взять среднее из трех чисел, а затем выбрать оттенок серого, где все три числа являются этим средним. Итак, для пикселя (60, 100, 200) среднее значение равно 120, поэтому мы сопоставляем его с серым пикселем (120, 120, 120).

Посмотрим, как добиться этого результата с помощью кода!

Чтобы объяснить некоторые тонкости здесь:

  • В трехмерном массиве есть несколько способов интерпретации «усреднения». Например, мы могли бы намереваться взять среднее значение по каждому отдельному числу в массиве, в результате чего получится одно число. Мы могли бы иметь в виду среднее значение каждой строки, среднее значение каждого столбца или среднее значение каждого пикселя. Таким образом, нам нужно использовать ключевое слово «ось», чтобы указать, какое среднее значение нам нужно. В данном случае это среднее значение по пикселям.
  • Как только мы получим средние значения, нам нужно будет «сложить» их друг на друга, чтобы создать изображение в оттенках серого. Другой способ подумать об этом состоит в том, что мы хотели бы установить значения красного, зеленого и синего каждого пикселя равными среднему значению этого пикселя.
  • Обратите внимание: вполне вероятно, что среднее значение RGB для любого пикселя может не быть целым числом, но Numpy ожидает целые числа в изображении, поэтому мы просто преобразуем изображение в оттенках серого обратно в целые числа.

И ... если все пойдет хорошо, мы должны получить все еще очаровательного малыша Йоды с оттенками серого:

2. Переворот по горизонтали / вертикали

Мы также можем перевернуть нашего малыша Йоду горизонтально или вертикально. Сделав шаг назад, давайте посмотрим, как бы мы могли полностью изменить обычный список чисел.

Если у меня есть list_of_nums, которые равны [1, 2, 3, 4, 5], выполняется следующее:

список_числов [:: - 1]

приведет к обратному списку: [5, 4, 3, 2, 1].

Чтобы объяснить [:: - 1], это сокращение для [0: len (list_of_nums), - 1], которое говорит:

«Пройти весь мой список, делая шаги размера -1», что еще проще означает «пройти мой список назад»

Мы можем использовать это удобное обозначение для зеркального отражения всего изображения. Вот код для вертикального переворота:

Поскольку строки являются первой осью нашего изображения (которое представляет собой трехмерный массив), мы можем просто использовать одно и то же преобразование [:: - 1] для всего изображения, и мы обратим порядок всех строк. Изменение порядка строк на обратное аналогично переворачиванию изображения по вертикали, в результате чего получается перевернутый младенец Йода:

Перевернуть изображение по горизонтали так же просто, используя следующий код:

Ого! Что за запутанный синтаксис: [:, :: - 1]. Что ж, это снова просто сокращение для [0: numRows, 0: numCols: -1], которое на словах гласит: «перебирать все мои строки и перебирать все мои столбцы в обратном порядке». Это дает эффект сохранения строк такими, какие они есть, но переворачивание столбцов, что приводит к горизонтальному переворачиванию:

3. Размытие изображения

В заключение давайте взглянем на более сложную трансформацию - размытие.

Размытие - довольно интуитивная концепция, когда дело касается изображений. Это то, что происходит, когда ваша камера не в фокусе или если что-то движется слишком быстро, чтобы камера могла правильно запечатлеть. Оказывается, мы действительно можем преобразовать изображение нашего малыша Йоды, чтобы создать этот эффект размытия.

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

Выполняя это преобразование для каждого пикселя в изображении младенца Йоды (кроме тех, которые находятся на границе, потому что мы не можем создать эти блоки), мы заставляем каждый пиксель включать информацию о пикселях, которые находятся рядом. Таким образом, пиксели больше не являются независимыми, а скорее «размываются» друг в друге.

Давайте посмотрим на код, необходимый для этого.

Выглядит устрашающе! Надеюсь, комментарии немного помогут, но общая идея такова:

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

Результат:

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

Вот и все! Надеюсь, вы узнали немного о манипуляциях с изображениями в Numpy и снова влюбились в малыша Йоду.

~ Удачи!