Apri Riconoscimento facciale CV non accurato


13

Nella mia app sto provando a riconoscere il volto su un'immagine specifica usando Open CV, qui prima sto allenando un'immagine e poi dopo aver allenato quell'immagine se eseguo il riconoscimento facciale su quell'immagine, riconosce quel volto allenato. Tuttavia, quando passo a un'altra immagine della stessa persona, il riconoscimento non funziona. Funziona solo sull'immagine allenata, quindi la mia domanda è: come posso correggerla?

Aggiornamento: Quello che voglio fare è che l'utente dovrebbe selezionare l'immagine di una persona dalla memoria e quindi dopo aver allenato quell'immagine selezionata voglio recuperare tutte le immagini dalla memoria che corrispondono al volto della mia immagine allenata

Ecco la mia classe di attività:

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

Classe File personali:

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

Queste sono le immagini che sto cercando di confrontare qui il volto della persona è sempre lo stesso nel riconoscimento che non corrisponde! Immagine 1 Immagine 2


Quando ho realizzato il mio ultimo anno di assegnazione per il sistema di partecipazione automatica, ho usato 8-10 immagini di me stesso con pose e condizioni di illuminazione leggermente diverse per addestrare il classificatore.
ZdaR,

È possibile capovolgere orizzontalmente la stuoia dell'immagine di allenamento per gestire tale requisito.
nfl-x

@ nfl-x lanciando immagini non risolverà il problema dell'accuratezza abbiamo bisogno di qualcosa di meglio La recente risposta su tensorflow sembra a posto ma non ci sono sufficienti informazioni o tutorial sulla sua implementazione per Android, quindi la nostra ipotesi migliore è di continuare a votare questo post tale che un esperto possa intervenire e fornire una soluzione adeguata per Android
Mr. Patel,

Risposte:


5

Aggiornare

In base alla nuova modifica nella domanda, è necessario un modo per identificare al volo nuove persone le cui foto potrebbero non essere state disponibili durante la fase di addestramento del modello. Questi compiti si chiamano pochi shot learning . Questo è simile ai requisiti delle agenzie di intelligence / polizia per trovare i loro obiettivi utilizzando le riprese della telecamera CCTV. Come al solito non ci sono abbastanza immagini di un bersaglio specifico, durante l'allenamento, usano modelli come FaceNet . Consiglio davvero di leggere il documento, tuttavia, spiego alcuni dei suoi punti salienti qui:

  • Generalmente, l'ultimo strato di un classificatore è un vettore * 1 con n-1 degli elementi quasi uguali a zero e uno vicino a 1. L'elemento vicino a 1 determina la previsione del classificatore sull'etichetta dell'input. Architettura tipica della CNN
  • Gli autori hanno scoperto che se si allena una rete di classificazione con una specifica funzione di perdita su un enorme set di dati di facce, è possibile utilizzare l'output del livello semifinale come rappresentazione di qualsiasi faccia, indipendentemente dal fatto che si trovi nel set di addestramento o meno, gli autori chiamano questo vettore Face Embedding .
  • Il risultato precedente significa che con un modello FaceNet molto ben addestrato, puoi riassumere qualsiasi faccia in un vettore. L'attributo molto interessante di questo approccio è che i vettori del volto di una persona specifica in angoli / posizioni / stati diversi sono vicini nello spazio euclideo (questa proprietà è imposta dalla funzione di perdita scelta dagli autori).inserisci qui la descrizione dell'immagine
  • In breve, hai un modello che ottiene facce come input e restituisce vettori. È molto probabile che i vettori vicini tra loro appartengano alla stessa persona (per verificare che sia possibile utilizzare KNN o semplicemente una distanza euclidea).

Una implementazione di FaceNet è disponibile qui . Ti suggerisco di provare a eseguirlo sul tuo computer per conoscere con cosa hai effettivamente a che fare. Successivamente, potrebbe essere meglio eseguire le seguenti operazioni:

  1. Trasforma il modello FaceNet menzionato nel repository nella sua versione tflite ( questo post sul blog potrebbe aiutare)
  2. Per ogni foto inviata dall'utente, utilizzare Face API per estrarre i volti
  3. Usa il modello minimizzato nella tua app per ottenere le decorazioni facciali della faccia estratta.
  4. Elabora tutte le immagini nella galleria dell'utente, ottenendo i vettori per i volti nelle foto.
  5. Quindi confrontare ogni vettore trovato nel passaggio 4 con ogni vettore trovato nel passaggio 3 per ottenere le corrispondenze.

Risposta originale

Ti sei imbattuto in una delle sfide più frequenti dell'apprendimento automatico: il sovradimensionamento. Il rilevamento e il riconoscimento del volto sono di per sé una vasta area di ricerca e quasi tutti i modelli ragionevolmente precisi utilizzano un qualche tipo di apprendimento profondo. Nota che anche rilevare un viso con precisione non è così semplice come sembra, tuttavia, come lo stai facendo su Android, puoi utilizzare Face API per questa attività. (Altre tecniche più avanzate come MTCNN sono troppo lente / difficili da implementare su un portatile). È stato dimostrato che alimentare la modella con una foto del viso con molto rumore di fondo o più persone all'interno non funziona. Quindi, non puoi davvero saltare questo passaggio.

Dopo aver ottenuto una bella faccia ritagliata degli obiettivi candidati dallo sfondo, è necessario superare la sfida di riconoscere i volti rilevati. Ancora una volta, tutti i modelli competenti al meglio delle mie conoscenze, stanno usando una sorta di apprendimento profondo / reti neurali convoluzionali. Usarli su un telefono cellulare è una sfida, ma grazie a Tensorflow Lite puoi minimizzarli ed eseguirli all'interno della tua app. Un progetto sul riconoscimento facciale su telefoni Android su cui avevo lavorato è qui che puoi controllare. Tieni presente che qualsiasi buon modello dovrebbe essere addestrato su numerose istanze di dati etichettati, tuttavia ci sono una pletora di modelli già addestrati su grandi set di dati di volti o altre attività di riconoscimento delle immagini, per modificarli e utilizzare le loro conoscenze esistenti, possiamo impiegaretrasferimento di apprendimento , per un rapido avvio sul rilevamento di oggetti e trasferimento di apprendimento strettamente correlato al proprio caso, consultare questo post del blog.

Nel complesso, devi ottenere numerose istanze dei volti che desideri rilevare oltre a numerose foto di volti di persone che non ti interessano, quindi devi formare un modello basato sulle risorse sopra menzionate e quindi devi usa TensorFlow lite per ridurne le dimensioni e incorporarlo nella tua app. Quindi, per ogni fotogramma, chiami Android Face API e inserisci (il volto probabilmente rilevato) nel modello e identifichi la persona.

A seconda del livello di tolleranza per il ritardo e del numero di dimensioni del set di allenamento e del numero di obiettivi, è possibile ottenere vari risultati, tuttavia è possibile raggiungere con precisione 90% + se si hanno solo poche persone target.


Non voglio utilizzare la connessione di rete nella mia app, quindi Google Cloud Vision è fuori discussione, ma Lite Flow Flow sembra essere abbastanza interessante, è gratuito? e se puoi fornirne un esempio funzionante, lo apprezzerò! Grazie
R.Coder

Ottima risposta a proposito!
R.Coder

È gratis. Controlla questo per un esempio funzionante. Siamo stati in grado di identificare i volti di 225 persone senza utilizzare la connessione di rete con una precisione molto elevata, anche se c'erano alcuni problemi nel lato dell'esperienza dell'utente. Ma dovrebbe essere un buon calcio d'inizio.
Farzad Vertigo,

Ok, ci proverò
R.Coder

1
Ha funzionato!!!! alla fine ho estratto quel modello di faccia tflite e ho ottenuto una precisione superiore all'80% su una singola immagine allenata. ma la complessità temporale è davvero enorme !!, Per confrontare due immagini ci vogliono almeno 5-6 secondi qualche idea su come ridurlo?
R.Coder

2

Se ho capito bene, stai allenando il classificatore con una singola immagine. In tal caso, questa immagine specifica è tutto ciò che il classificatore sarà in grado di riconoscere. Avresti bisogno di un set di immagini notevolmente più grande che mostri la stessa persona, almeno 5 o 10 immagini diverse.


Hai qualche esempio su come farlo?
R.Coder

Sì, sto facendo il riconoscimento facciale su una singola immagine statica
R.Coder

Vedi qui per esempio come usare train(): docs.opencv.org/3.4/dd/d65/…
Florian Echtler

Questa risposta non aiuta se è possibile fornire alcuni esempi codificati relativi ad Android sarebbe meglio!
R.Coder

0

1) Modifica il valore di soglia durante l'inizializzazione di LBPHrecognizer su -> LBPHFaceRecognizer (1, 8, 8, 8, 100)

2) allena ogni faccia con almeno 2-3 immagini poiché questi riconoscitori funzionano principalmente sul confronto

3) Impostare la soglia di precisione durante il riconoscimento. Fai qualcosa del genere:

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

Bene, puoi modificare il mio codice attuale e fornire un esempio funzionante per farlo in Java?
R.Coder
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.