Прочитав несколько месяцев назад статью про щелевую съемку, я твердо решил написать небольшую утилитку, позволяющую создавать щелевые фотографии. Перетряхнув закрома, я обнаружил следующие ресурсы: отложенный в долгий ящик учебный курс по 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))
i = 0
max_i = len(filenames)
for file in filenames:
img = Image.open(os.path.join(args.dir_img, file))
add_slice(img_slt, img, args.offset, args.width, args.horizontal, i)
i += 1
print max_i - i, 'r',
img_slt.save(args.res_img, 'JPEG')
if __name__ == "__main__":
main()
Автор: OlloSnow