O reconhecimento de rosto CV aberto não é preciso

13

No meu aplicativo, estou tentando fazer o reconhecimento de rosto em uma imagem específica usando o Open CV, aqui primeiro estou treinando uma imagem e, depois de treinar essa imagem, se eu executar o reconhecimento de rosto nessa imagem, ela reconhecerá com êxito o rosto treinado. No entanto, quando ligo para outra foto da mesma pessoa, o reconhecimento não funciona. Funciona apenas na imagem treinada, então minha pergunta é como retificá-la?

Atualização: O que eu quero fazer é que o usuário selecione a imagem de uma pessoa do armazenamento e, depois de treinar essa imagem selecionada, eu quero buscar todas as imagens do armazenamento que correspondam à face da minha imagem treinada

Aqui está a minha turma de atividades:

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 My File Utils:

   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 são as imagens que eu estou tentando comparar aqui, o rosto da pessoa ainda é o mesmo em reconhecimento, não é compatível! Imagem 1 Imagem 2

R.Coder
fonte
Quando construí minha tarefa no último ano para o Sistema de Atendimento Automático, usei 8 a 10 imagens minhas com poses e condições de iluminação ligeiramente diferentes para treinar o classificador.
ZdaR 18/11/19
Você pode virar o tapete da imagem do treinamento horizontalmente para lidar com esse requisito.
Nfl-x
@ As imagens invertidas da nfl-x não resolverão o problema de precisão. Precisamos de algo melhor. Resposta recente no tensorflow parece ok, mas não há informações ou tutoriais suficientes disponíveis sobre sua implementação para o Android, portanto, nosso melhor palpite é continuar votando neste post. de tal forma que um especialista pode intervir e fornecer uma solução adequada para o Android
Mr. Patel

Respostas:

5

Atualizar

De acordo com a nova edição da pergunta, você precisa de uma maneira de identificar novas pessoas em tempo real cujas fotos podem não estar disponíveis durante a fase de treinamento do modelo. Essas tarefas são chamadas de poucas aprendizagens . Isso é semelhante aos requisitos das agências de inteligência / polícia para encontrar seus alvos usando imagens de câmeras de CFTV. Como geralmente não há imagens suficientes de um alvo específico, durante o treinamento, eles usam modelos como o FaceNet . Eu realmente sugiro a leitura do artigo, no entanto, explico alguns de seus destaques aqui:

  • Geralmente, a última camada de um classificador é um vetor * 1 com n-1 dos elementos quase igual a zero e um próximo a 1. O elemento próximo a 1 determina a previsão do classificador sobre o rótulo da entrada. Arquitetura típica da CNN
  • Os autores descobriram que se eles treinam uma rede classificadora com uma função de perda específica em um enorme conjunto de dados de faces, você pode usar a saída de camada semifinal como representação de qualquer face, independentemente de estar ou não no conjunto de treinamento, os autores chamam esse vetor Face Embedding .
  • O resultado anterior significa que, com um modelo FaceNet muito bem treinado, você pode resumir qualquer face em um vetor. O atributo muito interessante dessa abordagem é que os vetores do rosto de uma pessoa específica em diferentes ângulos / posições / estados têm proximidade no espaço euclidiano (essa propriedade é reforçada pela função de perda que os autores escolheram).insira a descrição da imagem aqui
  • Em resumo, você tem um modelo que obtém faces como vetores de entrada e retorna. É provável que os vetores próximos um do outro pertençam à mesma pessoa (para verificar se você pode usar o KNN ou apenas a distância euclidiana simples).

Uma implementação do FaceNet pode ser encontrada aqui . Eu sugiro que você tente executá-lo no seu computador para saber com o que realmente está lidando. Depois disso, talvez seja melhor fazer o seguinte:

  1. Transforme o modelo FaceNet mencionado no repositório em sua versão tflite ( este post do blog pode ajudar)
  2. Para cada foto enviada pelo usuário, use a API do rosto para extrair o (s) rosto (s)
  3. Use o modelo minificado no seu aplicativo para obter os encaixes de rosto do rosto extraído.
  4. Processe todas as imagens na galeria do usuário, obtendo os vetores para os rostos nas fotos.
  5. Em seguida, compare cada vetor encontrado na etapa 4 com cada vetor encontrado na etapa 3 para obter as correspondências.

Resposta original

Você se deparou com um dos desafios mais comuns do aprendizado de máquina: o excesso de ajustes. A detecção e o reconhecimento de rostos são uma enorme área de pesquisa por si só e quase todos os modelos razoavelmente precisos estão usando algum tipo de aprendizado profundo. Observe que mesmo detectar um rosto com precisão não é tão fácil quanto parece; no entanto, como você está fazendo no Android, você pode usar o Face API para esta tarefa. (Outras técnicas mais avançadas, como o MTCNN, são muito lentas / difíceis de implantar em um telefone). Foi demonstrado que apenas alimentar o modelo com uma foto de rosto com muito ruído de fundo ou várias pessoas no interior não funciona. Portanto, você realmente não pode pular esta etapa.

Depois de obter um bom rosto aparado dos alvos candidatos em segundo plano, você precisa superar o desafio de reconhecer os rostos detectados. Novamente, todos os modelos competentes, com o melhor de meu conhecimento, estão usando algum tipo de aprendizagem profunda / redes neurais convolucionais. Usá-los em um telefone celular é um desafio, mas, graças ao Tensorflow Lite, você pode reduzi- los e executá-los em seu aplicativo. Um projeto sobre reconhecimento facial em telefones Android em que eu trabalhei está aqui e você pode conferir. Lembre-se de que qualquer bom modelo deve ser treinado em várias instâncias de dados rotulados; no entanto, há uma infinidade de modelos já treinados em grandes conjuntos de dados de rostos ou outras tarefas de reconhecimento de imagem, para ajustá-los e usar o conhecimento existente, podemos empregartransferência de aprendizado , para iniciar rapidamente a detecção de objetos e o aprendizado de transferência intimamente relacionado ao seu caso, verifique esta postagem do blog.

No geral, você precisa obter várias instâncias dos rostos que deseja detectar, além de várias fotos de rostos de pessoas que não lhe interessam; depois, você precisa treinar um modelo com base nos recursos mencionados acima e precisa use o TensorFlow Lite para diminuir seu tamanho e incorporá-lo ao seu aplicativo. Para cada quadro, você chama a API Face Android e alimenta (o rosto provavelmente detectado) no modelo e identifica a pessoa.

Dependendo do seu nível de tolerância a atraso e do número de tamanhos do conjunto de treinamento e do número de metas, é possível obter vários resultados; no entanto, é possível obter% 90 + de precisão se você tiver apenas algumas pessoas-alvo.

Farzad Vertigo
fonte
Como não quero usar a conexão de rede no meu aplicativo, a visão da nuvem do Google está fora de questão, mas o fluxo de tensor Lite parece ser bastante interessante, é grátis? e se você puder fornecer um exemplo de trabalho, eu aprecio isso! Graças
R.Coder
Ótima resposta pelo caminho!
R.Coder
É grátis. Verifique isso para um exemplo de trabalho. Conseguimos identificar rostos de 225 pessoas sem usar a conexão de rede com uma precisão muito alta, embora houvesse algumas falhas no lado da experiência do usuário. Mas isso deve ser um bom começo.
Farzad Vertigo
Ok, eu vou dar-lhe uma tentativa
R.Coder
11
Funcionou!!!! Acabei por extrair o modelo líquido face tflite e obtive uma precisão acima de 80% em uma única imagem treinada. mas a complexidade do tempo é realmente muito grande !, Para comparar duas imagens, leva no mínimo 5 a 6 segundos, alguma idéia de como reduzir isso?
R.Coder
2

Se bem entendi, você está treinando o classificador com uma única imagem. Nesse caso, essa imagem específica é tudo o que o classificador poderá reconhecer. Você precisaria de um conjunto de fotos notavelmente maior, mostrando a mesma pessoa, algo como 5 ou 10 imagens diferentes, no mínimo.

Florian Echtler
fonte
Você tem algum exemplo de como fazer isso?
R.Coder
Sim, eu estou fazendo reconhecimento de face em uma única imagem estática
R.Coder
Veja aqui, por exemplo, como usar train(): docs.opencv.org/3.4/dd/d65/…
Florian Echtler
Esta resposta não ajuda se você puder fornecer um exemplo codificado relacionado ao Android, seria melhor!
R.Coder
0

1) Altere o valor do limite ao inicializar o LBPHrecognizer para -> LBPHFaceRecognizer (1, 8, 8, 8, 100)

2) treine cada rosto com pelo menos 2-3 fotos, já que esses reconhecedores trabalham principalmente em comparação

3) Defina o limite de precisão durante o reconhecimento. Faça algo parecido com isto:

//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
fonte
Bem, você pode editar meu código atual e fornecer um exemplo de trabalho para fazer isso em java?
R.Coder