Vídeo del detector de objetos en la nube en Raspberry Pi

Prólogo

Actualmente circula en Internet un vídeo que muestra cómo el piloto automático de Tesla ve la carretera.

Hace tiempo que tengo ganas de emitir vídeo enriquecido con un detector y en tiempo real.

Vídeo del detector de objetos en la nube en Raspberry Pi

El problema es que quiero transmitir video desde Raspberry y el rendimiento del detector de red neuronal deja mucho que desear.

Dispositivo de computadora neuronal Intel

Consideré diferentes soluciones.

В último artículo Experimenté con Intel Neural Computer Stick. El hardware es potente, pero requiere su propio formato de red.

Aunque Intel proporciona convertidores para los principales sistemas, existen varios obstáculos.

Por ejemplo, el formato de la red requerida puede ser incompatible y, si es compatible, es posible que algunas capas no sean compatibles con el dispositivo y, si son compatibles, pueden ocurrir errores durante el proceso de conversión, como resultado de lo cual obtenemos algunas cosas extrañas en la salida.

En general, si desea algún tipo de red neuronal arbitraria, es posible que no funcione con NCS. Por lo tanto, decidí intentar solucionar el problema utilizando las herramientas más extendidas y accesibles.

La nube

La alternativa obvia a una solución de hardware local es ir a la nube.

Opciones listas para usar: mis ojos se vuelven locos.

Todos los líderes:

... Y decenas de otros menos conocidos.

Elegir entre esta variedad no es nada fácil.

Y decidí no elegir, sino empaquetar el viejo esquema de trabajo en OpenCV en Docker y ejecutarlo en la nube.

La ventaja de este enfoque es la flexibilidad y el control: puede cambiar la red neuronal, el alojamiento, el servidor, en general, cualquier capricho.

Servidor

Comencemos con un prototipo local.

Tradicionalmente uso Flask para REST API, OpenCV y red MobileSSD.

Después de instalar las versiones actuales en Docker, descubrí que OpenCV 4.1.2 no funciona con Mobile SSD v1_coco_2018_01_28, y tuve que volver al probado 11/06_2017.

Al inicio del servicio, cargamos los nombres de las clases y la red:

def init():
    tf_labels.initLabels(dnn_conf.DNN_LABELS_PATH)
    return cv.dnn.readNetFromTensorflow(dnn_conf.DNN_PATH, dnn_conf.DNN_TXT_PATH)

En una ventana acoplable local (en una computadora portátil no muy joven), tarda 0.3 segundos, en Raspberry, 3.5.

Comencemos el 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, Frambuesa - 1.7.

Convertir el escape del tensor en json legible:

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

Además exportar esta operación a través de Flask(La entrada es una imagen, la salida son los resultados del detector en json).

Una opción alternativa, en la que se transfiere más trabajo al servidor: él mismo rodea los objetos encontrados y devuelve la imagen terminada.

Esta opción es buena cuando no queremos arrastrar opencv al servidor.

Estibador

Recopilamos la imagen.

El código se peina y se publica en Github, Docker lo tomará directamente desde allí.

Como plataforma, usaremos el mismo Debian Stretch que en Raspberry; no nos desviaremos de la tecnología probada.

Debe instalar flask, protobuf, request, opencv_python, descargar Mobile SSD, el código del servidor de Github e iniciar el 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 cliente detector en base a solicitudes.

Publicar en Docker Hub

Los registros de Docker se están multiplicando a una velocidad nada menor que la de los detectores de nubes.

Para no molestarnos, pasaremos de forma conservadora. Docker Hub.

  1. Registrarse
  2. Acceso:
    iniciar sesión en la ventana acoplable
  3. Pensemos en un nombre significativo:
    etiqueta acoplable opencv-detect tprlab/opencv-detect-ssd
  4. Sube la imagen al servidor:
    ventana acoplable push tprlab/opencv-detect-ssd

Nos lanzamos en la nube

La elección de dónde ejecutar el contenedor también es bastante amplia.

Todos los grandes actores (Google, Microsoft, Amazon) ofrecen una microinstancia gratuita durante el primer año.
Después de experimentar con Microsoft Azure y Google Cloud, me decidí por este último porque despegó más rápido.

No escribí instrucciones aquí, ya que esta parte es muy específica del proveedor seleccionado.

Probé diferentes opciones de hardware,
Niveles bajos (compartidos y dedicados): 0.4 - 0.5 segundos.
Coches más potentes: 0.25 - 0.3.
Bueno, incluso en el peor de los casos, las ganancias se triplican, puedes intentarlo.

Vídeo

Lanzamos un transmisor de video OpenCV simple en Raspberry, detectando a través de Google Cloud.
Para el experimento se utilizó un archivo de vídeo que fue filmado en una 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")

Con el detector no obtenemos más de tres fotogramas por segundo, todo va muy lento.
Si llevas una máquina potente a GCloud, puedes detectar de 4 a 5 fotogramas por segundo, pero la diferencia es casi invisible a la vista y sigue siendo lenta.

Vídeo del detector de objetos en la nube en Raspberry Pi

Los costos de la nube y el transporte no tienen nada que ver con esto; el detector funciona con hardware común y funciona a esa velocidad.

Dispositivo de computadora neuronal

No pude resistirme y ejecuté la prueba comparativa en NCS.

La velocidad del detector fue ligeramente inferior a 0.1 segundos, en cualquier caso 2-3 veces más rápida que la nube en una máquina débil, es decir, 8-9 fotogramas por segundo.

Vídeo del detector de objetos en la nube en Raspberry Pi

La diferencia en los resultados se explica por el hecho de que NCS estaba ejecutando la versión Mobile SSD 2018_01_28.

PD: Además, los experimentos han demostrado que una máquina de escritorio bastante potente con un procesador I7 muestra resultados ligeramente mejores y resultó posible exprimir 10 fotogramas por segundo.

Racimo

El experimento fue más allá e instalé el detector en cinco nodos de Google Kubernetes.
Las cápsulas en sí eran débiles y cada una de ellas no podía procesar más de 2 fotogramas por segundo.
Pero si ejecuta un clúster con N nodos y analiza fotogramas en N subprocesos, entonces con una cantidad suficiente de nodos (5) puede lograr los 10 fotogramas por segundo deseados.

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

Esto es lo que sucedió:

Vídeo del detector de objetos en la nube en Raspberry Pi

Un poco menos rápido que con NCS, pero más vigoroso que en una corriente.

La ganancia, por supuesto, no es lineal: hay superposiciones para sincronización y copia profunda de imágenes opencv.

Conclusión

En general, el experimento nos permite concluir que si lo intentas, puedes salirte con la tuya con una simple nube.

Pero un potente escritorio o hardware local permite conseguir mejores resultados y sin trucos.

referencias

Fuente: habr.com

Añadir un comentario