Детектор движения на Raspberry Pi

На основе одноплатного микрокомпьютера можно собрать 3D-принтер, построить медиацентр и даже разработать настольную игру. Такие компьютеры можно применить и для построения систем безопасности, в частности, систем видеонаблюдения. Пользуясь возможностями микрокомпьютера Raspberry Pi и алгоритмами машинного зрения, разработаем простой детектор движения.

Для построения детектора движения будем использовать микрокомпьютер Raspberry Pi 3 Model B, оснащенный четырехъядерным процессором ARMv7 и оперативной памятью объемом 1 Гбайт. Ещё нам потребуется карта памяти MicroSD, блок питания с разъемом MicroUSB и веб-камера. Будем считать, что на компьютер уже установлена операционная система Raspberry Pi OS и настроено подключение к Интернет.

Перед написанием скрипта необходимо пройти несколько подготовительных шагов.

Обновляем программные пакеты и устанавливаем библиотеку opencv.

sudo apt-get update sudo apt-get upgrade sudo apt-get install python3-opencv

Переходим в домашний каталог, создаем папку проекта motion_detector, переходим в эту папку, в ней создаем папку images для сохранения кадров с обнаруженным движением.

cd ~ mkdir motion_detector cd motion_detector mkdir images

Создаем пустой файл скрипта simple_detector.py и задаем права на его запуск.

touch simple_detector.py chmod +x simple_detector.py

Далее переходим к разработке скрипта для обнаружения движения.

Сначала задаем путь к интерпретатору Python и импортируем необходимые библиотеки.

#!/usr/bin/python3 import cv2 import numpy as np from datetime import datetime

Создаем экземпляр класса для отсечения фона.

backSub = cv2.createBackgroundSubtractorMOG2(50, 16, True)

Инициируем видеозахват с камеры и устанавливаем разрешение кадра.

cap = cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)

Захват и обработку изображений будем выполнять в бесконечном цикле. Каждая итерация цикла начинается с захвата изображения с камеры.

while True: _, frame = cap.read()

Изображение frame — тензор размерности 720x1280x3.

Детектор движения на Raspberry Pi

Далее отсекаем фон.

fg_mask = backSub.apply(frame)

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

Детектор движения на Raspberry Pi

Преобразуем это изображение в черно-белое таким образом, чтобы тени также были отнесены к движущимся объектам.

_, mask_thr = cv2.threshold(fg_mask, 100, 255, 0)

На изображении могут быть артефакты в виде мелких точек, вызванные изменениями освещенности, атмосферными осадками, отражениями разных источников света. Для исключения таких артефактов применим к изображению операцию размыкания (opening) с квадратным ядром 5×5. В результате изображение будет очищено от мелких артефактов.

kernel_open = np.ones((5,5), np.uint8) mask_open = cv2.morphologyEx(mask_thr, cv2.MORPH_OPEN, kernel_open

Также применяем операцию замыкания (closing) с квадратным ядром 9×9. Операция замыкания позволяет объединить близко расположенные области белого цвета.

kernel_close = np.ones((9,9), np.uint8) mask_close = cv2.morphologyEx(mask_open, cv2.MORPH_CLOSE, kernel_close)

Результат двух последних действий приведен ниже.

Детектор движения на Raspberry Pi

Далее выполняем поиск контуров.

_, contours, _ = cv2.findContours(mask_close, cv2.RETR_EXTERNAL,\ cv2.CHAIN_APPROX_SIMPLE)

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

area_threshold = 100 contours_sel = [cnt for cnt in contours if cv2.contourArea(cnt) > area_threshold]

После этой операции контуры малой площади исключаются из процесса обработки.

Детектор движения на Raspberry Pi

Оцениваем отношение суммарной площади контуров к площади всего кадра.

total_area = 0 for cnt in contours_sel: total_area += cv2.contourArea(cnt) rel_area = total_area / (frame.shape[0] * frame.shape[1]) * 100

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

motion_threshold = 0.5 if rel_area > motion_threshold: frame_boxes = frame.copy() for cnt in contours_sel: x,y,w,h = cv2.boundingRect(cnt) cv2.rectangle(frame_boxes, (x, y), (x + w, y + h), (0, 0, 255), 2) dt = datetime.now() dt_image = dt.strftime('%d.%m.%Y %H:%M:%S.%f')[:-3] cv2.putText(frame_boxes, dt_image, (20,40),\ cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 2) dt_file = dt.strftime('%Y-%m-%d_%H-%M-%S.%f')[:-3] fname_out = 'images/' + dt_file + '.jpg' cv2.imwrite(fname_out, frame_boxes)

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

Детектор движения на Raspberry Pi

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

  • быстродействие детектора составляет около 1,5 кадров в секунду;
  • за одни сутки накапливается около 5 тысяч изображений объемом до 700 Мбайт, таким образом, карта памяти объемом 32 Гбайта позволит хранить кадры с признаками движения приблизительно за один месяц работы системы.

Пример работы детектора приведен на видеозаписи. Cкрипт детектора выложен в репозитории github.com/mporuchikov/motion_detector.

44
3 комментария

Прикольная идея.
Было бы интересно узнать какой объём ресурсов raspbery остаются доступными, чтоб прикрутить функционал.

1
Ответить
Автор

Шестичасовой эксперимент показал, что при работе скрипта средняя загрузка процессора на Raspberry Pi 3 Model B составляет около 44%. Таким образом, более половины ресурсов процессора остаются свободными.

1
Ответить