关于人脸识别课程,不了解的可先看课程预告:重磅上线!免费带您从0到1手撸一款人脸识别SDK应用

关于人脸识别技术应用及核心技术简介,可以看之前发送的iOS课程第一篇文章:人脸识别技术应用及核心技术简介

接下来,我们将为您呈现 Android 人脸识别SDK的完整系列文章,为大家详细介绍人脸识别的核心技术及SDK开发过程,带您从0到1写一款Android版人脸识别SDK应用,Github 实战Demo地址在最后一篇文章。

本系列文章分为5篇,分别为:

  • 人脸识别SDK(Android)之设备相机画面采集

  • 人脸识别SDK(Android)之如何在Mac上构建Android NCNN库

  • 人脸识别SDK(Android)之人脸识别引擎代码(即本篇)

  • 人脸识别SDK(Android)之项目回顾总结

  • 人脸识别SDK之(Android)Github示例及集成详解 

核心NDK代码

在上一节课程中我们介绍了如何编译Android平台的NCNN库。本节课讲介绍如何通过NCNN识别人脸。

环境准备

OpenCV: 3.4.0 (图像处理,对图片进行裁剪)NDK: 3.10.2 (Android Studio)cmake: 21.1.6352462 (Android Studio)Android studio JDK: 11 (jdk 11)

OpenCV 可以从官方网站下载 https://opencv.org/releases/ , NDK 以及 cmake 打开 Android Studio的 Android SDK 面板-> SDK Tools进行下载如下图所示

图片

开发环境已经配置好了,我们先看下项目的的结构目录

图片

  • assets目录下的yolov5n-0.5.bin(模型权重)以及yolov5n-0.5.param(模型的结构信息) 人脸识别的模型文件。

  • cpp 目录下include 目录下的opencv以及opencv2是opencv相关的代码,include 目录下的其他头文件是编译ncnn库时生成的头文件,可以回顾下第三课的内容。

  • face_jni.cpp 提供识别人脸的c方法

  • yoloface.cpp以及yoloface.h 对图片进行人脸检测并提取人脸的五个主要关键点,双眼、鼻尖和两侧嘴角。

  • jniLibs 这里是ncnn编译后的静态库文件

在第一节课中我们说到在onPreviewFrame方法会返回相机采取的每一帧图片信息,我们须需要将获取到的图片信息进行人脸检测。

下面提供了CMakeLists.txt配置文件的说明以及对图片进行人脸检测的c代码。

CMakeLists.txt文件配置信息

#导入头文件目录include_directories(include        ../cpp/)#导入OpenCV的配置文件set(OpenCV_DIR /Users/admin/work/OpenCV-android-sdk/sdk/native/jni)find_package(OpenCV 3 REQUIRED)#source directory源文件目录file(GLOB FACE_SRC *.h        *.cpp)set(FACE_COMPILE_CODE ${FACE_SRC})#添加ncnn库add_library(libncnn STATIC IMPORTED )set_target_properties(libncnn        PROPERTIES IMPORTED_LOCATION        ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libncnn.a)#编译为动态库add_library(Face        SHARED        ${FACE_COMPILE_CODE})#添加工程所依赖的库find_library(log-lib log android)target_link_libraries(Face        libncnn        android        z        jnigraphics        ${OpenCV_LIBS}        ${log-lib})

具体的人脸识别检测代码如下​​​​​​​

extern "C"JNIEXPORT jobject JNICALLJava_com_ncnn_demo_detect_FaceDetect_faceDetectPoint(JNIEnv *env, jobject instance,jbyteArray yuv,jint imageWidth, jint imageHeight) {    int input_size = 640;    // letterbox pad to multiple of 32    int w = imageWidth;    int h = imageHeight;    float scale = 1.f;    if (w > h) {        scale = (float) input_size / w;        w = input_size;        h = h * scale;    } else {        scale = (float) input_size / h;        h = input_size;        w = w * scale;    }    //将yuv byte数组转为jbyte*    jbyte *yuvBytes = env->GetByteArrayElements(yuv, nullptr);    //jbyte* 强转char*    unsigned char *yuvChars = (unsigned char *) yuvBytes;    auto *faceImageCharDate = new unsigned char[(int) imageHeight * (int) imageWidth * 3];    //ncnn 将yuv 逐句转为rgb格式    ncnn::yuv420sp2rgb(yuvChars, imageWidth, imageHeight, faceImageCharDate);   //ncnn 将图像转为ncnn::Mat格式    ncnn::Mat ncnn_img;    ncnn_img = ncnn::Mat::from_pixels(faceImageCharDate, ncnn::Mat::PIXEL_RGB, imageWidth,                                      imageHeight);    ncnn::Mat in = ncnn::Mat::from_pixels_resize(faceImageCharDate, ncnn::Mat::PIXEL_RGB,                                                 imageWidth, imageHeight, w, h);    //finalBbox 人脸检测信息    std::vector<Object> finalBbox;    g_yoloface->detect(in, finalBbox, imageWidth, imageHeight, scale);    // 创建ArrayList<FaceInfo>    jclass faceListClass = env->FindClass("java/util/ArrayList");    jmethodID faceListConstructor = env->GetMethodID(faceListClass, "<init>", "()V");    jobject faceList = env->NewObject(faceListClass, faceListConstructor);    for (int i = 0; i < finalBbox.size(); i++) {        // 创建FaceArea对象        jclass faceAreaClass = env->FindClass("com/ncnn/demo/detect/FaceInfoList$FaceArea");        jmethodID faceAreaConstructor = env->GetMethodID(faceAreaClass, "<init>", "(IIII)V");        jobject faceArea = env->NewObject(faceAreaClass, faceAreaConstructor,                                          (int) finalBbox[i].rect.x, (int) finalBbox[i].rect.y,                                          (int) finalBbox[i].rect.width,                                          (int) finalBbox[i].rect.height);        // 创建ArrayList<FacePointInfo>(这里先创建一个空的,因为示例中没有FacePointInfo的详细构造)        jclass pointListClass = env->FindClass("java/util/ArrayList");        jmethodID pointListConstructor = env->GetMethodID(pointListClass, "<init>", "()V");        jobject pointList = env->NewObject(pointListClass, pointListConstructor);        for (int j = 0; j < finalBbox[i].pts.size(); j++) {            // 创建FacePointInfo对象            jclass facePointInfoClass = env->FindClass(                    "com/ncnn/demo/detect/FaceInfoList$FacePointInfo");            jmethodID facePointInfoConstructor = env->GetMethodID(facePointInfoClass, "<init>",                                                                  "(II)V");            jobject facePointInfo = env->NewObject(facePointInfoClass, facePointInfoConstructor,                                                   (int) finalBbox[i].pts[j].x,                                                   (int) finalBbox[i].pts[j].y);            // 将FacePointInfo对象添加到list中            jmethodID pointListAdd = env->GetMethodID(pointListClass, "add", "(Ljava/lang/Object;)Z");            env->CallBooleanMethod(pointList, pointListAdd, facePointInfo);        }
        // 创建FaceInfo对象        jclass faceInfoClass = env->FindClass("com/ncnn/demo/detect/FaceInfoList$FaceInfo");        jmethodID faceInfoConstructor = env->GetMethodID(faceInfoClass, "<init>",                                                         "(Lcom/ncnn/demo/detect/FaceInfoList$FaceArea;Ljava/util/ArrayList;)V");        jobject faceInfo = env->NewObject(faceInfoClass, faceInfoConstructor, faceArea, pointList);        // 将FaceInfo对象添加到faceList中        jmethodID listAdd = env->GetMethodID(faceListClass, "add", "(Ljava/lang/Object;)Z");        env->CallBooleanMethod(faceList, listAdd, faceInfo);    }    // 创建FaceInfoList对象    jclass faceInfoListClass = env->FindClass("com/ncnn/demo/detect/FaceInfoList");    jmethodID faceInfoListConstructor = env->GetMethodID(faceInfoListClass, "<init>",                                                         "(Ljava/util/ArrayList;)V");    jobject faceInfoList = env->NewObject(faceInfoListClass, faceInfoListConstructor, faceList);    //释放内存    finalBbox.clear();    in.release();    ncnn_img.release();    free(faceImageCharDate);    env->ReleaseByteArrayElements(yuv, yuvBytes, 0);    return faceInfoList;}

检查代码的具体步骤:

  • 先将图片yuv数组转为jbyte

  • jbyte* 强转char*

  • 使用ncnn 将yuv 数据转为RGB格式,在RGB格式中,每个像素由红、绿、蓝三个分量组成,因此转换后的图像数据大小将是width * height * 3字节

  • ncnn 将图像转为ncnn::Mat格式并通过from_pixels_resize 进行缩放(最长方向宽或者高的值为640)

  • 通过g_yoloface->detect 进行检测,如果finalBbox数组的size大于0说明检测到人脸了

  • finalBbox 遍历人脸信息将人脸的信息进行返回。

欢迎感兴趣的朋友通过下方名片关注我们,共同学习探讨人脸识别技术。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐