PIL на Python от простого к сложному

Чтобы дойти до сложных алгоритмов обработки, стоит проанализировать стандартные схемы, с чего я и предлагаю начать.

Для примеров обработки будет использоваться изображение с различным наборов цветов:

image

Для старта нам потребуется два модуля библиотеки:

from PIL import Image, ImageDraw 

Настроим инструменты для комфортной дальнейшей работы:

image = Image.open('test.jpg')  # Открываем изображение
draw = ImageDraw.Draw(image)  # Создаем инструмент для рисования
width = image.size[0]  # Определяем ширину
height = image.size[1]  # Определяем высоту
pix = image.load()  # Выгружаем значения пикселей

Приступим


Обрабатывать изображения будем в формате RGB. Также PIL поддерживает работу с форматами 1, L, P, RGB, RGBA, CMYK, YCbCr, LAB, HSV, I, F.

Значения пикселя в изображении задаются в формате: (x,y),(red, green, blue), где x,y — координаты, а числовые значения RGB находятся в диапазоне от 0 до 255. То есть работаем с 8-битным изображением.

Оттенок серого


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


for x in range(width):
    for y in range(height):
       r = pix[x, y][0] #узнаём значение красного цвета пикселя
       g = pix[x, y][1] #зелёного
       b = pix[x, y][2] #синего
       sr = (r + g + b) // 3 #среднее значение
       draw.point((x, y), (sr, sr, sr)) #рисуем пиксель

image.save("result.jpg", "JPEG") #не забываем сохранить изображение

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


Инверсия


Инверсия получается путём вычета из 255 текущего цвета:


for x in range(width):
   for y in range(height):
      r = pix[x, y][0]
      g = pix[x, y][1]
      b = pix[x, y][2]
      draw.point((x, y), (255 - r, 255 - g, 255 - b))

image

Инверсия оттенка серого


Совмещая два предыдущих алгоритма можно написать следующий код:


for x in range(width):
    for y in range(height):
        r = pix[x, y][0]
        g = pix[x, y][1]
        b = pix[x, y][2]
        sr = (r + g + b) // 3
        draw.point((x, y), (255 - sr, 255 - sr, 255 - sr))

image

Выборочная инверсия оттенка серого


Для этого алгоритма нужно определить пороговое значение, которое я возьму за 100:

for x in range(width):
    for y in range(height):
        r = pix[x, y][0]
        g = pix[x, y][1]
        b = pix[x, y][2]
        if (r+g+b)>100: #если сумма значений больше 100 , то используем инверисю
            sr = (r + g + b) // 3
            draw.point((x, y), (255-sr, 255-sr, 255-sr))
        else: #иначе обычный оттенок серого
            sr = (r + g + b) // 3
            draw.point((x, y), (sr, sr, sr))

image

Заключение


В следующих статьях я хотел бы рассказать о том, как более локально подходить к фильтрации изображения, путём разделения его на области, а также показать интересные возможности DFS в алгоритмах обработки изображения
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 25

    +2
    PIL на Python

    Всё же речь не о PIL, а о Pillow. Оригинальная библиотека PIL никогда не была внутри модуля PIL, это самодеятельность Pillow. Оригинальный PIL не разрабатывается с 2011-го и не существует для Python 3.


    PIL работает с изображениями в формате RGB.

    Pillow поддерживает режимы 1, L, P, RGB, RGBA, CMYK, YCbCr, LAB, HSV, I, F.


    нам нужно получить среднее арифметическое значение

    Это не единственный и при этом не самый лучший алгоритм обесцвечивания


    r = pix[x, y][0]
    g = pix[x, y][1]
    b = pix[x, y][2]

    r, g, b = pix[x, y] — даже такая мелочь ускоряет код на 20%

      0
      Спасибо. Статью немного дописал. Код писал с акцентом на понимание, а не на оптимизацию.
      +1

      Правильная формула для получения серого цвета:
      Y = 0.299 R + 0.587 G + 0.114 B

        +2
        А правильная функция:
        grayscale_image = image.convert("L")
        • UFO just landed and posted this here
          0
          Да, в интернете много формул с акцентом на то, что цвета человеческий глаз воспринимает иначе и соответственно алгоритмы совершенно другие. Спасибо за замечание, статью дополнил, но для общего понимания, я считаю, приведённого алгоритма достаточно.
            0
            Правильная формула для получения серого цвета

            Это не совсем так. Почитайте, например, статью «Об относительной яркости, или насколько живучим бывает легаси».

              0
              хорошее замечание
            +3

            Вот бы вы еще написали что за PIL, о каких "более сложных алгоритмах обработки (изображений?)" идет речь и зачем вот это все надо.

              0

              Небольшой код ревью: у вас очень много одинакового кода в примерах, не было бы проще вынести отдельную функцию вроде handle(x, y, [ r, g, b]) и в каждом параграфе определить конкретную реализацию?


              def handlePixel(( x, y ), [ r, g, b ]):
                pass
              
              for x in range(width):
                for y in range(height):
                  draw.point((x, y), handlePixel(( x, y ), pix[x, y]))
              
              # позже в статье
              def handlePixel(( x, y ), [ r, g, b ]):
                sr = (r + g + b) // 3
                return ( 255 - sr, 255 - sr, 255 - sr )

              Ну и я бы использовал кортежы везде (вместо массива [r,g,b]), но эт такое

                +1
                зачем у вас ( x, y ) в
                def handlePixel(( x, y ), [ r, g, b ])
                ?

                у меня отлично так работает, проверено лично в PyCharm:

                def handlePixel(r, g, b):
                    sr = (r + g + b) //3
                    return (255 - sr, 255 - sr, 255 - sr )
                
                for x in range(width):
                  for y in range(height):
                      r,g,b=pix[x, y]
                      draw.point((x, y), handlePixel(r,g,b))
                  –1

                  во-первых, странно что вам понадобилось запускать код в PyCharm чтобы это увидеть


                  во-вторых, да, можно и без этого обойтись — координаты не используются ни в одном куске кода. но как знать — может в какой-то момент и понадобится нечто большее? ;) например, для тех же фильтров пригодится окно изображения — размеры окна, координаты куда писать и данные пикселей окна. но принцип YAGNI говорит что не надо этот параметр функции добавлять =)


                  def handlePixel([ r, g, b ])

                  вполне себе заработает. передавать цвет пикселя массивом — банально проще:


                  # не нужно деструктурировать в три переменных здесь
                  draw.point((x, y), handlePixel(pix[x, y])
                    +1
                    более того, вы в определении функции передаете параметры в таком виде
                    def handlePixel([ r, g, b ])

                    def handlePixel(( x, y ), [ r, g, b ])

                    (smiling)
                      0

                      не понял что вас смущает — pix[x, y] — это массив из трех элементов, я их деструктуририрую в определении функции, вроде ничего необычного. а координаты я передал в виде кортежа — просто так (на самом деле я думал что можно будет рисовать прямо в этой функции).

                        0

                        Запись со списком в заголовке определения функции — это синтаксическая ошибка.


                        Например, код


                        def f([a, b, c]):
                            return a + b + c

                        просто упадёт с ошибкой парсинга.


                        Кстати, pix[x, y] — это не массив, а кортеж, если быть точным. Массивы могут быть многомерными и обычно ассоциируются с массивами Numpy или стандартным array.array. Здесь же функция просто возвращает три значения. И [r, g, b] — это тоже не массив, а список.

                          0

                          таки да, ~переоценил~ перепутал я питон с жаваскриптом, деструктурирование в параметрах функций отсутствует.

                +6

                Снова люди, вчера изучившие питон, пытаются научить ему других. А потом все думают, что питон "медленный" и "непонятный"

                  +1
                  Девочка няшная.
                  Насчёт PIL – не разбираюсь, однако же, не торопитесь закидывать помидорами. Имел опыт с OpenCV под плюсы и Пайтон и могу сообщить, что поток выполнения может проводить большУю часть времени в алгоритмах обработки, а не в отрисовке даже в плюсах.
                  Вывод 1 – большинство либ для рисования предоставляют подобный функционал не на одном языке.
                  Вывод 2 – язык Пайтон, к сожалению, не лучший для обработки изображений с большим количеством точек, как и для других нужд, где желательна (ну например) возможность обработки видео без потери кадров в секунду.
                    0

                    Согласен, но для склейки вызовов библиотечных функций он очень даже ничего. Понятно, что если обрабатывать изображение попиксельно в питоновских циклах, ничего путного из этого не выйдет.

                      0
                      Девочка няшная.

                      Простим уж новичку незнание стандартов индустрии :)
                      +2
                      Фи, питон. Лучше бы что-нибудь поинтереснее выбрал:load 'media/imagekit'
                      load 'primitives'
                      mean =: +/ % #
                      round =: floor f. @ +&0.5
                      gray =: round f. @ #~&3"0 @ mean f. rows
                      negative =: 255&-
                      negative_gray =: negative f. @ gray f.

                      image =: read_image 'input.jpg'
                      (negative_gray image) write_image 'output.jpg'
                      view_image 'output.jpg'
                        +1

                        Откройте для себя scikit-image и/или OpenCV и не занимайтесь ерундой. Писать на Python циклы с попиксельным обходом изображений — это бесперспективно и бессмысленно.


                        Pillow неплохая библиотека, но не для image processing. Хотя в ней несомненно есть некоторые функции обработки изображений. Кстати, рекомендую почитать.

                          0
                          Старая добрая статья с хабра
                            0

                            В ней изображения обрабатываются циклами в Python. Я бы не стал эту статью кому-то советовать.

                              0
                              Да, только статьи уж больно похожи

                          Only users with full accounts can post comments. Log in, please.