Панорамная камера на 4-х Raspberry Pi

Если кубик один, то это кубик Рубика, другие кубики в единственном экземпляре интереса не представляют. Если кубиков несколько, то из них можно построить много разных конструкций. Имея четыре компьютера Raspberry Pi и четыре матрицы с объективами, можно построить панорамную камеру. Как говорил Лис, нет в мире совершенства, и у этой камеры в сравнении с построенными ранее будут свои плюсы и свои минусы. Чтобы не захлебнуться в потенциальном обилии возможностей и получить-таки работоспособный экземпляр, будем строго следовать принципу KISS (keep it simple, stupid). Берем четыре компьютера и пишем простейшую программу для осуществления синхронной съемки всеми камерами. Из принципа KISS следует, что в идеале программа для всех камер должна быть одинаковой. Например, 4 камеры с ИК приемниками запускаются с общего пульта дистанционного управления. Но, вынужден огорчить читателя, как и всякий нормальный герой, я пошел в обход, по одной простой причине - у меня не было четырех приемников ИК сигнала, а ехать их покупать было в лом. Хотя учитывая их копеечную стоимость, это было бы наверное самым правильным решением. Но мы идем другим путем. Один компьютер объявляется главным, а остальные 3 ведомыми. Устройство снабжается двумя кнопками, одна для выбора выдержки а вторая для старта процесса съемки. При нажатии на желтую кнопку последовательно перебираются выдержки от 1/20 с до 1/1700с + автомат. Выдержки индицируются в двоичном формате 3мя светодиодами. Соответственно все диоды потушены - автомат, первый горит - 1/20 с , второй - 1/50 с, первый и второй горят - 1/100 с . Третий - 1/200 с, Третий и первый - 1/400 с. Третий и второй - 1/800 с. Первый, второй и третий - 1/1700 с. При нажатии на красную кнопку стартует режим предварительного просмотра и загорается 4 светодиод. Он горит 5 секунд и в момент его выключения все 4 компьютера делают снимок. Пять секунд задержки выбраны для того, чтобы можно было отойти достаточно далеко от камеры и не маячить на переднем плане. Хотя процедура была организована максимально симметрично (главный компьютер подает команду и затем, как и остальные, считывает команду с отдельного контакта и запускает процесс), тем не менее главный компьютер опережал остальные. Пришлось вводить задержку через параметр bouncetime. На следующем снимке внешний вид камеры без корпуса - сейчас голый конструктивизм еще в моде, но в будущем я собираюсь сделать защитный корпус. Без корпуса вес камеры примерно 300 г.

raspberry pi

Для настройки загрузки программ и выгрузки снимков к камере можно подключить WiFi точку доступа. Это обеспечивает полный контроль каждого из четырех компьютеров Raspberry Pi с удаленного компьютера методами, описанными в предыдущей статье.

Используются 4 камеры, описанные в статье Picamera. Разброс между камерами даже одной партии довольно велик. Что не удивительно, поскольку смещение объектива относительно центра матрицы на 0,3 мм дает сдвиг в 10 градусов по углу зрения. Для максимального использования всей площади матрицы требуется довольно тонкая юстировка. Фокусировка и юстировка осуществляется по монитору, подключаемому к разъему HDMI последовательно каждой из камер. Можно контролировать картинку и на удаленном компьютере, но задержки делают эту процедуру менее комфортной.

Ниже приведена электрическая схема связи компьютеров через GPIO. Обращаю внимание на конденсоры в 100 нФ. Они жизненно необходимы, без них с ложными срабатываниями никакими программными средствами мне справиться не удалось.

Управление осуществляется двумя программами - одной для главного компьютера и тремя для ведомых. Последние различаются только именем записываемых файлов. При старте программы считывают из файла имя предыдущей сессии и добавляют к ней одну букву и снова записывают в файл. Дальше в рамках сессии снимки последовательно нумеруются. Баланс белого задан одинаковым принудительно через команду camera.awb_gains =1.1. Попытка задавать баланс белого через предустановки типа дневной свет, облачно и лампы накаливания успехом не увенчались, поскольку они задавали только отношение между синим и красным каналами, а зеленый выбирался автоматически. В идеале надо, конечно, задавать баланс индивидуально для каждой камеры, но я ограничился простейшим вариантом, когда усиление в красном и синем каналах задается одним числом. Программы стартуют при запуске компьютеров за счет файла buton4.desktop в папке /etc/xdg/autostart/.

[Desktop Entry]
Version=1.0
Encoding=UTF-8
Name=Buton4
Comment=
Exec=sudo python /home/pi/buton4s.pyc
Terminal=false
Type=Application

Текст самих программ, написанных на Python, приведен ниже. Программы содержат бесконечный цикл, из которого не предусмотрен выход, поэтому их следует рассматривать только как средство продемонстрировать работоспособность конструкции.

Для главного компьютера:

import RPi.GPIO as GPIO
import time
import picamera
fr=0
bp=0
flag1=0
GPIO.setmode(GPIO.BCM)
GPIO.setup(23, GPIO.IN, pull_up_down = GPIO.PUD_UP)
GPIO.setup(24, GPIO.IN, pull_up_down = GPIO.PUD_UP)
GPIO.setup(5, GPIO.OUT)
GPIO.setup(25, GPIO.OUT)
GPIO.setup(27, GPIO.OUT)
GPIO.setup(22, GPIO.OUT)
GPIO.setup(6, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)
GPIO.output(5, False)
GPIO.output(27, False)
GPIO.output(22, False)
GPIO.output(25, False)
f = open('/home/pi/fotopicam/foto.txt', 'r')
fs = f.read()
f.close()
ffs = fs +'f'
f = open('/home/pi/fotopicam/foto.txt', 'w')
f.write(ffs)
f.close()
with picamera.PiCamera() as camera:
     camera.resolution = (2592, 1944)
     camera.framerate = 15
     camera.awb_mode = 'off'
     camera.awb_gains =1.1
     def camFunction(channel):
        camera.capture('/home/pi/fotopicam/'+ffs+'pi189n%03d.jpg' % fr) 
        camera.stop_preview()

        time.sleep(1)
        GPIO.remove_event_detect(6)
     def printFunction(channel):
        global flag1
        global bp
        global fr            
        if flag1 == 0:
           flag1=1
           if channel ==24:  
              fr=fr+1
              GPIO.output(25, True)
              if bp == 0:
                 sh = 0
              elif bp == 1:
                 sh = 50000
              elif bp == 2:
                 sh = 20000
              elif bp == 3:
                 sh = 10000
              elif bp == 4:
                 sh = 5000
              elif bp == 5:
                 sh = 2500
              elif bp == 6:
                 sh = 1250
              elif bp == 7:
                 sh = 600
             

              camera.exif_tags['EXIF.FocalLength'] = '9.5'
              camera.exif_tags['EXIF.FocalLengthIn35mmFilm'] = '16.5'
              camera.iso = 100
              camera.shutter_speed = sh
              GPIO.add_event_detect(6, GPIO.FALLING, callback=camFunction, bouncetime=150)
              camera.start_preview() 
              time.sleep(5)
              GPIO.output(25, False)
           else: 
              if bp < 7:
                 bp=bp+1
              else:
                 bp=0
              if bp == 0:
                 GPIO.output(5, False)
                 GPIO.output(27, False)
                 GPIO.output(22, False)
   
              elif bp == 1:
                 GPIO.output(5, True)
                 GPIO.output(27, False)
                 GPIO.output(22, False)
   
              elif bp == 2:
                 GPIO.output(5, False)
                 GPIO.output(27, True)
                 GPIO.output(22, False)
   
              elif bp == 3:
                 GPIO.output(5, True)
                 GPIO.output(27, True)
                 GPIO.output(22, False)
   
              elif bp == 4:
                 GPIO.output(5, False)
                 GPIO.output(27, False)
                 GPIO.output(22, True)
   
              elif bp == 5:
                 GPIO.output(5, True)
                 GPIO.output(27, False)
                 GPIO.output(22, True)
   
              elif bp == 6:
                 GPIO.output(5, False)
                 GPIO.output(27, True)
                 GPIO.output(22, True)
   
              elif bp == 7:
                 GPIO.output(5, True)
                 GPIO.output(27, True)
                 GPIO.output(22, True)
               
           flag1=0 
           
     GPIO.add_event_detect(23, GPIO.RISING, callback=printFunction, bouncetime=300)
     GPIO.add_event_detect(24, GPIO.FALLING, callback=printFunction, bouncetime=300)
   
     while True:
        a=1
   
GPIO.cleanup()

Для ведомых:

import RPi.GPIO as GPIO
import time
import picamera
fr=0
bp=0
flag1=0
GPIO.setmode(GPIO.BCM)
GPIO.setup(23, GPIO.IN, pull_up_down = GPIO.PUD_UP)
GPIO.setup(5, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)
GPIO.setup(27, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)
GPIO.setup(22, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)
f = open('/home/pi/fotopicam/foto.txt', 'r')
fs = f.read()
f.close()
ffs = fs +'f'
f = open('/home/pi/fotopicam/foto.txt', 'w')
f.write(ffs)
f.close()
with picamera.PiCamera() as camera:
     camera.resolution = (2592, 1944)
     camera.framerate = 15
     camera.awb_mode = 'off'
     camera.awb_gains =1.1
     def camFunction(can):  
         camera.capture('/home/pi/fotopicam/'+ffs+'pi195n%03d.jpg' % fr)
         camera.stop_preview()
         time.sleep(1)
         GPIO.remove_event_detect(23)
         GPIO.add_event_detect(23, GPIO.RISING, callback=printFunction, bouncetime=100)
   
     def printFunction(can):
         global flag1
         global bp
         global fr
         fr=fr+1
         bp=GPIO.input(5) + 2*GPIO.input(27) + 4*GPIO.input(22)

         GPIO.remove_event_detect(23)
         GPIO.add_event_detect(23, GPIO.FALLING, callback=camFunction, bouncetime=100)
         if flag1 == 0:
              flag1=1
              if bp == 0:
                   sh = 0
              elif bp == 1:
                   sh = 50000
              elif bp == 2:
                   sh = 20000
              elif bp == 3:
                   sh = 10000
              elif bp == 4:
                   sh = 5000
              elif bp == 5:
                   sh = 2500
              elif bp == 6:
                   sh = 1250
              elif bp == 7:
                   sh = 600

              
              camera.exif_tags['EXIF.FocalLength'] = '9.5'
              camera.exif_tags['EXIF.FocalLengthIn35mmFilm'] = '16.5'
              camera.iso = 100
              camera.shutter_speed = sh 
              camera.start_preview()
              flag1=0
   
     GPIO.add_event_detect(23, GPIO.RISING, callback=printFunction, bouncetime=100)
     while True:
         a=1
   
GPIO.cleanup()

Поскольку переносить тексты на Python через буфер обмена занятие не для слабонервных, то здесь и здесь лежат эти программы для скачивания.

Сшивка осуществляется утилитой пакетной сборки панорам программы hugin.Файл со сценарием был получен сшивкой нескольких панорам вручную. После сшивки первой панорамы в файле сценария менялись номера файлов и он загружался в hugin. Контрольные точки, полученные от сшивки предыдущей панорамы, проверялись на адекватность, к ним добавлялись новые. Примерно к четвертой или пятой сшивке оказалось,что изменения больше вносить не надо и сразу получается приемлемая панорама. Тут следует иметь в виду, что это не касается точек ближнего плана. Многокамерные системы всегда обладают неустранимым параллаксом, и в случае сюжетноважного переднего плана для конкретной панорамы можно найти лучшее решение,чем среднее по больнице, но это лечение строго индивидуально и для следующего пациента скорее всего приведет к летальному результату.

Первые результаты испытаний при съемке со штатива приведены ниже. При щелчке мышью панорама демонстрируется с помощью технологии flash и программы SaladoPlayer.

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

31.10.2014
Установите проигрыватель Flash

Облако тегов:
3D печать
Arduino
Raspberry Pi
Аэрофотосъемка
Байдарки
Геомеханика
История
Камеры
Макросъемка
Объективы
Освещение
Панорамы
Принадлежности
Принтеры
Программы
Сканеры
Стереосъемка
Фильтры
Фокусировка
Фотокубики
...
rss