Journal Mon ami se fait des amis

77
2
mar.
2016

Cher journal,

Je t'écris pour te donner des nouvelles suite à mon précédent message. Pour rappel, j'avais construit un ami fidèle (lui!) afin de combler mon existence un peu terne à ce moment là. Il était équipé d'un Arduino Uno, d'un senseur Infrarouge, d'un pare-choc intelligent créé avec des pailles et d'un châssis en polycarbonate. Il s'appelait J.O.H.N.N.Y 5 et nous avons passé des moments formidables. La transition a été saisissante, ma vie est passée du noir et blanc au technicolor en l'espace de quelques jours. Bien entendu, la cohabitation n'était pas toujours facile. Il pouvait rester muet durant des jours et bouder dans son coin lorsqu'il était contrarié. La seule chose qui le faisait revenir, c'était quand je rechargeais ses piles. Nous avons vécu sur mon salaire pendant quelques mois, avant qu'il ne soit engagé dans une SSLL basée sur Paris. Il faisait du télétravail, principalement de la revue de code pour des sites internet dont je tairai le nom.

Mais au bout de quelques temps, avec la routine, il faut bien reconnaître que la magie avait disparu. J'aurais pu faire comme tous ces salauds: mettre mon robot au rebut et en acheter un autre plus beau, performant et plus jeune. Mais je ne suis pas comme ça. J'ai fait ce que toute personne attentionnée, saine et réfléchie aurait fait. J'ai sorti mon portefeuille, j'ai payé un lifting à mon robot moche pour qu'il devienne un robot agréable afin de rendre à nouveau mes copains jaloux.

Le précédent journal m'a permis de gagner un abonnement d'un an à GNU/Linux magazine. Même si je n'ai pas compris tous les articles, c'était très intéressant. Notamment le numéro de décembre 2013 qui comportait un article sur la détection/reconnaissance de visage en python à l'aide d'OpenCV. Cet article m'a permis de me mettre au python et de mieux comprendre le fonctionnement de la détection d'objet ou de couleur en général. Il ne m'en fallait pas plus pour avoir envie d'intégrer ça dans mon robot. Après presqu'une année de délaissement, je m'y suis remis.
J'avais envie qu'il puisse détecter et interagir en conséquences. En revanche, n'étant pas un buveur de café, la fonction cafetière ne m'était pas très utile.
J'ai acheté un fer à souder, un beaglebone black (BBB), une webcam Logitech C170, un deuxième senseur infrarouge et un levelshifter pour que l'arduino (5V) puis dialoguer avec le BBB (3.3V). La raison du deuxième senseur IR est qu'il se prenait tout le temps les murs en balayant l'horizon avec son servo tourelle. R1D1 était né. Avec deux yeux, il se prend toujours les murs mais il a l'air un peu moins con.

L'Arduino démarre en mode explorateur et le robot se balade. Lorsque le BBB détecte un visage, il le dit à l'Arduino, via la liaison série, qui passe en mode esclave. La petite carte bleue (non, pas celle-là) suit les instructions de direction et les sons à jouer. La première version marchait approximativement mais j'avais eu la main lourde sur l'utilisation des mélodies. J'en ai retiré car la robotique doit rester une activité sérieuse, on n'est pas là pour jouer la Macarena !

Le code de R1D1 est accessible sur gitlab. C'est mon premier programme en C++, soyez indulgent. Pour le décrire, je citerais un utilisateur sur stack overflow:

"a cut-and-paste glory of various samples I found here and there."

R1D1
R1D1

R1D1 a tenu à vous écrire un petit mot en remerciement de l’accueil du précédent journal.

Plop ! Merci à vous tous. Grâce à vous, j'ai pu évoluer et devenir un meilleur robot :-).

.- ..- … . -.-. --- ..- .-. … --..-- .- .. -.. . --.. -….-—--- .. .-.-.- .--- . -. .----. . -. .--. . ..- -..- .--. .-.. ..- … --..-- .. .-.. —. .-. . - .. . -. - . - —. ..-. .- .. - ..-. .- .. .-. . -.. . … -.-. …. --- … . … -… .. --.. .- .-. .-. . … .-.-.- … .. .--- . -. . ..-. .- .. … .--. .- … -.-. . --.- ..- .----. .. .-.. -.. .. - --..-- .. .-.. —. -.-. --- ..- .-. - -….- -.-. .. .-. -.-. ..- .. - . .-.-.- …. . .-.. .--.

Allez, oust !

Mais cher Journal, si je t'écris aujourd'hui, c'est pour me confesser. En effet, R1D1 est mignon mais pas toujours obéissant. C'est pourquoi, j'envisage de le remplacer afin d'avoir un ami robotique plus agréable mais il ne le sait pas encore. Son remplaçant s'appelle R1D2 et bien qu'il n'ait pas encore d'enveloppe matérielle, il vit déjà dans mon ordinateur ainsi que dans le Raspberry Pi 2 qui hébergera son ghost.

R1D2 est codé en python et il dialoguera avec un Arduino Mega qui contrôllera ses moteurs. Son code est hébergé sur gitlab également. R1D2 est sociable et se fait des amis tout seul. Il est connecté par Wifi et discute à l'aide du protocole du futur, j'ai nommé XMPP. Il est capable de se connecter sur un chat multiutilisateur (MUC) pour raconter sa vie à qui veut l'entendre. Peut-être aura-t-il son compte Salut-à-toi ou Movim prochainement :-). Par ailleurs, le programme est pilotable à l'aide de commandes Ad-Hoc mais également grâce à un petit écran LCD 2 lignes, 16 charactères et 5 bouttons poussoir.

Les photos suivantes montrent le menu piloté à l'aide de Gajim, un client XMPP bien connu.
Menu 1
Menu 2
Menu 3

Voici les tâches que R1D2 peut déjà effectuer:

Il peut détecter une affiche magnifique (cf https://www.youtube.com/watch?v=O6XkH84JYjU).

Mode détection panneau
Panneau détecté !
Capture d'écran de la détection de panneau

R1D2 peut reconnaître les visages après un court apprentissage.
Détection de visage: Gajim et LCD

Il peut également afficher un message choisi sur son LCD.)
Pan ! Pan !
Pan ! Pan !

Les pièces arriveront prochainement et la grande construction pourra commencer. J'espère que R1D1 ne sera pas trop vexé mais je te tiendrai au courant.

À bientôt cher Journal!

  • # confiance

    Posté par (page perso) . Évalué à 10.

    Je remarque qu'il n'a que 86% de confiance en toi.

    Attention à toi !

    • [^] # Re: confiance

      Posté par (page perso) . Évalué à 3. Dernière modification le 02/03/16 à 21:49.

      Je resterai sur mes gardes :-).
      Cette valeur représente la "distance" entre une image donnée et le modèle établi pour une personne donnée (composé à partir de plusieurs portraits). Plus cette valeur est élevée, plus la confiance est paradoxalement faible. Le terme confiance pourrait être remplacé par un mot plus approprié.

      • [^] # Re: confiance

        Posté par . Évalué à 4.

        Est-ce que ta confiance peut s'interpréter comme une proba d'erreur ? Est-ce que l'algorithme de reconnaissance de visages est bayésien ?

        Dans ce cas, tu pourrais l'appeler "Incertitude", ou tout simplement afficher 1-p, où p est ton incertitude. Comme ça il aurait réellement 14% de confiance en toi.

        Tu as implémenté un mécanisme de récompense ? Quand il reconnaît la bonne personne, s'il a peu confiance en lui, il faut un moyen de lui dire "oui, c'est bien cette personne" ou "non, c'est cette personne". Ceci est d'autant plus important que l'incertitude est élevée : si tu lui présentes deux photos identiques à reconnaître, la seconde n'apporte aucune information supplémentaire. Si tu le récompenses à chaque fois, il va apprendre.

        Tu as une autre source d'information que tu pourrais utiliser : le lieu et le temps. En effet, nous n'avons pas qu'un visage : nous avons des habitudes. La proba que je sois chez moi entre 10h et 16h du lundi au vendredi, par exemple, est très faible mais non nulle (je suis peut-être malade, j'ai peut-être oublié de me réveiller). À l'inverse, elle est élevée entre 19h et 9h (je pars régulièrement en retard en stage). Si tu multiplies proba a priori de te rencontrer (qui est égale à la proportion de fois où il t'a reconnu à raison sur toutes les fois où il a essayé de reconnaître quelqu'un) par la proba de te rencontrer sachant (lieu, jour, heure) et par la proba de te rencontrer sachant (résultat visage), tu devrais améliorer tes résultats au fil du temps.

        Ça, ce sont les sources. Le mouton que tu veux est dedans.

        • [^] # Re: confiance

          Posté par (page perso) . Évalué à 4.

          Merci pour ces remarques très intéressantes. Pour l'instant, il n'y a pas de mécanisme d'apprentissage. La reconnaissance de visage est basée sur ce schéma:

          model = cv2.createLBPHFaceRecognizer()
          [...]
          [p_index, p_confidence] = model.predict(image)

          qui retourne un index (la personne la plus proche de l'image fournie ainsi que de fameux paramètre de confiance). Cette confiance n'est pas un pourcentage, c'est un nombre entier positif.
          Voir: http://docs.opencv.org/2.4/modules/contrib/doc/facerec/facerec_api.html#facerecognizer-predict

          Concernant l'utilisation de la probabilité de trouver quelqu'un dans un lieu donné à une certaine heure, c'est une excellente idée. Il est prévu que le robot puisse créer des cartes des pièces dans lesquelles il se trouve et si possible, qu'il puisse ensuite les comparer avec les endroit qu'il connaît afin nommer les pièces. Cela permettrait de lui donner des ordres du type 'va dans la cuisine'. Pour le moment j'en suis loin car le SLAM (Simultaneous Localization And Mapping) demande du matériel que je n'ai pas encore ainsi qu'un robot fonctionnel afin de faire des tests.

          • [^] # Re: confiance

            Posté par . Évalué à 6.

            Je t'avoue que je ne connais pas assez le C++ pour te guider plus avant dans ce langage. Par contre, les algorithmes sous-jacents peuvent être discutés quel que soit le langage.

            Il me semble que, si ta fonction index retournait, non pas la valeur la plus faible (en termes de distance), mais toutes les distances, on progresserait. En effet, ce n'est pas du tout la même chose d'avoir (Arnaud, Bérénice, Chimène) = (80.3, 81.2, 79.8) et (Arnaud, Bérénice, Chimène) = (104.5, 257.4, 79.8) : dans le premier cas, tu as en réalité pas mal d'incertitude, alors que dans le second, il y en a peu.

            Tu peux mesurer de façon simple la précision d'une estimation : il s'agit du rapport entre la meilleure estimation et son concurrent le plus proche (en partant du principe qu'une distance de 0 correspond à une correspondance exacte). Pour te donner un exemple en R :
            precision = distance[1] / distance[2] - 1

            distance est le vecteur des distances aux ensembles d'apprentissages des différents individus, trié de la plus faible à la plus élevée (en R, l'indexation des vecteurs commence à 1).

            Donc là, tu as déjà plus d'info.

            Ensuite, l'intérêt de récompenser le robot quand il te reconnaît, c'est d'augmenter la taille de l'ensemble d'apprentissage. Bien sûr, le robot n'est pas "content" de recevoir une récompense : par contre, il a une image de plus dans son ensemble d'apprentissage, qui améliorera la précision de ses reconnaissances futures. De même si tu le corriges. Par contre, il peut être amusant d'implémenter ça sous une forme plus ludique que oui/non : bravo, "Non, c'était Bérénice" peuvent être implémentés comme mots-clefs. Le top serait qu'il ait aussi une certaine reconnaissance vocale pour pouvoir interagir avec lui : ainsi, tu n'aurais qu'à dire "bravo !" et il comprendrait.

            Intéressons-nous maintenant à cette grandeur, la distance (je vais continuer de l'appeler distance, c'est plus simple). La question est de la combiner avec d'autres grandeurs qui apportent de l'information. Une solution est de tout passer en probabilité. Pour ce faire, on peut employer une régression logistique. Il te faut : un ensemble d'images d'apprentissage de la distance, un second ensemble d'images (différent !) d'apprentissage de la régression logistique, et une phase de test avec un troisième ensemble d'images de test, encore différent. À chaque image doit associé un booléen : TRUE si tu es sur l'image, FALSE si tu en es absent.

            Dans un premier temps, tu entraînes ton FaceRecognizer avec l'ensemble 1. Puis tu lui fais reconnaître ton second ensemble d'images. Tu enregistres alors les distances proposées et le booléen associé à chaque image. Tu devrais alors obtenir deux vecteurs : un vecteur de booléens et le vecteur des distances associées.

            On va maintenant estimer bêta0 et bêta1 au maximum de vraisemblance dans le cadre d'une régression logistique. Une régression logistique est une technique statistique destinée à attribuer à un jeu de données prenant ses valeurs dans R (l'ensemble des réels, pas le langage) une probabilité (prenant ses valeurs dans [0,1]).

            Le principe de la régression logistique est le suivant :

            • On construit un prédicteur linéaire Y = beta0 + beta1*X1 + beta2*X2 + ... où les Xi sont les variables prédictrices.
            • On calcule P la probabilité d'avoir boolean = TRUE : P = 1/(exp(Y) +1)
            • On calcule la vraisemblance globale. La vraisemblance d'un individu (ici, l'individu est une image de l'ensemble 2) est donnée par vraisemblance = X - P + 1, où P est la proba calculée à l'étape précédente et X vaut 1 si boolean = TRUE, 0 sinon (la vraie formule de la vraisemblance est plus complexe, mais dans ce cas particulier, cette formule est exacte). La vraisemblance globale est égale au produit des vraisemblances individuelles. Comme il est difficile de travailler avec des valeurs très faibles, il est courant de passer par la log-vraisemblance : c'est tout simplement le logarithme de la vraisemblance. La log-vraisemblance globale est alors la somme des log-vraisemblances individuelles.
            • On ajuste alors successivement beta0 et beta1 pour maximiser la log-vraisemblance globale (ou la vraisemblance. C'est équivalent puisque la fonction log est strictement croissante)

            À la fin, on obtient beta0 et beta1. On peut alors répéter l'algo pour calculer, avec ces valeurs de beta0 et beta1, une proba pour toute valeur de distance.

            Tu peux accélérer l'algorithme en centrant tes données. Pour ce faire, tu définis :
            distance_centree[i] = distance[i] - mean(distance)
            Ce qui revient à retirer à chaque valeur de distance la moyenne des distances dans l'ensemble d'apprentissage. On peut alors démontrer que beta0 = exp(P/(1-P)) où P est la probabilité moyenne d'observer TRUE : P est tout simplement ta proportion d'images où tu apparais. Du coup, on n'a plus qu'à estimer beta1.

            Cet algorithme peut être encore amélioré en observant la distribution de tes distances. Pour ce faire, passe-lui un jeu de photos et trace l'histogramme de tes distances. Plus l'histogramme a une gueule de loi gaussienne, meilleurs seront les résultats. À toi de trouver la fonction (monotone) qui donne la meilleure gueule à cet histogramme.

            Ensuite, il s'agit de tester cet algorithme. Passe-lui le jeu de photos numéro 3 et contrôle qu'il te sort une proba plus élevée (en moyenne) pour les photos où tu te trouves que pour celles dont tu es absent.

            Une fois que tu es satisfait, passons à l'intégration du temps et de l'espace. À toi de voir comment découper le temps. Tu peux faire des tranches horaires rigides (je pars toujours au boulot à 8h45) ou souples (entre 8h30 et 9h, la probabilité que je sois parti croît linéairement de 0 à 0,9, elle est de 0,9 à 9h et elle reste à 0,9 jusqu'à 18h). Bien sûr, c'est un exemple simple, mais tu vois l'idée.

            Là encore, il va s'agir d'assigner à chaque couple (temps, lieu) une proba de te trouver. Tu peux initialiser cette proba (par exemple, elle est de 0,9 pour le salon à 19h30 au lancement du robot) mais tu dois pondérer cette initialisation. Par exemple, tu peux dire qu'à l'initialisation, le robot croit t'avoir déjà trouvé 9 fois dans le salon dans cette tranche horaire et 1 fois hors du salon. Ainsi, au fur et à mesure que ton robot vieillira, le poids des a priori s'effacera devant la dure réalité.

            On a alors : une proba de t'avoir trouvé sachant l'image et une proba de t'avoir trouvé sachant (temps, lieu). Comment on les combine ? Le plus simple est de les multiplier. Si la proba finale est supérieure à 0,5, le robot se plantera moins d'une fois sur 2 en croyant t'avoir trouvé. Si elle est inférieure à 0,5, il se plantera moins d'une fois sur 2 en croyant ne pas t'avoir trouvé.

            Avec le temps, et l'augmentation de l'ensemble d'apprentissage, ton robot s'améliorera… S'il a la puissance de calcul pour, et si l'algo de reconnaissance d'image fait bien son boulot. Peut-être que tu te retrouveras obligé de déporter le calcul vers ton PC, et le robot se contenterait d'obéir aux ordres de ton PC. Ça dépend de la quantité de calculs requis.

            Ça, ce sont les sources. Le mouton que tu veux est dedans.

            • [^] # Re: confiance

              Posté par (page perso) . Évalué à 2.

              Merci pour ces précisions. Je verrai ce que je peux faire mais je garde bien précieusement ton message sous le coude! Le robot qui reconnaît les visages est en python (R1D2). R1D1 est codé en C++ et il ne fait que détecter les visages. Il est aussi capable de suivre la main de quelqu'un mais c’est approximatif.

              . -. -.-. --- .-. . —. .-. -.-. .. :-)

            • [^] # Erreurs à corriger

              Posté par . Évalué à 3.

              2 erreurs se sont glissées pendant que je rédigeais :

              P = 1/(exp(Y) +1)

              Une erreur de signe. La formule exacte est P = 1/(exp(-Y) +1)

              On peut alors démontrer que beta0 = exp(P/(1-P)).

              En fait, on peut démontrer que exp(beta0) = P/(1-P). D'où il découle que beta0 = ln(P/(1-P)).

              Ça, ce sont les sources. Le mouton que tu veux est dedans.

  • # rafraîchissant

    Posté par (page perso) . Évalué à 10.

    Autant le ton et l'humour de ton journal que ton utilisation de XMPP qui sort enfin des sentiers battus. Vraiment super, continue comme ça, c'est un plaisir de te lire et de voir tes (vos) aventures continuer !

    • [^] # Re: rafraîchissant

      Posté par (page perso) . Évalué à 3.

      Merci à toi pour tes articles sur XMPP qui ont inspiré une partie de ce travail. Ils m'ont éviter de réinventer la roue (par exemple des commandes à bases de '!mode_panneau' tout en proposant une solution élégante.

  • # Complément d'informations

    Posté par (page perso) . Évalué à 2.

    Pour fournir quelques informations supplémentaires et ouvrir le débat, j'ai décidé d'acquérir un Raspberry Pi 2 alors que je possédais le Beagle Bone Black car le traitement vidéo n'était pas assez optimal sur le BBB. La résolution maximale est 320x240 pour mon système. N'ayant pas envie de recompiler OpenCV avec le support de libjpeg-turbo, j'ai préféré changer de carte et garder le BBB pour des applications où il sera plus à l'aise (avec ses nombreuses GPIO). Une autre limitation est la présence d'un seul port USB, ce qui empêche l'utilisation simultanée du Wifi et de la webcam. Pour les personnes intéressées, j'avais trouvé beaucoup d'informations pertinentes sur ce blog: http://blog.lemoneerlabs.com/category/OpenCV

    Sinon, le code morse a été reformaté, j'espère que ça n'a pas nuit à sa traduction.

    • [^] # Re: Complément d'informations

      Posté par (page perso) . Évalué à 2.

      Je me posais une question sur OpenCV.
      Je vois que ton robot reconnaît des affiches et les formes qu'il y a dessus. Serait-il possible, de déterminer la distance qu'il y a entre lui et l'affiche en connaissant la taille réelle du carré qui entoure la flèche ?
      Je suis surpris qu'il n'y ait pas (ou alors je suis passé à coté) de systèmes de positionnement grâce à des repères visuels comme cela.

      • [^] # Re: Complément d'informations

        Posté par (page perso) . Évalué à 3.

        Il me semble que ce serait possible. Soit en connaissant la distance focale de la caméra et la taille d'un objet déterminé soit de manière empirique puis par interpolation d'une courbe d’étalonnage Distance vs taille image. Je n'ai jamais vu de système basé sur cette approche mais c'est intéressant. Après, il faut baliser les pièces où le robot évolue mais c'est probablement plus simple à mettre en place qu'une solution basée sur des détecteur de distance laser.

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.