Для примеров обработки будет использоваться изображение с различным наборов цветов:
Для старта нам потребуется два модуля библиотеки:
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") #не забываем сохранить изображение
Инверсия
Инверсия получается путём вычета из 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))
Инверсия оттенка серого
Совмещая два предыдущих алгоритма можно написать следующий код:
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))
Выборочная инверсия оттенка серого
Для этого алгоритма нужно определить пороговое значение, которое я возьму за 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))
Заключение
В следующих статьях я хотел бы рассказать о том, как более локально подходить к фильтрации изображения, путём разделения его на области, а также показать интересные возможности DFS в алгоритмах обработки изображения
Комментарии (21)
samodum
09.05.2019 03:59Правильная формула для получения серого цвета:
Y = 0.299 R + 0.587 G + 0.114 BYunow Автор
10.05.2019 00:11Да, в интернете много формул с акцентом на то, что цвета человеческий глаз воспринимает иначе и соответственно алгоритмы совершенно другие. Спасибо за замечание, статью дополнил, но для общего понимания, я считаю, приведённого алгоритма достаточно.
masai
10.05.2019 01:59Правильная формула для получения серого цвета
Это не совсем так. Почитайте, например, статью «Об относительной яркости, или насколько живучим бывает легаси».
shybovycha
09.05.2019 08:01+3Вот бы вы еще написали что за PIL, о каких "более сложных алгоритмах обработки (изображений?)" идет речь и зачем вот это все надо.
shybovycha
09.05.2019 08:10Небольшой код ревью: у вас очень много одинакового кода в примерах, не было бы проще вынести отдельную функцию вроде
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]
), но эт такоеqpy
10.05.2019 00:37+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))
shybovycha
10.05.2019 01:26-1во-первых, странно что вам понадобилось запускать код в PyCharm чтобы это увидеть
во-вторых, да, можно и без этого обойтись — координаты не используются ни в одном куске кода. но как знать — может в какой-то момент и понадобится нечто большее? ;) например, для тех же фильтров пригодится окно изображения — размеры окна, координаты куда писать и данные пикселей окна. но принцип YAGNI говорит что не надо этот параметр функции добавлять =)
def handlePixel([ r, g, b ])
вполне себе заработает. передавать цвет пикселя массивом — банально проще:
# не нужно деструктурировать в три переменных здесь draw.point((x, y), handlePixel(pix[x, y])
qpy
10.05.2019 10:55+1более того, вы в определении функции передаете параметры в таком виде
def handlePixel([ r, g, b ])
def handlePixel(( x, y ), [ r, g, b ])
(smiling)shybovycha
10.05.2019 13:30не понял что вас смущает —
pix[x, y]
— это массив из трех элементов, я их деструктуририрую в определении функции, вроде ничего необычного. а координаты я передал в виде кортежа — просто так (на самом деле я думал что можно будет рисовать прямо в этой функции).masai
10.05.2019 22:16Запись со списком в заголовке определения функции — это синтаксическая ошибка.
Например, код
def f([a, b, c]): return a + b + c
просто упадёт с ошибкой парсинга.
Кстати,
pix[x, y]
— это не массив, а кортеж, если быть точным. Массивы могут быть многомерными и обычно ассоциируются с массивами Numpy или стандартнымarray.array
. Здесь же функция просто возвращает три значения. И[r, g, b]
— это тоже не массив, а список.shybovycha
11.05.2019 08:08таки да, ~переоценил~ перепутал я питон с жаваскриптом, деструктурирование в параметрах функций отсутствует.
Ktulhy
09.05.2019 08:14+6Снова люди, вчера изучившие питон, пытаются научить ему других. А потом все думают, что питон "медленный" и "непонятный"
NotThatEasy
10.05.2019 00:38+1Девочка няшная.
Насчёт PIL – не разбираюсь, однако же, не торопитесь закидывать помидорами. Имел опыт с OpenCV под плюсы и Пайтон и могу сообщить, что поток выполнения может проводить большУю часть времени в алгоритмах обработки, а не в отрисовке даже в плюсах.
Вывод 1 – большинство либ для рисования предоставляют подобный функционал не на одном языке.
Вывод 2 – язык Пайтон, к сожалению, не лучший для обработки изображений с большим количеством точек, как и для других нужд, где желательна (ну например) возможность обработки видео без потери кадров в секунду.masai
10.05.2019 02:05Согласен, но для склейки вызовов библиотечных функций он очень даже ничего. Понятно, что если обрабатывать изображение попиксельно в питоновских циклах, ничего путного из этого не выйдет.
assembled
10.05.2019 00:38+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'
iroln
11.05.2019 02:03Откройте для себя scikit-image и/или OpenCV и не занимайтесь ерундой. Писать на Python циклы с попиксельным обходом изображений — это бесперспективно и бессмысленно.
Pillow неплохая библиотека, но не для image processing. Хотя в ней несомненно есть некоторые функции обработки изображений. Кстати, рекомендую почитать.
andreymal
Всё же речь не о PIL, а о Pillow. Оригинальная библиотека PIL никогда не была внутри модуля PIL, это самодеятельность Pillow. Оригинальный PIL не разрабатывается с 2011-го и не существует для Python 3.
Pillow поддерживает режимы 1, L, P, RGB, RGBA, CMYK, YCbCr, LAB, HSV, I, F.
Это не единственный и при этом не самый лучший алгоритм обесцвечивания
r, g, b = pix[x, y]
— даже такая мелочь ускоряет код на 20%Yunow Автор
Спасибо. Статью немного дописал. Код писал с акцентом на понимание, а не на оптимизацию.