Открыть список
Как стать автором
Обновить

Tilt-Shift фотографии своими руками

PythonОбработка изображений
Что такое Tilt-Shift объективы и что с их помощью можно сделать знают многие. Недавно на хабре была статья о Tilt-Shift генераторе, который создает этот эффект путем обработки обычной фотографии. Но программка эта написана только для Windows, да еще и платить за нее надо. Все плагины для графических редакторов почему-то тоже требовали денег и лицензий. Поэтому было принято решение с этим вопросом разобраться самостоятельно и сделать инструмент пусть немного проще профессионального софта, и не идеально симулирующий оптику объектива, но бесплатный, открытый и доступный всем желающим! Что из этого получилось, а что нет — можете посмотреть сами.

Начнем с того, что сделаем свою первую миниатюру.
Приступая к работе, в первую очередь необходимо выбрать фотографию. Нет смысла пытаться обрабатывать изображения, которые не подходят изначально. Преимущественно для этого используются пейзажные, городские снимки, снятые с возвышения — это в первую очередь и помагает создать иллюзию маленького мира. Так же хорошо, если в кадре есть небольшие объекты, такие как транспорт и люди.



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

Для работы я выбрал эту фотографию:



После добавления насыщенности и применения фильтров в фотошопе получается такой результат:



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

Перейдем к программированию.

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



В месте с черными пикселями наложения боке происходить не будет и сцена останется в фокусе. Маска была сделана методом “так примерно покатит”, это всего лишь демонстрация подхода.
Немного поковырявшись в python’е, получился такой небольшой и аккуратный скрипт:

  1. import Image
  2. import sys
  3. import numpy as np
  4. from scipy import ndimage
  5. import ImageEnhance
  6. # INIT
  7. blur_size = 6
  8. image_base = "/Users/Mango/Desktop/tiltshift_alpha.png"
  9. image_mask = "/Users/Mango/Desktop/mask_tiltshift.png"
  10. image_output = "/Users/Mango/Desktop/tiltshift_preview.png"
  11. # LOAD
  12. im_base = Image.open(image_base)
  13. im_mask = Image.open(image_mask)
  14. im_mask = im_mask.resize(im_base.size)
  15. # PROCESS
  16. enh = ImageEnhance.Color(im_base)
  17. im_base = enh.enhance(1.7)
  18. enh = ImageEnhance.Contrast(im_base)
  19. im_base = enh.enhance(1.2)
  20. im_blurred = np.array(im_base, dtype=float)
  21. im_blurred = ndimage.gaussian_filter(im_blurred, sigma=[blur_size,blur_size,0])
  22. im_blurred = Image.fromarray(np.uint8(im_blurred))
  23. im_mask = im_mask.convert("L")
  24. im_base = im_base.convert("RGBA")
  25. # MERGE AND SAVE
  26. im_base.paste(im_blurred,mask=im_mask)
  27. im_base.save(image_output)

Вначале получаем исходное изображение и маску, затем размер маски подгоняется под размер изображения и начинается обработка. С помощью модуля ImageEnhance регулируются такие показатели как цвет, яркость и контраст. После чего в im_blurred сохраняется копия изображения в виде массива. Для создания боке я использовал старый добрый фиьтр размытия Гаусса. Его результат отличается от того же Lens Blur в профессиональных редакторах, но для начала вполне неплохой результат.
На финальной стадии размытое изображение накладывается на наш оригинал, используя альфа-маску. Так же стоит учесть, что каждый слой должен иметь правильную палитру. Маска используется в монохромном режиме L, а исходному изображению при помощи convert(«RGBA») добавляется альфа-слой, который и позволяем с помощью маски накладывать второй слой.
Вот что получилось в итоге:



Теперь немного допилим полученный код и избавимся от некрасивого внешнего дополнения в виде вручную нарисованной маски. В этом нет ничего сложного, она представляет собой так называемый reflected gradient и задается двумя коллинеарными векторами.



В еще более упрощенном виде эту модель можно представить следующим образом:



Вся система задается несколькими параметрами: направление, длина вектора А, зоны фокусировки и вектора Б. Все расстояния для простоты задаем по оси y.
Построение градиента происходит следующим образом. Сначала по заданным расстояниям строится изображение шириной в один пиксель, оно будет выступать в роли паттерна. После чего разными манипуляциями можно придать ему любую форму и повернуть на нужный угол.
Немного поэкспериментировав у меня получилась такая функция:

  1. import Image
  2. import ImageDraw
  3. import ImageOps
  4. import math
  5. def draw_mask(angle,width,height,offset_init,offset_A,offset_focus,offset_B):
  6. offset = height*offset_init/100
  7. vectorA = offset+offset_A*height/100
  8. focus = vectorA+offset_focus*height/100
  9. vectorB = focus+offset_B*height/100
  10. mask = Image.new('L', (width,height))
  11. mask_1px = Image.new('L', (1,height))
  12. draw_1px = ImageDraw.Draw(mask_1px)
  13. for y in range (0,offset): # draw white zone
  14. draw_1px.point((0,y),255)
  15. for y in range (offset,vectorA): # draw vectorA
  16. draw_1px.point((0,y),(vectorA-y)*(255/(vectorA-offset)))
  17. for y in range (vectorA,focus): # draw white zone
  18. draw_1px.point((0,y),0)
  19. for y in range (focus,vectorB): # draw vectorB
  20. draw_1px.point((0,y),255-(vectorB-y)*(255/(vectorB-focus)))
  21. for y in range (vectorB,height): # draw white zone
  22. draw_1px.point((0,y),255)
  23. m_width,m_height = mask.size
  24. mask_1px = mask_1px.resize((int(m_width*3),m_height), Image.ANTIALIAS)
  25. mask_1px = ImageOps.invert(mask_1px)
  26. mask_top = mask_1px.rotate(angle,Image.NEAREST,1)
  27. mask_top = ImageOps.invert(mask_top)
  28. mask.convert("RGBA")
  29. n_width,n_height = mask_top.size
  30. mask.paste(mask_top,(-n_width/2,-(n_height/2-height/2)))
  31. mask.convert("L")
  32. return mask



Этот код учитывает угол поворота на случай, если захочется сделать инструмент более универсальным или прикрутить к веб-интерфейсу, чем я и собираюсь заняться в ближайшем будущем.
Если кому понадобятся исходники, финальная версия есть на github.
Теги:tilt-shiftpythonimage processingPILобработка изображений
Хабы: Python Обработка изображений
Всего голосов 107: ↑91 и ↓16 +75
Просмотры66.2K

Похожие публикации

Лучшие публикации за сутки