309 lines
9.9 KiB
C++
309 lines
9.9 KiB
C++
/*
|
|
* Copyright (C) 2006 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
|
|
#include <utils/misc.h>
|
|
#include <utils/String8.h>
|
|
#include <utils/Log.h>
|
|
|
|
#include <android/bitmap.h>
|
|
|
|
#include "jni.h"
|
|
#include <nativehelper/JNIHelp.h>
|
|
|
|
using namespace android;
|
|
|
|
extern "C"
|
|
{
|
|
#include <fd_emb_sdk.h>
|
|
}
|
|
|
|
struct FaceData
|
|
{
|
|
float confidence;
|
|
float midpointx;
|
|
float midpointy;
|
|
float eyedist;
|
|
};
|
|
|
|
struct FaceOffsets
|
|
{
|
|
jfieldID confidence;
|
|
jfieldID midpointx;
|
|
jfieldID midpointy;
|
|
jfieldID eyedist;
|
|
jfieldID eulerx;
|
|
jfieldID eulery;
|
|
jfieldID eulerz;
|
|
} gFaceOffsets;
|
|
|
|
struct FaceDetectorOffsets
|
|
{
|
|
jfieldID fd;
|
|
jfieldID sdk;
|
|
jfieldID dcr;
|
|
jfieldID width;
|
|
jfieldID height;
|
|
jfieldID maxFaces;
|
|
jfieldID bwbuffer;
|
|
} gFaceDetectorOffsets;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static void getFaceData(btk_HDCR hdcr, FaceData* fdata)
|
|
{
|
|
btk_Node leftEye, rightEye;
|
|
|
|
btk_DCR_getNode(hdcr, 0, &leftEye);
|
|
btk_DCR_getNode(hdcr, 1, &rightEye);
|
|
|
|
fdata->eyedist = (float)(rightEye.x - leftEye.x) / (1 << 16);
|
|
fdata->midpointx = (float)(rightEye.x + leftEye.x) / (1 << 17);
|
|
fdata->midpointy = (float)(rightEye.y + leftEye.y) / (1 << 17);
|
|
fdata->confidence = (float)btk_DCR_confidence(hdcr) / (1 << 24);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static void doThrow(JNIEnv* env, const char* exc, const char* msg = NULL)
|
|
{
|
|
jclass npeClazz = env->FindClass(exc);
|
|
env->ThrowNew(npeClazz, msg);
|
|
}
|
|
|
|
static void
|
|
nativeClassInit
|
|
(JNIEnv *_env, jclass _this)
|
|
{
|
|
gFaceDetectorOffsets.fd = _env->GetFieldID(_this, "mFD", "J");
|
|
gFaceDetectorOffsets.sdk = _env->GetFieldID(_this, "mSDK", "J");
|
|
gFaceDetectorOffsets.dcr = _env->GetFieldID(_this, "mDCR", "J");
|
|
gFaceDetectorOffsets.width = _env->GetFieldID(_this, "mWidth", "I");
|
|
gFaceDetectorOffsets.height = _env->GetFieldID(_this, "mHeight", "I");
|
|
gFaceDetectorOffsets.maxFaces = _env->GetFieldID(_this, "mMaxFaces", "I");
|
|
gFaceDetectorOffsets.bwbuffer = _env->GetFieldID(_this, "mBWBuffer", "[B");
|
|
|
|
jclass faceClass = _env->FindClass("android/media/FaceDetector$Face");
|
|
gFaceOffsets.confidence = _env->GetFieldID(faceClass, "mConfidence", "F");
|
|
gFaceOffsets.midpointx = _env->GetFieldID(faceClass, "mMidPointX", "F");
|
|
gFaceOffsets.midpointy = _env->GetFieldID(faceClass, "mMidPointY", "F");
|
|
gFaceOffsets.eyedist = _env->GetFieldID(faceClass, "mEyesDist", "F");
|
|
gFaceOffsets.eulerx = _env->GetFieldID(faceClass, "mPoseEulerX", "F");
|
|
gFaceOffsets.eulery = _env->GetFieldID(faceClass, "mPoseEulerY", "F");
|
|
gFaceOffsets.eulerz = _env->GetFieldID(faceClass, "mPoseEulerZ", "F");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static jint
|
|
initialize(JNIEnv *_env, jobject _this,
|
|
jint w, jint h, jint maxFaces)
|
|
{
|
|
// load the configuration file
|
|
const char* root = getenv("ANDROID_ROOT");
|
|
String8 path(root);
|
|
path.appendPath("usr/share/bmd/RFFstd_501.bmd");
|
|
// path.appendPath("usr/share/bmd/RFFspeed_501.bmd");
|
|
|
|
const int MAX_FILE_SIZE = 65536;
|
|
void* initData = malloc( MAX_FILE_SIZE ); /* enough to fit entire file */
|
|
int filedesc = open(path.string(), O_RDONLY);
|
|
int initDataSize = read(filedesc, initData, MAX_FILE_SIZE);
|
|
close(filedesc);
|
|
|
|
// --------------------------------------------------------------------
|
|
btk_HSDK sdk = NULL;
|
|
btk_SDKCreateParam sdkParam = btk_SDK_defaultParam();
|
|
sdkParam.fpMalloc = malloc;
|
|
sdkParam.fpFree = free;
|
|
sdkParam.maxImageWidth = w;
|
|
sdkParam.maxImageHeight = h;
|
|
|
|
btk_Status status = btk_SDK_create(&sdkParam, &sdk);
|
|
// make sure everything went well
|
|
if (status != btk_STATUS_OK) {
|
|
// XXX: be more precise about what went wrong
|
|
doThrow(_env, "java/lang/OutOfMemoryError", NULL);
|
|
return 0;
|
|
}
|
|
|
|
btk_HDCR dcr = NULL;
|
|
btk_DCRCreateParam dcrParam = btk_DCR_defaultParam();
|
|
btk_DCR_create( sdk, &dcrParam, &dcr );
|
|
|
|
btk_HFaceFinder fd = NULL;
|
|
btk_FaceFinderCreateParam fdParam = btk_FaceFinder_defaultParam();
|
|
fdParam.pModuleParam = initData;
|
|
fdParam.moduleParamSize = initDataSize;
|
|
fdParam.maxDetectableFaces = maxFaces;
|
|
status = btk_FaceFinder_create( sdk, &fdParam, &fd );
|
|
btk_FaceFinder_setRange(fd, 20, w/2); /* set eye distance range */
|
|
|
|
// make sure everything went well
|
|
if (status != btk_STATUS_OK) {
|
|
// XXX: be more precise about what went wrong
|
|
doThrow(_env, "java/lang/OutOfMemoryError", NULL);
|
|
return 0;
|
|
}
|
|
|
|
// free the configuration file
|
|
free(initData);
|
|
|
|
// initialize the java object
|
|
_env->SetLongField(_this, gFaceDetectorOffsets.fd, (jlong)fd);
|
|
_env->SetLongField(_this, gFaceDetectorOffsets.sdk, (jlong)sdk);
|
|
_env->SetLongField(_this, gFaceDetectorOffsets.dcr, (jlong)dcr);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
destroy(JNIEnv *_env, jobject _this)
|
|
{
|
|
btk_HFaceFinder hfd =
|
|
(btk_HFaceFinder)(_env->GetLongField(_this, gFaceDetectorOffsets.fd));
|
|
btk_FaceFinder_close( hfd );
|
|
|
|
btk_HDCR hdcr = (btk_HDCR)(_env->GetLongField(_this, gFaceDetectorOffsets.dcr));
|
|
btk_DCR_close( hdcr );
|
|
|
|
btk_HSDK hsdk = (btk_HSDK)(_env->GetLongField(_this, gFaceDetectorOffsets.sdk));
|
|
btk_SDK_close( hsdk );
|
|
}
|
|
|
|
static jint
|
|
detect(JNIEnv *_env, jobject _this,
|
|
jobject bitmap)
|
|
{
|
|
// get the fields we need
|
|
btk_HDCR hdcr = (btk_HDCR)(_env->GetLongField(_this, gFaceDetectorOffsets.dcr));
|
|
btk_HFaceFinder hfd =
|
|
(btk_HFaceFinder)(_env->GetLongField(_this, gFaceDetectorOffsets.fd));
|
|
u32 width = _env->GetIntField(_this, gFaceDetectorOffsets.width);
|
|
u32 height = _env->GetIntField(_this, gFaceDetectorOffsets.height);
|
|
|
|
jbyteArray bwbufferObject = (jbyteArray)
|
|
_env->GetObjectField(_this, gFaceDetectorOffsets.bwbuffer);
|
|
|
|
// get to our BW temporary buffer
|
|
jbyte* bwbuffer = _env->GetByteArrayElements(bwbufferObject, 0);
|
|
|
|
// convert the image to B/W
|
|
uint8_t* dst = (uint8_t*)bwbuffer;
|
|
|
|
uint16_t const* src;
|
|
AndroidBitmapInfo bitmapInfo;
|
|
AndroidBitmap_getInfo(_env, bitmap, &bitmapInfo);
|
|
AndroidBitmap_lockPixels(_env, bitmap, (void**) &src);
|
|
|
|
int wpr = bitmapInfo.stride / 2;
|
|
for (u32 y=0 ; y<height; y++) {
|
|
for (u32 x=0 ; x<width ; x++) {
|
|
uint16_t rgb = src[x];
|
|
int r = rgb >> 11;
|
|
int g2 = (rgb >> 5) & 0x3F;
|
|
int b = rgb & 0x1F;
|
|
// L coefficients 0.299 0.587 0.11
|
|
int L = (r<<1) + (g2<<1) + (g2>>1) + b;
|
|
*dst++ = L;
|
|
}
|
|
src += wpr;
|
|
}
|
|
|
|
// run detection
|
|
btk_DCR_assignGrayByteImage(hdcr, bwbuffer, width, height);
|
|
|
|
int numberOfFaces = 0;
|
|
if (btk_FaceFinder_putDCR(hfd, hdcr) == btk_STATUS_OK) {
|
|
numberOfFaces = btk_FaceFinder_faces(hfd);
|
|
} else {
|
|
ALOGE("ERROR: Return 0 faces because error exists in btk_FaceFinder_putDCR.\n");
|
|
}
|
|
|
|
// release the arrays we're using
|
|
AndroidBitmap_unlockPixels(_env, bitmap);
|
|
_env->ReleaseByteArrayElements(bwbufferObject, bwbuffer, 0);
|
|
return numberOfFaces;
|
|
}
|
|
|
|
static void
|
|
get_face(JNIEnv *_env, jobject _this,
|
|
jobject face, jint)
|
|
{
|
|
btk_HDCR hdcr = (btk_HDCR)(_env->GetLongField(_this, gFaceDetectorOffsets.dcr));
|
|
btk_HFaceFinder hfd =
|
|
(btk_HFaceFinder)(_env->GetLongField(_this, gFaceDetectorOffsets.fd));
|
|
|
|
FaceData faceData;
|
|
btk_FaceFinder_getDCR(hfd, hdcr);
|
|
getFaceData(hdcr, &faceData);
|
|
|
|
_env->SetFloatField(face, gFaceOffsets.confidence, faceData.confidence);
|
|
_env->SetFloatField(face, gFaceOffsets.midpointx, faceData.midpointx);
|
|
_env->SetFloatField(face, gFaceOffsets.midpointy, faceData.midpointy);
|
|
_env->SetFloatField(face, gFaceOffsets.eyedist, faceData.eyedist);
|
|
_env->SetFloatField(face, gFaceOffsets.eulerx, 0);
|
|
_env->SetFloatField(face, gFaceOffsets.eulery, 0);
|
|
_env->SetFloatField(face, gFaceOffsets.eulerz, 0);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static const char *classPathName = "android/media/FaceDetector";
|
|
|
|
static JNINativeMethod methods[] = {
|
|
{"nativeClassInit", "()V", (void*)nativeClassInit },
|
|
{"fft_initialize", "(III)I", (void*)initialize },
|
|
{"fft_detect", "(Landroid/graphics/Bitmap;)I", (void*)detect },
|
|
{"fft_get_face", "(Landroid/media/FaceDetector$Face;I)V",(void*)get_face },
|
|
{"fft_destroy", "()V", (void*)destroy },
|
|
};
|
|
|
|
int register_android_media_FaceDetector(JNIEnv *_env)
|
|
{
|
|
return jniRegisterNativeMethods(_env, classPathName, methods, NELEM(methods));
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
jint JNI_OnLoad(JavaVM* vm, void*)
|
|
{
|
|
JNIEnv* env = NULL;
|
|
jint result = -1;
|
|
|
|
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
|
|
ALOGE("ERROR: GetEnv failed\n");
|
|
goto bail;
|
|
}
|
|
assert(env != NULL);
|
|
|
|
if (register_android_media_FaceDetector(env) < 0) {
|
|
ALOGE("ERROR: MediaPlayer native registration failed\n");
|
|
goto bail;
|
|
}
|
|
|
|
/* success -- return valid version number */
|
|
result = JNI_VERSION_1_4;
|
|
|
|
bail:
|
|
return result;
|
|
}
|