Разработка
NTA

Детектор движения на 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.

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

fg_mask = backSub.apply(frame)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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)

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

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

  • быстродействие детектора составляет около 1,5 кадров в секунду;

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

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

{ "author_name": "NTA", "author_type": "editor", "tags": [], "comments": 0, "likes": 1, "favorites": 2, "is_advertisement": false, "subsite_label": "dev", "id": 233862, "is_wide": true, "is_ugc": false, "date": "Wed, 14 Apr 2021 14:28:20 +0300", "is_special": false }
0
0 комментариев
Популярные
По порядку

Комментарии

null