IMA4 2017/2018 P30: Contrôle d'une caméra WiFi. : Différence entre versions
(→Semaine 3) |
|||
(143 révisions intermédiaires par 2 utilisateurs non affichées) | |||
Ligne 1 : | Ligne 1 : | ||
+ | <include nopre noesc src="/home/pedago/pimasc/include/video-ControleCameraWiFi-iframe.html" /> | ||
__TOC__ | __TOC__ | ||
<br style="clear: both;"/> | <br style="clear: both;"/> | ||
=Présentation générale= | =Présentation générale= | ||
− | + | ==Dépôt git du projet== | |
+ | Vous trouverez un lien vers le git de ce projet [https://archives.plil.fr/thubert/UrbikCamera/blob/master/README.md ici]. Pour y accéder, vous devez au préalable créer un utilisateur et faire une demande d'accès à l'adresse thom4s.hubert@gmail.com | ||
==Description== | ==Description== | ||
Ligne 69 : | Ligne 71 : | ||
Durant la présentation de notre projet, la question de l'utilité de notre caméra nous été posé. Comme indiqué dans la partie "analyse des concurrents", il existe un grand nombre de caméra Wi-Fi déjà existante sur le marché. La réponse à cette question à été évoqué dans le partie "objectif". Le fait est que le but de ce projet est de développer une caméra qui devra se greffer au mobilier déjà existant, avec toute les contraintes que cela apporte en terme de matériel et d'architecture réseau. | Durant la présentation de notre projet, la question de l'utilité de notre caméra nous été posé. Comme indiqué dans la partie "analyse des concurrents", il existe un grand nombre de caméra Wi-Fi déjà existante sur le marché. La réponse à cette question à été évoqué dans le partie "objectif". Le fait est que le but de ce projet est de développer une caméra qui devra se greffer au mobilier déjà existant, avec toute les contraintes que cela apporte en terme de matériel et d'architecture réseau. | ||
+ | |||
+ | Un autre intérêt de ce projet est la recherche quant à la réalité augmentée et la reconnaissance d'image. Nous allons devoir rechercher une solution de reconnaissance d'image, adaptée au système sur laquelle elle doit être implémentée, soit un BananaPro, et adaptée et optimisée pour l'utilisation que nous allons en faire. | ||
=Préparation du projet= | =Préparation du projet= | ||
Ligne 205 : | Ligne 209 : | ||
Une fois cela fait, nous pourrons nous concentrer sur le développement de l'application. | Une fois cela fait, nous pourrons nous concentrer sur le développement de l'application. | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
=Réalisation du Projet= | =Réalisation du Projet= | ||
Ligne 216 : | Ligne 214 : | ||
{| class="wikitable" | {| class="wikitable" | ||
− | !Tâche !! Prélude !! Heures S1 !! Heures S2 !! Heures S3 !! Heures S4 !! Heures S5 !! Heures S6 !! Heures S7 !! Heures S8 !! Heures S9 !! Heures S10 !! Total | + | !Tâche !! Prélude !! Heures S1 !! Heures S2 !! Heures S3 !! Heures S4 !! Heures S5 !! Heures S6 !! Heures S7 !! Heures S8 !! Heures S9 !! Heures S10 !! Heures S11 !! Heures S12 !! Heures S13 !! Heures supplémentaires !! Total |
+ | |- | ||
+ | |Définition cahier des charges | ||
+ | |5h | ||
+ | |2h | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | |- | ||
+ | |Configuration de la Banana D1 | ||
+ | | | ||
+ | |~10h | ||
+ | |~10h | ||
+ | |~10h | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
|- | |- | ||
− | | | + | |Configuration de la BananaPro |
− | | ~10h | + | | |
− | | | + | |~8-10h |
+ | |~8-10h | ||
+ | |~4h | ||
+ | | | ||
+ | | | ||
+ | |~10h | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | |- | ||
+ | |Installation OpenCV | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | |~10h | ||
+ | |~10h | ||
+ | |~10h | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | |- | ||
+ | |Reconnaissance d'image | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | |~3-4h | ||
+ | |~3-4h | ||
+ | |~5-7h | ||
+ | |~5-7h | ||
+ | |~5-7h | ||
+ | |~5-7h | ||
+ | |~5-7h | ||
+ | |~5-7h | ||
+ | |~5-7h | ||
+ | |~5-7h | ||
+ | |~10h | ||
+ | |~15-20h | ||
+ | | | ||
+ | |- | ||
+ | |Serveur | ||
+ | | | ||
+ | |~5-7h | ||
+ | |~5-7h | ||
+ | |~5-7h | ||
+ | |~5-7h | ||
+ | |~5-7h | ||
+ | |~5-7h | ||
+ | |~5-7h | ||
+ | |~5-7h | ||
+ | |~5-7h | ||
+ | |~5-7h | ||
+ | |~5-7h | ||
+ | |~5-7h | ||
+ | |~5-7h | ||
+ | | | ||
+ | | | ||
+ | |- | ||
+ | |Wiki | ||
+ | |~1h | ||
+ | |~1h | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | |~5-6h | ||
+ | |- | ||
+ | |Réunions tuteurs | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | |2h | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | |2h | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | |- | ||
+ | |Total | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
| | | | ||
| | | | ||
Ligne 255 : | Ligne 401 : | ||
'''Banana Pi D1''' | '''Banana Pi D1''' | ||
+ | Le banana-pi D1 comporte deux modes de fonctionnement. | ||
+ | * Soit il est utilisé comme il est à la sortie d'usine, et dans ce cas il crée un flux vidéo automatiquement en parallèle de l'enregistrement des fichiers vidéo sur carte SD | ||
+ | * Soit on choisit d'installer un OS dessus. | ||
+ | |||
+ | Dans notre cas, il est important de pouvoir modifier l'os afin de pouvoir personnaliser son fonctionnement (Contrôle des servo-moteurs et éventuellement préparation de l'image à traiter) | ||
+ | |||
+ | On peut donc compiler un OS grâce aux [https://github.com/Lamobo/Lamobo-D1 sources] fournies, l'installer sur la carte grâce à un utilitaire également fournis(Burntool). | ||
+ | |||
+ | Mais nous n'avons pas réussi à utiliser la carte banana pi D1. Les différents problèmes que nous avons rencontré sont : | ||
+ | * Difficultés à compiler un OS à l'aide des sources fournies | ||
+ | * L'image obtenue est dans un format spécifique (extensions .jffs2 et .sqsh4) qui ne sont pas compatibles avec les outils classiques pour installer des OS | ||
+ | * L'outil fourni, burntool, qui doit permettre de flasher le D1 ne fonctionne pas. La carte est détectée par l'os, mais le driver ne s'installe pas et Burntool ne trouve pas la carte. | ||
+ | * La carte ne fonctionne pas comme prévu lors de sa mis sous tension sans avoir d'os : pas de stream, pas d'enregistrement vidéo. | ||
+ | |||
+ | Ces problèmes nous font penser que la Banana-Pi D1 est défectueuse. | ||
=== Configuration du point d'accès Wi-Fi de la Banana Pi Pro === | === Configuration du point d'accès Wi-Fi de la Banana Pi Pro === | ||
Ligne 264 : | Ligne 425 : | ||
L'interface wlan0 à ensuite été activé en modifiant le fichier /etc/network/interfaces: | L'interface wlan0 à ensuite été activé en modifiant le fichier /etc/network/interfaces: | ||
− | + | {| class="wikitable centre" | |
− | // STATION MODE | + | | |
− | + | // STATION MODE <br> | |
− | //auto wlan0 | + | //auto wlan0 <br> |
− | + | //iface wlan0 inet dhcp <br> | |
− | //iface wlan0 inet dhcp | + | //wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf <br> |
− | + | // ACESS POINT MODE <br> | |
− | //wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf | + | allow-hotplug wlan0 <br> |
− | + | iface wlan0 inet static <br> | |
− | // ACESS POINT MODE | + | address 192.168.100.1 <br> |
− | + | netmask 255.255.255.0 <br> | |
− | allow-hotplug wlan0 | + | |} |
− | |||
− | iface wlan0 inet static | ||
− | |||
− | address 192.168.100.1 | ||
− | |||
− | netmask 255.255.255.0 | ||
− | |||
− | |||
Pour configurer notre point d'accès, nous avons utilisé l'outil '''hostapd'''. La configuration en elle même se fait grâce au fichier /etc/hostapd/hostapd.conf comme suit: | Pour configurer notre point d'accès, nous avons utilisé l'outil '''hostapd'''. La configuration en elle même se fait grâce au fichier /etc/hostapd/hostapd.conf comme suit: | ||
− | + | {| class="wikitable centre" | |
− | interface=wlan0 | + | | |
+ | interface=wlan0 <br> | ||
+ | driver=nl80211 <br> | ||
+ | ssid=BananaPro <br> | ||
+ | channel=6 <br> | ||
+ | hw_mode=g <br> | ||
+ | macaddr_acl=0 <br> | ||
+ | auth_algs=1 <br> | ||
+ | ignore_broadcast_ssid=0 <br> | ||
+ | wpa=2 <br> | ||
+ | wpa_passphrase=12345678 <br> | ||
+ | wpa_key_mgmt=WPA-PSK <br> | ||
+ | wpa_pairwise=TKIP <br> | ||
+ | rsn_pairwise=CCMP <br> | ||
+ | |} | ||
− | + | Il nous a aussi fallu configurer un serveur dhcp, afin d'attribuer une adresse IP au appareils connectés. Pour cela, nous avons installé l'outil '''udcpd'''. Ce dernier se configure via le fichier /etc/udhcpd.conf: | |
− | + | {| class="wikitable centre" | |
+ | | | ||
+ | start 192.168.100.101 #default: 192.168.0.20 <br> | ||
+ | end 192.168.100.254 #default: 192.168.0.254 <br> | ||
+ | |} | ||
− | + | Nous avons finalement réussi à faire fonctionner notre point d'accès, a partir duquel nous pouvons accéder au serveur Web qui sera créé: | |
− | + | === Mise en place du serveur Web === | |
− | + | Afin de mettre en place notre serveur web, nous avons choisis Nginx en raison de ses performances, [http://www.hostingadvice.com/how-to/nginx-vs-apache/#performance légèrement supérieures à un serveur Web comme apache]. | |
− | + | ==Semaine 2== | |
− | + | ===Résolution des problème de réseau=== | |
− | + | Notre architecture réseau, tel que décrit en introduction, se compose de deux réseaux. L'un créée par notre routeur, lequel relie notre BPiPro et notre BPiD1. L'autre réseau est composé des utilisateurs se connectant au point d'accès de la BPiPro. | |
− | + | Ces deux réseau ont été configuré avec des serveurs DHCP, lesquels délivrent des adresses IP au clients connectés. Afin de récupérer le stream provenant de d'une caméra connecté au routeur depuis un appareil connecté au point d'accès de la BPiPro, nous avons du configurer l'attribution des adresses IP et mettre en place un Gateway entre la BPiPro et le routeur: | |
− | + | '''Routeur''' | |
− | + | {| class="wikitable centre" | |
+ | | | ||
+ | start 192.168.0.100 <br> | ||
+ | end 192.168.0.254 <br> | ||
+ | |} | ||
− | + | '''Point d'accès de la BPiPro''' | |
− | |||
+ | * /etc/udhcpd.conf: | ||
− | + | {| class="wikitable centre" | |
+ | | | ||
+ | start 192.168.0.200 <br> | ||
+ | end 192.168.0.254 <br> | ||
+ | |} | ||
− | + | * /etc/network/interfaces | |
− | |||
− | + | {| class="wikitable centre" | |
− | + | | | |
− | + | // ACESS POINT MODE <br> | |
− | + | allow-hotplug wlan0 <br> | |
− | + | iface wlan0 inet static <br> | |
− | + | address 192.168.0.199 <br> | |
− | + | netmask 255.255.255.0 <br> | |
− | + | // liaison interface wlan0 avec routeur <br> | |
− | + | Gateway 192.169.0.199 192.169.0.1 <br> | |
− | + | |} | |
===Mise en place du stream sur le serveur web=== | ===Mise en place du stream sur le serveur web=== | ||
Ligne 347 : | Ligne 525 : | ||
'''Caméra''' | '''Caméra''' | ||
+ | |||
Concernant la caméra, il a été décidé que nous utiliserons une caméra type Webcam simple connecté à notre BPiPro, étant nos difficultés à faire marcher la caméra BananaPi D1 efficacement. Ce point vient apporter quelques modifications concernant notre architecture réseau. Nous devons dorénavant uniquement nous connecter au point d'accès de la BPiPro. | Concernant la caméra, il a été décidé que nous utiliserons une caméra type Webcam simple connecté à notre BPiPro, étant nos difficultés à faire marcher la caméra BananaPi D1 efficacement. Ce point vient apporter quelques modifications concernant notre architecture réseau. Nous devons dorénavant uniquement nous connecter au point d'accès de la BPiPro. | ||
'''Application''' | '''Application''' | ||
+ | Cette réunion fut aussi l’occasion pour nous d'aborder la question de l'application. Nous avons réfléchi à la manière de traîter la reconnaissance d'image. | ||
+ | Notre but est de reconnaître des bâtiments dans un panorama urbain, balayé par notre caméra. Après quelques reflections, il s'avère qu'une solution envisageable consisterait en : | ||
+ | * Une initialisation du système : | ||
+ | ** D'abord, on photographie l'ensemble du panorama que la caméra peut avoir en visu | ||
+ | ** Ensuite on repère les différents bâtiments sur les images (Il faut encore trouver une solution concrête pour réaliser ça) | ||
+ | ** On lie à ces bâtiments les informations à afficher | ||
+ | |||
+ | *Ensuite, en exploitation : | ||
+ | **Le banana-pro va chercher à identifier les bâtiments repérés précédemment | ||
+ | **Pour cela il réalise sa recherche en connaissant la position des servos, donc la position grossière des bâtiemnts. Cela devrait permettre de limiter la recherche aux seules images concernées. | ||
===Open-CV=== | ===Open-CV=== | ||
Ligne 387 : | Ligne 576 : | ||
</code> | </code> | ||
+ | |||
+ | Par la suite, nous utiliserons le Python pour les raisons suivantes : | ||
+ | * Plus grande aisance de notre binôme et préférence de nos responsables en entreprise pour ce langage | ||
+ | * Des solutions de Streaming en python plus simples et déjà existantes | ||
===Mise en place d'un dépôt github=== | ===Mise en place d'un dépôt github=== | ||
Ligne 392 : | Ligne 585 : | ||
==Semaine 4== | ==Semaine 4== | ||
+ | |||
+ | Durant cette semaine, nous avons commencé à mettre en place l'application. Cette dernière doit récupérer le flux vidéo issue de la caméra afin de la traiter et de la steamer. Nous avons donc commencé à nous renseigner et à travailler sur ces deux points, à savoir le traitement de l'image et le stream de l'image traité. | ||
+ | |||
+ | ===Stratégie=== | ||
+ | |||
+ | La stratégie que nous avons adopté consiste à traiter l'image via un programme en python. En utilisant les bibliothèques fournit par '''OpenCV''', ce programme créera une classe "caméra" et s'occupera de traiter l'image. Un deuxième programme devra récupérer cette classe et permettre le stream en direct de l'image traité. | ||
+ | Pour ma mise en place du stream, nous nous sommes tourné vers '''Flask'''. Il s'agit d'un micro-framework open source permettant de créer des applications web en python. | ||
+ | |||
+ | ===Mise en place d'un environnement python=== | ||
+ | |||
+ | Avant d'installer OpenCV, il nous avons du mettre en place un environnement virtuel en python. Cette opération est nécessaire si l'on veut intégrer les bibliothèques fourni par OpenCV et par Flask à nos applications uniquement. Cela permet aussi d'utiliser les bibliothèques propre à une version de python, sans gêner l’exécution d’éventuels autres applications requérant d'autre versions. Le but est donc "d'isoler" nos programmes du reste du système. | ||
+ | |||
+ | Afin de créer notre environnement virtuel, nous nous sommes servi de l'outil '''virtualenv''', via la commande suivante: | ||
+ | |||
+ | <code> | ||
+ | virtualenv env | ||
+ | </code> | ||
+ | |||
+ | Cette commande se charge de créer une copie local des bibliothèques de python dans le dossier nommé "env" | ||
+ | |||
+ | Pour activer cet environnement, exécute cette commande: | ||
+ | |||
+ | <code> | ||
+ | source env/bin/activate | ||
+ | </code> | ||
+ | |||
+ | ===Installation d'open CV=== | ||
+ | |||
+ | La première étape à consisté à installer OpenCV. | ||
+ | |||
+ | * Build | ||
+ | |||
+ | La première étape à consisté à générer les makefiles utiles à la compilation: | ||
+ | |||
+ | {| class="wikitable centre" | ||
+ | | | ||
+ | cmake -D CMAKE_BUILD_TYPE=RELEASE \ <br> | ||
+ | -D CMAKE_INSTALL_PREFIX=/usr/local \ <br> | ||
+ | -D INSTALL_C_EXAMPLES=OFF \ <br> | ||
+ | -D INSTALL_PYTHON_EXAMPLES=ON \ <br> | ||
+ | -D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib/modules \ <br> | ||
+ | -D BUILD_EXAMPLES=ON .. <br> | ||
+ | |} | ||
+ | |||
+ | * Compilation | ||
+ | |||
+ | On compile ensuite en lançant 4 threads en parallèle: | ||
+ | |||
+ | <code> | ||
+ | make -j4 | ||
+ | </code> | ||
+ | |||
+ | ===Début mise en place du stream=== | ||
+ | |||
+ | Flask permet assez facilement de générer un petit serveur web. Ce dernier n'est pas très pas très robuste mais peut servir pour tester notre application. Afin de tester la récupération d'une frame par flask, nous avons réalisé un programme de test simple: | ||
+ | |||
+ | * Classe caméra | ||
+ | |||
+ | Ce programme se sert de la bibliothèque cv2, qu'on a préalablement linké à notre environnement virtuel python. Ce dernier créer une classe '''Camera''', et contient la méthode '''get_frame''', retournant une frame de notre flux vidéo. | ||
+ | |||
+ | {| class="wikitable centre" | ||
+ | | | ||
+ | import cv2 <br> | ||
+ | import time <br> | ||
+ | |||
+ | class Camera(): <br> | ||
+ | |||
+ | ::def __init__(self): <br> | ||
+ | :::self.cap = cv2.VideoCapture(0) <br> | ||
+ | :::print("Preparation de la camera") <br> | ||
+ | :::time.sleep(1) <br> | ||
+ | :::self.frame = self.cap.read() <br> | ||
+ | |||
+ | ::def get_frame(self): <br> | ||
+ | :::self.frames = open("stream.jpg", 'w+') <br> | ||
+ | :::s, img = self.cap.read() <br> | ||
+ | :::if s: <br> | ||
+ | ::::cv2.imwrite("stream.jpg", img) <br> | ||
+ | :::return self.frames.read() <br> | ||
+ | |||
+ | ::def __del__(self): <br> | ||
+ | :::self.cap.release() <br> | ||
+ | :::print("Extinction de la camera") <br> | ||
+ | :::return() <br> | ||
+ | |} | ||
+ | |||
+ | * Programme flask: | ||
+ | |||
+ | Notre programme flask importe la classe caméra et exécute la méthode '''get_frame''' pour générer un stream vidéo grâce à la fonction '''video_feed'''. | ||
+ | |||
+ | {| class="wikitable centre" | ||
+ | | | ||
+ | from flask import Flask, render_template, Response <br> | ||
+ | from camera import Camera <br> | ||
+ | |||
+ | app = Flask(__name__) <br> | ||
+ | |||
+ | @app.route('/') <br> | ||
+ | def index(): <br> | ||
+ | :return render_template('index.html') <br> | ||
+ | |||
+ | def gen(camera): <br> | ||
+ | :while True: <br> | ||
+ | ::frame = camera.get_frame()<br> | ||
+ | ::yield (b'--frame\r\n'<br> | ||
+ | ::b'Content-Type: image/jpeg\r\n\r\n' + str(frame) + b'\r\n') <br> | ||
+ | |||
+ | @app.route('/video_feed') <br> | ||
+ | def video_feed(): <br> | ||
+ | :return Response(gen(Camera()), <br> | ||
+ | ::mimetype='multipart/x-mixed-replace; boundary=frame') <br> | ||
+ | |||
+ | if __name__ == '__main__': <br> | ||
+ | :app.run(host='0.0.0.0', debug=True, threaded=True) <br> | ||
+ | |} | ||
+ | |||
+ | Ce programme permet la mise en place d'un serveur web à l'adresse <IP_locale>:5000: | ||
+ | |||
+ | -- insérer photo -- | ||
+ | |||
+ | La prochaine étape va consister à porter notre application flask sur notre serveur web nginx plus robuste. | ||
+ | |||
+ | ===Reconnaissance d'image=== | ||
+ | |||
+ | Parallèllement à la mise en place d'un stream à partir d'OpenCv et de Flask, nous travaillons sur la reconnaissance d'image. Nous nous sommes donnés comme objectifs de réaliser assez rapidement un programme qui puisse retrouver un objet connu à priori dans une image captée par la caméra. | ||
+ | Pour ce faire, nous avons réfléchi et trouvé la solution suivante. | ||
+ | * Après le mapping de la zone couverte, il s'agit de créer manuellement un masque pour chaque bâtiment dont on veut donner les infos. | ||
+ | * A chaque masque créé, on associe les différentes information : par exemple le nom du bâtiment, ses dates... | ||
+ | * Ensuite, lors de l'utilisation de la camera, le programme va appliquer le masque sur l'image obtenue lors du mapping, ce qui donnera le contour de l'objet à retrouver | ||
+ | * Il suffit alors de retrouver l'objet ainsi détouré dans le flux vidéo et de lui associer les informations à afficher | ||
+ | |||
+ | ===Implémentation de la reconnaissance d'image=== | ||
+ | Dans un premier temps, nous devons trouver une méthode l'implémentation de la reconnaissance d'image. | ||
+ | OpenCV propose une bibliothèque, xFeatures2d, qui permet de retrouver un élément dans une image de manière assez sûre, c'est à dire malgré des modifications de luminosité ou d'angle. | ||
+ | Le fonctionnement est le suivant. | ||
+ | Pour chaque image, on récupère des points-clef, qui sont les "coins" détectés sur l'image, soit des points faciles à représenter. | ||
+ | On calcule ensuite les descripteurs des points-clef, qui résument les caractéristiques de l'image en faisant abstraction de leur positions absolue. | ||
+ | OpenCV propose les commandes suivantes, en Python : | ||
+ | |||
+ | surf = cv2.xfeatures2d.SURF_create() //Crée l'objet Surf qui permet la detection de keypoints | ||
+ | kpobjet, desobjet = surf.detectAndCompute(objet, None) //La methode detectAndCompute de l'objet surf retourne les keypoints et les descriptors | ||
+ | |||
+ | On peut donc visualiser les keypoints (en bleu) obtenus sur une image de démo : | ||
+ | |||
+ | [[fichier:P30KeypointsFixes.png ]] | ||
+ | |||
+ | On peut voir que leur densité est élevée. Peut-être faudra-t-il jouer sur ce paramètre pour réduire le temps de traîtement. | ||
+ | |||
==Semaine 5== | ==Semaine 5== | ||
+ | |||
+ | Durant cette semaine, nous nous sommes attelé à mettre en place notre application sur un serveur web fonctionnel. | ||
+ | |||
+ | ===Installation d'uWSGI et configuration de Nginx=== | ||
+ | |||
+ | La première étape à consisté à porter notre application flask sur nginx. Pour cela, nous avons utilisé uWSGI. Cet outil permet de faire le lien entre notre serveur nginx, lequel permet de gérer les requêtes des clients, et notre application web flask, chargé de récupérer un flux vidéo. | ||
+ | |||
+ | * Installation et création d'un point d'entré uWSGI | ||
+ | |||
+ | Installation: | ||
+ | <code> pip install uwsgi </code> | ||
+ | |||
+ | On créer tout d'abord un programme uWSGI qui se charge de d'importer notre application flask. Ce programme servira plus tard de point d'entée à notre application flask. Ce programme sera par la suite exécuté par l'outil uwsgi, qui se chargera de créer un serveur wsgi local à partir de cette application. | ||
+ | |||
+ | {| class="wikitable centre" | ||
+ | | | ||
+ | from UrbikCamera import web | ||
+ | |||
+ | if __name__ == "__main__": | ||
+ | :app.run() | ||
+ | |} | ||
+ | |||
+ | On peut déjà tester notre serveur wsgi en exécutant cette commande:<br> | ||
+ | <code> uwsgi --socket 0.0.0.0:5000 --protocol=http -w '''nom_programme_uwsgi''':app </code><br> | ||
+ | |||
+ | -- screen test uwsgi -- | ||
+ | |||
+ | * Configuration de uwsgi | ||
+ | |||
+ | Afin de rendre notre serveur WSGI plus robuste, nous avons créé un fichier de configuration: | ||
+ | |||
+ | {| class="wikitable centre" | ||
+ | | | ||
+ | [uwsgi]<br> | ||
+ | module = wsgi<br> | ||
+ | |||
+ | '''#start up en master mode'''<br> | ||
+ | master = true<br> | ||
+ | '''#création de 5 process pour traiter les requêtes'''<br> | ||
+ | processes = 5<br> | ||
+ | |||
+ | '''#utilisation d'une socket pour gérer la connexion avec les clients'''<br> | ||
+ | socket = camera.sock<br> | ||
+ | '''#droit de la socket pour le groupe: lecture et écriture'''<br> | ||
+ | chmod-socket = 660<br> | ||
+ | '''#nettoyage de la socket apres utilisation'''<br> | ||
+ | vacuum = true<br> | ||
+ | |||
+ | '''#nécessaire pour configurer uWSGI''' <br> | ||
+ | die-on-term = true<br> | ||
+ | |} | ||
+ | |||
+ | * Création d'un script de lancement | ||
+ | |||
+ | Afin de rendre le lancement de notre serveur uwsgi automatique, on créé un script qui se lance automatique au démarrage de notre ordinateur. | ||
+ | NB: testé sur Linux Mint mais pas sur Bananian. | ||
+ | |||
+ | '''/etc/systemd/system/app_uwsgu.service:''' | ||
+ | |||
+ | {| class="wikitable centre" | ||
+ | | | ||
+ | '''#métadata ...'''<br> | ||
+ | [Unit]<br> | ||
+ | Description=lancement d'uWSGI<br> | ||
+ | After=network.target<br> | ||
+ | |||
+ | '''#indication de l'utilisateur pour les droits '''<br> | ||
+ | [Service]<br> | ||
+ | User=root<br> | ||
+ | '''#spécification des chemin d'accès de l'environnement vituel et de la source du projet'''<br> | ||
+ | WorkingDirectory=.../UrbikCamera<br> | ||
+ | Environment="PATH=.../UrbikCamera/env/bin"<br> | ||
+ | ExecStart=.../UrbikCamera/env/bin/uwsgi --ini myproject.ini<br> | ||
+ | |||
+ | '''#permet à systemd le lancement de ce script'''<br> | ||
+ | [Install]<br> | ||
+ | WantedBy=multi-user.target<br> | ||
+ | |} | ||
+ | |||
+ | On peut ensuite activer et lancer ce script grâce aux commandes: | ||
+ | |||
+ | <code>systemctl start myproject <br> | ||
+ | systemctl enable myproject <br></code> | ||
+ | |||
+ | * Configuration de nginx | ||
+ | |||
+ | La dernière étape consiste à configurer Nginx de manière à le faire communiquer avec notre serveur WSGI. L'idée est de mettre en place un proxy inverse pour qu'un utilisateur puisse accéder à notre serveur uWSGI, qui contient notre application flask, et ce, via Nginx. | ||
+ | |||
+ | {| class="wikitable centre" | ||
+ | | | ||
+ | '''#Bloc serveur: permet écoute sur le port 80 et gestion des requêtes sur localhost'''<br> | ||
+ | server {<br> | ||
+ | :listen 80;<br> | ||
+ | :server_name localhost;<br> | ||
+ | |||
+ | '''#Activation d'uwsgi sur nginx'''<br> | ||
+ | :location / {<br> | ||
+ | ::include uwsgi_params;<br> | ||
+ | ::'''#On transfère les requêtes des utilisateurs à la socket du serveur uwsgi que l'on créé dans le ficher le configuration'''<br> | ||
+ | ::uwsgi_pass unix:.../UrbikCamera/camera.sock;<br> | ||
+ | :}<br> | ||
+ | }<br> | ||
+ | |} | ||
+ | |||
==Semaine 6== | ==Semaine 6== | ||
+ | |||
+ | ===Installation d'une application simple sur la BananaPro=== | ||
+ | |||
+ | Nous avons commencé dans un premier temps à tester notre application Flask sur la BananaPro. Nous avons pour cela mis en place une application simple affichant un texte. Les résultats se sont avéré plutôt satisfaisant pour notre petite application. | ||
+ | |||
+ | ===Tests de performances sur la reconnaissance d'image=== | ||
+ | |||
+ | La mise en place de la la reconnaissance d'image fut par contre un peu plus problématique, étant donné l'incapacité de notre serveur à traiter plus d'un client à la fois. Il s'est avéré par la suite que même sans traitement d'image, notre serveur mis en place avec Flask et uWSGI est incapable de traiter plusieurs clients efficacement. Autrement dit, seul un seul client peut accéder à un stream vidéo (issue de notre application ou directement de notre webcam). | ||
+ | |||
+ | ===Reconnaissance d'image : Matcher les keypoints de deux images=== | ||
+ | |||
+ | Pour matcher deux images, OpenCV propose la recherche et l'utilisation de points-clefs, et de leurs descripteurs. | ||
+ | |||
+ | Les points-clefs sont des points spécifiques de l'image, de sorte à ce qu'il soit très probable qu'on retrouve les mêmes entre l'image de base et l'image de la caméra. De ces points-clefs, on extrait alors les descripteurs, qui à partir des points-clefs vont donner les caractéristiques géométriques de l'images. Notre but sera de pouvoir retrouver ces caractéristiques entre les deux images, et donc calculer la déformation, la rotation et la translation entre cl'image source, et appliquer ces mêmes modifications au calque en live. | ||
+ | |||
+ | |||
+ | Dans notre cas, nous utiliserons un Matcher basé sur force brute pour commencer. Nous testerons son efficacité plus tard pour optimiser les performances. | ||
+ | On l'implémente dous OpenCV de la manière suivante: | ||
+ | bf = cv2.BFMatcher() | ||
+ | matches = bf.knnMatch(desobjet,despano, k=2) | ||
+ | |||
+ | Dans la liste des matches, nous supprimons les couples de points dont la distance Euclidienne est supérieure à une constante que nous avons fixée, pour éliminer les matches aberrants. | ||
+ | |||
+ | good = [] | ||
+ | for m,n in matches: | ||
+ | if m.distance < 0.65*n.distance: | ||
+ | good.append(m) | ||
+ | |||
+ | On obtient alors une matrice "good", qui contient les meilleurs matches. | ||
+ | |||
+ | On peut alors visualiser les couples de points acceptés, ici reliés par un trait. Le motif de l'image à retrouver est présent à gauche, et l'image obtenue lors du stream est à droite. Cette image a été obtenue en live, sur l'un de nos ordinateurs personnels. | ||
+ | |||
+ | |||
+ | [[Fichier:P30-ReconnaissanceAvecMasque.png]] | ||
+ | |||
+ | On peut voir une majorité de cohérence dans les matches, bien que certains d'entre eux semblent incorrects (Nottemment en bas de l'image). On pourra réduire leur nombre en réduisant le facteur de la distance, mais à condition que ce filtrage ne soit pas trop strict. | ||
+ | |||
==Semaine 7== | ==Semaine 7== | ||
+ | === Reconnaissance d'image : Calcul d'une matrice de transformation=== | ||
+ | Pour continuer dans le processus de reconnaissance d'image, nous devons calculer une matrice de transformation entre notre image de base et l'image que nous avons obtenu par la caméra. | ||
+ | |||
+ | Gràce aux points-clef que nous avons précédemment calculé, nous allons pouvoir obtenir une matrice, qui va décrire la déformation qu'il y a entre les deux images. On pourra alors applique cette déformation au calque, et appliquer ce calque sur notre image en live. | ||
+ | |||
+ | Sous Open-CV, et en Python, nous pouvons faire cela avec les lignes: | ||
+ | |||
+ | On crée deux matrices contenant seulement les coordonnées des points matchés de l'image de bas et de l'image de destination | ||
+ | |||
+ | src_pts = np.float32([ kppano[m.trainIdx].pt for m in good ]).reshape(-1,1,2) | ||
+ | dst_pts = np.float32([ target._kpcible[m.queryIdx].pt for m in good ]).reshape(-1,1,2) | ||
+ | |||
+ | On calcule alors la matrice de perspective | ||
+ | |||
+ | M, masque = cv2.findHomography(dst_pts, src_pts, cv2.RANSAC,5.0) | ||
+ | |||
+ | |||
+ | Nous appliquerons cette matrice à un masque à afficher, représentant une image à superposer à la frame obtenue. | ||
+ | |||
+ | Voici une image qui nous a servi de référence : | ||
+ | |||
+ | [[fichier: P30Reference.jpg]] | ||
+ | |||
+ | On a créé un masque à partir d'elle: | ||
+ | |||
+ | [[fichier : P30-Masque.jpg]] | ||
+ | |||
+ | Ce masque va être appliqué sur une image uniformément verte, le but sera d'afficher un cercle vert pour détourer le ventilateur. | ||
+ | |||
+ | Sous OpenCV, nous utilisons la fonction findHomography qui permet d'obtenir cette matrice à partir des descripteurs de points-clef. On peut donc alors appliquer cette modification de perspective, qui corrige également les erreurs de rotation et de position. On peut alors obtenir une version corrigée de notre masque, et l'afficher sur l'image en live : | ||
+ | |||
+ | [[fichier: P30Rotation.png]] | ||
+ | |||
+ | ==Semaine 8== | ||
+ | |||
+ | ===Choix de la méthode de reconnaissance d'image=== | ||
+ | Pour implémenter notre reconnaissance de points, il existe plusieurs | ||
+ | algorithmes : | ||
+ | * La methode Surf, pour Speeded-Up Robust Features , qui est une version optimisée de | ||
+ | l’algorithme Sift. | ||
+ | *Les methodes Fast et Brief, l'une qui permet d'obtenir les descripteurs, l'autre les keypoints. | ||
+ | * La methode Orb, pour Oriented Fast and Rotated Brief, basée sur Fast et Brief | ||
+ | Pour pouvoir comparer les methodes, nous les avons implémenté tous les trois: | ||
+ | if methode == 0: | ||
+ | #'''Surf''' | ||
+ | #print("Target Surf") | ||
+ | surf = cv2.xfeatures2d.SURF_create() | ||
+ | kpcible,descible = surf.detectAndCompute(reference, None) | ||
+ | elif methode == 1: | ||
+ | #'''Fast+Brief''' | ||
+ | #print("Target Fabrief") | ||
+ | fast = cv2.FastFeatureDetector_create() | ||
+ | kpcible = fast.detect(reference, None) | ||
+ | brief = cv2.xfeatures2d.BriefDescriptorExtractor_create() | ||
+ | kpcible, descible = brief.compute(reference, kpcible) | ||
+ | elif methode == 2: | ||
+ | #'''ORB''' | ||
+ | #print("Target Orb") | ||
+ | orb = cv2.ORB_create() | ||
+ | kpcible, descible = orb.detectAndCompute(reference, None) | ||
+ | |||
+ | Après quelques tests, il est apparu que la methode Surf est assez rapide et très fiable, la methode Orb est assez fiable mais très rapide, et que Fast et Brief n'est ni rapide ni fiable. On garde la possibilité de choisir entre Orb et Surf pour l'optimisation du programme par la suite. | ||
+ | |||
+ | ==Semaine 9== | ||
+ | ===Reconnaissance d'image : gestion d'objets multiples=== | ||
+ | Pour gérer plusieurs objets, nous avons mis en place une classe. Chaque instance de la classe correspondra à un objet à cibler, et la classe comprendra les methodes pour cibles, l'objet, les coordonées de servomoteurs, les keypoints et leurs descripteurs, le masque à appliquer ainsi que le nom de l'objet. | ||
+ | |||
+ | Pour sauvegarder le nom et la position des objets, nous enregistrons ces données dans le fichier trajectoire, qui stocke le nom, et stockera les coordonnées pour les servomoteurs. | ||
+ | A partir de ce fichier, on peut retrouver les images de références qui porteront le nom: <nom_de_l_objet>_ref.jpg et le masque associé sous le nom de :<nom_de_l_objet>_fond.png | ||
+ | |||
+ | On va ensuite créer une liste composée des différentes instance, et le programme n'aura qu'à parcourir cette liste en mettant à jour les points clef, les descripteurs, le masque et les coordonnées de l'objet. La classe s'appelle ObjetCible, et peut être retrouvée dans les premières lignes du fichier camera_opencv.py. | ||
+ | |||
+ | ===Serveur : Problèmes et résolution=== | ||
+ | |||
+ | Le problème semble être lié a notre application en elle même: En effet, chaque client qui se connecte accède à l'application via un reverse proxy permis par uWSGI. Cependant, l’accès à l'application par plusieurs utilisateurs implique l'utilisation d'une ressource (ici, le flux vidéo de la caméra) de manière partagé. D'où l'incapacité du serveur à traiter plusieurs clients. De plus, les ressources de la BananaPro étant limité, on ne peut pas se permettre de faire tourner l'application plusieurs fois. | ||
+ | |||
+ | A la lumière de ce qui vient d’être énoncé, il apparaît clairement que les points suivants doivent être travaillé en priorité: | ||
+ | * L'application doit pouvoir générer une frame en continue | ||
+ | * L'application doit permettre l'accès en lecture à une frame pour plusieurs clients. | ||
+ | |||
+ | ==Semaine 10== | ||
+ | |||
+ | ===Réunion Urbik=== | ||
+ | Lors de cette réunion, nous avons présenté l'avancement du projet, en particulier sur la reconnaissance d'image. Nous avons pu faire la démonstration de notre programme de reconnaissance d'image. | ||
+ | Nous avons également parlé de la question du serveur, et réfléchis à la manière de l'implémenter. | ||
+ | |||
+ | ===Gestion des servomoteurs=== | ||
+ | Nous nous sommes mis à la gestion des servomoteurs. Sur Banana Pro, il existe la bibliothèque WiringPy qui permet d'utiliser facilement les ports GPIO. Mais malheureusement, il n'existe qu'un seul port capable de gérer la PWM hardware. | ||
+ | Par conséquent, le comportement de l'un des servomoteurs est instable et donc inutilisable pour le projet. De plus, l'un des deux servo a besoin d'être constemment remis à jour car il ne garde pas la position qu'on lui donne automatiquement. | ||
+ | Comme solution temporaire, nous avons pris le parti d'utiliser un arduino connecté via port série pour le contrôle des servos. | ||
+ | |||
+ | Nous avons donc mis au point un petit protocole de communication entre le BananaPro et l'arduino: | ||
+ | Les coordonnées envoyées ont des valeurs comprises entre 0 et 255. Ces valeurs peuvent être codées sur deux caractères hexadécimaux. Nous envoyons donc quatre caractères à l'arduino. A chaque fois qu'il en reçoit quatre, il peut ajuster correctement les servomoteurs. | ||
+ | |||
+ | ===Stratégie à adopter pour le serveur=== | ||
+ | |||
+ | Étant donné les difficultés que nous avons rencontré lors de la mise en place du serveur via nginx et Flask, nous avons décidé de concevoir nous même notre serveur web. Ce serveur doit pouvoir être fiable et surtout fonctionner pour plusieurs clients. | ||
+ | |||
+ | L'idée pour notre serveur est donc la suivante: | ||
+ | * L'application génère une frame dans une thread. | ||
+ | * Cette frame est ensuite écrite sur une file de message. | ||
+ | * Une méthode get_frame est mise en place permettant de lire sur cette file de message et de récupérer une frame à un moment donné. | ||
+ | * La thread de traitement d'image doit se faire parallèlement à l’exécution du serveur. | ||
+ | * Chaque client doit pouvoir accéder dans une thread à la frame via la méthode get_frame. | ||
+ | |||
+ | Nous nous sommes donc orienté dans un premier vers un serveur TCP basique. Ce dernier traite les connexion des clients via des threads séparé, et génère une page html en écrivant directement sur la socket. De la même manière, la frame récupéré est écrite sur la socket pour chaque client. | ||
+ | |||
+ | ==Semaine 11== | ||
+ | === Découpage en threads et optimisation=== | ||
+ | Un profiling nous a montré que le plus coûteux en terme de temps est le calcul des points clefs, pour calculer la déformation du masque. Par conséquent, pour gagner en performances, il faut trouver un moyen de limiter le nombre de fois par secondes où le masque à appliquer est calculé. | ||
+ | Une solution, que nous avons mise en place, est de séparer la partie du programme qui incruste le masque de la partie qui le calcule. On peut alors limiter son calcul et augmenter le nombre d'actualisations de l'image reçue. Cela permet à l'image sur laquelle on incruste le masque d'être plus fluide. | ||
+ | |||
+ | Plus précisément, pour limiter le nombre de calculs de masque, nous ne le faisons que si il y a une modification de l'image reçue. Dans le cas contraire, comme l'image ne varie pas, le masque doit être ajouté exactement au même endroit et avec la même déformation, et n'a pas besoin d'être modifié. | ||
+ | On a aussi limité les calculs du masque en rajoutant la ligne suivante dans le thread qui calcule les masques: | ||
+ | time.sleep(0.1) | ||
+ | On oblige ainsi le thread à être inactif pendant 100 ms, ce qui libère le processeur pour le reste des opérations | ||
+ | |||
+ | Avec ces optimisations, nous obtenons un résultat aux alentours de 12 FPS. | ||
+ | |||
+ | ===Mise en place d'un serveur HTTP=== | ||
+ | |||
+ | Après quelques recherches, nous avons vu qu'il existait un module http.serveur construit autour du module TCPsocket que nous avons utilisé la semaine dernière. Ce dernier comporte une série de méthodes pré-établi permettant: | ||
+ | * De gérer les requêtes des clients via les méhodes do_GET, do_POST, etc ... (voir git) | ||
+ | * De traiter ces requêtes dans une thread séparé. | ||
+ | * De générer facilement une page html en envoyant des headers très facilement. | ||
+ | |||
+ | Afin de mettre en place le stream, nous avons repris [https://gist.github.com/n3wtron/4624820 l'idée suivante]. | ||
+ | L'idée est la suivante: | ||
+ | * Le client se connecte via l'adresse IP:PORT/index.html et envoie une requête GET index.html au serveur. | ||
+ | * Ceci fait, le serveur gère sa requête et génère une page html comportant une image dont la source se situe à l'adresse suivante: IP:PORT/cam.mjpg. | ||
+ | * Une requête GET cam.mjpg est donc envoyé au serveur, celle-ci est à son tour traité. | ||
+ | * La frame est récupéré via la méthode get_frame et est écrite sur la socket en continue. | ||
+ | |||
+ | Voici ce qu'on peut alors obtenir: | ||
+ | |||
+ | [[Fichier:P30Public.jpg|500px]] | ||
+ | |||
+ | ===Tests sur plusieurs clients=== | ||
+ | |||
+ | Les tests effectué ont été très satisfaisant, plusieurs clients parvenant à se connecter au même moment avec un temps de latence assez faible. | ||
+ | |||
+ | La suite consiste à mettre en place une page admin afin de contrôler les servos-moteurs qui équipent la caméra. | ||
+ | |||
+ | ==Semaine 12== | ||
+ | |||
+ | ===Création d'une page admin=== | ||
+ | |||
+ | L'idée pour cette page admin est de permettre à un administrateur de modifier la position de caméra à distance pour pouvoir obtenir des images de référence. Pour cela, on procède de la même manière que que la page de la semaine précédente (qui devient une page publique). | ||
+ | |||
+ | La page admin est mise en place de manière suivante: | ||
+ | * La page html est composé de 4 boutons (haut, bas, gauche et droite), envoyant des requêtes de type POST, traité via la méthode do_POST: | ||
+ | * On lit sur la socket pour savoir quelle bouton à été pressé et on appelle la méthode qui agis en conséquence en faisant appel à une méthode correspondante. | ||
+ | * Afin d'éviter le rafraîchissement de la page après l'envoi d'une requête post, on se sert d'Ajax pour écrire un petit script en JavaScript permettant d'envoyer des requêtes POST de manière asynchrone. | ||
+ | |||
+ | En parallèle du serveur, cela va lancer un Thread spécifique qui va être capable de fournir les frames de la caméra, de contrôler les servomoteurs, et d'enregistrer les frames et leurs informations liées si l'administrateur appuie sur le bouton. | ||
+ | |||
+ | [[Fichier:P30Admin.png|600px]] | ||
+ | |||
+ | ==Semaine 13== | ||
+ | |||
+ | ===Mise en place d'une authentification pour la page admin=== | ||
+ | |||
+ | Afin de sécuriser l'accès à notre page admin, on met en place un système d’authentification. Après plusieurs essais, la solution qui à été retenu consiste à utiliser la requête d'authentification propre à HTTP. | ||
+ | |||
+ | De la même manière que pour les requêtes GET et POST, celle-ci est traité via la méthode do_AUTHHEAD: | ||
+ | * A chaque connexion sur la page admin, le client doit se connecter. | ||
+ | * Les identifiants rentrés sont encodé et comparé avec la clé que l'on a choisi (par exemple admin:1234), elle même encodé en base 64. | ||
+ | * Si les identifiants sont corrects, le client possède l'autorisation d'accéder à la page admin. | ||
=Documents Rendus= | =Documents Rendus= | ||
+ | |||
+ | [[Média : Rapport_Projet_HUBERT_DJERABA.pdf]] | ||
+ | |||
+ | Lien vers le git: https://archives.plil.fr/thubert/UrbikCamera |
Version actuelle datée du 15 juin 2018 à 21:50
Sommaire
- 1 Présentation générale
- 2 Analyse du projet
- 3 Préparation du projet
- 4 Réalisation du Projet
- 5 Documents Rendus
Présentation générale
Dépôt git du projet
Vous trouverez un lien vers le git de ce projet ici. Pour y accéder, vous devez au préalable créer un utilisateur et faire une demande d'accès à l'adresse thom4s.hubert@gmail.com
Description
Ce projet est issu d'une collaboration ente Polytech Lille et la start'up Urbik. Cette dernière développe du mobilier urbain connecté et souhaite intégrer à son mobilier déjà existant une caméra Wi-Fi. L'idée du projet est donc d'explorer les possibilités qu'une telle caméra pourrait apporter. Cette dernière doit transmettre un flux vidéo sur un site internet. Plusieurs utilisateurs pourront accéder au site en même temps afin d’accéder à une application. En ce qui concerne l'application, on peut envisager un certain nombre d'usages, comme de la réalité augmenté par exemple.
Objectifs
Le projet se découpe en deux parties. La première consiste à développer une caméra Wi-Fi fonctionnelle. La deuxième partie sera elle dédié à la réalisation de l'application en elle même.
Développement de la caméra Wi-Fi
Un point important concernant la caméra Wi-Fi est que celle-ci doit pouvoir se greffer au mobilier déjà existant développé par Urbik. La stratégie à adopter sera donc soumise à certaines contraintes matérielles et architecturales.
Pour développer cette caméra, nous avons à notre disposition une Banana Pi Pro ainsi qu'une caméra Banana Pi D1. Ces modules sont indépendants et sont relié ensemble en un réseau local.
La Banana Pi Pro possède un point d'accès Wi-Fi que nous devront obligatoirement utiliser afin que l'utilisateur puisse s'y connecter. Ce dernier se verra proposer un accès au site internet contenant l'application (L'accès à internet sera bloqué). La Banana Pi Pro est accompagné d'une caméra Banana Pi D1 pouvant fonctionner de manière autonome. Nous devrons donc réfléchir à un moyen efficace d'utiliser les ressources des deux Banana Pi.
Étant donné que plusieurs utilisateurs pourront se connecter en même temps sur la caméra, celle-ci devra seulement effectuer une rotation cyclique. Cette rotation se fera à l'aide de deux servomoteurs.
La création de l'application Web
Cette application devra permettre à l'utilisateur d'obtenir un certain nombre d'informations sur ce que transmet la caméra. On envisage aussi de mettre en place une application de réalité augmenté. Ces dites informations pourront donc être affiché en temps réel. On peut aussi imaginer permettre à l'utilisateur de coller une texture sur un objet afin de se le représenter dans un autre matériau ou une autre couleur par exemple.
Analyse du projet
Positionnement par rapport à l'existant
Il existe sur le marché un très grand nombre de caméra Wi-Fi, comme celle-ci par exemple.
Beaucoup de caméras Wi-Fi sur le marché proposent en plus de visionner le flux vidéo via une application et ce, à des pris parfois très abordables, comme cette caméra.
La valeur ajouté de notre caméra si situera donc essentiellement au niveau de l'accessibilité de la solution proposée, puisqu'elle va utiliser des fonctionnalités open-source des fonctionnalités prévues (mesure taille et réalité augmenté)
Analyse du premier concurrent
On se propose dans un premier temps d'analyser cette caméra.
il s'agit d'une caméra moyenne-gamme. Celle-ci propose un grand éventail de fonctionnalités, ce qui la rend assez attractive étant donné son prix.
Fonctionnalités:
- Image en 720p
- Couverture à 360°.
- Détection de Mouvement.
- Audio Bidirectionnel, permettant la communication à distance.
- Vision Nocturne.
- Alertes d'activité. L'utilisateur de recevoir des informations en temps réel sur son smart-phone.
- La détection de Pleurs de Bébé.
La caméra propose en plus le "YI Cloud", un service de cloud permettant de visionner les enregistrements de la caméra.
Toutes ses fonctionnalités la rendent idéale pour une utilisation domestique.
Analyse du second concurrent
La deuxième caméra concurrente que l'on choisis d'analyser est une caméra haut de gamme professionnelle.
Cette caméra se focalise surtout sur les performances, avec un résolution en 1080p, un zoom en x20 et un nombre important de fonctionnalités tel que du tracking ainsi que des IVS (Intelligent video software) permettant de la détection d'objet, de la surveillance de périmètre, etc ...
Scénario d'usage du produit ou du concept envisagé
Durant une première réunion avec les responsables d'Übrik,le scénario d'usage a été éclairci. Notre caméra va s'intégrer dans un projet d'immobilier urbain. Cet immobilier fournit un point d'accès WiFi publique. Placé dans un endroit stratégique, comme un lieu touristique par exemple, n'importe quel utilisateur pourrait avoir accès à un site internet leur permettant de visualiser l'aménagement de la ville au alentours et d'avoir des informations historique ou des anecdotes sur les monuments locaux. Toutes ces informations seraient affichées en réalité augmentée.
Réponse à la question difficile
Durant la présentation de notre projet, la question de l'utilité de notre caméra nous été posé. Comme indiqué dans la partie "analyse des concurrents", il existe un grand nombre de caméra Wi-Fi déjà existante sur le marché. La réponse à cette question à été évoqué dans le partie "objectif". Le fait est que le but de ce projet est de développer une caméra qui devra se greffer au mobilier déjà existant, avec toute les contraintes que cela apporte en terme de matériel et d'architecture réseau.
Un autre intérêt de ce projet est la recherche quant à la réalité augmentée et la reconnaissance d'image. Nous allons devoir rechercher une solution de reconnaissance d'image, adaptée au système sur laquelle elle doit être implémentée, soit un BananaPro, et adaptée et optimisée pour l'utilisation que nous allons en faire.
Préparation du projet
Cahier des charges
Besoin
Il s’agit ici de développer un prototype de caméra intelligente et interactive permettant de mettre en valeur une ville, ses ressources et ses aménagements. Plusieurs utilisateurs devront pouvoir se connecter au même moment à un point d’accès Wi-Fi afin d'accéder à l’application qui intégrera les fonctionnalités prévus.
Contraintes
Contraintes techniques
Le flux vidéo doit être accessible aux clients connectés, indépendamment de leur nombre Le mouvement de la caméra, se limite à une simple rotation afin de balayer le plus d’espace possible. On envisage de rendre notre application accessible via un site internet. Ce dernier devra donc être simple, ergonomique et sécurisé. La gestion de l’énergie est à prendre en compte, mais elle n’est pas réalisée par nos soins. Nous nous contenterons d’optimiser notre système pour réduire sa consommation. La caméra devra aussi être résistante et étanche, puisqu'elle se destine à priori à un usage extérieur.
Contraintes matérielles
Pour réaliser notre caméra, nous utiliserons le matériel suivant:
- Une Banana Pi. Celle-ci intégrera toutes les applications prévus. (modification du flux (Réalité augmentée), gestion du serveur web)
- La caméra en elle même consistera en une Banana-Pi BPI-D1, open source. (Source du flux vidéo)
Fonctions de base
Orientation de la caméra
- Réalisation d’un mouvement cyclique
- Pas de modification de la trajectoire en fonctionnement (Mais possibilité en mode administrateur?)
Traitement de l’image
- Ajout de réalité augmentée au flux vidéo
Création d’un réseau wi-fi lié à la caméra
- Permet d’accéder au flux vidéo
- Accès à la caméra par Internet (Pour administration?)
Envoi de l’image par streaming
- Serveur de streaming proposé par la caméra
- Accès au stream via le point d’accès wi-fi
Réalisation d’un client web
- Adapté aux navigateurs web (mobiles ou non)
- Intégration du flux vidéo
- Interface utilisateur permettant de modifier les informations ajoutées par la VR
Priorités de réalisation
Voici l’ordre des tâches que nous envisageons pour ce projet:
- La transmission d’un flux vidéo depuis la caméra
- L’intégration de ce flux dans un site web
- La conception mécanique de la caméra
- L’implémentation de fonctionnalités de réalité augmentée
Facteurs de qualité
Voici maintenant les critères de qualité, suivant leur ordre d’importance:
- Prototype fonctionnel
- Application web fonctionnelle et efficace
- Intégration de la réalité augmentée
- Application web esthétique et ergonomique
- Sécurité
- Consommation énergétique réduite
NB: le cahier des charges est susceptible de changer en fonction de notre avancement (ou non) dans le projet.
Choix techniques : matériel et logiciel
Une grande partie du matériel qui sera utilisé à été prêté par Urbik:
Carte de développement
Stockage
- 2*Carte Micro SD 8 Go, pour le stockage des Banana Pi
- Une clé USB 4 Go
- Un Support MicroSD -> SD
Câbles et alimentations
- Rallonge microUSB mâle -> USB type A femelle
- USB typeA Y mâle -> microUSB & USB typeA femelle
- Bloc secteur Alimentation 230V ~ 5V microUSB
Accessoires
- Antenne WiFi pour Banana Pi Pro
- Adaptateur Ethernet USB TypeA
Divers
- Module RTC compatible Banana Pi Pro
- Shield développement GPIO Banana Pro
- Une clé Wi-Fi pour RaspPi
- Adaptateur DVI -> HDMI
A cela, on ajoute 2 servomoteurs, prêté par Polytech Lille
Liste des tâches à effectuer
On se propose, sur le court terme, de se concentrer sur le développement de la caméra Wi-Fi en elle même. Le but est de pouvoir visualiser le flux vidéo transmis par la caméra sur un site internet:
- Il faut, dans un premier temps, installer des systèmes d'exploitation sur les Banana Pi Pro et D1.
- Créer un serveur web sur la Banana Pi Pro
- Configurer la communication entre la Banana Pi Pro et la Banana Pi D1
- Configurer la rotation des servomoteur
Une fois cela fait, nous pourrons nous concentrer sur le développement de l'application.
Réalisation du Projet
Feuille d'heures
Tâche | Prélude | Heures S1 | Heures S2 | Heures S3 | Heures S4 | Heures S5 | Heures S6 | Heures S7 | Heures S8 | Heures S9 | Heures S10 | Heures S11 | Heures S12 | Heures S13 | Heures supplémentaires | Total |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Définition cahier des charges | 5h | 2h | ||||||||||||||
Configuration de la Banana D1 | ~10h | ~10h | ~10h | |||||||||||||
Configuration de la BananaPro | ~8-10h | ~8-10h | ~4h | ~10h | ||||||||||||
Installation OpenCV | ~10h | ~10h | ~10h | |||||||||||||
Reconnaissance d'image | ~3-4h | ~3-4h | ~5-7h | ~5-7h | ~5-7h | ~5-7h | ~5-7h | ~5-7h | ~5-7h | ~5-7h | ~10h | ~15-20h | ||||
Serveur | ~5-7h | ~5-7h | ~5-7h | ~5-7h | ~5-7h | ~5-7h | ~5-7h | ~5-7h | ~5-7h | ~5-7h | ~5-7h | ~5-7h | ~5-7h | |||
Wiki | ~1h | ~1h | ~5-6h | |||||||||||||
Réunions tuteurs | 2h | 2h | ||||||||||||||
Total |
Prologue
Réunion avec les responsables d'Urbik
Préalablement au commencement du projet, nous avons rencontrés les responsables d'Urbik afin de valider le cahier des charges et de clarifier certains points. A l'issue de cette réunion, tout les objectifs ont été validés, avec cependant de nombreuses recommandations:
- Installer un OS léger et minimaliste afin d'économiser les ressources de la Banana Pi Pro
- Utiliser la librairie OpenCV afin de réaliser l'application
- Utiliser au mieux les capacité de la caméra afin de faciliter le traitement de l'application
- Pour des raisons pratiques, utiliser le chipset intégré à la caméra afin de contrôler les servomoteurs (Ces derniers étant en dehors de l'habitacle du mobilier et donc éloigné de la Banana Pi Pro)
- Pour le traitement de l'image, utiliser 2 flux vidéo. L'un sera transmis directement par la caméra, l'autre sera dédié à l'application
Semaine 1
Installation des OS
Banana Pi Pro
Pour le Banana Pi Pro, nous avons choisis l'OS Bananian. Nous avons choisi cet OS pour sa simplicité et pour sa bonne compatibilité avec notre système. Cet OS nous garantit aussi d'avoir uniquement les fonctions dont nous avons besoins, ce qui nous permettra d'économiser le plus d'espace possible.
Banana Pi D1 Le banana-pi D1 comporte deux modes de fonctionnement.
- Soit il est utilisé comme il est à la sortie d'usine, et dans ce cas il crée un flux vidéo automatiquement en parallèle de l'enregistrement des fichiers vidéo sur carte SD
- Soit on choisit d'installer un OS dessus.
Dans notre cas, il est important de pouvoir modifier l'os afin de pouvoir personnaliser son fonctionnement (Contrôle des servo-moteurs et éventuellement préparation de l'image à traiter)
On peut donc compiler un OS grâce aux sources fournies, l'installer sur la carte grâce à un utilitaire également fournis(Burntool).
Mais nous n'avons pas réussi à utiliser la carte banana pi D1. Les différents problèmes que nous avons rencontré sont :
- Difficultés à compiler un OS à l'aide des sources fournies
- L'image obtenue est dans un format spécifique (extensions .jffs2 et .sqsh4) qui ne sont pas compatibles avec les outils classiques pour installer des OS
- L'outil fourni, burntool, qui doit permettre de flasher le D1 ne fonctionne pas. La carte est détectée par l'os, mais le driver ne s'installe pas et Burntool ne trouve pas la carte.
- La carte ne fonctionne pas comme prévu lors de sa mis sous tension sans avoir d'os : pas de stream, pas d'enregistrement vidéo.
Ces problèmes nous font penser que la Banana-Pi D1 est défectueuse.
Configuration du point d'accès Wi-Fi de la Banana Pi Pro
Nous nous sommes ensuite attelé à configurer notre Banana Pi Pro pour en faire un point d'accès Wi-Fi.
Nous avons d'abord configuré le module "AP6181". Son activation se fait via la commande "modprobe ap6210". Afin de rendre son activation automatique en mode AP (access point) au démarrage de la, BPi Pro, nous avons ajouté "ap6210 op_mode=2" dans le fichier /etc/modules.
L'interface wlan0 à ensuite été activé en modifiant le fichier /etc/network/interfaces:
// STATION MODE |
Pour configurer notre point d'accès, nous avons utilisé l'outil hostapd. La configuration en elle même se fait grâce au fichier /etc/hostapd/hostapd.conf comme suit:
interface=wlan0 |
Il nous a aussi fallu configurer un serveur dhcp, afin d'attribuer une adresse IP au appareils connectés. Pour cela, nous avons installé l'outil udcpd. Ce dernier se configure via le fichier /etc/udhcpd.conf:
start 192.168.100.101 #default: 192.168.0.20 |
Nous avons finalement réussi à faire fonctionner notre point d'accès, a partir duquel nous pouvons accéder au serveur Web qui sera créé:
Mise en place du serveur Web
Afin de mettre en place notre serveur web, nous avons choisis Nginx en raison de ses performances, légèrement supérieures à un serveur Web comme apache.
Semaine 2
Résolution des problème de réseau
Notre architecture réseau, tel que décrit en introduction, se compose de deux réseaux. L'un créée par notre routeur, lequel relie notre BPiPro et notre BPiD1. L'autre réseau est composé des utilisateurs se connectant au point d'accès de la BPiPro.
Ces deux réseau ont été configuré avec des serveurs DHCP, lesquels délivrent des adresses IP au clients connectés. Afin de récupérer le stream provenant de d'une caméra connecté au routeur depuis un appareil connecté au point d'accès de la BPiPro, nous avons du configurer l'attribution des adresses IP et mettre en place un Gateway entre la BPiPro et le routeur:
Routeur
start 192.168.0.100 |
Point d'accès de la BPiPro
- /etc/udhcpd.conf:
start 192.168.0.200 |
- /etc/network/interfaces
// ACESS POINT MODE |
Mise en place du stream sur le serveur web
Cette semaine à été dédié à la finalisation de la mise en place du serveur web avec l'ajout du stream au niveau du site. Étant donné les problème rencontré avec la caméra BPi D1, nous avons décidé d'utiliser l'outil motion.
Ce dernier se charge de créer un serveur de stream à l'adresse IP : <IP_machine:8081>, que nous avons mis en place de manière très simple:
<p style="text-align: center;"><img src="192.168.1.25:8081" alt="" /></p>
Semaine 3
Deuxième réunion Urbik
Durant cette semaine, nous avons pu rencontrer à nouveau les responsables nos tuteurs de projet afin de discuter plus en détail de l'application que nous devons mettre place.
Caméra
Concernant la caméra, il a été décidé que nous utiliserons une caméra type Webcam simple connecté à notre BPiPro, étant nos difficultés à faire marcher la caméra BananaPi D1 efficacement. Ce point vient apporter quelques modifications concernant notre architecture réseau. Nous devons dorénavant uniquement nous connecter au point d'accès de la BPiPro.
Application
Cette réunion fut aussi l’occasion pour nous d'aborder la question de l'application. Nous avons réfléchi à la manière de traîter la reconnaissance d'image. Notre but est de reconnaître des bâtiments dans un panorama urbain, balayé par notre caméra. Après quelques reflections, il s'avère qu'une solution envisageable consisterait en :
- Une initialisation du système :
- D'abord, on photographie l'ensemble du panorama que la caméra peut avoir en visu
- Ensuite on repère les différents bâtiments sur les images (Il faut encore trouver une solution concrête pour réaliser ça)
- On lie à ces bâtiments les informations à afficher
- Ensuite, en exploitation :
- Le banana-pro va chercher à identifier les bâtiments repérés précédemment
- Pour cela il réalise sa recherche en connaissant la position des servos, donc la position grossière des bâtiemnts. Cela devrait permettre de limiter la recherche aux seules images concernées.
Open-CV
Nous avons commencé à prendre en main Open-CV. Nous réalisons un programme basique, qui permet de récupérer un stream et le modifier frame par frame. Nous avons fait le choix d'utiliser le C++ pour ce premier programme, mais nous réfléchissons à l'utilisation du langage Python qui aurait l’intérêt d'être plus simple et de fournir une bonne documentation sur le net.
Le fonctionnement de notre code est très simple. Il récupère un stream vidéo existant à l'aide des lignes de code const std::string url = "http://localhost:8081";
cv::VideoCapture capture(url);
et on crée trois fenêtres :
cv::namedWindow("Stream", CV_WINDOW_AUTOSIZE);
cv::namedWindow("Stream1", CV_WINDOW_AUTOSIZE);
cv::namedWindow("Stream2", CV_WINDOW_AUTOSIZE);
On va ensuite récupérer les frames.
cv::Mat frame;
if (!capture.read(frame)) { //Error }
On les modifie et on les affiche:
cv::Mat outFrame;
threshold(frame, outFrame, threshold_value, max_BINARY_value, THRESH_BINARY);
cv::imshow("Stream1", outFrame);
Par la suite, nous utiliserons le Python pour les raisons suivantes :
- Plus grande aisance de notre binôme et préférence de nos responsables en entreprise pour ce langage
- Des solutions de Streaming en python plus simples et déjà existantes
Mise en place d'un dépôt github
Nous avons également mis en place un dépôt github, pour mettre à disposition notre code source. Pour y accéder, vous devez au préalable vous créer un compte et faire la demande d'accès par mail à l'adresse thom4s.hubert@gmail.com
Semaine 4
Durant cette semaine, nous avons commencé à mettre en place l'application. Cette dernière doit récupérer le flux vidéo issue de la caméra afin de la traiter et de la steamer. Nous avons donc commencé à nous renseigner et à travailler sur ces deux points, à savoir le traitement de l'image et le stream de l'image traité.
Stratégie
La stratégie que nous avons adopté consiste à traiter l'image via un programme en python. En utilisant les bibliothèques fournit par OpenCV, ce programme créera une classe "caméra" et s'occupera de traiter l'image. Un deuxième programme devra récupérer cette classe et permettre le stream en direct de l'image traité. Pour ma mise en place du stream, nous nous sommes tourné vers Flask. Il s'agit d'un micro-framework open source permettant de créer des applications web en python.
Mise en place d'un environnement python
Avant d'installer OpenCV, il nous avons du mettre en place un environnement virtuel en python. Cette opération est nécessaire si l'on veut intégrer les bibliothèques fourni par OpenCV et par Flask à nos applications uniquement. Cela permet aussi d'utiliser les bibliothèques propre à une version de python, sans gêner l’exécution d’éventuels autres applications requérant d'autre versions. Le but est donc "d'isoler" nos programmes du reste du système.
Afin de créer notre environnement virtuel, nous nous sommes servi de l'outil virtualenv, via la commande suivante:
virtualenv env
Cette commande se charge de créer une copie local des bibliothèques de python dans le dossier nommé "env"
Pour activer cet environnement, exécute cette commande:
source env/bin/activate
Installation d'open CV
La première étape à consisté à installer OpenCV.
- Build
La première étape à consisté à générer les makefiles utiles à la compilation:
cmake -D CMAKE_BUILD_TYPE=RELEASE \ |
- Compilation
On compile ensuite en lançant 4 threads en parallèle:
make -j4
Début mise en place du stream
Flask permet assez facilement de générer un petit serveur web. Ce dernier n'est pas très pas très robuste mais peut servir pour tester notre application. Afin de tester la récupération d'une frame par flask, nous avons réalisé un programme de test simple:
- Classe caméra
Ce programme se sert de la bibliothèque cv2, qu'on a préalablement linké à notre environnement virtuel python. Ce dernier créer une classe Camera, et contient la méthode get_frame, retournant une frame de notre flux vidéo.
import cv2 class Camera():
|
- Programme flask:
Notre programme flask importe la classe caméra et exécute la méthode get_frame pour générer un stream vidéo grâce à la fonction video_feed.
from flask import Flask, render_template, Response app = Flask(__name__) @app.route('/')
def gen(camera):
@app.route('/video_feed')
if __name__ == '__main__':
|
Ce programme permet la mise en place d'un serveur web à l'adresse <IP_locale>:5000:
-- insérer photo --
La prochaine étape va consister à porter notre application flask sur notre serveur web nginx plus robuste.
Reconnaissance d'image
Parallèllement à la mise en place d'un stream à partir d'OpenCv et de Flask, nous travaillons sur la reconnaissance d'image. Nous nous sommes donnés comme objectifs de réaliser assez rapidement un programme qui puisse retrouver un objet connu à priori dans une image captée par la caméra. Pour ce faire, nous avons réfléchi et trouvé la solution suivante.
- Après le mapping de la zone couverte, il s'agit de créer manuellement un masque pour chaque bâtiment dont on veut donner les infos.
- A chaque masque créé, on associe les différentes information : par exemple le nom du bâtiment, ses dates...
- Ensuite, lors de l'utilisation de la camera, le programme va appliquer le masque sur l'image obtenue lors du mapping, ce qui donnera le contour de l'objet à retrouver
- Il suffit alors de retrouver l'objet ainsi détouré dans le flux vidéo et de lui associer les informations à afficher
Implémentation de la reconnaissance d'image
Dans un premier temps, nous devons trouver une méthode l'implémentation de la reconnaissance d'image. OpenCV propose une bibliothèque, xFeatures2d, qui permet de retrouver un élément dans une image de manière assez sûre, c'est à dire malgré des modifications de luminosité ou d'angle. Le fonctionnement est le suivant. Pour chaque image, on récupère des points-clef, qui sont les "coins" détectés sur l'image, soit des points faciles à représenter. On calcule ensuite les descripteurs des points-clef, qui résument les caractéristiques de l'image en faisant abstraction de leur positions absolue. OpenCV propose les commandes suivantes, en Python :
surf = cv2.xfeatures2d.SURF_create() //Crée l'objet Surf qui permet la detection de keypoints kpobjet, desobjet = surf.detectAndCompute(objet, None) //La methode detectAndCompute de l'objet surf retourne les keypoints et les descriptors
On peut donc visualiser les keypoints (en bleu) obtenus sur une image de démo :
On peut voir que leur densité est élevée. Peut-être faudra-t-il jouer sur ce paramètre pour réduire le temps de traîtement.
Semaine 5
Durant cette semaine, nous nous sommes attelé à mettre en place notre application sur un serveur web fonctionnel.
Installation d'uWSGI et configuration de Nginx
La première étape à consisté à porter notre application flask sur nginx. Pour cela, nous avons utilisé uWSGI. Cet outil permet de faire le lien entre notre serveur nginx, lequel permet de gérer les requêtes des clients, et notre application web flask, chargé de récupérer un flux vidéo.
- Installation et création d'un point d'entré uWSGI
Installation:
pip install uwsgi
On créer tout d'abord un programme uWSGI qui se charge de d'importer notre application flask. Ce programme servira plus tard de point d'entée à notre application flask. Ce programme sera par la suite exécuté par l'outil uwsgi, qui se chargera de créer un serveur wsgi local à partir de cette application.
from UrbikCamera import web if __name__ == "__main__":
|
On peut déjà tester notre serveur wsgi en exécutant cette commande:
uwsgi --socket 0.0.0.0:5000 --protocol=http -w nom_programme_uwsgi:app
-- screen test uwsgi --
- Configuration de uwsgi
Afin de rendre notre serveur WSGI plus robuste, nous avons créé un fichier de configuration:
[uwsgi] #start up en master mode #utilisation d'une socket pour gérer la connexion avec les clients #nécessaire pour configurer uWSGI |
- Création d'un script de lancement
Afin de rendre le lancement de notre serveur uwsgi automatique, on créé un script qui se lance automatique au démarrage de notre ordinateur. NB: testé sur Linux Mint mais pas sur Bananian.
/etc/systemd/system/app_uwsgu.service:
#métadata ... #indication de l'utilisateur pour les droits #permet à systemd le lancement de ce script |
On peut ensuite activer et lancer ce script grâce aux commandes:
systemctl start myproject
systemctl enable myproject
- Configuration de nginx
La dernière étape consiste à configurer Nginx de manière à le faire communiquer avec notre serveur WSGI. L'idée est de mettre en place un proxy inverse pour qu'un utilisateur puisse accéder à notre serveur uWSGI, qui contient notre application flask, et ce, via Nginx.
#Bloc serveur: permet écoute sur le port 80 et gestion des requêtes sur localhost
#Activation d'uwsgi sur nginx
} |
Semaine 6
Installation d'une application simple sur la BananaPro
Nous avons commencé dans un premier temps à tester notre application Flask sur la BananaPro. Nous avons pour cela mis en place une application simple affichant un texte. Les résultats se sont avéré plutôt satisfaisant pour notre petite application.
Tests de performances sur la reconnaissance d'image
La mise en place de la la reconnaissance d'image fut par contre un peu plus problématique, étant donné l'incapacité de notre serveur à traiter plus d'un client à la fois. Il s'est avéré par la suite que même sans traitement d'image, notre serveur mis en place avec Flask et uWSGI est incapable de traiter plusieurs clients efficacement. Autrement dit, seul un seul client peut accéder à un stream vidéo (issue de notre application ou directement de notre webcam).
Reconnaissance d'image : Matcher les keypoints de deux images
Pour matcher deux images, OpenCV propose la recherche et l'utilisation de points-clefs, et de leurs descripteurs.
Les points-clefs sont des points spécifiques de l'image, de sorte à ce qu'il soit très probable qu'on retrouve les mêmes entre l'image de base et l'image de la caméra. De ces points-clefs, on extrait alors les descripteurs, qui à partir des points-clefs vont donner les caractéristiques géométriques de l'images. Notre but sera de pouvoir retrouver ces caractéristiques entre les deux images, et donc calculer la déformation, la rotation et la translation entre cl'image source, et appliquer ces mêmes modifications au calque en live.
Dans notre cas, nous utiliserons un Matcher basé sur force brute pour commencer. Nous testerons son efficacité plus tard pour optimiser les performances.
On l'implémente dous OpenCV de la manière suivante:
bf = cv2.BFMatcher() matches = bf.knnMatch(desobjet,despano, k=2)
Dans la liste des matches, nous supprimons les couples de points dont la distance Euclidienne est supérieure à une constante que nous avons fixée, pour éliminer les matches aberrants.
good = [] for m,n in matches: if m.distance < 0.65*n.distance: good.append(m)
On obtient alors une matrice "good", qui contient les meilleurs matches.
On peut alors visualiser les couples de points acceptés, ici reliés par un trait. Le motif de l'image à retrouver est présent à gauche, et l'image obtenue lors du stream est à droite. Cette image a été obtenue en live, sur l'un de nos ordinateurs personnels.
On peut voir une majorité de cohérence dans les matches, bien que certains d'entre eux semblent incorrects (Nottemment en bas de l'image). On pourra réduire leur nombre en réduisant le facteur de la distance, mais à condition que ce filtrage ne soit pas trop strict.
Semaine 7
Reconnaissance d'image : Calcul d'une matrice de transformation
Pour continuer dans le processus de reconnaissance d'image, nous devons calculer une matrice de transformation entre notre image de base et l'image que nous avons obtenu par la caméra.
Gràce aux points-clef que nous avons précédemment calculé, nous allons pouvoir obtenir une matrice, qui va décrire la déformation qu'il y a entre les deux images. On pourra alors applique cette déformation au calque, et appliquer ce calque sur notre image en live.
Sous Open-CV, et en Python, nous pouvons faire cela avec les lignes:
On crée deux matrices contenant seulement les coordonnées des points matchés de l'image de bas et de l'image de destination
src_pts = np.float32([ kppano[m.trainIdx].pt for m in good ]).reshape(-1,1,2) dst_pts = np.float32([ target._kpcible[m.queryIdx].pt for m in good ]).reshape(-1,1,2)
On calcule alors la matrice de perspective
M, masque = cv2.findHomography(dst_pts, src_pts, cv2.RANSAC,5.0)
Nous appliquerons cette matrice à un masque à afficher, représentant une image à superposer à la frame obtenue.
Voici une image qui nous a servi de référence :
On a créé un masque à partir d'elle:
Ce masque va être appliqué sur une image uniformément verte, le but sera d'afficher un cercle vert pour détourer le ventilateur.
Sous OpenCV, nous utilisons la fonction findHomography qui permet d'obtenir cette matrice à partir des descripteurs de points-clef. On peut donc alors appliquer cette modification de perspective, qui corrige également les erreurs de rotation et de position. On peut alors obtenir une version corrigée de notre masque, et l'afficher sur l'image en live :
Semaine 8
Choix de la méthode de reconnaissance d'image
Pour implémenter notre reconnaissance de points, il existe plusieurs algorithmes :
- La methode Surf, pour Speeded-Up Robust Features , qui est une version optimisée de
l’algorithme Sift.
- Les methodes Fast et Brief, l'une qui permet d'obtenir les descripteurs, l'autre les keypoints.
- La methode Orb, pour Oriented Fast and Rotated Brief, basée sur Fast et Brief
Pour pouvoir comparer les methodes, nous les avons implémenté tous les trois:
if methode == 0: #Surf #print("Target Surf") surf = cv2.xfeatures2d.SURF_create() kpcible,descible = surf.detectAndCompute(reference, None) elif methode == 1: #Fast+Brief #print("Target Fabrief") fast = cv2.FastFeatureDetector_create() kpcible = fast.detect(reference, None) brief = cv2.xfeatures2d.BriefDescriptorExtractor_create() kpcible, descible = brief.compute(reference, kpcible) elif methode == 2: #ORB #print("Target Orb") orb = cv2.ORB_create() kpcible, descible = orb.detectAndCompute(reference, None)
Après quelques tests, il est apparu que la methode Surf est assez rapide et très fiable, la methode Orb est assez fiable mais très rapide, et que Fast et Brief n'est ni rapide ni fiable. On garde la possibilité de choisir entre Orb et Surf pour l'optimisation du programme par la suite.
Semaine 9
Reconnaissance d'image : gestion d'objets multiples
Pour gérer plusieurs objets, nous avons mis en place une classe. Chaque instance de la classe correspondra à un objet à cibler, et la classe comprendra les methodes pour cibles, l'objet, les coordonées de servomoteurs, les keypoints et leurs descripteurs, le masque à appliquer ainsi que le nom de l'objet.
Pour sauvegarder le nom et la position des objets, nous enregistrons ces données dans le fichier trajectoire, qui stocke le nom, et stockera les coordonnées pour les servomoteurs. A partir de ce fichier, on peut retrouver les images de références qui porteront le nom: <nom_de_l_objet>_ref.jpg et le masque associé sous le nom de :<nom_de_l_objet>_fond.png
On va ensuite créer une liste composée des différentes instance, et le programme n'aura qu'à parcourir cette liste en mettant à jour les points clef, les descripteurs, le masque et les coordonnées de l'objet. La classe s'appelle ObjetCible, et peut être retrouvée dans les premières lignes du fichier camera_opencv.py.
Serveur : Problèmes et résolution
Le problème semble être lié a notre application en elle même: En effet, chaque client qui se connecte accède à l'application via un reverse proxy permis par uWSGI. Cependant, l’accès à l'application par plusieurs utilisateurs implique l'utilisation d'une ressource (ici, le flux vidéo de la caméra) de manière partagé. D'où l'incapacité du serveur à traiter plusieurs clients. De plus, les ressources de la BananaPro étant limité, on ne peut pas se permettre de faire tourner l'application plusieurs fois.
A la lumière de ce qui vient d’être énoncé, il apparaît clairement que les points suivants doivent être travaillé en priorité:
- L'application doit pouvoir générer une frame en continue
- L'application doit permettre l'accès en lecture à une frame pour plusieurs clients.
Semaine 10
Réunion Urbik
Lors de cette réunion, nous avons présenté l'avancement du projet, en particulier sur la reconnaissance d'image. Nous avons pu faire la démonstration de notre programme de reconnaissance d'image. Nous avons également parlé de la question du serveur, et réfléchis à la manière de l'implémenter.
Gestion des servomoteurs
Nous nous sommes mis à la gestion des servomoteurs. Sur Banana Pro, il existe la bibliothèque WiringPy qui permet d'utiliser facilement les ports GPIO. Mais malheureusement, il n'existe qu'un seul port capable de gérer la PWM hardware. Par conséquent, le comportement de l'un des servomoteurs est instable et donc inutilisable pour le projet. De plus, l'un des deux servo a besoin d'être constemment remis à jour car il ne garde pas la position qu'on lui donne automatiquement. Comme solution temporaire, nous avons pris le parti d'utiliser un arduino connecté via port série pour le contrôle des servos.
Nous avons donc mis au point un petit protocole de communication entre le BananaPro et l'arduino: Les coordonnées envoyées ont des valeurs comprises entre 0 et 255. Ces valeurs peuvent être codées sur deux caractères hexadécimaux. Nous envoyons donc quatre caractères à l'arduino. A chaque fois qu'il en reçoit quatre, il peut ajuster correctement les servomoteurs.
Stratégie à adopter pour le serveur
Étant donné les difficultés que nous avons rencontré lors de la mise en place du serveur via nginx et Flask, nous avons décidé de concevoir nous même notre serveur web. Ce serveur doit pouvoir être fiable et surtout fonctionner pour plusieurs clients.
L'idée pour notre serveur est donc la suivante:
- L'application génère une frame dans une thread.
- Cette frame est ensuite écrite sur une file de message.
- Une méthode get_frame est mise en place permettant de lire sur cette file de message et de récupérer une frame à un moment donné.
- La thread de traitement d'image doit se faire parallèlement à l’exécution du serveur.
- Chaque client doit pouvoir accéder dans une thread à la frame via la méthode get_frame.
Nous nous sommes donc orienté dans un premier vers un serveur TCP basique. Ce dernier traite les connexion des clients via des threads séparé, et génère une page html en écrivant directement sur la socket. De la même manière, la frame récupéré est écrite sur la socket pour chaque client.
Semaine 11
Découpage en threads et optimisation
Un profiling nous a montré que le plus coûteux en terme de temps est le calcul des points clefs, pour calculer la déformation du masque. Par conséquent, pour gagner en performances, il faut trouver un moyen de limiter le nombre de fois par secondes où le masque à appliquer est calculé. Une solution, que nous avons mise en place, est de séparer la partie du programme qui incruste le masque de la partie qui le calcule. On peut alors limiter son calcul et augmenter le nombre d'actualisations de l'image reçue. Cela permet à l'image sur laquelle on incruste le masque d'être plus fluide.
Plus précisément, pour limiter le nombre de calculs de masque, nous ne le faisons que si il y a une modification de l'image reçue. Dans le cas contraire, comme l'image ne varie pas, le masque doit être ajouté exactement au même endroit et avec la même déformation, et n'a pas besoin d'être modifié. On a aussi limité les calculs du masque en rajoutant la ligne suivante dans le thread qui calcule les masques:
time.sleep(0.1)
On oblige ainsi le thread à être inactif pendant 100 ms, ce qui libère le processeur pour le reste des opérations
Avec ces optimisations, nous obtenons un résultat aux alentours de 12 FPS.
Mise en place d'un serveur HTTP
Après quelques recherches, nous avons vu qu'il existait un module http.serveur construit autour du module TCPsocket que nous avons utilisé la semaine dernière. Ce dernier comporte une série de méthodes pré-établi permettant:
- De gérer les requêtes des clients via les méhodes do_GET, do_POST, etc ... (voir git)
- De traiter ces requêtes dans une thread séparé.
- De générer facilement une page html en envoyant des headers très facilement.
Afin de mettre en place le stream, nous avons repris l'idée suivante. L'idée est la suivante:
- Le client se connecte via l'adresse IP:PORT/index.html et envoie une requête GET index.html au serveur.
- Ceci fait, le serveur gère sa requête et génère une page html comportant une image dont la source se situe à l'adresse suivante: IP:PORT/cam.mjpg.
- Une requête GET cam.mjpg est donc envoyé au serveur, celle-ci est à son tour traité.
- La frame est récupéré via la méthode get_frame et est écrite sur la socket en continue.
Voici ce qu'on peut alors obtenir:
Tests sur plusieurs clients
Les tests effectué ont été très satisfaisant, plusieurs clients parvenant à se connecter au même moment avec un temps de latence assez faible.
La suite consiste à mettre en place une page admin afin de contrôler les servos-moteurs qui équipent la caméra.
Semaine 12
Création d'une page admin
L'idée pour cette page admin est de permettre à un administrateur de modifier la position de caméra à distance pour pouvoir obtenir des images de référence. Pour cela, on procède de la même manière que que la page de la semaine précédente (qui devient une page publique).
La page admin est mise en place de manière suivante:
- La page html est composé de 4 boutons (haut, bas, gauche et droite), envoyant des requêtes de type POST, traité via la méthode do_POST:
- On lit sur la socket pour savoir quelle bouton à été pressé et on appelle la méthode qui agis en conséquence en faisant appel à une méthode correspondante.
- Afin d'éviter le rafraîchissement de la page après l'envoi d'une requête post, on se sert d'Ajax pour écrire un petit script en JavaScript permettant d'envoyer des requêtes POST de manière asynchrone.
En parallèle du serveur, cela va lancer un Thread spécifique qui va être capable de fournir les frames de la caméra, de contrôler les servomoteurs, et d'enregistrer les frames et leurs informations liées si l'administrateur appuie sur le bouton.
Semaine 13
Mise en place d'une authentification pour la page admin
Afin de sécuriser l'accès à notre page admin, on met en place un système d’authentification. Après plusieurs essais, la solution qui à été retenu consiste à utiliser la requête d'authentification propre à HTTP.
De la même manière que pour les requêtes GET et POST, celle-ci est traité via la méthode do_AUTHHEAD:
- A chaque connexion sur la page admin, le client doit se connecter.
- Les identifiants rentrés sont encodé et comparé avec la clé que l'on a choisi (par exemple admin:1234), elle même encodé en base 64.
- Si les identifiants sont corrects, le client possède l'autorisation d'accéder à la page admin.
Documents Rendus
Média : Rapport_Projet_HUBERT_DJERABA.pdf
Lien vers le git: https://archives.plil.fr/thubert/UrbikCamera