Детекция изменений в сцене и сохранение видеофрагментов в формате h264 на Raspberry Pi без декодирования


    Добрый день. В этой статье я расскажу, далеко не в первый раз, как на Raspberry Pi 3 и более слабых платформах одновременно детектировать движение и сохранять/транслировать видео в формате H264. Я поделюсь с такими же новичками в мире Raspberry Pi, как и я, о том, что узнал сам за несколько дней, пока разбирался в способах решения задачи. Говорить буду о работе с камерой Raspberry Pi простым человеческим языком.


    Я рассмотрю простую задачу "Требуется захватывать с CSI-камеры Raspberry Pi v.1.3 или 2.1 видео разрешением 720p @ 25 FPS, нарезать его в файлы продолжительностью 1 минуту в формате h264, чтобы затем выгружать на удаленный сервер те, в которых обнаружено изменение сцены".


    Чтобы не обременять деталями, за которыми ускользнет понимание основных вещей, код буду предоставлять в минимальном виде.


    Для продвинутых пользователей, наверняка более полезно будет прочитать статью, однако, если Вы (как и я) в вопросе не очень разбираетесь, лучше начать с этой, где описаны основы подхода.

    Большинство статей о работе с камерой RPi, которые вы найдете в интернете, будут использовать OpenCV, если речь идет о том, чтобы что-то в сцене обнаруживать и менять, либо ffmpeg, если требуется делать что-то с видео.


    Поскольку я двигался со стороны OpenCV, то я попал в следующую ловушку, которая характеризуется следующей отправной точкой мыслительного процесса: "камера вещает в RGB/MJPEG, как-то из этого надо сделать H264 и положить в MP4...".


    Начните читать про камеру Raspberry Pi вот здесь, прежде чем вообще что-то делать, потому что все datasheets-ы от продавцов камер (включая Амперку), которые будут подсовывать вам поисковые системы — это не то пальто, ничего полезного в них нет.


    Да, у камеры Raspberry Pi уже есть объектив. Трудно найти информацию о том, какое фокусное расстояние, но я предполагаю, что оно находится в районе 2.8-3.2 мм. Мой коллега распечатал корпус, который можно использовать как для стандартного объектива, так и прилепить любой CS-mount с переменным фокусом, а еще у этого корпуса есть насадка для штатива. Если кому-то надо файл для печати на 3d-принтере, обращайтесь — предоставлю без проблем, на Thingverse не выкладывали.


    Не пытайтесь на Raspberry 3 и более слабых платформах кодировать/декодировать видео — они слишком слабы для этого. Не получится выполнить FullHD кодирование даже с OpenMAX.


    Для меня следующая мысль была совсем не очевидна откровением: можно с камеры Raspberry Pi брать видео-поток уже кодированный в H264 и просто сохранять его в видео-файл.


    В случае FFmpeg это выглядит так:


    #!/bin/bash
    
    SS=$1
    FPS=$2
    
    WIDTH=1600
    HEIGHT=1200
    
    while :
    do
    
    ffmpeg -f video4linux2 -input_format h264 -video_size ${WIDTH}x${HEIGHT} \
        -framerate $FPS -i /dev/video0 -vcodec copy -map 0 \
        -segment_time $SS -f segment \
        -strftime 1 -reset_timestamps 1 \
        -segment_format mp4 /video/file_%s.mp4
    done

    Этот код сохраняет куски продолжительностью $SS и с FPS равным $FPS в файлы, с именем unix-timestamp. При этом не выполняется никакого перекодирования, только упаковка в MP4. Загрузка CPU в этом случае будет единицы процентов. Далее, эти файлы можно загрузить на FTP.


    На камере v2.1 не стоит использовать разрешение 1080p — получите очень странную картинку с обрезкой границ (очень узкий FOV). Используйте разрешение 1600x1200 — это дает прекрасный результат на том же FPS. Кроме этого, файлы, которые будут генерироваться ffmpeg или raspivid (см. далее) будут непропорционально большими. Это особенность реализации данной камеры.


    Если хотите работать с бОльшими разрешениями, то, вероятно, потребуется увеличить RAM у GPU Raspberry Pi, иначе словите что-то подобное — я словил.

    Если вы берете фрагмент в формате H264, само собой, вы не можете с ним работать без декодирования. В связи с этим многие рецепты как определить наличие движения в сцене сразу перестают быть доступными: не получится на Raspberry Pi 3 одновременно декодировать FullHD поток и определять в нем движение средствами OpenCV в реальном времени, даже просто поток 1280x720 @ 30 FPS декодировать не получится в реальном времени.


    Как же тогда осуществлять детекцию движения и одновременно работать с H264? Способ есть — обработка векторов движения H264.


    Камера Raspberry Pi умеет возвращать еще и так называемые векторы движения (motion vectors) для потока H264. Эта структура используется кодеком. Однако, она может использоваться и нами для определения присутствия движения в сцене.


    Вот в этом примере из репозитория Raspberry Pi вы можете увидеть, что MV определен как:


    typedef struct
    {
       signed char x_vector;
       signed char y_vector;
       short sad;
    } INLINE_MOTION_VECTOR; 

    где {x,y}_vector — векторы движения в определенном месте кадра, а sad — предположительно вот это. И sad и vectors могут использоваться для оценки движения в кадре. У меня не дошли руки до sad, я использовал motion vectors.


    С помощью FFmpeg добраться до motion vectors не получится, поэтому будем использовать родную утилиту Raspivid:


    #!/bin/bash
    
    SS=$1
    FPS=$2
    
    W=1600
    H=1200
    
    while :
    do
        raspivid -pts -stm -w $W -h $H -fps $FPS -t 0 \
                        -sn $(date +%s) -sg $(($SS*1000)) \
                        -o /video/video_%d.h264 -x /video/mv_%d.txt
    done

    имя файла, генерируемое raspivid просто увеличивается на +1, то есть, вы не можете использовать timestamp, поэтому придется делать постпроцессинг на основании данных stat о созданном файле.

    Эта утилита может использоваться и как источник для FFmpeg и для CVLC, если вы хотете сделать RTSP трансляцию. Вообще, она умеет делать много что хорошего — в частности сохранять motion vectors в файл.


    Обратите внимание, если у вас видео "мигает", вероятно, неправильно настроена компенсация частоты мигания лампочек, которая связана с частотой тока электросети. Особо видно это на низкокачественных лампах дневного света. Установка FPS=25 (вместо 30) вкупе с встроенным механизмом подавления мигания, который имеет камера, для нашей электросети 50Hz дает лучший результат, без мигания.


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

    Итак, вышеприведенный фрагмент кода, будучи запущенным на RPi будет создавать два набора файлов — один набор с H264 видео, второй набор с матрицами MV.


    Видео, в файлах *.h264 — это сырой поток H264, а не MP4, вы можете проиграть их с помощью VLC, но эти файлы будут проигрываться некорректно, например, ускоренно. В документации по Raspberry Pi предлагается установить gpac:


    sudo apt install -y gpac

    и упаковывать h264 в mp4 с помощью следующей команды:


    time MP4Box -add /video/video_1588645858.h264 /video/video_1588645858.mp4
    AVC-H264 import - frame size 1280 x 720 at 25.000 FPS
    AVC Import results: 1560 samples - Slices: 26 I 1534 P 0 B - 0 SEI - 26 IDR
    Saving to /video/video_1588645858.mp4: 0.500 secs Interleaving
    
    real    0m11.164s
    user    0m0.548s
    sys 0m0.469s

    Минутный фрагмент упаковывается за 11 секунд на Raspberry Pi 3, то есть подходит для использования.


    Для работы с MV можно отправной точкой взять следующий тривиальный код:


    import os
    import sys
    import struct
    import numpy as np
    
    resolution = [1280, 720]
    
    framelength = int(((resolution[0] + 16) / 16) * (resolution[1] / 16) * 4)
    
    framedata = []
    
    cnt = 0
    
    while True:
        data = sys.stdin.read(framelength)
    
        if data == '':
          break
    
        cnt += 1
    
        framedata = struct.unpack('>%db' % framelength, data)
        vectors = np.reshape(framedata[:(resolution[1]/16 * (resolution[0]/16 + 1) * 4)], (resolution[1]/16, resolution[0]/16 + 1, 4)).astype(np.float32)
        vectors = np.multiply(vectors[:, :, 0:1], vectors[:, :, 0:1])
    
        print int(np.sum(vectors))

    Он выполнен на примере кода на C, приведенного ранее.


    time $(cat /video/mv_1588645871.txt| python proc.py > /dev/null)
    
    real    0m26.288s
    user    0m25.005s
    sys 0m0.583s

    Этот код работает быстро, поскольку вся логика внутри NumPy (то есть на C), но его можно еще ускорить, если обрабатывать, например, 50% кадров, а не все. Есть риск пропустить MV, но движение как правило занимает несколько фреймов, поэтому не страшно.


    Гистограмма видео с изменениями в сцене выглядит так, как представлено на следующем изображении:


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


    Определить изменение в сцене можно с помощью следующего скрипта:


    cat /video/mv_1588645871.txt | python proc.py | awk '{ if ($1 > 0) print "motion" }' | grep motion > /dev/null
    echo $?
    0

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


    Таким образом, вот и вся наука для работы с кодированным видео и определением "движения в сцене" без декодирования, которую необходимо знать для того, чтобы выполнять все операции на Raspberry Pi без перегрузки ресурсов платформы.


    Если вам требуется определять не просто изменение в кадре, но и области движения, вы можете анализировать области матрицы, в которых MV не нулевые. Еще можно обратиться к материалу на Medium, где авторы используют MV из Python.


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

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 18

      0
      В статье не хватает анализа, не получится ли ту же задачу решить еще проще, вообще без рассмотрения векторов движения. Тупо на основании неодинаковости размера соседних по времени видеофрагментов.
        0

        Так из-за шума они всегда должны быть разными, разве нет?

          +1
          Ну да. Однако, ожидается, что размер из-за присутствующего всегда слабого шума или из-за медленного изменения освещенности будет «гулять» гораздо меньше, чем при появлении в кадре чего-то движущегося.
            0

            Это допущение, которое может ничего не иметь общего под собой. Например, файл с одной пустой сценой с миганием (см статью) и без мигания различаются как 200 и 38MB. Я к тому, что чем больше у вам блэкбокс, тем больше вам надо расширять область допущений.

        +1
        Здравствуйте.

        Данный способ явно не подойдет для детекции области движения. Что же до детекции изменения сцены — подойти может. На практике — не всегда, поскольку вы столкнетесь с невозможностью отличить «дребезг» сцены от условного движения.

        В случае векторов, вы можете оценивать как интегральный показатель (как в статье), так и индивидуальные a*a + b*b по ячейкам сцены.

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

        На MV есть вполне себе статьи научные, например, такая.

        Впрочем, на stack overflow, ваш метод тоже обсуждается.
          0
          Уже довольно давно думаю как сделать для своего умного дома на Home Assistant, чтобы по камере (можно на Raspberry Pi) составлялся список людей, которые часто мелькают перед ней — но как-то дальше идеи не идёт — как думаете можно такое реализовать?
            0
            Можно, если добавите Movidius и сделаете на OpenVino, но я бы предложил взять Jetson Nano (если DeepStream) или Intel NUC, если, опять же, OpenVino использовать.

            Raspberry Pi — не релевантная платформа для стримингового Face ReID на выском FPS и HD-видео.
              0

              Если использовать Jetson Nano или другой микрокомпьютер, то какое программное решение позволит это сделать? Просто OpenVino мне кажется довольно общий ответ.
              И есть ли вариант для домашнего использования?

                0

                Я не знаю, какое конкретно ПО поможет вам это сделать — мои коллеги делают такое ПО сами на Nvidia Deepstream и нейросетевых моделях для детекции и реидентификации лиц, плюс трекинг и поиск по косинусной мере.


                В общем говоря, это совершенно другая песня.

          +2

          Я такую же задачу решил для себя.


          https://habr.com/ru/post/424191/


          У меня работает и на Zero плате. Загрузка процессора небольшая. 18650 4S4P хватает на неделю автономной работы если, конечно ездой не злоупотреблять (моторы много берут)


          Правда, по большей части, все это уже решено до нас. Python picamera.
          Но код у модуля у picamera не сложный. Все собственно камера делает (h.264).

            0
            В первой строке статьи ссылка на вашу статью.
              +1

              Да. Извините, не заметил.

            0
            *смотрит в шкаф*
            а на первой б-малинке с 256мб озу которая получится? всё думаю куда её приспособить…

            Only users with full accounts can post comments. Log in, please.