IMA3/IMA4 2018/2020 P18 : Différence entre versions
(→Traitement du stream d'images de profondeurs) |
(→Le harnais) |
||
(367 révisions intermédiaires par 4 utilisateurs non affichées) | |||
Ligne 419 : | Ligne 419 : | ||
===Semaine 1=== | ===Semaine 1=== | ||
− | + | La décision de fusionner avec le projet P17 a été prise.En, effet ces derniers conçoivent un dispositif d'aide au déplacement pour un enfant. Ainsi, les fonctions principales telle que le traitement d'informations ou encore le système de communication sont communes à nos projet. | |
− | + | Dans un premier temps nous nous sommes rassemblés pour présenter nos projets ainsi que les objectifs de chacun.Nous avons alors pu distinguer quatre axes de travail: les capteurs, les actionneurs, la gestion d'énergie et la réalisation d'un PCB. | |
− | |||
− | Nous avons | ||
{| class="wikitable" | {| class="wikitable" | ||
Ligne 482 : | Ligne 480 : | ||
Équipe 1 : | Équipe 1 : | ||
− | + | Notre premier travail a été de réfléchir sur les axes d'améliorations concernant le prototype proposé lors du semestre 6. En effet, la rencontre avec Florian nous a permis d'affirmer que le dispositif sous la forme d'une main lui plaisait. Ainsi,dans un premier temps, notre premier objectif a été d'améliorer l'aspect fonctionnel de notre dispositif, comme par exemple un choix de moteur plus performants et plus adapté ou encore trouver le moyen de faire varier l'intensité des moteurs vibrants. Nous pouvons également évoqué toute la partie sonore qui n’était pas encore présente sur le premier prototype. Puis dans un deuxième temps nous nous sommes penchées sur l’aspect design de la main mais aussi de tout le dispositif comportant la RPi,la batterie et la caméra. | |
− | + | ||
− | |||
− | |||
Ligne 500 : | Ligne 496 : | ||
Équipe 1 : | Équipe 1 : | ||
− | * | + | * Gestion des moteurs vibrants: Nous avons défini trois niveau de dangerosité d'un obstacle. Chaque niveau correspondra à une séquence de vibrations spécifique. Par exemple,pour un simple indication (danger faible), les vibrations seront espacées par un délai important.Pour un obstacle ou un changement de relief plus important,ce délai sera raccourcis. Et enfin, en cas de grand danger,les moteurs vibreront en continu. |
− | |||
− | |||
− | |||
− | Pour | + | * Pour avertir l’utilisateur des potentiels dangers nous avons décidé d’utiliser 4 moteurs vibrants. Chacun sera disposé sur la main afin de représenter, chacun, une direction : devant, derrière, droite et gauche.Nous avons, alors, décidé de garder un modèle de main gardant le pouce dans le but d'avoir quatre zones bien définis où placer les moteurs vibrants. |
+ | * Concernant la partie sonore, nous avons choisis le module MP3 DFR0299 pour stocker et exploiter les enregistrements d'indications sonores[https://www.gotronic.fr/art-module-mp3-dfr0299-22404.htm]. De plus,pour connecter les écouteurs,un port jack serait directement soudé sur le PCB.[https://fr.farnell.com/lumberg/1503-02/fiche-femelle-jack-cms-3-5mm/dp/1216979?ost=1216979&ddkey=https%3Afr-FR%2FElement14_France%2Fsearch] | ||
Équipe 2 : | Équipe 2 : | ||
Ligne 513 : | Ligne 507 : | ||
* Angle de vision du D435 : 90°. On le positionne vers le bas pour déterminer les changements de reliefs. C'est ce sur quoi nous allons nous concentrer puisque la reconnaissance du dénivelé est ce qui fait défaut à Florian. Les obstacles en hauteur (type branche), pourraient être détectés par l'ajout d'un capteur à ultrason ou Lidar. | * Angle de vision du D435 : 90°. On le positionne vers le bas pour déterminer les changements de reliefs. C'est ce sur quoi nous allons nous concentrer puisque la reconnaissance du dénivelé est ce qui fait défaut à Florian. Les obstacles en hauteur (type branche), pourraient être détectés par l'ajout d'un capteur à ultrason ou Lidar. | ||
+ | ===Semaine 4=== | ||
− | + | Les commandes de matériels se terminant fin novembre, nous avons constitué la liste des composants nécessaires pour les différentes parties du projet en cherchant les références exactes de chaque élément. | |
− | * | + | * Motoréducteur 2 axes 2218 :[https://www.gotronic.fr/art-motoreducteur-2-axes-2218-21783.htm#complte_desc] |
+ | [[Fichier:MotoreducteurP18.jpg|right|100px]] | ||
− | + | Pour mettre en mouvement les doigts de la main nous avons décidé d’utiliser 3 moto-réducteurs 2axes 2218 du fabricant Gotronic. Leurs tensions d’alimentation est comprise entre 3V et 9V. Leur vitesse à vide sous 6V est de 100 tr/min. La dimension des ses moteurs est intéressante pour notre application, en effet, nous souhaitions mettre 3 de ces moteurs dans le prototype de notre main. | |
+ | <br>Dimensions : 26 x 12 x 10 mm. | ||
+ | <br>Diamètre de l’axe : 3 mm. | ||
+ | <br>Longueur de l’axe : 10 mm. | ||
+ | <br>Diamètre de l'axe arrière : 1 mm. | ||
+ | <br>Longueur de l'axe arrière : 4,5 mm. | ||
− | * | + | *Vibreur miniature VM1201 :[https://www.gotronic.fr/art-vibreur-miniature-vm1201-20685.htm] |
+ | [[Fichier:Moteur vibrants P18.jpg|right|100px]] | ||
− | + | Pour avertir l’utilisateur des potentiels dangers nous avons décidé d’utiliser 4 moteurs vibrants. Chacun sera disposé sur la main afin de représenter, chacun, une direction : devant, derrière, droite et gauche. Ces moteurs nécessitent une tension d’alimentation comprise entre 2V et 5V. | |
+ | <br>Dimensions : Ø10 x 2,7 mm. | ||
− | |||
− | |||
− | |||
− | + | * Contrôleur moteurs TB6612FNG :[https://www.mouser.fr/ProductDetail/757-TB6612FNGC8EL] | |
− | + | Le contrôleur moteur TB6612FNG permet de contrôler deux moteurs. Les pins iIN1 et iIN2 permettent de choisir le sens de rotation du moteur. La PWM permet de réguler la vitesse de rotation. Nous retrouvons alors la commande sur les pins iO1 et iO2. L’alimentation de la partie puissance moteur doit être comprise entre 4.5V et 13.5V. L’alimentation de la partie commande doit être, quant à elle, comprise entre 2.7V à 5V. Le contrôleur moteur ne fonctionne que si /STBY est au niveau de logique 1. | |
− | + | ===Semaine 5=== | |
− | + | * Création des composants nécessaires sur Altium. | |
− | + | * Définition du modèle de main et recherche pour son fonctionnement en fonction de notre choix de moteurs. Il s'agira de modifier le modèle sur un logiciel de CAO comme Fusion360 par exemple pour créer l'espace nécessaire aux moteurs. | |
− | |||
− | |||
− | |||
+ | Possibilité de modèle de la main : [https://www.thingiverse.com/thing:1294517] ,[https://www.thingiverse.com/thing:3000641] | ||
* Écriture du Wiki | * Écriture du Wiki | ||
Ligne 547 : | Ligne 545 : | ||
===Semaine 6=== | ===Semaine 6=== | ||
− | == | + | *Equipe 1 |
+ | |||
+ | Selection definitve de la main : [https://www.thingiverse.com/thing:1294517],[[Fichier:Main Th P18.PNG|right|200px]] | ||
+ | |||
+ | Tuto pour réaliser la main : [https://joel-gibbard.squarespace.com/obtutorials/?offset=1459353025258&category=Tutorial] | ||
+ | La paume de la main peut contenir un PCB de 60mm x 45mm. | ||
+ | L'impression 3D de cette main utilise du filament flexible (200 g) et du filament PLA (100 g). | ||
+ | ===Semaine 7=== | ||
===Documents Rendus=== | ===Documents Rendus=== | ||
Ligne 559 : | Ligne 564 : | ||
http://www.picaxe.com/docs/spe033.pdf | http://www.picaxe.com/docs/spe033.pdf | ||
schéma = p10 | schéma = p10 | ||
+ | |||
+ | Gantt S8 :[[Fichier:GanttP18.png]] | ||
==Projet S8== | ==Projet S8== | ||
− | + | Suite à notre soutenance du S7,il a été décidé que le PCB initialement prévu pour faire fonctionner les moteurs de la main est supprimé. Cette fonction sera assurée par la Raspberry. | |
− | |||
− | |||
− | |||
==Semaine 1== | ==Semaine 1== | ||
Ligne 570 : | Ligne 574 : | ||
===Equipe1=== | ===Equipe1=== | ||
− | + | Nous avons défini la réaction de la main selon différents cas, potentiellement rencontrés par Florian : [[Fichier:Réactions_dispositif.PNG|300px]] | |
− | |||
− | |||
+ | Récupération des fichiers sources de la main à imprimer en 3D: nous pouvons ainsi les modifier pour inclure l'espace nécessaire pour nos moteurs. | ||
+ | <br>Schéma de la main : | ||
+ | <br>[[Fichier:Schema main P18.jpg|300px]] | ||
+ | <br>Ce schéma prend en compte les dimensions des moteurs et le passage de leurs fils d'alimentations, mais aussi le passage des fils pour fermer et ouvrir la main.Nous y avons également placé les emplacements des 4 moteurs vibrants au bout d'un doigt et sur le bord de la main. | ||
Sélection filament pour l'impression 3D : | Sélection filament pour l'impression 3D : | ||
Ligne 583 : | Ligne 589 : | ||
==Semaine 2== | ==Semaine 2== | ||
− | Nous avons récupéré une partie du matériel commandé au S7. | + | Nous avons récupéré une partie du matériel commandé au S7. Ainsi, nous disposons du capteur D435 Intel RealSense,des trois motoréducteurs 2 axes et de deux modules de contrôleurs moteurs. |
===Equipe 1=== | ===Equipe 1=== | ||
− | + | Les installations sur la Raspberry étant en cours, nous avons réalisé le circuit moteur et nous nous sommes familiarisées avec leur fonctionnement à l'aide d'un Arduino. | |
− | |||
− | |||
− | |||
− | Les installations sur la Raspberry étant en cours, nous avons réalisé le circuit | ||
+ | Ce montage est composé d’un Arduino, d'un module contrôleur moteurs TB6612FNG, de deux motoréducteurs et d’un moteur vibrant. | ||
+ | <br>[[Fichier:TestArduino.PNG|300px]] | ||
+ | <br>Comme évoqué précédemment, un contrôleur moteur utilise une PWM pour contrôler la vitesse de rotation et de deux données tout ou rien pour décider du sens de rotation du moteur. | ||
+ | <br>[[Fichier:Controleur moteurP18.png|400px]] | ||
− | |||
− | |||
Code de test du fonctionnement des moteurs sur Arduino : | Code de test du fonctionnement des moteurs sur Arduino : | ||
Ligne 626 : | Ligne 630 : | ||
===Equipe 2=== | ===Equipe 2=== | ||
− | Configuration de la raspberry pi3 pour faire les | + | Configuration de la raspberry pi3 pour faire les premières installations des paquets pour |
− | |||
*la librealsense2 | *la librealsense2 | ||
*OpenCV | *OpenCV | ||
Ligne 633 : | Ligne 636 : | ||
==Semaine 3== | ==Semaine 3== | ||
+ | ===Equipe 1=== | ||
+ | Nous avons pu nous procurer une RPi3 B+ et ainsi effectuer le montage sur une breadboard ainsi que les tests concernant les moteurs mais aussi la partie audio. | ||
+ | <br>[[Fichier:Montage Moteur P18.jpg|200px]] | ||
+ | [[Fichier:Montage moteur P18 zoom.jpg|300px]] | ||
+ | |||
+ | Nous utilisons la bibliothèque Pigpio pour avoir accès aux GPIO et au contrôle de la PWM des moteurs depuis notre code en C. Nous avons besoin de contrôler simultanément les moteurs et les minis moteurs vibrants en plus de l'audio. Pour réaliser l’exécution parallèle nous pensons employer des forks. | ||
+ | |||
+ | |||
+ | Nous utilisons le lecteur multimédia en ligne de commande omxplayer pour lancer l'audio. | ||
+ | <br>Fonctionnement dans le terminal : | ||
+ | <br>Lancement: omxplayer fichier.mp3 | ||
+ | <br>Arrêt : q | ||
+ | <br>Nous devrons par la suite inclure ces deux commandes dans le code C général. | ||
+ | |||
===Equipe 2=== | ===Equipe 2=== | ||
====Configuration de la raspberry:==== | ====Configuration de la raspberry:==== | ||
Ligne 651 : | Ligne 668 : | ||
==Semaine 4== | ==Semaine 4== | ||
+ | ===Equipe 1=== | ||
+ | Suite au montage effectué la semaine dernière, nous avons décidé de faire un hat pour un gain de place et de propreté du montage. Ainsi, deux axes de travail se dégagent pour l'équipe 1 : continuer le code qui contrôle les moteurs et la partie sonore, et modéliser le hat. | ||
+ | <br> Afin d'accentuer le gain de place,nous avons décidé de ne pas utiliser les modules TB6612FNG. En effet, nous disposions des contrôleurs moteurs TB6612FNG et des capacités avec les valeurs souhaitées, ainsi, nous allons les utiliser pour le hat. | ||
+ | <br>Pour la modélisation de la carte, nous avons suivi le diagramme suivant pour reconstituer le module: | ||
+ | <br>[[Fichier:Datasheet TB6612FNG.PNG|400px]] | ||
+ | |||
+ | |||
+ | Cette modélisation sera faite avec le logiciel Fritzing. | ||
+ | <br> | ||
+ | [[Fichier:Montage Hat P18.PNG]] | ||
+ | <br>Nous retrouvons alors la Raspberry, les 4 moteurs vibrants, les 4 motoréducteurs, les deux contrôleurs moteurs et leurs capacités qui leurs sont propres. Nous pouvons aussi remarquer la présence de 6 composants, ce sont des « solder jumper ». Ces derniers permettent de choisir, après l’impression de la carte, les liaisons voulues avec une simple soudure. A noter également qu'il y a un quatrième motoréducteur, la présence de ce dernier laisse la possibilité aux futurs utilisateurs de la carte d’en faire usage pour d'autres projets. | ||
+ | |||
+ | |||
+ | Nous avons mis en place dans le code des forks pour chaque tâches. Néanmoins nous sommes confronté au type d'erreur ci-dessous lors de l'exécution. | ||
+ | <br>[[Fichier:Err pigpio.jpg|300px]] | ||
+ | <br>Nous avons fini par nous rendre compte que l'usage des forks n'est pas supporté par la bibliothèque Pigpio employée et entraîne des conflits. Nous allons donc tester le fonctionnement avec des threads. | ||
+ | |||
+ | ==Semaine 5== | ||
+ | ===Equipe 1=== | ||
+ | |||
+ | La deuxième étape de la modélisation du hat sur Fritzing est la création de la vue schématique. | ||
+ | <br>[[Fichier:Schematique Hat P18.PNG]] | ||
+ | <br>Pour revenir sur l'utilisation des solder jumper, lors de la soudure, nous chercherons à contrôler M8 et M2 par les mêmes pins. | ||
+ | <br>De plus,le hat est créé pour une RPi4. Le choix des pins est le suivant : | ||
+ | <br>[[Fichier:Emplacement pins P18.jpg|200px]] | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | L'utilisation de threads n'est pas bloquante vis-à-vis de la bibliothèque pigpio et permet de faire fonctionner simultanément les moteurs et moteurs vibrant. Nous allons donc mettre en place plusieurs threads pour les différentes composantes du fonctionnement de la main : | ||
+ | *la gestion des moteurs | ||
+ | *celle des minis moteurs vibrants | ||
+ | *celle de l'audio | ||
+ | <br>Le lancement de l'audio depuis le code C peut se faire avec la fonction "execl". Comme l'arrêt de l'audio se fait en écrivant la lettre 'q' dans un terminal nous allons explorer la piste d'un pipe pour envoyer ce caractère sur la sortie standard. | ||
+ | |||
+ | ==Semaine 6== | ||
+ | ===Equipe 1=== | ||
+ | |||
+ | Enfin, la dernière étape avant l’envoie à l’impression de la carte est le routage. La carte est à deux faces et les pistes sont de 16 mil. | ||
+ | <br>Routage complet de la carte :<br>[[Fichier:Routage Hat P18.PNG|500px]] | ||
+ | <br> Routage de la face supérieure de la carte : | ||
+ | <br>[[Fichier:Hat face superieur P18.PNG|400px|]] | ||
+ | <br> Routage de la face inférieure de la carte : | ||
+ | <br>[[Fichier:Hat face inferieur P18.PNG|400px]] | ||
+ | |||
+ | |||
+ | |||
+ | La piste du pipe ne donne pas de résultats concluants. L'envoi du caractère n'arrête pas le lecteur. | ||
+ | <br>En parallèle des tests sur l'audio, nous créons et organisons le code en sous-fonctions pour chaque tâche que vont lancer les threads. | ||
+ | |||
+ | |||
+ | |||
+ | === Suite à la situation de confinement, nous avons trouvé plus pertinent de retranscrire l'avancée du travail en grandes parties plutôt qu'en semaines === | ||
+ | |||
+ | ==Mise au point de l'avancement du projet et tâches à réaliser en condition de confinement (18/03/2020)== | ||
+ | |||
+ | ===Partie matérielle=== | ||
+ | |||
+ | |||
+ | * Matériel disponible pour le travail à distance (Raspberry, capteur, montage, coque capteur) | ||
+ | * POINTS INCERTAINS : | ||
+ | ** Récupération du hat pour souder les composants | ||
+ | ** Impression 3D de la main puis sa récupération | ||
+ | ** Liaison des différentes parties sur le harnais | ||
+ | |||
+ | ===Partie réalisation=== | ||
+ | |||
+ | * Code du capteur : Fonction de moyennage faite. Il reste à délimiter les différentes zones, étalonner, découper les frames reçues, détecter des trous et des reliefs en utilisant les fonctions déjà réalisées | ||
− | + | * Code des actionneurs : lancer tous les threads et les fermer comme souhaité, notamment au niveau de l'audio | |
− | |||
+ | * Faire la liaison entre les deux codes | ||
− | |||
+ | * Début de réalisation du harnais | ||
− | [[Fichier: | + | ===Organisation=== |
+ | |||
+ | * Création d'un git | ||
+ | ** Lien : https://archives.plil.fr/nourekhlas/P18.git | ||
+ | ** Ajout de tous les codes | ||
+ | ** Réalisation d'un document précisant le découpage du code de gestion du capteur permettant la répartition du travail [[Fichier:Modularite_code_p18.pdf]] | ||
− | |||
− | + | ==Configuration et installation du SDK sur la raspberry== | |
− | ==Configuration et | ||
===Configuration de la raspberry=== | ===Configuration de la raspberry=== | ||
Ligne 674 : | Ligne 762 : | ||
====Raspberry pi 4==== | ====Raspberry pi 4==== | ||
− | *la première étape consiste à | + | *la première étape consiste à configurer les interfaces réseaux : |
Dans le fichier etc/network/interfaces: | Dans le fichier etc/network/interfaces: | ||
Ligne 850 : | Ligne 938 : | ||
*création d'un dossier build | *création d'un dossier build | ||
*cmake -L .. 2> error | *cmake -L .. 2> error | ||
− | *Installation des libraries nécessaires manquantes | + | *Installation des libraries nécessaires manquantes indiquées dans le fichier error |
*make | *make | ||
Pour tester : vérfier la présence des executables dans le dossier tools et/ou examples (test : connexion D435 et lancer rs-enumerate-devices) | Pour tester : vérfier la présence des executables dans le dossier tools et/ou examples (test : connexion D435 et lancer rs-enumerate-devices) | ||
Ligne 859 : | Ligne 947 : | ||
[[Fichier:Partie gadrillage capteurP18.png|400px|right]] | [[Fichier:Partie gadrillage capteurP18.png|400px|right]] | ||
L'algorithme de traitement des données se décompose en 3 "grandes" parties: | L'algorithme de traitement des données se décompose en 3 "grandes" parties: | ||
− | *Etape 1: | + | *Etape 1: Détection des devices et sélection du premier |
*Etape 2: Récupération des données | *Etape 2: Récupération des données | ||
*Etape 3: Algorithme de traitement | *Etape 3: Algorithme de traitement | ||
Ligne 868 : | Ligne 956 : | ||
l'idée serait de subdiviser l'image (frame) recueillie en 3 ou 4 plages de profondeurs ; | l'idée serait de subdiviser l'image (frame) recueillie en 3 ou 4 plages de profondeurs ; | ||
On va faire un premier test sur un sol uniformément plat (dans une salle) en fixant le capteur au même niveau que l'aurait Florian; | On va faire un premier test sur un sol uniformément plat (dans une salle) en fixant le capteur au même niveau que l'aurait Florian; | ||
− | une fois | + | une fois la frame obtenu on détermine, pour chaque plage de profondeur la moyenne de tous les pixels concernés pour déterminer la valeur représentant le sol dans cette partie là. |
Puis on stocke ces valeurs ci dans des variables globales qui seront ré-utilisées dans le reste du code. | Puis on stocke ces valeurs ci dans des variables globales qui seront ré-utilisées dans le reste du code. | ||
− | *Calcul de l'écart entre chaque pixel et la moyenne de sol de la zone du pixel, si cet écart est supérieur au seuil prédéfini, l'indice de ce pixel est | + | *Calcul de l'écart entre chaque pixel et la moyenne de sol de la zone du pixel, si cet écart est supérieur au seuil prédéfini, l'indice de ce pixel est ajouté dans un tableau pixels_suspects. |
− | *On teste ensuite l'étendue | + | *On teste ensuite l'étendue de l'obstacle en regardant la zone de pixels autour (un périmètre de 30 pixels autour) puis on garde la moyenne de profondeur de cette zone. |
− | *On compare la moyenne des zones détectées pour en déterminer l'obstacle le plus " | + | *On compare la moyenne des zones détectées pour en déterminer l'obstacle le plus "dangereux". |
− | *une fois celui ci déterminé, on garde ces valeurs pour faire un tracking de | + | *une fois celui ci déterminé, on garde ces valeurs pour faire un tracking de cet obstacle le temps qu'il sorte du champ de vision ou qu'un obstacle plus dangereux soit détecté. |
*on génère ensuite les 3 chiffres qui vont être transmis à l'autre partie du code (actionneurs) | *on génère ensuite les 3 chiffres qui vont être transmis à l'autre partie du code (actionneurs) | ||
− | **Le premier indiquera si c'est un obstacle en hauteur ou un trou (signe de la moyenne des écarts; | + | **Le premier indiquera si c'est un obstacle en hauteur ou un trou (signe de la moyenne des écarts); |
**Le second relèvera la dangerosité de l'obstacle : proportionnelle à la moyenne d'écart préalablement calculée; | **Le second relèvera la dangerosité de l'obstacle : proportionnelle à la moyenne d'écart préalablement calculée; | ||
**Le troisième indiquera la position dans l'espace (voir photo exemple) | **Le troisième indiquera la position dans l'espace (voir photo exemple) | ||
Ligne 919 : | Ligne 1 007 : | ||
void rs2_get_depth_table(rs2_device* dev, STDepthTableControl* group, int mode, rs2_error** error); | void rs2_get_depth_table(rs2_device* dev, STDepthTableControl* group, int mode, rs2_error** error); | ||
− | + | <br><br> | |
− | ====Test de | + | ==Code Capteur (final)== |
+ | |||
+ | ====Test de récupération des données de profondeur==== | ||
Ligne 929 : | Ligne 1 019 : | ||
Le code c'est finalement articulé sous 4 grandes parties: | Le code c'est finalement articulé sous 4 grandes parties: | ||
− | + | ====Récupération des données du flux et leur transformation en matrice de profondeur:==== | |
*Cette partie a été relativement rapide, on s'est principalement basée sur les fonctions fournies par le SDK librealsense et les exemples. Des fonctions spécifiques nous permettre de configurer le flux de données (résolution,format,fps...) rs2_config_enable_stream, puis rs2_get_video_stream_resolution pour obtenir le profil réel du flux; puis enfin rs2_get_frame_data. | *Cette partie a été relativement rapide, on s'est principalement basée sur les fonctions fournies par le SDK librealsense et les exemples. Des fonctions spécifiques nous permettre de configurer le flux de données (résolution,format,fps...) rs2_config_enable_stream, puis rs2_get_video_stream_resolution pour obtenir le profil réel du flux; puis enfin rs2_get_frame_data. | ||
La fonction get frame data nous permet de récupérer un tableau de la profondeur correspondant a chaque pixel du frame. | La fonction get frame data nous permet de récupérer un tableau de la profondeur correspondant a chaque pixel du frame. | ||
(rs2_get_frame_data renvoie un unint16_t *) | (rs2_get_frame_data renvoie un unint16_t *) | ||
− | + | ====Premier filtrage pour cibler la zone voulue:==== | |
*Après notre discussion avec Florian et ses accompagnatrices nous avons décidez de se focaliser principalement sur les reliefs et sur le sol. Cette décision nous a permis de : | *Après notre discussion avec Florian et ses accompagnatrices nous avons décidez de se focaliser principalement sur les reliefs et sur le sol. Cette décision nous a permis de : | ||
**Fortement limiter le nuage de points | **Fortement limiter le nuage de points | ||
Ligne 945 : | Ligne 1 035 : | ||
[[Fichier:filtrage1.png|300px|left]] | [[Fichier:filtrage1.png|300px|left]] | ||
<br><br><br><br><br> | <br><br><br><br><br> | ||
− | + | ====Élimination du sol:==== | |
*'''Théorie''' | *'''Théorie''' | ||
[[Fichier:position_p18.jpg|300px|right]] | [[Fichier:position_p18.jpg|300px|right]] | ||
Nous sommes parties de l’hypothèse que le capteur sera correctement fixe sur le harnais pour vérifier les distances fixées sur ce croquis a droite (Valeurs fixées en macros). Nous avons ensuite diviser l'image de profondeur perçue en NB_ZONES différentes. Chaque zone est espacée de l'autre d'un pas_zone qui nous permet en utilisant une simple règle de Pythagore de déterminer la profondeur moyenne de la zone. | Nous sommes parties de l’hypothèse que le capteur sera correctement fixe sur le harnais pour vérifier les distances fixées sur ce croquis a droite (Valeurs fixées en macros). Nous avons ensuite diviser l'image de profondeur perçue en NB_ZONES différentes. Chaque zone est espacée de l'autre d'un pas_zone qui nous permet en utilisant une simple règle de Pythagore de déterminer la profondeur moyenne de la zone. | ||
− | [[Fichier:seuil.png| | + | [[Fichier:seuil.png|200px|left]] |
− | [[Fichier:etalon.png|300px|left]] | + | [[Fichier:etalon.png|500px|left]] |
− | *'''Limites''' | + | <br><br><br><br><br><br><br><br><br> |
+ | Après avoir déterminer ces seuils, on élimine le sol en fixant a 0 toutes les valeurs a l’intérieur de l'intervalle [seuil_zone[zone]-dz ; seuil_zone[zone]+dz] | ||
+ | [[Fichier:elim.png|300px|left]] | ||
+ | <br><br><br><br><br> | ||
+ | A la fin de ces deux filtrage il ne reste plus dans cette matrice que les anomalies ou les profondeurs qui semblent suspects selon nos critères de sélection. | ||
+ | [[Fichier:mat_post_filtr.png|300px|centre]] | ||
+ | |||
+ | On obtient donc une matrice comme celle ci ou les X correspondent a des valeurs non nulles de profondeurs.t Cette dispositions de valeurs nous fait donc penser a des clusters (rassemblements de données présentant les mêmes caractéristiques). | ||
+ | *'''Limites''' | ||
+ | **Cette méthode de détection de sol n'est absolument pas la méthode idéale. L’idéal serait de faire une détection dynamique (en temps réel) en revanche en augmentant le nombre de zones (en diminuant le pas) on obtient un résultat plutôt correct. | ||
+ | **Le second point négatif de cet méthode est la détermination de la marge dz que l'on fixe. A travers les tests pratiques on pourras déterminer un dz cohérent avec le type de sol sur lequel se balade Florian et des conditions d'utilisations. | ||
+ | ** On peut s’inspirer d'autre pistes pour trouver un algorithme d’élimination plus performant (cf pistes a développer) | ||
+ | |||
+ | ====Détection des Clusters dans la matrice et leur traitement:==== | ||
+ | Ce qui était parti d'une intention de détection de clusters c'est transformer en une détection de voisins. | ||
+ | L’idée principale est donc de détecter un pixel dont la valeur est différent de 0, une fois celui ci détecté, une structure Cluster_t est initialisée: | ||
+ | typedef struct cluster{ | ||
+ | int moyenne; | ||
+ | int zone; | ||
+ | int type; | ||
+ | int debut_y; | ||
+ | int debut_x; | ||
+ | int taille; | ||
+ | int danger; | ||
+ | } Cluster_t | ||
+ | |||
+ | |||
+ | typedef struct list_c{ | ||
+ | struct cluster c[MAX_CLUSTER]; | ||
+ | int nb_clusters; | ||
+ | }List_c; | ||
+ | |||
+ | Ces clusters sont ensuite identifies par une fonction clust_detec pour en déterminer la taille et la valeur moyenne et donc en ressortir un indice de dangerosité. | ||
+ | Les Clusters sont tous ensuite classes dans une liste ordonnée dans l'ordre croissant de dangerosité puis le(s) clusters les plus dangereux seront signalés, sous la forme de 3 digits, au code des actionneurs grâce a une FDM IPC. | ||
+ | |||
+ | *'''Récursivité et ses limites'''[[Fichier:clustexemple.png|300px|right]] | ||
+ | La première approche que j'ai eu , était pour moi la plus intuitive : la récursion. | ||
+ | void clust_detec(int ** depth,int rows,int row_length,int x,int y,int * taille,int * moyenne) | ||
+ | { if(x<0||x>=rows||y<0||y>=row_length) | ||
+ | return; | ||
+ | int ind= x*row_length+y; | ||
+ | if((*depth)[ind]==0) | ||
+ | return; | ||
+ | *moyenne += (*depth)[ind]; | ||
+ | (*taille)=(*taille)+1; | ||
+ | (*depth)[ind]=0; | ||
+ | //Itération sur les voisins | ||
+ | clust_detec(depth,rows,row_length,x+1,y,taille,moyenne); | ||
+ | clust_detec(depth,rows,row_length,x-1,y,taille,moyenne); | ||
+ | clust_detec(depth,rows,row_length,x,y+1,taille,moyenne); | ||
+ | clust_detec(depth,rows,row_length,x,y-1,taille,moyenne); | ||
+ | } | ||
+ | Ce code est fonctionnel et donne des résultats correctes mais il est extrêmement coûteux en termes de mémoire sur la stack. En effet la pile d’exécution est inondée par des appels récursifs de clust_detec et génère donc, au bout d'un certain moment, une erreur de segmentation fault (stack overflow). | ||
+ | |||
+ | <br> | ||
+ | *'''Non récursif''' | ||
+ | **Création d'une stack / queue qui remplace la file d’exécution. | ||
+ | Pour régler ce problème de stack overflow et après des recherches intensive sur internet, j'ai eu la brillante idée de créer une structure stack qui me permettra de simuler/remplacer le comportement de la file d’exécution mais dans un espace mémoire plus grand. | ||
+ | |||
+ | struct stack | ||
+ | { | ||
+ | int maxsize; // define max capacity of stack | ||
+ | int top; | ||
+ | struct coordonnees *items; | ||
+ | }; | ||
+ | |||
+ | struct stack* newStack(int capacity) | ||
+ | { | ||
+ | struct stack *pt = (struct stack*)malloc(sizeof(struct stack)); | ||
+ | |||
+ | pt->maxsize = capacity; | ||
+ | pt->top = -1; | ||
+ | pt->items = (struct coordonnees *)malloc(sizeof(struct coordonnees) * capacity); | ||
+ | |||
+ | return pt; | ||
+ | } | ||
+ | Le code source a été trouvé sur internet puis remodelisé pour s'adapter a notre utilisation de la stack. | ||
+ | Les fonctionnalités push(SP,x,y); pop(SP); isEmpty ... sont présentes dans l'archive git (cf lien) | ||
+ | En C++ , C# ou java on aurait pu directement utiliser la classe stack implémentée. | ||
+ | |||
+ | **'''Flood fill non récursif''' | ||
+ | Pour transformer l'algorithme cluster_detec en un algorithme non récursif je me suis inspirée des algorithmes Flood Fill. En effet ces algorithmes sont utilisés pour détecter les pixel d'une couleur spécifique puis changer sa couleur et celles de tous ses voisins si elles ne correspondent pas à la couleur désirée. Souvent implémenté de manière récursive, cet algorithme récursif a montré ses limites lorsqu'on traite d'un grand nombre de données. c'est pour cela que des algorithmes non récursif de Flood fill ont émergés | ||
+ | [https://stackoverflow.com/questions/21865922/non-recursive-implementation-of-flood-fill-algorithm] | ||
+ | A l'aide de l'algorithme présenté comme réponse sur ce lien j'ai pu élaborer une version non récursive de notre cluster_detec qui possède des résultats identiques sans les problèmes de stack overflow. | ||
+ | void clust_detect_nr(int ** depth,int rows,int row_length,int x,int y,int * taille,int * moyenne) | ||
+ | { | ||
+ | static const int dx[4] = {0, 1, 0, -1}; | ||
+ | static const int dy[4] = {-1, 0, 1, 0}; | ||
+ | struct stack * sp=newStack(rows*row_length); | ||
+ | int i=0; | ||
+ | push(sp,x,y); | ||
+ | while(!isEmpty(sp)) | ||
+ | { struct coordonnees c = pop(sp); | ||
+ | //printf("pop\n"); | ||
+ | for(int i = 0; i < 4; i++) { | ||
+ | int vx = c.x + dx[i]; | ||
+ | int vy = c.y + dy[i]; | ||
+ | if(vx >= 0 && vx < rows && vy >= 0 && vy < row_length && (*depth)[vx*row_length+vy] !=0) | ||
+ | { | ||
+ | *moyenne += (*depth)[ vx*row_length+vy]; | ||
+ | (*taille)=(*taille)+1; | ||
+ | (*depth)[vx*row_length+vy]=0; | ||
+ | push(sp,vx,vy); | ||
+ | } | ||
+ | }} | ||
+ | free(sp->items); | ||
+ | free(sp); | ||
+ | return; | ||
+ | } | ||
+ | |||
+ | ====Transmission des données par IPC:==== | ||
+ | On utilise les librairies systèmes (valables sur raspbian) | ||
+ | #include <sys/wait.h> | ||
+ | #include <sys/types.h> | ||
+ | #include <sys/ipc.h> | ||
+ | #include <sys/shm.h> | ||
+ | #include <sys/msg.h> | ||
+ | #include <sys/stat.h> | ||
+ | libipc :[[Fichier:Libipc.zip]] | ||
+ | //IPC | ||
+ | #define CLE_MSG (key_t)1000 | ||
+ | #define TYPE_REPONSE_CLIENT 3 | ||
+ | |||
+ | typedef struct { | ||
+ | int digit_y; | ||
+ | int digit_type; | ||
+ | int digit_danger; | ||
+ | } infos_t; | ||
+ | |||
+ | typedef struct { | ||
+ | int zone; | ||
+ | int type; | ||
+ | int danger; | ||
+ | } donnees_t; | ||
+ | |||
+ | /* Structures pour les reponses */ | ||
+ | typedef struct { | ||
+ | long type; | ||
+ | donnees_t donnees; | ||
+ | } reponseClient_t; | ||
+ | |||
+ | int recuperationFDM (void) { | ||
+ | int msgid; | ||
+ | if((msgid = msgget((key_t)CLE_MSG, 0)) == -1) { | ||
+ | perror("Erreur de recuperation de la FDM\n"); | ||
+ | exit(EXIT_FAILURE); | ||
+ | } | ||
+ | printf("msgidClient %d\n",msgid); | ||
+ | return msgid; | ||
+ | } | ||
+ | |||
+ | void envoiReponseClientFDM (donnees_t donnees) { | ||
+ | reponseClient_t reponse; | ||
+ | /* Récupération de la file de messages */ | ||
+ | int msgid = recuperationFDM (); | ||
+ | /* Envoi de la reponse */ | ||
+ | reponse.type=TYPE_REPONSE_CLIENT; | ||
+ | reponse.donnees=donnees; | ||
+ | if(msgsnd(msgid, &reponse, sizeof(reponseClient_t) - sizeof(long), 0) == -1) { | ||
+ | perror("Erreur d'envoi de la réponse\n"); | ||
+ | exit(EXIT_FAILURE); | ||
+ | } | ||
+ | printf("Réponse envoyée\n"); | ||
+ | } | ||
+ | |||
+ | Envoi des informations après chaque fin de traitement d'image de profondeurs | ||
+ | donnees_t donnees; | ||
+ | donnees.danger = clusts.c[clusts.nb_clusters-1].danger; | ||
+ | donnees.type = clusts.c[clusts.nb_clusters-1].type; | ||
+ | if(clusts.c[clusts.nb_clusters-1].debut_y>=0 && clusts.c[clusts.nb_clusters-1].debut_y<width/3) | ||
+ | donnees.zone = 1;//Gauche | ||
+ | else if(clusts.c[clusts.nb_clusters-1].debut_y>=width/3 && clusts.c[clusts.nb_clusters-1].debut_y<(2*width/3)) | ||
+ | donnees.zone = 2;//Milieu/Devant | ||
+ | else if(clusts.c[clusts.nb_clusters-1].debut_y>=(2*width/3) && clusts.c[clusts.nb_clusters-1].debut_y<width) | ||
+ | donnees.zone = 3;//Droite | ||
+ | envoiReponseClientFDM(donnees); | ||
+ | ====== LIMITES====== | ||
+ | La majorité du traitement repose sur les deux premières phases de filtrages qui seront , dans l'état actuel, affinés et modifiés après plusieurs essais pratiques pour augmenter la précision et la pertinence des valeurs traitées lors de la détection de cluster. | ||
+ | Malheureusement ces essais ne pourront être effectués à cause du confinement. | ||
+ | |||
+ | =====Pistes à developper ===== | ||
+ | Il existe des méthodes pour la détection du contour des objets dans une image, et une solution serait d’utiliser ces méthodes afin de localiser les bords des obstacles. | ||
+ | lien code sur l'archive git. | ||
+ | |||
+ | ======Filtre de Sobel====== | ||
+ | Cette méthode consiste à utiliser un filtre de Sobel, celui-ci va calculer le gradient de l’intensité (de la distance) de chaque pixel. Un contour se caractérise par une rupture d’intensité dans l’image, ces changements d’intensités se traduisent par des discontinuités dans la profondeur. | ||
+ | Ce filtre comporte deux dérivées, une dérivée pour la direction horizontale et une dérivée pour la direction verticale. Ceci permet d’éliminer l’inclinaison du sol par rapport à la caméra et conserver uniquement les contours des objets. Il suffit de détecter pour chaque pixel sa variation par rapport à ses voisins de gauche et de droite et de dessus et dessous. | ||
+ | La méthode consiste à calculer le gradient en H puis en V autour du pixel. | ||
+ | |||
+ | [[Fichier:gx.png]] [[Fichier:gy.png]] | ||
+ | |||
+ | Donc à chaque position (x, y) d’un pixel, il faut calculer la différence entre les 3 pixels à sa gauche avec les 3 à sa droite selon les matrices de pondération données ci-dessus pour déterminer le gradient en x, les 3 pixels au dessus avec les 3 pixels en dessous pour avoir le gradient en y. Le pixel fait partie d’un contour si son contour est élevée. | ||
+ | |||
+ | ======Initialisation des valeurs et tableaux====== | ||
− | + | int gx; // Gradient suivant l’axe x | |
− | + | int gy; // Gradiant suivant l’axe y | |
− | + | int g; // Module du gradient | |
+ | int E[height][width];// Structure d'entrée (image de profondeur) | ||
+ | int Sobel[height][width]; // Structure de la transformée de Sobel | ||
+ | |||
+ | L’initialisation comporte les deux dérivées des directions gx et gy, le module du gradient est calculé avec g. On initialise un tableau d’entrée E[ ][ ] qui sera traité avec le filtre de Sobel Sobel[ ][ ], et enfin un dernier tableau Sr [ ][ ] de dimension plus réduite pour permettre l’affichage du résultat en mode texte. | ||
− | * | + | Ici l’image sera chargée dans le tableau E, on va parcourir les lignes et colonnes de l'image. |
+ | for (y = 0; y < height; ++y) | ||
+ | { | ||
+ | for (x = 0; x < width; ++x) | ||
+ | E[y][x] = *depth_frame_data++; | ||
+ | } | ||
− | |||
− | |||
+ | La création du filtre de Sobel se fait de la façon suivante: | ||
− | + | // printf("Début Sobel"); | |
− | + | for (y = 0; y < height; ++y) | |
− | + | { | |
− | + | for (x = 0; x < width; ++x) | |
− | + | { if (x==0 || y==0 || x==width-1 ||y==height-1) | |
+ | g=0; // mise à 0 du bord | ||
+ | |||
+ | else | ||
+ | { //derivee en x | ||
+ | gx=(- (E[y-1][x-1] + E[y][x-1] + E[y][x-1] + E[y+1][x-1]) + (E[y-1][x+1] + E[y][x+1] + E[y][x+1] + E[y+1][x+1])); | ||
− | === | + | //derivee en y |
+ | gy=(- (E[y-1][x-1] + E[y-1][x] + E[y-1][x] + E[y-1][x-1]) + (E[y+1][x-1] + E[y+1][x] + E[y+1][x] + E[y+1][x+1])); | ||
+ | |||
+ | //amplitude | ||
+ | g=sqrt((float)gx*gx+(float)gy*gy); | ||
+ | // if (g>255) g=255; //écrêtage à 255 pour avoir un résultat sur un seul octet | ||
+ | |||
+ | } | ||
+ | Sobel[y][x]=g; // Et voici la structure contenant la transformée de Sobel | ||
+ | Sr[y / HEIGHT_RATIO][x / WIDTH_RATIO] += (g/poids); | ||
− | + | Quelques recherche sur internet ont été très utiles pour la création du filtre, celui-ci contient les dérivées en x et y , les matrices dérivées vont ensuite aider au calcul du gradient avec g=sqrt(gx*2+gy*2). | |
+ | En sortant de la boucle on obtient notre filtre Sobel[ ][ ], on peut par la suite déterminer une image à l’affichage Sr[ ][ ] avec un ratio de 8x5 afin d’obtenir une image plus grande que celle proposée dans le code original de l’exemple “depth.c” du SDK de la caméra D435. | ||
+ | Dans le tableau Sr [ ][ ] on va faire la somme des HEIGHT_RATIO x WIDTH_RATIO pixels encadrant du tableau Sobel[ ][ ] et la diviser par le nombre de pixels défini dans la variable poids = HEIGHT_RATIO x WIDTH_RATIO. | ||
− | + | ====Boucle d'affichage==== | |
+ | La boucle d’affichage de l’image en mode texte se fait comme suit: | ||
+ | Chaque pixel sera affiché sous la forme d’un caractère qui correspondra à la valeur du pixel. | ||
+ | On a créé un tableau pixels[ ] qui contient les caractères à afficher | ||
+ | static const char* pixels = " .0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; | ||
+ | Comme il n’est pas possible d’avoir autant de caractères différents que de valeurs de gradient, on fait un écrêtage à 32 (cette valeur aurait pu être augmenté à 37 avec la table que nous avons définie). | ||
− | * | + | for (y = 0; y < rows; y++) |
+ | { | ||
+ | for (x = 0; x < row_length; x++) | ||
+ | {int pixel_index = (Sr[y][x]/8); // on divise par 8 pour avoir une échelle de correspondant aux nombre de valeurs pouvant être affichées | ||
+ | if (pixel_index > 32) | ||
+ | pixel_index=32; | ||
+ | *out++ = pixels[pixel_index]; // on remplit la case buffer[out] du tableau buffer. | ||
+ | |||
+ | } | ||
+ | *out++ = '\n'; // on insère un retour à la ligne à la fin de chaque ligne | ||
+ | |||
+ | } | ||
+ | *out++ = 0; // On remet l’index du tableau buffer à 0 | ||
+ | |||
+ | printf("\n%s", buffer); // On imprime l’image sur le terminal | ||
− | * | + | rows et row_length correspondent au nombre de lignes et de colonnes de l’image divisé par notre ratio 8*5. Nous allons charger pixel_index avec les données de notre tableau Sr[ ][ ]/8. En effet, le tableau Sobel[ ][ ] contient des valeurs entre 0 et 255 qu’il faut ramener à une échelle de 0 à 32 en divisant la valeur par 8. |
+ | Ensuite il suffit de déterminer un seuil à ne pas dépasser pour le contour final. Si une valeur de notre tableau est supérieure à 32 alors elle sera limitée à 32 pour ne pas déborder dans la recherche d’un élément en dehors des limites du tableau pixel[ ]. Puis on affiche notre structure pour visualiser les contours. | ||
− | + | ====Limites==== | |
− | + | Grâce à cette méthode nous pouvons détecter les obstacles et on remarque qu’ils sont assez facilement reconnaissables. On constate qu’il y a du bruit dans l’image et qu’il faudrait éventuellement supprimer ce bruit et améliorer ce filtre pour avoir une très bonne précision pour l’analyse des reliefs sur terrain accidenté Ceci pourra se faire facilement en éliminant les valeurs inférieures à un seuil à déterminer. Les obstacles sont délimités par des pixels dont le gradient est supérieur à un seuil à affiner. La dangerosité peut être déterminée par la taille de la zone et sa position dans l’image.. La localisation des objets devant la caméra est relativement facile à effectuer par rapport aux coordonnées de l’objet dans l’image. La distance de l’objet peut être déterminée par rapport à sa projection dans l’image, ou mieux en récupérant la valeur de la distance dans le tableau E[ ][ ]. | |
− | + | ======Le résultat du filtre de Sobel====== | |
− | + | [[Fichier:testSobel.png|200px]] [[Fichier:Trouspapier.png|200px]] [[Fichier:Trous.jpg|200px]] [[Fichier:Sobel.png|200px]] | |
− | |||
− | |||
==Exécution des codes après modification== | ==Exécution des codes après modification== | ||
Ligne 1 048 : | Ligne 1 373 : | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
===Erreur de compilation=== | ===Erreur de compilation=== | ||
Ligne 1 081 : | Ligne 1 396 : | ||
[[Fichier:compilenoerror.png|600px]] | [[Fichier:compilenoerror.png|600px]] | ||
+ | |||
+ | ==Le Hat== | ||
+ | |||
+ | ===Partie soudure=== | ||
+ | |||
+ | La partie soudure du hat était délicate du point du vue de la précision de soudure afin ne pas déborder sur les pins du même composant qui sont espacées de 0,5 mm seulement. Plusieurs essais ont du être faits, les premières soudures ont été faites sur les condensateurs avec l'étain fourni qui était sans plomb, j'ai trouvé des difficultés à appliquer l'étain sur les pins des composants. La panne s'est vite oxydée, le problème venait peut être de l'étain ou du nettoyage de la panne sur une éponge humide. Après plusieurs essais de nettoyages de la panne, celle-ci est restée noire. Une éponge métallique et de l'étain avec du plomb que je trouve plus facile à manipuler ont suffit pour retrouver une panne brillante. | ||
+ | |||
+ | Les conseils de Monsieur Redon sur la soudure des CMS ont été très utiles, et m'ont permis d’acquérir la technique de soudure de ces composants. Après plusieurs séances d’entraînement, les CMS ont été soudés proprement. Le plus dur a été de souder les contrôleurs moteurs, il fallait être rapide pour ne pas trop chauffer le composant mais aussi être précis. | ||
+ | |||
+ | Après avoir soudé les contrôleurs sur une nouvelle plaque à cause des échecs de soudure, il a fallut souder les condensateurs, les connecteurs pour les moteurs et les moteurs vibrants et enfin les connecteurs pour la Raspberry. Ces composants ont été fournis par Monsieur Redon afin d'aboutir au mieux à la finalisation du hat. | ||
+ | Le hat est donc composé de six capacités, deux contrôleurs moteurs, de huit connecteurs pour les moteurs commandant l'ouverture et la fermeture de la main et de moteurs vibrants. Enfin d'un connecteur pour la connexion à la Raspberry. On note que d'après le schématique, il faut faire des ponts de soudure sur l’arrière de la plaque pour mettre les moteurs M3 et M8 en service. Les moteurs M8 et M2 sont commandés ensemble. | ||
+ | Grâce à un moteur DC et des vibreurs, le hat a pu être testé avec une Raspberry Pi3 Model B. | ||
+ | |||
+ | [[Fichier:Hatdevant.jpg|400px]] [[Fichier:Hatderriere.jpg|400px]] | ||
+ | |||
+ | ===Partie code=== | ||
+ | |||
+ | L'implémentation du code pour faire le test du hat s'est faite sur une Raspberry Pi3. Le test à été fait avec un seul moteur, c'est à dire, qu'un seul connecteur pour moteur à été soudé afin de vérifier le premier contrôleur puis un deuxième connecteur pour le deuxième moteur pour vérifier le second contrôleur. Une fois la vérification faite les deux autres connecteurs moteurs ont été soudé. | ||
+ | |||
+ | La librairie de commande des GPIO que nous avons utilisées avec la Raspberry Pi, nécessite non pas d’initialiser avec les numéros des pins de celle-ci mais avec les numéros des GPIO qui se trouve sur la Datasheet de la Raspberry. L’initialisation des GPIO et des moteurs correspondants s'effectue conformément à la figure ci-dessous. | ||
+ | |||
+ | [[Fichier:GPIO.png|200px]] [[Fichier:Moteuretvib.jpg|350px]] <br> [[Fichier:Raspberrypi3.jpg|450px]][[Fichier:testhat.mp4|frame|Test du hat]] | ||
+ | |||
+ | Remarque : Lors du lancement du code il est nécessaire de préciser sudo avant l’exécution du programme sinon une erreur apparaît ne reconnaissant pas les fonctions GPIO utilisées dans le code. Ceci est une particularité de la librairie piggpio qui ne peut être utilisé qu'en mode sudo. | ||
+ | |||
+ | == Code de gestion de la main == | ||
+ | Les codes sont sur le git : https://archives.plil.fr/nourekhlas/P18.git | ||
+ | <br> | ||
+ | |||
+ | |||
+ | [[Fichier:Schema global actions main.PNG|500px]] | ||
+ | |||
+ | <br>Les fonctions s'articulent selon les valeurs les trois paramètres suivant : | ||
+ | [[Fichier:Valeurs parametres.PNG|400px]] | ||
+ | |||
+ | |||
+ | <br>Le comportement des actionneurs est fonction de ces paramètres. Les moteur dépendent de danger, les vibrations de danger et direction, l'audio des trois à la fois. | ||
+ | *Moteurs : la valeur de danger conditionne la vitesse de fermeture par le biais de la PWM et le temps avant la réouverture de la main | ||
+ | **Un ajustement sera nécessaire à trouver entre la vitesse donnée au moteur contrôlant le pouce et les deux autres reliés aux autres doigts pour avoir un mouvement le plus naturel possible. | ||
+ | *Minis moteurs vibrants : selon la direction, un seul des trois moteurs se déclenche. Pour un danger faible et intermédiaire les vibrations se font par intermittence, plus ou moins rapidement. Lors d'un grand danger, elles sont continues. Un cycle de vibration prend au maximum six secondes. | ||
+ | <br>[[Fichier:Vibrations.PNG|300px]] | ||
+ | <br> | ||
+ | **Le quatrième moteur disponible à l'arrière de la main pourrait être déclenché ou non selon que l'obstacle soit au sol ou en hauteur. Des tests avec la main et en présence de Florian permettraient de déterminer si la compréhension de la position de l'obstacle n'est pas perturbée par cette vibration supplémentaire. | ||
+ | *Indications sonores : les trois paramètres forment dix-huit cas de figures. Un enregistrement a été réalisé pour chacun avec une gradation dans les tournures de phrases selon le danger (les enregistrements sont sur le git) | ||
+ | **Pour suivre la demande de Florian, ils sont enregistrés avec une voix de jeune garçon. Ils durent de trois à six secondes. | ||
+ | [[Fichier:Script ecrit enregistrements.PNG|400px]] | ||
+ | |||
+ | |||
+ | Ajout d'un thread File de Messages IPC pour récupérer le résultat de l'analyse de l'environnement par le code capteur. Les valeurs sont mises à jour à une fréquence permettant de ne pas couper un enregistrement ni un cycle de vibrations. L'adaptation au temps de fermeture et d'ouverture de la main pourra de faire une fois la main imprimée. | ||
+ | |||
+ | |||
+ | Les tentatives pour arrêter l'audio et qu'il n'empêche pas le bon fonctionnement des actionneurs en parallèle ont été nombreuses, mais nous n'avons pas réussi avoir avoir un résultat fonctionnel. | ||
+ | <br>Comme le lecteur peut être quitté depuis un terminal en tapant la lettre 'q', une première approche a été d'utiliser un pipe pour envoyer le même caractère sur la sortie standard. Nous avons essayer en lançant omxplayer depuis le programme principal et depuis un script shell ainsi que de faire ce lancement en arrière plan. | ||
+ | <br>Nous avons également essayé de régler ce problème avec l'ajout d'options au lancement du lecteur : omxplayer --no-osd --no-keys fichier.mp3 | ||
+ | *--no-osd désactive l'affichage des informations sur le terminal | ||
+ | *--no-keys permet de fermer le lecteur par SIGINT depuis le terminal | ||
+ | <br>Le test sur d'autres lecteurs, comme vlc aussi en ligne de commande, ou d'autres fonctions n'a pas pu se faire car nous n'avons pas réussi à connecter la Raspberry au réseau pour mettre à jour et faire les installations. | ||
+ | |||
+ | |||
+ | La partie gestion des différents moteurs s’exécute correctement en l’absence de la partie audio. En l'état actuel le lancement d'une piste audio et son arrêt est possible, mais ne fonctionne qu’une seule fois par exécution du code. Le thread audio lance un script shell avec un paramètre qui permet de démarrer le fichier audio correspondant à la situation. Le lancement du script et du lecteur se fait en arrière plan. L'envoi de la commande "pkill omxplayer" permet de le fermer lorsque que l'enregistrement arrive à son terme. Mais dès lors que l'enregistrement démarre, les moteurs restent bloqués dans leur état précédent. | ||
+ | |||
+ | ==Le harnais== | ||
+ | |||
+ | Le harnais doit comporté la RPi4 avec son hat,le capteur et la batterie.Cependant,l'ensemble du harnais a été conçu avec l'idée d'un harnais modulable. Il y a deux pièce principales que constituent la plaque avant et la plaque arrière du harnais. Ensuite, chaque pièce vient se fixer sur l'une de ces dernières. Ainsi, le harnais peut être adaptable à d'autres version de RPi,à d'autres styles de caméra,à d'autre formes de batterie,etc. | ||
+ | Toutes les modélisations ont été réalisées sur le logiciel de CAO Onshape. | ||
+ | |||
+ | ===Coque et bras de la caméra=== | ||
+ | La caméra doit se situé à l'avant de l'utilisateur et la position de la caméra doit être réglable. | ||
+ | <br>[[Fichier:Schema harnais devant P18.jpg|200px]] | ||
+ | <br><br>Pour ce faire, nous avons modélisé 4 piéces qui constitue le bras et la coque de la caméra. | ||
+ | <br><br>Premiérement, nous avons modélisé la coque de la caméra selon les mesures suivantes. | ||
+ | <br>[[Fichier:Dimensions Camera P18.PNG|250px]] [[Fichier:Coque P18.PNG|250px]] | ||
+ | <br>[[Fichier:Photo coque camera P18.JPG|250px]][[Fichier:Phpoto coque camera 1 P18.JPG|250px]] | ||
+ | <br>[[Fichier:Cotes-capteur.pdf]] | ||
+ | <br> Datasheet D435 Intel : [[https://cdn-reichelt.de/documents/datenblatt/EB00/INTEL_D400_DB-EN.pdf]] | ||
+ | <br><br> Viennent ensuite les deux parties qui constituerons le bras supportant la caméra.Nous pouvons ajouter une pièce qui permettra au fil de la caméra de rester contre le bras du harnais. Cette pièce sera imprimée en filament flex, contrairement aux autres pièces évoquées précédemment. | ||
+ | <br>[[Fichier:Tube 50 P18.PNG|250px]][[Fichier:Tube 30 P18.PNG|250px]][[ Fichier:Sert cable P18.PNG|75px]] [[Fichier:Photo bras P18.JPG|350px]] | ||
+ | <br> | ||
+ | <br>Et enfin,nous avons modélisé la pièce qui permet de fixer le bras à la plaque avant du harnais. | ||
+ | <br>[[Fichier:Porte tube P18.PNG|250px]][[Fichier:Photo fixe bras P18.JPG|250px]] | ||
+ | |||
+ | <br><br>Finalement, voici le résultat : | ||
+ | <br>[[Fichier:Photo bras camera P18.jpg|400px]][[Fichier:Zoom bras coque.jpg|300px]]<br>[[Fichier:Autre zoom bras coque.jpg|300px]] | ||
+ | [[Fichier:Photo dessus bras coque P18.jpg|350px]] | ||
+ | <br><br> Le bras mesure au maximum 20cm. | ||
+ | |||
+ | ===Boite Raspberry=== | ||
+ | Avec notre hat, il etait difficle de trouver un fichier de boite de RPi 4 qui convienne. Nous avons donc, là aussi, dû créer une boite pour la Raspberry Pi 4 avec le hat (et un potentiel ventilateur) sur mesures. | ||
+ | <br>Voici des schéma qui expliquent la disposition des éléments dans la boite: | ||
+ | <br>[[Fichier:Vu P18.JPG|300px]][[Fichier:Vu horizontale P18.jpg|400px]] | ||
+ | <br>Lien vers les connecteurs : [[https://www.gotronic.fr/art-connecteur-2x20-cts-he40b-22163.htm]] | ||
+ | <br>Lien vers le ventilateur : [[https://www.reichelt.com/fr/fr/raspberry-pi-ventilateur-30x30x10mm-pour-bo-tiers-nespi-rpi-fan-30x30-p211466.html?PROVID=2788&gclid=Cj0KCQjw4dr0BRCxARIsAKUNjWSZW3qoPNNK4KCf62XpbB9zG8M6zaFV2FM-udYmMq52V-Qg0MoMKcsaAguGEALw_wcB&&r=1]] | ||
+ | <br>[[Fichier:Dimensions RPi4 P18.PNG|400px]]<br>Datasheet Raspberry Pi 4 :[[https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2711/rpi_DATA_2711_1p0_preliminary.pdf]] | ||
+ | [[https://www.raspberrypi.org/documentation/hardware/raspberrypi/mechanical/rpi_MECH_4b_4p0.pdf]] | ||
+ | <br>Voici la modélisation de la boite avec son couvercle : | ||
+ | <br>[[Fichier:Boite Rpi4 P18.PNG|200px]][[Fichier:Couvercle Rpi P18.PNG|200px]] | ||
+ | <br> A noter que nous avons ajouté une ouverture (visible sur la troisième photo ci-dessous) afin de faire passer une gaine avec à l'intérieur les fils d'alimentation des motoréducteurs et des moteurs vibrants. | ||
+ | <br>Et voici, l'impression : | ||
+ | <br>[[Fichier:Rpi interieur P18.JPG|200px]][[Fichier:Photo zoom Rpi P18.JPG|200px]][[Fichier:Photo zoom Rpi1.JPG|200px]][[Fichier:Photo boite Rpi P18.JPG|200px]] | ||
+ | <br><br>Dimensions:115*70*53mm (l*L*h) | ||
+ | |||
+ | ===Plaque avant=== | ||
+ | La plaque avant du harnais se devait d’être solide; en effet, elle supporte le poids de la caméra ainsi que la Raspberry Pi 4 et son hat, mais aussi légère et peu encombrante. Ainsi, voici la forme de la plaque que nous avons décidé de modéliser et aussi un petit accessoire qui nous permet de concentrer les fils et ainsi éviter d’abîmer les fils. | ||
+ | <br>[[Fichier:Plaque avant P18.PNG|200px]] [[Fichier:Onshape range cable.PNG|200px]] | ||
+ | <br>[[Fichier:Plaque avant photo P18.JPG|200px]] [[Fichier:Range cable P18.JPG|200px]] | ||
+ | <br><br> Les connections de l'ensemble du dispositif s'effectuent avec 4 fils: | ||
+ | * fils d'alimentation de la Raspberry Pi 4 relié à la batterie ( en vert ) | ||
+ | * fils de connection avec le capteur ( en bleu ) [pour notre capteur, l'embout est du type USB-C ( bleu pointillé )] | ||
+ | * fils d'alimentations des motoréducteurs et moteurs vibrants ( en rouge ) | ||
+ | * écouteur prise jack ( en rose ) | ||
+ | [[Fichier:Schema fils P18.jpg|400px]] | ||
+ | [[Fichier:Plaque avant avec fils P18.JPG|350px]] | ||
+ | |||
+ | ===Boite batterie=== | ||
+ | Il était impératif que notre dispositif soit nomade afin d'offrir à Florian le plus d'autonomie possible. Nous disposons alors d'une batterie externe dont les dimensions sont donnés sur le schéma suivant : | ||
+ | <br>[[Fichier:Photo batterie P18.PNG|150px]] | ||
+ | <br>Lien vers la batterie externe:[https://fr.rs-online.com/web/p/batteries-externes/7757508/?relevancy-data=636F3D3126696E3D4931384E525353746F636B4E756D626572266C753D656E266D6D3D6D61746368616C6C26706D3D5E2828282872737C5253295B205D3F293F285C647B337D5B5C2D5C735D3F5C647B332C347D5B705061415D3F29297C283235285C647B387D7C5C647B317D5C2D5C647B377D2929292426706F3D3126736E3D592673723D2673743D52535F53544F434B5F4E554D4245522677633D4E4F4E45267573743D37373537353038267374613D3737353735303826&searchHistory=%7B%22enabled%22%3Atrue%7D] | ||
+ | <br>La boite de la batterie se composent de seulement deux pièce: la boite et le couvercle ; tout deux adapter au design de la batterie externe.<br>[[Fichier:Boite batterie P18.PNG|250px]][[Fichier:Couvercle batterie P18.PNG|250px]] | ||
+ | [[Fichier:Photo boite batterie P18.JPG|200px]] | ||
+ | <br> Dimensions:130*91.8*27mm | ||
+ | |||
+ | ===Plaque arrière=== | ||
+ | Tout comme la plaque avant, la plaque arrière doit répondre aux cahier des charges concernant la solidité et le confort. La plaque arrière ne supporte que la batterie dans notre application. Le choix de faire deux piéce distincte repose sur l'idée principale d'un harnais dit "modulable", ainsi il est tout a fait envisageable d'adapter la boite à d'autres batterie ou un autre objet. | ||
+ | <br>[[Fichier:Arriere plaque arriere P18.JPG|200px]][[Fichier:Plaque arriere P18.PNG|200px]] | ||
+ | <br>Nous aboutissons alors à l'arrière du harnais : | ||
+ | <br>[[Fichier:Arriere avec batterie P18.JPG|200px]] | ||
+ | |||
+ | ===Finalisation du harnais=== | ||
+ | Pour que le harnais soit accessible, nous avons décidé d'ajouter un système de réglage pour adapter à toutes les morphologies et aussi un système d'attache relativement simple (Lien vers le fichier.stl des boucle d'attache: [https://www.thingiverse.com/thing:2137647]]). | ||
+ | <br>[[Fichier:Modele reglage P18.PNG|150px]][[Fichier:Attache harnais P18.JPG|150px]][[Fichier:Photo accessoire harnais P18.JPG|150px]] | ||
+ | <br>Après la couture des bretelles et l'assemblage, voici le résultat final de notre harnais : | ||
+ | <br>[[Fichier:Face P18.jpg|400px]][[Fichier:Dos P18.JPG|250px]] | ||
+ | <br>[[Fichier:Photo profil 1 P18.jpg|400px]][[Fichier:Photo profil 2 P18.jpg|400px]] | ||
+ | <br><br> Liste du matériel nécessaire : | ||
+ | *3 vis M5 de longeur 25mm et les boulons associés | ||
+ | *24 vis M3 de longeur 10mm et les boulons associés | ||
+ | <br>Pour information, le harnais seule pèse 398g. | ||
+ | |||
+ | ==La main== | ||
+ | Nous avions déjà décidé au semestre dernier le modèle de la main. Ce semestre nous avons décidé des modifications à effectuer sur le fichier .stl de la main pour l'adapter à notre dispositif. Ainsi, l'objectif du semestre 8 a été d'effectuer ces changements et d'imprimer la main. | ||
+ | <br>[[Fichier:Main P18.PNG|400px]] | ||
+ | [[Fichier:Main paume P18.PNG|400px]] | ||
+ | <br> Nous pouvons remarquer que l'emplacement rectangulaire des trois motoréducteurs ainsi que les emplacements circulaires pour les mini moteurs vibrants. | ||
+ | <br> L'impression de la paume dure 25h. Aprés de multiples tentatives, la meilleur position d'impression est celle illustrée sur la photo suivante par le logiciel Cura. | ||
+ | <br>[[Fichier:Main Cura P18.PNG|500px]] | ||
+ | <br>Nous vous présentons l'évolution de l'impression à différents stades: | ||
+ | <br>[[Fichier:Main 1 p18.jpg|200px]][[Fichier:Main 2 P18.jpg|200px]][[Fichier:Main 3 P18.jpg|200px]][[Fichier:Main 4 P18.JPG|200px]] | ||
+ | <br> Voici la main en fin d'impression : | ||
+ | <br>[[Fichier:Mksozj.jpg|350px]][[Fichier:Main zoom 2.JPG|250px]][[Fichier:Main zoom 1.JPG|250px]]<br>[[Fichier:Main zoom 3.JPG|350px]][[Fichier:Main zoom 4.JPG|200px]][[Fichier:Main derr.JPG|200px]] | ||
+ | |||
+ | <br> Par manque de temps, nous n'avons pas pu imprimer la coque arrière. | ||
+ | === Documents rendus === | ||
+ | |||
+ | Rapport du S8 : [[Fichier:Rapport S8 Projet P18.pdf]] | ||
+ | <br>Support de présentation : [[Fichier:Plan de présentation S8.pdf]] |
Version actuelle datée du 4 mai 2020 à 09:42
Sommaire
- 1 Présentation générale
- 2 Analyse du projet
- 3 Préparation du projet
- 4 Réalisation du Projet
- 4.1 Projet S6
- 4.2 Projet S7
- 4.3 Projet S8
- 4.4 Semaine 1
- 4.5 Semaine 2
- 4.6 Semaine 3
- 4.7 Semaine 4
- 4.8 Semaine 5
- 4.9 Semaine 6
- 4.10 Mise au point de l'avancement du projet et tâches à réaliser en condition de confinement (18/03/2020)
- 4.11 Configuration et installation du SDK sur la raspberry
- 4.12 Code de détection de Profondeur (Controle capteur)
- 4.13 Algorithme
- 4.14 Code Capteur (final)
- 4.14.1 Test de récupération des données de profondeur
- 4.14.2 Traitement du stream d'images de profondeurs
- 4.14.2.1 Récupération des données du flux et leur transformation en matrice de profondeur:
- 4.14.2.2 Premier filtrage pour cibler la zone voulue:
- 4.14.2.3 Élimination du sol:
- 4.14.2.4 Détection des Clusters dans la matrice et leur traitement:
- 4.14.2.5 Transmission des données par IPC:
- 4.14.2.6 Boucle d'affichage
- 4.14.2.7 Limites
- 4.15 Exécution des codes après modification
- 4.16 Le Hat
- 4.17 Code de gestion de la main
- 4.18 Le harnais
- 4.19 La main
Présentation générale
Point important
Le projet a été proposé pour venir en aide à Florian, un adolescent malvoyant de 16 ans suivi à l'Institut d’Éducation Motrice (IEM) de Lille. Le dispositif et les choix de fonctionnement dépendent donc des besoins spécifiques de cet utilisateur. Nous avons eu l'occasion de le rencontrer le 7 mai 2019 et de mieux appréhender ce qu'il attendait du projet.
Ainsi notre travail avant cette date a été fait en supposant en grande partie les besoins de l'adolescent avec qui nous n'avions pas eu de contact préalable. Dans la suite, se trouve notre interprétation première du projet puis les modifications suite à la rencontre.
Description
Le projet consiste à réaliser un dispositif permettant le déplacement en toute sécurité et autonomie d'une personne malvoyante. Le dispositif donne la possibilité de se déplacer sur des terrains aussi bien plats qu'accidentés sans autre aide extérieure. Placé sur son l'épaule, la personne malvoyante est en mesure de se diriger par l'intermédiaire des commandes vocales et des pressions, rappelant celles d'une main d'un accompagnateur humain, qu'elle reçoit du dispositif.
- Suite à la rencontre, nous avons appris que le dispositif sera utilisé uniquement dans un contexte de promenades en forêt ou en montagne.
Objectifs
Le dispositif permet à la personne malvoyante une grande autonomie en étant prévenue lorsqu'un obstacle se présente à elle - qu'il soit fixe ou mobile - et lui indique comment l'éviter ou le contourner. Il s'agit d'être une alternative accessible à tous aux solutions existantes telles que les cannes ou les chiens guides, en étant aussi performante tout en apportant un aspect humain via les pressions sur l'épaule. Tout en conservant l'autonomie, la sensation sécurisante d'avoir une personne à ces côtés est présente. Cela est notamment important pour les enfants qui se sentent souvent plus rassurés avec un accompagnateur.
- L'adolescent n'utilise pas de dispositif d'aide au déplacement (ni canne, ni chien guide). Il n'est pas en mesure d'appréhender le relief, mais il parvient à se déplacer seul sur un terrain plat et dans un environnement connu. En cas de dénivelé il a besoin de la présence d'un accompagnateur près de lui et se déplace avec appréhension. Le but du dispositif est alors d'indiquer la différence de relief immédiat et la présence d'obstacles au sol en conservant le côté rassurant des pressions sur l'épaule et préventif par le biais indications sonores.
Analyse du projet
Positionnement par rapport à l'existant
Plusieurs dispositifs permettent de réduire l’appréhension du monde par les personnes malvoyantes. En effet, l'environnement, dans lequel on évolue, regorge d'obstacles mobiles ou non. Une rapide analyse est alors nécessaire pour augmenter la sécurité des utilisateurs. Tout d'abord, l'Homme semble être le plus adapté à évoluer dans la société, il se révèle alors être l'accompagnant le plus sécurisant. Cependant cela réduit considérablement l'autonomie de l'individu et reste coûteux si cette assistance est professionnelle. Une alternative peut être l'appel à un animal, tel que le chien, qui offre plus d'autonomie. Des alternatives technologiques existent. Par exemple, le boîtier BuzzClip qui permet de détecter les obstacles; néanmoins il détecte seulement les obstacles à la hauteur de son installation. Ou encore, l'Ultracane qui permet le signalement d'obstacles dans l'axe de la canne.
Analyse du premier concurrent
Nos premiers concurrent sont les chiens d'aveugles.
Le chien est reconnu pour sa sensibilité à l’environnement et aussi sa fidélité à son maître. Le dressage lui permet d'analyser et de réagir, tout en prévenant son maître, à une situation particulière. Cependant cette formation a un coût élevé : de 15 000 euros à 19 000 euros. L'animal demande aussi une attention supplémentairement (vétérinaire, nourriture...). De plus, l'accompagnement d'un animal peut être limité ou interdit dans certains lieux ce qui peut diminuer la liberté de l'utilisateur dans ses mouvements.
Analyse du second concurrent
Notre deuxième concurrent est l'Ultracane.
La cane est répandue pour sa facilité d'utilisation. L'Ultracane permet, de plus, de détecter les obstacles à moins de 4 mètres (environ 2 mètres au bout de la canne qui mesure 1,60 mètre) grâce à la technologie des ultrasons. Un obstacle se traduit par des vibrations dans la canne pour prévenir l'utilisateur. Cependant ces obstacles doivent se situer dans l'axe de la canne. Ceci force alors l'utilisateur à mettre en mouvement sa canne autour de lui ce qui peut diminuer la compréhension des vibrations ; mais aussi porte l'attention sur le malvoyant. Ainsi, ce dispositif permet une autonomie complète, une sécurité plus ou moins établie, néanmoins l'utilisation n'est pas discrète.
Scénario d'usage du produit ou du concept envisagé
Dans notre scénario d'usage on prend le cas d'une jeune malvoyante nommée Léa. Elle désire un moment de solitude et décide de s'aventurer dans la foret. Pour autant elle veut se sentir en sécurité face à d’éventuels obstacles ou imprévus sur son chemin. Elle active alors son "système d'aide au déplacement", le pose sur son épaule et commence sa promenade. Après une semaine particulièrement pluvieuse et venteuse, la foret est pleine d'obstacles. Dès l’entrée de Lea sur le sentier, celle-ci ressent une pression au milieu de son épaule et entend "attention obstacle en hauteur", elle comprend qu'elle doit être vigilante par rapport à un obstacle tel qu'une branche et lève donc sa main pour la repousser. En s'aventurant plus profondément, elle ressent des pressions successives sur son épaule et entends "attention obstacle mobile à gauche à 40m". Elle s’arrête alors, recule et prends la meilleure décision pour contourner l'obstacle en toute sécurité sachant que si l'obstacle représentait encore un danger, son "système d'aide au déplacement" lui aurait signalé sa présence par un nouveaux signal auditif. Elle continue donc sa promenade en suivant les pressions (gauche, droite, vers le bas) et indications de son "système d'aide au déplacement" par rapport aux différents obstacles et le type de terrain (exemple "attention obstacle en hauteur" ou "terrain en pente glissant"...). Léa a donc pu profiter d'une après midi très apaisante, en autonomie complète grâce a son "système d'aide au déplacement". Elle se relevera demain pour une nouvelle aventure en ville ou en campagne toujours guidée par son "système d'aide au déplacement".
Question(s) difficile(s)
- Comment gérer l'autonomie du dispositif pour éviter à l'utilisateur de devoir le recharger trop régulièrement ?
- Comment réduire l'encombrement du dispositif pour une utilisation aussi discrète que possible ?
Réponse à la question difficile
- Le dispositif étant destiné à un usage occasionnel, sur la durée d'une balade ou d'une petite randonnée soit une à quatre heures, il peut y avoir deux solutions. L'usage de piles à recharger ou d'une batterie externe légère peut-être envisagé.
- Nous avions envisagé un patch pour une utilisation discrète. Néanmoins, suite à la rencontre avec l'adolescent, un dispositif sous forme de main placé sur un harnais ne le gêne pas. Il s'agit donc de placer l'ensemble du dispositif sur le harnais. La mains étant imprimée en 3D, la taille est réglable pour correspondre aux dimensions l'épaule de l'adolescent. Lors des semestres 7 et 8, nous réaliserons un PCB pour limiter au maximum la place occupée par les différents composants électronique ce qui permettra de réduire l'encombrement qui en découle.
Bibliographie et webographie
Préparation du projet
Cahier des charges du groupe
Notre dispositif doit permettre d'aider une personne malvoyante à se déplacer. Il est constitué de deux parties communicant entre elles par Bluetooth. La première est placée sur l'épaule et sert à prévenir l'utilisateur de la présence d'obstacles par l'intermédiaire de vibrations et d'indications vocales. Elles proposent également à l'utilisateur un moyen de les éviter. La seconde partie contient les capteurs servant à analyser l'environnement. Ils doivent permettre de détecter des obstacles immobiles proches et ceux en mouvement, déterminer la présence de dénivelé (capteur de distance). Éventuellement de déterminer où se situent les limites de la route ou du chemin qu'emprunte l'utilisateur (par traitement d'images).
À l'issue du semestre 6 :
- Réaliser un prototype du dispositif en vue d'une rencontre avec l’ergothérapeute à l'origine du sujet et l'adolescent malvoyant et leur présenter nos deux idées de dispositif. La présentation nous permettra d'avoir des retours sur notre vision du projet et de l'adapter concrètement aux besoins de l'utilisateur durant la quatrième année, par exemple en ne gardant qu'une seule version ou en les combinant.
- La première version est un dispositif discret sous la forme d'un patch à placer sur l'épaule. Il guide par le biais de vibrations (moteurs vibrants) dans la direction à emprunter accompagnées par des informations sonores sur l'environnement, la nature de la position des obstacles. En cas de danger imminent l'utilisateur est prévenu par une alerte sonore couplée aux vibrations.
- La seconde est une main réalisée à l'imprimante 3D dans le but de conserver l'aspect rassurant de la main d'un accompagnateur. Comme le patch elle utilise les vibrations. La main applique des pressions sur l'épaule en cas de détection d'un danger et est légèrement chauffante pour rappeler le contact humain.
Modification du cahier des charges suite à la rencontre avec l'adolescent
- La détection d'obstacles en hauteur, mobiles ou immobiles n'est plus une priorité. Le dispositif doit avant tout prévenir du changement de relief.
- Le côté chauffant de la main ou du patch n'est pas souhaité par Florian.
- Les deux dispositifs pourront être combinés. La main avec les pressions pouvant se placer sur un manteau et être utilisée même par temps froid. Le patch avec les vibrations disposé sur l'épaule ou un t-shirt.
Projet IMA4 :
- Réaliser le dispositif sous ses deux formes, le patch et la main, qui pourront se combiner.
- La main effectue des pressions plus ou moins importantes en fonction du danger et peut se placer sur un manteau
- Le patch vibre selon les obstacles et peut se placer sur la peau ou un t-shirt
- Faire le choix définitif des capteurs à utiliser.
- Associer la partie détection de l'environnement par les différents capteurs au patch et à la main grâce à une liaison directe ou Bluetooth.
- Réaliser la partie indications sonores :
- Liaisons avec les capteurs et la main ou le patch
- Enregistrement des phrases types
- Utilisation d'oreillettes
Cahier des charges des équipes
Equipe 1: Travail sur le dispositif (main et/ou patch)
La partie principale de notre projet consiste a élaborer un dispositif portable permettant de guider une personne malvoyante, celui ci doit être :
- Portable et léger (cette partie doit pouvoir être positionnée sur l’épaule d'un adolescent de 16 ans)
- Peu encombrant mais solide : Le dispositif sera utilisé lors de balades en foret, donc d'activités physique; il doit donc être suffisamment solide et stable pour résister a l’activité et aux mouvements de l'enfant sans le gêner.
- Discret et esthétique.
- Remplacer tout ce que la main humaine représente de part son cote rassurant (chaleur), ses indication (indications sonores) et ses pressions face a un danger.
Equipe 2: Travail Capteurs
La seconde partie du projet et la plus technique est la captation de l'environnement. Notre dispositif doit:
- Capter l'environnement de la personne malvoyante
- Mesurer la distance séparant l'utilisateur a l'obstacle
- Analyser les données observées pour déterminer la nature de l'obstacle, sa position et comment l’éviter
- Communique avec la deuxième partie du projet pour transmettre, a l'utilisateur, les bonnes informations (les plus utiles, pour que l'adolescent puisse correctement les interpréter)
Liste des tâches à effectuer
Equipe 1: Travail sur le dispositif (main et/ou patch)
Projet S6:
L'objectif à la fin de ce semestre est d'avoir un premier prototype fonctionnel démontrant le fonctionnement général du projet:
- Effectuer des recherches pour trouver une matière suffisamment souple pour se posée discrètement sur l’épaule et fine pour faire passer les vibrations.
- Trouver un design de main articulée à imprimer en 3D
- Élaborer le code pour que le dispositif réagisse en fonction des indication extérieurs:
- Main : la main en 3D doit se resserrer lorsqu'un obstacle est trop proche, les moteurs vibrant qui lui sont intégrés doivent vibrer en indiquant vers quelle direction est l'obstacle.
- Patch : Le patch fonctionne comme les tableaux a LED : en fonction de la position de l'obstacle , une partie des moteurs vibreurs vont s'activer pour indiquer a l'utilisateur la provenance de l'obstacle (pour lui permettre de réagir) exemple: si l'obstacle est devant a gauche, seule une partie spécifique des moteurs ( a gauche vers arrière de l’épaule ) seront mobilisés.
- Trouver le microprocesseur adapter pour avoir le dispositif le moins encombrant réunissant toutes les fonctions nécessaires:
- il doit commander une vingtaine de moteurs vibrants
- Doit avoir la possibilité de communication en bluetooth (intégrée ou a l'aide d'un module à commander)
- Penser au design final du dispositif et étudier sa stabilité sur l’épaule de l’adolescent
Projet IMA4:
A la fin de cette année, le dispositif doit être complètement fonctionnel et adapté aux besoins de Florian. Ainsi, elle consistera à :
- Imprimer la version finale de la main 3D
- Sourcing: Commander tous les composants nécessaires pour faire le patch ET la main.
- Mettre en place la connexion bluetooth ou la communication directe (câble) pour envoyer les indications sonores
- Compléter le code pour que le dispositif réagisse correctement au données envoyées par la partie capteur
- Programmer le microprocesseur pour commander le patch
- Faire évoluer le design final du dispositif pour qu'il soit le plus stable et adaptable a la personnalité, aux envies et besoin de Florian.
Equipe 2: Travail Capteurs
Projet S6:
L'objectif de ce semestre est de réunir dans un rapport clair une étude des différents capteurs présents sur le marché.
- Identifier les capteurs potentiellement intéressants pour le projet
- Pour chaque capteur expliciter:
- Le principe de fonctionnement (technique)
- Utilisation
- Le type de données renvoyées
- Comment récupérer et analyser les données renvoyées par le capteur (traitement des données)
- Leur poids et prix
Projet IMA4:
L’élaboration de la partie capteur sera le plus gros enjeux de cette année. On devra:
- Se baser sur l'étude effectuée au S6 pour sélectionner le meilleur capteur pour la problématique de Florian (principalement la détection des reliefs)
- Implémenter le processus de traitement de données
- Implémenter le processus de communication avec le dispositif sur l’épaule (communication bluetooth ou filaire)
- Création du boitier + harnais contenant tout le dispositif de captation
Choix techniques : matériel et logiciel
Equipe 1 : Travail sur le dispositif (main et/ou patch)
- Matériel :
- Mini moteurs vibrants
- Servomoteur
- Carte Arduino → remplacée en IMA4 par un PCB et les composants nécessaires (microcontrôleur)
- Logiciel :
- Arduino
- Onshape pour la modélisation de la main
- Fritzing pour la réalisation du PCB en IMA4
Equipe 2 : Travail Capteurs
- Matériel :
- Capteur à ultrason
- Lidar
- Kinect
- Caméra
- Logiciel :
- Skanect
- ReconstructMe
- OpenCV
Autre
- Matériel :
- Module Bluetooth
- Harnais
Calendrier prévisionnel
Nous avons décider de rassembler toutes les taches que nous avons a faire dans un trello , pour rendre plus facile la manipulation et le suivi de l'avancement (réel) de chaque taches.
Lien vers notre Board Trello: [1]
Réalisation du Projet
Projet S6
Semaine 4
Réflexion
Face à la problématique qui nous a été dressé la première étape de notre projet sera la réflexion sur la forme et le comportement du dispositif. Nous devons intégrer à notre projet le caractère unique et spécifique du à l'handicap de l’utilisateur.
Concernant la forme de notre dispositif nous envisageons deux possibilités, sachant qu'un combinaison des deux serait tout à fait possible :
- Main : Un impression en 3D d'une main se rapprocherait de la réalité. Cette main devra être rassurante. Elle exercera une pression sur l'épaule de l'utilisateur pour indiquer un danger potentiel. De plus, au bout des doigts se trouvera des moteurs vibrants permettant de modérer l'information par rapport au degré de danger.
- Patch : Ce patch sera en contact avec la peau de l'utilisateur sur l'épaule.Tout autour se trouvera des moteurs vibrants. En divisant en 4 parties on pourra distinguer 4 indications différentes.
Pour compléter le système d'indication nous avons pensé à une indication auditive. Ainsi un système d’oreillettes semble être adapté et discret.
Pour indiquer un danger potentiel le systèmes doit analyser des données. Ces données peuvent être de nature différentes. On pourra utilisés différents capteurs : ultrasons,lidar,analyse d'image...
Semaine 5
Séance design
Lors de cette séance, nous avons rencontré une intervenante extérieure pour centrer notre projet autour de l'utilisateur.En effet, notre dispositif est destiné à être utilisé par un adolescent malvoyant, il est donc primordial de se mettre à sa place pour développer une solution pertinente.
La séance a commencé par une réflexion de groupe pour décrire notre projet et en donner nos perceptions. Cela nous a permis de nous rendre compte que si nous avions la motivation d'aider l'adolescent, nous étions déjà focalisées sur les solutions techniques. Il est important de prendre en compte que l'utilisateur peut s’être adapté et qu'il est trouvé une alternative à une fonction de notre dispositif, elle deviendra alors inutile.L'usager doit rester un point central.
À partir des informations du aux recherches, observations et rencontres, il s'agit de dégager les traits caractéristiques pour obtenir un usager type. C'est l'étape de la collecte. Nous avons eu ensuite la présentation de plusieurs méthodes a fin de réutiliser ces données pour mieux comprendre notre utilisateur :
- le Persona : fiche de présentation détaillée de l'utilisateur type qui doit constituer sa réalité reflétant sa personnalité,ses activités. Cette fiche a pour but de mettre en évidence les problématiques de l'utilisateur en lien avec sa fragilité.
- carte d'empathie : fiche décrivant toutes les sensations de l’utilisateur, ce qu’il entend, voit, dit, ressent, pense…
Après avoir mis l'usager au centre de notre réflexion, on peut établir un meilleur scénario d'usage.Ce dernier peut être réalisé sous différentes formes : un texte narratif, une bande dessinée ou un storyboard, une vidéo...
Pour appréhender de manière plus concrète notre scénario d'usage, nous avons testé la méthode Lego® Serious Play®. À l'aide d'un nombre limité de pièces de Lego nous avons recréé le scénario d'usage, ce qui nous a permis de réfléchir à certains cas particuliers que nous n'avions pas envisagé. Par exemple en cas d'obstacle mobile, comment notre dispositif doit réagir. À l'issue de cette séance, une rencontre avec l'adolescent semble indispensable afin de répondre pleinement au cahier de charge.
Semaine 6
Capteurs
Réflexion sur le type de capteur que l'on pourrait utiliser :
1. Capteur de distance :
- capteur à ultrasons HC-SR04 (celui utilisé habituellement avec un arduino) : renvoie la distance d'un obstacle immobile ou mobile (on peut en déduire la vitesse)
- capteur laser LIDAR-Lite v3 : même utilisation que le capteur à ultrason, permettrait d'avoir plus de précision.
Pour que les capteur de distance prennent en compte une plus large partie de l'environnement, nous réfléchissons à placer le capteur sur un servomoteur. Ainsi le capteur pourrait pivoter de haut en bas et prendre en compte la présence d'obstacles à la hauteur de la personne et les changements de dénivelé, les trous.
2. Camera (Stéréoscopie):
- détecter les formes dans le but de différencier un trou d'un rocher par exemple, éventuellement nommer les obstacles, pour que l'utilisateur se repère mieux (annonces vocales type : Attention, trou à 1m)
- détecter les zones de couleurs (par exemple en forêt : différencier un chemin gris du vert des plantes)
En utilisant une camera, se pose les questions de consommation et de puissance de calcul pour traiter les images en temps réel.
Lien: [2]
Semaine 7
Programme
Durant cette semaine nous nous sommes penchés sur le code qui permettra au prototype de fonctionner. Ce prototype est une illustration très générale et simplifiée du projet. Nous tenons à ce que ce soit le plus accessible pour le présenter aux utilisateurs (l'adolescent malvoyant/aveugle). Le code se décompose donc en 2 partie :
- la première pour lancer la prise de mesure par le capteur ultrasonore
- la seconde pour faire réagir la mains ( les servo et les moteurs vibrants) correctement
Le principe de fonctionnement de la main est qu'elle se refermera plus ou moins rapidement lorsqu'un obstacle est captée à une distance inférieure à une distance minimale. Tant que le dispositif est allumé on prend des mesures permettant de déterminer la distance entre l'obstacle et le capteur;on compare ensuite cette distance (et/ou vitesse) a des valeurs minimales préalablement enregistrées pour que la main réagisse plus rapidement si la vitesse est grande ou exerce une simple pression si l'obstacle est simplement immobile.
Pour ce faire, nous avons utilisé 2 servo-moteurs ainsi que 2 moteur vibrant (ceux disponibles). Après quelques soudures nous avons pu avancer sur le code.
Code a la fin de la semaine 7 :Fichier:Code main.zip
Code mesure de distance et alimentation du moteur vibreur.
Semaine 8
Composants
Pour limiter l'encombrement du dispositif, nous voulons réaliser un PCB. Une partie de l'équipe s'est donc intéressée au fonctionnement du logiciel Frizing.Au sein du PCB, nous prévoyons de placer un microcontrôleur ATMEGA2560. Nous avons regardé quelles pins utiliser pour relier les 24 moteurs vibrants prévus pour le patch.
Nous avons fais quelques recherches sur les composants qui nous seront nécessaire à la réalisation de notre projet. Ainsi, voici la liste des composants électroniques :
Composant | Référence | Fabricant | Image |
---|---|---|---|
Capteur à ultrasons |
HC-SR04 |
OSEPP Electonics |
|
Moteur Vibrateur en Disque 2.0 mm |
316040001 |
Seeed Studio |
|
Micro Servo Moteur SG90 9g |
TPSG90 |
Tower Pro |
|
Arduino MEGA 2560 |
A000067 |
Arduino |
Cependant à la fin de la séance, après avoir discuté avec notre encadrant, nous avons décidé de nous concentrer sur la réalisation du prototype de la main à présenter à l'adolescent avec le matériel déjà à notre disposition (une carte Arduino UNO, trois moteurs vibrants, deux servomoteurs) et de finalement réaliser le PCB et commander les autres composants durant l'année d'IMA4.
Nous avons sélectionné un modèle de main sur le site Thingiverse dans le but d'imprimer cette dernière avec une machine 3D du FABRICARIUM de l'école.
Lien du modèle de la main: [4]
Semaine 9
Modélisation de la main
Nous avions, la séance dernière, sélectionné un modèle de main. Malheureusement nous n'avons pas réussi à l'imprimer dû à un dysfonctionnement de la machine durant la nuit. La durée d'impression de cette main devenait alors trop importante par rapport au temps que nous disposions avant la rencontre avec l'adolescent. Ainsi nous avons dû modéliser une main simplifiée nécessitant moins d'heures d'impression et correspondant tout de même à notre cahier des charges. Nous l'avons modélisé sur le logiciel Onshape.
Cette modélisation rend approximativement compte des articulations d'une main humaine. Les dimensions ont été prises à partir d'une main réelle. Nous avons décidé de ne pas imprimer le pouce qui se révèle n’être pas nécessaire à notre prototype. Nous avons placé des encoches à l’intérieur des doigts afin de pouvoir y insérer des fils qui seront entraînés par les servomoteurs. Pour ces derniers nous avons créé des emplacements pour qu'ils s’intègrent au dispositif.
Semaine 10
Impression 3D de la main
La séance dernière nous avons réussi à finaliser notre fichier de la modélisation de notre main. Nous avons pu au cours de la semaine commencer l'impression. Nous avons alors pour cette séance toutes les pièces de notre main. Nous remarquons que la taille des encoches accueillant les servomoteurs est trop juste. Nous nous sommes donc rendus au FABRICARIUM pour modifier cela. Les différentes parties de la main peuvent donc ensuite être assemblées. On définit l’état initiale de la main comme sa position à plat.Pour permettre aux doigts de revenir de sorte que la main soit à son état initial on doit fixer des élastiques sur chaque doigts aux niveaux des 8 articulations.Ensuite nous avons installé les fil pour relier les doigts aux servomoteurs. Ces derniers,en tournant, permettent de s’opposer à la forces des élastiques. En parallèle, nous nous sommes chargé de réaliser un support pour présenter le prototype à l'équipe pédagogique de l'adolescent et lui même.
Semaine 11
Finalisation du prototype
Durant cette séance, nous avons finalisé notre prototype pour pouvoir le présenter durant la séance prochaine à Florian et son équipe pédagogique. Dans un premier temps nous avons testé le code. Celui-ci permet la rotation des servomoteurs lorsque notre capteur à ultrason détecte un obstacle à une distance inférieur à une distance que nous avons paramétré dans le code. Cette rotation entraîne alors le mouvement des doigts de façon à ce que la main reproduise les pressions exercées par un accompagnateur face à un obstacle. De plus, nous avons effectué quelques modifications sur la structure de la main pour que le prototype soit opérationnel.
Pour préparer notre rencontre avec Florian nous avons décidé de faire une présentation à l'aide de l'outil Powtoon afin d'expliquer de façon pédagogique notre dispositif et pouvoir en discuter sans inclure la partie technique de notre projet.
Lien Powtoon: [5]
Semaine 12
Rencontre avec Florian
Nous nous sommes rendu à l'Institut d'Education Motrice de Lille,école spécialisée Jules Ferry, afin de rencontrer Florian. Dans une première approche, nous nous sommes présentés afin de faire connaissance. Nous avons alors pu en savoir un peu plus sur les spécificités du handicap de Florian. Ce dernier ne distingue pas les reliefs. En effet, lorsqu’il se promène,son accompagnateur doit lui indiquer lorsqu'un trou ou un rocher se trouve sur le chemin. Seul, Florian peut marcher sur un chemin large, néanmoins il longe le côté pour se rassurer.L'indication exacte des distances n'est pas utile. Florian porte un corset, notre dispositif doit donc s'y adapter, un harnais semble être une bonne option.
Nous lui avons ensuite présenté une vidéo qui met en scène notre dispositif et aussi notre prototype de main. Florian semble préférer la main au patch. Certaines remarques de ce dernier nous permettent d'affiner notre cahier des charges. Florian possède une sensibilité qui devra être prise en compte au niveau des vibrations des moteurs vibrants. Florian démontre une certaine appréhension concernant la puissance de la main lorsque cette dernière exerce une pression. Ainsi,nous devrons alors être capable de régler la puissance de la main ainsi que la vitesse de vibration des moteurs vibreurs. De plus, il a été décidé que ces deux paramètres devront être proportionnels au danger. A noter que l'idée d'un fil chauffant n'est pas utile selon l'accompagnatrice de Florian. Florian donne au système audio une place très importante. Florian est un adolescent qui réagit vite selon son accompagnatrice et connaît parfaitement sa droite et sa gauche.Il sait alors se situer dans l'espace. Ainsi les indications apportées par l'audio peuvent être des phrases construites et précises. Florian aimerait que la voix soit celle d'un garçon de son âge, 16 ans, et aussi deux oreillettes.Le volume des indications doit être progressif en fonction du danger. Par exemple lorsque l'obstacle s’approche, le son augmente. De plus, Florian possède un téléphone, on pourra donc avoir accès au GPS.Ceci nous permettra d’améliorer notre dispositif. En effet,on pourra penser qu'en cas de danger important la géolocalisation permettra de prévenir un responsable.Un téléchargement de la carte du parcours désiré par Florian peut être effectué pour indiquer, rassurer ou même optimiser un parcours avec le moins de danger possible.
Travail supplémentaire
Nous avons rajouté au prototype de la main un moteur vibrant qui se déclenche en même temps que les pressions. Dans le code a été ajouté la détection de vitesses différentes. Si un obstacle arrive avec une faible vitesse, la main se referme une fois. Si la vitesse dépasse un certain seuil, la main réagit en se refermant deux fois de suite. Nous avons également ajouté un moteur vibrant sur le bout de la main. Il se déclenche dès qu'un obstacle est détecté et vibre en continu le temps que la main se plie et se déplie avant de se stopper.
Vidéo de la main lorsqu'un obstacle arrive à faible vitesse puis rapidement.
Recherche sur les différents types de capteurs que l'on pourrait utiliser :
Fichier:Capteur analyse environnement.pdf
Documents Rendus
Rapport pour le semestre 6 :
Fichier:Rapport Projet P18 IMA3 4 2018 2019.pdf
Projet S7
Semaine 1
La décision de fusionner avec le projet P17 a été prise.En, effet ces derniers conçoivent un dispositif d'aide au déplacement pour un enfant. Ainsi, les fonctions principales telle que le traitement d'informations ou encore le système de communication sont communes à nos projet.
Dans un premier temps nous nous sommes rassemblés pour présenter nos projets ainsi que les objectifs de chacun.Nous avons alors pu distinguer quatre axes de travail: les capteurs, les actionneurs, la gestion d'énergie et la réalisation d'un PCB.
Partie | P17&P18 | P18 |
---|---|---|
Capteurs |
Distance |
Relief, profondeur |
Actionneurs |
Minis moteurs vibrants |
Moteurs DC pour la main |
Gestion énergie |
Taille limitée |
|
PCB gérant les actionneurs, les indications sonores et la main pour P18 |
|
|
Nous nous sommes répartis dans plusieurs groupes.
Équipe 1 Partie sonore, actionneurs : Laurine, Clara
Équipe 2 Choix définitif des capteurs : Nour, Caroline, Émilie
Équipe 3 Gestion de l'énergie : Jing, Ming
La réalisation de la carte dépend des choix des éléments ci-dessus. Nous nous répartirons sa réalisation par la suite.
Semaine 2
Équipe 1 :
Notre premier travail a été de réfléchir sur les axes d'améliorations concernant le prototype proposé lors du semestre 6. En effet, la rencontre avec Florian nous a permis d'affirmer que le dispositif sous la forme d'une main lui plaisait. Ainsi,dans un premier temps, notre premier objectif a été d'améliorer l'aspect fonctionnel de notre dispositif, comme par exemple un choix de moteur plus performants et plus adapté ou encore trouver le moyen de faire varier l'intensité des moteurs vibrants. Nous pouvons également évoqué toute la partie sonore qui n’était pas encore présente sur le premier prototype. Puis dans un deuxième temps nous nous sommes penchées sur l’aspect design de la main mais aussi de tout le dispositif comportant la RPi,la batterie et la caméra.
Équipe 2 : Recherches pour choisir entre les capteurs D435 et D415 d'Intel
- D415
- D435
Semaine 3
Équipe 1 :
- Gestion des moteurs vibrants: Nous avons défini trois niveau de dangerosité d'un obstacle. Chaque niveau correspondra à une séquence de vibrations spécifique. Par exemple,pour un simple indication (danger faible), les vibrations seront espacées par un délai important.Pour un obstacle ou un changement de relief plus important,ce délai sera raccourcis. Et enfin, en cas de grand danger,les moteurs vibreront en continu.
- Pour avertir l’utilisateur des potentiels dangers nous avons décidé d’utiliser 4 moteurs vibrants. Chacun sera disposé sur la main afin de représenter, chacun, une direction : devant, derrière, droite et gauche.Nous avons, alors, décidé de garder un modèle de main gardant le pouce dans le but d'avoir quatre zones bien définis où placer les moteurs vibrants.
- Concernant la partie sonore, nous avons choisis le module MP3 DFR0299 pour stocker et exploiter les enregistrements d'indications sonores[6]. De plus,pour connecter les écouteurs,un port jack serait directement soudé sur le PCB.[7]
Équipe 2 :
- Besoin d'une Raspberry Pi 4 pour traiter les données du capteur D435.
- Angle de vision du D435 : 90°. On le positionne vers le bas pour déterminer les changements de reliefs. C'est ce sur quoi nous allons nous concentrer puisque la reconnaissance du dénivelé est ce qui fait défaut à Florian. Les obstacles en hauteur (type branche), pourraient être détectés par l'ajout d'un capteur à ultrason ou Lidar.
Semaine 4
Les commandes de matériels se terminant fin novembre, nous avons constitué la liste des composants nécessaires pour les différentes parties du projet en cherchant les références exactes de chaque élément.
- Motoréducteur 2 axes 2218 :[8]
Pour mettre en mouvement les doigts de la main nous avons décidé d’utiliser 3 moto-réducteurs 2axes 2218 du fabricant Gotronic. Leurs tensions d’alimentation est comprise entre 3V et 9V. Leur vitesse à vide sous 6V est de 100 tr/min. La dimension des ses moteurs est intéressante pour notre application, en effet, nous souhaitions mettre 3 de ces moteurs dans le prototype de notre main.
Dimensions : 26 x 12 x 10 mm.
Diamètre de l’axe : 3 mm.
Longueur de l’axe : 10 mm.
Diamètre de l'axe arrière : 1 mm.
Longueur de l'axe arrière : 4,5 mm.
- Vibreur miniature VM1201 :[9]
Pour avertir l’utilisateur des potentiels dangers nous avons décidé d’utiliser 4 moteurs vibrants. Chacun sera disposé sur la main afin de représenter, chacun, une direction : devant, derrière, droite et gauche. Ces moteurs nécessitent une tension d’alimentation comprise entre 2V et 5V.
Dimensions : Ø10 x 2,7 mm.
- Contrôleur moteurs TB6612FNG :[10]
Le contrôleur moteur TB6612FNG permet de contrôler deux moteurs. Les pins iIN1 et iIN2 permettent de choisir le sens de rotation du moteur. La PWM permet de réguler la vitesse de rotation. Nous retrouvons alors la commande sur les pins iO1 et iO2. L’alimentation de la partie puissance moteur doit être comprise entre 4.5V et 13.5V. L’alimentation de la partie commande doit être, quant à elle, comprise entre 2.7V à 5V. Le contrôleur moteur ne fonctionne que si /STBY est au niveau de logique 1.
Semaine 5
- Création des composants nécessaires sur Altium.
- Définition du modèle de main et recherche pour son fonctionnement en fonction de notre choix de moteurs. Il s'agira de modifier le modèle sur un logiciel de CAO comme Fusion360 par exemple pour créer l'espace nécessaire aux moteurs.
Possibilité de modèle de la main : [11] ,[12]
- Écriture du Wiki
Semaine 6
- Equipe 1
Tuto pour réaliser la main : [14]
La paume de la main peut contenir un PCB de 60mm x 45mm. L'impression 3D de cette main utilise du filament flexible (200 g) et du filament PLA (100 g).
Semaine 7
Documents Rendus
https://www.mouser.fr/datasheet/2/408/TB6612FNG_datasheet_en_20141001-708260.pdf schéma = p7
http://www.picaxe.com/docs/spe033.pdf schéma = p10
Projet S8
Suite à notre soutenance du S7,il a été décidé que le PCB initialement prévu pour faire fonctionner les moteurs de la main est supprimé. Cette fonction sera assurée par la Raspberry.
Semaine 1
Equipe1
Nous avons défini la réaction de la main selon différents cas, potentiellement rencontrés par Florian :
Récupération des fichiers sources de la main à imprimer en 3D: nous pouvons ainsi les modifier pour inclure l'espace nécessaire pour nos moteurs.
Schéma de la main :
Ce schéma prend en compte les dimensions des moteurs et le passage de leurs fils d'alimentations, mais aussi le passage des fils pour fermer et ouvrir la main.Nous y avons également placé les emplacements des 4 moteurs vibrants au bout d'un doigt et sur le bord de la main.
Sélection filament pour l'impression 3D :
Equipe2
Semaine 2
Nous avons récupéré une partie du matériel commandé au S7. Ainsi, nous disposons du capteur D435 Intel RealSense,des trois motoréducteurs 2 axes et de deux modules de contrôleurs moteurs.
Equipe 1
Les installations sur la Raspberry étant en cours, nous avons réalisé le circuit moteur et nous nous sommes familiarisées avec leur fonctionnement à l'aide d'un Arduino.
Ce montage est composé d’un Arduino, d'un module contrôleur moteurs TB6612FNG, de deux motoréducteurs et d’un moteur vibrant.
Comme évoqué précédemment, un contrôleur moteur utilise une PWM pour contrôler la vitesse de rotation et de deux données tout ou rien pour décider du sens de rotation du moteur.
Code de test du fonctionnement des moteurs sur Arduino :
// initialisation int PWMA=3; int AIN1=1; int AIN2=2; int a; // rapport cyclique entre 0 et 255 void setup() { // broches 3 (PWMA), 1 (AIN1), 2 (AIN2) en sorties pinMode(PWMA,OUTPUT); pinMode(AIN1,OUTPUT); pinMode(AIN2,OUTPUT); } void loop() { // on ne travaille que dans le sens 1 : AIN1=1, AIN2=0 digitalWrite(AIN1,0); digitalWrite(AIN2,1); // fixation d'une valeur pour la PWM a=200; analogWrite(PWMA, a); delay(200); a=10; analogWrite(PWMA, a); }
Equipe 2
Configuration de la raspberry pi3 pour faire les premières installations des paquets pour
- la librealsense2
- OpenCV
- Cmake...
Semaine 3
Equipe 1
Nous avons pu nous procurer une RPi3 B+ et ainsi effectuer le montage sur une breadboard ainsi que les tests concernant les moteurs mais aussi la partie audio.
Nous utilisons la bibliothèque Pigpio pour avoir accès aux GPIO et au contrôle de la PWM des moteurs depuis notre code en C. Nous avons besoin de contrôler simultanément les moteurs et les minis moteurs vibrants en plus de l'audio. Pour réaliser l’exécution parallèle nous pensons employer des forks.
Nous utilisons le lecteur multimédia en ligne de commande omxplayer pour lancer l'audio.
Fonctionnement dans le terminal :
Lancement: omxplayer fichier.mp3
Arrêt : q
Nous devrons par la suite inclure ces deux commandes dans le code C général.
Equipe 2
Configuration de la raspberry:
Dans etc/network/interfaces rajouter
auto lo iface lo inet loopback auto eth0 iface eth0 inet static address 172.26.145.199 netmask 255.255.255.0 gateway 172.26.145.254
ifdown/ifup eth0
NE PAS OUBLIER:
export https_proxy=http://proxy.plil.fr:3128
Semaine 4
Equipe 1
Suite au montage effectué la semaine dernière, nous avons décidé de faire un hat pour un gain de place et de propreté du montage. Ainsi, deux axes de travail se dégagent pour l'équipe 1 : continuer le code qui contrôle les moteurs et la partie sonore, et modéliser le hat.
Afin d'accentuer le gain de place,nous avons décidé de ne pas utiliser les modules TB6612FNG. En effet, nous disposions des contrôleurs moteurs TB6612FNG et des capacités avec les valeurs souhaitées, ainsi, nous allons les utiliser pour le hat.
Pour la modélisation de la carte, nous avons suivi le diagramme suivant pour reconstituer le module:
Cette modélisation sera faite avec le logiciel Fritzing.
Nous retrouvons alors la Raspberry, les 4 moteurs vibrants, les 4 motoréducteurs, les deux contrôleurs moteurs et leurs capacités qui leurs sont propres. Nous pouvons aussi remarquer la présence de 6 composants, ce sont des « solder jumper ». Ces derniers permettent de choisir, après l’impression de la carte, les liaisons voulues avec une simple soudure. A noter également qu'il y a un quatrième motoréducteur, la présence de ce dernier laisse la possibilité aux futurs utilisateurs de la carte d’en faire usage pour d'autres projets.
Nous avons mis en place dans le code des forks pour chaque tâches. Néanmoins nous sommes confronté au type d'erreur ci-dessous lors de l'exécution.
Nous avons fini par nous rendre compte que l'usage des forks n'est pas supporté par la bibliothèque Pigpio employée et entraîne des conflits. Nous allons donc tester le fonctionnement avec des threads.
Semaine 5
Equipe 1
La deuxième étape de la modélisation du hat sur Fritzing est la création de la vue schématique.
Pour revenir sur l'utilisation des solder jumper, lors de la soudure, nous chercherons à contrôler M8 et M2 par les mêmes pins.
De plus,le hat est créé pour une RPi4. Le choix des pins est le suivant :
L'utilisation de threads n'est pas bloquante vis-à-vis de la bibliothèque pigpio et permet de faire fonctionner simultanément les moteurs et moteurs vibrant. Nous allons donc mettre en place plusieurs threads pour les différentes composantes du fonctionnement de la main :
- la gestion des moteurs
- celle des minis moteurs vibrants
- celle de l'audio
Le lancement de l'audio depuis le code C peut se faire avec la fonction "execl". Comme l'arrêt de l'audio se fait en écrivant la lettre 'q' dans un terminal nous allons explorer la piste d'un pipe pour envoyer ce caractère sur la sortie standard.
Semaine 6
Equipe 1
Enfin, la dernière étape avant l’envoie à l’impression de la carte est le routage. La carte est à deux faces et les pistes sont de 16 mil.
Routage complet de la carte :
Routage de la face supérieure de la carte :
Routage de la face inférieure de la carte :
La piste du pipe ne donne pas de résultats concluants. L'envoi du caractère n'arrête pas le lecteur.
En parallèle des tests sur l'audio, nous créons et organisons le code en sous-fonctions pour chaque tâche que vont lancer les threads.
Suite à la situation de confinement, nous avons trouvé plus pertinent de retranscrire l'avancée du travail en grandes parties plutôt qu'en semaines
Mise au point de l'avancement du projet et tâches à réaliser en condition de confinement (18/03/2020)
Partie matérielle
- Matériel disponible pour le travail à distance (Raspberry, capteur, montage, coque capteur)
- POINTS INCERTAINS :
- Récupération du hat pour souder les composants
- Impression 3D de la main puis sa récupération
- Liaison des différentes parties sur le harnais
Partie réalisation
- Code du capteur : Fonction de moyennage faite. Il reste à délimiter les différentes zones, étalonner, découper les frames reçues, détecter des trous et des reliefs en utilisant les fonctions déjà réalisées
- Code des actionneurs : lancer tous les threads et les fermer comme souhaité, notamment au niveau de l'audio
- Faire la liaison entre les deux codes
- Début de réalisation du harnais
Organisation
- Création d'un git
- Lien : https://archives.plil.fr/nourekhlas/P18.git
- Ajout de tous les codes
- Réalisation d'un document précisant le découpage du code de gestion du capteur permettant la répartition du travail Fichier:Modularite code p18.pdf
Configuration et installation du SDK sur la raspberry
Configuration de la raspberry
Sur la machine linux (zabeth) vérifier/activer la transmission de connexion réseau aux périphs:
iptables -P FORWARD ACCEPT
Raspberry pi 4
- la première étape consiste à configurer les interfaces réseaux :
Dans le fichier etc/network/interfaces:
auto lo iface lo inet loopback
auto eth0 iface eth0 inet static address 172.26.145.33 netmask 255.255.255.0 gateway 172.26.145.254
Dans le fichier etc/resolv.conf indiquer le serveur DNS de l'école:
nameserver 193.48.57.48
Pour enregistrer les changements:
ifup eth0 ifdown eth0
Pour vérifer les changements :
ip a
Ne pas oublier de :
Exporter le proxy de l'école : export https_proxy=http://proxy.plil.fr:3128
- Seconde étape :
Dans le menu raspi-config activer le mode experimental pour OpenGL ( dans Advanced options)
Raspberry pi 3+
La mémoire RAM d'une raspberry pi 3 est bien trop faible pour la compilation du SDK. Pour contourner ce problème on peut utiliser la cross-compilation https://solarianprogrammer.com/2018/05/06/building-gcc-cross-compiler-raspberry-pi/ ou regarder la crosscompile directement integrée sur ubuntu
Raspberry Pi 3 – Autre possibilité avec Ubuntu Mate 18.04
Configuration de la raspberry:
sudo raspi-config
→ Interfacing options → ssh → Advanced option → expand filesystem
Update Ubuntu.
Clone du répertoire librealsense :
git clone https://github.com/IntelRealSense/librealsense.git
Dans le dossier librealsense, installation des packets de base requis pour construire les binaires librealsense:
sudo apt-get install git libssl-dev libusb-1.0-0-dev pkg-config libgtk-3-dev sudo apt-get install libglfw3-dev libgl1-mesa-dev libglu1-mesa-dev
Exécutez le script d'autorisations Intel Realsense situé à partir du répertoire racine librealsense:
./scripts/setup_udev_rules.sh
Créez et appliquez des modules de noyau corrigés pour:
./scripts/patch-realsense-ubuntu-lts.sh
Le module de suivi nécessite le module du noyau hid_sensor_custom pour fonctionner correctement :
echo 'hid_sensor_custom' | sudo tee -a /etc/modules
Création du dossier build et exécuter Cmake :
cmake ../ -DBUILD_EXAMPLES=true - Construit librealsense avec les démos et les tutoriels
Recompiler et installer les binaires librealsense :
sudo make && sudo make install
Si problème lors de la compilation du à la mémoire RAM trop faible de la raspberry Pi3, un swapfile peut être effectué afin de rajouter par exemple 3 Go de RAM
Désactiver l’utilisation du swap :
sudo swapoff -a
Création du swapfile avec 3 Go :
# dd if=/dev/zero of=/swapfile bs=3072 count=1048576
Autorisation Root :
sudo chmod 600 /swapfile
Marquer le fichier comme espace swap :
sudo mkswap /swapfile
Activer le Swap :
sudo swapon /swapfile
Vérifier si le swap à été crée :
sudo swapon –show
Vérifier la structure finale de la partition :
free -h
Définir le fichier Swap comme permanent :
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Installation du SDK
Les étapes a suivre sont :
- git clone https://github.com/IntelRealSense/librealsense.git
- création d'un dossier build
- cmake -L .. 2> error
- Installation des libraries nécessaires manquantes indiquées dans le fichier error
- make
Pour tester : vérfier la présence des executables dans le dossier tools et/ou examples (test : connexion D435 et lancer rs-enumerate-devices)
Code de détection de Profondeur (Controle capteur)
Algorithme
L'algorithme de traitement des données se décompose en 3 "grandes" parties:
- Etape 1: Détection des devices et sélection du premier
- Etape 2: Récupération des données
- Etape 3: Algorithme de traitement
Etape en amont:
l'idée serait de subdiviser l'image (frame) recueillie en 3 ou 4 plages de profondeurs ; On va faire un premier test sur un sol uniformément plat (dans une salle) en fixant le capteur au même niveau que l'aurait Florian; une fois la frame obtenu on détermine, pour chaque plage de profondeur la moyenne de tous les pixels concernés pour déterminer la valeur représentant le sol dans cette partie là. Puis on stocke ces valeurs ci dans des variables globales qui seront ré-utilisées dans le reste du code.
- Calcul de l'écart entre chaque pixel et la moyenne de sol de la zone du pixel, si cet écart est supérieur au seuil prédéfini, l'indice de ce pixel est ajouté dans un tableau pixels_suspects.
- On teste ensuite l'étendue de l'obstacle en regardant la zone de pixels autour (un périmètre de 30 pixels autour) puis on garde la moyenne de profondeur de cette zone.
- On compare la moyenne des zones détectées pour en déterminer l'obstacle le plus "dangereux".
- une fois celui ci déterminé, on garde ces valeurs pour faire un tracking de cet obstacle le temps qu'il sorte du champ de vision ou qu'un obstacle plus dangereux soit détecté.
- on génère ensuite les 3 chiffres qui vont être transmis à l'autre partie du code (actionneurs)
- Le premier indiquera si c'est un obstacle en hauteur ou un trou (signe de la moyenne des écarts);
- Le second relèvera la dangerosité de l'obstacle : proportionnelle à la moyenne d'écart préalablement calculée;
- Le troisième indiquera la position dans l'espace (voir photo exemple)
La zone de captation du capteur peut être limitée grâce à:
The default scale of an Intel RealSense D400 device is one millimeter, allowing for a maximum expressive range of ~65 meters. The depth scale can be modified by calling rs2_set_option(...) with RS2_OPTION_DEPTH_UNITS, which specifies the number of meters per one increment of depth. 0.001 would indicate millimeter scale, while 0.01 would indicate centimeter scale.
Fonctions utilisées:
https://github.com/IntelRealSense/librealsense/wiki/Projection-in-RealSense-SDK-2.0*
http://docs.ros.org/kinetic/api/librealsense2/html/rs__advanced__mode_8h_source.html
Fixer l'echelle:
/** When called on a depth sensor, this method will return the number of meters represented by a single depth unit * \param[in] sensor depth sensor * \param[out] error if non-null, receives any error that occurs during this call, otherwise, errors are ignored * \return the number of meters represented by a single depth unit */ float rs2_get_depth_scale(rs2_sensor* sensor, rs2_error** error);
Filtrer les données:
/** * \brief sets the active region of interest to be used by auto-exposure algorithm * \param[in] sensor the RealSense sensor * \param[in] min_x lower horizontal bound in pixels * \param[in] min_y lower vertical bound in pixels * \param[in] max_x upper horizontal bound in pixels * \param[in] max_y upper vertical bound in pixels * \param[out] error if non-null, receives any error that occurs during this call, otherwise, errors are ignored */ void rs2_set_region_of_interest(const rs2_sensor* sensor, int min_x, int min_y, int max_x, int max_y, rs2_error** error);
Récupérer les données de profondeurs
/* Sets new values for STDepthTableControl, returns 0 if success */ void rs2_set_depth_table(rs2_device* dev, const STDepthTableControl* group, rs2_error** error);
/* Gets new values for STDepthTableControl, returns 0 if success */ void rs2_get_depth_table(rs2_device* dev, STDepthTableControl* group, int mode, rs2_error** error);
Code Capteur (final)
Test de récupération des données de profondeur
Traitement du stream d'images de profondeurs
Le code c'est finalement articulé sous 4 grandes parties:
Récupération des données du flux et leur transformation en matrice de profondeur:
- Cette partie a été relativement rapide, on s'est principalement basée sur les fonctions fournies par le SDK librealsense et les exemples. Des fonctions spécifiques nous permettre de configurer le flux de données (résolution,format,fps...) rs2_config_enable_stream, puis rs2_get_video_stream_resolution pour obtenir le profil réel du flux; puis enfin rs2_get_frame_data.
La fonction get frame data nous permet de récupérer un tableau de la profondeur correspondant a chaque pixel du frame. (rs2_get_frame_data renvoie un unint16_t *)
Premier filtrage pour cibler la zone voulue:
- Après notre discussion avec Florian et ses accompagnatrices nous avons décidez de se focaliser principalement sur les reliefs et sur le sol. Cette décision nous a permis de :
- Fortement limiter le nuage de points
- Définir une position claire du capteur sur le corps de Florian -> Facilite la conception du harnais.
Pour une personne de la taille de florian, avoir une vue plus ou moins complète de 50cm a 3m devant semblait correcte. Nous avons donc fixer la plage de traitement aux profondeurs se situant entre 1m et 3m de profondeurs (avec une marge). Ces paramètres sont fixes en tant que Macros aux début du code et peuvent être modifiés après les essais pratiques. Pour déterminer l’unité de mesure correspondant a 1 metres on fixe une variable one_metre en utilisant une fonction également fournie par le sdk get_depth_unit_value() (1m = 1.0/get_depth_unit_value()). On effectue après un premier tri du tableau de profondeur que nous possédons en fixant a 0 tous les pixels a extérieure de notre intervalle [z_min;z_max]. Grace a ce premier filtrage on divise en moyenne de 2-3 le nombre de valeurs a traiter dans les phases suivantes.
Élimination du sol:
- Théorie
Nous sommes parties de l’hypothèse que le capteur sera correctement fixe sur le harnais pour vérifier les distances fixées sur ce croquis a droite (Valeurs fixées en macros). Nous avons ensuite diviser l'image de profondeur perçue en NB_ZONES différentes. Chaque zone est espacée de l'autre d'un pas_zone qui nous permet en utilisant une simple règle de Pythagore de déterminer la profondeur moyenne de la zone.
Après avoir déterminer ces seuils, on élimine le sol en fixant a 0 toutes les valeurs a l’intérieur de l'intervalle [seuil_zone[zone]-dz ; seuil_zone[zone]+dz]
A la fin de ces deux filtrage il ne reste plus dans cette matrice que les anomalies ou les profondeurs qui semblent suspects selon nos critères de sélection.
On obtient donc une matrice comme celle ci ou les X correspondent a des valeurs non nulles de profondeurs.t Cette dispositions de valeurs nous fait donc penser a des clusters (rassemblements de données présentant les mêmes caractéristiques).
- Limites
- Cette méthode de détection de sol n'est absolument pas la méthode idéale. L’idéal serait de faire une détection dynamique (en temps réel) en revanche en augmentant le nombre de zones (en diminuant le pas) on obtient un résultat plutôt correct.
- Le second point négatif de cet méthode est la détermination de la marge dz que l'on fixe. A travers les tests pratiques on pourras déterminer un dz cohérent avec le type de sol sur lequel se balade Florian et des conditions d'utilisations.
- On peut s’inspirer d'autre pistes pour trouver un algorithme d’élimination plus performant (cf pistes a développer)
Détection des Clusters dans la matrice et leur traitement:
Ce qui était parti d'une intention de détection de clusters c'est transformer en une détection de voisins. L’idée principale est donc de détecter un pixel dont la valeur est différent de 0, une fois celui ci détecté, une structure Cluster_t est initialisée:
typedef struct cluster{ int moyenne; int zone; int type; int debut_y; int debut_x; int taille; int danger; } Cluster_t
typedef struct list_c{ struct cluster c[MAX_CLUSTER]; int nb_clusters; }List_c;
Ces clusters sont ensuite identifies par une fonction clust_detec pour en déterminer la taille et la valeur moyenne et donc en ressortir un indice de dangerosité. Les Clusters sont tous ensuite classes dans une liste ordonnée dans l'ordre croissant de dangerosité puis le(s) clusters les plus dangereux seront signalés, sous la forme de 3 digits, au code des actionneurs grâce a une FDM IPC.
- Récursivité et ses limites
La première approche que j'ai eu , était pour moi la plus intuitive : la récursion.
void clust_detec(int ** depth,int rows,int row_length,int x,int y,int * taille,int * moyenne) { if(x<0||x>=rows||y<0||y>=row_length) return; int ind= x*row_length+y; if((*depth)[ind]==0) return; *moyenne += (*depth)[ind]; (*taille)=(*taille)+1; (*depth)[ind]=0; //Itération sur les voisins clust_detec(depth,rows,row_length,x+1,y,taille,moyenne); clust_detec(depth,rows,row_length,x-1,y,taille,moyenne); clust_detec(depth,rows,row_length,x,y+1,taille,moyenne); clust_detec(depth,rows,row_length,x,y-1,taille,moyenne); }
Ce code est fonctionnel et donne des résultats correctes mais il est extrêmement coûteux en termes de mémoire sur la stack. En effet la pile d’exécution est inondée par des appels récursifs de clust_detec et génère donc, au bout d'un certain moment, une erreur de segmentation fault (stack overflow).
- Non récursif
- Création d'une stack / queue qui remplace la file d’exécution.
Pour régler ce problème de stack overflow et après des recherches intensive sur internet, j'ai eu la brillante idée de créer une structure stack qui me permettra de simuler/remplacer le comportement de la file d’exécution mais dans un espace mémoire plus grand.
struct stack { int maxsize; // define max capacity of stack int top; struct coordonnees *items; };
struct stack* newStack(int capacity) { struct stack *pt = (struct stack*)malloc(sizeof(struct stack));
pt->maxsize = capacity; pt->top = -1; pt->items = (struct coordonnees *)malloc(sizeof(struct coordonnees) * capacity);
return pt; }
Le code source a été trouvé sur internet puis remodelisé pour s'adapter a notre utilisation de la stack. Les fonctionnalités push(SP,x,y); pop(SP); isEmpty ... sont présentes dans l'archive git (cf lien) En C++ , C# ou java on aurait pu directement utiliser la classe stack implémentée.
- Flood fill non récursif
Pour transformer l'algorithme cluster_detec en un algorithme non récursif je me suis inspirée des algorithmes Flood Fill. En effet ces algorithmes sont utilisés pour détecter les pixel d'une couleur spécifique puis changer sa couleur et celles de tous ses voisins si elles ne correspondent pas à la couleur désirée. Souvent implémenté de manière récursive, cet algorithme récursif a montré ses limites lorsqu'on traite d'un grand nombre de données. c'est pour cela que des algorithmes non récursif de Flood fill ont émergés [17] A l'aide de l'algorithme présenté comme réponse sur ce lien j'ai pu élaborer une version non récursive de notre cluster_detec qui possède des résultats identiques sans les problèmes de stack overflow.
void clust_detect_nr(int ** depth,int rows,int row_length,int x,int y,int * taille,int * moyenne) { static const int dx[4] = {0, 1, 0, -1}; static const int dy[4] = {-1, 0, 1, 0}; struct stack * sp=newStack(rows*row_length); int i=0; push(sp,x,y); while(!isEmpty(sp)) { struct coordonnees c = pop(sp); //printf("pop\n"); for(int i = 0; i < 4; i++) { int vx = c.x + dx[i]; int vy = c.y + dy[i]; if(vx >= 0 && vx < rows && vy >= 0 && vy < row_length && (*depth)[vx*row_length+vy] !=0) { *moyenne += (*depth)[ vx*row_length+vy]; (*taille)=(*taille)+1; (*depth)[vx*row_length+vy]=0; push(sp,vx,vy); } }} free(sp->items); free(sp); return; }
Transmission des données par IPC:
On utilise les librairies systèmes (valables sur raspbian)
#include <sys/wait.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/msg.h> #include <sys/stat.h>
libipc :Fichier:Libipc.zip
//IPC #define CLE_MSG (key_t)1000 #define TYPE_REPONSE_CLIENT 3
typedef struct {
int digit_y; int digit_type; int digit_danger; } infos_t;
typedef struct { int zone; int type; int danger; } donnees_t;
/* Structures pour les reponses */ typedef struct { long type; donnees_t donnees; } reponseClient_t;
int recuperationFDM (void) { int msgid; if((msgid = msgget((key_t)CLE_MSG, 0)) == -1) { perror("Erreur de recuperation de la FDM\n"); exit(EXIT_FAILURE); } printf("msgidClient %d\n",msgid); return msgid; }
void envoiReponseClientFDM (donnees_t donnees) { reponseClient_t reponse; /* Récupération de la file de messages */ int msgid = recuperationFDM (); /* Envoi de la reponse */ reponse.type=TYPE_REPONSE_CLIENT; reponse.donnees=donnees; if(msgsnd(msgid, &reponse, sizeof(reponseClient_t) - sizeof(long), 0) == -1) { perror("Erreur d'envoi de la réponse\n"); exit(EXIT_FAILURE); } printf("Réponse envoyée\n"); }
Envoi des informations après chaque fin de traitement d'image de profondeurs
donnees_t donnees; donnees.danger = clusts.c[clusts.nb_clusters-1].danger; donnees.type = clusts.c[clusts.nb_clusters-1].type; if(clusts.c[clusts.nb_clusters-1].debut_y>=0 && clusts.c[clusts.nb_clusters-1].debut_y<width/3) donnees.zone = 1;//Gauche else if(clusts.c[clusts.nb_clusters-1].debut_y>=width/3 && clusts.c[clusts.nb_clusters-1].debut_y<(2*width/3)) donnees.zone = 2;//Milieu/Devant else if(clusts.c[clusts.nb_clusters-1].debut_y>=(2*width/3) && clusts.c[clusts.nb_clusters-1].debut_y<width) donnees.zone = 3;//Droite envoiReponseClientFDM(donnees);
LIMITES
La majorité du traitement repose sur les deux premières phases de filtrages qui seront , dans l'état actuel, affinés et modifiés après plusieurs essais pratiques pour augmenter la précision et la pertinence des valeurs traitées lors de la détection de cluster. Malheureusement ces essais ne pourront être effectués à cause du confinement.
Pistes à developper
Il existe des méthodes pour la détection du contour des objets dans une image, et une solution serait d’utiliser ces méthodes afin de localiser les bords des obstacles. lien code sur l'archive git.
Filtre de Sobel
Cette méthode consiste à utiliser un filtre de Sobel, celui-ci va calculer le gradient de l’intensité (de la distance) de chaque pixel. Un contour se caractérise par une rupture d’intensité dans l’image, ces changements d’intensités se traduisent par des discontinuités dans la profondeur. Ce filtre comporte deux dérivées, une dérivée pour la direction horizontale et une dérivée pour la direction verticale. Ceci permet d’éliminer l’inclinaison du sol par rapport à la caméra et conserver uniquement les contours des objets. Il suffit de détecter pour chaque pixel sa variation par rapport à ses voisins de gauche et de droite et de dessus et dessous. La méthode consiste à calculer le gradient en H puis en V autour du pixel.
Donc à chaque position (x, y) d’un pixel, il faut calculer la différence entre les 3 pixels à sa gauche avec les 3 à sa droite selon les matrices de pondération données ci-dessus pour déterminer le gradient en x, les 3 pixels au dessus avec les 3 pixels en dessous pour avoir le gradient en y. Le pixel fait partie d’un contour si son contour est élevée.
Initialisation des valeurs et tableaux
int gx; // Gradient suivant l’axe x int gy; // Gradiant suivant l’axe y int g; // Module du gradient int E[height][width];// Structure d'entrée (image de profondeur) int Sobel[height][width]; // Structure de la transformée de Sobel
L’initialisation comporte les deux dérivées des directions gx et gy, le module du gradient est calculé avec g. On initialise un tableau d’entrée E[ ][ ] qui sera traité avec le filtre de Sobel Sobel[ ][ ], et enfin un dernier tableau Sr [ ][ ] de dimension plus réduite pour permettre l’affichage du résultat en mode texte.
Ici l’image sera chargée dans le tableau E, on va parcourir les lignes et colonnes de l'image.
for (y = 0; y < height; ++y) { for (x = 0; x < width; ++x) E[y][x] = *depth_frame_data++; }
La création du filtre de Sobel se fait de la façon suivante:
// printf("Début Sobel"); for (y = 0; y < height; ++y) { for (x = 0; x < width; ++x) { if (x==0 || y==0 || x==width-1 ||y==height-1) g=0; // mise à 0 du bord else { //derivee en x gx=(- (E[y-1][x-1] + E[y][x-1] + E[y][x-1] + E[y+1][x-1]) + (E[y-1][x+1] + E[y][x+1] + E[y][x+1] + E[y+1][x+1]));
//derivee en y gy=(- (E[y-1][x-1] + E[y-1][x] + E[y-1][x] + E[y-1][x-1]) + (E[y+1][x-1] + E[y+1][x] + E[y+1][x] + E[y+1][x+1])); //amplitude g=sqrt((float)gx*gx+(float)gy*gy); // if (g>255) g=255; //écrêtage à 255 pour avoir un résultat sur un seul octet } Sobel[y][x]=g; // Et voici la structure contenant la transformée de Sobel Sr[y / HEIGHT_RATIO][x / WIDTH_RATIO] += (g/poids);
Quelques recherche sur internet ont été très utiles pour la création du filtre, celui-ci contient les dérivées en x et y , les matrices dérivées vont ensuite aider au calcul du gradient avec g=sqrt(gx*2+gy*2). En sortant de la boucle on obtient notre filtre Sobel[ ][ ], on peut par la suite déterminer une image à l’affichage Sr[ ][ ] avec un ratio de 8x5 afin d’obtenir une image plus grande que celle proposée dans le code original de l’exemple “depth.c” du SDK de la caméra D435. Dans le tableau Sr [ ][ ] on va faire la somme des HEIGHT_RATIO x WIDTH_RATIO pixels encadrant du tableau Sobel[ ][ ] et la diviser par le nombre de pixels défini dans la variable poids = HEIGHT_RATIO x WIDTH_RATIO.
Boucle d'affichage
La boucle d’affichage de l’image en mode texte se fait comme suit: Chaque pixel sera affiché sous la forme d’un caractère qui correspondra à la valeur du pixel. On a créé un tableau pixels[ ] qui contient les caractères à afficher static const char* pixels = " .0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; Comme il n’est pas possible d’avoir autant de caractères différents que de valeurs de gradient, on fait un écrêtage à 32 (cette valeur aurait pu être augmenté à 37 avec la table que nous avons définie).
for (y = 0; y < rows; y++) { for (x = 0; x < row_length; x++) {int pixel_index = (Sr[y][x]/8); // on divise par 8 pour avoir une échelle de correspondant aux nombre de valeurs pouvant être affichées if (pixel_index > 32) pixel_index=32; *out++ = pixels[pixel_index]; // on remplit la case buffer[out] du tableau buffer. } *out++ = '\n'; // on insère un retour à la ligne à la fin de chaque ligne } *out++ = 0; // On remet l’index du tableau buffer à 0 printf("\n%s", buffer); // On imprime l’image sur le terminal
rows et row_length correspondent au nombre de lignes et de colonnes de l’image divisé par notre ratio 8*5. Nous allons charger pixel_index avec les données de notre tableau Sr[ ][ ]/8. En effet, le tableau Sobel[ ][ ] contient des valeurs entre 0 et 255 qu’il faut ramener à une échelle de 0 à 32 en divisant la valeur par 8. Ensuite il suffit de déterminer un seuil à ne pas dépasser pour le contour final. Si une valeur de notre tableau est supérieure à 32 alors elle sera limitée à 32 pour ne pas déborder dans la recherche d’un élément en dehors des limites du tableau pixel[ ]. Puis on affiche notre structure pour visualiser les contours.
Limites
Grâce à cette méthode nous pouvons détecter les obstacles et on remarque qu’ils sont assez facilement reconnaissables. On constate qu’il y a du bruit dans l’image et qu’il faudrait éventuellement supprimer ce bruit et améliorer ce filtre pour avoir une très bonne précision pour l’analyse des reliefs sur terrain accidenté Ceci pourra se faire facilement en éliminant les valeurs inférieures à un seuil à déterminer. Les obstacles sont délimités par des pixels dont le gradient est supérieur à un seuil à affiner. La dangerosité peut être déterminée par la taille de la zone et sa position dans l’image.. La localisation des objets devant la caméra est relativement facile à effectuer par rapport aux coordonnées de l’objet dans l’image. La distance de l’objet peut être déterminée par rapport à sa projection dans l’image, ou mieux en récupérant la valeur de la distance dans le tableau E[ ][ ].
Le résultat du filtre de Sobel
Exécution des codes après modification
Lorsqu'il s'agit de modifier le code pour pourvoir faire des tests, nous devons procéder à plusieurs manipulations. Tout d'abord on doit se rendre dans le dossier /librealsense/examples/. Pour la suite on prendra exemple de code "distance", celui ci se situe dans /librealsense/examples/C/. On garde le dossier original dont on va faire une copie "distance2" que l'on va pouvoir modifier.
Compilation
La compilation donnera une erreur si le CMakeLists.txt n'a pas était corrigé. Comme vu précédemment avec le hello_realsense2, nous devons reprendre la même forme du CMakeLists.txt.
L'ajout de la ligne suivante est indispensable:
#Find librealsense2 installed package find_package(realsense2 REQUIRED)
Ensuite il suffit de créer un dossier build puis d’exécuter les commandes suivantes
Erreur de compilation
Lorsque l'on souhaite modifier le code en ajoutant une librairie qui n'est pas indiqué dans le code du SDK, nous avons l'erreur suivante.
Après avoir fait plusieurs recherches sur internet et en comparant les codes fourni par le SDK, j'ai découvert que le problème était dans le CMakeLists.txt. En effet il s'agit d'ajouter la librairie qui a été ajouté dans le code. Dans le code c'est la librairie math.h qui a été ajouté, comme on peut le voir dans l'erreur il ne reconnaît pas les formules mathématiques utilisées.
Sur certains forum ils indiquaient l'ajout de la librairie m dans le fichier CMakeLists.txt. La ligne de code est la suivante
target_link_libraries(rs-depth m ${realsense2_LIBRARY})
En changeant cette ligne et en y ajoutant la librairie m correspondant à math.h, nous n'avons plus d'erreur de compilation.
Le Hat
Partie soudure
La partie soudure du hat était délicate du point du vue de la précision de soudure afin ne pas déborder sur les pins du même composant qui sont espacées de 0,5 mm seulement. Plusieurs essais ont du être faits, les premières soudures ont été faites sur les condensateurs avec l'étain fourni qui était sans plomb, j'ai trouvé des difficultés à appliquer l'étain sur les pins des composants. La panne s'est vite oxydée, le problème venait peut être de l'étain ou du nettoyage de la panne sur une éponge humide. Après plusieurs essais de nettoyages de la panne, celle-ci est restée noire. Une éponge métallique et de l'étain avec du plomb que je trouve plus facile à manipuler ont suffit pour retrouver une panne brillante.
Les conseils de Monsieur Redon sur la soudure des CMS ont été très utiles, et m'ont permis d’acquérir la technique de soudure de ces composants. Après plusieurs séances d’entraînement, les CMS ont été soudés proprement. Le plus dur a été de souder les contrôleurs moteurs, il fallait être rapide pour ne pas trop chauffer le composant mais aussi être précis.
Après avoir soudé les contrôleurs sur une nouvelle plaque à cause des échecs de soudure, il a fallut souder les condensateurs, les connecteurs pour les moteurs et les moteurs vibrants et enfin les connecteurs pour la Raspberry. Ces composants ont été fournis par Monsieur Redon afin d'aboutir au mieux à la finalisation du hat. Le hat est donc composé de six capacités, deux contrôleurs moteurs, de huit connecteurs pour les moteurs commandant l'ouverture et la fermeture de la main et de moteurs vibrants. Enfin d'un connecteur pour la connexion à la Raspberry. On note que d'après le schématique, il faut faire des ponts de soudure sur l’arrière de la plaque pour mettre les moteurs M3 et M8 en service. Les moteurs M8 et M2 sont commandés ensemble. Grâce à un moteur DC et des vibreurs, le hat a pu être testé avec une Raspberry Pi3 Model B.
Partie code
L'implémentation du code pour faire le test du hat s'est faite sur une Raspberry Pi3. Le test à été fait avec un seul moteur, c'est à dire, qu'un seul connecteur pour moteur à été soudé afin de vérifier le premier contrôleur puis un deuxième connecteur pour le deuxième moteur pour vérifier le second contrôleur. Une fois la vérification faite les deux autres connecteurs moteurs ont été soudé.
La librairie de commande des GPIO que nous avons utilisées avec la Raspberry Pi, nécessite non pas d’initialiser avec les numéros des pins de celle-ci mais avec les numéros des GPIO qui se trouve sur la Datasheet de la Raspberry. L’initialisation des GPIO et des moteurs correspondants s'effectue conformément à la figure ci-dessous.
Remarque : Lors du lancement du code il est nécessaire de préciser sudo avant l’exécution du programme sinon une erreur apparaît ne reconnaissant pas les fonctions GPIO utilisées dans le code. Ceci est une particularité de la librairie piggpio qui ne peut être utilisé qu'en mode sudo.
Code de gestion de la main
Les codes sont sur le git : https://archives.plil.fr/nourekhlas/P18.git
Les fonctions s'articulent selon les valeurs les trois paramètres suivant :
Le comportement des actionneurs est fonction de ces paramètres. Les moteur dépendent de danger, les vibrations de danger et direction, l'audio des trois à la fois.
- Moteurs : la valeur de danger conditionne la vitesse de fermeture par le biais de la PWM et le temps avant la réouverture de la main
- Un ajustement sera nécessaire à trouver entre la vitesse donnée au moteur contrôlant le pouce et les deux autres reliés aux autres doigts pour avoir un mouvement le plus naturel possible.
- Minis moteurs vibrants : selon la direction, un seul des trois moteurs se déclenche. Pour un danger faible et intermédiaire les vibrations se font par intermittence, plus ou moins rapidement. Lors d'un grand danger, elles sont continues. Un cycle de vibration prend au maximum six secondes.
- Le quatrième moteur disponible à l'arrière de la main pourrait être déclenché ou non selon que l'obstacle soit au sol ou en hauteur. Des tests avec la main et en présence de Florian permettraient de déterminer si la compréhension de la position de l'obstacle n'est pas perturbée par cette vibration supplémentaire.
- Indications sonores : les trois paramètres forment dix-huit cas de figures. Un enregistrement a été réalisé pour chacun avec une gradation dans les tournures de phrases selon le danger (les enregistrements sont sur le git)
- Pour suivre la demande de Florian, ils sont enregistrés avec une voix de jeune garçon. Ils durent de trois à six secondes.
Ajout d'un thread File de Messages IPC pour récupérer le résultat de l'analyse de l'environnement par le code capteur. Les valeurs sont mises à jour à une fréquence permettant de ne pas couper un enregistrement ni un cycle de vibrations. L'adaptation au temps de fermeture et d'ouverture de la main pourra de faire une fois la main imprimée.
Les tentatives pour arrêter l'audio et qu'il n'empêche pas le bon fonctionnement des actionneurs en parallèle ont été nombreuses, mais nous n'avons pas réussi avoir avoir un résultat fonctionnel.
Comme le lecteur peut être quitté depuis un terminal en tapant la lettre 'q', une première approche a été d'utiliser un pipe pour envoyer le même caractère sur la sortie standard. Nous avons essayer en lançant omxplayer depuis le programme principal et depuis un script shell ainsi que de faire ce lancement en arrière plan.
Nous avons également essayé de régler ce problème avec l'ajout d'options au lancement du lecteur : omxplayer --no-osd --no-keys fichier.mp3
- --no-osd désactive l'affichage des informations sur le terminal
- --no-keys permet de fermer le lecteur par SIGINT depuis le terminal
Le test sur d'autres lecteurs, comme vlc aussi en ligne de commande, ou d'autres fonctions n'a pas pu se faire car nous n'avons pas réussi à connecter la Raspberry au réseau pour mettre à jour et faire les installations.
La partie gestion des différents moteurs s’exécute correctement en l’absence de la partie audio. En l'état actuel le lancement d'une piste audio et son arrêt est possible, mais ne fonctionne qu’une seule fois par exécution du code. Le thread audio lance un script shell avec un paramètre qui permet de démarrer le fichier audio correspondant à la situation. Le lancement du script et du lecteur se fait en arrière plan. L'envoi de la commande "pkill omxplayer" permet de le fermer lorsque que l'enregistrement arrive à son terme. Mais dès lors que l'enregistrement démarre, les moteurs restent bloqués dans leur état précédent.
Le harnais
Le harnais doit comporté la RPi4 avec son hat,le capteur et la batterie.Cependant,l'ensemble du harnais a été conçu avec l'idée d'un harnais modulable. Il y a deux pièce principales que constituent la plaque avant et la plaque arrière du harnais. Ensuite, chaque pièce vient se fixer sur l'une de ces dernières. Ainsi, le harnais peut être adaptable à d'autres version de RPi,à d'autres styles de caméra,à d'autre formes de batterie,etc. Toutes les modélisations ont été réalisées sur le logiciel de CAO Onshape.
Coque et bras de la caméra
La caméra doit se situé à l'avant de l'utilisateur et la position de la caméra doit être réglable.
Pour ce faire, nous avons modélisé 4 piéces qui constitue le bras et la coque de la caméra.
Premiérement, nous avons modélisé la coque de la caméra selon les mesures suivantes.
Fichier:Cotes-capteur.pdf
Datasheet D435 Intel : [[18]]
Viennent ensuite les deux parties qui constituerons le bras supportant la caméra.Nous pouvons ajouter une pièce qui permettra au fil de la caméra de rester contre le bras du harnais. Cette pièce sera imprimée en filament flex, contrairement aux autres pièces évoquées précédemment.
Et enfin,nous avons modélisé la pièce qui permet de fixer le bras à la plaque avant du harnais.
Finalement, voici le résultat :
Le bras mesure au maximum 20cm.
Boite Raspberry
Avec notre hat, il etait difficle de trouver un fichier de boite de RPi 4 qui convienne. Nous avons donc, là aussi, dû créer une boite pour la Raspberry Pi 4 avec le hat (et un potentiel ventilateur) sur mesures.
Voici des schéma qui expliquent la disposition des éléments dans la boite:
Lien vers les connecteurs : [[19]]
Lien vers le ventilateur : [[20]]
Datasheet Raspberry Pi 4 :[[21]]
[[22]]
Voici la modélisation de la boite avec son couvercle :
A noter que nous avons ajouté une ouverture (visible sur la troisième photo ci-dessous) afin de faire passer une gaine avec à l'intérieur les fils d'alimentation des motoréducteurs et des moteurs vibrants.
Et voici, l'impression :
Dimensions:115*70*53mm (l*L*h)
Plaque avant
La plaque avant du harnais se devait d’être solide; en effet, elle supporte le poids de la caméra ainsi que la Raspberry Pi 4 et son hat, mais aussi légère et peu encombrante. Ainsi, voici la forme de la plaque que nous avons décidé de modéliser et aussi un petit accessoire qui nous permet de concentrer les fils et ainsi éviter d’abîmer les fils.
Les connections de l'ensemble du dispositif s'effectuent avec 4 fils:
- fils d'alimentation de la Raspberry Pi 4 relié à la batterie ( en vert )
- fils de connection avec le capteur ( en bleu ) [pour notre capteur, l'embout est du type USB-C ( bleu pointillé )]
- fils d'alimentations des motoréducteurs et moteurs vibrants ( en rouge )
- écouteur prise jack ( en rose )
Boite batterie
Il était impératif que notre dispositif soit nomade afin d'offrir à Florian le plus d'autonomie possible. Nous disposons alors d'une batterie externe dont les dimensions sont donnés sur le schéma suivant :
Lien vers la batterie externe:[23]
La boite de la batterie se composent de seulement deux pièce: la boite et le couvercle ; tout deux adapter au design de la batterie externe.
Dimensions:130*91.8*27mm
Plaque arrière
Tout comme la plaque avant, la plaque arrière doit répondre aux cahier des charges concernant la solidité et le confort. La plaque arrière ne supporte que la batterie dans notre application. Le choix de faire deux piéce distincte repose sur l'idée principale d'un harnais dit "modulable", ainsi il est tout a fait envisageable d'adapter la boite à d'autres batterie ou un autre objet.
Nous aboutissons alors à l'arrière du harnais :
Finalisation du harnais
Pour que le harnais soit accessible, nous avons décidé d'ajouter un système de réglage pour adapter à toutes les morphologies et aussi un système d'attache relativement simple (Lien vers le fichier.stl des boucle d'attache: [24]]).
Après la couture des bretelles et l'assemblage, voici le résultat final de notre harnais :
Liste du matériel nécessaire :
- 3 vis M5 de longeur 25mm et les boulons associés
- 24 vis M3 de longeur 10mm et les boulons associés
Pour information, le harnais seule pèse 398g.
La main
Nous avions déjà décidé au semestre dernier le modèle de la main. Ce semestre nous avons décidé des modifications à effectuer sur le fichier .stl de la main pour l'adapter à notre dispositif. Ainsi, l'objectif du semestre 8 a été d'effectuer ces changements et d'imprimer la main.
Nous pouvons remarquer que l'emplacement rectangulaire des trois motoréducteurs ainsi que les emplacements circulaires pour les mini moteurs vibrants.
L'impression de la paume dure 25h. Aprés de multiples tentatives, la meilleur position d'impression est celle illustrée sur la photo suivante par le logiciel Cura.
Nous vous présentons l'évolution de l'impression à différents stades:
Voici la main en fin d'impression :
Par manque de temps, nous n'avons pas pu imprimer la coque arrière.
Documents rendus
Rapport du S8 : Fichier:Rapport S8 Projet P18.pdf
Support de présentation : Fichier:Plan de présentation S8.pdf