Неправильное распознавание лиц с 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;
        }
    }
}

Мой файл Utils Class:

   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

R.Coder
источник
Когда я строил свое окончательное годовое задание для автоматической системы посещаемости, я использовал 8-10 изображений себя с немного различными позами и условиями освещения для обучения классификатора.
ZdaR
Вы можете перевернуть тренировочный мат горизонтально, чтобы удовлетворить это требование.
НФЛ-х
@ nfl-x, перелистывание изображений не решит проблему точности, нам нужно что-то лучшее. Недавний ответ на tenorflow кажется нормальным, но нет достаточной информации или учебных пособий по его реализации для Android, поэтому мы должны продолжать голосовать за этот пост. такой, что эксперт может вмешаться и предоставить правильное решение для Android
г-н Патель

Ответы:

5

Обновить

Согласно новому редактированию в вопросе, вам нужен способ на лету идентифицировать новых людей, чьи фотографии могут быть недоступны на этапе обучения модели. Эти задачи называются обучением в несколько выстрелов . Это похоже на требования разведывательных / полицейских органов по обнаружению своих целей с использованием видеозаписи с камеры видеонаблюдения. Поскольку обычно не хватает изображений конкретной цели, во время обучения они используют такие модели, как FaceNet . Я действительно предлагаю прочитать газету, однако, я объясняю некоторые из ее основных моментов здесь:

  • Как правило, последний слой классификатора представляет собой вектор * 1, где n-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 + легко достижима, если у вас всего несколько человек.

Фарзад Головокружение
источник
Я не хочу использовать сетевое подключение в своем приложении, поэтому об облачном видении Google не может быть и речи, но тензор потока облегчен, кажется довольно интересным, бесплатно? и если вы можете предоставить рабочий пример этого, я буду признателен за это! Спасибо
Р.Кодер
Отличный ответ, кстати!
Р.Кодер
Это бесплатно. Проверьте это для рабочего примера. Мы смогли идентифицировать лица 225 человек без использования сетевого подключения с очень высокой точностью, хотя были некоторые проблемы с пользовательским интерфейсом. Но это должно быть хорошим началом.
Фарзад Головокружение
Хорошо, я попробую
R.Coder
1
Это сработало!!!! В итоге я извлек эту модель лица и получил точность выше 80% на одном обученном изображении. но сложность времени действительно огромна !!. Для сравнения двух изображений требуется не менее 5-6 секунд, есть идеи, как это уменьшить?
Р.Кодер
2

Если я правильно понимаю, вы тренируете классификатор с одним изображением. В этом случае это конкретное изображение - это все, что классификатор сможет когда-либо распознать. Вам понадобится значительно больший обучающий набор изображений, показывающих одного и того же человека, что-то вроде 5 или 10 разных изображений, как минимум.

Флориан Эхтлер
источник
У вас есть пример, как это сделать?
Р.Кодер
Да, я делаю распознавание лица на одном статическом изображении
R.Coder
Смотрите здесь пример использования train(): docs.opencv.org/3.4/dd/d65/…
Флориан Эхтлер,
Этот ответ не поможет, если вы можете предоставить какой-то закодированный пример, относящийся к Android, было бы лучше!
Р.Кодер
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?
Р.Кодер