Vidéo du détecteur d'objets cloud sur Raspberry Pi

Prologue

Une vidéo circule désormais sur Internet montrant comment le pilote automatique de Tesla voit la route.

Cela faisait longtemps que j’avais envie de diffuser des vidéos enrichies avec un détecteur, et en temps réel.

Vidéo du détecteur d'objets cloud sur Raspberry Pi

Le problème est que je souhaite diffuser une vidéo depuis Raspberry et que les performances du détecteur de réseau neuronal laissent beaucoup à désirer.

Clé d'ordinateur neuronal Intel

J'ai envisagé différentes solutions.

В dernier article expérimenté avec Intel Neural Computer Stick. Le matériel est puissant, mais nécessite son propre format réseau.

Même si Intel propose des convertisseurs pour les principaux frameworks, il existe un certain nombre de pièges.

Par exemple, le format du réseau requis peut être incompatible, et s'il est compatible, certaines couches peuvent ne pas être prises en charge sur l'appareil, et si elles sont prises en charge, des erreurs peuvent survenir pendant le processus de conversion, ce qui entraîne nous obtenons des choses étranges en sortie.

En général, si vous souhaitez une sorte de réseau neuronal arbitraire, il se peut qu'il ne fonctionne pas avec NCS. J'ai donc décidé d'essayer de résoudre le problème en utilisant les outils les plus répandus et les plus accessibles.

Nuage

L’alternative évidente à une solution matérielle locale est d’aller vers le cloud.

Options toutes faites - mes yeux sont fous.

Tous les dirigeants :

... Et des dizaines d'autres moins connus.

Choisir parmi cette variété n’est pas du tout facile.

Et j'ai décidé de ne pas choisir, mais d'emballer le bon vieux schéma de travail sur OpenCV dans Docker et de l'exécuter dans le cloud.

L'avantage de cette approche est la flexibilité et le contrôle - vous pouvez modifier le réseau neuronal, l'hébergement, le serveur - en général, à tout moment.

Serveur

Commençons par un prototype local.

Traditionnellement, j'utilise Flask pour le réseau REST API, OpenCV et MobileSSD.

Après avoir installé les versions actuelles sur Docker, j'ai découvert qu'OpenCV 4.1.2 ne fonctionnait pas avec Mobile SSD v1_coco_2018_01_28, et j'ai dû revenir au 11/06_2017 éprouvé.

Au démarrage du service, nous chargeons les noms de classes et le réseau :

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

Sur un docker local (sur un ordinateur portable pas très jeune), cela prend 0.3 seconde, sur Raspberry - 3.5.

Commençons le calcul :

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 seconde, Framboise - 1.7.

Transformer l'échappement du tenseur en json lisible :

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

Plus loin exporter cette opération via Flask(l'entrée est une image, la sortie est les résultats du détecteur en json).

Une option alternative, dans laquelle davantage de travail est transféré au serveur : il entoure lui-même les objets trouvés et renvoie l'image finie.

Cette option est bonne lorsque nous ne voulons pas faire glisser opencv vers le serveur.

Docker

Nous collectons l'image.

Le code est passé au peigne fin et publié sur GithubGenericName, Docker le prendra directement à partir de là.

En tant que plate-forme, nous utiliserons le même Debian Stretch que sur Raspberry - nous ne nous écarterons pas de la pile technologique éprouvée.

Vous devez installer flask, protobuf, requêtes, opencv_python, télécharger Mobile SSD, le code du serveur depuis Github et démarrer le serveur.

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 client du détecteur en fonction des demandes.

Publication sur Docker Hub

Les registres Docker se multiplient à une vitesse tout aussi rapide que les détecteurs de nuages.

Afin de ne pas nous embêter, nous passerons prudemment par DockerHub.

  1. S'inscrire
  2. Se connecter:
    connexion docker
  3. Trouvons un nom significatif :
    balise docker opencv-detect tprlab/opencv-detect-ssd
  4. Téléchargez l'image sur le serveur :
    docker push tprlab/opencv-detect-ssd

Nous lançons dans le cloud

Le choix de l’endroit où exécuter le conteneur est également assez large.

Tous les grands acteurs (Google, Microsoft, Amazon) proposent une micro-instance gratuitement la première année.
Après avoir expérimenté Microsoft Azure et Google Cloud, j'ai opté pour ce dernier car il a décollé plus rapidement.

Je n'ai pas écrit d'instructions ici, car cette partie est très spécifique au prestataire sélectionné.

J'ai essayé différentes options matérielles,
Niveaux bas (partagés et dédiés) - 0.4 à 0.5 seconde.
Voitures plus puissantes - 0.25 - 0.3.
Eh bien, même dans le pire des cas, les gains sont multipliés par trois, vous pouvez essayer.

Vidéos

Nous lançons un simple streamer vidéo OpenCV sur Raspberry, détectant via Google Cloud.
Pour l’expérience, un fichier vidéo a été utilisé, filmé à une intersection aléatoire.


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")

Avec le détecteur on n'obtient pas plus de trois images par seconde, tout se passe très lentement.
Si vous utilisez une machine puissante dans GCloud, vous pouvez détecter 4 à 5 images par seconde, mais la différence est presque invisible à l'œil nu, elle est toujours lente.

Vidéo du détecteur d'objets cloud sur Raspberry Pi

Le cloud et les coûts de transport n'ont rien à voir avec cela : le détecteur fonctionne avec du matériel ordinaire et fonctionne à une telle vitesse.

Bâton d'ordinateur neuronal

Je n'ai pas pu résister et j'ai exécuté le benchmark sur NCS.

La vitesse du détecteur était légèrement inférieure à 0.1 seconde, en tout cas 2 à 3 fois plus rapide que le nuage sur une machine faible, soit 8 à 9 images par seconde.

Vidéo du détecteur d'objets cloud sur Raspberry Pi

La différence de résultats s'explique par le fait que NCS exécutait Mobile SSD version 2018_01_28.

PS De plus, des expériences ont montré qu'un ordinateur de bureau assez puissant doté d'un processeur I7 affiche des résultats légèrement meilleurs et qu'il s'est avéré possible d'y extraire 10 images par seconde.

Cluster

L'expérience est allée plus loin et j'ai installé le détecteur sur cinq nœuds dans Google Kubernetes.
Les pods eux-mêmes étaient faibles et chacun d’eux ne pouvait pas traiter plus de 2 images par seconde.
Mais si vous exécutez un cluster avec N nœuds et analysez les images dans N threads, alors avec un nombre suffisant de nœuds (5), vous pouvez atteindre les 10 images par seconde souhaitées.

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

Voici ce qui s'est passé:

Vidéo du détecteur d'objets cloud sur Raspberry Pi

Un peu moins rapide qu'avec NCS, mais plus vigoureux qu'en mono-stream.

Bien entendu, le gain n'est pas linéaire - il existe des superpositions pour la synchronisation et la copie approfondie des images opencv.

Conclusion

Globalement, l’expérience nous permet de conclure que si vous essayez, vous pouvez vous en sortir avec un simple cloud.

Mais un ordinateur de bureau puissant ou un matériel local vous permet d'obtenir de meilleurs résultats, et ce, sans aucune astuce.

références

Source: habr.com

Ajouter un commentaire