活体检测包括3种类型:RGB彩色图活体、NIR近红外活体和3D结构光活体。RGB彩色图活体使用普通摄像头图片判断,抵御能力普通,如果对活体有要求,建议直接考虑NIR近红外活体和3D结构光活体。
NIR近红外活体需要配备红外双目摄像头,主要通过红外反射形成的红外图像进行判断。3D结构光活体需要配备3D结构光摄像头,通过两个摄像头图片的微小区别形成深度图像,判断虚假平面类的攻击。两种活体判断都需要人脸离摄像头较近且完整的场景。
商米不同型号设备摄像头配置选择可能有区别,请咨询商米销售。另外在选择设备和方案阶段,我们很乐于提供一些实际场景应用的经验。
1. NIR近红外活体检测
1.1 NIR近红外活体检测简介
NIR近红外活体检测利用近红外成像原理,比如屏幕无法成像,不同材质反射率不同等,可以抵御手机和照片等多种攻击。
红外成像有多种类型,比较常见的有两种:第一种是inutive模组采集的pattern红外图,这种红外图是带斑点的;与此相对应的是用Boteye模组采集的clean红外图,这种红外图不带斑点是干净的。商米人脸识别SDK使用clean红外图,商米设备配备的红外摄像头都满足红外活体检测要求。
此外,商米人脸识别演示APP中已经预设了商米配置的双目红外摄像头的偏差值,以下三点设置细节可帮助使用其他途径获得的双目摄像头的情况:
- RGB和NIR图像分辨率均为640×480。
- NIR近红外活体检测需保证RGB图像和IR图像在时间上是对应的。
- RGB图和IR图存在一定的位置偏差,以RGB图检测到的人脸位置为参考,IR图在x方向和y方向的偏差如表1.1.1所示。
表1.1.1 IR摄像头偏移情况
IR摄像头型号 | IR图相比RGB图在x方向的偏移量 | IR图相比RGB图在y方向的偏移量 |
1 | -40 | 0 |
2 | 10 | -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结构光摄像头的偏差值,一般可以直接使用,两种摄像头的参数略有不同,以下三点设置细节可帮助您了解更多:
- RGB图像分辨率为 640×480,3D结构光活体检测深度图分辨率如表2.1.1所示。
- 3D结构光活体检测需保证RGB图像和深度图像在时间上是对应的。
- 彩色图和深度图存在一定的位置偏差,以彩色图检测到的人脸位置为参考,深度图在x方向和y方向的偏差如表2.1.1所示。
表2.1.1 配置3D摄像头的商米设备情况
设备型号 | 3D摄像头型号 | 深度图分辨率 | 深度图相比彩色图在x方向偏移量 | 深度图相比彩色图在y方向偏移量 | 是否旋转 |
K1 | Pro A | 640*480 | 28 | -20 | 否 |
K2 | D2plus | 400*640 | 75 | -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结构光活体检测。