NIR近红外活体和3D结构光活体

活体检测包括3种类型:RGB彩色图活体、NIR近红外活体和3D结构光活体。RGB彩色图活体使用普通摄像头图片判断,抵御能力普通,如果对活体有要求,建议直接考虑NIR近红外活体和3D结构光活体。

NIR近红外活体需要配备红外双目摄像头,主要通过红外反射形成的红外图像进行判断。3D结构光活体需要配备3D结构光摄像头,通过两个摄像头图片的微小区别形成深度图像,判断虚假平面类的攻击。两种活体判断都需要人脸离摄像头较近且完整的场景。

商米不同型号设备摄像头配置选择可能有区别,请咨询商米销售。另外在选择设备和方案阶段,我们很乐于提供一些实际场景应用的经验。

1. NIR近红外活体检测

1.1 NIR近红外活体检测简介

NIR近红外活体检测利用近红外成像原理,比如屏幕无法成像,不同材质反射率不同等,可以抵御手机和照片等多种攻击。

红外成像有多种类型,比较常见的有两种:第一种是inutive模组采集的pattern红外图,这种红外图是带斑点的;与此相对应的是用Boteye模组采集的clean红外图,这种红外图不带斑点是干净的。商米人脸识别SDK使用clean红外图,商米设备配备的红外摄像头都满足红外活体检测要求。

此外,商米人脸识别演示APP中已经预设了商米配置的双目红外摄像头的偏差值,以下三点设置细节可帮助使用其他途径获得的双目摄像头的情况:

  1. RGB和NIR图像分辨率均为640×480。
  2. NIR近红外活体检测需保证RGB图像和IR图像在时间上是对应的。
  3. RGB图和IR图存在一定的位置偏差,以RGB图检测到的人脸位置为参考,IR图在x方向和y方向的偏差如表1.1.1所示。

表1.1.1 IR摄像头偏移情况

IR摄像头型号IR图相比RGB图在x方向的偏移量IR图相比RGB图在y方向的偏移量
1-400
210-10

1.2 NIR活体使用步骤

NIR近红外活体检测是人脸识别基础上的附加功能,在使用 人脸识别Andriod_SDK_1.4 调用人脸识别功能的过程中(特别注意第3.7、3.9和3.15),如需要NIR近红外活体检测功能,依次添加以下四个步骤:

第一步:在第3.7节的配置SDK时,添加对NIR活体检测阈值设定:

param.setNirLivenessThreshold(0.5f); //阈值越高,活体检测越严格,人脸通过活体检测的难度越大
param.setNirXOffset(-40); //根据设备型号设置红外图相对彩色图x方向偏移量
param.setNirYOffset(0); //根据设备型号设置红外图相对彩色图y方向偏移量

第二步:将RGB图的全部通道从Bitmap类型转成字节数组;将IR图的一个通道从Bitmap类型转成字节数组,两组示例代码如下。

RGB图全部通道从Bitmap类型转成字节数组:

public static byte[] getPixelsBGR(Bitmap image) {
    // 计算图片有多少字节
    int bytes = image.getByteCount();

    ByteBuffer buffer = ByteBuffer.allocate(bytes); // 创建新的buffer
    image.copyPixelsToBuffer(buffer); // 移动字节数据到buffer

    byte[] temp = buffer.array(); // 获取包含数据的底层数组
    byte[] pixels = new byte[(temp.length / 4) * 3]; // 分配空间给BGR

    // 对像素重组
    for (int i = 0; i ‹ temp.length / 4; i++) {
        pixels[i * 3] = temp[i * 4 + 2];        //B
        pixels[i * 3 + 1] = temp[i * 4 + 1];    //G
        pixels[i * 3 + 2] = temp[i * 4];        //R
    }
    return pixels;
}

IR图的一个通道从Bitmap类型转成字节数组:

public static byte[] getSingleChannelPixels(Bitmap image){
    if(image == null){return null;}
    int bytes = image.getByteCount();

    ByteBuffer buffer = ByteBuffer.allocate(bytes); // Create a new buffer
    image.copyPixelsToBuffer(buffer); // Move the byte data to the buffer

    byte [] temp = buffer.array();
    byte[] pixels = new byte[(temp.length / 4)];
    for (int i = 0; i ‹ temp.length / 4; i++) {
        pixels[i] = temp[i * 4];
    } 
    return pixels;

}

第三步:检查第3.15节中提到的执行选择功能不是PredictMode_None;再使用SunmiFaceImage构造函数及上一步得到的彩色图字节流和红外图字节流创建SunmiFaceImage实例;最后通过实例中的setLivenessMode方法指定需要进行的活体检测方法:

SunmiFaceImage image = new SunmiFaceImage(RGBData, RGBbitmap.getHeight(), RGBbitmap.getWidth(), IRData, IRHeight, IRWidth, null, null, null, 1);
image.setPredictMode(SunmiFaceMode.PredictMode_Feature);//执行功能选择不能是PredictMode_None
image.setLivenessMode(SunmiFaceLivenessMode.LivenessMode_None);//不进行活体检测
image.setLivenessMode(SunmiFaceLivenessMode.LivenessMode_NIR);//只进行NIR活体检测
image.setLivenessMode(SunmiFaceLivenessMode.LivenessMode_NIR|SunmiFaceLivenessMode.LivenessMode_RGB)//同时进行NIR和RGB活体检测

第四步:获取活体检测分数及SDK返回的响应值:

SunmiFaceImageFeatures features = new SunmiFaceImageFeatures(); //构造空的人脸特征对象
ret = SunmiFaceSDK.getImageFeatures(image, features); //使用人脸识别SDK进行特征提取
SunmiFaceFeature faceFeature = SunmiFaceLib.SunmiFaceFeatureArrayGetItem(features.getFeatures(), 0);  //提取置信度最高的人脸特征信息
float rgb_liveness_score = faceFeature.getRgbLivenessScore(); //获取置信度最高的人脸RGB活体检测分数
float nir_liveness_score = faceFeature.getNirLivenessScore();//获取置信度最高的人脸NIR活体检测分数

开发者可根据不同的需求对rgb_liveness_score和nir_liveness_score进行后续代码逻辑开发。当返回的ret为FACE_CODE_NOT_LIVENESS时,说明SDK根据当前设定的RGB和NIR活体检测阈值认为当前人脸是假脸;而ret为FACE_CODE_OK时说明当前人脸通过了RGB彩色图和NIR近红外活体检测。

2. 3D结构光活体检测

2.1 3D结构光活体检测简介

商米人脸识别SDK使用奥比中光3D结构光摄像头实现3D结构光活体检测功能。3D摄像头采集到的深度图以字节流的形式输出,其中每个像素点占据2个字节,其物理意义为在图像上该点物体到摄像头的距离(距离单位为毫米)。3D结构光活体检测反欺诈能力相对较强,建议安全性要求较高场景选用。

此外,商米人脸识别演示APP中已经预设了商米配置的3D结构光摄像头的偏差值,一般可以直接使用,两种摄像头的参数略有不同,以下三点设置细节可帮助您了解更多:

  1. RGB图像分辨率为 640×480,3D结构光活体检测深度图分辨率如表2.1.1所示。
  2. 3D结构光活体检测需保证RGB图像和深度图像在时间上是对应的。
  3. 彩色图和深度图存在一定的位置偏差,以彩色图检测到的人脸位置为参考,深度图在x方向和y方向的偏差如表2.1.1所示。

表2.1.1 配置3D摄像头的商米设备情况

设备型号3D摄像头型号深度图分辨率深度图相比彩色图在x方向偏移量深度图相比彩色图在y方向偏移量是否旋转
K1Pro A640*48028-20
K2D2plus400*64075-15

2.2 3D结构光活体检测使用步骤

3D结构光活体检测是人脸识别基础上的附加功能,在使用 人脸识别Andriod_SDK_1.4 调用人脸识别功能的过程中(特别注意第3.7、3.9和3.15),如需要3D结构光活体检测功能,依次添加以下四个步骤:

第一步:在第3.7节的配置SDK时,添加对3D结构光活体检测阈值设定:

param.setDepthLivenessThreshold(0.5f); //阈值越高,活体检测越严格,人脸通过活体检测的难度越大
param.setDepthXOffset(75); //根据设备型号设置深度图相对彩色图x方向偏移量
param.setDepthYOffset(-15); //根据设备型号设置深度图相对彩色图y方向偏移量

第二步:根据使用的摄像头型号,Pro A(常见于K1,T2)或D2plus(常见于K2),具体见表2.1.1,确定图像是否需要旋转,需要旋转的进行彩色图和深度图的旋转,最后再把彩色图从Bitmap类型转换成字节数组,示例代码分别如下:

旋转彩色图:

public static Bitmap rotateBitmap(Bitmap origin, float alpha) {
        if (origin == null) {
            return null;
        }
        int width = origin.getWidth();
        int height = origin.getHeight();
        Matrix matrix = new Matrix();
        matrix.setRotate(alpha);
        // 围绕原地进行旋转
        Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false);
        if (newBM.equals(origin)) {
            return newBM;
        }

        return newBM;
    }

旋转深度图:

public static byte [] rotateDepthImage(byte[] depthByte, int height, int width){
        if(depthByte == null){return null;}

        int depthLen = height * width * 2;
        byte [] newDepth = new byte [depthLen];
        for (int i = 0; i‹=width-1; i++){
            for(int j = 0; j‹=height-1; j++){
                newDepth[2*(i*height+j)] = depthByte[2*((height-1-j)*width+i)];
                newDepth[2*(i*height+j)+1] = depthByte[2*((height-1-j)*width+i)+1];
            }
        }
        return newDepth;
    }

将彩色图从Bitmap类型转成字节数组:

public static byte[] getPixelsBGR(Bitmap image) {
    // 计算图片有多少字节
    int bytes = image.getByteCount();

    ByteBuffer buffer = ByteBuffer.allocate(bytes); // 创建新的buffer
    image.copyPixelsToBuffer(buffer); // 移动字节数据到buffer

    byte[] temp = buffer.array(); // 获取包含数据的底层数组
    byte[] pixels = new byte[(temp.length / 4) * 3]; // 分配空间给BGR

    // 对像素重组
    for (int i = 0; i ‹ temp.length / 4; i++) {
        pixels[i * 3] = temp[i * 4 + 2];        //B
        pixels[i * 3 + 1] = temp[i * 4 + 1];    //G
        pixels[i * 3 + 2] = temp[i * 4];        //R
    }
    return pixels;
}

第三步:检查第3.15节中提到的执行选择功能不是PredictMode_None;再使用SunmiFaceImage构造函数及上一步得到的彩色图字节流和红外图字节流创建SunmiFaceImage实例;最后通过实例中的setLivenessMode方法指定需要进行的活体检测方法:

SunmiFaceImage image = new SunmiFaceImage(RGBData, RGBbitmap.getHeight(), RGBbitmap.getWidth(), null, null, null, DepthData, DepthHeight, DepthWidth, 1);
image.setPredictMode(SunmiFaceMode.PredictMode_Feature);//执行功能选择不能是PredictMode_None
image.setLivenessMode(SunmiFaceLivenessMode.LivenessMode_None);//不进行活体检测
image.setLivenessMode(SunmiFaceLivenessMode.LivenessMode_Depth);//只进行3d活体检测
image.setLivenessMode(SunmiFaceLivenessMode.LivenessMode_Depth|SunmiFaceLivenessMode.LivenessMode_RGB)//同时进行3d和RGB活体检测

第四步:获取活体检测分数及SDK返回的响应值:

SunmiFaceImageFeatures features = new SunmiFaceImageFeatures(); //构造空的人脸特征对象
ret = SunmiFaceSDK.getImageFeatures(image, features); //使用人脸识别SDK进行特征提取
SunmiFaceFeature faceFeature = SunmiFaceLib.SunmiFaceFeatureArrayGetItem(features.getFeatures(), 0);  //提取置信度最高的人脸特征信息
float rgb_liveness_score = faceFeature.getRgbLivenessScore(); //获取置信度最高的人脸RGB活体检测分数
float depth_liveness_score = faceFeature.getDepthLivenessScore();//获取置信度最高的人脸3D活体检测分数

开发者可根据不同的需求对rgb_liveness_score和depth_liveness_score进行后续代码逻辑开发。当返回的ret为FACE_CODE_NOT_LIVENESS时,说明SDK根据当前设定的RGB和3D结构光活体检测阈值认为当前人脸是假脸;而ret为FACE_CODE_OK时说明当前人脸通过了RGB彩色图和3D结构光活体检测。