Видеорегистратор за 5 минут

Сначала - идея.

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

Обычная USB-веб-камера подключается к серверу под управлением Linux. Требуется, чтобы она постоянно делала снимки и эти снимки записываются в файлы. Затем из нескольких файлов снимков склеивается видеофайл. То же самое делается еще раз. Полученные видеофайлы сравниваются по размеру. Если перед камерой ничего не происходит, то их размеры будут примерно одинаковыми (точно равными они быть не могут).

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

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

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

Для реализации такого простого видеорегистратора понадобится сервер или компьютер под управлением Linux, установленный на нем ffmpeg и приведенный ниже скрипт на языке Perl. Собственно сам скрипт очень простой, там больше комментариев, чем программирования.

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

Скрипт в виде архива можно скачать. Размер 4,4 кб.

#!/usr/bin/perl -w
#-----------------------------------------------------------------------------------
#	                    Видеорегистратор
#
# Запись видео и звука с USB веб камеры при обнаружении движения в поле зрения. Если
# камеры нет или выбрано неправильное устройство /dev/video, то скрипт не будет
# работать и прервется с ошибкой. Задержка срабатывания детектора движения может
# достигать 6 секунд.
# Требуется установленный ffmpeg и Perl.
# Скрипт работает в домашнем каталоге $HOME того пользователя, который его запускает.
# Выход - Ctrl+C (возможно несколько раз) или закрыть окно терминала.
#
#      Версия 0.01, 7 августа 2011 года
# Первая рабочая версия. Проверена на Ubuntu 10.10  Desktop и 11.04 Desktop. Не работает
# на RHEL 6 из-за того, что примерно в 20% случаев не захватывается видео с камеры.
#
#      Версия 0.02, 12 августа 2011 года
# Добавлена проверка исключений при вычислении $FACTOR.
# Добавлена проверка правильности выполнения команды записи с камеры.
#-----------------------------------------------------------------------------------
# Ключи командной строки
$dd = "-d";    # Вывод отладочной печати по ключу -d
$d = "-dd";    # Вывод отладочной печати по ключу -dd - еще больше
$h = "-h";     # Печать сновной информации о программе
# ***Отладка*** Печать по ключу -h
# Далее все блоки, реагирующие на наличие ключей в командной строке, устроены так же
# Считываем аргументы командной строки в массив (их может и не быть)
# Сначала сделаем копию массива аргументов командной строки, затем разберем эту копию
# на скаляры $arg. После этого проверим, есть ли среди элементов значение "-h". Если
# есть - печатаем.
@COMANDKEYS = @ARGV;
while ($arg = shift @COMANDKEYS) {
     if ($arg =~ m/$h/) {
         print "Версия 0.02, 12 августа 2011 года\n";
         print "Допустимы ключи -h, -d, -dd\n\n";
                         }
                                  }
# ***Конец отладки***
# ***Отладка*** Печать по ключу -dd
@COMANDKEYS = @ARGV;
while ($arg = shift @COMANDKEYS) {
     if ($arg =~ m/$d/) {
         print "Введенные ключи командной строки @ARGV\n";    # Печать массива ключей командной строки
         $ARGV = @ARGV;                                       # Получение размера массива ключей командной строки
         print "Количество ключей командной строки $ARGV\n";  # Печать размера массива ключей командной строки
         print "Образец, с которым ищется совпадение $arg\n"; # Печать образца, с которым ищется совпадение
                         }
                                }
# ***Конец отладки***
# Сделаем текущим домашний каталог пользователя
chdir;
# ***Отладка*** Печать текущего каталога. Печать по ключу -dd
@COMANDKEYS = @ARGV;
while ($arg = shift @COMANDKEYS) {
     if ($arg =~ m/$d/) {
         use Cwd;
         $CURDIR = cwd;
         print "Текущий каталог - $CURDIR\n";
                        }
                                }
# ***Конец отладки***
# Определяем переменные. Каталог videoregistrator будет скрытым (dot кталог)
$VIDEOREGISTRATOR="./.videoregistrator";
$IMAGES="./.videoregistrator/images";
$VIDEO="./.videoregistrator/video";
# Имя устройства веб камеры
$DEVVIDEO="/dev/video0";
#$DEVVIDEO="/dev/video1";
# Длительность записываемых видеофрагментов в формате чч:мм:сс
$RECTIME="00:03:00"; 
#$RECTIME="00:01:00";
# Проверка существования каталогов $HOME/.videoregistrator
#                                  $HOME/.videoregistrator/images
#                                  $HOME/.videoregistrator/video
# и создание этих каталогов, если их нет
# Обычно используется конструкция if (-d, $VIDEOREGISTRATOR) {...
# Но в Ubuntu 11.04 она не работает, требуется убрать запятую после -d
if (-d $VIDEOREGISTRATOR) {
        print "Каталог $VIDEOREGISTRATOR уже существует - OK\n";
        }
       else
        {print "Создается каталог $VIDEOREGISTRATOR\n";
         mkdir "$VIDEOREGISTRATOR", 0755 or warn "Невозможно создать каталог $VIDEOREGISTRATOR: $!\n";
        }
if (-d $IMAGES) {
        print "Каталог $IMAGES уже существует - OK\n";
        }
       else
        {print "Создается каталог $IMAGES\n";
         mkdir "$IMAGES", 0755 or warn "Невозможно создать каталог $IMAGES: $!\n";
        }
if (-d $VIDEO) {
        print "Каталог $VIDEO уже существует - OK\n";
        }
       else
        {print "Создается каталог $VIDEO\n";
         mkdir "$VIDEO", 0755 or warn "Невозможно создать каталог $VIDEO: $!\n";
        }
$FACTOR=0.95;  # Инициализируем переменную
# Начинаем бесконечный цикл
while (1) {
# Начинаем цикл для выявления наличия движения в кадре.
# Из двух фагментов видео продолжительностью по 2 секунды захватываем по 5 кадров.
# Удаляем 4 первых кадра в каждой последовательности и склеиваем оставшиеся кадры
# в видеофайл.
# Смотрим размер получившихся видеофайлов (значение получается в байтах).
# Если $FACTOR - отношение размеров видеофайлов - больше или меньше пороговых
# значений то начинаем запись видео длительностью $RECTIME.
# Значения, с которым сравнивается $FACTOR подбираются экспериментально, они
# зависят от инерционности работы автоматики камеры (работа АРУ и фокусировки).
#     Для дешевой A4Tech - нижнее ???, верхнее 1.06 - медленно отрабатывает АРУ
#     Для A4Tech на ножке - нижнее 0.85, верхнее 1.04
#     Для Asus EEE 901 - нижнее ???, верхнее 1.02 - очень "быстрая" камера
#     Для съемки HD камерой Logitech B910 - нижнее 0.85, верхнее 1.02
if ( 0.85 < $FACTOR && $FACTOR < 1.02 ) {
         system ("/usr/bin/ffmpeg -t 00:00:02 -f video4linux2 -s 640x480 -i $DEVVIDEO -f image2 -r 5 $IMAGES/old_image%d.jpg 2>/dev/null");
         sleep 1;  # Пауза 1 секунда для сброса переходных процессов в веб камере
         system ("/usr/bin/ffmpeg -t 00:00:02 -f video4linux2 -s 640x480 -i $DEVVIDEO -f image2 -r 5 $IMAGES/new_image%d.jpg 2>/dev/null");
# Удаляем по 4 первых фото в старой и новой сериях - эти сильно отличаются
# от остальных из-за инерционности работы автоматики камеры
# Затем переименовываем файлы так, чтобы их номера начинались с 1
         for(1..4) {
             unlink ("$IMAGES/old_image$_.jpg");
             unlink ("$IMAGES/new_image$_.jpg");
                    }
         for(5..11) {
             $i = $_-4;
             $old_name_old_image = "$IMAGES/old_image$_.jpg";
             $new_name_old_image = "$IMAGES/old_image$i.jpg";
             rename $old_name_old_image, $new_name_old_image;
             unlink ("$IMAGES/old_image$_.jpg");
             $old_name_new_image = "$IMAGES/new_image$_.jpg";
             $new_name_new_image = "$IMAGES/new_image$i.jpg";
             rename $old_name_new_image, $new_name_new_image;
             unlink ("$IMAGES/new_image$_.jpg");
             # ***Отладка*** Печать
             #          print "$old_name_old_image\n";
             #          print "$new_name_old_image\n";
             #          print "$old_name_new_image\n";
             #          print "$new_name_new_image\n";
             # ***Конец отладки***
                    }
# Из оставшихся фото делаем два видеоролика
         system ("ffmpeg -f image2 -r 5 -i $IMAGES/old_image%d.jpg  -s 640x480 -y -an -r 25 $IMAGES/old_video_test.avi 2>/dev/null");
         system ("ffmpeg -f image2 -r 5 -i $IMAGES/new_image%d.jpg  -s 640x480 -y -an -r 25 $IMAGES/new_video_test.avi 2>/dev/null");
   $SIZEOLDVIDEO=(-s "$IMAGES/old_video_test.avi");
   $SIZENEWVIDEO=(-s "$IMAGES/new_video_test.avi");
# Вычисляем отношение размеров файлов нового и старого видео
# eval - защита от деления на ноль, в таком случае $FACTOR = 0.95
   $FACTOR= eval '$SIZENEWVIDEO/$SIZEOLDVIDEO';
   if ($@) { $FACTOR = 0.95; }
      else {$FACTOR = $SIZENEWVIDEO/$SIZEOLDVIDEO;}
# ***Отладка*** Печать по ключу -d
@COMANDKEYS = @ARGV;
while ($arg = shift @COMANDKEYS) {
     if ($arg =~ m/$dd/) {
   print "Размер старого файла $SIZEOLDVIDEO байт\n";
   print "Размер нового файла $SIZENEWVIDEO байт\n";
   print "FACTOR --- $FACTOR\n\n";
                        }
                                }
# ***Конец отладки***
         }
else
      {
       $SEPARATOR="_";
       $SEPARATORT="-";
       $O=0;
       # Получение даты ((год+1900), (месяц+1))
       ($sec,$min,$hour,$mday,$mon,$year)=localtime(time);
        if ( $sec < 10 ) { $sec = $O.$sec; } 
	if ( $min < 10 ) { $min = $O.$min; } 
	if ( $hour < 10 ) { $hour = $O.$hour; }
        if ( $hour > 24 ) { $hour -= 12; }
	if ( $mday < 10 ) { $mday = $O.$mday; } 
	if ( $mon < 10 ) { $mon = $O.($mon+1); }
       $DATE = ($year+1900).$SEPARATOR.$mon.$SEPARATOR.$mday.$SEPARATOR.$hour.$SEPARATORT.$min;
       # ***Отладка*** Печать по ключу -d
       @COMANDKEYS = @ARGV;
       while ($arg = shift @COMANDKEYS) {
             if ($arg =~ m/$dd/) {
                print "Идет запись в файл с меткой $DATE\n\n";
                                  }
                                         }
       # ***Конец отладки***
       # Записываем видеофрагмент длительностью $RECTIME минут. ffmpeg - глючная, поэтому
       # командную строку надо сначала тестировать отдельно и для конкретного компьютера.
       # Даже после автономного тестирования, в скрипте она может работать не так, как
       # ожидается. Например, команда может не работать при указании времени записи (-t),
       # но работает, если время не указывать.
       # Несколько примеров:
# Запись видео 1920x1010 и стереозвука (рабочая станция, хорошая веб камера)
# Такой трехминутный видеофайл может достигать 200 Мб
#system ("/usr/bin/ffmpeg -y -t $RECTIME -f oss -ac 2 -ar 48000 -f alsa -i hw:1 $VIDEO/$DATE.raw.avi -f video4linux2 -s 1920x1010 -r 30000/1001 -i $DEVVIDEO -sameq -aspect 16:9 -f avi -vcodec mjpeg -r 30000/1001 -y $VIDEO/$DATE.video.avi 2>/dev/null");
# Запись видео 640x480 и стереозвука (рабочая станция 1)
system ("/usr/bin/ffmpeg -y -t $RECTIME -f oss -ac 2 -f alsa -i hw:1 -f video4linux2 -s 640x480 -r 30 -i $DEVVIDEO $VIDEO/$DATE.video.avi 2>/dev/null");
# Запись видео 640x480 и звука (рабочая станция 2)
#system ("/usr/bin/ffmpeg -t $RECTIME -y -f oss -f alsa -i hw:2,0 -f video4linux2 -s 640x480 -r 30 -i $DEVVIDEO $VIDEO/$DATE.video.avi 2>/dev/null");
# Запись видео 640x480 без звука (сервер)
#system ("/usr/bin/ffmpeg -t $RECTIME -f video4linux2 -s 640x480 -i $DEVVIDEO $VIDEO/$DATE.video.mpg 2>/dev/null");
# Для камеры ноутбука Asus EEE 901 - стерео микрофон, разрешение видео 640x480
#system ("/usr/bin/ffmpeg -t $RECTIME -y -f oss -ac 2 -ar 48000 -f alsa -i hw:0 -f video4linux2 -s 640x480 -r 30 -i $DEVVIDEO $VIDEO/$DATE.video.avi 2>/dev/null");
# Запись видео - строка для отладки и экспериментов
# system ("/usr/bin/ffmpeg -y -f oss -ac 2 -ar 48000 -f alsa -i hw:1 ./.videoregistrator/video/test_raw.avi -y -f video4linux2 -s 640x480 -r 30000/1001 -i $DEVVIDEO -sameq -f avi -vcodec mjpeg ./.videoregistrator/video/test_video.avi");
# Проверяем код завершения команды. Для получения истинного кода завершения полученное значение следует разделить на 256.
# Если код завершения команды не равен нулю, то печатаем и завершаем программу.
$RET_CODE = $?/256;
if ($? != 0) {
              print "Код возврата ffmpeg: $RET_CODE\n";
              print "Ошибка в команде записи с камеры\n";
              exit;
              }
       $FACTOR=0.95;  # Возвращаем $FACTOR в исходное состояние.
# Отправка сообщения по электронной почте о состоявшейся записи файла с видео. Для серверов s-01 и s-07
# команды разные.
# system ("echo 's-01 - записан файл с меткой $DATE' | mail -s 's-01' sysmail\@***.ru");
# system ("echo 's-07 - записан файл с меткой $DATE' | ssmtp sysmail\@***.ru");
       sleep 1;    # Пауза 1 секунда для сброса переходных процессов в веб камере
      }
}

Андрей Ракитин