Обновлено 26 февраля 2018
Кравченко Виктор

Raspberry Pi 3: Камера (#1) — первое включение, видео, фото, timelapse, отправка фото по e-mail

Радиотехника и электроника Цифровые устройства Умный дом Raspberry
01 На заметку:
У данной статьи есть видеоверсия!

Подписывайтесь на канал , чтобы быть в курсе обновлений!
02
03

Введение

Существует всего 2 вида камер, предлагаемых официальным сообществом Raspberry — обычная камера и камера ночного видения (инфракрасная камера для съемки в условиях слабого освещения):

05

Это второе поколение модулей с камерами для Raspberry Pi. От первого поколения они отличаются новой 8-мегапиксельной матрицей Sony IMX219 с разрешением 3280×2464 пикселей (подобные матрицы устанавливаются в некоторые мобильные телефоны), вместо 5-мегапиксельной OmniVision OV5647 с разрешением 2592×1944 пикселей. Замена матрицы не принесла увеличения производительности. Режимы записи видео остались прежними: 1080p30, 720p60 и 640×480p60/90. Тем не менее, был сделал серьезный скачок в качестве изображения.

06
07

В данной статье будет использован китайский клон первой версии оригинальной камеры, так как он в 4 раза дешевле оригинала:

09

Все описанное в данной статье также справедливо и для оригинальных камер.

10 На заметку:
Перед всеми манипуляциями, как обычно, необходимо обновить систему:
1
2
sudo apt-get update sudo apt-get dist-upgrade
11

Подключение и настройка

Камера общается с Малинкой по интерфейсу CSI, и подключается к специальному разъему на плате:

12
13 На заметку:
Интерфейс CSI, в отличие от USB-камер, позволяет не загружать процессор Малинки и использовать камеру максимально эффективно.
14 На заметку:
Камера потребляет ток 200-250мА — это необходимо учитывать, если используется слабый источник питания.
15

Подключив камеру к Малинке, в настройках Raspberry OS (Preferences → Raspberry Pi Configuration) необходимо включить возможность использования камеры — вкладка Interfaces пункт Camera кликнуть на Enabled.

16

Для более удобной работы с камерой может пригодиться пластиковый кронштейн:
Постоянная ссылка

Кронштейн монтажный пластиковый для модуля камеры Raspberry Pi 3

Кронштейн монтажный пластиковый для модуля камеры Raspberry Pi 3
17
18

После перезагрузки, камера готова к использованию.

19 На заметку:
В случае возникновения проблем с эксплуатацией камеры, в первую очередь, необходимо методом простой прозвонки, удостовериться в наличии контакта между Малинкой и камерой.



Также нужно убедиться, что разъем, соединяющий камеру с платой, тоже надежно зафиксирован.
20

Базовый функционал

Для базовых манипуляций с камерой существует 3 утилиты командной строки, которые предустановлены в системе:

  • raspivid — утилита для захвата видео,
  • raspistill — утилита для захвата фотографий,
  • raspiyuv — утилита, аналогичная raspistill, только вместо jpg-файлов, в результате генерирует raw-файлы (несжатые, необработанные).

21

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

22

Все утилиты должны запускаться с параметрами.

23 На заметку:
Подробно о параметрах стандартных утилит по работе с камерой можно почитать на официальном ресурсе Raspberry Py Camera Module.

Полный список параметров для каждой утилиты можно получить, если выполнить утилиту без параметров
1
raspistill

либо выполнить утилиту с параметром --help:
1
raspistill --help

24

Ряд параметров является общим для всех этих утилит и разбит на 2 группы:

  • настройки окна просмотра, например:
    • --preview, -p — параметры окна просмотра <'x,y,w,h'>
    • --opacity, -op — установка прозрачности окна просмотра 0...255 (0 — абсолютно прозрачно, 255 — непрозрачно)
  • настройки самой камеры, например:
    • --contrast, -co — контрастность (-100...100)
    • --brightness, -br — яркость (0...100)
    • --exposure, -ex — режим экспозиции
    • --annotate, -a — добавление к изображению текстовых данных, например дата, время, настройки или произвольный текст

25

Остальные параметры специфичны для каждой утилиты.

26 На заметку:
Необязательный параметр -t устанавливает время работы утилиты в миллисекундах. В случае работы утилиты raspistill (raspiyuv), фотография будет сделана по завершении этого времени. Для утилиты raspivid этот параметр задает длительность съемки видео.

Если параметр -t опущен, по умолчанию, используется время работы в 5 секунд.
27 На заметку:
Если, горящий во время работы камеры, светодиод мешает (например, отражается в объектив во время ночной съемки через стекло), его можно отключить. Для этого в файл config.txt:
1
sudo nano /boot/config.txt

Нужно добавить строку:
1
disable_camera_led=1


После — сохранить файл config.txt и перезагрузить Малинку
28

Примеры использования стандартных утилит

Далее приведены примеры использования утилит:

29
1
raspistill -t 2000 -o image.jpg -w 640 -h 480 -v
30
Сделать фото с отсрочкой в 2 секунды (-t 2000), разрешением 640×480 (-w 640 -h 480) с выводом информации во время работы утилиты (-v) и сохранить в файл image.jpg (-o image.jpg).
31
1
raspivid -t 10000 -o video.h264
32
Записать видео длиной 10 секунд (-t 10000) и сохранить в файл video.h264 (-o video.h264).
33 На заметку:
Для отражения изображения по горизонтали/вертикали нужно использовать параметры -hf и -vf.
34

Для воспроизведения видео можно воспользоваться предустановленным плеером OMXPlayer. Если по каким-то причинам его нет, установить его можно командой:

35
1
sudo apt-get install omxplayer
36

OMXPlayer — плеер командной строки, без визуального интерфейса. Чтобы запустить ролик, нужно вызвать плеер с указанием, в качестве параметра, имени воспроизводимого файла:

37
1
omxplayer video.h264
38

Поскольку файлы с разрешением .h264, записанные утилитой raspivid, по сути являются сжатым видеопотоком без медиаконтейнера, то при попытке воспроизвести их, при помощи OMXPlayer, они будут воспроизводиться с увеличенной скоростью, вне зависимости от заданной при записи частоты кадров. Для того, чтобы сделать записанные файлы нормально воспроизводимыми, их нужно поместить в любой медиаконтейнер — mkv, avi, mp4 и т.д. Делается это при помощи утилиты avconv — параметром -r задается частота кадров нового видео:

Более подробно речь об утилите avconv пойдет далее в статье.
39
1
avconv -i video.h264 -r 30 -vcodec copy video.mkv
40 На заметку:
После того, как видео помещено в видеоконтейнер, воспроизвести его можно будет только при помощи медиаплеера KODI (либо на любом настольном компьютере, в т.ч. и на Windows), так как он умеет использовать аппаратное декодирование видео. Ни OMXPlayer, ни VLCPlayer не смогут корректно воспроизвести полученный файл на Raspberry Pi 3.
41

Timelapse-видео

Процесс создания Timelapse-видео состоит из 2 этапов. На первом этапе создается набор фотографий, сделанных с заданным интервалом при помощи утилиты raspistill с параметрами -o, -t и -tl:

  • -oимя файла, должно содержать маску %04d, для того, чтобы утилита автоматически нумеровала файлы в соответствии с номером кадра. 04 в маске означает количество цифр в номере — 0000, 0001, 0002 и т.д. Маска %08d подставит восьмизначный номер — 00000000, 00000001, 00000002, ... Например, -o image%03d.jpg будет генерировать имена файлов image000.jpg, image001.jpg, image002.jpg и т.д.
  • -t — длительность работы камеры, в миллисекундах.
  • -tl — интервал между кадрами, в миллисекундах. Если параметр -tl устанавливается равным нулю, то камера делает фото подряд с паузой примерно 30 миллисекунд, это время необходимо для расчета экспозиции.

42

Пример:

43
1
raspistill -o myphoto%04d.jpg -t 30000 -tl 2000
44
Сделать серию фото в течение 30 секунд (-t 30000), с паузой между фотографиями в 2 секунды (-tl 2000) и сохранить фотографии с именами myphoto0000.jpg, myphoto0001.jpg, myphoto0002.jpg и т.д. (-o myphoto%04d.jpg).
45 На заметку:
В случае появления сообщения о пропуске кадров Skipping frame X to restart at frame Y:

Сообщение говорит о том, что камера не успевает сделать снимок и он будет пропущен, необходимо:
  • увеличить паузу между кадрами (параметр -tl);
  • добавить в команду параметр -md 1 — ручная установка режима съемки в 1 (подробно о режимах здесь);
  • добавить в команду параметр -bm — включение режима burst capture mode (режим захвата пакетов).
46

После этого все полученные снимки нужно собрать в одно видео при помощи утилиты avconv.

47

Если утилита не установлена в системе, установить её можно командой:

48
1
sudo apt-get install libav-tools
49

Утилита avconv требовательна к нумерации файлов.

50

В случае, если необходимо собрать видео из файлов, нумерация которых начинается не с 0000, необходимо привести имена файлов к виду, подходящему для обработки утилитой avconv

51

Для управления процессом сборки могут понадобиться некоторые параметры:

  • -y — наличие флага разрешает, не спрашивая, перезаписывать выходной файл.
  • -r n — в переменной n указывается количество кадров в секунду выходного видео, например, -r 10 — 10 кадров в секунду.
  • -i name — шаблон имен файлов входной последовательности, например, -i myphoto%4d.jpg, где фрагментом %4d задается формат нумерации (4 цифры).
  • -vcodec — задает кодек, при помощи которого нужно кодировать видео, например, для использования кодека H.264, нужно передать значение параметра libx264: -vcodec libx264 (для получения списка доступных кодеков нужно утилите avconv передать параметр -codecs).
  • -q:v n — качество выходного видео, n — значение из диапазона от 1 (самое лучшее) до 31 (самое худшее).
  • -start_number start — если нумерация последовательности начинается не с 0, то start принимает значение первого номера в последовательности, например, если последовательность содержит файлы с именами image_0213.jpg, image_0214.jpg, image_0215.jpg и т.д., то start=213: -start_number 213

Подробно об утилите можно почитать на официальной странице.
52
1
avconv -start_number 213 -i image_%5d.jpg -r 30 -vcodec libx264 -q:v 3 output.mp4
53 На заметку:
На самом деле возможности утилиты avconv намного шире. Например, с её помощью можно осуществлять видеозахват области со слежением за мышью с экрана вашей Малинки:
1
avconv -f x11grab -follow_mouse centered -show_region 1 -r 25 -t 10 -s cif -i :0.0 video.mkv

Ну а для полноценного захвата рабочего стола Малинка слабовата:
1
avconv -f x11grab -r 25 -s 1920x1080 -t 10 -i :0.0 -vcodec libx264 video.mkv
54

Использование камеры из Python — библиотека PiCamera

Помимо стандартных утилит, камеру можно использовать программными методами. Такой подход необходим, например, при построении систем безопасности, когда нужно активизировать запись, при наступлении какого-нибудь события или по расписанию.

Все скетчи на питоне, демонстрируемые далее, можно скачать как отдельно, по ссылке справа от каждого скетча, так и одним архивом — raspberry_camera_sketches.rar (16,7 KB).

Документация по работе с библиотекой PiCamera доступна в онлайн-режиме — picamera.readthedocs.io. Для доступа к документации в режиме оффлайн её можно установить командой:
1
sudo apt-get install python-picamera-docs


Для оффлайн-установки библиотеки PiCamera доступен репозиторий.
55

Для работы с камерой в языке Python, понадобится библиотека PiCamera, которая предустановлена в системе. Если по какой-то причине её нет, то установить библиотеку можно командой:

56
1
sudo apt-get install python3-picamera
57 Важно:
Нельзя сохранять скетчи с именем picamera.py — это сделает невозможным использование библиотеки PiCamera в Python.
58

Когда библиотека установлена, в скетче её необходимо импортировать:

59 Python
1
import picamera
60

Также, для работы с временными паузами, нужно импортировать функцию sleep() из библиотеки time:

61 Python
1
from time import sleep
62

Далее создается экземпляр класса PiCamera, с которым удобно работать:

63 Python
1
camera=picamera.PiCamera()
64 На заметку:
Справку по объекту можно получить при помощи функции help(), выполнив последовательно в оболочке Shell команды:
1
2
>>> import picamera >>> help(picamera)

Либо вызвать функцию print() из кода программы:
1
2
3
import picamera camera=picamera.PiCamera() print(help(camera))
65

Для корректного завершения работы с камерой и высвобождения занятых ресурсов, необходимо использовать метод close(). При вызове этого метода, будет также остановлена вся активность камеры — закрыто окно предпросмотра и завершена активная запись:

66 Python
1
camera.close()
67

В соответствии с условностями, вызывать метод close() необходимо в разделе finally блока try:

68 Python
1
2
3
4
5
6
7
8
9
10
11
12
13
import RPi.GPIO as GPIO # Импортируем библиотеку по работе с GPIO import picamera # Импортируем библиотеку PiCamera from time import sleep import datetime as dt try: camera = picamera.PiCamera() # === Основной код === finally:
camera.close() # Корректно завершаем любую активность камеры
GPIO.cleanup() # Возвращаем пины в исходное состояние print("End of program") # Информируем о завершении работы программы
69

Для проверки работоспособности можно на 10 секунд вызвать окно предпросмотра (preview window):

70 Python
1
2
3
4
5
6
7
import picamera from time import sleep camera = picamera.PiCamera() camera.start_preview() # Открываем окно предпросмотра sleep(10) # через 10 секунд camera.stop_preview() # Закрываем окно предпросмотра
camera_002_ preview.py (924 Bytes)
71

Для окна предпросмотра можно устанавливать прозрачность:

72 Python
1
2
3
camera.start_preview(alpha=190) # Открываем окно предпросмотра с прозрачностью 190 (255 - не прозрачно, 0 - прозрачно) # или прозрачность можно изменить после открытия окна просмотра camera.preview.alpha = 100
73

Теперь, можно, по аналогии с утилитами raspistill и raspivid, при помощи объекта camera сделать фото с камеры:

74 Python
1
2
3
4
5
# Если не указывать абсолютный путь, файл будет создан в папке со скетчем camera.capture('image.jpg') # или # можно указать абсолютный путь для сохранения camera.capture('/home/pi/image.jpg')
75

Или записать видео:

76 Python
1
2
3
camera.start_recording('video.h264') # Начинаем запись и сохраняем в файл video.h264 camera.wait_recording(60) # Записываем 1 минуту, после чего camera.stop_recording() # Завершаем запись
77 На заметку:
Использование нативного метода wait_recording() для указания времени записи, предпочтительнее метода sleep() библиотеки time, потому что он отслеживает ошибки записи (например, ошибка нехватки места на диске) в процессе работы. А при использовании метода sleep(), об ошибках станет известно только при остановке записи методом stop_recording().
78

На фото/видео и окно предпросмотра можно накладывать текст и изменять его параметры — размер, цвет, цвет фона. В аннотациях, помимо цифр и знаков, допустимы только буквы латинского алфавита, при попытке отобразить кириллический символ будет выведена ошибка. В примере, в качестве текста используется актуальное время, которое обновляется каждые 200 мс:

79 Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import picamera import datetime as dt camera = picamera.PiCamera() camera.start_preview() # Открыть окно предпросмотра camera.annotate_text = dt.datetime.now().strftime('%Y-%m-%d %H:%M:%S') # Выводим дату/время camera.annotate_text_size = 50 # Размер текста из диапазона 6...160, по-умолчанию 32 camera.annotate_foreground = picamera.Color(y=0.2, u=0, v=0) # Цвет текста - серый camera.annotate_background = picamera.Color('black') # На черном фоне camera.start_recording('timestamped.h264') # Начинаем запись start = dt.datetime.now() # Обнуляем счетчик while (dt.datetime.now() - start).seconds < 30: # Пока не прошло 30 секунд camera.annotate_text = dt.datetime.now().strftime('%Y-%m-%d %H:%M:%S') # Обновляем текст camera.wait_recording(0.2) # Каждые 200 мс camera.stop_recording() # Завершаем запись camera.stop_preview() # Закрываем окно предпросмотра
80 На заметку:
Особенностью использования цветов отображения текста на изображении с камеры (annotate) является различие в задании цветов для фона (annotate_background) и самого текста (annotate_foreground). Дело в том, что цвета для фона можно задавать удобными значениями Color('yellow'), Color('blue'), Color('purple') и т.д., в то время как цвет текста задается в формате YUVColor(y=0.2, u=0, v=0). Но не стоит пугаться сложности формул конвертации RGB в YUV — в свойстве annotate_foreground поддерживается только составляющая Y, которая по сути отвечает за яркость текста. На это указывает справка к классу camera:

81

Объект camera позволяет задавать настройки для камеры:

82 Python
1
2
3
4
5
6
7
8
9
10
camera.sharpness = 0 # Резкость camera.contrast = 0 # Контрастность camera.brightness = 50 # Яркость camera.saturation = 0 # Насыщенность camera.ISO = 0 # Чувствительность camera.rotation = 0 # Поворот camera.hflip = False # Отражение по горизонтали camera.vflip = False # Отражение по вертикали camera.crop = (0.0, 0.0, 1.0, 1.0) # Обрезка camera.awb_mode = "sunlight" # Баланс белого
83

И изменять их во времени:

84 Python
1
2
3
4
5
camera.start_preview() for i in range(100): camera.brightness = i # Каждые 100 миллисекунд меняем яркость от 0 до 100 sleep(0.1) camera.stop_preview()
85

Конструктор класса PiCamera позволяет задавать некоторые настройки при создании экземпляра класса PiCamera:

86 Python
1
2
import picamera camera = picamera.PiCamera(sensor_mode=6, framerate=24)
87 На заметку:
Параметр sensor_mode задает одну из базовых настроек, поддерживаемых камерой на аппаратном уровне. Варианты значений и диапазоны допустимых частот кадров описаны в документации.
88

Перед тем, как сделать фото или записать видео, можно настроить разрешение и частоту кадров:

89 Python
1
2
camera.resolution = (1280, 720) # Разрешение camera.framerate = 24 # Частота кадров
90

Причем, отдельно задать размер фото/видео можно при вызове методов capture() и start_recording(), передав нужное значение параметра resize:

91 Python
1
2
3
camera.resolution = (1280, 720) # Разрешение по-умолчанию camera.capture('image.jpg', resize=(320, 240)) # Изменить разрешение фото camera.start_recording('video.h264', resize=(640, 480)) # Изменить разрешение видео
92

К изображению камеры можно применять различные эффекты:

93 Python
1
2
3
4
5
6
camera.start_preview() for effect in camera.IMAGE_EFFECTS: # Поочередно получаем все доступные эффекты camera.image_effect = effect # И применяем их к изображению camera.annotate_text = "Effect: %s" % effect # Накладываем на изображение название примененного эффекта sleep(5) # Можно смело использовать sleep() так как запись не ведется camera.stop_preview()
94

Аналогичным образом можно перебрать варианты настройки баланса белого awb_mode и варианты режимов экспозиции exposure_mode:

95 Python
1
2
3
4
5
6
7
8
9
10
11
12
13
camera.start_preview() for mode in camera.AWB_MODES: # Поочередно получаем все доступные режимы баланса белого camera.awb_mode = mode # И задаем режим работы камеры camera.annotate_text = "AWB mode: %s" % mode # Накладываем на изображение название режима баланса белого sleep(5) # Можно смело использовать sleep() так как запись не ведется # или for mode in camera.EXPOSURE_MODES: # Выводим поочередно все доступные режимы экспозиции camera.exposure_mode = mode # И задаем режим работы камеры camera.annotate_text = "Exposure: %s" % mode # Накладываем на изображение название режима экспозиции sleep(5) # Можно смело использовать sleep() так как запись не ведется camera.stop_preview()
96

При помощи метода capture_continuous(), не составит труда сгенерировать набор фотографий для Timelapse. В примере фото делается в начале каждого часа:

97 Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import RPi.GPIO as GPIO # Импортируем библиотеку по работе с GPIO import picamera # Импортируем библиотеку PiCamera from time import sleep from datetime import datetime, timedelta def wait(): # Функция расчета точной паузы между снимками next_minute = (datetime.now() + timedelta(seconds=60)) # Добавляем минуту к текущему времени next_minute = next_minute.replace(second=0, microsecond=0) # Обнуляем секунды/миллисекунды now = datetime.now() delay = ((next_minute - now).seconds + (next_minute - now).microseconds/1000000) # Получаем разницу в секундах c точностью до микросекунд sleep(delay) # Ждем try: camera = picamera.PiCamera() camera.start_preview() wait() # Ждем наступления новой минуты for filename in camera.capture_continuous('img_{timestamp:%Y-%m-%d-%H-%M}.jpg'): print('Captured %s' % filename) wait() # Функция для точного расчета паузы между снимками, для упрощения можно заменить на sleep() camera.stop_preview() # Закрываем окно предпросмотра finally: camera.close() # Корректно завершаем любую активность камеры GPIO.cleanup() # Возвращаем пины в исходное состояние print("End of program") # Информируем о завершении работы программы
98

Отключить мешающий светодиод можно свойством led:

99 На заметку:
Поскольку при подключении камеры, пин управления светодиодом камеры перемещается в GPIO Малинки и становится недоступным напрямую для процессора ARM, этот метод работает только при установленной библиотеке RPi.GPIO и при установленном режиме пинов BCMGPIO.setmode(GPIO.BCM).
100 Python
1
2
3
4
5
6
import RPi.GPIO as GPIO import picamera GPIO.setmode(GPIO.BCM) camera = picamera.PiCamera() camera.led = False
101

Использование камеры из Python — библиотека PiCamera (продвинутый уровень)

Библиотека PiCamera предоставляет очень широкие возможности по взаимодействию с камерой. Ниже представлены примеры решения некоторых неочевидных задач.

102

Захват видео в несколько потоков (максимум 4). Для каждого из них, при помощи параметра resize можно устанавливать индивидуальное разрешение. Каждый поток идентифицируется параметром splitter_port (0, 1, 2, 3):

103 Python
1
2
3
4
5
6
7
8
9
10
# Команда без параметра splitter_port по умолчанию занимает порт 1 camera.start_recording('sp_highres.h264') # Запускаем запись видео с параметрами по умолчанию camera.start_recording('sp_lowres.h264', splitter_port=0, resize=(640, 480)) # Параллельно запускаем другой поток с уменьшенным разрешением camera.start_recording('sp_lowres.h264', splitter_port=2, resize=(320, 240)) # Параллельно запускаем другой поток с ещё более уменьшенным разрешением camera.wait_recording(60) # Записываем 1 минуту видео camera.stop_recording() # Останавливаем запись всех потоков по очереди camera.stop_recording(splitter_port=0) camera.stop_recording(splitter_port=2)
104

Разделение видеопотока и сохранение его в разные файлы. Функция бывает полезна, например, для циклической записи видеоданных равной продолжительности в определенное количество файлов, таким образом, что, после того, как будет записан последний файл, данные начнут записываться заново в первый файл (принцип работы видеорегистратора).

105 На заметку:
Нельзя использовать данный метод для построения систем безопасности с круглосуточной записью видео — это выведет из строя накопитель, который не предназначен для таких режимов работы.
106

При чем возможна реализация двумя способами. При помощи split_recording():

107 Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import picamera # Импортируем библиотеку PiCamera camera = picamera.PiCamera(resolution=(640, 480)) # Задаем разрешение при инициализации camera.start_preview() # Открываем окно предпросмотра camera.start_recording('/home/pi/1.h264') # Начинаем записывать в файл #1 camera.wait_recording(5) # Записываем 5 секунд startindex = 2 # Задаем начальный индекс while(1): # Бесконечный цикл for i in range(startindex, 11): # Будем разделять в файлы с именами от 1.h264 до 10.h264 camera.split_recording('/home/pi/%d.h264' % i) # Разделяем видеопоток camera.wait_recording(5) # Записываем 5 секунд startindex = 1 # После записи 10 файла возвращем индекс к началу camera.stop_recording() # Останавливаем запись camera.stop_preview() # Закрываем окно предпросмотра
108

И при помощи record_sequence():

109 Python
1
2
3
4
5
6
7
8
9
10
11
import picamera # Импортируем библиотеку PiCamera camera = picamera.PiCamera(resolution=(640, 480)) # Задаем разрешение при инициализации camera.start_preview() # Открываем окно предпросмотра while(1): # Бесконечный цикл for filename in camera.record_sequence( '/home/pi/%d.h264' % i for i in range(1, 11)): # Будем разделять в файлы с именами от 1.h264 до 10.h264 camera.wait_recording(5) # Записываем 5 секунд camera.stop_recording() # Останавливаем запись camera.stop_preview() # Закрываем окно предпросмотра
110

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

111

В примере демонстрируется постоянная запись в буфер длительностью 20 секунд в течение 100 секунд, причем только последние 10 секунд будут сохранены в файл:

112 Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import RPi.GPIO as GPIO # Импортируем библиотеку по работе с GPIO import sys, traceback # Импортируем библиотеки для обработки исключений import picamera # Импортируем библиотеку по работе с камерой import datetime as dt # Импортируем библиотеку для работы с датой/временем from time import sleep # Импортируем функцию для работы с паузами try: camera = picamera.PiCamera() # Создаем экземпляр класса для работы с камерой camera.rotation=180 stream = picamera.PiCameraCircularIO(camera, seconds=20) # Создаем буфер размером в 20 секунд camera.annotate_text_size = 100 # Размер текста из диапазона 6...160, по-умолчанию 32 #camera.annotate_foreground = picamera.Color(y=1, u=0, v=0) camera.annotate_background = picamera.Color('black') # На черном фоне camera.start_recording(stream, format='h264') # Начинаем запись в буфер i=0 while (i<100): annotate_text = "" if (i<95): camera.annotate_text = "Before event (" + str(i) + ") - " # Маркируем время до наступления события else: camera.annotate_text = "After event (" + str(i) + ") - " # Маркируем время после наступления события camera.annotate_text = annotate_text + dt.datetime.now().strftime('%Y-%m-%d %H:%M:%S') # Добавляем дату/время camera.wait_recording(1) # Ждем секунду i+=1 print(i) # Выводим состояние в оболочку Shell camera.stop_recording() # Останавливаем запись stream.copy_to("/home/pi/stream.h264", seconds=10) # Сохраняем последние 10 секунд из буфера в файл except KeyboardInterrupt: # ... print("Exit pressed Ctrl+C") # Выход из программы по нажатию Ctrl+C except: # ... print("Other Exception") # Прочие исключения print("--- Start Exception Data:") traceback.print_exc(limit=2, file=sys.stdout) # Подробности исключения через traceback print("--- End Exception Data:") finally: camera.close() # Завершаем работу с экзепляром класса камеры print("CleanUp") # Информируем сбросе пинов GPIO.cleanup() # Возвращаем пины в исходное состояние print("End of program") # Информируем о завершении работы программы
113

Далее в статье будет приведен пример системы безопасности с записью 15-секундного видео (5 секунд до наступления события и 10 секунд после).

114

Пример использования камеры Raspberry Pi 3 в системах безопасности.

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

115
Подробно о модуле HC-SR501, его устройстве, принципе работы и вариантах использования можно почитать в статье — Инфракрасный датчик движения HC-SR501: использование с Arduino и Raspberry Pi 3.
116

В качестве датчика движения будет использоваться пьезоэлектрический инфракрасный датчик движения HC-SR501. Преимущество его использования заключается в том, что напряжение его питания должно быть в диапазоне 5-20 В, а уровень логической единицы на выходе равен 3,3 В. Эти параметры позволяют подключать датчик HC-SR501 напрямую к Raspberry Pi 3 без конвертеров питания и логических уровней.

117
118

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

119

Схема подключения (принципиальная и наглядная):

120
Слева — принципиальная схема, справа — наглядная (номинал резистора R1 — 220 Ом)
121
122

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

123

Скетч:

124 Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import RPi.GPIO as GPIO # Импортируем библиотеку по работе с GPIO import sys, traceback # Импортируем библиотеки для обработки исключений import picamera # Импортируем библиотеку по работе с камерой import datetime as dt # Импортируем библиотеку для работы с датой/временем from datetime import timedelta from time import sleep # Импортируем функцию для работы с паузами # Импорт библиотек, модулей и классов для работы с почтой ======================================================================================================== import smtplib # Импортируем библиотеку по работе с SMTP import os # Функции для работы с операционной системой, не зависящие от используемой операционной системы # Добавляем необходимые подклассы - MIME-типы import mimetypes # Импорт класса для обработки неизвестных MIME-типов, базирующихся на расширении файла from email import encoders # Импортируем энкодер from email.mime.base import MIMEBase # Общий тип from email.mime.text import MIMEText # Текст/HTML from email.mime.image import MIMEImage # Изображения from email.mime.audio import MIMEAudio # Аудио from email.mime.multipart import MIMEMultipart # Многокомпонентный объект periodBetweenEvents = 60 # Минимальный период между отправками фото try: flagBusy = False # Флаг занятости - для контроля завершенности предыдущего вызова # Переменная, предохраняющая от слишком частого срабатывания события и отправки фото lastAction = dt.datetime.now() lastAction = lastAction - timedelta(seconds=(periodBetweenEvents-20)) # Даем 20 секунд на калибровку датчика движения # Функция-обработчик срабатывания датчика движения =============================================================================================================== def detect_motion(pin): # Объявляем функцию-обработчик callback_func1 global flagBusy # Даем понять функции, что переменная не локальная, а глобальная global camera # Даем понять функции, что переменная не локальная, а глобальная global lastAction # Даем понять функции, что переменная не локальная, а глобальная print("Состояние пина " + str(pin) + " изменилось " + str(GPIO.input(pin))) GPIO.output(pinLED, GPIO.input(pin)) # Устанавливаем состояние светодиода в соответствии с состоянием датчика print((dt.datetime.now()-lastAction).seconds) if not flagBusy and ((dt.datetime.now()-lastAction).seconds > periodBetweenEvents): # Если флаг занятости установлен или не прошло 30 секунд пропускаем итерацию flagBusy=True # Устанавливаем флаг занятости, чтобы дать процессу завершиться if GPIO.input(pin): # Если наступление события (0->1), то делаем кадр print("Отправляю фото...") sleep(2) # Пауза нужна, чтобы объект успел попасть в кадр до момента съемки # Сначала задаем параметры накладываемого поверх текста - дата/время кадра camera.annotate_text = dt.datetime.now().strftime('%Y-%m-%d %H:%M:%S') # Выводим дату/время camera.annotate_text_size = 50 # Размер текста из диапазона 6...160, по-умолчанию 32 camera.annotate_foreground = picamera.Color(y=1.0, u=0.0, v=0.0) # Цвет текста - белый camera.annotate_background = picamera.Color('black') # На черном фоне camera.capture('/home/pi/image.jpg') # Осуществляем захват изображения send_email("mymail@mail.ru", # Когда кадр сделан, его нужно отправить на электронку "Обнаружено движение", # Тема "Внимание! Устройством безопасности обнаружено движение. Фото во вложении.", # Текст сообшения ["/home/pi/image.jpg"]) # Список файлов print("Фото отправлено") lastAction = dt.datetime.now() # Дата/время последнего снимка flagBusy=False # Возвращаем флаг, чтобы при следующем срабатывании функция была исполнена # Функции по отправке электронной почты с вложением ============================================================================================================== def send_email(addr_to, msg_subj, msg_text, files): # Здесь задаются идентификационные данные учетной записи - логин, пароль ================================================================================= addr_from = "my_addr@server.ru" # Отправитель, как правило совпадает с полем логин password = "password" # Пароль # ======================================================================================================================================================== msg = MIMEMultipart() # Создаем сообщение msg['From'] = addr_from # Адресат msg['To'] = addr_to # Получатель msg['Subject'] = msg_subj # Тема сообщения body = msg_text # Текст сообщения msg.attach(MIMEText(body, 'plain')) # Добавляем в сообщение текст process_attachement(msg, files) # Этот блок настраивается для каждого почтового провайдера отдельно, подробно - http://codius.ru/articles/280 ============================================ # Например, для yandex настройки будут следующими: server = smtplib.SMTP_SSL('smtp.yandex.ru', 465) # Создаем объект SMTP #server = smtplib.SMTP_SSL('smtp.server.ru', 465) # Создаем объект SMTP #server.starttls() # Начинаем шифрованный обмен по TLS #server.set_debuglevel(True) # Включаем режим отладки, если не нужен - можно закомментировать server.login(addr_from, password) # Получаем доступ server.send_message(msg) # Отправляем сообщение server.quit() # Выходим # ======================================================================================================================================================== def process_attachement(msg, files): # Функция по обработке списка, добавляемых к сообщению файлов for f in files: if os.path.isfile(f): # Если файл существует attach_file(msg,f) # Добавляем файл к сообщению elif os.path.exists(f): # Если путь не файл и существует, значит - папка dir = os.listdir(f) # Получаем список файлов в папке for file in dir: # Перебираем все файлы и... attach_file(msg,f+"/"+file) # ...добавляем каждый файл к сообщению def attach_file(msg, filepath): # Функция по добавлению конкретного файла к сообщению filename = os.path.basename(filepath) # Получаем только имя файла ctype, encoding = mimetypes.guess_type(filepath) # Определяем тип файла на основе его расширения if ctype is None or encoding is not None: # Если тип файла не определяется ctype = 'application/octet-stream' # Будем использовать общий тип maintype, subtype = ctype.split('/', 1) # Получаем тип и подтип if maintype == 'image': # Если изображение with open(filepath, 'rb') as fp: # Открываем файл для чтения file = MIMEImage(fp.read(), _subtype=subtype) # Используем тип MIMEImage fp.close() # После использования файл обязательно нужно закрыть else: # Неизвестный тип файла with open(filepath, 'rb') as fp: file = MIMEBase(maintype, subtype) # Используем общий MIME-тип file.set_payload(fp.read()) # Добавляем содержимое общего типа (полезную нагрузку) fp.close() encoders.encode_base64(file) # Содержимое должно кодироваться как Base64 file.add_header('Content-Disposition', 'attachment', filename=filename) # Добавляем заголовки msg.attach(file) # Добавляем файл к отправляемому сообщению # Основной код программы ========================================================================================================================================= camera = picamera.PiCamera() # Создаем экземпляр класса для работы с камерой camera.rotation=180 # Инициализация пинов GPIO.setmode(GPIO.BCM) pinLED=24 # Пин светодиода pinSensor=15 # Пин датчика GPIO.setup(pinLED, GPIO.OUT, initial=0) # Пин светодиода в режим OUTPUT GPIO.setup(pinSensor, GPIO.IN) # Сигнальный пин сенсора в режим INPUT, без подтяжки GPIO.add_event_detect(pinSensor, GPIO.BOTH, callback=detect_motion) # Назначаем функцию-обработчик (detect_motion) на детектирование события (изменение сигнала - и по фронту и по спаду) while 1: sleep(1) # Бесконечный цикл с итерацией раз в 10 секунд except KeyboardInterrupt: # ... print("Exit pressed Ctrl+C") # Выход из программы по нажатию Ctrl+C except: # ... print("Other Exception") # Прочие исключения print("--- Start Exception Data:") traceback.print_exc(limit=2, file=sys.stdout) # Подробности исключения через traceback print("--- End Exception Data:") finally: camera.close() # Завершаем работу с экзепляром класса камеры print("CleanUp") # Информируем сбросе пинов GPIO.cleanup() # Возвращаем пины в исходное состояние print("End of program") # Информируем о завершении работы программы
125

От частого срабатывания датчика движения, пример дополнен программным «предохранителем» — переменная periodBetweenEvents — он не позволяет делать и отправлять фото чаще чем раз в 6- секунд.

126
Письмо системы безопасности с фото
Письмо системы безопасности с фото
127

Данный пример обладает одним существенным недостатком — из-за разницы в углах охвата камеры и датчика движения, очень вероятны ситуации, когда снимок будет сделан до того, как нужный объект попадет в поле зрения камеры, либо уже после того, как объект из него удалится. Этот недостаток можно устранить разными способами:

  • внедрить паузу, корректирующую время создания снимка (строка 42 скетча);
  • расположить камеру и датчик движения так, чтобы избегать таких проблем;
  • при детектировании события начинать видеозапись заданной продолжительностью.

128

Но библиотека PiCamera, как уже было упомянуто выше, обладает возможностью осуществлять циклическую запись видео в буфер заданного размера. Это позволяет получить доступ к видеоданным, записанным до наступления события. В следующем примере, Малинка постоянно осуществляет запись в буфер длительностью 30 секунд. При наступлении события, в файл записывается 5 секунд до наступления события и 10 секунд после его наступления. Каждое последующее событие записывается в новый файл — его имя формируется из номера по порядку и даты/времени наступившего события.

129 Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import RPi.GPIO as GPIO # Импортируем библиотеку по работе с GPIO import sys, traceback # Импортируем библиотеки для обработки исключений import picamera # Импортируем библиотеку по работе с камерой import datetime as dt # Импортируем библиотеку для работы с датой/временем from datetime import timedelta from time import sleep # Импортируем функцию для работы с паузами periodBetweenEvents = 60 # Минимальный период между отправками фото try: saveEvent = False # Флаг занятости - для контроля завершенности предыдущего вызова # Переменная, предохраняющая от слишком частого срабатывания события и отправки фото lastAction = dt.datetime.now() lastAction = lastAction - timedelta(seconds=(periodBetweenEvents-10)) # Даем 20 секунд на калибровку датчика движения startEvent = dt.datetime.now() # Функция-обработчик срабатывания датчика движения =============================================================================================================== def detect_motion(pin): # Объявляем функцию-обработчик callback_func1 global saveEvent # Даем понять функции, что переменная не локальная, а глобальная global camera # Даем понять функции, что переменная не локальная, а глобальная global lastAction # Даем понять функции, что переменная не локальная, а глобальная global startEvent # Переменная номера файла print("Состояние пина " + str(pin) + " изменилось " + str(GPIO.input(pin))) GPIO.output(pinLED, GPIO.input(pin)) # Устанавливаем состояние светодиода в соответствии с состоянием датчика print((dt.datetime.now()-lastAction).seconds) if not saveEvent and ((dt.datetime.now()-lastAction).seconds > periodBetweenEvents): # Если флаг занятости установлен или не прошло 30 секунд пропускаем итерацию if GPIO.input(pin): # Если наступление события (0->1), то сохраняем видео print("Продолжаем запись 10 секунд после наступления события") startEvent = dt.datetime.now() # Запоминаем, когда наступило событие saveEvent = True # Устанавливаем флаг срабатывания события # Основной код программы ========================================================================================================================================= camera = picamera.PiCamera() # Создаем экземпляр класса для работы с камерой camera.rotation=180 # Поворачиваем изображение на 180 градусов # Инициализация пинов GPIO.setmode(GPIO.BCM) pinLED=24 # Пин светодиода pinSensor=15 # Пин датчика GPIO.setup(pinLED, GPIO.OUT, initial=0) # Пин светодиода в режим OUTPUT GPIO.setup(pinSensor, GPIO.IN) # Сигнальный пин сенсора в режим INPUT, без подтяжки GPIO.add_event_detect(pinSensor, GPIO.BOTH, callback=detect_motion) # Назначаем функцию-обработчик (detect_motion) на детектирование события (изменение сигнала - и по фронту и по спаду) camera.annotate_text_size = 50 # Размер текста из диапазона 6...160, по-умолчанию 32 stream = picamera.PiCameraCircularIO(camera, seconds=30) # Создаем буфер размером в 30 секунд camera.start_recording(stream, format='h264') # Начинаем запись в буфер fileNum = 1 # Начальный номер файла while 1: # Бесконечный цикл annotate_text = "" if saveEvent: annotate_text = "After event - " # Маркируем кадры после наступления события else: annotate_text = "Before event - " # Маркируем кадры до наступления события camera.annotate_text = annotate_text + dt.datetime.now().strftime('%Y-%m-%d %H:%M:%S') # Добавляем дату/время if saveEvent and ((dt.datetime.now()-startEvent).seconds > 10): print("Запись события завершена, приступаем к соранению") camera.stop_recording() # Останавливаем запись fileName = '%03d' % fileNum + startEvent.strftime('_%Y_%m_%d_%H_%M_%S')+'.h264' # Формируем имя файла print('Видеозапись события будет сохранена в файл '+ fileName) stream.copy_to("/home/pi/"+fileName, seconds=15) # Сохраняем последние 10 секунд из буфера в файл print('Событие сохранено') stream.clear() # Очищаем буфер camera.start_recording(stream, format='h264') # Начинаем новую запись в буфер fileNum+=1 # Наращиваем номер для имени файла lastAction = dt.datetime.now() # Дата/время последнего снимка saveEvent = False # Сбрасываем флаг записи события else: camera.wait_recording(0.2) # Ждем 0.2 секунды except KeyboardInterrupt: # ... print("Exit pressed Ctrl+C") # Выход из программы по нажатию Ctrl+C except: # ... print("Other Exception") # Прочие исключения print("--- Start Exception Data:") traceback.print_exc(limit=2, file=sys.stdout) # Подробности исключения через traceback print("--- End Exception Data:") finally: camera.close() # Завершаем работу с экзепляром класса камеры print("CleanUp") # Информируем сбросе пинов GPIO.cleanup() # Возвращаем пины в исходное состояние print("End of program") # Информируем о завершении работы программы
130
Аннотации к видео показывают момент срабатывания датчика движения — bBefore Event/b (до наступления события) и bAfter Event/b (после наступления события)
Аннотации к видео показывают момент срабатывания датчика движения — Before Event (до наступления события) и After Event (после наступления события)
131

Качество съемки

Для оценки качества съемки, прилагаются файлы записи видео в разных режимах. Видеопоток в файлах не помещен в медиаконтейнер, поэтому видео воспроизводится с частотой 30 fps. Поместить видеопоток в медиаконтейнер можно при помощи утилиты avconv:

132

Воспроизвести файлы *.h264 на Windows-ПК можно при помощи медиаплеера VLC.

133 На заметку:
Во время съемки камера умышленно приводится в движение, для тестирования записи динамической картинки.
134

Видео записано при помощи скетча:

135 Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import RPi.GPIO as GPIO # Импортируем библиотеку по работе с GPIO import picamera # Импортируем библиотеку PiCamera from time import sleep import datetime as dt def start_video_capture(filename): global camera annotate=('Resolution:' + str(camera.resolution) + ', Framerate:' + str(camera.framerate) + ', Sensor Mode:' + str(camera.sensor_mode)) camera.annotate_text = annotate # Выводим дату/время camera.annotate_text_size = 20 # Размер текста из диапазона 6...160, по-умолчанию 32 camera.annotate_foreground = picamera.Color(y=1, u=0, v=0) # Цвет текста - серый camera.annotate_background = picamera.Color('black') # На черном фоне camera.start_recording('/home/pi/'+filename) # Начинаем запись и сохраняем в файл camera.wait_recording(20) # Записываем 1 минуту, после чего camera.stop_recording() # Завершаем запись try: # Задаем параметры основных режимов params=[ [1, 30, (1920, 1080), '1080p'], [5, 45, (1296, 730), '720p'], [6, 60, (640, 480), '480p'], [7, 90, (640, 480), '480p'] ] camera = picamera.PiCamera() for i in range(0,4): camera.sensor_mode = params[i][0] # Задаем режим работы сенсора (https://picamera.readthedocs.io/en/release-1.13/fov.html#sensor-modes) print(camera.sensor_mode) camera.rotation = 180 # Разворачиваем камеру camera.resolution = params[i][2] # Задаем разрешение camera.framerate = params[i][1] # Задаем частоту кадров start_video_capture(params[i][3] + str(camera.framerate) + 'fps.h264') # Начинаем запись и сохраняем в файл finally: camera.close() # Корректно завершаем любую активность камеры GPIO.cleanup() # Возвращаем пины в исходное состояние print("End of program") # Информируем о завершении работы программы
136

В некоторых местах заметен значительный пропуск кадров.

138

Похожие запросы:

  • Raspberry Pi 3 GPIO — камера
  • Как подключить камеру к Raspberry Pi 3
  • Как настроить камеру Raspberry Pi 3
  • Как при помощи камеры сделать timelapse-видео
  • Сшивание timelapse-видео из изображений
  • Convert media files like a geek: a guide to video transcoding with Avconv
  • Creating a timelapse clip with avconv
  • Creating a time-lapse from a series of images using avconv
comments powered by HyperComments