Pull to refresh

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

Reading time 5 min
Views 67K
Что такое 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.  
  7. # INIT
  8. blur_size = 6
  9. image_base = "/Users/Mango/Desktop/tiltshift_alpha.png"
  10. image_mask = "/Users/Mango/Desktop/mask_tiltshift.png"
  11. image_output = "/Users/Mango/Desktop/tiltshift_preview.png"
  12.  
  13. # LOAD
  14. im_base = Image.open(image_base)
  15. im_mask = Image.open(image_mask)
  16. im_mask = im_mask.resize(im_base.size)
  17.  
  18. # PROCESS
  19. enh = ImageEnhance.Color(im_base)
  20. im_base = enh.enhance(1.7)
  21. enh = ImageEnhance.Contrast(im_base)
  22. im_base = enh.enhance(1.2)
  23.  
  24. im_blurred = np.array(im_base, dtype=float)
  25. im_blurred = ndimage.gaussian_filter(im_blurred, sigma=[blur_size,blur_size,0])
  26. im_blurred = Image.fromarray(np.uint8(im_blurred))
  27. im_mask = im_mask.convert("L")
  28. im_base = im_base.convert("RGBA")
  29.  
  30. # MERGE AND SAVE
  31. im_base.paste(im_blurred,mask=im_mask)
  32. 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.  
  6. def draw_mask(angle,width,height,offset_init,offset_A,offset_focus,offset_B):
  7. offset = height*offset_init/100
  8. vectorA = offset+offset_A*height/100
  9. focus = vectorA+offset_focus*height/100
  10. vectorB = focus+offset_B*height/100
  11.  
  12. mask = Image.new('L'(width,height))
  13. mask_1px = Image.new('L'(1,height))
  14. draw_1px = ImageDraw.Draw(mask_1px)
  15. for y in range (0,offset)# draw white zone
  16. draw_1px.point((0,y),255)
  17. for y in range (offset,vectorA)# draw vectorA
  18. draw_1px.point((0,y),(vectorA-y)*(255/(vectorA-offset)))
  19. for y in range (vectorA,focus): # draw white zone
  20. draw_1px.point((0,y),0)
  21. for y in range (focus,vectorB): # draw vectorB
  22. draw_1px.point((0,y),255-(vectorB-y)*(255/(vectorB-focus)))
  23. for y in range (vectorB,height)# draw white zone
  24. draw_1px.point((0,y),255)
  25.  
  26. m_width,m_height = mask.size
  27. mask_1px = mask_1px.resize((int(m_width*3),m_height), Image.ANTIALIAS)
  28. mask_1px = ImageOps.invert(mask_1px)
  29. mask_top = mask_1px.rotate(angle,Image.NEAREST,1)
  30. mask_top = ImageOps.invert(mask_top)
  31.  
  32. mask.convert("RGBA")
  33. n_width,n_height = mask_top.size
  34. mask.paste(mask_top,(-n_width/2,-(n_height/2-height/2)))
  35. mask.convert("L")
  36. return mask



Этот код учитывает угол поворота на случай, если захочется сделать инструмент более универсальным или прикрутить к веб-интерфейсу, чем я и собираюсь заняться в ближайшем будущем.
Если кому понадобятся исходники, финальная версия есть на github.
Tags:
Hubs:
+75
Comments 60
Comments Comments 60

Articles