Відео з хмарним детектором об'єктів на Raspberry Pi

Пролог

По мережі зараз гуляє відео – як автопілот Тесли бачить дорогу.

У мене давно свербіли руки транслювати відео, збагачене детектором, та й у реальному часі.

Відео з хмарним детектором об'єктів на Raspberry Pi

Проблема в тому, що транслювати відео я хочу з Raspberry, а продуктивність нейромережевого детектора залишає бажати кращого.

Intel Neural Computer Stick

Я розглядав різні варіанти рішення.

В минулій статті експериментував із Intel Neural Computer Stick. Залізниця потужна, але потребує свого формату мережі.

Незважаючи на те, що Інтел надає конвертери для основних фреймворків, тут є низка підводних каменів.

Наприклад, формат потрібної мережі може бути несумісний, а якщо сумісний, то якісь шари можуть не підтримуватись на девайсі, а якщо підтримуються, то в процесі конвертації можуть відбуватися помилки, в результаті яких на виході отримуємо якісь дивні речі.

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

Хмара

Очевидна альтернатива локально-хардварному рішенню піти в хмару.

Готових варіантів – очі розбігаються.

Усі лідери:

… І десятки менш відомих.

Вибрати серед цього різноманіття зовсім непросто.

І я вирішив не вибирати, а завернути стару добру робочу схему на OpenCV у докер і запустити його у хмарі.

Перевага такого підходу в гнучкості та контролі - можна поміняти нейромережа, хостинг, сервер - загалом, будь-яка примха.

Сервер

Почнемо із локального прототипу.

Традиційно я використовую Flask для REST API, OpenCV та MobileSSD мережу.

Поставивши на докер поточні версії, виявив, що OpenCV 4.1.2 не працює з Mobile SSD v1_coco_2018_01_28, і довелося відкотитися на перевірену 11_06_2017.

На старті сервісу завантажуємо імена класів та мережу:

def init():
    tf_labels.initLabels(dnn_conf.DNN_LABELS_PATH)
    return cv.dnn.readNetFromTensorflow(dnn_conf.DNN_PATH, dnn_conf.DNN_TXT_PATH)

На локальному докері (на не наймолодшому лаптопі) це займає 0.3 секунди, на Raspberry - 3.5.

Запускаємо розрахунок:

def inference(img):
    net.setInput(cv.dnn.blobFromImage(img, 1.0/127.5, (300, 300), (127.5, 127.5, 127.5), swapRB=True, crop=False))
    return net.forward()

Докер - 0.2 сек, Raspberry - 1.7.

Перетворюємо тензорний вихлоп на читальний json:

def build_detection(data, thr, rows, cols):
    ret = []
    for detection in data[0,0,:,:]:
        score = float(detection[2])
        if score > thr:
            cls = int(detection[1])
            a = {"class" : cls, "name" : tf_labels.getLabel(cls),  "score" : score}
            a["x"] = int(detection[3] * cols)
            a["y"] = int(detection[4] * rows)
            a["w"] = int(detection[5] * cols ) - a["x"]
            a["h"] = int(detection[6] * rows) - a["y"]
            ret.append(a)
    return ret

далі експортуємо цю операцію через Flask(На вході картинка, на виході - результати детектора в JSON).

Альтернативний варіант, у якому більше роботи перекладається на сервер: він сам обводить знайдені об'єкти та повертає готову картинку.

Такий варіант хороший там, де ми не хочемо тягнути opencv на сервер.

Докер

Збираємо образ.

Код зачесаний і викладений на Гітхаб, докер візьме його безпосередньо звідти.

Як платформа візьмемо той же Debian Stretch, що і на Raspberry - не будемо уникати перевіреного техстека.

Треба поставити flask, protobuf, requests, opencv_python, завантажити Mobile SSD, код сервера з Гітхаба і запустити сервер.

FROM python:3.7-stretch

RUN pip3 install flask
RUN pip3 install protobuf
RUN pip3 install requests
RUN pip3 install opencv_python

ADD http://download.tensorflow.org/models/object_detection/ssd_mobilenet_v1_coco_11_06_2017.tar.gz /
RUN tar -xvf /ssd_mobilenet_v1_coco_11_06_2017.tar.gz

ADD https://github.com/tprlab/docker-detect/archive/master.zip /
RUN unzip /master.zip

EXPOSE 80

CMD ["python3", "/docker-detect-master/detect-app/app.py"]

простий клієнт для детектора на основі потреб.

Публікація на Docker Hub

Реєстри докеру розмножуються зі швидкістю не меншою, ніж хмарні детектори.

Щоб не морочитися, ми консервативно підемо через ДокерХаб.

  1. реєструємося
  2. Авторизуємося:
    вхід докера
  3. Придумаємо змістовне ім'я:
    docker tag opencv-detect tprlab/opencv-detect-ssd
  4. Завантажуємо образ на сервер:
    docker push tprlab/opencv-detect-ssd

Запускаємо у хмарі

Вибір, де запустити контейнер, теж дуже широкий.

Усі великі гравці (Гугл, Мікрософт, Амазон) пропонують мікроінстанс безкоштовно у перший рік.
Поексперементувавши з Microsoft Azure та Google Cloud, зупинився на останньому тому, що швидше злетіло.

Не став тут писати інструкцію, оскільки ця частина дуже специфічна для обраного провайдера.

Спробував різні варіанти заліза,
Низькі рівні (shared та виділені) - 0.4 - 0.5 секунди.
Машини потужніші - 0.25 - 0.3.
Що ж, навіть у гіршому випадку виграш утричі, можна спробувати.

Відео

Запускаємо простий OpenCV відеостример на Raspberry, детектируя через Google Cloud.
Для експерименту використали відеофайл, колись знятий на випадковому перехресті.


def handle_frame(frame):
    return detect.detect_draw_img(frame)
       
def generate():
    while True:
        rc, frame = vs.read()
        outFrame = handle_frame(frame)
        if outFrame is None:
            (rc, outFrame) = cv.imencode(".jpg", frame)
        yield(b'--framern' b'Content-Type: image/jpegrnrn' + bytearray(outFrame) + b'rn')

@app.route("/stream")
def video_feed():
    return Response(generate(), mimetype = "multipart/x-mixed-replace; boundary=frame")

З детектором виходить не більше трьох кадрів за секунду, все йде дуже повільно.
Якщо в GCloud взяти потужну машину, можна детектувати 4-5 кадрів на секунду, але різниця оком практично непомітна, все одно повільно.

Відео з хмарним детектором об'єктів на Raspberry Pi

Хмара та транспортні витрати тут не до того, на звичайному залозі детектор і працює з такою швидкістю.

Neural Computer Stick

Не втримався та прогнав бенчмарк на NCS.

Швидкість детектора була трохи повільніше 0.1 секунди, у будь-якому випадку в 2-3 рази швидше за хмари на слабкій машині, тобто 8-9 кадрів в секунду.

Відео з хмарним детектором об'єктів на Raspberry Pi

Різниця в результатах пояснюється тим, що NCS запускався Mobile SSD версії 2018_01_28.

PS Крім того, експерименти показали, що досить потужна десктопна машина з I7 процесором показує найкращі результати і на ній виявилося можливо вичавити 10 кадрів в секунду.

кластер

Експеримент пішов далі і я поставив детектор на п'яти вузлах Google Kubernetes.
Самі по собі поди були слабкі і кожен із них не міг обробити більше 2-х кадрів на секунду.
Але якщо запустити кластер на N вузлів і розбирати кадри N потоків - то при достатній кількості вузлів (5) можна досягти бажаних 10 кадрів в секунду.

def generate():
    while True:
        rc, frame = vs.read()
        if frame is not None:
            future = executor.submit(handle_frame, (frame.copy()))
            Q.append(future)

        keep_polling = len(Q) > 0
        while(keep_polling):            
            top = Q[0]
            if top.done():
                outFrame = top.result()
                Q.popleft()
                if outFrame:
                    yield(b'--framern' b'Content-Type: image/jpegrnrn' + bytearray(outFrame) + b'rn')
                keep_polling = len(Q) > 0
            else:
                keep_polling = len(Q) >= M

Ось що вийшло:

Відео з хмарним детектором об'єктів на Raspberry Pi

Трохи не так швидко як з NCS, але бадьоріше ніж в один потік.

Виграш, звичайно, не лінійний - вистрілюють накладки на синхронізацію та глибоке копіювання картинок opencv.

Висновок

Загалом експеримент дозволяє зробити висновок, що, якщо постаратися, можна викрутитися з простою хмарою.

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

Посилання

Джерело: habr.com

Додати коментар або відгук