La reconnaissance faciale du CV ouvert n'est pas précise

13

Dans mon application, j'essaie de faire la reconnaissance faciale sur une image spécifique en utilisant Open CV, ici d'abord je forme une image, puis après la formation de cette image si je lance la reconnaissance faciale sur cette image, elle reconnaît avec succès ce visage formé. Cependant, lorsque je me tourne vers une autre image de la même personne, la reconnaissance ne fonctionne pas. Cela fonctionne juste sur l'image entraînée, donc ma question est de savoir comment la rectifier?

Mise à jour: ce que je veux faire, c'est que l'utilisateur sélectionne l'image d'une personne dans le stockage, puis après la formation de cette image sélectionnée, je veux récupérer toutes les images du stockage qui correspondent au visage de mon image formée

Voici ma classe d'activité:

public class MainActivity extends AppCompatActivity {
    private Mat rgba,gray;
    private CascadeClassifier classifier;
    private MatOfRect faces;
    private ArrayList<Mat> images;
    private ArrayList<String> imagesLabels;
    private Storage local;
    ImageView mimage;
    Button prev,next;
    ArrayList<Integer> imgs;
    private int label[] = new int[1];
    private double predict[] = new double[1];
    Integer pos = 0;
    private String[] uniqueLabels;
    FaceRecognizer recognize;
    private boolean trainfaces() {
        if(images.isEmpty())
            return false;
        List<Mat> imagesMatrix = new ArrayList<>();
        for (int i = 0; i < images.size(); i++)
            imagesMatrix.add(images.get(i));
        Set<String> uniqueLabelsSet = new HashSet<>(imagesLabels); // Get all unique labels
        uniqueLabels = uniqueLabelsSet.toArray(new String[uniqueLabelsSet.size()]); // Convert to String array, so we can read the values from the indices

        int[] classesNumbers = new int[uniqueLabels.length];
        for (int i = 0; i < classesNumbers.length; i++)
            classesNumbers[i] = i + 1; // Create incrementing list for each unique label starting at 1
        int[] classes = new int[imagesLabels.size()];
        for (int i = 0; i < imagesLabels.size(); i++) {
            String label = imagesLabels.get(i);
            for (int j = 0; j < uniqueLabels.length; j++) {
                if (label.equals(uniqueLabels[j])) {
                    classes[i] = classesNumbers[j]; // Insert corresponding number
                    break;
                }
            }
        }
        Mat vectorClasses = new Mat(classes.length, 1, CvType.CV_32SC1); // CV_32S == int
        vectorClasses.put(0, 0, classes); // Copy int array into a vector

        recognize = LBPHFaceRecognizer.create(3,8,8,8,200);
        recognize.train(imagesMatrix, vectorClasses);
        if(SaveImage())
            return true;

        return false;
    }
    public void cropedImages(Mat mat) {
        Rect rect_Crop=null;
        for(Rect face: faces.toArray()) {
            rect_Crop = new Rect(face.x, face.y, face.width, face.height);
        }
        Mat croped = new Mat(mat, rect_Crop);
        images.add(croped);
    }
    public boolean SaveImage() {
        File path = new File(Environment.getExternalStorageDirectory(), "TrainedData");
        path.mkdirs();
        String filename = "lbph_trained_data.xml";
        File file = new File(path, filename);
        recognize.save(file.toString());
        if(file.exists())
            return true;
        return false;
    }

    private BaseLoaderCallback callbackLoader = new BaseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            switch(status) {
                case BaseLoaderCallback.SUCCESS:
                    faces = new MatOfRect();

                    //reset
                    images = new ArrayList<Mat>();
                    imagesLabels = new ArrayList<String>();
                    local.putListMat("images", images);
                    local.putListString("imagesLabels", imagesLabels);

                    images = local.getListMat("images");
                    imagesLabels = local.getListString("imagesLabels");

                    break;
                default:
                    super.onManagerConnected(status);
                    break;
            }
        }
    };

    @Override
    protected void onResume() {
        super.onResume();
        if(OpenCVLoader.initDebug()) {
            Log.i("hmm", "System Library Loaded Successfully");
            callbackLoader.onManagerConnected(BaseLoaderCallback.SUCCESS);
        } else {
            Log.i("hmm", "Unable To Load System Library");
            OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION, this, callbackLoader);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        prev = findViewById(R.id.btprev);
        next = findViewById(R.id.btnext);
        mimage = findViewById(R.id.mimage);
       local = new Storage(this);
       imgs = new ArrayList();
       imgs.add(R.drawable.jonc);
       imgs.add(R.drawable.jonc2);
       imgs.add(R.drawable.randy1);
       imgs.add(R.drawable.randy2);
       imgs.add(R.drawable.imgone);
       imgs.add(R.drawable.imagetwo);
       mimage.setBackgroundResource(imgs.get(pos));
        prev.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(pos!=0){
                  pos--;
                  mimage.setBackgroundResource(imgs.get(pos));
                }
            }
        });
        next.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(pos<5){
                    pos++;
                    mimage.setBackgroundResource(imgs.get(pos));
                }
            }
        });
        Button train = (Button)findViewById(R.id.btn_train);
        train.setOnClickListener(new View.OnClickListener() {
            @RequiresApi(api = Build.VERSION_CODES.KITKAT)
            @Override
            public void onClick(View view) {
                rgba = new Mat();
                gray = new Mat();
                Mat mGrayTmp = new Mat();
                Mat mRgbaTmp = new Mat();
                classifier = FileUtils.loadXMLS(MainActivity.this);
                Bitmap icon = BitmapFactory.decodeResource(getResources(),
                        imgs.get(pos));
                Bitmap bmp32 = icon.copy(Bitmap.Config.ARGB_8888, true);
                Utils.bitmapToMat(bmp32, mGrayTmp);
                Utils.bitmapToMat(bmp32, mRgbaTmp);
                Imgproc.cvtColor(mGrayTmp, mGrayTmp, Imgproc.COLOR_BGR2GRAY);
                Imgproc.cvtColor(mRgbaTmp, mRgbaTmp, Imgproc.COLOR_BGRA2RGBA);
                /*Core.transpose(mGrayTmp, mGrayTmp); // Rotate image
                Core.flip(mGrayTmp, mGrayTmp, -1); // Flip along both*/
                gray = mGrayTmp;
                rgba = mRgbaTmp;
                Imgproc.resize(gray, gray, new Size(200,200.0f/ ((float)gray.width()/ (float)gray.height())));
                if(gray.total() == 0)
                    Toast.makeText(getApplicationContext(), "Can't Detect Faces", Toast.LENGTH_SHORT).show();
                classifier.detectMultiScale(gray,faces,1.1,3,0|CASCADE_SCALE_IMAGE, new Size(30,30));
                if(!faces.empty()) {
                    if(faces.toArray().length > 1)
                        Toast.makeText(getApplicationContext(), "Mutliple Faces Are not allowed", Toast.LENGTH_SHORT).show();
                    else {
                        if(gray.total() == 0) {
                            Log.i("hmm", "Empty gray image");
                            return;
                        }
                        cropedImages(gray);
                        imagesLabels.add("Baby");
                        Toast.makeText(getApplicationContext(), "Picture Set As Baby", Toast.LENGTH_LONG).show();
                        if (images != null && imagesLabels != null) {
                            local.putListMat("images", images);
                            local.putListString("imagesLabels", imagesLabels);
                            Log.i("hmm", "Images have been saved");
                            if(trainfaces()) {
                                images.clear();
                                imagesLabels.clear();
                            }
                        }
                    }
                }else {
                   /* Bitmap bmp = null;
                    Mat tmp = new Mat(250, 250, CvType.CV_8U, new Scalar(4));
                    try {
                        //Imgproc.cvtColor(seedsImage, tmp, Imgproc.COLOR_RGB2BGRA);
                        Imgproc.cvtColor(gray, tmp, Imgproc.COLOR_GRAY2RGBA, 4);
                        bmp = Bitmap.createBitmap(tmp.cols(), tmp.rows(), Bitmap.Config.ARGB_8888);
                        Utils.matToBitmap(tmp, bmp);
                    } catch (CvException e) {
                        Log.d("Exception", e.getMessage());
                    }*/
                    /*    mimage.setImageBitmap(bmp);*/
                    Toast.makeText(getApplicationContext(), "Unknown Face", Toast.LENGTH_SHORT).show();
                }
            }
        });
        Button recognize = (Button)findViewById(R.id.btn_recognize);
        recognize.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(loadData())
                    Log.i("hmm", "Trained data loaded successfully");
                rgba = new Mat();
                gray = new Mat();
                faces = new MatOfRect();
                Mat mGrayTmp = new Mat();
                Mat mRgbaTmp = new Mat();
                classifier = FileUtils.loadXMLS(MainActivity.this);
                Bitmap icon = BitmapFactory.decodeResource(getResources(),
                        imgs.get(pos));
                Bitmap bmp32 = icon.copy(Bitmap.Config.ARGB_8888, true);
                Utils.bitmapToMat(bmp32, mGrayTmp);
                Utils.bitmapToMat(bmp32, mRgbaTmp);
                Imgproc.cvtColor(mGrayTmp, mGrayTmp, Imgproc.COLOR_BGR2GRAY);
                Imgproc.cvtColor(mRgbaTmp, mRgbaTmp, Imgproc.COLOR_BGRA2RGBA);
                /*Core.transpose(mGrayTmp, mGrayTmp); // Rotate image
                Core.flip(mGrayTmp, mGrayTmp, -1); // Flip along both*/
                gray = mGrayTmp;
                rgba = mRgbaTmp;
                Imgproc.resize(gray, gray, new Size(200,200.0f/ ((float)gray.width()/ (float)gray.height())));
                if(gray.total() == 0)
                    Toast.makeText(getApplicationContext(), "Can't Detect Faces", Toast.LENGTH_SHORT).show();
                classifier.detectMultiScale(gray,faces,1.1,3,0|CASCADE_SCALE_IMAGE, new Size(30,30));
                if(!faces.empty()) {
                    if(faces.toArray().length > 1)
                        Toast.makeText(getApplicationContext(), "Mutliple Faces Are not allowed", Toast.LENGTH_SHORT).show();
                    else {
                        if(gray.total() == 0) {
                            Log.i("hmm", "Empty gray image");
                            return;
                        }
                        recognizeImage(gray);
                    }
                }else {
                    Toast.makeText(getApplicationContext(), "Unknown Face", Toast.LENGTH_SHORT).show();
                }
            }
        });


    }
    private void recognizeImage(Mat mat) {
        Rect rect_Crop=null;
        for(Rect face: faces.toArray()) {
            rect_Crop = new Rect(face.x, face.y, face.width, face.height);
        }
        Mat croped = new Mat(mat, rect_Crop);
        recognize.predict(croped, label, predict);
        int indice = (int)predict[0];
        Log.i("hmmcheck:",String.valueOf(label[0])+" : "+String.valueOf(indice));
        if(label[0] != -1 && indice < 125)
            Toast.makeText(getApplicationContext(), "Welcome "+uniqueLabels[label[0]-1]+"", Toast.LENGTH_SHORT).show();
        else
            Toast.makeText(getApplicationContext(), "You're not the right person", Toast.LENGTH_SHORT).show();
    }
    private boolean loadData() {
        String filename = FileUtils.loadTrained();
        if(filename.isEmpty())
            return false;
        else
        {
            recognize.read(filename);
            return true;
        }
    }
}

My File Utils Class:

   public class FileUtils {
        private static String TAG = FileUtils.class.getSimpleName();
        private static boolean loadFile(Context context, String cascadeName) {
            InputStream inp = null;
            OutputStream out = null;
            boolean completed = false;
            try {
                inp = context.getResources().getAssets().open(cascadeName);
                File outFile = new File(context.getCacheDir(), cascadeName);
                out = new FileOutputStream(outFile);

                byte[] buffer = new byte[4096];
                int bytesread;
                while((bytesread = inp.read(buffer)) != -1) {
                    out.write(buffer, 0, bytesread);
                }

                completed = true;
                inp.close();
                out.flush();
                out.close();
            } catch (IOException e) {
                Log.i(TAG, "Unable to load cascade file" + e);
            }
            return completed;
        }
        public static CascadeClassifier loadXMLS(Activity activity) {


            InputStream is = activity.getResources().openRawResource(R.raw.lbpcascade_frontalface);
            File cascadeDir = activity.getDir("cascade", Context.MODE_PRIVATE);
            File mCascadeFile = new File(cascadeDir, "lbpcascade_frontalface_improved.xml");
            FileOutputStream os = null;
            try {
                os = new FileOutputStream(mCascadeFile);
                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = is.read(buffer)) != -1) {
                    os.write(buffer, 0, bytesRead);
                }
                is.close();
                os.close();

            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }


            return new CascadeClassifier(mCascadeFile.getAbsolutePath());
        }
        public static String loadTrained() {
            File file = new File(Environment.getExternalStorageDirectory(), "TrainedData/lbph_trained_data.xml");

            return file.toString();
        }
    }

Ce sont les images que j'essaie de comparer ici, le visage d'une personne est toujours le même en reconnaissance, il ne correspond pas! Image 1 Image 2

R.Coder
la source
Lorsque j'ai construit mon affectation de dernière année pour le système de présence automatique, j'ai utilisé 8 à 10 images de moi avec des poses et des conditions d'éclairage légèrement différentes pour former le classificateur.
ZdaR
Vous pouvez retourner votre tapis d'image d'entraînement horizontalement pour répondre à cette exigence.
nfl-x
@ nfl-x flipping images ne résoudra pas le problème de précision nous avons besoin de quelque chose de mieux une réponse récente sur tensorflow semble correcte mais il n'y a pas suffisamment d'informations ou de tutoriels disponibles sur son implémentation pour Android, donc notre meilleure supposition est de continuer à voter pour ce post de sorte qu'un expert peut intervenir et fournir une solution appropriée pour Android
M. Patel

Réponses:

5

Mise à jour

Selon le nouveau montage de la question, vous avez besoin d'un moyen d'identifier à la volée de nouvelles personnes dont les photos pourraient ne pas être disponibles pendant la phase de formation du modèle. Ces tâches sont appelées apprentissage en quelques coups . Cela est similaire aux exigences des services de renseignement / de police pour trouver leurs cibles à l'aide de séquences de caméras de vidéosurveillance. Comme il n'y a généralement pas assez d'images d'une cible spécifique, pendant la formation, ils utilisent des modèles tels que FaceNet . Je suggère vraiment de lire le document, cependant, j'explique quelques-uns de ses points forts ici:

  • Généralement, la dernière couche d'un classificateur est un vecteur * 1 avec n-1 des éléments presque égaux à zéro et un proche de 1. L'élément proche de 1 détermine la prédiction du classificateur sur l'étiquette de l'entrée. Architecture CNN typique
  • Les auteurs ont compris que s'ils formaient un réseau de classificateurs avec une fonction de perte spécifique sur un énorme ensemble de données de faces, vous pouvez utiliser la sortie de la couche semi-finale comme représentation de n'importe quelle face, qu'elle soit ou non dans l'ensemble d'apprentissage, les auteurs appellent ce vecteur Face Embedding .
  • Le résultat précédent signifie qu'avec un modèle FaceNet très bien formé, vous pouvez résumer n'importe quel visage en un vecteur. L'attribut très intéressant de cette approche est que les vecteurs du visage d'une personne spécifique sous différents angles / positions / états sont proches dans l'espace euclidien (cette propriété est renforcée par la fonction de perte que les auteurs ont choisie).entrez la description de l'image ici
  • En résumé, vous avez un modèle qui obtient des visages en entrée et renvoie des vecteurs. Les vecteurs proches les uns des autres sont très susceptibles d'appartenir à la même personne (pour vérifier que vous pouvez utiliser KNN ou simplement une distance euclidienne simple).

Une implémentation de FaceNet peut être trouvée ici . Je vous suggère d'essayer de l'exécuter sur votre ordinateur pour savoir à quoi vous avez réellement affaire. Après cela, il est préférable de procéder comme suit:

  1. Transformez le modèle FaceNet mentionné dans le référentiel en sa version tflite ( ce blog peut aider)
  2. Pour chaque photo soumise par l'utilisateur, utilisez l'API Face pour extraire le (s) visage (s)
  3. Utilisez le modèle réduit dans votre application pour obtenir les incorporations de visage du visage extrait.
  4. Traitez toutes les images dans la galerie de l'utilisateur, en obtenant les vecteurs pour les visages dans les photos.
  5. Comparez ensuite chaque vecteur trouvé à l'étape 4 avec chaque vecteur trouvé à l'étape 3 pour obtenir les correspondances.

Réponse originale

Vous êtes tombé sur l'un des défis les plus courants de l'apprentissage automatique: le sur-ajustement. La détection et la reconnaissance des visages sont un vaste domaine de recherche en soi et presque tous les modèles raisonnablement précis utilisent une sorte d'apprentissage en profondeur. Notez que même détecter un visage avec précision n'est pas aussi facile qu'il y paraît, cependant, comme vous le faites sur Android, vous pouvez utiliser l' API Face pour cette tâche. (Autres techniques plus avancées telles que MTCNN sont trop lentes / difficiles à déployer sur un combiné). Il a été démontré que le simple fait d'alimenter le modèle avec une photo de visage avec beaucoup de bruit de fond ou plusieurs personnes à l'intérieur ne fonctionne pas. Donc, vous ne pouvez vraiment pas sauter cette étape.

Après avoir obtenu un joli visage taillé des cibles candidates de l'arrière-plan, vous devez surmonter le défi de reconnaître les visages détectés. Encore une fois, tous les modèles compétents, à ma connaissance, utilisent une sorte de réseau neuronal d'apprentissage profond / convolutionnel. Les utiliser sur un téléphone mobile est un défi, mais grâce à Tensorflow Lite, vous pouvez les réduire et les exécuter dans votre application. Un projet sur la reconnaissance faciale sur les téléphones Android sur lequel j'avais travaillé est ici que vous pouvez vérifier. Gardez à l'esprit que tout bon modèle doit être formé sur de nombreuses instances de données étiquetées, mais il existe une pléthore de modèles déjà formés sur de grands ensembles de données de visages ou d'autres tâches de reconnaissance d'image, pour les ajuster et utiliser leurs connaissances existantes, nous pouvons utilisertransfert d'apprentissage , pour un démarrage rapide sur la détection d'objets et transfert d'apprentissage qui est étroitement lié à votre cas, consultez cet article de blog.

Dans l'ensemble, vous devez obtenir de nombreuses instances des visages que vous souhaitez détecter ainsi que de nombreuses photos de visages de personnes qui ne vous intéressent pas, puis vous devez former un modèle basé sur les ressources susmentionnées, puis vous devez utilisez TensorFlow lite pour réduire sa taille et l'intégrer dans votre application. Ensuite, pour chaque image, vous appelez Android Face API et introduisez (le visage probablement détecté) dans le modèle et identifiez la personne.

En fonction de votre niveau de tolérance au retard et du nombre de tailles de jeu d'entraînement et de cibles, vous pouvez obtenir différents résultats.Cependant, une précision de% 90 + est facilement réalisable si vous n'avez que quelques personnes cibles.

Farzad Vertigo
la source
Je ne veux pas utiliser la connexion réseau dans mon application, donc la vision de Google Cloud est hors de question, mais tensor flow lite semble être assez intéressant, est-ce gratuit? et si vous pouvez en fournir un exemple, je l'apprécierai! Merci
R.Coder
Excellente réponse au fait!
R.Coder
C'est gratuit. Vérifiez ceci pour un exemple de travail. Nous avons pu identifier les visages de 225 personnes sans utiliser de connexion réseau avec une très grande précision, bien qu'il y ait eu quelques problèmes du côté de l'expérience utilisateur. Mais cela devrait être un bon coup d'envoi.
Farzad Vertigo
D'accord, je vais essayer
R.Coder
1
Ça a marché!!!! J'ai finalement extrait ce modèle de filet net tflite et obtenu une précision supérieure à 80% sur une seule image entraînée. mais la complexité du temps est vraiment très énorme !!, Pour comparer deux images, il faut au minimum 5 à 6 secondes une idée sur la façon de réduire cela?
R.Coder
2

Si je comprends bien, vous entraînez le classificateur avec une seule image. Dans ce cas, cette image spécifique est tout ce que le classifieur pourra jamais reconnaître. Vous auriez besoin d'un ensemble de photos d'entraînement sensiblement plus grand montrant la même personne, quelque chose comme 5 ou 10 images différentes au moins.

Florian Echtler
la source
Avez-vous un exemple sur la façon de procéder?
R.Coder
Oui, je fais la reconnaissance faciale sur une seule image statique
R.Coder
Voir ici pour un exemple d'utilisation train(): docs.opencv.org/3.4/dd/d65/…
Florian Echtler
Cette réponse n'aide pas si vous pouvez fournir un exemple codé concernant Android, ce serait mieux!
R.Coder
0

1) Modifiez la valeur de seuil lors de l'initialisation de LBPHrecognizer en -> LBPHFaceRecognizer (1, 8, 8, 8, 100)

2) entraînez chaque visage avec au moins 2-3 images, car ces dispositifs de reconnaissance fonctionnent principalement sur la comparaison

3) Définissez le seuil de précision lors de la reconnaissance. Faites quelque chose comme ça:

//predicting result
// LoadData is a static class that contains trained recognizer
// _result is the gray frame image captured by the camera
LBPHFaceRecognizer.PredictionResult ER = LoadData.recog.Predict(_result);
int temp_result = ER.Label;

imageBox1.SizeMode = PictureBoxSizeMode.StretchImage;
imageBox1.Image = _result.Mat;

//Displaying predicted result on screen
// LBPH returns -1 if face is recognized
if ((temp_result != -1) && (ER.Distance < 55)){  
     //I get best accuracy at 55, you should try different values to determine best results
     // Do something with detected image
}
Riz
la source
Pouvez-vous modifier mon code actuel et fournir un exemple de travail pour le faire en java?
R.Coder