Відкрити розпізнавання обличчя CV не точно


13

У своєму додатку я намагаюся розпізнати обличчя на певному зображенні за допомогою 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


Коли я створив свій останній рік для системи автоматичного відвідування, я використовував 8-10 зображень себе з дещо різними позими та умовами освітлення для підготовки класифікатора.
ЗдаР

Ви можете перевернути килимок для навчального зображення горизонтально, щоб впоратися з цією вимогою.
nfl-x

@ nfl-x перегортання зображень не вирішить проблему точності. Нам потрібна якась краща нещодавня відповідь на tensorflow, здається, добре, але не існує достатньої інформації та підручників щодо її реалізації для Android, тому найкраще здогадуватися, щоб ми продовжували голосувати на цій посаді такий, що експерт може втрутитися і надати належне рішення для андроїда
містер Патель

Відповіді:


5

Оновлення

Згідно з новою редакцією у запитанні, вам потрібен спосіб ідентифікувати нових людей під час руху, фотографії яких можуть бути недоступними під час навчального етапу моделі. Ці завдання називаються малостріляними навчанням . Це схоже на вимоги розвідувальних / поліцейських органів для пошуку своїх цілей за допомогою камер відеоспостереження. Оскільки зазвичай не вистачає зображень конкретної цілі, під час тренінгу вони використовують такі моделі, як FaceNet . Я дійсно пропоную прочитати документ, однак, я пояснюю декілька його основних моментів тут:

  • Як правило, останній шар класифікатора - це вектор * 1 з n-1 елементів, що майже дорівнює нулю, і один, близький до 1. Елемент, близький до 1, визначає передбачення класифікатора щодо мітки введення. Типова архітектура CNN
  • Автори з'ясували, що якщо вони навчають мережу класифікаторів із певною функцією втрат на величезному наборі даних облич, ви можете використовувати вихідний півфінальний рівень як зображення будь-якого обличчя, незалежно від того, він є у навчальному наборі чи ні, автори називають цей векторний Face Embedding .
  • Попередній результат означає, що за допомогою дуже добре навченої моделі FaceNet ви можете звести будь-яке обличчя у вектор. Дуже цікавим атрибутом цього підходу є те, що вектори особи конкретної людини в різних ракурсах / положеннях / станах є близькими в евклідовому просторі (ця властивість застосовується функцією втрат, яку обрали автори).введіть тут опис зображення
  • Підсумовуючи, у вас є модель, яка отримує обличчя як вхідні та повертаючі вектори. Близькі один до одного вектори, ймовірно, належать одній людині (Для перевірки того, що ви можете використовувати KNN або просто просту евклідову відстань).

Одну реалізацію FaceNet можна знайти тут . Я пропоную вам спробувати запустити його на комп’ютері, щоб дізнатися, з чим ви насправді маєте справу. Після цього, можливо, найкраще зробити наступне:

  1. Перетворіть згадану в сховищі модель FaceNet у її версію tflite ( ця пошта для блогів може допомогти)
  2. Для кожної фотографії, наданої користувачем, використовуйте Face API для витягування обличчя
  3. Використовуйте мінімізовану модель у своєму додатку, щоб отримати вбудовані обличчя витягнутого обличчя.
  4. Обробляйте всі зображення в галереї користувача, отримуючи вектори для облич на фотографіях.
  5. Потім порівняйте кожен вектор, знайдений на кроці 4, з кожним вектором, знайденим у step3, щоб отримати збіги.

Оригінальний відповідь

Ви зіткнулися з однією з найпоширеніших проблем машинного навчання: Overfitting. Розпізнавання та розпізнавання облич - це величезна область власних досліджень, і майже всі досить точні моделі використовують певне глибоке навчання. Зауважте, що навіть точно виявити обличчя не так просто, як здається, однак, як ви це робите на android, ви можете використовувати Face API для цього завдання. (Інші більш досконалі методи, такі як MTCNN , занадто повільні / важкі для розгортання на слухавці). Показано, що просто годувати модель фотографією обличчя з великою кількістю фонового шуму або кількох людей всередині не працює. Отже, ви дійсно не можете пропустити цей крок.

Отримавши приємне підстрижене обличчя кандидатів-мішеней з фону, вам потрібно подолати завдання розпізнавання виявлених облич. Знову ж таки, всі грамотні моделі, наскільки мені відомо, використовують певні нейромережі глибокого навчання / згортання. Використання їх на мобільному телефоні - це складне завдання, але завдяки Tensorflow Lite ви можете мінімізувати їх та запускати їх у своєму додатку. Тут ви можете перевірити проект розпізнавання облич на андроїд-телефонах, над яким я працював . Майте на увазі, що будь-яка хороша модель повинна навчатися на численних примірниках мічених даних, однак є безліч моделей, які вже пройшли навчання на великих наборах даних облич або інших завданнях розпізнавання зображень, щоб налаштувати їх і використовувати наявні знання, ми можемо використовуватитрансферне навчання , для швидкого початку виявлення об'єктів та передачі навчання, яке тісно пов'язане з вашим випадком, перегляньте цю публікацію в блозі.

Загалом, ви повинні отримати численні екземпляри облич, які ви хочете виявити, а також численні фотографії людей, які вас не цікавлять, тоді вам потрібно навчити модель на основі вищезгаданих ресурсів, і тоді вам потрібно використовуйте TensorFlow lite, щоб зменшити розмір і вбудувати його у свою програму. Тоді для кожного кадру ви зателефонуєте на Android android Face API і подайте (можливо, виявлене обличчя) в модель і визначте особу.

Залежно від рівня толерантності до затримки та кількості навчального набору та кількості цілей, ви можете отримати різні результати, однак, точність% 90 + легко досяжна, якщо у вас є лише кілька цільових людей.


Я не хочу використовувати мережеве підключення у своєму додатку, тому Google хмарне бачення не викликає сумнівів, але тензорний потік Lite здається досить цікавим, це безкоштовно? і якщо ви можете надати робочий приклад цього, я буду вдячний! Спасибі
R.Coder

Чудова відповідь до речі!
R.Coder

Це безкоштовно. Перевірте це на робочому прикладі. Нам вдалося виявити обличчя 225 осіб, не використовуючи мережеве з'єднання з дуже високою точністю, хоча в роботі користувачів було деякі проблеми. Але це має бути гарним ударом.
Фарзад Вертиго

Гаразд, я спробую
R.Coder

1
Це спрацювало!!!! Зрештою, я видобув цю модель чистих моделей tflite і отримав вище 80% точності на одному тренованому зображенні. але складність у часі дійсно величезна !! Для порівняння двох зображень потрібно мінімум 5 - 6 секунд, будь-яке уявлення про те, як зменшити це?
R.Coder

2

Якщо я правильно розумію, ви тренуєте класифікатора з одним зображенням. У цьому випадку це одне конкретне зображення - це все, що класифікатор зможе коли-небудь розпізнати. Вам знадобиться помітно більший навчальний набір зображень із зображенням тієї самої людини, щонайменше, як 5 або 10 різних зображень.


Чи є у вас приклад, як це зробити?
Р.Кодер

Так, я розпізнаю обличчя на одному статичному зображенні
R.Coder

Дивіться тут, наприклад, як користуватися train(): docs.opencv.org/3.4/dd/d65/…
Florian Echtler

Ця відповідь не допомагає, якщо ви можете навести якийсь кодований приклад, що стосується андроїда, було б краще!
R.Coder

0

1) Змініть порогове значення під час ініціалізації LBPHrecognizer на -> 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
}

Ну чи можете ви відредагувати мій поточний код і надати робочий приклад, щоб зробити це в Java?
R.Coder
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.