Prólogo
Um vídeo está circulando na Internet mostrando como o piloto automático da Tesla vê a estrada.
Há muito tempo que estou ansioso para transmitir vídeo enriquecido com detector e em tempo real.
O problema é que quero transmitir vídeo do Raspberry, e o desempenho do detector de rede neural nele deixa muito a desejar.
Stick de computador neural Intel
Considerei soluções diferentes.
В
Embora a Intel forneça conversores para as principais estruturas, existem várias armadilhas.
Por exemplo, o formato da rede necessária pode ser incompatível e, se for compatível, algumas camadas podem não ser suportadas no dispositivo e, se forem suportadas, podem ocorrer erros durante o processo de conversão, como resultado dos quais obtemos algumas coisas estranhas na saída.
Em geral, se você deseja algum tipo de rede neural arbitrária, ela pode não funcionar com o NCS. Portanto, decidi tentar resolver o problema utilizando as ferramentas mais difundidas e acessíveis.
Cloud
A alternativa óbvia para uma solução de hardware local é ir para a nuvem.
Opções prontas - meus olhos correm arregalados.
Todos os líderes:
... E dezenas de outros menos conhecidos.
Escolher entre esta variedade não é nada fácil.
E decidi não escolher, mas embrulhar o bom e velho esquema de trabalho do OpenCV no Docker e executá-lo na nuvem.
A vantagem dessa abordagem é a flexibilidade e o controle - você pode alterar a rede neural, a hospedagem, o servidor - em geral, qualquer capricho.
Servidor
Vamos começar com um protótipo local.
Tradicionalmente eu uso Flask para API REST, OpenCV e rede MobileSSD.
Depois de instalar as versões atuais no Docker, descobri que o OpenCV 4.1.2 não funciona com Mobile SSD v1_coco_2018_01_28 e tive que reverter para o comprovado 11/06_2017.
No início do serviço, carregamos os nomes das classes e da rede:
def init():
tf_labels.initLabels(dnn_conf.DNN_LABELS_PATH)
return cv.dnn.readNetFromTensorflow(dnn_conf.DNN_PATH, dnn_conf.DNN_TXT_PATH)
Em um docker local (em um laptop não muito jovem), leva 0.3 segundos, no Raspberry - 3.5.
Vamos começar 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 seg, Raspberry - 1.7.
Transformando a exaustão do tensor em json legível:
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
Mais
Uma opção alternativa, em que mais trabalho é transferido para o servidor: ele próprio circula os objetos encontrados e retorna a imagem finalizada.
Esta opção é boa quando não queremos arrastar o opencv para o servidor.
Docker
Coletamos a imagem.
O código é penteado e postado em
Como plataforma, usaremos o mesmo Debian Stretch do Raspberry - não nos desviaremos da pilha de tecnologia comprovada.
Você precisa instalar flask, protobuf, requests, opencv_python, baixar Mobile SSD, código do servidor do 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"]
Simples
Publicando no Docker Hub
Os registros Docker estão se multiplicando a uma velocidade não menor que a dos detectores de nuvem.
Para não incomodar, iremos de forma conservadora
- Registro
- Conecte-se:
login da janela de encaixe - Vamos encontrar um nome significativo:
tag docker opencv-detect tprlab/opencv-detect-ssd - Carregue a imagem no servidor:
docker push tprlab/opencv-detect-ssd
Lançamos na nuvem
A escolha de onde executar o contêiner também é bastante ampla.
Todos os grandes players (Google, Microsoft, Amazon) oferecem uma microinstância gratuitamente durante o primeiro ano.
Depois de experimentar o Microsoft Azure e o Google Cloud, optei pelo último porque decolou mais rápido.
Não escrevi instruções aqui, pois esta parte é muito específica para o fornecedor selecionado.
Eu tentei diferentes opções de hardware,
Níveis baixos (compartilhados e dedicados) - 0.4 - 0.5 segundos.
Carros mais potentes - 0.25 - 0.3.
Bem, mesmo na pior das hipóteses, os ganhos são três vezes, você pode tentar.
Vídeo
Lançamos um streamer de vídeo OpenCV simples no Raspberry, detectando através do Google Cloud.
Para o experimento, foi utilizado um arquivo de vídeo que já foi filmado em um cruzamento aleatório.
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")
Com o detector não conseguimos mais que três frames por segundo, tudo acontece muito devagar.
Se você colocar uma máquina poderosa no GCloud, poderá detectar de 4 a 5 quadros por segundo, mas a diferença é quase invisível a olho nu, ainda é lenta.
Os custos de nuvem e transporte não têm nada a ver com isso: o detector funciona em hardware comum e funciona a essa velocidade.
Computador Neural Stick
Não resisti e fiz o benchmark no NCS.
A velocidade do detector foi ligeiramente mais lenta que 0.1 segundos, em qualquer caso, 2 a 3 vezes mais rápida que a nuvem em uma máquina fraca, ou seja, 8 a 9 quadros por segundo.
A diferença nos resultados é explicada pelo fato de o NCS estar executando a versão 2018_01_28 do Mobile SSD.
PS Além disso, experimentos mostraram que uma máquina desktop bastante poderosa com processador I7 apresenta resultados um pouco melhores e foi possível extrair 10 quadros por segundo nela.
Cluster
O experimento foi além e instalei o detector em cinco nós do Google Kubernetes.
Os próprios pods eram fracos e cada um deles não conseguia processar mais de 2 quadros por segundo.
Mas se você executar um cluster com N nós e analisar quadros em N threads, com um número suficiente de nós (5) poderá atingir os 10 quadros por segundo desejados.
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
Veja o que aconteceu:
Um pouco menos rápido do que com NCS, mas mais vigoroso do que em um stream.
O ganho, claro, não é linear - existem sobreposições para sincronização e cópia profunda de imagens opencv.
Conclusão
No geral, o experimento nos permite concluir que, se você tentar, poderá escapar com uma nuvem simples.
Mas um desktop poderoso ou hardware local permite obter melhores resultados e sem truques.
referências
Fonte: habr.com