com.joravasal.keyface.FindFacesView.java Source code

Java tutorial

Introduction

Here is the source code for com.joravasal.keyface.FindFacesView.java

Source

/**************************************
 * 
 * KeyFace - A program for android that recognizes faces in real time
 *  using OpenCV libraries.
 *  Copyright (C) 2012  Jorge Avalos-Salguero
 *  To contact the author: joravasal@gmail.com
 *  or search for my profile in LinkedIn.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 **************************************/

package com.joravasal.keyface;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.LinkedList;
import java.util.List;

import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.CvException;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.highgui.Highgui;
import org.opencv.highgui.VideoCapture;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;

import android.content.Context;
import android.graphics.Bitmap;
import android.os.Environment;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.widget.Toast;

public class FindFacesView extends CameraAccessView {

    private Mat mRgba;
    private Mat mGray;
    private CascadeClassifier cascade;
    private String tag = "FindFacesView::";
    private int savedFaces;

    public FindFacesView(Context context) {
        super(context);
        Log.i(tag, "Constructor-1");
        constructor(context);
    }

    public FindFacesView(Context context, AttributeSet attrs) {
        super(context);
        Log.i(tag, "Constructor-2");
        constructor(context);
    }

    public FindFacesView(Context context, AttributeSet attrs, int defStyle) {
        super(context);
        Log.i(tag, "Constructor-3");
        constructor(context);
    }

    private void constructor(Context context) {
        try {
            InputStream is = context.getResources().openRawResource(R.raw.haarcascade_frontalface_default);
            File cascadeDir = context.getDir("cascade", Context.MODE_PRIVATE);
            File cascadeFile = new File(cascadeDir, "haarcascade_frontalface_default.xml");
            FileOutputStream os = new FileOutputStream(cascadeFile);

            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = is.read(buffer)) != -1)
                os.write(buffer, 0, bytesRead);
            is.close();
            os.close();

            cascade = new CascadeClassifier(cascadeFile.getAbsolutePath());
            if (cascade.empty()) {
                cascade = null;
                Log.e(tag, "Error loading cascade");
            }
            Log.i(tag, "Cascade loaded: " + cascadeFile.getAbsolutePath());
            cascadeFile.delete();
            cascadeDir.delete();

            int saveSize = new Integer(KeyFaceActivity.prefs.getString("savedFaceSize", "200"));
            String appdir = Environment.getExternalStorageDirectory().getPath() + "/"
                    + KeyFaceActivity.globalappcontext.getString(R.string.app_dir);

            KeyFaceActivity.recogAlgorithm = (IRecognitionAlgorithm) new PCAfaceRecog(appdir,
                    new Size(saveSize, saveSize));

            savedFaces = KeyFaceActivity.prefs.getInt("savedFaces", savedFaces);

        } catch (Exception e) {
            e.printStackTrace();
            Log.e(tag, "Error in class builder: " + e.getCause());
        }
    }

    @Override
    protected Bitmap processFrame(VideoCapture camera) {
        //Log.i(tag,"Processing frame for our delight");

        Mat mRgbaAux = new Mat();
        Mat mGrayAux = new Mat();
        camera.retrieve(mRgbaAux, Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGBA);
        camera.retrieve(mGrayAux, Highgui.CV_CAP_ANDROID_GREY_FRAME);
        //Correct the direction of the image
        mRgba = correctCameraImage(mRgbaAux);
        mGray = correctCameraImage(mGrayAux);

        AlgorithmReturnValue resExample = null;
        //We look for faces in the captured images
        if (cascade != null) {
            int faceSize = Math.round(mGray.rows() * KeyFaceActivity.minFaceSize);
            List<Rect> faces = new LinkedList<Rect>();
            try {
                cascade.detectMultiScale(mGray, faces, 1.1, 2, 2, new Size(faceSize, faceSize));
            } catch (CvException e) {
                System.err.println(e.getMessage());
            }
            for (Rect r : faces) { //For each face

                //The Rectangle commented is the area that will be used to check the face,
                //but an ellipse is shown instead, I think it looks better.
                //Core.rectangle(mRgba, r.tl(), r.br(), new Scalar(0,0,255,100), 3);

                String nombre = null;

                // We try to recognize it
                AlgorithmReturnValue res = KeyFaceActivity.recogAlgorithm.recognizeFace(mGray.submat(r));
                resExample = res;
                if (res.getResult() != -1) {
                    //if it worked, we find the name
                    nombre = findName(res.getResult());
                }
                Point center = new Point(r.x + (r.width / 2), r.y + (r.height / 2));
                //If nombre is null we have no name, thus is unrecognized and draw a red circle, together with the text "Unknown"
                if (nombre == null) {
                    Core.ellipse(mRgba, center, new Size(r.width / 2 - 5, r.height / 2 + 20), 0, 0, 360,
                            new Scalar(255, 0, 0, 30), 3);
                    Core.rectangle(mRgba, new Point(r.x + 45, r.y + r.height + 20),
                            new Point(r.x + 200, r.y + r.height + 60), new Scalar(70, 50, 50, 255), Core.FILLED);
                    Core.putText(mRgba, "Unknown", new Point(r.x + 50, r.y + r.height + 50),
                            Core.FONT_HERSHEY_PLAIN, 2, new Scalar(200, 200, 200, 100));

                    //Check if the user is tryaing to save a new face
                    if (KeyFaceActivity.addingFaces && faces.size() == 1) {
                        //All is in order, we save a new image and update our account of faces. We update the recognizer data as well.
                        addFaceToDB(mGray, r, savedFaces);

                        KeyFaceActivity.toastHandler.post(new Runnable() {
                            public void run() {
                                KeyFaceActivity.prefs.edit()
                                        .putInt("savedFaces", KeyFaceActivity.prefs.getInt("savedFaces", 0) + 1)
                                        .apply();
                            }
                        });

                        /*KeyFaceActivity.lock.lock();
                        try {
                           KeyFaceActivity.faceAdded = true;
                           KeyFaceActivity.addingFaces = false;
                           KeyFaceActivity.condition.signalAll();
                        }
                        finally {
                           KeyFaceActivity.lock.unlock();
                        }
                        */

                        if (!KeyFaceActivity.recogAlgorithm.updateData(false)) {
                            System.err.println("Couldn't update the recognition algorithm with the new picture.");
                        }
                        KeyFaceActivity.addingFaces = false;

                        KeyFaceActivity.toastHandler.post(new Runnable() {
                            public void run() {
                                Toast.makeText(KeyFaceActivity.globalappcontext, "Face saved successfully!",
                                        Toast.LENGTH_SHORT).show();
                            }
                        });
                    }
                    //The user tried to save a face when there was more than one, it fails and sends a message to the user.
                    else if (KeyFaceActivity.addingFaces && faces.size() > 1) {
                        KeyFaceActivity.toastHandler.post(new Runnable() {
                            public void run() {
                                Toast.makeText(KeyFaceActivity.globalappcontext,
                                        "Make sure there is only one face!", Toast.LENGTH_SHORT).show();
                            }
                        });
                        KeyFaceActivity.addingFaces = false;
                    }
                }

                else { //We know this face!
                    Core.ellipse(mRgba, center, new Size(r.width / 2 - 5, r.height / 2 + 20), 0, 0, 360,
                            new Scalar(0, 255, 0, 100), 3);
                    Core.rectangle(mRgba, new Point(r.x + 45, r.y + r.height + 20),
                            new Point(r.x + 200, r.y + r.height + 60), new Scalar(50, 70, 50, 255), Core.FILLED);
                    Core.putText(mRgba, nombre, new Point(r.x + 50, r.y + r.height + 50), Core.FONT_HERSHEY_PLAIN,
                            2, new Scalar(0, 255, 0, 100));
                    if (KeyFaceActivity.addingFaces && faces.size() == 1) {
                        //If the user tries to save a face when it is already known we don let him.
                        KeyFaceActivity.toastHandler.post(new Runnable() {
                            public void run() {
                                Toast.makeText(KeyFaceActivity.globalappcontext, "This face is already known!",
                                        Toast.LENGTH_SHORT).show();
                            }
                        });
                        KeyFaceActivity.addingFaces = false;
                    }
                }
            }
            //If there is no face we tell the user there was a mistake
            if (KeyFaceActivity.addingFaces && faces.size() <= 0) {
                KeyFaceActivity.toastHandler.post(new Runnable() {
                    public void run() {
                        Toast.makeText(KeyFaceActivity.globalappcontext, "No face found!", Toast.LENGTH_SHORT)
                                .show();
                    }
                });
                KeyFaceActivity.addingFaces = false;
            }
        }

        savedFaces = KeyFaceActivity.prefs.getInt("savedFaces", savedFaces);

        if (KeyFaceActivity.prefs.getBoolean("showData", false)) {
            try {
                if (resExample != null) {
                    //background rectangle for extra info on PCA
                    Core.rectangle(mRgba, new Point(0, mRgba.height() - 100),
                            new Point(mRgba.width(), mRgba.height()), new Scalar(50, 50, 50, 50), Core.FILLED);
                    //Data for closest image 
                    Core.putText(mRgba, "1st", new Point(5, mRgba.height() - 80), Core.FONT_HERSHEY_PLAIN, 2,
                            new Scalar(250, 250, 250, 200));
                    Core.putText(mRgba, Integer.toString(resExample.getClosestImage()),
                            new Point(5, mRgba.height() - 55), Core.FONT_HERSHEY_PLAIN, 2,
                            new Scalar(250, 250, 250, 200));
                    Core.putText(mRgba, Double.toString(resExample.getDistClosestImage() / 100000).substring(0, 6),
                            new Point(5, mRgba.height() - 30), Core.FONT_HERSHEY_PLAIN, 2,
                            new Scalar(250, 250, 250, 200));
                    //Data for second closest image
                    Core.putText(mRgba, "2nd", new Point(180, mRgba.height() - 80), Core.FONT_HERSHEY_PLAIN, 2,
                            new Scalar(250, 250, 250, 200));
                    Core.putText(mRgba, Integer.toString(resExample.getSecondClosestImage()),
                            new Point(180, mRgba.height() - 55), Core.FONT_HERSHEY_PLAIN, 2,
                            new Scalar(250, 250, 250, 200));
                    Core.putText(mRgba,
                            Double.toString(resExample.getDistSecondClosestImage() / 100000).substring(0, 6),
                            new Point(180, mRgba.height() - 30), Core.FONT_HERSHEY_PLAIN, 2,
                            new Scalar(250, 250, 250, 200));
                    //Data for farthest image
                    Core.putText(mRgba, "Last", new Point(355, mRgba.height() - 80), Core.FONT_HERSHEY_PLAIN, 2,
                            new Scalar(250, 250, 250, 200));
                    Core.putText(mRgba, Integer.toString(resExample.getFarthestImage()),
                            new Point(355, mRgba.height() - 55), Core.FONT_HERSHEY_PLAIN, 2,
                            new Scalar(250, 250, 250, 200));
                    Core.putText(mRgba, Double.toString(resExample.getDistFarthestImage() / 100000).substring(0, 6),
                            new Point(355, mRgba.height() - 30), Core.FONT_HERSHEY_PLAIN, 2,
                            new Scalar(250, 250, 250, 200));
                    //Num images and threshold
                    Core.putText(mRgba, "Images:" + savedFaces, new Point(15, mRgba.height() - 5),
                            Core.FONT_HERSHEY_PLAIN, 2, new Scalar(250, 250, 250, 200));
                    Core.putText(mRgba,
                            "Th:" + Double.toString(resExample.getThreshold() / 100000).substring(0,
                                    Math.min(6, Double.toString(resExample.getThreshold() / 100000).length())),
                            new Point(240, mRgba.height() - 5), Core.FONT_HERSHEY_PLAIN, 2,
                            new Scalar(250, 250, 250, 200));
                } else {
                    Core.rectangle(mRgba, new Point(0, mRgba.height() - 30), new Point(200, mRgba.height()),
                            new Scalar(50, 50, 50, 50), Core.FILLED);
                    Core.putText(mRgba, "Images:" + savedFaces, new Point(15, mRgba.height() - 5),
                            Core.FONT_HERSHEY_PLAIN, 2, new Scalar(250, 250, 250, 200));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        Bitmap bmp = Bitmap.createBitmap(mRgba.cols(), mRgba.rows(), Bitmap.Config.ARGB_8888);

        if (Utils.matToBitmap(mRgba, bmp))
            return bmp;

        bmp.recycle();
        return null;
    }

    private String findName(int res) {
        String nm = Integer.toString(res);
        /*try{
           nm = KeyFaceActivity.allnames.get(res);
           if(nm.isEmpty() || nm == null)
        return "> Without name ("+res+")";
        }
        catch (IndexOutOfBoundsException e) {
           System.err.println("Failed in the array of names");
           return "> Without name ("+res+")";
        }*/
        return "> " + nm;
    }

    @Override
    public void run() {
        super.run();
        Log.i(tag, "Running thread");
        synchronized (this) {
            if (mRgba != null)
                mRgba.release();
            if (mGray != null)
                mGray.release();

            mRgba = null;
            mGray = null;
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder _holder, int format, int width, int height) {
        super.surfaceChanged(_holder, format, width, height);
        Log.i(tag, "Surfance changed");
        synchronized (this) {
            mGray = new Mat();
            mRgba = new Mat();
        }
    }

    private void addFaceToDB(Mat mRGBA, Rect r, int numFaces) {
        //Code to put the face in a bitmap and save it in the phone memory
        Mat aux = mRGBA.submat(r);
        int saveSize = new Integer(KeyFaceActivity.prefs.getString("savedFaceSize", "200"));
        //Mat aux2 = new Mat(new Size(saveSize, saveSize), aux.type());
        Imgproc.resize(aux, aux, new Size(saveSize, saveSize));
        final Bitmap bm = Bitmap.createBitmap(saveSize, saveSize, Bitmap.Config.ARGB_8888);
        Imgproc.cvtColor(aux, aux, Imgproc.COLOR_GRAY2RGBA, 4);
        Utils.matToBitmap(aux, bm);

        try {
            //Check if our folder exists (where all the photos are)
            File directory = new File(Environment.getExternalStorageDirectory(),
                    KeyFaceActivity.globalappcontext.getString(R.string.app_dir));
            if (!directory.exists() && !directory.mkdirs()) {
                throw new IOException("Path to app directory could not be opened or created.");
            }
            //save image
            String lfile = Environment.getExternalStorageDirectory().getPath() + "/"
                    + KeyFaceActivity.globalappcontext.getString(R.string.app_dir) + "/Face" + numFaces + ".png";
            OutputStream out = new FileOutputStream(lfile);
            bm.compress(Bitmap.CompressFormat.PNG, 100, out);
            out.flush();
            out.close();
        } catch (IOException e) {
            System.err.println(e.getMessage());
        }
    }

}