Android 人脸特征点检测(主动形状模型) ASM Demo (Active Shape Model on Android)

目前Android平台上进行人脸特征识别非常火爆,本人研究生期间一直从事人脸特征的处理,所以曾经用过一段ASM(主动形状模型)提取人脸基础特征点,所以这里采用JNI的方式将ASM在Android平台上进行了实现,同时在本应用实例中,给出了几个其他的图像处理的示例。

由于ASM (主动形状模型,Active Shape Model)的核心算法比较复杂,所以这里不进行算法介绍,我之前写过一篇详细的算法介绍和公式推导,有兴趣的朋友可以参考下面的连接:

ASM(主动形状模型)算法详解

接下来介绍本应用的实现。

首先,给出本应用的项目源码:

Android ASM Demo

在这个项目源码的README中详细介绍了怎么配置运行时环境,请仔细阅读。

本项目即用到了Android JNI开发,又用到了Opencv4Android,所以,配置起来还是很复杂的。Android JNI开发配置请参考:Android JNI,Android 上使用Opencv请参考:Android Opencv

整个应用的代码比较多,所以如果想很好的了解项目原理,最好还是将代码下载下来仔细看看。

首先给出本地cpp代码,下面的本地cpp代码负责调用stasm提供的c语言接口:

#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <android/log.h>

#include <opencv2/opencv.hpp>
#include "./stasm/stasm_lib.h"

using namespace cv;
using namespace std;

CascadeClassifier cascade;
bool init = false;
const String APP_DIR = "/data/data/com.example.asm/app_data/";

extern "C" {
/*
 * do Canny edge detect
 */
JNIEXPORT void JNICALL Java_com_example_asm_NativeCode_DoCanny(JNIEnv* env,
        jobject obj, jlong matSrc, jlong matDst, jdouble threshold1 = 50,
        jdouble threshold2 = 150, jint aperatureSize = 3) {

    Mat * img = (Mat *) matSrc;
    Mat * dst = (Mat *) matDst;
    cvtColor(*img, *dst, COLOR_BGR2GRAY);
    Canny(*img, *dst, threshold1, threshold2, aperatureSize);
}

/*
 * face detection
 * matDst: face region
 * scaleFactor = 1.1
 * minNeighbors = 2
 * minSize = 30 * 30
 */
JNIEXPORT void JNICALL Java_com_example_asm_NativeCode_FaceDetect(JNIEnv* env,
        jobject obj, jlong matSrc, jlong matDst, jdouble scaleFactor, jint minNeighbors, jint minSize) {

    Mat * src = (Mat *) matSrc;
    Mat * dst = (Mat *) matDst;

    float factor = 0.3;
    Mat img;
    resize(*src, img, Size((*src).cols * factor, (*src).rows * factor));

    String cascadeFile = APP_DIR + "haarcascade_frontalface_alt2.xml";

    if (!init) {
        cascade.load(cascadeFile);
        init = true;
    }

    if (cascade.empty() != true) {
        vector<Rect> faces;
        cascade.detectMultiScale(img, faces, scaleFactor, minNeighbors, 0
                | CV_HAAR_FIND_BIGGEST_OBJECT
                | CV_HAAR_DO_ROUGH_SEARCH
                | CV_HAAR_SCALE_IMAGE, Size(minSize, minSize));

        for (int i = 0; i < faces.size(); i++) {
            Rect rect = faces[i];
            rect.x /= factor;
            rect.y /= factor;
            rect.width /= factor;
            rect.height /= factor;

            if (i == 0) {
                (*src)(rect).copyTo(*dst);
            }

            rectangle(*src, rect.tl(), rect.br(), Scalar(0, 255, 0, 255), 3);
        }
    }
}

/*
 *  do ASM
 *  error code:
 *  -1: illegal input Mat
 *  -2: ASM initialize error
 *  -3: no face detected
 */
JNIEXPORT jintArray JNICALL Java_com_example_asm_NativeCode_FindFaceLandmarks(
        JNIEnv* env, jobject, jlong matAddr, jfloat ratioW, jfloat ratioH) {
    const char * PATH = APP_DIR.c_str();

    clock_t StartTime = clock();
    jintArray arr = env->NewIntArray(2 * stasm_NLANDMARKS);
    jint *out = env->GetIntArrayElements(arr, 0);

    Mat img = *(Mat *) matAddr;
    cvtColor(img, img, COLOR_BGR2GRAY);

    if (!img.data) {
        out[0] = -1; // error code: -1(illegal input Mat)
        out[1] = -1;
        img.release();
        env->ReleaseIntArrayElements(arr, out, 0);
        return arr;
    }

    int foundface;
    float landmarks[2 * stasm_NLANDMARKS]; // x,y coords

    if (!stasm_search_single(&foundface, landmarks, (const char*) img.data,
            img.cols, img.rows, " ", PATH)) {
        out[0] = -2; // error code: -2(ASM initialize failed)
        out[1] = -2;
        img.release();
        env->ReleaseIntArrayElements(arr, out, 0);
        return arr;
    }

    if (!foundface) {
        out[0] = -3; // error code: -3(no face found)
        out[1] = -3;
        img.release();
        env->ReleaseIntArrayElements(arr, out, 0);
        return arr;
    } else {
        for (int i = 0; i < stasm_NLANDMARKS; i++) {
            out[2 * i] = cvRound(landmarks[2 * i] * ratioW);
            out[2 * i + 1] = cvRound(landmarks[2 * i + 1] * ratioH);
        }
    }
    double TotalAsmTime = double(clock() - StartTime) / CLOCKS_PER_SEC;
    __android_log_print(ANDROID_LOG_INFO, "com.example.asm.native",
            "running in native code, \nStasm Ver:%s Img:%dx%d ---> Time:%.3f secs.", stasm_VERSION,
            img.cols, img.rows, TotalAsmTime);

    img.release();
    env->ReleaseIntArrayElements(arr, out, 0);
    return arr;
}
}

stasm代码比较多,这里不具体给出,这里特别给出一下Android.mk这个Android平台JNI代码的makefile

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
OPENCV_CAMERA_MODULES:=off
OPENCV_INSTALL_MODULES:=on
OPENCV_LIB_TYPE:=STATIC
ifeq ("$(wildcard $(OPENCV_MK_PATH))","")
#try to load OpenCV.mk from default install location
include /home/wesong/software/OpenCV-2.4.10-android-sdk/sdk/native/jni/OpenCV.mk
else
include $(OPENCV_MK_PATH)
endif
LOCAL_MODULE    := Native

FILE_LIST := $(wildcard $(LOCAL_PATH)/stasm/*.cpp)  $(wildcard $(LOCAL_PATH)/stasm/MOD_1/*.cpp)

LOCAL_SRC_FILES := Native.cpp $(FILE_LIST:$(LOCAL_PATH)/%=%)

LOCAL_LDLIBS +=  -llog -ldl
include $(BUILD_SHARED_LIBRARY)

# other library
include $(CLEAR_VARS)
LOCAL_MODULE := opencv_java-prebuild
LOCAL_SRC_FILES := libopencv_java.so
include $(PREBUILT_SHARED_LIBRARY)

需要特别注意: NDK在Ubuntu平台下build代码时会自动删除已经存在了的动态链接库文件,因为我们需要在Android项目中引用OpenCV4Android提供的libopencv_java.so这个链接库,然而每次build JNI代码的时候NDK都会把这个.so文件删了,所以,需要用一个小trick,就是上面的Android.mk文件中最后一部分,采用prebuild的libopencv_java.so

这个地方当时迷糊了我很久,并且浪费了很多时间进行处理,这个现象在Windows上是不存在的。WTF!

然后是Android中java代码对Native JNI code的调用

package com.example.asm;

public class NativeCode {
    static {
        System.loadLibrary("Native");
    }

    /*
     * Canny edge detect
     * threshold1 = 50
     * threshold2 = 150
     * aperatureSize = 3
     */
    public static native void DoCanny(long matAddr_src, long matAddr_dst, double threshold1,
            double threshold2, int aperatureSize);

    /*
     * do face detect
     * scaleFactor = 1.1
     * minNeighbors = 2
     * minSize = 30 (30 * 30)
     */
    public static native void FaceDetect(long matAddr_src, long matAddr_dst,
            double scaleFactor, int minNeighbors, int minSize);

    /*
     * do ASM
     * find landmarks
     */
    public static native int[] FindFaceLandmarks(long matAddr, float ratioW, float ratioH);
}

然后就是主程序啦,主程序中有很多trick,目的是让Android能够高效的进行计算,因为ASM的计算量非常大,在Android平台上来说,需要消耗大量的时间,所以肯定不能放在UI线程中进行ASM计算。

本应用中通过AsyncTask来进行ASM特征点人脸定位

    private class AsyncAsm extends AsyncTask<Mat, Integer, List<Integer>> {
        private Context context;
        private Mat src;

        public AsyncAsm(Context context) {
            this.context = context;
        }

        @Override
        protected List<Integer> doInBackground(Mat... mat0) {
            List<Integer> list = new ArrayList<Integer>();
            Mat src = mat0[0];
            this.src = src;

            int[] points = NativeImageUtil.FindFaceLandmarks(src, 1, 1);
            for (int i = 0; i < points.length; i++) {
                list.add(points[i]);
            }

            return list;
        }

        // run on UI thread
        @Override
        protected void onPostExecute(List<Integer> list) {
            MainActivity.this.drawAsmPoints(this.src, list);
        }
    };

并且在主界面中,实时的进行人脸检测,这里人脸检测是通过开启一个新的线程进行的:

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        Log.d(TAG, "onPreviewFrame");

        Size size = camera.getParameters().getPreviewSize();
        Bitmap bitmap = ImageUtils.yuv2bitmap(data, size.width, size.height);
        Mat src = new Mat();
        Utils.bitmapToMat(bitmap, src);
        src.copyTo(currentFrame);

        Log.d("com.example.asm.CameraPreview", "image size: w: " + src.width()
                + " h: " + src.height());

        // do canny
        Mat canny_mat = new Mat();
        Imgproc.Canny(src, canny_mat, Params.CannyParams.THRESHOLD1,
                Params.CannyParams.THRESHOLD2);
        Bitmap canny_bitmap = ImageUtils.mat2Bitmap(canny_mat);

        iv_canny.setImageBitmap(canny_bitmap);

        // do face detect in Thread
        faceDetectThread.assignTask(Params.DO_FACE_DETECT, src);
    }

线程定义如下:

package com.example.asm;

import org.opencv.core.Mat;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;

public class FaceDetectThread extends Thread {
    private final String TAG = "com.example.asm.FaceDetectThread";

    private Context mContext;
    private Handler mHandler;

    private ImageUtils imageUtils;

    public FaceDetectThread(Context context) {
        mContext = context;
        imageUtils = new ImageUtils(context);
    }

    public void assignTask(int id, Mat src) {

        // do face detect
        if (id == Params.DO_FACE_DETECT) {
            Message msg = new Message();
            msg.what = Params.DO_FACE_DETECT;
            msg.obj = src;
            this.mHandler.sendMessage(msg);
        }
    }

    @Override
    public void run() {
        Looper.prepare();
        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == Params.DO_FACE_DETECT) {
                    Mat detected = new Mat();
                    Mat face = new Mat();
                    Mat src = (Mat) msg.obj;
                    detected = imageUtils.detectFacesAndExtractFace(src, face);

                    Message uiMsg = new Message();
                    uiMsg.what = Params.FACE_DETECT_DONE;
                    uiMsg.obj = detected;
                    // send Message to UI
                    ((MainActivity) mContext).mHandler.sendMessage(uiMsg);
                }
            }
        };
        Looper.loop();
    }
}

貌似代码有点多,所以,还是请看源代码吧。

下面给出几个系统的应用截图,由于本人太屌丝,所以用的红米1S,性能不是很好,请见谅。。。

同时感谢Google提供赫本照片,再次申明文明转载,MD.

应用启动之后:

分为四个主窗口,第一个是摄像头预览,第二个是人脸检测,第三个是Canny边缘检测,最后一个是ASM计算,因为ASM计算比较耗时,所以提供了一个按钮对最新的人脸计算ASM.

计算ASM以后:

然后点击第四个区域可以进行ASM特征点的图片查看:

第二个人脸检测窗口点击以后会进行一个人脸检测的Activity:

点击第三个窗口可以进入Canny边缘检测的Activity:

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-18 18:29:34

Android 人脸特征点检测(主动形状模型) ASM Demo (Active Shape Model on Android)的相关文章

face landmark 人脸特征点检测

1.ASM&AAM算法 ASM(Active Shape Model)算法介绍:http://blog.csdn.net/carson2005/article/details/8194317 AAM(Active Appearance Model)算法介绍:http://blog.csdn.net/carson2005/article/details/8196996 AAM(Active Appreance Model)算法用于人脸识别总结:http://blog.csdn.net/colour

人脸特征点定位与检测

前两周研究了一点关于人脸表情识别的文章,很多方法都是基于在提取人脸特征点的基础上完成的,然后利用网格模型或者形变模型对特征点进行分析和检测以及分类的方法实现对人脸表情的识别,可以看出人脸关键点(特征点)的提取与定位在人脸表情识别占有很重要的地位,决定画1-2周时间研读一些人脸关键点提取的文章,本周主要调研了人脸特征点检测.定位和人脸校准的算法: 人脸特征点检测的方法主要看了ASM(Active Shape Model)算法,是基于点分布模型PDM的算法,在近几年的CVPR很多文章有介绍,主要也是

Active Contour Model(主动轮廓线模型)

图像分割之(五)活动轮廓模型之Snake模型简介 [email protected] http://blog.csdn.net/zouxy09 在“图像分割之(一)概述”中咱们简单了解了目前主流的图像分割方法.下面咱们主要学习下基于能量泛函的分割方法.这里学习下Snake模型简单的知识,Level Set(水平集)模型会在后面的博文中说到. 基于能量泛函的分割方法: 该类方法主要指的是活动轮廓模型(active contour model)以及在其基础上发展出来的算法,其基本思想是使用连续曲线来

切割图像(五)主动轮廓模型Snake简要模型

切割图像(五)主动轮廓模型Snake简要模型 [email protected] http://blog.csdn.net/zouxy09 在"图像切割之(一)概述"中咱们简单了解了眼下主流的图像切割方法.以下咱们主要学习下基于能量泛函的切割方法.这里学习下Snake模型简单的知识,Level Set(水平集)模型会在后面的博文中说到. 基于能量泛函的切割方法: 该类方法主要指的是活动轮廓模型(active contour model)以及在其基础上发展出来的算法,其基本思想是使用连续

人脸识别完整项目实战(14):实时人脸特征点标定程序设计

一.前言 本文是<人脸识别完整项目实战>系列博文第14章<实时人脸特征点标定程序设计>,本章内容详细介绍Win10 环境下,基于Visual Studio 2015 + Opencv + Dlib开发环境,如何实现实时视频流人脸特征点标定程序的设计.本文内容已经同步录制成视频课程,课程地址:<人脸识别完整项目实战> 二.正文 2.1 界面设计 人脸特征点标定程序沿用之前的界面设计,新增人脸特征点标定按钮,界面设计如下图所示: 2.2 执行结果 人脸特征点标定程序运行后,

人脸识别系列之人脸检测--训练基于肤色特征的检测

前言: 基于特征的方法是利用人脸的先验知识导出的规则进行人脸检测. 一般来说,常用的特征包括人脸和人脸器官典型的边缘和形状特征(如人脸轮廓.虹膜轮廓.嘴唇轮廓等).纹理特征(纹理是在图上表现为灰度或颜色分布的某种规律性,这种规律性在不同类别的纹理中有其不同特点,人脸有其特定的纹理特征).颜色特征(人脸肤色特征,目前主要有RGB,HSV,YCbCr,YIQ,HIS等彩色空间模型被用来表示人脸的肤色,从而进行基于颜色信息的人脸检测方法的研究). 人脸检测的方法: 基于规则/知识方法 – 人脸模式的变

主动轮廓模型(重点)

V={v1,v2,...,vL}是边界上的点.其中vi=(xi,yi). 能量函数: 内部能量函数: Eint(vi)是依赖于轮廓形状的能量函数. 推动主动轮廓形状的改变并保持轮廓上点间的距离. Econ:连续能量(切向力),迫使不封闭的曲线变成直线而封闭的曲线变成圆环. Ebal  :膨胀能量(法向力),强制轮廓在没有外来影响的情况下扩展或收缩. 外部能量函数:Eext(vi)是依赖于图象性质的能量函数. 将变形模板向感兴趣的特征(如边缘)吸引构建能量函数:考虑目标的尺寸和形状. Emag:

第二十五节,目标定位、特征点检测依据目标检测

一 目标定位 对象检测,它是计算机视觉领域中一个新兴的应用方向,相比前两年,它的性能越来越好.在构建对象检测之前,我们先了解一下对象定位,首先我们看看它的定义. 图片分类任务我们已经熟悉了,就是算法遍历图片,判断其中的对象是不是汽车,这就是图片分类.这节我们要学习构建神经网络的另一个问题,即定位分类问题.这意味着,我们不仅要用算法判断图片中是不是一辆汽车,还要在图片中标记出它的位置,用边框或红色方框把汽车圈起来, 这就是定位分类问题. 其中"定位"的意思是判断汽车在图片中的具体位置.这

Android 人脸识别源码APP后台接口设计

Android softboy人脸识别源码APP后台接口设计,这个是最近开发的一个人脸识别人脸系统框架,系统内容比较复杂.这里简化了主要的接口与数据,然后结合 softboy人脸识别app,就可以体验极速的人脸识别考勤体验. 这个离线app支持上传考勤记录,下载人脸数据进行离线人脸识别和活体检测,有限规避相片打卡视频欺骗等. 这个APP的下载体验地址https://pan.baidu.com/s/1i5oXoJ7 接下来看一下接口功能设计,还原提意见哦.慢慢的福利,正在做的朋友可以直接下载参考.