高级使用

双目红外活体检测

1. 摄像头要求

为了提升双目活检的效果,算法对红外摄像头和红外补光灯有一定的要求。需要选择成像清晰的红外摄像头,控制红外补光灯的 强度,防止红外图像太亮或太暗这两种极端的状况。

参数

指标

宽动态比

>= 80db

信噪比

>= 50db

白平衡

建议开启

帧率

越大越好

格式

YUV/MJPG

分辨率

>= 480p

双目间距

<= 3cm

红外正常曝光

../_images/normal_exp.jpg

红外过度曝光(不能活检)

../_images/high_exp.jpg

红外低曝光(不能活检)

../_images/low_exp.jpg

2. 指标

活体检测存在几个标准的指标,如下所示:

  • 错误攻击接受率:如0.5%,指1000次作弊假体攻击,会有5次被判定为真人。

  • 真人通过率:如99%,指100次真人请求,会有99次因为活体分数高于阈值而通过。

  • 阈值(Threshold):高于此数值,则可判断为活体, 推荐0.4即可

3. 使用

//活体检测(bbox 彩色摄像头对应的人脸边框)
float score = dfaceNIRLiveness.liveness_check(color_box, ir_box);

4. Android原生Camera API双目配置

请与开发板厂商沟通Android固件原生Camera API是否支持同时打开两个摄像头预览,安卓5.1及5.1以前的版本默认最大支持同时打开1个摄像头预览。如果不支持双目预览,需要重新编译HAL硬件层。 在CameraHal_Module.h中将#define CAMERAS_SUPPORTED_SIMUL_MAX 1 修改为 2。

RGB静默活体检测

1. 摄像头要求

目前单目活体在宽动态摄像头下效果最佳,可选用USB宽动态摄像头或者MIPI摄像头+板芯ISP这种方案。摄像头噪点过大也会影响单活活检效果。

参数

指标

宽动态比

>= 80db

信噪比

>= 50db

白平衡

建议开启

帧率

越大越好

格式

YUV/MJPG

分辨率

>=480p

2. 级别配置

RGB活体初始化需要指定活体级别,分别是LEVEL_1, LEVEL_2, LEVEL_3, 双目红外的辅助RGB活体请选择LEVEL_1, 单目普通RGB活体选择LEVEL_2或LEVEL_3即可。

3. 使用

//活体检测
float score = dfaceRGBLiveness.liveness_check(frame, bbox);

4. 指标

活体检测存在几个标准的指标,如下所示:

  • 错误攻击接受率:如0.5%,指1000次作弊假体攻击,会有5次被判定为真人。

  • 真人通过率:如99%,指100次真人请求,会有99次因为活体分数高于阈值而通过。

  • 阈值(Threshold):高于此数值,则可判断为活体。

RGB单目活体阈值越高,错误攻击接受率会降低,但真人通过率也会降低,请根据实际应用场景调整,推荐0.1-0.5.

活体检测方案选择

1. 活体检测安全等级排名

RGB活检(LEVEL_1)+NIR红外活检 > RGB活检(LEVEL_2) > NIR红外活检

2. 识别距离配置

识别距离主要受摄像头分辨率和识别最小脸设置值有关,我们提供两个最小脸设置,分别是检测最小脸设置和识别最小脸设置。值越小,检测和识别距离越远(注意检测最小脸会影响检测速度,值越小,速度越慢)。摄像头分辨率越高,能够进一步提升检测和识别距离上限。

分辨率

检测最小脸

识别最小脸

有效距离

480P

100px

100px

0.7m

720P

120px

100px

2.0m

1080P

160px

100px

5.0m

提高人脸识别准确率

为了提高生产环境的准确率,需要关注以下几个指标,找到最适合的平衡点。

方法

缺点

提高识别阈值

增加部分识别确认时间

增加人脸质量过滤

额外增加计算时间

提高人脸底库质量

额外增加维护底库成本

改用更高精度的模型

增加识别计算耗时

人脸关键点和姿态

1. 关键点编号

../_images/WFLW_annotation.png

公开人脸特征比对代码

我们使用余弦相似算法来计算两个人脸特征字节数组的相似度。

../_images/cosine.jpg

JAVA版:

public static double sumSquares(float[] data) {
    double ans = 0.0;
    for (int k = 0; k < data.length; k++) {
        ans += data[k] * data[k];
    }
    return (ans);
}

public static double norm(float[] data) {
    return (Math.sqrt(sumSquares(data)));
}


public static double dotProduct(float[] v1, float[] v2) {
    double result = 0;
    for (int i = 0; i < v1.length; i++) {
        result += v1[i] * v2[i];
    }
    return result;
}

/**
 * Comare 2 face feature cosine similarity, cosine = (v1 dot v2)/(||v1|| * ||v2||)
 * @param feature1 first face feature byte array
 * @param feature2 second face feature byte array
 * @note Use Little-Endian default
 * @return face feature cosine similarity
 */
public static double similarityByFeature(byte[] feature1, byte[] feature2) {
    float[] featureArr1 = new float[feature1.length/4];
    for (int x = 0; x < feature1.length; x+=4) {
        int accum = 0;
        accum= accum|(feature1[x] & 0xff) << 0;
        accum= accum|(feature1[x+1] & 0xff) << 8;
        accum= accum|(feature1[x+2] & 0xff) << 16;
        accum= accum|(feature1[x+3] & 0xff) << 24;
        featureArr1[x/4] = Float.intBitsToFloat(accum);
    }

    float[] featureArr2 = new float[feature2.length/4];
    for (int x = 0; x < feature2.length; x+=4) {
        int accum = 0;
        accum= accum|(feature2[x] & 0xff) << 0;
        accum= accum|(feature2[x+1] & 0xff) << 8;
        accum= accum|(feature2[x+2] & 0xff) << 16;
        accum= accum|(feature2[x+3] & 0xff) << 24;
        featureArr2[x/4] = Float.intBitsToFloat(accum);
    }

    // ||v1||
    double feature1_norm = norm(featureArr1);
    // ||v2||
    double feature2_norm = norm(featureArr2);
    // v1 dot v2
    double innerProduct = dotProduct(featureArr1, featureArr2);
    //cosine = (v1 dot v2)/(||v1|| * ||v2||)
    double cosine = innerProduct / (feature1_norm*feature2_norm);
    // normalization to 0~1
    double normCosine = 0.5 + 0.5*cosine;
    return normCosine;
}

C++版:

union FloatType {
    float FloatNum;
    int IntNum;
};


static void convertBytes2Feature(unsigned char* bytes, int length, std::vector<float> &feature) {
    int feathreLen = length / 4;
    for (int i = 0; i<feathreLen; i++) {
        FloatType Number;
        Number.IntNum = 0;
        Number.IntNum = bytes[4 * i + 3];
        Number.IntNum = (Number.IntNum << 8) | bytes[4 * i + 2];
        Number.IntNum = (Number.IntNum << 8) | bytes[4 * i + 1];
        Number.IntNum = (Number.IntNum << 8) | bytes[4 * i + 0];
        feature.push_back(Number.FloatNum);
    }
}

//vector norm
static float norm(const vector<float>& vec){
    int n = vec.size();
    float sum = 0.0;
    for (int i = 0; i<n; ++i)
        sum += vec[i] * vec[i];
    return sqrt(sum);
}

/**
* Compare face feature cosine similarity.
* @param[in] feature1 First face feature
* @param[in] feature2  Second face feature
* @return face feature similarity volume
* @note  cosine = (v1 dot v2)/(||v1|| * ||v2||)
*/
static float similarityByFeature(vector<unsigned char>& feature1, vector<unsigned char>& feature2){
    std::vector<float> feature_arr1;
    std::vector<float> feature_arr2;
    convertBytes2Feature(feature1.data(), feature1.size(), feature_arr1);
    convertBytes2Feature(feature2.data(), feature2.size(), feature_arr2);

    int n = feature_arr1.size();
    float tmp = 0.0;
    for (int i = 0; i<n; ++i){
        tmp += feature_arr1[i] * feature_arr2[i];
    }
    float cosine = tmp / (norm(feature_arr1)*norm(feature_arr2));
    float norm_cosine = 0.5 + 0.5 * cosine;
    return norm_cosine;
}