Хочешь уверенно проходить IT-интервью?

Мы понимаем, как сложно подготовиться: стресс, алгоритмы, вопросы, от которых голова идёт кругом. Но с AI тренажёром всё гораздо проще.
💡 Почему Т1 тренажёр — это мастхэв?
- Получишь настоящую обратную связь: где затык, что подтянуть и как стать лучше
- Научишься не только решать задачи, но и объяснять своё решение так, чтобы интервьюер сказал: "Вау!".
- Освоишь все этапы собеседования, от вопросов по алгоритмам до диалога о твоих целях.
Зачем листать миллион туториалов? Просто зайди в Т1 тренажёр, потренируйся и уверенно удиви интервьюеров. Мы не обещаем лёгкой прогулки, но обещаем, что будешь готов!
Реклама. ООО «Смарт Гико», ИНН 7743264341. Erid 2VtzqwP8vqy
В OpenCV существует множество вариантов для трансляции видеопотока. Можно использовать один из них – IP-камеры, но с ними бывает довольно трудно работать. Так, некоторые IP-камеры не позволяют получить доступ к RTSP-потоку (англ. Real Time Streaming Protocol). Другие IP-камеры не работают с функцией OpenCV cv2.VideoCapture
. В конце концов, такой вариант может быть слишком дорогостоящим для ваших задач, особенно, если вы хотите построить сеть из нескольких камер.
Как отправлять видеопоток со стандартной веб-камеры с помощью OpenCV? Одним из удобных способов является использование протоколов передачи сообщений и соответствующих библиотек ZMQ и ImageZMQ.
Поэтому сначала мы кратко обсудим транспорт видеопотока вместе с ZMQ, библиотекой для асинхронной передачи сообщений в распределенных системах. Далее, мы реализуем два скрипта на Python:
- Клиент, который будет захватывать кадры с простой веб-камеры.
- Сервер, принимающий кадры и ищущий на них выбранные типы объектов (например, людей, собак и автомобили).
Для демонстрации работы узлов применяются четыре платы Raspberry Pi с подключенными модулями камер. На их примере мы покажем, как использовать дешевое оборудование в создании распределенной сети из камер, способных отправлять кадры на более мощную машину для дополнительной обработки.
Передача сообщений и ZMQ
Передача сообщений – парадигма программирования, традиционно используемая в многопроцессорных распределенных системах. Концепция предполагает, что один процесс может взаимодействовать с другими процессами через посредника – брокера сообщений (англ. message broker). Посредник получает запрос, а затем обрабатывает акт пересылки сообщения другому процессу/процессам. При необходимости брокер сообщений также отправляет ответ исходному процессу.
ZMQ является высокопроизводительной библиотекой для асинхронной передачи сообщений, используемой в распределенных системах. Этот пакет обеспечивает высокую пропускную способность и малую задержку. На основе ZMQ Джефом Бассом создана библиотека ImageZMQ, которую сам Джеф использует для компьютерного зрения на своей ферме вместе с теми же платами Raspberry Pi.

Начнем с того, что настроим клиенты и сервер.
Конфигурирование системы и установка необходимых пакетов

Сначала установим opencv и ZMQ. Чтобы избежать конфликтов, развертывание проведем в виртуальной среде:
$ workon <env_name> # например, py3cv4
$ pip install opencv-contrib-python
$ pip install zmq
$ pip install imutils
Теперь нам нужно клонировать репозиторий с ImageZMQ:
$ cd ~
$ git clone https://github.com/jeffbass/imagezmq.git
Далее, можно скопировать директорию с исходником или связать ее с вашим виртуальным окружением. Рассмотрим второй вариант:
$ cd ~/.virtualenvs/py3cv4/lib/python3.6/site-packages
$ ln -s ~/imagezmq/imagezmq imagezmq
Библиотеку ImageZMQ нужно установить и на сервер, и на каждый клиент.
Примечание: чтобы быть увереннее в правильности введенного пути, используйте дополнение через табуляцию.
Подготовка клиентов для ImageZMQ
В этом разделе мы осветим важное отличие в настройке клиентов.
Наш код будет использовать имя хоста клиента для его идентификации. Для этого достаточно и IP-адреса, но настройка имени хоста позволяет проще считать назначение клиента.
В нашем примере для определенности мы предполагаем, что вы используете Raspberry Pi с операционной системой Raspbian. Естественно, что клиент может быть построен и на другой ОС.
Чтобы сменить имя хоста, запустите терминал (это можно сделать через SSH-соединение) и введите команду raspi-config
:
$ sudo raspi-config
Вы увидите следующее окно терминала. Перейдите к пункту 2 Network Options.

На следующем шаге выберите опцию N1 Hostname.

На этом этапе задайте осмысленное имя хоста (например, pi-livingroom, pi-bedroom, pi-garage). Так вам будет легче ориентироваться в клиентах сети и сопоставлять имена и IP-адреса.

Далее, необходимо согласиться с изменениями и перезагрузить систему.
В некоторых сетях вы можете подключиться через SSH, не предоставляя IP-адрес явным образом:
$ ssh pi@pi-frontporch
Определение отношений сервер-клиент
Прежде чем реализовать стриминг потокового видео по сети, определим отношения клиентов и сервера. Для начала уточним терминологию:
- Клиент – устройство, отвечающее за захват кадров с веб-камеры с использованием OpenCV, а затем за отправку кадров на сервер.
- Сервер — компьютер, принимающий кадры от всех клиентов.
Конечно, и сервер, и клиент могут и принимать, и отдавать какие-то данные (не только видеопоток), но для нас важно следующее:
- Существует как минимум одна (а скорее всего, несколько) система, отвечающая за захват кадров (клиент).
- Существует только одна система, используемая для получения и обработки этих кадров (сервер).
Структура проекта
Структура проекта будет состоять из следующих файлов:
$ $ tree
.
├── MobileNetSSD_deploy.caffemodel
├── MobileNetSSD_deploy.prototxt
├── client.py
└── server.py
0 directories, 4 files
Два первых файла из списка соответствуют файлам предобученной нейросети Caffe MobileNet SSD для распознавания объектов. В репозитории по ссылке можно найти соответствующие файлы, чьи названия, правда, могут отличаться от приведенных (*.caffemodel
и deploy.prototxt
). Сервер (server.py
) использует эти файлы Caffe в DNN-модуле OpenCV.
Скрипт client.py
будет находиться на каждом устройстве, которое отправляет поток на сервер.
Реализация клиентского стримера на OpenCV
Начнем с реализации клиента. Что он будет делать:
- Захватывать видеопоток с камеры (USB или RPi-модуль).
- Отправлять кадры по сети через ImageZMQ.
Откроем файл client.py
и вставим следующий код:
# импортируем необходимые библиотеки
from imutils.video import VideoStream # захват кадров с камеры
import imagezmq
import argparse # обработка аргумента командной строки, содержащего IP-адрес сервера
import socket # получение имени хоста Raspberry Pi
import time # для учета задержки камеры перед отправкой кадров
# создаем парсер аргументов и парсим
ap = argparse.ArgumentParser()
ap.add_argument("-s", "--server-ip", required=True,
help="ip address of the server to which the client will connect")
args = vars(ap.parse_args())
# инициализируем объект ImageSender с адресом сокета сервера
sender = imagezmq.ImageSender(connect_to="tcp://{}:5555".format(
args["server_ip"]))
Назначение импортируемых модулей описано в комментариях. В последних строчках создается объект-отправитель, которому передаются IP-адрес и порт сервера. Указанный порт 5555
обычно не вызывает конфликтов.
Инициализируем видеопоток и начнем отправлять кадры на сервер.
# получим имя хоста, инициализируем видео поток,
# дадим датчику камеры прогреться
rpiName = socket.gethostname()
vs = VideoStream(usePiCamera=True).start()
#vs = VideoStream(src=0).start()
time.sleep(2.0) # задержка для начального разогрева камеры
while True:
# прочитать кадр с камеры и отправить его на сервер
frame = vs.read()
sender.send_image(rpiName, frame)
Теперь у нас есть объект VideoStream
, созданный для захвата фреймов с RPi-камеры. Если вы используете USB-камеру, раскомментируйте следующую строку и закомментируйте ту, что активна сейчас.
В этом месте вы также можете установить разрешение камеры. Мы будем использовать максимальное, так что аргумент не передастся. Если вы обнаружите задержку, надо уменьшить разрешение, выбрав одно из доступных значений, представленных в таблице. Например:
vs = VideoStream(usePiCamera=True, resolution=(320, 240)).start()
Для USB-камеры такой аргумент не предусмотрен. В следующей строке после считывания кадра можно изменить его размер:
frame = imutils.resize(frame, width=320)
В последних строках скрипта происходит захват и отправка кадров на сервер.
Реализация сервера
На стороне сервера необходимо обеспечить:
- Прием кадров от клиентов.
- Детектирование объектов на каждом из входящих кадров.
- Подсчет количества объектов для каждого из кадров.
- Отображение смонтированного кадра (панели), содержащего изображения от всех активных устройств.
Последовательно заполним файл с описанием сервера server.py
:
# импортируем необходимые библиотеки
from imutils import build_montages # монтаж всех входящих кадров
from datetime import datetime
import numpy as np
import imagezmq
import argparse
import imutils
import cv2
# создаем парсер аргументов и парсим их
ap = argparse.ArgumentParser()
ap.add_argument("-p", "--prototxt", required=True,
help="path to Caffe 'deploy' prototxt file")
ap.add_argument("-m", "--model", required=True,
help="path to Caffe pre-trained model")
ap.add_argument("-c", "--confidence", type=float, default=0.2,
help="minimum probability to filter weak detections")
ap.add_argument("-mW", "--montageW", required=True, type=int,
help="montage frame width")
ap.add_argument("-mH", "--montageH", required=True, type=int,
help="montage frame height")
args = vars(ap.parse_args())
Библиотека imutils
упрощает работу с изображениями (есть на GitHub и PyPi).
Пять аргументов, обрабатываемых с помощью парсера argparse
:
--prototxt
: путь к файлу прототипа глубокого изучения Caffe.--model
: путь к предообученной модели нейросети Caffe.--confidence
: порог достоверности для фильтрации случаев нечеткого обнаружения.--montageW
: количество столбцов для монтажа общего кадра, состоящего в нашем примере из 2х2 картинок (то есть montageW = 2) . Часть ячеек может быть пустой.--montageH
: аналогично предыдущему пункту — количество строк в общем кадре.
Вначале инициализируем объект ImageHub
для работы с детектором объектов. Последний построен на базе MobileNet Single Shot Detector.
imageHub = imagezmq.ImageHub()
# инициализируем список меток классов сети MobileNet SSD, обученной
# для детектирования, генерируем набор ограничивающих прямоугольников
# разного цвета для каждого класса
CLASSES = ["background", "aeroplane", "bicycle", "bird", "boat",
"bottle", "bus", "car", "cat", "chair", "cow", "diningtable",
"dog", "horse", "motorbike", "person", "pottedplant", "sheep",
"sofa", "train", "tvmonitor"]
# загружаем сериализованную модель Caffe с диска
print("[INFO] loading model...")
net = cv2.dnn.readNetFromCaffe(args["prototxt"], args["model"])
Объект ImageHub
используется сервером для приема подключений от каждой платы Raspberry Pi. По существу, для получения кадров по сети и отправки назад подтверждений здесь используются сокеты и ZMQ .
Предположим, что в системе безопасности мы отслеживаем только три класса подвижных объектов: собаки, люди и автомобили. Эти метки мы запишем в множество CONSIDER
, чтобы отфильтровать прочие неинтересные нам классы (стулья, растения и т. д.).
Кроме того, необходимо следить за активностью клиентов, проверяя время отправки тем или иным клиентом последнего кадра.
# инициализируем выбранный набор подсчитываемых меток классов,
# словарь-счетчик и словарь фреймов
CONSIDER = set(["dog", "person", "car"])
objCount = {obj: 0 for obj in CONSIDER}
frameDict = {}
# инициализируем словарь, который будет содержать информацию
# о том когда устройство было активным в последний раз
lastActive = {}
lastActiveCheck = datetime.now()
# храним ожидаемое число клиентов, период активности
# вычисляем длительность ожидания между проверкой
# на активность устройства
ESTIMATED_NUM_PIS = 4
ACTIVE_CHECK_PERIOD = 10
ACTIVE_CHECK_SECONDS = ESTIMATED_NUM_PIS * ACTIVE_CHECK_PERIOD
# назначаем ширину и высоту монтажного кадра
# чтобы просматривать потоки от всех клиентов вместе
mW = args["montageW"]
mH = args["montageH"]
print("[INFO] detecting: {}...".format(", ".join(obj for obj in
CONSIDER)))
Далее необходимо зациклить потоки, поступающие от клиентов и обработку данных на сервере.
# начинаем цикл по всем кадрам
while True:
# получаем имя клиента и кадр,
# подтверждаем получение
(rpiName, frame) = imageHub.recv_image()
imageHub.send_reply(b'OK')
# если устройства нет в словаре lastActive,
# это новое подключенное устройство
if rpiName not in lastActive.keys():
print("[INFO] receiving data from {}...".format(rpiName))
# записываем время последней активности для устройства,
# от которого мы получаем кадр
lastActive[rpiName] = datetime.now()
Итак, сервер забирает изображение в imageHub
, высылает клиенту сообщение о подтверждении получения. Принятое сервером сообщениеimageHub.recv_image
содержит имя хоста rpiName
и кадр frame
. Остальные строки кода нужны для учета активности клиентов.
Затем мы работаем с кадром, формируя блоб (о функции blobFromImage
читайте подробнее в посте pyimagesearch). Блоб передается нейросети для детектирования объектов.
Замечание: мы продолжаем рассматривать цикл, поэтому здесь и далее будьте внимательны с отступами в коде.
# изменяем размер кадра, чтобы ширина была не больше 400 пикселей,
# захватываем размеры кадров и создаем блоб
frame = imutils.resize(frame, width=400)
(h, w) = frame.shape[:2]
blob = cv2.dnn.blobFromImage(cv2.resize(frame, (300, 300)),
0.007843, (300, 300), 127.5)
# передаем блоб нейросети, получаем предсказания
net.setInput(blob)
detections = net.forward()
# сбрасываем число объектов для интересующего набора
objCount = {obj: 0 for obj in CONSIDER}
Теперь мы хотим пройтись по детектированным объектам, чтобы посчитать и выделить их цветными рамками:
# циклически обходим детектированные объекты
for i in np.arange(0, detections.shape[2]):
# извлекаем вероятность соответствующего предсказания
confidence = detections[0, 0, i, 2]
# отфильтруем слабые предсказания,
# гарантируя минимальную достоверность
if confidence > args["confidence"]:
# извлекаем индекс метки класса
idx = int(detections[0, 0, i, 1])
# проверяем, что метка класса в множестве интересных нам
if CLASSES[idx] in CONSIDER:
# подсчитываем детектированный объект
objCount[CLASSES[idx]] += 1
# вычисляем координаты рамки, ограничивающей объект
box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
(startX, startY, endX, endY) = box.astype("int")
# рисуем рамку вокруг объекта
cv2.rectangle(frame, (startX, startY), (endX, endY),
(255, 0, 0), 2)
Далее, аннотируем каждый кадр именем хоста и количеством объектов. Наконец, монтируем из нескольких кадров общую панель:
# отобразим имя клиента на кадре
cv2.putText(frame, rpiName, (10, 25),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
# отобразим число объектов на кадре
label = ", ".join("{}: {}".format(obj, count) for (obj, count) in
objCount.items())
cv2.putText(frame, label, (10, h - 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255,0), 2)
# обновим кадр в словаре кадров клиентов
frameDict[rpiName] = frame
# построим общий кадр из словаря кадров
montages = build_montages(frameDict.values(), (w, h), (mW, mH))
# покажем смонтированный кадр на экране
for (i, montage) in enumerate(montages):
cv2.imshow("Home pet location monitor ({})".format(i),
montage)
# детектируем нажатие какой-либо клавиши
key = cv2.waitKey(1) & 0xFF
Остался заключительный блок для проверки последних активностей всех клиентов. Эти операции особенно важны в системах безопасности, чтобы при отключении клиента вы не наблюдали неизменный последний кадр.
# если разница между текущим временем и временем последней активности
# больше порога, производим проверку
if (datetime.now() - lastActiveCheck).seconds > ACTIVE_CHECK_SECONDS:
# циклично обходим все ранее активные устройства
for (rpiName, ts) in list(lastActive.items()):
# удаляем клиент из словарей кадров и последних активных
# если устройство неактивно
if (datetime.now() - ts).seconds > ACTIVE_CHECK_SECONDS:
print("[INFO] lost connection to {}".format(rpiName))
lastActive.pop(rpiName)
frameDict.pop(rpiName)
# устанавливаем время последней активности
lastActiveCheck = datetime.now()
# если нажата клавиша `q`, выходим из цикла
if key == ord("q"):
break
# закрываем окна и освобождаем память
cv2.destroyAllWindows()
Запускаем стриминг видео с камер
Теперь, когда мы реализовали и клиент, и сервер, проверим их. Загрузим клиент на каждую плату Raspberry Pi с помощью SCP-протокола:
$ scp client.py pi@192.168.1.10:~
$ scp client.py pi@192.168.1.11:~
$ scp client.py pi@192.168.1.12:~
$ scp client.py pi@192.168.1.13:~
Проверьте, что на всех машинах установлены импортируемые клиентом или сервером библиотеки. Первым нужно запускать сервер. Сделать это можно следующей командой:
$ python server.py --prototxt MobileNetSSD_deploy.prototxt \
--model MobileNetSSD_deploy.caffemodel --montageW 2 --montageH 2
Далее запускаем клиенты, следуя инструкции (будьте внимательны, в вашей системе имена и адреса могут отличаться):
- Откройте SSH-соединение с клиентом:
ssh pi@192.168.1.10
- Запустите экран клиента:
screen
- Перейдите к профилю:
source ~/.profile
- Активируйте окружение:
workon py3cv4
- Установите ImageZMQ, следуя инструкциям библиотеки по установке
- Запустите клиент:
python client.py --server-ip 192.168.1.5
Ниже представлено демо-видео панели с процессом стриминга и распознавания объектов с четырех камер на Raspberry Pi.
Аналогичные решения из кластера камер и сервера можно использовать и для других задач, например:
- Распознавание лиц. Такую систему можно использовать в школах для обеспечения безопасности и автоматической оценки посещаемости.
- Робототехника. Объединяя несколько камер и компьютерное зрение, вы можете создать прототип системы автопилота.
- Научные исследования. Кластер из множества камер позволяет проводить исследования миграции птиц и животных. При этом можно автоматически обрабатывать фотографии и видео только в случае детектирования конкретного вида, а не просматривать видеопоток целиком.
Комментарии