[java] 열린 CV 얼굴 인식이 정확하지 않습니다

내 응용 프로그램에서 Open CV를 사용하여 특정 이미지에서 얼굴 인식을 시도하고 있습니다. 여기서 먼저 하나의 이미지를 훈련 한 다음 해당 이미지에서 얼굴 인식을 실행하면 해당 이미지를 훈련 한 후 훈련 된 얼굴을 성공적으로 인식합니다. 그러나 같은 사람 인식의 다른 그림을 볼 때 작동하지 않습니다. 훈련 된 이미지에서만 작동하므로 내 질문은 어떻게 수정합니까?

업데이트 : 내가하고 싶은 일은 사용자가 저장소에서 사람의 이미지를 선택한 다음 선택한 이미지를 훈련 한 후 훈련 된 이미지의 얼굴과 일치하는 저장소에서 모든 이미지를 가져오고 싶습니다.

내 활동 수업은 다음과 같습니다.

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

내 파일 활용 수업 :

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

이것들은 내가 비교하려고하는 이미지입니다. 사람의 얼굴은 여전히 ​​일치하지 않습니다.
이미지 1
이미지 2



답변

최신 정보

질문의 새로운 편집에 따르면 모델의 훈련 단계에서 사진을 사용할 수 없었던 새로운 사람들을 즉시 식별 할 수있는 방법이 필요합니다. 이러한 작업을 소수의 학습 이라고 합니다. 이것은 CCTV 카메라 영상을 사용하여 목표를 찾기위한 정보 / 경찰 기관의 요구 사항과 유사합니다. 일반적으로 특정 대상의 이미지가 충분하지 않으므로 훈련 중에 FaceNet 과 같은 모델을 사용합니다 . 논문을 읽는 것이 좋지만 여기에 몇 가지 주요 내용을 설명합니다.

  • 일반적으로 분류기의 마지막 레이어는 n-1의 요소가 0과 거의 같고 1에 가까운 1 * 1 벡터입니다. 1에 가까운 요소는 입력 레이블에 대한 분류기의 예측을 결정합니다. 일반적인 CNN 아키텍처
  • 저자는 거대한 얼굴 데이터 세트에서 특정 손실 함수를 사용하여 분류 자 ​​네트워크를 학습하는 경우 훈련 세트에 있든 없든 상관없이 준결승 레이어 출력을 얼굴의 표현으로 사용할 수 있다는 것을 알았습니다. 저자는이 벡터를 Face Embedding 이라고 부릅니다 .
  • 이전 결과는 잘 훈련 된 FaceNet 모델을 사용하여 모든 얼굴을 벡터로 요약 할 수 있음을 의미합니다. 이 접근법의 매우 흥미로운 속성은 다른 각도 / 위치 / 상태의 특정 사람 얼굴의 벡터가 유클리드 공간에서 근접해 있다는 것입니다 (이 속성은 저자가 선택한 손실 함수에 의해 시행됩니다).여기에 이미지 설명을 입력하십시오
  • 요약하면,면을 입력으로 받고 벡터를 반환하는 모델이 있습니다. 서로 가까이있는 벡터는 같은 사람에 속할 가능성이 높습니다 (KNN 또는 간단한 유클리드 거리를 사용할 수 있는지 확인하기 위해).

FaceNet의 한 구현은 여기 에서 찾을 수 있습니다 . 실제로 다루는 내용을 알기 위해 컴퓨터에서 실행 해 보는 것이 좋습니다. 그 후에는 다음을 수행하는 것이 가장 좋습니다.

  1. 저장소에 언급 된 FaceNet 모델을 tflite 버전으로 변환하십시오 ( 블로그 포스트가 도움 될 수 있음)
  2. 사용자가 제출 한 각 사진에 대해 Face API를 사용하여 얼굴을 추출하십시오.
  3. 추출 된 얼굴의 얼굴 임베딩을 얻으려면 앱에서 축소 된 모델을 사용하십시오.
  4. 사용자 갤러리의 모든 이미지를 처리하여 사진에서 얼굴의 벡터를 가져옵니다.
  5. 그런 다음 4 단계에서 찾은 각 벡터와 3 단계에서 찾은 각 벡터를 비교하여 일치하는 것을 얻습니다.

원래 답변

머신 러닝의 가장 큰 과제 중 하나 인 과적 합을 발견했습니다. 얼굴 인식 및 인식은 자체적으로 거대한 연구 분야이며 거의 모든 합리적으로 정확한 모델은 일종의 딥 러닝을 사용합니다. 얼굴을 정확하게 감지하는 것만 큼 쉽지는 않지만 Android에서 수행하는 것처럼 이 작업에 Face API 를 사용할 수 있습니다 . ( MTCNN 과 같은 다른 고급 기술 은 핸드셋에 배포하기에는 너무 느리고 어렵습니다). 배경 소음이 많거나 내부에 여러 사람이있는 얼굴 사진으로 모델을 공급하는 것만으로는 작동하지 않는 것으로 나타났습니다. 따라서이 단계를 건너 뛸 수 없습니다.

배경에서 후보 대상의 멋진 손질 된 얼굴을 얻은 후에는 감지 된 얼굴을 인식하는 문제를 극복해야합니다. 다시 말하지만, 내가 아는 한 모든 유능한 모델은 일종의 딥 러닝 / 컨볼 루션 신경망을 사용하고 있습니다. 휴대폰에서 사용하는 것은 어려운 일이지만 Tensorflow Lite 덕분에 앱을 축소하고 앱 내에서 실행할 수 있습니다. 내가 작업 한 안드로이드 폰의 얼굴 인식에 관한 프로젝트가 있습니다 . 좋은 모델은 레이블이 지정된 여러 데이터 인스턴스에 대해 훈련해야하지만, 대규모 얼굴 데이터 세트 또는 기타 이미지 인식 작업에 대해 이미 훈련 된 수많은 모델이 있으며,이를 조정하고 기존 지식을 사용하려면전송 학습 , 물체 감지 및 확인 밀접하게 사건에 관련되어 전송 학습에 대한 빠른 시작을 위해 블로그 게시물을.

전반적으로 감지하려는 얼굴의 수많은 인스턴스와 관심이없는 사람들의 수많은 얼굴 사진을 가져와야하며 위에서 언급 한 리소스를 기반으로 모델을 훈련해야합니다. TensorFlow Lite를 사용하여 크기를 줄이고 앱에 포함하십시오. 그런 다음 각 프레임에 대해 Android Face API를 호출하고 (아마도 감지 된 얼굴)을 모델에 공급하고 사람을 식별합니다.

지연 허용 오차 수준과 훈련 세트 크기 및 목표 수에 따라 다양한 결과를 얻을 수 있지만 목표 인원이 적은 경우 % 90 + 정확도를 쉽게 달성 할 수 있습니다.


답변

올바르게 이해하면 단일 이미지로 분류자를 훈련시킵니다. 이 경우이 특정 이미지는 분류자가 인식 할 수있는 모든 것입니다. 같은 사람을 나타내는 눈에 띄게 더 큰 훈련 세트가 필요합니다.


답변

1) LBPH 인식기를 초기화하는 동안 임계 값을-> LBPHFaceRecognizer (1, 8, 8, 8, 100)로 변경하십시오.

2) 인식기는 주로 비교 작업을 수행하기 때문에 적어도 2-3 장의 사진으로 각 얼굴을 훈련시킵니다.

3) 인식하는 동안 정확도 임계 값을 설정하십시오. 다음과 같이하십시오 :

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


답변