オープンCV顔認識が正確ではありません


13

私のアプリでは、Open CVを使用して特定の画像で顔認識を実行しようとしています。最初に1つの画像をトレーニングし、その画像をトレーニングした後、その画像で顔認識を実行すると、トレーニングされた顔が正常に認識されます。しかし、同じ人物の別の写真に目を向けると、認識が機能しません。訓練された画像でのみ機能するので、私の質問はどのように修正するのですか?

更新:私がやりたいことは、ユーザーがストレージから人物の画像を選択し、その選択した画像をトレーニングした後、トレーニングした画像の顔に一致するすべての画像をストレージからフェッチすることです

これが私の活動クラスです:

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フリッピング画像は正確性の問題を解決しないため、テンソルフローに関する最近の回答が大丈夫だと思われますが、Androidの実装で利用できる十分な情報またはチュートリアルがないため、この投稿への投票を続けることが最善の推測ですエキスパートが介入し、Androidに適切なソリューションを提供できるようにする
Mr. Patel

回答:


5

更新

問題の新しい編集によると、モデルのトレーニングフェーズ中に写真が利用できなかった可能性のある新しいユーザーをその場で識別する方法が必要です。これらのタスクは、少数ショット学習と呼ばます。これは、CCTVカメラ映像を使用してターゲットを見つけるための諜報機関/警察の要件に似ています。通常、特定のターゲットの十分な画像がないため、トレーニング中はFaceNetなどのモデルを使用します。論文を読むことをお勧めしますが、ここではそのハイライトのいくつかを説明します。

  • 一般に、分類子の最後の層は、n-1の要素がほぼゼロになり、1に1に近い* 1ベクトルです。1に近い要素は、入力のラベルに関する分類子の予測を決定します。 典型的なCNNアーキテクチャ
  • 著者は、顔の巨大なデータセットで特定の損失関数を使用して分類子ネットワークをトレーニングする場合、トレーニングセットに含まれているかどうかに関係なく、セミファイナルレイヤー出力を任意の顔の表現として使用できることを理解しました。著者はこのベクトルをFace Embeddingと呼んでいます。
  • 前の結果は、非常によくトレーニングされたFaceNetモデルを使用して、任意の顔をベクトルに要約できることを意味します。このアプローチの非常に興味深い属性は、さまざまな角度/位置/状態の特定の人物の顔のベクトルがユークリッド空間で近似していることです(この特性は、著者が選択した損失関数によって強制されます)。ここに画像の説明を入力してください
  • 要約すると、顔を入力として取得し、ベクトルを返すモデルがあります。互いに近いベクトルは、同じ人物に属している可能性が非常に高いです(KNNまたは単純なユークリッド距離を使用できることを確認するため)。

FaceNetの実装の1つはここにあります。私はあなたがあなたが実際に何を扱っているかを知るためにあなたのコンピュータでそれを走らせることを試みることを勧めます。その後、次のことを行うのが最善です。

  1. リポジトリで言及されているFaceNetモデルをtfliteバージョンに変換します(このブログ記事が役立つ場合があります)
  2. ユーザーが送信した写真ごとに、Face APIを使用して顔を抽出します
  3. アプリで縮小モデルを使用して、抽出された顔の顔の埋め込みを取得します。
  4. ユーザーのギャラリー内のすべての画像を処理し、写真の顔のベクトルを取得します。
  5. 次に、手順4で見つかった各ベクトルを手順3で見つけた各ベクトルと比較して、一致を取得します。

元の回答

機械学習の最も一般的な課題の1つであるオーバーフィットに遭遇しました。顔の検出と認識は、それ自体が大きな研究分野であり、ほぼすべての合理的に正確なモデルは、ある種のディープラーニングを使用しています。顔を正確に検出することは見かけほど簡単ではないことに注意してください。ただし、Androidで行う場合と同様に、このタスクにはFace APIを使用できます。(MTCNNなどのその他のより高度な手法は、ハンドセットに展開するには遅すぎる/困難です)。背景ノイズの多い顔写真や、複数の人が中にいるモデルにモデルをフィードするだけでは機能しないことが示されています。したがって、このステップをスキップすることはできません。

背景から候補ターゲットのトリミングされた素敵な顔を取得した後、検出された顔を認識するという課題を克服する必要があります。繰り返しますが、私の知る限りすべての有能なモデルは、ある種のディープラーニング/畳み込みニューラルネットワークを使用しています。携帯電話でそれらを使用することは困難ですが、Tensorflow Liteのおかげで、それらを縮小してアプリ内で実行できます。私が手がけたAndroidフォンでの顔認識に関するプロジェクトがここにあります。優れたモデルは、ラベル付けされたデータの多数のインスタンスでトレーニングする必要がありますが、顔やその他の画像認識タスクの大規模なデータセットでトレーニング済みのモデルが多数あり、それらを微調整して既存の知識を使用するため、転移学習、あなたのケースに密接に関連しているオブジェクト検出と転移学習のクイックスタートについては、このブログ投稿をチェックしてください。

全体として、検出したい顔の多数のインスタンスと、気にしていない人々の多数の顔写真を取得する必要があります。次に、上記のリソースに基づいてモデルをトレーニングする必要があります。 TensorFlow liteを使用してサイズを小さくし、アプリ内に埋め込みます。次に、フレームごとに、Android Face APIを呼び出し、モデルに(おそらく検出された顔)を送り、人物を識別します。

遅延の許容レベルとトレーニングセットのサイズとターゲットの数に応じて、さまざまな結果が得られますが、ターゲットの人数が少ない場合は、90%以上の精度を簡単に達成できます。


アプリでネットワーク接続を使用したくないので、Google Cloudビジョンは問題外ですが、テンソルフローライトは非常に興味深いようです。無料ですか?そして、あなたがそれの実用的な例を提供できれば、私はそれを感謝します!ありがとう
R.Coder

ちなみに大答え!
R.Coder

それは無料です。実際の例については、これを確認してください。ユーザーエクスペリエンスの面でいくつかの不具合がありましたが、非常に高い精度でネットワーク接続を使用せずに225人の顔を特定することができました。しかし、それは良いキックオフになるはずです。
Farzadめまい

わかりました、試してみます
R.Coder

1
動いた!!!!私は最終的にその顔のネットモデルtfliteを抽出し、1つのトレーニング済み画像で80%を超える精度を得ました。しかし、時間の複雑さは本当に非常に巨大です!!、2つの画像を比較する場合、それを削減する方法について、最低5〜6秒かかります。
R.Coder

2

私が正しく理解していれば、1つの画像で分類器を訓練していることになります。その場合、この1つの特定の画像が、分類子が認識できるすべてのものになります。少なくとも、少なくとも5枚または10枚の異なる画像のように、同じ人物を示す著しく大きな写真のトレーニングセットが必要になります。


それを行う方法の例はありますか?
R.Coder

はい、単一の静止画像で顔認識を行っています
R.Coder

使用方法の例についてはこちらをご覧くださいtrain()docs.opencv.org/3.4/dd/d65/...
フロリアン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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.