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

Щелевая съёмка: Ловля птичек с помощью питона

Время на прочтение 3 мин
Количество просмотров 22K


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

Наблюдая за интенсивным трафиком синиц у кормушки, я не сомневался, что заснять синичку в полете будет «как два байта переслать». Водрузив камеру на штативчик, я пододвинул ее к кромушке и, включив режим покадровой съемки с интервалом 0.5 секунд, спрятался в доме. Синички вернулись через пару минут. Выждав еще минут пять, я радостно выбежал на террасу, схватил камеру и устремился к ноутбуку. Каково же было мое разочарование, когда через полчаса я увидел обработанные результаты съемки — чередующиеся светлые и темные горизонтальные полосы с мелкими враплениями вертикальных «срезов» синичек, шириной в несколько пикселей. Похоже синички летали слишком быстро.

Не беда, переставим камеру в режим спортивного видео (60 кадров/секунду) и попробуем еще разок. Синички, очевидно, уже привыкли к штативу, поэтому воздушный трафик восстановился через несколько секунд. Еще через час я был счастливым обладателем серии фотографий, сделанных с интервалом 1/60 сек. (в разложении видео на фото сильно помог ffmpeg). Я был уверен в успехе. Но результат все равно оказался плачевным — полоски из синичек стали чуть шире, но связной картины не образовывали. После анализа (методом пристального взгляда) нескольких фотосерий стало понятно, что синички летают не просто быстро, а очень быстро. От момента взлета синички до ее растворения среди деревьев проходит менее 0,3 секунды!


Т. е. чтобы сделать щелевое фото полета синицы шириной хотя бы 120px нужно снимать с частотой 400 кадров/секунду! Это было фиаско.

В тяжелой задумчивости, оторвав ребенка от игры в MineCraft и нацепив на него лыжи, я отправился на лыжную прогулку. Камеру я захватил с собой. Ребенок, проникшись моей проблемой, предложил — «Пап, а может тебе огонь в лыжном домике заснять?».

Эврика! Пройдя несколько километров по лыжне, мы добрели до хижины. Разведя огонь и поставив жариться колбаски, я укрепил камеру напротив…


Проведя час-другой в хижине, мы сытые и довольные вернулись домой.

Результат оказался любопытным, но скучноватым:


Почесав в затылке, я решил добавить ключ -hz в код для того, чтобы можно было сделать щелевое фото не только вертикальной щелью, но и горизонтальной. В этот раз все оказалось куда интереснее:


Соответствующий щелевому фото видео фрагмент:


На щелевом фото виден вход, выход и снова вход моего сына через пространственно-временное окно. Обратите внимание на синусоидальное искажение створки двери! Интуитивно угаданное смещение щели (600px, ~55% высоты фото) оказалось самым интересным, прогон по остальным смещениям не дал столь эффектного результата.

Краткие выводы: Искать подходящие объекты для щелевого фото — занятие азартное и захватывающее. Щелевое фото не просто очередной эффект, а очень интересный и забавный взгляд на мир. Надеюсь, что несложный код, приведенный ниже, снизит для вас порог вхождения в него. Удачи!

P.S. <задумчиво>Где бы мне достать камеру с частотой 400 кадров/секунду?</задумчиво> У вас нет, случайно?

#Slit-scan photography
#CopyLeft 2013 OlloSnow

import os, Image, argparse

def add_slice(img_slt, img, offst, wdth, hz, num):
  if hz:
    img_tmp = img.crop((0, offst, img.size[0], offst+wdth))
    img_slt.paste(img_tmp, (0, num*wdth, img.size[0], (num+1)*wdth))
  else:
    img_tmp = img.crop((offst, 0, offst+wdth, img.size[1]))
    img_slt.paste(img_tmp, (num*wdth, 0, (num+1)*wdth, img.size[1]))
  return

def main():
  parser = argparse.ArgumentParser(usage=\
    '%(prog)s dir_images res_image [-hz] [-o slit_offset] [-w slit_width] [--help]')
  parser.add_argument('dir_img', type = str,\
    help = 'images directory')
  parser.add_argument('res_img', type = str,\
    help = 'result slit-scan image')
  parser.add_argument('-hz', '--horizontal', action='store_true',\
    help = 'horizontal orientation of the slit (vertical by default)')
  parser.add_argument('-o', '--offset', type = int, default = 0,\
    help = 'slit offset (pixels; 0 by default)')
  parser.add_argument('-w', '--width',type = int, default = 1,\
    help = 'slit width (pixels; 1 by default)')
  args = parser.parse_args()
    
  filenames = sorted(os.listdir(args.dir_img))
  img = Image.open(os.path.join(args.dir_img, filenames[0]))
  if args.horizontal:
    width = img.size[0]
    height = len(filenames)*args.width
  else:
    width = len(filenames)*args.width
    height = img.size[1]
  img_slt = Image.new('RGB',(width,height))
  
  max_i = len(filenames)
  for i, file in enumerate(filenames):
    img = Image.open(os.path.join(args.dir_img, file))
    add_slice(img_slt, img, args.offset, args.width, args.horizontal, i)
    print max_i - i, '\r',
    
  img_slt.save(args.res_img, 'JPEG')
  
if __name__ == "__main__":
  main()

Теги:
Хабы:
+35
Комментарии 21
Комментарии Комментарии 21

Публикации

Истории

Работа

Python разработчик
130 вакансий
Data Scientist
66 вакансий

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн