El reconocimiento facial de CV abierto no es preciso


13

En mi aplicación, estoy tratando de hacer reconocimiento facial en una imagen específica usando Open CV, aquí primero estoy entrenando una imagen y luego después de entrenar esa imagen si ejecuto el reconocimiento facial en esa imagen, reconoce con éxito esa cara entrenada. Sin embargo, cuando paso a otra imagen de la misma persona, el reconocimiento no funciona. Simplemente funciona en la imagen entrenada, así que mi pregunta es ¿cómo lo rectifico?

Actualización: lo que quiero hacer es que el usuario seleccione la imagen de una persona del almacenamiento y luego, después de entrenar esa imagen seleccionada, quiero obtener todas las imágenes del almacenamiento que coincidan con la imagen de mi imagen entrenada

Aquí está mi clase de actividad:

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

Mi clase de utilidades de archivo:

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

¡Estas son las imágenes que estoy tratando de comparar aquí, la cara de la persona es la misma aún en reconocimiento de que no coincide! Imagen 1 Imagen 2


Cuando construí mi asignación de último año para el Sistema de Asistencia Automática, usé 8-10 imágenes de mí mismo con poses y condiciones de iluminación ligeramente diferentes para entrenar al clasificador.
ZdaR

Puedes voltear horizontalmente tu tapete de imagen de entrenamiento para manejar ese requisito.
nfl-x

@ nfl-x voltear imágenes no resolverá el problema de precisión, necesitamos algo mejor. La respuesta reciente sobre tensorflow parece estar bien, pero no hay suficiente información o tutoriales disponibles sobre su implementación para Android, por lo que nuestra mejor suposición es seguir votando esta publicación tal que un experto pueda intervenir y proporcionar una solución adecuada para Android
Sr. Patel

Respuestas:


5

Actualizar

De acuerdo con la nueva edición en la pregunta, necesita una forma de identificar nuevas personas sobre la marcha cuyas fotos podrían no haber estado disponibles durante la fase de entrenamiento del modelo. Estas tareas se denominan pocos disparos de aprendizaje . Esto es similar a los requisitos de las agencias de inteligencia / policía para encontrar sus objetivos utilizando imágenes de cámaras de CCTV. Como generalmente no hay suficientes imágenes de un objetivo específico, durante el entrenamiento, utilizan modelos como FaceNet . Realmente sugiero leer el documento, sin embargo, explico algunos de sus aspectos más destacados aquí:

  • En general, la última capa de un clasificador es un vector * 1 con n-1 de los elementos casi igual a cero y uno cercano a 1. El elemento cercano a 1 determina la predicción del clasificador sobre la etiqueta de la entrada. Arquitectura típica de CNN
  • Los autores descubrieron que si entrenan una red de clasificación con una función de pérdida específica en un gran conjunto de datos de caras, puede usar la salida de la capa semifinal como una representación de cualquier cara, independientemente de si está en el conjunto de entrenamiento o no, los autores llaman a este vector Face Embedded .
  • El resultado anterior significa que con un modelo FaceNet muy bien entrenado, puede resumir cualquier cara en un vector. El atributo muy interesante de este enfoque es que los vectores de la cara de una persona específica en diferentes ángulos / posiciones / estados son próximos en el espacio euclidiano (esta propiedad se ve reforzada por la función de pérdida que los autores eligieron).ingrese la descripción de la imagen aquí
  • En resumen, tiene un modelo que obtiene caras como entrada y devuelve vectores. Es muy probable que los vectores cercanos entre sí pertenezcan a la misma persona (para verificar que puede usar KNN o simplemente una distancia euclidiana simple).

Una implementación de FaceNet se puede encontrar aquí . Le sugiero que intente ejecutarlo en su computadora para saber con qué está tratando realmente. Después de eso, podría ser mejor hacer lo siguiente:

  1. Transforme el modelo FaceNet mencionado en el repositorio a su versión tflite ( esta publicación de blog podría ayudar)
  2. Para cada foto enviada por el usuario, use Face API para extraer las caras
  3. Use el modelo minimizado en su aplicación para obtener las incrustaciones de la cara extraída.
  4. Procese todas las imágenes en la galería del usuario, obteniendo los vectores para las caras en las fotos.
  5. Luego compare cada vector encontrado en el paso 4 con cada vector encontrado en el paso 3 para obtener las coincidencias.

Respuesta original

Te encontraste con uno de los desafíos más frecuentes del aprendizaje automático: el sobreajuste. La detección y el reconocimiento de rostros es una gran área de investigación por sí sola y casi todos los modelos razonablemente precisos están utilizando algún tipo de aprendizaje profundo. Tenga en cuenta que incluso detectar una cara con precisión no es tan fácil como parece, sin embargo, como lo está haciendo en Android, puede usar Face API para esta tarea. (Otras técnicas más avanzadas como MTCNN son demasiado lentas / difíciles de implementar en un teléfono). Se ha demostrado que simplemente alimentar al modelo con una foto de la cara con mucho ruido de fondo o con varias personas adentro no funciona. Entonces, realmente no puedes saltarte este paso.

Después de obtener una buena cara recortada de los objetivos candidatos desde el fondo, debe superar el desafío de reconocer las caras detectadas. Una vez más, todos los modelos competentes que yo sepa, están utilizando algún tipo de redes neuronales convolucionales o de aprendizaje profundo. Usarlos en un teléfono móvil es un desafío, pero gracias a Tensorflow Lite puedes minificarlos y ejecutarlos dentro de tu aplicación. Aquí puede ver un proyecto sobre reconocimiento facial en teléfonos Android en el que había trabajado . Tenga en cuenta que cualquier buen modelo debe ser entrenado en numerosas instancias de datos etiquetados, sin embargo, hay una gran cantidad de modelos ya entrenados en grandes conjuntos de datos de caras u otras tareas de reconocimiento de imágenes, para ajustarlos y usar su conocimiento existente, podemos empleartransferencia de aprendizaje , para un inicio rápido en la detección de objetos y transferencia de aprendizaje que está estrechamente relacionado con su caso, consulte esta publicación de blog.

En general, debe obtener numerosas instancias de los rostros que desea detectar, además de numerosas imágenes de rostros de personas que no le importan, luego debe capacitar a un modelo basado en los recursos mencionados anteriormente, y luego debe use TensorFlow lite para disminuir su tamaño e incrustarlo en su aplicación. Entonces, para cada fotograma, se llama a la API de Android Face e introduce (la cara probablemente detectada) en el modelo e identifica a la persona.

Dependiendo de su nivel de tolerancia a la demora y la cantidad de entrenamiento establecido y la cantidad de objetivos, puede obtener varios resultados, sin embargo, la precisión de% 90+ es fácil de lograr si solo tiene unas pocas personas objetivo.


No quiero usar la conexión de red en mi aplicación, por lo que Google Cloud Vision está fuera de discusión, pero tensor flow lite parece ser bastante interesante ¿es gratis? y si puedes proporcionar un ejemplo de trabajo, ¡te lo agradeceré! Gracias
R.Coder

Gran respuesta por cierto!
R.Coder

Es gratis. Verifique esto para un ejemplo de trabajo. Pudimos identificar rostros de 225 personas sin utilizar la conexión de red con una precisión muy alta, aunque hubo algunos problemas técnicos en el lado de la experiencia del usuario. Pero eso debería ser un buen comienzo.
Farzad Vértigo

Está bien, lo intentaré
R.Coder

1
¡¡¡¡Funcionó!!!! Finalmente extraje ese modelo de red de cara tflite y obtuve una precisión superior al 80% en una sola imagen entrenada. ¡Pero la complejidad del tiempo es realmente enorme! ¡Para comparar dos imágenes, se necesita un mínimo de 5 a 6 segundos para tener alguna idea de cómo reducir eso?
R.Coder

2

Si entiendo correctamente, estás entrenando al clasificador con una sola imagen. En ese caso, esta imagen específica es todo lo que el clasificador podrá reconocer. Necesitaría un conjunto de imágenes de entrenamiento notablemente más grandes que muestren a la misma persona, algo así como 5 o 10 imágenes diferentes como mínimo.


¿Tienes algún ejemplo sobre cómo hacer eso?
R.Coder

Sí, estoy haciendo reconocimiento facial en una sola imagen estática
R.Coder

Vea aquí, por ejemplo, cómo usar train(): docs.opencv.org/3.4/dd/d65/…
Florian Echtler

¡Esta respuesta no ayuda si puede proporcionar algún ejemplo codificado relacionado con Android, sería mejor!
R.Coder

0

1) Cambie el valor del umbral mientras inicializa LBPHrecognizer a -> LBPHFaceRecognizer (1, 8, 8, 8, 100)

2) entrene cada cara con al menos 2-3 imágenes ya que estos reconocedores trabajan principalmente en comparación

3) Establecer el umbral de precisión mientras se reconoce. Haz algo como esto:

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

Bueno, ¿puedes editar mi código actual y proporcionar un ejemplo de trabajo para hacer eso en Java?
R.Coder
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.