人脸活体识别4:Android实现人脸眨眼 张嘴 点头 摇头识别(可实时检测)

发布于:2025-07-03 ⋅ 阅读:(43) ⋅ 点赞:(0)

人脸活体识别4:Android实现人脸眨眼 张嘴 点头 摇头识别(可实时检测)

目录

人脸活体识别4:Android实现人脸眨眼 张嘴 点头 摇头识别(可实时检测)

1. 前言

2.人脸活体识别方法

(1)基于人脸动作的检测​​

(2)​​基于红外的活体识别​​

(3)基于深度的活体识别​​

 3.人脸检测模型

 4. 活体识别模型

(1) 将Pytorch模型转换ONNX模型

(2) 将Pytorch模型转换为NCNN模型

5. 活体识别Android部署

(1) Android部署NCNN模型

(2) Android编译异常解决方法

6.活体识别效果(Android版本)

7. 项目Android源码下载


1. 前言

人脸活体识别技术是确保人脸识别系统安全性的关键,主要用于区分真实人脸与伪造攻击(如照片、视频、3D面具等)。本项目基于深度学习技术,构建了一套高鲁棒性的面部活体检测系统,可精准识别眨眼(闭眼)、张嘴、点头(低头)、摇头(侧脸)等生物特征动作,可有效防范照片、视频、3D面具等伪造攻击等多种场景,​​可应用于金融支付、远程身份核验(如银行开户)等场景。

整套项目分为人脸活体识别数据集说明,模型开发和训练,模型边缘侧部署C++/Android等多个章节,本篇是项目《​​​​人脸活体识别》系列文章之《Android实现人脸眨眼 张嘴 点头 摇头识别》;为了方便后续模型工程化和Android平台部署,项目对活体识别模型进行轻量化,并提供Python/C++/Android多个版本;

整套活体识别系统,在普通Android手机上可以达到实时的检测效果,CPU(4线程)约40ms左右,GPU约30ms左右 ,基本满足业务的性能需求。准确率还挺高的,采用轻量级mobilenet_v2模型的人脸活体识别准确率也可以高达99.9661%左右,满足业务性能需求。

模型 input size Test准确率
Mobilenet 112×112 99.9661
Resnet18 112×112 99.9322
MobileVit 112×112 99.9322

     

【尊重原创,转载请注明出处】https://blog.csdn.net/guyuealian/article/details/148774240

 【Android APP Demo体验】https://download.csdn.net/download/guyuealian/91133467


 更多人脸识别和活体识别文章,请参考:

  1. 人脸活体识别1:眨眼 张嘴 点头 摇头人脸数据集
  2. 人脸活体识别2:Pytorch实现人脸眨眼 张嘴 点头 摇头识别(含训练代码和数据集)
  3. 人脸活体识别3:C/C++实现人脸眨眼 张嘴 点头 摇头识别(可实时检测)
  4. 人脸活体识别4:Android实现人脸眨眼 张嘴 点头 摇头识别(可实时检测)
  5. 人脸识别2:InsightFace实现人脸识别Face Recognition(含源码下载)
  6. 人脸识别3:C/C++ InsightFace实现人脸识别Face Recognition(含源码)
  7. 人脸识别4:Android InsightFace实现人脸识别Face Recognition(含源码)

2.人脸活体识别方法

人脸活体识别的方法有很多,如基于人脸动作的活体识别​,基于红外的活体识别,基于深度的活体识别等等方法。

(1)基于人脸动作的检测​​

    要求用户配合完成随机指令动作(如眨眼、点头、张嘴、摇头等),通过计算机视觉算法(如光流法、关键点跟踪)分析动作的自然性和时序连贯性。

优点​​:实现简单,能有效抵御照片和静态视频攻击。​​

缺点​​:依赖用户配合,体验较差;可能被高仿动态视频(如Deepfake)欺骗。

(2)​​基于红外的活体识别​​

​​     利用红外摄像头捕捉人脸的红外反射特性或热辐射分布,如采用红外光谱分析​​,活体皮肤对特定波长红外光的吸收/反射模式与非活体不同。​​

优点​​:无需用户配合,可抵御照片、视频及部分3D面具攻击。

缺点​​:设备成本较高;受环境温度影响(如低温可能降低检测精度)。

(3)基于深度的活体识别​​

    通过3D深度摄像头(如结构光、ToF)获取人脸的三维几何信息(如鼻梁高度、曲面曲率),非活体(照片、屏幕)缺乏真实的深度结构。​​

优点​​:防御能力最强,可识别高级3D头套攻击。

​​缺点​​:硬件成本高,需专用3D传感器。

本项目实现方案是采用基于人脸动作的活体识别方法,即先采用通用的人脸检测模型,进行人脸检测定位人脸区域,然后按照一定规则裁剪人脸检测区域,再训练一个人脸活体识别分类器,完成人脸活体识别的任务。人脸动作主要包含:眨眼(闭眼)、张嘴、点头(低头)、摇头(侧脸)。


 3.人脸检测模型

本项目人脸检测训练代码请参考:https://github.com/Linzaer/Ultra-Light-Fast-Generic-Face-Detector-1MB 

这是一个基于SSD改进且轻量化后人脸检测模型,很slim,整个模型仅仅1.7M左右,在普通Android手机都可以实时检测。人脸检测方法在网上有一大堆现成的方法可以使用,完全可以不局限我这个方法。

当然可以基于YOLOv5训练一个人脸检测模型:人脸检测和行人检测2:YOLOv5实现人脸检测和行人检测(含数据集和训练代码)


 4. 活体识别模型

关于人脸活体识别模型训练,请参考《人脸活体识别2:Pytorch实现人脸眨眼 张嘴 点头 摇头识别(含训练代码和数据集)

整套活体识别系统,在NCNN加速下,可以达到实时的检测效果,基本满足业务的性能需求。下表格给出MobilenetResnet18MobileVit模型识别的准确率:

模型 input size Test准确率
Mobilenet 112×112 99.9661
Resnet18 112×112 99.9322
MobileVit 112×112 99.9322

模型训练完成后,需要将Pytorch模型转换为NCNN格式,转换方法请参考如下

考虑到端上硬件处理性能比较弱鸡,项目C++和Android部署仅使用Mobilenet模型。

(1) 将Pytorch模型转换ONNX模型

项目Python源码提供了转换工具,可实现将Pytorch的模型转换为ONNX模型文件,且文件会默认保存在Pytorch的模型文件同一目录下。

python libs/convertor/convert2onnx.py

(2) 将Pytorch模型转换为NCNN模型

你也可以一步到位,使用PNNX工具,直接将Pytorch模型直接转换为NCNN文件

python libs/convertor/convert2ncnn.py

NCNN转换工具说明,请参考:


5. 活体识别Android部署

项目实现了Android版本的活体识别,模型部署框架采用NCNN,支持多线程CPU和GPU加速推理,在普通手机上可以实时处理。

(1) Android部署NCNN模型

模型推理采用NCNN,图像处理和Android核心算法部分均采用C++实现,上层Java通过JNI接口调用C++算法。

如果你想在这个Android Demo部署你自己训练的模型,你可将训练好的Pytorch模型转换ONNX ,再转换成NCNN模型,然后把原始的模型替换成你自己的TNCNN模型即可。

package com.cv.dl.model;

import android.graphics.Bitmap;

public class Detector {

    static {
        System.loadLibrary("cvdl-lib");
    }


    /***
     * 初始化检测模型
     * @param root:模型文件的根目录,放在assets文件夹下
     * @param det_model: 检测模型(不含后缀名)
     * @param rec_model: 识别模型(不含后缀名)
     * @param model_type:模型类型
     * @param num_thread:开启线程数
     * @param useGPU:是否开启GPU进行加速
     */
    public static native void init(String root, String det_model, String rec_model, int model_type, int num_thread, boolean useGPU);

    /***
     * 进行检测(用于静态图)
     * @param bitmap 图像(bitmap),ARGB_8888格式
     * @param score_thresh:置信度阈值
     * @param iou_thresh:  IOU阈值
     * @return
     */
    public static native FrameInfo[] detect(Bitmap bitmap, float score_thresh, float iou_thresh);

}

JNI接口

#include <jni.h>
#include <string>
#include <fstream>
#include "src/classifier.h"
#include "src/object_detection.h"
#include "src/Types.h"
#include "debug.h"
#include "android_utils.h"
#include "opencv2/opencv.hpp"
#include "file_utils.h"

using namespace dl;
using namespace vision;

static ObjectDetection *detector = nullptr;
static Classifier *recognize = nullptr;


JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    return JNI_VERSION_1_6;
}

JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *reserved) {

}


extern "C"
JNIEXPORT void JNICALL
Java_com_cv_dl_model_Detector_init(JNIEnv *env,
                                   jclass clazz,
                                   jstring root,
                                   jstring det_model,
                                   jstring rec_model,
                                   jint model_type,
                                   jint num_thread,
                                   jboolean use_gpu) {
    if (detector != nullptr) {
        delete detector;
        detector = nullptr;
    }
    std::string parent = env->GetStringUTFChars(root, 0);
    std::string det_model_ = env->GetStringUTFChars(det_model, 0);
    std::string rec_model_ = env->GetStringUTFChars(rec_model, 0);
    string det_pam_file = path_joint(parent, det_model_ + ".param");
    string det_bin_file = path_joint(parent, det_model_ + ".bin");
    string rec_pam_file = path_joint(parent, rec_model_ + ".param");
    string rec_bin_file = path_joint(parent, rec_model_ + ".bin");

    DeviceType device = use_gpu ? GPU : CPU;
    LOGW("parent         : %s", parent.c_str());
    LOGW("useGPU         : %d", use_gpu);
    LOGW("device_type    : %d", device);
    LOGW("model_type     : %d", model_type);
    LOGW("num_thread     : %d", num_thread);
    DetParam det_param = MODEL_TYPE[model_type];//模型参数
    detector = new ObjectDetection(det_bin_file, det_pam_file, det_param, num_thread, device, -1);
    RecParam rec_param = REC_MODEL;
    recognize = new Classifier(rec_bin_file, rec_pam_file, rec_param, num_thread, device);


}

extern "C"
JNIEXPORT jobjectArray JNICALL
Java_com_cv_dl_model_Detector_detect(JNIEnv *env, jclass clazz, jobject bitmap,
                                     jfloat score_thresh, jfloat iou_thresh) {
    cv::Mat bgr;
    BitmapToMatrix(env, bitmap, bgr);
    int src_h = bgr.rows;
    int src_w = bgr.cols;
    // 检测区域为整张图片的大小
    FrameInfo resultInfo;
    // 开始检测
    detector->detect(bgr, &resultInfo, score_thresh, iou_thresh);
    recognize->detect(bgr, &resultInfo);

    int nums = resultInfo.info.size();
    LOGW("image is (%d,%d,%d),score_thresh=%3.3f, iou_thresh=%3.3f, object nums: %d\n", bgr.cols,
         bgr.rows, bgr.channels(), score_thresh, iou_thresh, nums);
    auto BoxInfo = env->FindClass("com/cv/dl/model/FrameInfo");
    auto init_id = env->GetMethodID(BoxInfo, "<init>", "()V");
    auto box_id = env->GetMethodID(BoxInfo, "addBox", "(FFFFIFLjava/lang/String;)V");
    auto ky_id = env->GetMethodID(BoxInfo, "addKeyPoint", "(FFF)V");
    auto po_id = env->GetMethodID(BoxInfo, "addPolygon", "(FFFI)V");
    jobjectArray ret = env->NewObjectArray(resultInfo.info.size(), BoxInfo, nullptr);
    for (int i = 0; i < nums; ++i) {
        auto info = resultInfo.info[i];
        env->PushLocalFrame(1);
        //jobject obj = env->AllocObject(BoxInfo);
        jobject obj = env->NewObject(BoxInfo, init_id);
        // set bbox
        //LOGW("rect:[%f,%f,%f,%f] label:%d,score:%f \n", info.rect.x,info.rect.y, info.rect.w, info.rect.h, 0, 1.0f);
        jstring name = env->NewStringUTF(info.name.c_str());
        env->CallVoidMethod(obj, box_id, info.x1, info.y1, info.x2 - info.x1, info.y2 - info.y1,
                            info.category.label, info.category.score, name);
        // set keypoint
        for (const auto &kps: info.points) {
            //LOGW("point:[%f,%f] score:%f \n", lm.point.x, lm.point.y, lm.score);
            env->CallVoidMethod(obj, ky_id, (float) kps.x, (float) kps.y, 1.0f);
        }
        // set contours
        // LOGW("jni contours.size=%d\n", info.segInfo.contours.size());
        for (int group = 0; group < info.segInfo.contours.size(); ++group) {
            for (const auto &kps: info.segInfo.contours.at(group)) {
                //LOGW("jni contours group=%d,point:[%f,%f]\n", group, (float)kps.x, (float)kps.y);
                env->CallVoidMethod(obj, po_id, (float) kps.x, (float) kps.y, 1.0f, group);
            }
        }
        obj = env->PopLocalFrame(obj);
        env->SetObjectArrayElement(ret, i, obj);
    }
    return ret;
}

(2) Android编译异常解决方法

参考解决方法:Android项目常见问题解决办法


6.活体识别效果(Android版本)

活体识别Android APP在普通Android手机上可以达到实时的检测和识别效果,CPU(4线程)约40ms左右,GPU约30ms左右 ,基本满足业务的性能需求。

 【Android APP Demo体验】https://download.csdn.net/download/guyuealian/91133467

     


7. 项目Android源码下载

  【Android APP Demo体验】https://download.csdn.net/download/guyuealian/91133467

如需下载项目源码,请WX关注【AI吃大瓜】,回复【活体识别】即可下载

项目资源内容包含:

  1. 提供轻量化人脸检测模型
  2. 提供轻量化人脸活体识别模型(Mobilenet),支持眨眼(闭眼)、张嘴、点头(低头)、摇头(侧脸)动作识别,识别准确率可以高达99.9661%左右。
  3. 提供整套人脸活体识别Android Demo项目源码
  4. Android Demo源码可用于二次开发
  5. Android Demo在普通手机CPU/GPU上可以实时检测,CPU约40ms,GPU约30ms左右
  6. Android Demo支持图片、视频和摄像头测试
  7. 所有依赖库都已经配置好,可直接build运行,若运行出现异常,请参考Android项目常见问题解决办法

网站公告

今日签到

点亮在社区的每一天
去签到