prologo
Ora circola su Internet un video che mostra come il pilota automatico di Tesla vede la strada.
È da molto tempo che desidero trasmettere video arricchiti con un rilevatore e in tempo reale.
Il problema è che voglio trasmettere video dal Raspberry e le prestazioni del rilevatore di rete neurale su di esso lasciano molto a desiderare.
Chiavetta per computer neurale Intel
Ho considerato diverse soluzioni.
В
Anche se Intel fornisce convertitori per i principali framework, esistono numerose insidie.
Ad esempio, il formato della rete richiesta potrebbe essere incompatibile e, se è compatibile, alcuni livelli potrebbero non essere supportati sul dispositivo e, se sono supportati, potrebbero verificarsi errori durante il processo di conversione, di conseguenza otteniamo alcune cose strane in uscita.
In generale, se si desidera una sorta di rete neurale arbitraria, potrebbe non funzionare con NCS. Ho quindi deciso di provare a risolvere il problema utilizzando gli strumenti più diffusi e accessibili.
nuvola
L'ovvia alternativa a una soluzione hardware locale è passare al cloud.
Opzioni già pronte: i miei occhi si scatenano.
Tutti i leader:
... E decine di quelli meno conosciuti.
Scegliere tra questa varietà non è affatto facile.
E ho deciso di non scegliere, ma di avvolgere il buon vecchio schema di lavoro su OpenCV in Docker ed eseguirlo nel cloud.
Il vantaggio di questo approccio è la flessibilità e il controllo: puoi modificare la rete neurale, l'hosting, il server - in generale, qualsiasi capriccio.
Server
Iniziamo con un prototipo locale.
Tradizionalmente utilizzo Flask per API REST, OpenCV e rete MobileSSD.
Dopo aver installato le versioni attuali su Docker, ho scoperto che OpenCV 4.1.2 non funziona con Mobile SSD v1_coco_2018_01_28 e ho dovuto tornare alla comprovata 11/06_2017.
All'avvio del servizio carichiamo i nomi delle classi e della 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 una finestra mobile locale (su un laptop non molto giovane) ci vogliono 0.3 secondi, su Raspberry - 3.5.
Iniziamo 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 secondi, Lampone - 1.7.
Trasformare lo scarico del tensore in 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
ulteriormente
Un'opzione alternativa, in cui la maggior parte del lavoro viene spostata sul server: esso stesso circonda gli oggetti trovati e restituisce l'immagine finita.
Questa opzione è utile quando non vogliamo trascinare opencv sul server.
Docker
Raccogliamo l'immagine.
Il codice viene pettinato e pubblicato
Come piattaforma, prenderemo la stessa Debian Stretch di Raspberry: non ci allontaneremo dallo stack tecnologico collaudato.
È necessario installare flask, protobuf, request, opencv_python, scaricare Mobile SSD, codice server da Github e avviare il server.
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"]
Semplice
Pubblicazione su Docker Hub
I registri Docker si stanno moltiplicando a una velocità non inferiore a quella dei rilevatori di cloud.
Per non disturbarci, procederemo in modo conservativo
- Registrati
- Login:
login docker - Troviamo un nome significativo:
tag docker opencv-detect tprlab/opencv-detect-ssd - Carica l'immagine sul server:
docker push tprlab/opencv-detect-ssd
Lanciamo nel cloud
Anche la scelta su dove posizionare il contenitore è piuttosto ampia.
Tutti i big player (Google, Microsoft, Amazon) offrono una microistanza gratuita per il primo anno.
Dopo aver sperimentato Microsoft Azure e Google Cloud, ho optato per quest'ultimo perché è decollato più velocemente.
Non ho scritto istruzioni qui, poiché questa parte è molto specifica per il provider selezionato.
Ho provato diverse opzioni hardware,
Livelli bassi (condivisi e dedicati) - 0.4 - 0.5 secondi.
Auto più potenti - 0.25 - 0.3.
Bene, anche nel peggiore dei casi, le vincite sono tre volte, puoi provarci.
Video
Lanciamo un semplice streamer video OpenCV su Raspberry, rilevando tramite Google Cloud.
Per l'esperimento è stato utilizzato un file video filmato una volta 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 non otteniamo più di tre fotogrammi al secondo, tutto procede molto lentamente.
Se porti una macchina potente in GCloud, puoi rilevare 4-5 fotogrammi al secondo, ma la differenza è quasi invisibile alla vista, è comunque lento.
I costi del cloud e del trasporto non hanno nulla a che fare con questo; il rilevatore funziona su hardware normale e funziona ad una tale velocità.
Stick per computer neurale
Non ho potuto resistere e ho eseguito il benchmark su NCS.
La velocità del rilevatore era leggermente inferiore a 0.1 secondi, in ogni caso 2-3 volte più veloce della nuvola su una macchina debole, cioè 8-9 fotogrammi al secondo.
La differenza nei risultati è spiegata dal fatto che NCS utilizzava la versione Mobile SSD 2018_01_28.
PS Inoltre, gli esperimenti hanno dimostrato che una macchina desktop abbastanza potente con un processore I7 mostra risultati leggermente migliori e si è scoperto che è possibile spremere 10 fotogrammi al secondo.
grappolo
L'esperimento è andato oltre e ho installato il rilevatore su cinque nodi in Google Kubernetes.
I pod stessi erano deboli e ciascuno di essi non riusciva a elaborare più di 2 fotogrammi al secondo.
Ma se esegui un cluster con N nodi e analizzi i frame in N thread, con un numero sufficiente di nodi (5) puoi ottenere i 10 frame al secondo desiderati.
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 è successo:
Un po' meno veloce che con NCS, ma più vigoroso che in un flusso.
Il guadagno, ovviamente, non è lineare: ci sono sovrapposizioni per la sincronizzazione e la copia profonda delle immagini opencv.
conclusione
Nel complesso, l'esperimento ci permette di concludere che se ci provi, puoi farla franca con una semplice nuvola.
Ma un desktop potente o un hardware locale ti consentono di ottenere risultati migliori e senza trucchi.
riferimenti
Fonte: habr.com