前言

前一章已经讲述 seetaface6 库编译了
这章讲述 自己封装 seetaface6 库(JNI),提供几个简单的接口 供android 调用

1: java 接口类定义

1>主要就2个函数 1:加载模型文件 2 识别绘制人脸

package com.example.camerx;

import android.graphics.Bitmap;

public class SeetaFace {
    static{
        System.loadLibrary("testseetaface");

    }
    public native boolean loadModel(String datapath,String[] functions);    //加载模型
    public native int detectFaceEx(Bitmap input,int nflag); //nflag 1 detect  2 point5 4 point68 8 sex  16 age
}

2> seetaface hpp
按 windows demo 加载模型文件 提供几个函数供jni里调用

#include <iostream>
#include <string>
#include <vector>
#include <android/asset_manager_jni.h>
#include <seeta/FaceDetector.h>
#include <seeta/FaceLandmarker.h>
#include <seeta/FaceAntiSpoofing.h>
#include <seeta/Common/Struct.h>
#include <seeta/MaskDetector.h>         //口罩检测
#include <seeta/EyeStateDetector.h>     //眼睛状态检测
#include <seeta/AgePredictor.h>         //年龄检测
#include <seeta/GenderPredictor.h>      //性别检测
#include <seeta/QualityStructure.h>     //遮挡评估
#include <seeta/QualityOfBrightness.h>  //亮度评估
#include <seeta/QualityOfResolution.h>  //分辨率评估
#include <seeta/QualityOfIntegrity.h>   //完整性评估
#include <seeta/QualityOfClarity.h>     //清晰度检测(传统)
#include <seeta/QualityOfPose.h>		//姿态评估(传统)
//#include <seeta/QualityOfLBN.h>         //清晰度评估(深度)
//#include <seeta/QualityOfPoseEx.h>      //姿态评估(深度)
using namespace std;

class Seetaface
{
public:
    Seetaface(){}
	// 模型加载
    bool Init_model(string path,int nflag){
		int nResult = 0;
		if((nflag &1) > 0){
			//人脸框检测
			seeta::ModelSetting setting;
			setting.append(path+"/face_detector.csta");
			faceDetector = new seeta::FaceDetector(setting);  
			nResult |= faceDetector?1:0 ;
		}
		
		if((nflag &2) > 0){
			//人脸关键点模型初始化 5点
			seeta::ModelSetting setting;
			setting.append(path+"/face_landmarker_pts5.csta");
			landDetector5 = new seeta::FaceLandmarker(setting);  
			nResult |= landDetector5?2:0 ;
		}
		
		if((nflag &4) > 0){
			//人脸关键点模型初始化 68点
			seeta::ModelSetting setting;
			setting.append(path+"/face_landmarker_pts68.csta");
			landDetector68 = new seeta::FaceLandmarker(setting); 
			nResult |= landDetector68?4:0 ;
		}
		
		if((nflag &8) > 0){
			//性别检测
			seeta::ModelSetting setting;
			setting.append(path+"/gender_predictor.csta");
			genderPredictor = new seeta::GenderPredictor(setting); 
			nResult |= genderPredictor?8:0 ;
		}
		
		if((nflag &16) > 0){
			//性别检测
			seeta::ModelSetting setting;
			setting.append(path+"/age_predictor.csta");
			agePredictor = new seeta::AgePredictor(setting);  
			nResult |= agePredictor?16:0 ;
		}
		//其他的自行增加
		return nResult ;// nResult== nflag?1:0; //直接返回结果java更容易知道哪里模型加载失败
    }
	
	// 人脸检测
    SeetaFaceInfoArray detect_face(const SeetaImageData &simage){
        SeetaFaceInfoArray faces = faceDetector->detect(simage);
        return faces;
    }
	//5特征点检测
    std::vector<SeetaPointF> detect_land5(const SeetaImageData &simage,const SeetaRect &box){
        std::vector<SeetaPointF> points = landDetector5->mark(simage, box);
        return points;
    }
	//68特征点检测
    std::vector<SeetaPointF> detect_land68(const SeetaImageData &simage,const SeetaRect &box){
        std::vector<SeetaPointF> points = landDetector68->mark(simage, box);
        return points;
    }
	//五官遮挡检测
    int* detect_facemask(const SeetaImageData& simage,const SeetaRect& box){
        int masks[4];
        auto point_masks = faceMaskDetector->mark_v2(simage, box);
        for (int i = 0; i < point_masks.size()-2; i++) {
            masks[i]=point_masks[i].mask;
        }
        if(point_masks[3].mask==0 && point_masks[4].mask==0){
            masks[3]=0;
        }
        else{
            masks[3]=1;
        }
        return masks;
    }
    //年龄预测
    int predict_age(const SeetaImageData& simage,const std::vector<SeetaPointF>& points5){
        int age = 0;
        agePredictor->PredictAgeWithCrop(simage, points5.data(), age);
        return age;
    }
	//性别预测
    int  predict_gender_int(const SeetaImageData& simage,const std::vector<SeetaPointF>& points5){
        seeta::GenderPredictor::GENDER gender;
        int sex = 1;
        genderPredictor->PredictGenderWithCrop(simage, points5.data(), gender);
        if(gender == seeta::GenderPredictor::FEMALE)
            sex = 0;
        return sex;
    }
	
private:
    seeta::FaceDetector *faceDetector;              //人脸框
    seeta::FaceLandmarker *landDetector5;           //5特征点
    seeta::FaceLandmarker *landDetector68;          //68特征点
    seeta::FaceAntiSpoofing *liveDetector;          //活体检测
    seeta::FaceLandmarker *faceMaskDetector;        //五官遮挡检测
    seeta::AgePredictor *agePredictor;              //年龄预测
    seeta::GenderPredictor*genderPredictor;         //性别评估
    seeta::MaskDetector* maskDetector;              //口罩检测
    seeta::EyeStateDetector* eyeStateDetector;      //眼睛状态检测
    seeta::QualityRule* qualityClarity;             //清晰度评估(传统)
    seeta::QualityRule* qualityBright;              //明亮度评估(传统)
    seeta::QualityRule* qualityResolution;          //分辨率评估
    seeta::QualityOfPose* qualityPose;              //姿态评估(传统)
    seeta::QualityOfIntegrity* qualityIntegrity;    //完整性评估
};

3> jni 文件
BitmapToMatrix/MatrixToBitmap 图像转换函数

#include <jni.h>
#include <string>
#include <android/asset_manager_jni.h>
#include <android/bitmap.h>
#include <android/log.h>
#include <seeta/FaceDetector.h>
#include <seeta/FaceLandmarker.h>
#include <seeta/FaceAntiSpoofing.h>
#include <seeta/Common/Struct.h>
#include "seetaface.hpp"
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/opencv.hpp>


using namespace seeta::SEETA_FACE_DETECTOR_NAMESPACE_VERSION;

using namespace std;
using namespace cv;

static Seetaface seetaNet;

#define ASSERT(status, ret)     if (!(status)) { return ret; }
#define ASSERT_FALSE(status)    ASSERT(status, false)
bool BitmapToMatrix(JNIEnv * env, jobject obj_bitmap, cv::Mat & matrix) {
    void * bitmapPixels;                                            // Save picture pixel data
    AndroidBitmapInfo bitmapInfo;                                   // Save picture parameters

    ASSERT_FALSE( AndroidBitmap_getInfo(env, obj_bitmap, &bitmapInfo) >= 0);        // Get picture parameters
    ASSERT_FALSE( bitmapInfo.format == ANDROID_BITMAP_FORMAT_RGBA_8888
                  || bitmapInfo.format == ANDROID_BITMAP_FORMAT_RGB_565 );          // Only ARGB? 8888 and RGB? 565 are supported
    ASSERT_FALSE( AndroidBitmap_lockPixels(env, obj_bitmap, &bitmapPixels) >= 0 );  // Get picture pixels (lock memory block)
    ASSERT_FALSE( bitmapPixels );

    if (bitmapInfo.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
        cv::Mat tmp(bitmapInfo.height, bitmapInfo.width, CV_8UC4, bitmapPixels);    // Establish temporary mat
        tmp.copyTo(matrix);                                                         // Copy to target matrix
    } else {
        cv::Mat tmp(bitmapInfo.height, bitmapInfo.width, CV_8UC2, bitmapPixels);
        cv::cvtColor(tmp, matrix, cv::COLOR_BGR5652RGB);
    }

    //convert RGB to BGR
    cv::cvtColor(matrix,matrix,cv::COLOR_RGB2BGR);

    AndroidBitmap_unlockPixels(env, obj_bitmap);            // Unlock
    return true;
}



bool MatrixToBitmap(JNIEnv * env, cv::Mat & matrix, jobject obj_bitmap) {
    void * bitmapPixels;                                            // Save picture pixel data
    AndroidBitmapInfo bitmapInfo;                                   // Save picture parameters
    ASSERT_FALSE( AndroidBitmap_getInfo(env, obj_bitmap, &bitmapInfo) >= 0);        // Get picture parameters
    ASSERT_FALSE( bitmapInfo.format == ANDROID_BITMAP_FORMAT_RGBA_8888
                  || bitmapInfo.format == ANDROID_BITMAP_FORMAT_RGB_565 );          // Only ARGB? 8888 and RGB? 565 are supported
    ASSERT_FALSE( matrix.dims == 2
                  && bitmapInfo.height == (uint32_t)matrix.rows
                  && bitmapInfo.width == (uint32_t)matrix.cols );                   // It must be a 2-dimensional matrix with the same length and width
    ASSERT_FALSE( matrix.type() == CV_8UC1 || matrix.type() == CV_8UC3 || matrix.type() == CV_8UC4 );
    ASSERT_FALSE( AndroidBitmap_lockPixels(env, obj_bitmap, &bitmapPixels) >= 0 );  // Get picture pixels (lock memory block)
    ASSERT_FALSE( bitmapPixels );
    if (bitmapInfo.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
        cv::Mat tmp(bitmapInfo.height, bitmapInfo.width, CV_8UC4, bitmapPixels);
        switch (matrix.type()) {
            case CV_8UC1:   cv::cvtColor(matrix, tmp, cv::COLOR_GRAY2RGBA);     break;
            case CV_8UC3:   cv::cvtColor(matrix, tmp, cv::COLOR_RGB2RGBA);      break;
            case CV_8UC4:   matrix.copyTo(tmp);                                 break;
            default:        AndroidBitmap_unlockPixels(env, obj_bitmap);        return false;
        }
    } else {
        cv::Mat tmp(bitmapInfo.height, bitmapInfo.width, CV_8UC2, bitmapPixels);
        switch (matrix.type()) {
            case CV_8UC1:   cv::cvtColor(matrix, tmp, cv::COLOR_GRAY2BGR565);   break;
            case CV_8UC3:   cv::cvtColor(matrix, tmp, cv::COLOR_RGB2BGR565);    break;
            case CV_8UC4:   cv::cvtColor(matrix, tmp, cv::COLOR_RGBA2BGR565);   break;
            default:        AndroidBitmap_unlockPixels(env, obj_bitmap);        return false;
        }
    }
    AndroidBitmap_unlockPixels(env, obj_bitmap);                // Unlock
    return true;
}
void draw_box(cv::Mat &img, SeetaRect& box) {
    // 绘制单个人脸框
    cv::rectangle(img, cv::Point2i{box.x, box.y}, cv::Point2i{box.width+box.x, box.height+box.y}, cv::Scalar(0, 255, 255), 3, 8, 0);
}

void draw_points(cv::Mat &img, std::vector<SeetaPointF> &pts) {
    // 绘制特征点
    cv::Scalar color=cv::Scalar(225, 0, 225);
    if(pts.size()==68){
        color=cv::Scalar(225, 0, 0);
    }
    for (int j = 0; j < pts.size(); ++j) {
        cv::circle(img, cv::Point2d(pts[j].x, pts[j].y), 2, color, -1, 8,0);
    }
}
//com.example.testcamera
extern "C" JNIEXPORT jboolean
Java_com_example_camerx_SeetaFace_loadModel(JNIEnv *env,jobject thiz,jstring cstapath,jobjectArray string_array) {
    //模型初始化
    const char *modelpath = env->GetStringUTFChars(cstapath, 0); //jstring 转char*
    seetaNet.Init_face(modelpath);  //加载模型
    jint strlength = env->GetArrayLength(string_array);   //string_array 为要加载模型名称的字符串数组
    for (int i = 0; i < strlength; ++i) {
        jstring str = static_cast<jstring>(env->GetObjectArrayElement(string_array, i));
        const char* func = env->GetStringUTFChars(str,NULL);
        LOGI("获取java的参数:%s",func);
        string res = seetaNet.Init(modelpath,func);
        if(res !="ok"){
            LOGE("输入模型:%s名称不对",func);
            return JNI_FALSE;
        }
        else{
            LOGI("%s初始化成功",func);
        }
        env->ReleaseStringUTFChars(str,func);
    }

    env->ReleaseStringUTFChars(cstapath, modelpath);
    return JNI_TRUE;
}
//扩展的 1 detect  2 point5 4 point68 8 sex  16 age
extern "C" JNIEXPORT jint JNICALL
Java_com_example_camerx_SeetaFace_detectFaceEx(JNIEnv *env, jobject thiz, jobject input,jint iflag) {//,jobject bitmapOut
    cv::Mat timage ,tcimage;
    SeetaImageData tsimage;
    SeetaRect tsbox ;
    bool bit_cv = BitmapToMatrix(env,input,timage);  //bitmap转cvMat,格式还为RGBA
    cv::cvtColor(timage,tcimage,COLOR_RGBA2BGR);  //
  //  LOGI("Java_com_example_testcamera_SeetaFace_detectFace start 2 ");
    //cv图片转Seeta图片
    tsimage.width = tcimage.cols;
    tsimage.height = tcimage.rows;
    tsimage.channels = tcimage.channels();
    tsimage.data = tcimage.data;
    SeetaFaceInfoArray faces = seetaNet.detect_face(tsimage); //调用人脸检测
    if (faces.size<=0){
  //      LOGI("Java_com_example_testcamera_SeetaFace_detectFace start 3 ");
        return -1;
    }else{
        auto face = faces.data[0];
        tsbox = face.pos;
        //        float score = face.score;@
      //  int x1 = int(tsbox.x), y1 = int(tsbox.y), width = int(tsbox.width), height = int(tsbox.height);
    //    int bbox[4] = {x1, y1, width, height};
     //   jintArray boxInfo = env->NewIntArray(4);
     //   env->SetIntArrayRegion(boxInfo, 0, 4, bbox);
       // return boxInfo;
    }

    int age = 0;
   {
        iflag |= 1 ;
        if((iflag &1 ) > 0){
            draw_box(tcimage, tsbox);  //人脸框与五官画图
        }

        if((iflag &2 ) > 0){
            std::vector<SeetaPointF>  tpoints5 = seetaNet.detect_land5(tsimage, tsbox);
            if((iflag &16 ) > 0){
                age = seetaNet.predict_age(tsimage,tpoints5)*2;
            }
            if((iflag &8 ) > 0){
                  int sex = seetaNet.predict_gender_int(tsimage,tpoints5);
                  age += sex ;
            }
            draw_points(tcimage, tpoints5);  //五官画图
        }else if((iflag & 24) > 0 ){
            std::vector<SeetaPointF>  tpoints5 = seetaNet.detect_land5(tsimage, tsbox);
            if((iflag &16 ) > 0){
                age = seetaNet.predict_age(tsimage,tpoints5)*2;
            }
            if((iflag &8 ) > 0){
                   int sex = seetaNet.predict_gender_int(tsimage,tpoints5);
                   age += sex ;
            }
        }
        if((iflag &4 ) > 0){
            std::vector<SeetaPointF>  points68 = seetaNet.detect_land68(tsimage, tsbox);
            draw_points(tcimage, points68);  //五官画图
       }

    //    LOGI("Java_com_example_testcamera_SeetaFace_detectDraw 000 cols=%d rows=%d",imageOut.cols,imageOut.rows);
        bool cv_bit = MatrixToBitmap(env,tcimage,input);
    //    LOGI("Java_com_example_testcamera_SeetaFace_detectDraw %d ",(int)cv_bit);
   }
   return age;
}

4: mk 文件
目录结构
在这里插入图片描述

Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := SeetaQualityAssessor300
LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/libSeetaQualityAssessor300.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := SeetaAgePredictor600
LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/libSeetaAgePredictor600.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := SeetaAuthorize
LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/libSeetaAuthorize.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := SeetaEyeStateDetector200
LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/libSeetaEyeStateDetector200.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := SeetaFaceAntiSpoofingX600
LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/libSeetaFaceAntiSpoofingX600.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := SeetaFaceDetector600
LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/libSeetaFaceDetector600.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := SeetaFaceLandmarker600
LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/libSeetaFaceLandmarker600.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := SeetaGenderPredictor600
LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/libSeetaGenderPredictor600.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := SeetaMaskDetector200
LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/libSeetaMaskDetector200.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := SeetaPoseEstimation600
LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/libSeetaPoseEstimation600.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := tennis
LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/libtennis.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := omp
LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/libomp.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE:=opencv_java4
LOCAL_SRC_FILES:=$(TARGET_ARCH_ABI)/libopencv_java4.so
include $(PREBUILT_SHARED_LIBRARY)


include $(CLEAR_VARS)

LOCAL_MODULE    := testseetaface

#LOCAL_C_INCLUDES := $(LOCAL_PATH)/include

LOCAL_SRC_FILES := \
    seetaface_jni_1.cpp

LOCAL_C_INCLUDES += $(LOCAL_PATH)/seetaface6/include
LOCAL_C_INCLUDES += $(LOCAL_PATH)/opencv

#LOCAL_LDLIBS :=  -llog
LOCAL_LDLIBS := -L$(TARGET_ARCH_ABI) -lSeetaQualityAssessor300 \
-lSeetaAgePredictor600 -lSeetaAuthorize -lSeetaEyeStateDetector200 \
-lSeetaFaceAntiSpoofingX600 -lSeetaFaceDetector600 -lSeetaFaceLandmarker600 \
-lSeetaGenderPredictor600 -lSeetaMaskDetector200 -lSeetaPoseEstimation600 -ltennis -lopencv_java4 \
-llog -landroid -ldl -ljnigraphics


include $(BUILD_SHARED_LIBRARY)

Application.mk

APP_ABI := armeabi-v7a,arm64-v8a
APP_PLATFORM := android-23
APP_STL := c++_shared
APP_CPPFLAGS := -fexceptions -frtti
#APP_CPPFLAGS := -fexceptions -fno-rtti
APP_CPPFLAGS +=-std=c++11
APP_CPPFLAGS +=-fopenmp -static-openmp

5:ndk-build
生成
在这里插入图片描述

6:下章 创建android 工程测试

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐