Otwarte rozpoznawanie twarzy CV jest niedokładne


13

W mojej aplikacji próbuję rozpoznać twarz na konkretnym obrazie przy użyciu Open CV, tutaj najpierw trenuję jeden obraz, a następnie po treningu tego obrazu, jeśli uruchomię rozpoznawanie twarzy na tym obrazie, z powodzeniem rozpozna on wyszkoloną twarz. Jednak po przejściu do innego zdjęcia tej samej osoby rozpoznanie nie działa. Po prostu działa na wyszkolonym obrazie, więc moje pytanie brzmi: jak to naprawić?

Aktualizacja: Chcę, aby użytkownik wybrał obraz osoby z pamięci, a następnie po szkoleniu tego wybranego obrazu chcę pobrać wszystkie obrazy z pamięci, które pasują do twarzy mojego wyszkolonego obrazu

Oto moja klasa aktywności:

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

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

To są obrazy, które próbuję tu porównać. Twarz osoby jest wciąż taka sama, ale nie pasuje! Zdjęcie 1 Zdjęcie 2


Kiedy zbudowałem zadanie z ostatniego roku dla systemu automatycznej obecności, wykorzystałem 8-10 zdjęć siebie z nieco innymi pozami i warunkami oświetleniowymi, aby wyszkolić klasyfikatora.
ZdaR,

Możesz obrócić matę treningową w poziomie, aby sprostać temu wymaganiu.
nfl-x

@ nfl-x przerzucanie zdjęć nie rozwiąże problemu dokładności potrzebujemy czegoś lepszego ostatnia odpowiedź na tensorflow wydaje się być w porządku, ale nie ma wystarczających informacji lub samouczków na temat jego implementacji dla Androida, więc najlepiej zgadywać tak, że ekspert może interweniować i zapewnić właściwe rozwiązanie dla Androida
Mr. Patel

Odpowiedzi:


5

Aktualizacja

Zgodnie z nową edycją w pytaniu potrzebujesz sposobu na identyfikację nowych osób w locie, których zdjęcia mogły nie być dostępne podczas fazy szkoleniowej modelu. Zadania te nazywane są kilkoma uczeniami . Jest to podobne do wymagań agencji wywiadowczych / policyjnych, aby znaleźć swoje cele przy użyciu materiału z kamery CCTV. Jak zwykle nie ma wystarczającej liczby zdjęć konkretnego celu, podczas treningu używają modeli takich jak FaceNet . Naprawdę proponuję przeczytać ten artykuł, ale wyjaśniam tutaj kilka jego najważniejszych elementów:

  • Zasadniczo ostatnia warstwa klasyfikatora jest wektorem * 1 z n-1 elementów prawie równym zero, a jeden bliski 1. Element bliski 1 określa predykcję klasyfikatora względem etykiety wejścia. Typowa architektura CNN
  • Autorzy doszli do wniosku, że jeśli szkolą sieć klasyfikatora z określoną funkcją utraty na ogromnym zbiorze danych twarzy, możesz użyć wyjścia warstwy półfinałowej jako reprezentacji dowolnej twarzy, niezależnie od tego, czy jest ona w zestawie treningowym, czy nie, autorzy nazywają ten wektor Osadzanie twarzy .
  • Poprzedni wynik oznacza, że ​​przy bardzo dobrze wyszkolonym modelu FaceNet możesz podsumować dowolną twarz w wektorze. Bardzo interesującą cechą tego podejścia jest to, że wektory twarzy konkretnej osoby pod różnymi kątami / pozycjami / stanami znajdują się w pobliżu euklidesowej przestrzeni (właściwość ta jest wymuszona przez wybraną przez autorów funkcję straty).wprowadź opis zdjęcia tutaj
  • Podsumowując, masz model, który pobiera twarze jako dane wejściowe i zwraca wektory. Wektory blisko siebie najprawdopodobniej należą do tej samej osoby (dla sprawdzenia, czy można użyć KNN lub po prostu zwykłej odległości euklidesowej).

Jedną implementację FaceNet można znaleźć tutaj . Sugeruję, aby spróbować uruchomić go na komputerze, aby dowiedzieć się, z czym tak naprawdę masz do czynienia. Następnie najlepiej wykonać następujące czynności:

  1. Przekształć model FaceNet wspomniany w repozytorium w jego wersję tflite ( ten post może pomóc)
  2. Dla każdego zdjęcia przesłanego przez użytkownika użyj Face API do wyodrębnienia twarzy
  3. Użyj zminimalizowanego modelu w swojej aplikacji, aby uzyskać osadzenie twarzy wyodrębnionej twarzy.
  4. Przetwarzaj wszystkie obrazy w galerii użytkownika, uzyskując wektory dla twarzy na zdjęciach.
  5. Następnie porównaj każdy wektor znaleziony w kroku 4 z każdym wektorem znalezionym w kroku 3, aby uzyskać dopasowania.

Oryginalna odpowiedź

Natknąłeś się na jedno z najbardziej powszechnych wyzwań uczenia maszynowego: nadmierne dopasowanie. Wykrywanie i rozpoznawanie twarzy jest ogromnym obszarem badań, a prawie wszystkie dość dokładne modele wykorzystują pewien rodzaj głębokiego uczenia się. Pamiętaj, że nawet dokładne wykrycie twarzy nie jest tak proste, jak się wydaje, jednak robiąc to na Androidzie, możesz użyć Face API do tego zadania. (Inne bardziej zaawansowane techniki, takie jak MTCNN, są zbyt powolne / trudne do wdrożenia na telefonie). Wykazano, że samo karmienie modelu zdjęciem twarzy z dużym hałasem w tle lub wieloma osobami w środku nie działa. Tak więc naprawdę nie można pominąć tego kroku.

Po uzyskaniu ładnej, przyciętej twarzy kandydatów na cele z tła, musisz pokonać wyzwanie rozpoznania wykrytych twarzy. Ponownie, wszystkie kompetentne modele, zgodnie z moją najlepszą wiedzą, wykorzystują coś w rodzaju głębokiego uczenia / splotowych sieci neuronowych. Używanie ich na telefonie komórkowym jest wyzwaniem, ale dzięki Tensorflow Lite możesz je zminimalizować i uruchomić w swojej aplikacji. Projekt o rozpoznawania twarzy na Android telefony, że pracowali na to tutaj , że można sprawdzić. Należy pamiętać, że każdy dobry model powinien być przeszkolony w wielu przypadkach oznakowanych danych, jednak istnieje wiele modeli już przeszkolonych w zakresie dużych zbiorów danych twarzy lub innych zadań rozpoznawania obrazu, aby je ulepszyć i wykorzystać ich istniejącą wiedzę, możemy zatrudnićprzenieś naukę , aby szybko rozpocząć wykrywanie obiektów i przenieść uczenie się ściśle związane z twoją sprawą, sprawdź ten post na blogu.

Ogólnie rzecz biorąc, musisz uzyskać wiele wystąpień twarzy, które chcesz wykryć, oraz liczne zdjęcia twarzy osób, na których ci nie zależy, następnie musisz wytrenować model w oparciu o wyżej wymienione zasoby, a następnie musisz użyj TensorFlow Lite, aby zmniejszyć jego rozmiar i osadzić go w aplikacji. Następnie dla każdej klatki wywołujesz interfejs API Android Face i podajesz (prawdopodobnie wykrytą twarz) modelowi i identyfikujesz osobę.

W zależności od poziomu tolerancji na opóźnienie oraz liczby rozmiarów zestawu treningowego i liczby celów możesz uzyskać różne wyniki, jednak dokładność% 90 + jest łatwo osiągalna, jeśli masz tylko kilka osób docelowych.


Nie chcę używać połączenia sieciowego w mojej aplikacji, więc Google Cloud Vision nie wchodzi w rachubę, ale Lite Flow Flow wydaje się być dość interesujące, czy jest bezpłatne? i jeśli możesz podać działający przykład, docenię to! Dzięki
R.Coder,

Nawiasem mówiąc, świetna odpowiedź!
R.Coder

Jest wolne. Sprawdź to dla działającego przykładu. Byliśmy w stanie zidentyfikować twarze 225 osób bez korzystania z połączenia sieciowego z bardzo wysoką dokładnością, chociaż były pewne usterki po stronie doświadczenia użytkownika. Ale to powinno być dobre rozpoczęcie.
Farzad Vertigo

Dobra, spróbuję
R.Coder,

1
Zadziałało!!!! ostatecznie wyodrębniłem ten model siatki twarzy i uzyskałem ponad 80% dokładności na jednym wyuczonym obrazie. ale złożoność czasu jest naprawdę ogromna !!, Aby porównać dwa obrazy, potrzeba co najmniej 5 do 6 sekund, aby dowiedzieć się, jak to zmniejszyć?
R.Coder

2

Jeśli dobrze rozumiem, trenujesz klasyfikatora jednym obrazem. W takim przypadku ten jeden konkretny obraz jest wszystkim, co klasyfikator będzie w stanie rozpoznać. Potrzebny byłby znacznie większy zestaw zdjęć szkoleniowych przedstawiających tę samą osobę, co najmniej 5 lub 10 różnych zdjęć.


Czy masz jakiś przykład, jak to zrobić?
R.Coder

Tak, robię rozpoznawanie twarzy na jednym statycznym obrazie
R.Coder,

Zobacz tutaj przykład użycia train(): docs.opencv.org/3.4/dd/d65/…
Florian Echtler

Ta odpowiedź nie pomoże, jeśli możesz podać kodowany przykład dotyczący Androida, byłoby lepiej!
R.Coder,

0

1) Zmień wartość progową podczas inicjowania LBPHrecognizer na -> LBPHFaceRecognizer (1, 8, 8, 8, 100)

2) trenuj każdą twarz z co najmniej 2-3 zdjęciami, ponieważ te rozpoznające działają głównie na porównaniu

3) Ustaw próg dokładności podczas rozpoznawania. Zrób coś takiego:

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

Czy możesz edytować mój obecny kod i podać działający przykład, aby to zrobić w Javie?
R.Coder,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.