Prólogo
Agora circula por Internet un vídeo que mostra como o piloto automático de Tesla ve a estrada.
Levo moito tempo ansioso por emitir vídeo enriquecido cun detector, e en tempo real.
O problema é que quero transmitir vídeo desde Raspberry, e o rendemento do detector de rede neuronal nel deixa moito que desexar.
Intel Neural Computer Stick
Considerei diferentes solucións.
В
Aínda que Intel ofrece conversores para marcos principais, hai unha serie de trampas.
Por exemplo, o formato da rede requirida pode ser incompatible e, se é compatible, é posible que algunhas capas non sexan compatibles no dispositivo e, se son compatibles, poden producirse erros durante o proceso de conversión, como resultado do cal obtemos cousas estrañas na saída.
En xeral, se queres algún tipo de rede neuronal arbitraria, é posible que non funcione con NCS. Por iso, decidín intentar resolver o problema utilizando as ferramentas máis estendidas e accesibles.
Nube
A alternativa obvia a unha solución de hardware local é ir á nube.
Opcións preparadas: os meus ollos están locos.
Todos os líderes:
... E ducias de menos coñecidos.
Elixir entre esta variedade non é nada sinxelo.
E decidín non escoller, senón envolver o bo vello esquema de traballo en OpenCV en Docker e executalo na nube.
A vantaxe deste enfoque é a flexibilidade e o control - pode cambiar a rede neuronal, aloxamento, servidor - en xeral, calquera capricho.
Servidor
Comecemos cun prototipo local.
Tradicionalmente uso Flask para REST API, OpenCV e rede MobileSSD.
Despois de instalar as versións actuais en Docker, descubrín que OpenCV 4.1.2 non funciona con Mobile SSD v1_coco_2018_01_28 e tiven que volver ao probado 11/06_2017.
Ao inicio do servizo, cargamos os nomes das clases e a rede:
def init():
tf_labels.initLabels(dnn_conf.DNN_LABELS_PATH)
return cv.dnn.readNetFromTensorflow(dnn_conf.DNN_PATH, dnn_conf.DNN_TXT_PATH)
Nun docker local (nun portátil non moi novo) leva 0.3 segundos, en Raspberry - 3.5.
Imos comezar o cálculo:
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 segundos, Raspberry - 1.7.
Convertendo o escape tensor en json lexible:
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
Ademais
Unha opción alternativa, na que se traslada máis traballo ao servidor: el mesmo rodea os obxectos atopados e devolve a imaxe rematada.
Esta opción é boa onde non queremos arrastrar opencv ao servidor.
Docker
Recollemos a imaxe.
O código está peiteado e publicado
Como plataforma, adoptaremos o mesmo Debian Stretch que en Raspberry; non nos desviaremos da probada pila tecnolóxica.
Debe instalar flask, protobuf, requests, opencv_python, descargar Mobile SSD, código do servidor de Github e iniciar o servidor.
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"]
Simple
Publicación en Docker Hub
Os rexistros Docker multiplícanse a unha velocidade non inferior á dos detectores de nubes.
Para non molestar, imos pasar de forma conservadora
- Rexístrate
- Acceder:
inicio de sesión docker - Imos buscar un nome significativo:
etiqueta docker opencv-detect tprlab/opencv-detect-ssd - Cargue a imaxe ao servidor:
docker push tprlab/opencv-detect-ssd
Lanzamos na nube
A elección de onde executar o recipiente tamén é bastante ampla.
Todos os grandes xogadores (Google, Microsoft, Amazon) ofrecen unha microinstancia de balde durante o primeiro ano.
Despois de experimentar con Microsoft Azure e Google Cloud, decidín este último porque despegou máis rápido.
Non escribín instrucións aquí, xa que esta parte é moi específica para o provedor seleccionado.
Probei diferentes opcións de hardware,
Niveis baixos (compartidos e dedicados) - 0.4 - 0.5 segundos.
Coches máis potentes - 0.25 - 0.3.
Ben, mesmo no peor dos casos, as ganancias son tres veces, podes probar.
Vídeo
Lanzamos un sinxelo emisión de vídeo OpenCV en Raspberry, que se detecta a través de Google Cloud.
Para o experimento, utilizouse un ficheiro de vídeo que unha vez foi filmado nunha intersección aleatoria.
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")
Co detector non chegamos máis de tres fotogramas por segundo, todo vai moi lentamente.
Se levas unha máquina potente a GCloud, podes detectar 4-5 fotogramas por segundo, pero a diferenza é case invisible para o ollo, aínda é lenta.
A nube e os custos de transporte non teñen nada que ver con iso; o detector funciona con hardware común e funciona a tal velocidade.
Stick de ordenador neural
Non puiden resistirme e executei a referencia en NCS.
A velocidade do detector foi lixeiramente máis lenta que 0.1 segundos, en calquera caso 2-3 veces máis rápida que a nube nunha máquina débil, é dicir, 8-9 cadros por segundo.
A diferenza de resultados explícase polo feito de que NCS estaba a executar Mobile SSD versión 2018_01_28.
PD Ademais, os experimentos demostraron que unha máquina de escritorio bastante potente cun procesador I7 mostra resultados lixeiramente mellores e resultou posible espremer 10 cadros por segundo nela.
Clúster
O experimento foi máis alá e instalei o detector en cinco nodos de Google Kubernetes.
As vainas eran débiles e cada unha delas non podía procesar máis de 2 fotogramas por segundo.
Pero se executas un clúster con N nós e analizas cadros en N fíos, entón cun número suficiente de nós (5) podes acadar os 10 fotogramas por segundo desexados.
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
Aquí tes o que pasou:
Un pouco menos rápido que con NCS, pero máis vigoroso que nun fluxo.
A ganancia, por suposto, non é lineal: hai superposicións para a sincronización e a copia profunda de imaxes opencv.
Conclusión
En xeral, o experimento permítenos concluír que se o intentas, podes saír cunha simple nube.
Pero un potente escritorio ou hardware local permítelle conseguir mellores resultados, e sen ningún truco.
referencias
Fonte: www.habr.com