пралог
Па сетцы зараз гуляе відэа - як аўтапілот Теслы бачыць дарогу.
У мяне даўно свярбелі рукі трансляваць відэа, узбагачанае дэтэктарам, ды і ў рэальным часе.

Праблема ў тым, што трансляваць відэа я хачу з 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
далей (на ўваходзе карцінка, на выхадзе - вынікі дэтэктара ў 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"]
просты на аснове requests.
Публікацыя на Docker Hub
Рэестры докера размнажаюцца з хуткасцю не меншай, чым хмарныя дэтэктары.
Каб не затлумляцца, мы кансерватыўна пойдзем праз .
- Рэгіструемся
- Аўтарызуемся:
docker login - Прыдумаем змястоўнае імя:
docker tag opencv-detect tprlab/opencv-detect-ssd - Загружаем выяву на сервер:
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 кадраў у секунду, але розніца вокам практычна незаўважная, усё роўна павольна.

Воблака і транспартныя выдаткі тут не прычым, на звычайным жалезе дэтэктар і працуе з такой хуткасцю.
Neural Computer Stick
Не ўтрымаўся і прагнаў бенчмарк на NCS.
Хуткасць дэтэктара была крыху павольней 0.1 секунды, у любым выпадку ў 2-3 разы хутчэй аблокі на слабой машыне, т.е 8-9 кадраў у секунду.

Розніца ў выніках тлумачыцца тым, што на 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
Вось што атрымалася:

Трохі не так жвава як з NCS, але бадзёрыя чым у адзін струмень.
Выйгрыш, вядома, не лінены – выстрэльваюць накладкі на сінхранізацыю і глыбокае капіраванне малюнкаў opencv.
Заключэнне
У цэлым, эксперымент дазваляе зрабіць выснову, што, калі паспрабаваць, можна выкруціцца з простым воблакам.
Але магутны дэсктоп або лакальная жалязяка дазваляюць дамагчыся лепшых вынікаў, прычым без усялякіх хітрыкаў.
Спасылкі
Крыніца: habr.com
