Open CV Gesichtserkennung nicht korrekt

13

In meiner App versuche ich, mithilfe von Open CV eine Gesichtserkennung für ein bestimmtes Bild durchzuführen. Hier trainiere ich zuerst ein Bild und nach dem Training dieses Bildes, wenn ich die Gesichtserkennung für dieses Bild durchführe, erkennt es das trainierte Gesicht erfolgreich. Wenn ich mich jedoch einem anderen Bild derselben Person zuwende, funktioniert die Erkennung nicht. Es funktioniert nur mit dem trainierten Bild. Meine Frage ist also, wie ich es korrigieren kann.

Update: Ich möchte, dass der Benutzer das Bild einer Person aus dem Speicher auswählt. Nach dem Training des ausgewählten Bildes möchte ich alle Bilder aus dem Speicher abrufen, die dem Gesicht meines trainierten Bildes entsprechen

Hier ist meine Aktivitätsklasse:

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;
        }
    }
}

Meine Datei Utils Klasse:

   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();
        }
    }

Dies sind die Bilder, die ich hier zu vergleichen versuche. Das Gesicht einer Person ist immer noch das gleiche, aber es ist nicht passend! Bild 1 Bild 2

R.Coder
quelle
Als ich meine letzte Jahresaufgabe für das automatische Anwesenheitssystem erstellte, verwendete ich 8-10 Bilder von mir mit leicht unterschiedlichen Posen und Lichtverhältnissen, um den Klassifikator zu trainieren.
ZdaR
Sie können Ihre Trainingsbildmatte horizontal drehen, um diese Anforderung zu erfüllen.
nfl-x
@ nfl-x Das Umblättern von Bildern löst das Problem der Genauigkeit nicht. Wir brauchen eine bessere Antwort auf Tensorflow. Die aktuelle Antwort auf Tensorflow scheint in Ordnung zu sein, aber es sind nicht genügend Informationen oder Tutorials zur Implementierung für Android verfügbar. Wir raten daher, diesen Beitrag weiter abzustimmen so dass ein Experte eingreifen und eine geeignete Lösung für Android bieten kann
Mr. Patel

Antworten:

5

Aktualisieren

Entsprechend der neuen Bearbeitung in der Frage benötigen Sie eine Möglichkeit, neue Personen im laufenden Betrieb zu identifizieren, deren Fotos während der Trainingsphase des Modells möglicherweise nicht verfügbar waren. Diese Aufgaben werden als Wenig-Schuss-Lernen bezeichnet . Dies ähnelt den Anforderungen der Geheimdienste / Polizeibehörden, ihre Ziele mithilfe von CCTV-Kameraaufnahmen zu finden. Da normalerweise nicht genügend Bilder eines bestimmten Ziels vorhanden sind, werden während des Trainings Modelle wie FaceNet verwendet . Ich empfehle wirklich, die Zeitung zu lesen, erkläre hier jedoch einige ihrer Höhepunkte:

  • Im Allgemeinen ist die letzte Schicht eines Klassifikators ein * 1-Vektor, wobei n-1 der Elemente fast gleich Null und eins nahe 1 ist. Das Element nahe 1 bestimmt die Vorhersage des Klassifikators über die Bezeichnung der Eingabe. Typische CNN-Architektur
  • Die Autoren haben herausgefunden, dass Sie, wenn sie ein Klassifizierernetzwerk mit einer bestimmten Verlustfunktion auf einem riesigen Datensatz von Gesichtern trainieren, die Ausgabe der Halbfinalschicht als Darstellung jedes Gesichts verwenden können, unabhängig davon, ob es sich im Trainingssatz befindet oder nicht. Die Autoren nennen diesen Vektor Face Embedding .
  • Das vorherige Ergebnis bedeutet, dass Sie mit einem sehr gut trainierten FaceNet-Modell jedes Gesicht in einem Vektor zusammenfassen können. Das sehr interessante Merkmal dieses Ansatzes ist, dass die Vektoren des Gesichts einer bestimmten Person in verschiedenen Winkeln / Positionen / Zuständen im euklidischen Raum nahe beieinander liegen (diese Eigenschaft wird durch die von den Autoren gewählte Verlustfunktion erzwungen).Geben Sie hier die Bildbeschreibung ein
  • Zusammenfassend haben Sie ein Modell, das Gesichter als Eingabe erhält und Vektoren zurückgibt. Es ist sehr wahrscheinlich, dass die Vektoren nahe beieinander derselben Person gehören (zur Überprüfung, ob Sie KNN oder nur einen einfachen euklidischen Abstand verwenden können).

Eine Implementierung von FaceNet finden Sie hier . Ich schlage vor, Sie versuchen, es auf Ihrem Computer auszuführen, um zu erfahren, womit Sie sich tatsächlich befassen. Danach ist es möglicherweise am besten, Folgendes zu tun:

  1. Transformieren Sie das im Repository erwähnte FaceNet-Modell in seine tflite-Version ( dies Blogpost könnte helfen).
  2. Verwenden Sie für jedes vom Benutzer eingereichte Foto die Gesichts-API, um die Gesichter zu extrahieren.
  3. Verwenden Sie das verkleinerte Modell in Ihrer App, um die Gesichtseinbettungen des extrahierten Gesichts zu erhalten.
  4. Verarbeiten Sie alle Bilder in der Galerie des Benutzers und erhalten Sie die Vektoren für die Gesichter auf den Fotos.
  5. Vergleichen Sie dann jeden in Schritt 4 gefundenen Vektor mit jedem in Schritt 3 gefundenen Vektor, um die Übereinstimmungen zu erhalten.

Ursprüngliche Antwort

Sie sind auf eine der häufigsten Herausforderungen des maschinellen Lernens gestoßen: Überanpassung. Die Gesichtserkennung und -erkennung ist ein riesiges Forschungsgebiet für sich und fast alle einigermaßen genauen Modelle verwenden eine Art tiefes Lernen. Beachten Sie, dass selbst das genaue Erkennen eines Gesichts nicht so einfach ist, wie es scheint. Wenn Sie es jedoch auf Android ausführen, können Sie für diese Aufgabe die Gesichts-API verwenden . (Andere fortgeschrittenere Techniken wie MTCNN sind zu langsam / schwierig auf einem Mobilteil bereitzustellen .) Es hat sich gezeigt, dass es nicht funktioniert, das Modell nur mit einem Gesichtsfoto mit vielen Hintergrundgeräuschen oder mehreren Personen zu füttern. Sie können diesen Schritt also wirklich nicht überspringen.

Nachdem Sie ein schönes, zugeschnittenes Gesicht der Kandidatenziele aus dem Hintergrund erhalten haben, müssen Sie die Herausforderung bewältigen, die erkannten Gesichter zu erkennen. Wiederum verwenden alle kompetenten Modelle nach meinem besten Wissen eine Art Deep Learning / Convolutional Neural Networks. Die Verwendung auf einem Mobiltelefon ist eine Herausforderung, aber dank Tensorflow Lite können Sie sie minimieren und in Ihrer App ausführen. Ein Projekt zur Gesichtserkennung auf Android-Handys, an dem ich gearbeitet habe, ist das Transferlernen . Für einen schnellen Einstieg in die Objekterkennung und das Transferlernen, das eng mit Ihrem Fall zusammenhängt, lesen Sie diesen Blog-Beitrag. hier , das Sie überprüfen können. Denken Sie daran, dass jedes gute Modell auf zahlreiche Instanzen gekennzeichneter Daten trainiert werden sollte. Es gibt jedoch eine Vielzahl von Modellen, die bereits auf großen Datenmengen von Gesichtern oder anderen Bilderkennungsaufgaben trainiert wurden, um sie zu optimieren und ihr vorhandenes Wissen zu nutzen, das wir einsetzen können

Insgesamt müssen Sie zahlreiche Instanzen der Gesichter erhalten, die Sie erkennen möchten, sowie zahlreiche Gesichtsbilder von Personen, die Sie nicht interessieren. Dann müssen Sie ein Modell trainieren, das auf den oben genannten Ressourcen basiert, und dann müssen Sie Verwenden Sie TensorFlow lite, um die Größe zu verringern und in Ihre App einzubetten. Für jeden Frame rufen Sie dann die Android Face API auf und geben (das wahrscheinlich erkannte Gesicht) in das Modell ein und identifizieren die Person.

Abhängig von Ihrer Toleranz für Verzögerungen und der Anzahl der Trainingssätze und der Anzahl der Ziele können Sie verschiedene Ergebnisse erzielen. Eine Genauigkeit von% 90 + ist jedoch leicht zu erreichen, wenn Sie nur wenige Zielpersonen haben.

Farzad Schwindel
quelle
Ich möchte keine Netzwerkverbindung in meiner App verwenden, daher kommt Google Cloud Vision nicht in Frage, aber Tensor Flow Lite scheint ziemlich interessant zu sein, ist es kostenlos? und wenn Sie ein funktionierendes Beispiel dafür liefern können, werde ich es schätzen! Vielen Dank
R.Coder
Tolle Antwort übrigens!
R.Coder
Es ist kostenlos. Überprüfen Sie dies für ein funktionierendes Beispiel. Wir konnten Gesichter von 225 Personen ohne Netzwerkverbindung mit sehr hoher Genauigkeit identifizieren, obwohl es einige Probleme auf der Seite der Benutzererfahrung gab. Aber das sollte ein guter Auftakt sein.
Farzad Vertigo
Okay, ich werde es versuchen
R.Coder
1
Es funktionierte!!!! Ich extrahierte schließlich dieses Gesichtsnetzmodell Tflite und erreichte eine Genauigkeit von über 80% auf einem einzelnen trainierten Bild. Aber die Zeitkomplexität ist wirklich sehr, sehr groß! Für den Vergleich zweier Bilder dauert es mindestens 5 bis 6 Sekunden. Wie kann man das reduzieren?
R.Coder
2

Wenn ich das richtig verstehe, trainierst du den Klassifikator mit einem einzigen Bild. In diesem Fall ist dieses eine spezifische Bild alles, was der Klassifizierer jemals erkennen kann. Sie benötigen einen deutlich größeren Trainingssatz mit Bildern, die dieselbe Person zeigen, mindestens 5 oder 10 verschiedene Bilder.

Florian Echtler
quelle
Haben Sie ein Beispiel dafür?
R.Coder
Ja, ich mache Gesichtserkennung auf einem einzelnen statischen Bild
R.Coder
Hier finden Sie ein Beispiel für die Verwendung train(): docs.opencv.org/3.4/dd/d65/…
Florian Echtler
Diese Antwort hilft nicht, wenn Sie ein codiertes Beispiel in Bezug auf Android liefern können, wäre es besser!
R.Coder
0

1) Ändern Sie den Schwellenwert beim Initialisieren von LBPHrecognizer auf -> LBPHFaceRecognizer (1, 8, 8, 8, 100).

2) Trainiere jedes Gesicht mit mindestens 2-3 Bildern, da diese Erkenner hauptsächlich im Vergleich arbeiten

3) Stellen Sie die Genauigkeitsschwelle beim Erkennen ein. Mach so etwas:

//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
quelle
Können Sie meinen aktuellen Code bearbeiten und ein funktionierendes Beispiel dafür in Java bereitstellen?
R.Coder