Open CV人脸识别不准确

13
在我的应用程序中,我正在尝试使用OpenCV对特定图像进行人脸识别。首先,我训练一张图片,然后在训练完该图片后,如果我在该图片上运行人脸识别,它可以成功识别出已经训练的人脸。但是,当我转到同一个人的另一张图片时,识别就不起作用了。它只能在经过训练的图片上工作,所以我的问题是如何纠正这个问题?
更新: 我想做的是用户应该从存储中选择一个人的图像,然后在训练选定的图像之后,我想从存储中获取所有与我训练的图像相匹配的图像。
这是我的活动类:
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张我自己的图片,这些图片姿势和光照条件略有不同,用于训练分类器。 - ZdaR
您可以将训练图像矩阵水平翻转以处理该要求。 - nfl-x
@nfl-x 翻转图像并不能解决准确性问题,我们需要更好的方法。TensorFlow 的最近回答似乎还可以,但是关于它在 Android 上的实现没有足够的信息或教程可用,所以我们最好的猜测是继续投票这篇文章,以便专家能够介入并为 Android 提供适当的解决方案。 - Mr. Patel
3个回答

5

更新

根据问题中的新编辑,您需要一种识别新人的方法,这些人的照片可能在模型的训练阶段不可用。这些任务称为少样本学习。这类似于情报/警察机构使用闭路电视摄像头录像找到目标的要求。通常来说,特定目标的图像不足,他们在训练期间使用FaceNet等模型。我真的建议阅读这篇论文,但是我在这里解释了其中的一些亮点:

  • 通常,分类器的最后一层是一个n*1向量,其中n-1个元素几乎等于零,而一个接近1。接近1的元素决定了分类器对输入标签的预测。典型的CNN架构
  • 作者们发现,如果他们在一个巨大的人脸数据集上使用特定的损失函数训练分类器网络,那么可以使用半终层输出作为任何人脸的表示,无论它是否在训练集中,作者将这个向量称为面部嵌入
  • 前面的结果意味着,通过非常好的FaceNet模型训练,您可以将任何面孔总结成一个向量。这种方法非常有趣的属性是,在欧几里得空间中,特定人物面部的向量在不同角度/位置/状态下都是相近的(这个属性是由作者选择的损失函数强制实施的)。输入图像描述
  • 总之,您有一个模型,它以面部为输入并返回向量。彼此靠近的向量很可能属于同一个人(您可以使用KNN或简单的欧几里得距离来检查)。

FaceNet的一个实现可以在这里找到。我建议你尝试在电脑上运行它,以了解你正在处理的内容。之后,最好按照以下步骤进行:

  1. 将存储库中提到的FaceNet模型转换为其tflite版本(这篇博客文章可能会有所帮助)
  2. 对于用户提交的每张照片,使用Face API提取面部信息
  3. 在应用程序中使用压缩模型获取提取的面部的面部嵌入
  4. 处理用户图库中的所有图像,获取照片中面部的向量
  5. 然后将在step4中找到的每个向量与在step3中找到的每个向量进行比较,以获取匹配项。

原始答案

你遇到了机器学习中最常见的挑战之一:过拟合。面部检测和识别是一个巨大的研究领域,几乎所有相对准确的模型都使用某种深度学习技术。请注意,即使精确地检测人脸也不像看起来那么容易,但在Android上进行操作时,可以使用Face API完成此任务。(其他更先进的技术,如MTCNN,太慢/难以在手机上部署)。已经证明,仅仅向模型提供带有很多背景噪音或多人的人脸照片并不起作用。因此,你真的不能跳过这一步骤。
在从背景中获取候选目标的整洁面部后,您需要克服识别检测到的面部的挑战。据我所知,所有能胜任的模型都使用某种深度学习/卷积神经网络。在移动电话上使用它们是一项挑战,但由于Tensorflow Lite,您可以将它们缩小并在应用程序内运行。我曾经参与的一个有关安卓手机人脸识别的项目在这里,您可以查看。 请记住,任何好的模型都应该经过大量标记数据的训练,然而已经训练了大量面部或其他图像识别任务数据集的众多模型,为了调整它们并利用它们现有的知识,我们可以采用迁移学习,对于与您的案例密切相关的对象检测和迁移学习,可以查看这篇博客文章。

总的来说,您需要获取许多要检测的人脸实例以及许多不关心的人的人脸照片,然后基于上述资源训练模型,然后使用TensorFlow Lite缩小其大小并将其嵌入到您的应用程序中。然后,对于每个帧,您调用Android Face API并将(可能检测到的面孔)馈送到模型中并识别出该人。

根据您对延迟容忍度和训练集大小和目标数量的要求,可以获得各种结果,但是如果您只有几个目标人员,则可以轻松达到90%以上的准确率。


我不想在我的应用程序中使用网络连接,因此谷歌云视觉不在考虑范围内,但是TensorFlow Lite似乎非常有趣,它是免费的吗?如果您可以提供一个工作示例,我将不胜感激!谢谢。 - R.Coder
顺便说一句,回答得很好! - R.Coder
好的,我会尝试。 - R.Coder
1
成功了!!!我最终提取了那个人脸识别模型tflite,并在单个训练图像上获得了80%以上的准确率。但时间复杂度真的非常非常高!!对于比较两张图片,最少需要5到6秒钟。有什么办法可以减少这个时间吗? - R.Coder
如果您能解决并回答另一个问题,我将再次创建一个100声望的悬赏问题! - R.Coder
显示剩余12条评论

2

如果我理解正确,你是用一张图像来训练分类器。在这种情况下,这个特定的图像就是分类器能够识别的全部内容。你需要更大的训练集,其中包括至少5到10张不同的显示同一人物的图片。


你有没有关于如何做到这一点的示例? - R.Coder
是的,我正在对一张静态的单张图像进行人脸识别。 - R.Coder
请查看以下示例以了解如何使用train()函数:https://docs.opencv.org/3.4/dd/d65/classcv_1_1face_1_1FaceRecognizer.html#ac8680c2aa9649ad3f55e27761165c0d6 - 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接