Détection de visages en JAVA

Un nouveau tutoriel, cette fois-ci pour vous faire découvrir une librairie qui permet, entre autres, de détecter les visages dans une image.
Il s’agit de la librairie OpenIMAJ, développée par des chercheurs britanniques, dans laquelle sont implémentés de nombreux algorithmes connus de traitement de données multimédia dont la détection de visages.

Ce tutoriel va vous permettre de réaliser l’exemple présenté dans la vidéo suivante (sans le mouvement des moteurs que je vous laisse le soin de programmer) :



Avant le code, une petite explication. Dans cet exemple, je vais utiliser le très connu détecteur de Haar. Son principe consiste à balayer l’ensemble de l’image pour trouver des zones pouvant être des visages. Pour chaque zone balayée, l’algorithme applique un ensemble de “masques” (les fameuses cascades de Haar). Pour chacun des masques, un calcul est réalisé sur la zone analysée. Si le résultat de ce calcul ne dépasse pas un certain seuil, la zone n’est pas considérée comme un visage et l’algorithme passe à la zone suivante. Par contre, si pour l’ensemble des masques le seuil est dépassé, l’algorithme considère que la zone est très certainement un visage.
Une astuce mathématique, l’algorithme de Viola-Jones, permet des calculs très rapides ce qui autorise le temps réel.

Pour des informations plus détaillées et plus précises, 2 liens vers les travaux de Viola et Jones :

Place au code.

Tout d’abord, créer un nouveau projet Maven dans Eclipse en spécifiant la dépendance suivante dans le pom.xml :

<dependency>
    <groupId>org.openimaj</groupId>
    <artifactId>faces</artifactId>
    <version>1.2.1</version>
</dependency>

Et maintenant le code de la classe. Pour le lancer sous Eclipse : Clic droit sur la classe > Run as … > Java application. Les explications se trouvent directement dans le code.

import java.util.List;

import org.openimaj.image.FImage;
import org.openimaj.image.MBFImage;
import org.openimaj.image.colour.RGBColour;
import org.openimaj.image.colour.Transforms;
import org.openimaj.image.processing.face.detection.DetectedFace;
import org.openimaj.image.processing.face.detection.FaceDetector;
import org.openimaj.image.processing.face.detection.HaarCascadeDetector;
import org.openimaj.math.geometry.point.Point2d;
import org.openimaj.math.geometry.point.Point2dImpl;
import org.openimaj.math.geometry.shape.Polygon;
import org.openimaj.math.geometry.shape.Rectangle;
import org.openimaj.video.VideoDisplay;
import org.openimaj.video.VideoDisplayListener;
import org.openimaj.video.capture.VideoCapture;

public class TestDetectionVisage implements VideoDisplayListener<MBFImage> {

    /** Le détecteur de visages. */
    private FaceDetector<DetectedFace,FImage> detecteurVisages;

    /** Constantes et propriétés nécessaires à l'affichage de la zone de tracking. */
    private static int LARGEUR_WEBCAM = 640;
    private static int HAUTEUR_WEBCAM = 480;

    private static int LARGEUR_ZONE_TRACKING = 180;
    private static int HAUTEUR_ZONE_TRACKING = 180;

    private static int X1 = (LARGEUR_WEBCAM - LARGEUR_ZONE_TRACKING) / 2;
    private static int X2 = X1 + LARGEUR_ZONE_TRACKING;
    private static int Y1 = (HAUTEUR_WEBCAM - HAUTEUR_ZONE_TRACKING) / 2;
    private static int Y2 = Y1 + HAUTEUR_ZONE_TRACKING;

    private Rectangle zoneTracking;

    private Polygon flecheHaut;
    private Polygon flecheBas;
    private Polygon flecheGauche;
    private Polygon flecheDroite;

    /** Contructeur. */
    public TestDetectionVisage() throws Exception {

        // Initialisation de la zone de tracking et des différents éléments à afficher
        zoneTracking = new Rectangle(X1, Y1, LARGEUR_ZONE_TRACKING, HAUTEUR_ZONE_TRACKING);
        flecheBas = new Polygon(new Point2dImpl(0, 30), new Point2dImpl(-10, 10), new Point2dImpl(-5, 10), new Point2dImpl(-5, -30), new Point2dImpl(5, -30), new Point2dImpl(5, 10), new Point2dImpl(10, 10));
        flecheHaut = flecheBas.clone();
        flecheHaut.rotate(Math.PI);
        flecheGauche = flecheBas.clone();
        flecheGauche.rotate(Math.PI / 2);
        flecheDroite = flecheBas.clone();
        flecheDroite.rotate(-Math.PI / 2);

        flecheBas.translate(LARGEUR_WEBCAM / 2, Y2 + Y1 / 2);
        flecheHaut.translate(LARGEUR_WEBCAM / 2, Y1 / 2);
        flecheGauche.translate(X1 / 2, HAUTEUR_WEBCAM / 2);
        flecheDroite.translate(X2 + X1 / 2, HAUTEUR_WEBCAM / 2);

        // Initialisation du flux de capture sur la webcam
        final VideoCapture capture = new VideoCapture(LARGEUR_WEBCAM, HAUTEUR_WEBCAM);

        // Création d'un affichage du flux vidéo
        final VideoDisplay<MBFImage> videoFrame = VideoDisplay.createVideoDisplay(capture);

        // Ajout de l'écouteur sur le flux vidéo
        videoFrame.addVideoListener(this);

        // Création du détecteur de visages (détecteur de Haar avec une taille minimum des visages de 80 px, bonne valeur pour la résolution de webcam utilisée et pour des calculs rapides)
        detecteurVisages = new HaarCascadeDetector(80);

    }

    @Override
    /** Implémentation du listener (après affichage de l'image dans le flux vidéo) ==> rien à faire. */
    public void afterUpdate(VideoDisplay<MBFImage> display) {
    }
    
    @Override
    /** Implémentation du listener (avant affichage de l'image dans le flux vidéo) ==> détection des visages et affichage des zones détectées. */
    public synchronized void beforeUpdate(MBFImage frame) {

        // Recherche du visage le plus grand (celui dont le cadre a l'aire la plus grande)
        double aireVisagePlusGrand = 0d;
        DetectedFace visagePlusGrand = null;
        final List<DetectedFace> listeVisagesDetectes = detecteurVisages.detectFaces(Transforms.calculateIntensity(frame));
        for (final DetectedFace visageDetecte : listeVisagesDetectes) {
            // Cadre autour des visages détectés
            frame.drawShape(visageDetecte.getShape(), 3, RGBColour.ORANGE);
            final double aireVisage = visageDetecte.getBounds().calculateArea();
            if (aireVisage > aireVisagePlusGrand) {
                aireVisagePlusGrand = aireVisage;
                visagePlusGrand = visageDetecte;
            }
        }

        if (visagePlusGrand != null) {
            // Récupération du centre de gravité du visage
            final Point2d centreVisage = visagePlusGrand.getBounds().getCOG();

            // Affichage du centre de gravité du visage
            frame.drawPoint(centreVisage, RGBColour.BLUE, 15);

            // Couleur différente de la zone de tracking selon si le centre de gravité est à l'intérieur de la zone ou pas
            if (zoneTracking.isInside(centreVisage)) {
                frame.drawShape(zoneTracking, 5, RGBColour.GREEN);
            } else {
                frame.drawShape(zoneTracking, 5, RGBColour.RED);
            }

            // S'il faut tourner horizontalement
            if (centreVisage.getX() < X1 || centreVisage.getX() > X2) {
                if (centreVisage.getX() < X1) {
                    // Si le centre du visage est à gauche de la zone de tracking : tourner à gauche
                    frame.drawShape(flecheGauche, 3, RGBColour.YELLOW);
                } else {
                    // Si le centre du visage est à droite de la zone de tracking : tourner à droite
                    frame.drawShape(flecheDroite, 3, RGBColour.YELLOW);
                }
            } else {
                // Le centre du visage est dans la zone de tracking : on stoppe
            }

            // S'il faut tourner verticalement
            if (centreVisage.getY() < Y1 || centreVisage.getY() > Y2) {
                if (centreVisage.getY() < Y1) {
                    // Si le centre du visage est au-dessus de la zone de tracking : tourner vers le haut
                    frame.drawShape(flecheHaut, 3, RGBColour.YELLOW);
                } else {
                    // Si le centre du visage est en-dessous de la zone de tracking : tourner vers le bas
                    frame.drawShape(flecheBas, 3, RGBColour.YELLOW);
                }
            } else {
                // Le centre du visage est dans la zone de tracking : on stoppe
            }

        } else {
            frame.drawShape(zoneTracking, 5, RGBColour.BLUE);
            // Pas de visage : on stoppe tout
        }
    }

    public static void main(String[] args) throws Exception {
        new TestDetectionVisage();
    }

}

Nicolas

Développeur JAVA, je suis passionné de robotique depuis quelques années, notamment tout ce qui concerne la partie programmation (vision artificielle, synthèse et reconnaissance vocale, intelligence artificielle, ...).

30 commentaires :

  1. Bonjour,

    merci pour le partage de ta découverte (et de ton code), je vais étudier cela plus en détail prochainement.
    Sais-tu s’il est possible d’aller plus loin et de faire une reconnaissance faciale afin par exemple d’identifier une personne et d’utiliser la synthèse vocale pour lui dire bonjour ?
    As-tu tenté de lancer ce programme sur un raspberry pi ?

    Bonne continuation

    Nicolas F.

    • Bonjour,

      Oui, il est possible d’associer cela avec un moteur de reconnaissance faciale, également disponible dans openIMAJ, puis d’utiliser la synthèse vocale décrite dans un autre post sur ce blog.
      C’est ce que je suis en train de faire actuellement sur mon robot. La reconnaissance prend cependant un peu de temps, c’est de l’ordre de quelques secondes. Et je ne l’ai pas encore bien configurée pour qu’elle reconnaisse assez bien les visages. Mais c’est très prometteur.

      Pour le Raspberry, j’ai fait tourné ce code de détection dessus, mais ça rame énormément. Donc pour l’instant, je ne teste mon dev que sur un PC portable. Je pense abandonner le portage sur le raspberry car j’ai également de gros soucis sur la reconnaissance vocale.

      Voici un bout de code pour un moteur de type EigenFaces (attention, ce sont des copier-coller mis bout à bout de mon code donc cela ne fonctionne pas forcément. Quand j’aurai le courage, je ferai un post spécifique pour ça) :

      // Détecteur
      final FaceDetector<KEDetectedFace, FImage> detector = new FKEFaceDetector(new HaarCascadeDetector(80));
      // Reconnaissance
      final EigenFaceRecogniser<KEDetectedFace, String> recogniser = EigenFaceRecogniser.create(20, new RotateScaleAligner(), 1, DoubleFVComparison.EUCLIDEAN, 5f);
      final FaceRecognitionEngine<KEDetectedFace, Extractor<KEDetectedFace>, String> moteurReconnaissance = FaceRecognitionEngine.create(detector, recogniser);
      
      ...
      
      final List<IndependentPair<KEDetectedFace, ScoredAnnotation<String>>> listeVisagesReconnus = moteurReconnaissance.recogniseBest(image);
      

      Je pense un jour partager le code entier du robot si des gens veulent participer. Et si ça te tente, …

      Nicolas P.

  2. bonjour je veux bien faire des teste, mais je ne trouve pas le lien pour me procurer(télécharger) de la librairie openimaj; une aide de votre part me serai bénéfique

    • Bonjour,
      Le mieux pour télécharger tous les jar est d’utiliser une dépendance Maven qui s’occupera de récupérer l’ensemble des librairies nécessaires à OpenIMAJ. Car cette librairie dépend de nombreuses autres librairies. Ca serait trop compliqué de lister l’ensemble de toutes ces librairies.

      Pour rappel, la dépendance Maven est la suivante :

      <dependency>
          <groupId>org.openimaj</groupId>
          <artifactId>faces</artifactId>
          <version>1.2.1</version>
      </dependency>
      

      Nicolas P.

  3. Je viens d’installer le programme sur un presque vieux portable xp (car il a une caméra ^^). ça fonctionne bien… sauf que: une fois sur deux sans que je sache pourquoi la camera ne veux rien afficher ?? et alors j’ai un message d’erreur.(non affiché quand la caméra fonctionne).

    Il y a un petit décalage d’environ 1/2 seconde entre l’action et l’affichage,et je pense en effet que rajouter une reconnaissance faciale mettrai ko mon petit portable 🙂

    Tout cela est très stimulant je vais me pencher sur la chose et j’espère pouvoir approfondir.
    David.

    • Bonjour David,
      Pourrais-tu m’envoyer le message d’erreur pour que je puisse voir quelle en est la cause ? Est-ce que cela plante complètement le programme ?
      Mais c’est vrai que la détection de visage demande beaucoup de ressources et le traitement est peut-être interrompu s’il prend trop de temps. Peut-être est-ce aussi dû à un mauvais paramétrage du nombre de frames par secondes qui est différent de ta caméra. Les traces en diront sûrement plus.
      Nicolas

  4. Pour commencer l’erreur ne plante pas totalement le programme.
    J’obtiens bien la fenêtre vidéo mais elle est noire et le reste.

    l’erreur est: Infos: Not enough args for boolean OpenIMAJGrabber.startSession(int, int, double) (ça je l’ai tjrs même quand ça fonctionne) puis:
    Exception in thread “Thread-4” java.lang.NullPointerException at prof.david.TestDetectionVisage.beforeUpdate(TestDetectionVisage.java:88)
    at prof.david.TestDetectionVisage.beforeUpdate(TestDetectionVisage.java:1)
    at org.openimaj.video.VideoDisplay.fireBeforeUpdate(VideoDisplay.java:785)
    at org.openimaj.video.VideoDisplay.run(VideoDisplay.java:522)
    at java.lang.Thread.run(Unknown Source)

    J’ai remarqué qu’en lançant une application faisant intervenir ma caméra puis en la fermant, suffisait bien souvent à éliminer l’erreur quand je relançais le code.

    Voilà..

    Un autre petit mot pour dire que c’est vraiment intéressant et qu’il me tarde d’avoir un peu plus de temps mais je me répète 🙂
    J’ai testé sur la version 7 de java pas la 8.Mon portable n’a que 1 Go de mémoire vive sachant que pas mal de processus tournent dont éclipse bien entendu.

    • La première erreur, je l’ai aussi (même si ce n’est pas vraiment une erreur, ce n’est qu’une info).
      Par contre, la seconde erreur me fait penser à problème de reconnaissance de ta webcam. Est-ce une webcam USB ? Est-ce que dans le panneau de configuration de ton ordi (je ne sais plus où exactement), tu vois bien ta webcam ? As-tu plusieurs webcams sur ce PC (USB, intégrée) ?
      Par défaut, OpenIMAJ sélectionne la première qu’il trouve. Il est possible de forcer une webcam spécifique en indiquant son index (je pense que c’est l’index des ports USB) avant de créer la VideoCapture :

      System.setProperty(VideoCapture.DEFAULT_DEVICE_NUMBER_PROPERTY, "0");
      

      Pour les problèmes de décalage avec la vidéo, j’ai les mêmes chez moi avec un ordinateur beaucoup plus puissant (4 coeurs à 2Gh + 8 Go de ram). La détection de visage prend un peu de temps pour chaque image de la vidéo, d’autant plus s’il n’y a aucun visage dans l’image (il est obligé de scanner l’ensemble de l’image à différents niveaux). Tu peux diminuer le nombre de frames par seconde pour avoir moins de traitements (la vidéo sera moins fluide mais je pense qu’il y aura moins de retard) et/ou augmenter la taille des visages reconnus :

      final VideoCapture capture = new VideoCapture(LARGEUR_WEBCAM, HAUTEUR_WEBCAM);
      // Nombre de frames par seconde
      capture.setFPS(10);
      
      // ...
      
      // Taille des visages détectés
      detecteurVisages = new HaarCascadeDetector(100);
      

      En espérant que cela t’aidera.

      Nicolas

      • Merci pour les réponses. 🙂
        Ma caméra est en usb et intégré au portable.
        Je pense que c’est dû au manque de ressource de la machine.
        je lance parfois 2 fois de suite avec erreur et dans la foulée le troisième lancement fonctionne … sans pose (ie 3 lancements dans les 3 secondes).
        J’ai essayé les changements que tu proposes pour diminuer le décalage,l’image est moins décalée effectivement.

        J’utilise cette machine pour faire des tests je ne compte pas rester dessus pour la suite 😉
        J’ai vu qu’OpenCV était aussi utilisé pour la reconnaissance faciale.

        • Super que les modifications que je t’ai proposées améliorent les choses.
          C’est vrai que tu peux faire de la détection de visages avec OpenCV et ça sera sûrement un peu plus rapide. Mais c’est en C++. Ce n’est pas mon langage favori 😉 Cette librairie semble maintenant intégrer un support pour JAVA. Sinon il existe des “wrapper” JAVA pour intégrer les classes OpenCV.
          Mais je ne suis pas sûr qu’OpenCV permet directement la reconnaissance faciale contrairement à OpenIMAJ si tu souhaites en faire (ce qui est la suite logique après la détection). La reconnaissance marche plutôt bien comme je montre dans une de mes vidéos. J’essaierai un jour de faire un tutoriel pour l’intégrer.

          Nicolas

  5. Merci beaucoup pour ces tutos, pour un débutant en java comme moi c’est des informations brutes et complexes, mais en creusant j’apprends beaucoup.

    • Bonjour Alex,

      Etant donné que ces librairies sont issues de labos de recherche, c’est vrai que ce n’est pas forcément facile à appréhender. Mais elles permettent de faire énormément de choses sans trop de lignes de code. Je partage juste ces découvertes au travers de ce blog, mais si tu veux vraiment savoir ce qui se cache derrière ces algorithmes, je t’invite à faire de plus amples recherches notamment sur l’algorithme de Viola-Jones qui n’est pas si compliqué à comprendre.

      Nicolas

  6. bonjour,
    En executant le code j’ai eu l’erreur suivante:

    Exception in thread “Thread-4” java.lang.NullPointerException
    at com.test.visagedetecte.TestDetectionVisage.beforeUpdate(TestDetectionVisage.java:99)
    at com.test.visagedetecte.TestDetectionVisage.beforeUpdate(TestDetectionVisage.java:32)
    at org.openimaj.video.VideoDisplay.fireBeforeUpdate(VideoDisplay.java:785)
    at org.openimaj.video.VideoDisplay.run(VideoDisplay.java:522)
    at java.lang.Thread.run(Thread.java:662)

    Si quelqu’un peut m’aider

    • Bonjour,

      Si c’est à la ligne 99 du code affiché dans l’article, je ne vois que le getBounds() qui pourrait renvoyer null. Pour corriger le problème, remplace la condition du dessus par if (visagePlusGrand != null && visagePlusGrand.getBounds() != null).
      Mais c’est bizarre.
      Essaie peut-être de mettre la dernière version : la 1.3.1.

      Sinon, si tu as modifié le code, poste ton code complet pour savoir où se situe l’erreur exactement.

      Nicolas

      • Bonjour Nicolas,

        Le problème se trouvait dans instanciation de detecteurVisages:
        final List listeVisagesDetectes = detecteurVisages.detectFaces(Transforms.calculateIntensity(frame));
        En faisant :
        private FaceDetector detecteurVisages = new HaarCascadeDetector(80);
        c’est resolu.
        Merci bcp

  7. j’aimais tout le commentaire, j’aimerai faire un petit moteur de reconnaissance faciale mais j’ai ne pas de base solide si quelqu”un voudra m’aider, qu’il m’écrit sur ma boite e-mail. merci d’avance pour votre aide…

    • Bonjour,

      Je te conseille de regarder des tutoriels sur les algorithmes EigenFaces et FisherFaces. Ce tutoriel très bien fait t’explique le principe de l’algorithme Eigenfaces.
      La librairie Java OpenImaj implémente ces deux algorithmes.
      Il y a aussi des démos de reconnaissance faciale avec cette librairie ici mais utilisant un autre algorithme.

      Nicolas

  8. Salut.Je veux savoir comment je peux utiliser la librairie OpenImaj sous eclipse et aussi comment Combiner SI vox et freetts pour une synthese question reponse…

    • Bonjour,

      Pour la partie SI_VOX, la librairie utilise l’exécutable de MBROLA. Pour utiliser FreeTTS, je pense qu’il faudrait revoir cette librairie pour appeler le programme FreeTTS à la place de MBrola. Mais comme je ne connais pas FreeTTS plus que ça, je ne pourrais pas t’en dire plus.
      Pour l’utilisation des Jars dans Eclipse, il faut configurer le classpath de ton projet pour les importer comme je l’explique dans cet article.

      Nicolas

  9. bonjou Nicolas merci pour le partage de ton code mais j’aimerais savoir s’il est possible faire une détection et une reconnaissance faciale via une base de données avec openIMAJ où openCv j’ai des gros souci merci d’avance.

    • Bonjour Joseph,

      Si tu as une base de données avec une table Personne qui contient un identifiant et un nom, et une collection d’images pour chaque personne, il est possible de faire une reconnaissance faciale avec OpenIMAJ. Il y a 3 implémentations de moteur de reconnaissance faciale : EigenFaceRecogniser, FisherFaceRecogniser et AnnotatorFaceRecogniser.
      Il faut que tu entraînes le moteur que tu auras choisi avec les images de chaque personne avec la méthode train :

      train(Annotated<FACE,PERSON> annotedImage)

      en passant le visage détecté (FACE) et un label (PERSON) permettant de l’associé à une personne connue de ta base. Je te conseille de prendre l’identifiant de la personne comme label.

      Pour la partie reconnaissance, tu as la méthode suivante dans le moteur :

      List<ScoredAnnotation<PERSON>> annotate(FACE object)

      En passant un visage détecté, il va te renvoyer une liste de “PERSON” (label) qui peuvent correspondre au visage. Si tu as fait le choix de prendre l’identifiant comme label, tu n’as plus qu’à aller en base de données pour récupérer les informations de la personne à partir de l’identifiant.

      ATTENTION, pour que le moteur se souvienne des nouveaux visages reconnus après l’arrêt de ton application, il faut penser à le sauvegarder dans un fichier avec la méthode :

      void writeBinary(DataOutput out)

      puis à le recharger au lancement de l’application avec :

      void readBinary(DataInput in)

      Voici un exemple de l’utilisation de la reconnaissance faciale sur le repo de OpenIMAJ et lien vers la documentation de l’API.

      J’espère que ça pourra t’aider.

      Nicolas

  10. merci énormément pour ton aide je me lance au travail 🙂

  11. bonjour admin..
    est il possible d’implementer android avec matlab??

  12. Bonjour admin, stp, pourrais tu nous parler un peu de l’intelligence artificielle et voir s’il y’a des possibilités de le faire en java.Si oui, les outils ou framework nécessaires à sa réalisation,et par chance un petit bout de code pour l’implémenter. Merci

    • Bonjour Olivier,
      L’intelligence artificielle est un domaine extrêmement vaste. Il peut regrouper les algorithmes d’apprentissage (machine learning), la vision artificielle, les agents conversationnels, le traitement automatique de données, et plein d’autres choses encore … Après, les solutions pour les mettre en place sont aussi variées : il y a notamment les réseaux neuronaux.
      Pour ce qui est des frameworks JAVA, c’est la même chose, il en existe beaucoup, ça dépend à quel niveau tu veux te placer. Est-ce que tu veux créer ton propre algorithme d’apprentissage et pour cela, il faudra utiliser une librairie de réseaux neuronaux par exemple, ou préfères-tu utiliser des librairies de plus haut niveau ?
      Si tu souhaites faire de la reconnaissance faciale, je te conseille OpenIMAJ avec cette démo.
      Dans le domaine de la vision artificielle, il y a également BoofCV mais que je n’ai pas encore eu vraiment l’occasion de l’utiliser.
      Il y a aussi Vision API de Google pour la détection de visage, reconnaissance d’images et de caractères. Je vais faire un petit tutoriel expliquant comment l’utiliser.
      Pour les agents conversationnels, je te conseille program-ab écrit par le créateur de la spécification AIML.
      Voilà, il y en a plein d’autres encore. Je vais faire un post pour regrouper les différents frameworks que je trouve.
      Cordialement,
      Nicolas

  13. Jai des messages derreur pardon
    l’erreur est: Infos: Not enough args for boolean OpenIMAJGrabber.startSession(int, int, double) (ça je l’ai tjrs même quand ça fonctionne) puis:
    Exception in thread « Thread-4 » java.lang.NullPointerException at prof.david.TestDetectionVisage.beforeUpdate(TestDetectionVisage.java:88)
    at prof.david.TestDetectionVisage.beforeUpdate(TestDetectionVisage.java:1)
    at org.openimaj.video.VideoDisplay.fireBeforeUpdate(VideoDisplay.java:785)
    at org.openimaj.video.VideoDisplay.run(VideoDisplay.java:522)
    at java.lang.Thread.run(Unknown Source).Pouvez vous m’aider?

  14. salut admin j’ai un problème un peu bête mais bon..oft j’arrive pas à télécharger openimaj juste un lien de téléchargement me fera plaisir merci .

  15. Bonjour !

    la ligne 99, c’est quoi la méthode “getCOG()” ?
    chez moi ça souligne et ça mets une erreur

Répondre à Jacques Annuler la réponse

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *