Prologo
In rete gira un video - come l'autopilota della Tesla percepisce la strada.
Da tempo ho voglia di trasmettere un video arricchito da un detector, e anche in tempo reale.

Il problema è che voglio trasmettere video con Raspberry, ma le prestazioni del detector basato su reti neurali sono insoddisfacenti.
Intel Neural Computer Stick
Ho considerato diverse opzioni di soluzione.
In ho sperimentato con Intel Neural Computer Stick. È un dispositivo potente, ma ha bisogno del proprio formato di rete.
Nonostante Intel fornisca convertitori per i principali framework, ci sono diversi punti critici qui.
Ad esempio, il formato della rete necessaria potrebbe non essere compatibile, e se è compatibile, alcuni strati potrebbero non essere supportati sul dispositivo, e se sono supportati, durante il processo di conversione potrebbero verificarsi errori, il che ci porta a risultati strani.
Insomma, se si vuole una rete neurale arbitraria, con NCS potrebbe non funzionare. Perciò, ho deciso di provare a risolvere il problema tramite gli strumenti più popolari e accessibili.
Cloud
Un'alternativa ovvia alla soluzione hardware locale è passare al cloud.
Le opzioni pronte sono davvero tante.
Tutti i leader:
… E decine di altri meno conosciuti.
Non è affatto semplice scegliere tra questa varietà.
Ho deciso di non scegliere, ma di riutilizzare un vecchio schema funzionale su OpenCV nel docker e farlo girare nel cloud.
Il vantaggio di questo approccio è la flessibilità e il controllo: si può cambiare la rete neurale, l'hosting, il server — insomma, qualsiasi esigenza.
Server
Iniziamo con un prototipo locale.
Tradizionalmente utilizzo Flask per l’API REST, OpenCV e la rete MobileSSD.
Installando le versioni correnti su docker, ho scoperto che OpenCV 4.1.2 non funziona con Mobile SSD v1_coco_2018_01_28 e ho dovuto tornare alla collaudata 11_06_2017.
All'avvio del servizio carichiamo i nomi delle classi e la rete:
def init():
tf_labels.initLabels(dnn_conf.DNN_LABELS_PATH)
return cv.dnn.readNetFromTensorflow(dnn_conf.DNN_PATH, dnn_conf.DNN_TXT_PATH)
Su docker locale (su un laptop non proprio nuovo) ci vogliono 0.3 secondi, su Raspberry 3.5.
Avviamo il calcolo:
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()
Docker — 0.2 sec, Raspberry — 1.7.
Convertiamo l'output tensoriale in un json leggibile:
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
Avanti (l'immagine in ingresso, i risultati del rilevatore in json in uscita).
Un'alternativa in cui più lavoro viene trasferito sul server: esso stesso contorna gli oggetti trovati e restituisce l'immagine pronta.
Questa opzione è buona dove non vogliamo caricare opencv sul server.
Docker
Stiamo raccogliendo l'immagine.
Il codice è stato strutturato e pubblicato su , docker lo prenderà direttamente da lì.
Utilizzeremo la stessa piattaforma Debian Stretch che abbiamo su Raspberry — non ci allontaneremo dalla tecnologia provata.
Dobbiamo installare flask, protobuf, requests, opencv_python, scaricare Mobile SSD, il codice del server da GitHub e avviare il server.
DA python:3.7-stretch
ESEGUI pip3 install flask
ESEGUI pip3 install protobuf
ESEGUI pip3 install requests
ESEGUI pip3 install opencv_python
AGGIUNGI http://download.tensorflow.org/models/object_detection/ssd_mobilenet_v1_coco_11_06_2017.tar.gz /
ESEGUI tar -xvf /ssd_mobilenet_v1_coco_11_06_2017.tar.gz
AGGIUNGI https://github.com/tprlab/docker-detect/archive/master.zip /
ESEGUI unzip /master.zip
ESPONI 80
CMD ["python3", "/docker-detect-master/detect-app/app.py"]
Semplice basato su requests.
Pubblicazione su Docker Hub
I registri Docker proliferano a una velocità non inferiore a quella dei rilevatori cloud.
Per non complicarci la vita, adotteremo un approccio conservativo tramite .
- Registrati
- Autenticazione:
docker login - Inventiamo un nome significativo:
docker tag opencv-detect tprlab/opencv-detect-ssd - Carichiamo l'immagine sul server:
docker push tprlab/opencv-detect-ssd
Avviamo nel cloud
La scelta su dove eseguire il contenitore è anch'essa piuttosto ampia.
Tutti i grandi attori (Google, Microsoft, Amazon) offrono micro istanze gratuite per il primo anno.
Dopo aver sperimentato con Microsoft Azure e Google Cloud, ho scelto quest'ultimo—perché si è avviato più rapidamente.
Non ho scritto qui istruzioni, poiché questa parte è molto specifica per il provider scelto.
Ho provato diverse opzioni hardware,
Livelli bassi (shared e dedicati) — 0,4 — 0,5 secondi.
Macchine più potenti — 0,25 — 0,3.
Beh, anche nel peggiore dei casi, il guadagno è di tre volte, si può provare.
Video
Avviamo un semplice flusso video OpenCV su Raspberry, rilevando tramite Google Cloud.
Per l'esperimento è stato utilizzato un file video registrato in un incrocio casuale.
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")
Con il rilevatore si ottiene al massimo tre fotogrammi al secondo, tutto procede molto lentamente.
Se in GCloud si prende una macchina potente, si possono rilevare 4-5 fotogrammi al secondo, ma la differenza agli occhi è praticamente impercettibile, è comunque lento.

Il cloud e i costi di trasporto non centrano nulla, su hardware normale il rilevatore funziona a quella velocità.
Neural Computer Stick
Non ho resistito e ho eseguito un benchmark su NCS.
La velocità del rilevatore era poco più di 0,1 secondi, in ogni caso da 2 a 3 volte più veloce del cloud su una macchina debole, cioè 8-9 fotogrammi al secondo.

La differenza nei risultati è spiegata dal fatto che su NCS è stato eseguito il Mobile SSD della versione 2018_01_28.
P.S. Inoltre, gli esperimenti hanno dimostrato che una macchina desktop sufficientemente potente con processore I7 mostra risultati leggermente migliori e riesce a ottenere 10 fotogrammi al secondo.
Cluster
L'esperimento è andato oltre e ho installato il rilevatore su cinque nodi in Google Kubernetes.
I container stessi erano deboli e ognuno di essi non poteva elaborare più di 2 fotogrammi al secondo.
Ma se si avvia un cluster su N nodi e si elaborano i fotogrammi in N thread — allora, con un numero sufficiente di nodi (5), è possibile raggiungere i desiderati 10 fotogrammi al secondo.
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
Ecco cosa è uscito:

Un po' meno veloce rispetto a NCS, ma più reattivo rispetto a un singolo thread.
Il guadagno, ovviamente, non è lineare — si verificano ritardi legati alla sincronizzazione e alla copia profonda delle immagini in OpenCV.
Conclusione
In generale, l'esperimento consente di concludere che, se ci si impegna, è possibile cavarsela con un semplice cloud.
Tuttavia, un desktop potente o un hardware locale possono offrire risultati migliori, senza bisogno di alcun trucco.
Link
Fonte: habr.com
