1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.media;
18
19import android.graphics.Bitmap;
20import android.graphics.PointF;
21import android.util.Log;
22
23import java.lang.IllegalArgumentException;
24
25/**
26 * Identifies the faces of people in a
27 * {@link android.graphics.Bitmap} graphic object.
28 */
29public class FaceDetector {
30
31    /**
32     * A Face contains all the information identifying the location
33     * of a face in a bitmap.
34     */
35    public class Face {
36        /** The minimum confidence factor of good face recognition */
37        public static final float CONFIDENCE_THRESHOLD = 0.4f;
38        /** The x-axis Euler angle of a face. */
39        public static final int EULER_X = 0;
40        /** The y-axis Euler angle of a face. */
41        public static final int EULER_Y = 1;
42        /** The z-axis Euler angle of a face. */
43        public static final int EULER_Z = 2;
44
45        /**
46         * Returns a confidence factor between 0 and 1. This indicates how
47         * certain what has been found is actually a face. A confidence
48         * factor above 0.3 is usually good enough.
49         */
50        public float confidence() {
51            return mConfidence;
52        }
53        /**
54         * Sets the position of the mid-point between the eyes.
55         * @param point the PointF coordinates (float values) of the
56         *              face's mid-point
57         */
58        public void getMidPoint(PointF point) {
59            // don't return a PointF to avoid allocations
60            point.set(mMidPointX, mMidPointY);
61        }
62        /**
63         * Returns the distance between the eyes.
64         */
65        public float eyesDistance() {
66            return mEyesDist;
67        }
68        /**
69         * Returns the face's pose. That is, the rotations around either
70         * the X, Y or Z axis (the positions in 3-dimensional Euclidean space).
71         *
72         * @param euler the Euler axis to retrieve an angle from
73         *              (<var>EULER_X</var>, <var>EULER_Y</var> or
74         *              <var>EULER_Z</var>)
75         * @return the Euler angle of the of the face, for the given axis
76         */
77        public float pose(int euler) {
78            // don't use an array to avoid allocations
79            if (euler == EULER_X)
80                return mPoseEulerX;
81            else if (euler == EULER_Y)
82                return mPoseEulerY;
83            else if (euler == EULER_Z)
84                return mPoseEulerZ;
85           throw new IllegalArgumentException();
86        }
87
88        // private ctor, user not supposed to build this object
89        private Face() {
90        }
91        private float   mConfidence;
92        private float   mMidPointX;
93        private float   mMidPointY;
94        private float   mEyesDist;
95        private float   mPoseEulerX;
96        private float   mPoseEulerY;
97        private float   mPoseEulerZ;
98    }
99
100
101    /**
102     * Creates a FaceDetector, configured with the size of the images to
103     * be analysed and the maximum number of faces that can be detected.
104     * These parameters cannot be changed once the object is constructed.
105     * Note that the width of the image must be even.
106     *
107     * @param width  the width of the image
108     * @param height the height of the image
109     * @param maxFaces the maximum number of faces to identify
110     *
111     */
112    public FaceDetector(int width, int height, int maxFaces)
113    {
114        if (!sInitialized) {
115            return;
116        }
117        fft_initialize(width, height, maxFaces);
118        mWidth = width;
119        mHeight = height;
120        mMaxFaces = maxFaces;
121        mBWBuffer = new byte[width * height];
122    }
123
124    /**
125     * Finds all the faces found in a given {@link android.graphics.Bitmap}.
126     * The supplied array is populated with {@link FaceDetector.Face}s for each
127     * face found. The bitmap must be in 565 format (for now).
128     *
129     * @param bitmap the {@link android.graphics.Bitmap} graphic to be analyzed
130     * @param faces  an array in which to place all found
131     *               {@link FaceDetector.Face}s. The array must be sized equal
132     *               to the <var>maxFaces</var> value set at initialization
133     * @return the number of faces found
134     * @throws IllegalArgumentException if the Bitmap dimensions don't match
135     *               the dimensions defined at initialization or the given array
136     *               is not sized equal to the <var>maxFaces</var> value defined
137     *               at initialization
138     */
139    public int findFaces(Bitmap bitmap, Face[] faces)
140    {
141        if (!sInitialized) {
142            return 0;
143        }
144        if (bitmap.getWidth() != mWidth || bitmap.getHeight() != mHeight) {
145            throw new IllegalArgumentException(
146                    "bitmap size doesn't match initialization");
147        }
148        if (faces.length < mMaxFaces) {
149            throw new IllegalArgumentException(
150                    "faces[] smaller than maxFaces");
151        }
152
153        int numFaces = fft_detect(bitmap);
154        if (numFaces >= mMaxFaces)
155            numFaces = mMaxFaces;
156        for (int i=0 ; i<numFaces ; i++) {
157            if (faces[i] == null)
158                faces[i] = new Face();
159            fft_get_face(faces[i], i);
160        }
161        return numFaces;
162    }
163
164
165    /* no user serviceable parts here ... */
166    @Override
167    protected void finalize() throws Throwable {
168        fft_destroy();
169    }
170
171    /*
172     * We use a class initializer to allow the native code to cache some
173     * field offsets.
174     */
175    private static boolean sInitialized;
176    native private static void nativeClassInit();
177
178    static {
179        sInitialized = false;
180        try {
181            System.loadLibrary("FFTEm");
182            nativeClassInit();
183            sInitialized = true;
184        } catch (UnsatisfiedLinkError e) {
185            Log.d("FFTEm", "face detection library not found!");
186        }
187    }
188
189    native private int  fft_initialize(int width, int height, int maxFaces);
190    native private int  fft_detect(Bitmap bitmap);
191    native private void fft_get_face(Face face, int i);
192    native private void fft_destroy();
193
194    private int     mFD;
195    private int     mSDK;
196    private int     mDCR;
197    private int     mWidth;
198    private int     mHeight;
199    private int     mMaxFaces;
200    private byte    mBWBuffer[];
201}
202
203