Android 对接虹软活体检测

虹软的人脸识别算法因为免费,所有被很多开发者采用,今天来大致介绍一下如何使用,方便你接入的时候更加省时省力。因为人脸识别一般都放在服务器端,除非做离线识别,所有接下来就针对活体检测 sdk 来说明一下。

活体检测 SDK 下载:https://ai.arcsoft.com.cn/product/liveness_detection.html

1
2
3
4
5
6
ArcSoft_Liveness_Android_V1.0
├─doc
├─libs
│  └─armeabi-v7a 
└─samplecode
    └─LivenessDemo

将 LivenessDemo 导入 AndroidStudio, 紧接着配置 com.arcsoft.livenessdemo.Constants 文件:

1
2
3
4
5
6
7
8
9
public class Constants {

    public static final String FREESDKAPPID = "AADFSADFASFSDFSFASFAFSDFSF";
    public static final String FDSDKKEY = "SFASFASFASFASFASFAFAFAFWERQRQR";
    public static final String FTSDKKEY = "BASGTGRTQGSAGASFSFSFSFSFSF";

    public static final String LIVENESSAPPID = "AADFSADFASFSDFSFASFAFSDFSF";
    public static final String LIVENESSSDKKEY = "QRRQFDSGGGEQTQRAFSFSFAFASFA";
}

其中上面的 LIVENESSAPPIDFREESDKAPPID 是相同值,就是你应用的 APP_ID.

LIVENESSSDKKEY 是活体检测的 SDK KEY, 如下图:

而上面的 FDSDKKEYFTSDKKEY 就比较坑了,实际上是人脸识别(ArcFace)的 1.2 版本的 SDK_KEY, 所以我们还必须添加一个人脸识别的 SDK.

所以最后你想要运行这个 LivenessDemo 就必须申请两个 SDK, 人脸识别的 FDSDKKEYFTSDKKEY 如下:

完了之后你应该就可以正常的运行代码了,而且可以成功的激活活体检测引擎。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
 Executors.newSingleThreadExecutor().execute(new Runnable() {
    @Override
    public void run() {
        final long activeCode = livenessEngine.activeEngine(MainActivity.this,Constants.LIVENESSAPPID,
                Constants.LIVENESSSDKKEY).getCode();
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if(activeCode == ErrorInfo.MOK) {
                    toast("活体引擎激活成功");
                } else if(activeCode == ErrorInfo.MERR_AL_BASE_ALREADY_ACTIVATED){
                    toast("活体引擎已激活");
                } else {
                    toast("活体引擎激活失败,errorcode:" + activeCode);
                }
            }
        });
    }
});

如果激活失败,你就需要查看这里的 ErrorInfo.getCode() 的值了,可以对照下表来查看:

常量名常量值常量说明
MOK0成功
MERR_UNKNOWN1错误原因不明
MERR_INVALID_PARAM2无效的参数
MERR_UNSUPPORTED3引擎不支持
MERR_NO_MEMORY4内存不足
MERR_BAD_STATE5状态错误
MERR_USER_CANCEL6用户取消相关操作
MERR_EXPIRED7操作时间过期
MERR_USER_PAUSE8用户暂停操作
MERR_BUFFER_OVERFLOW9缓冲上溢
MERR_BUFFER_UNDERFLOW10缓冲下溢
MERR_NO_DISKSPACE11存贮空间不足
MERR_COMPONENT_NOT_EXIST12组件不存在
MERR_GLOBAL_DATA_NOT_EXIST13全局数据不存在
MERR_FSDK_INVALID_APP_ID28673无效的App Id
MERR_FSDK_INVALID_SDK_ID28674无效的SDK key
MERR_FSDK_INVALID_ID_PAIR28675AppId和SDKKey不匹配
MERR_FSDK_MISMATCH_ID_AND_SDK28676SDKKey 和使用的SDK 不匹配
MERR_FSDK_SYSTEM_VERSION_UNSUPPORTED28677系统版本不被当前SDK所支持
MERR_FSDK_LICENCE_EXPIRED28678SDK有效期过期,需要重新下载更新
MERR_FSDK_FACEFEATURE_ERROR_BASE81920人脸特征检测错误类型
MERR_FSDK_FACEFEATURE_UNKNOWN81921人脸特征检测错误未知
MERR_FSDK_FACEFEATURE_MEMORY81922人脸特征检测内存错误
MERR_FSDK_FACEFEATURE_INVALID_FORMAT81923人脸特征检测格式错误
MERR_FSDK_FACEFEATURE_INVALID_PARAM81924人脸特征检测参数错误
MERR_FSDK_FACEFEATURE_LOW_CONFIDENCE_LEVEL81925人脸特征检测结果置信度低
MERR_AF_EX_BASE_FEATURE_UNSUPPORTED_ON_INIT86017Engine不支持的检测属性
MERR_AF_EX_BASE_FEATURE_UNINITED86018需要检测是属性未初始化
MERR_AF_EX_BASE_FEATURE_UNPROCESSED86019待获取的属性未在PROCESS中处理过
MERR_AF_EX_BASE_FEATURE_UNSUPPORTED_ON_PROCESS86020PROCESS不支持的检测属性
MERR_AF_EX_BASE_INVALID_IMAGE_INF86021无效的输入图像
MERR_AF_EX_BASE_INVALID_FACE_INF86022无效的脸部信息
MERR_AL_BASE_ACTIVATION_FAIL90113Liveness SDK激活失败
MERR_AL_BASE_ALREADY_ACTIVATED90114Liveness SDK已激活
MERR_AL_BASE_NOT_ACTIVATED90115Liveness SDK未激活
MERR_AL_BASE_APPID_MISMATCH90116APPID不匹配
MERR_AL_BASE_VERION_MISMATCH90117SDK版本不匹配
MERR_AL_BASE_DEVICE_MISMATCH90118设备不匹配
MERR_AL_BASE_UNIQUE_IDENTIFIER_MISMATCH90119唯一标识不匹配
MERR_AL_BASE_PARAM_NULL90120参数为空
MERR_AL_BASE_SDK_EXPIRED90121SDK已过期
MERR_AL_BASE_VERSION_NOT_SUPPORT90122版本不支持
MERR_AL_BASE_SIGN_ERROR90123签名错误
MERR_AL_BASE_DATABASE_ERROR90124验证信息存储异常
MERR_AL_BASE_UNIQUE_CHECKOUT_FAIL90125唯一标识检查失败
MERR_AL_BASE_COLOR_SPACE_NOT_SUPPORT90126颜色空间不支持
MERR_AL_BASE_IMAGE_WIDTH_HEIGHT_NOT_SUPPORT90127图片宽度或高度不支持
MERR_ASF_BASE_READ_PHONE_STATE_DENIED90128READ_PHONE_STATE 权限被拒绝
MERR_AL_NETWORK_BASE_COULDNT_RESOLVE_HOST94209无法解析主机地址
MERR_AL_NETWORK_BASE_COULDNT_CONNECT_SERVER94210无法连接服务器
MERR_AL_NETWORK_BASE_CONNECT_TIMEOUT94211网络连接超时
MERR_AL_NETWORK_BASE_UNKNOWN_ERROR94212未知错误

如果活体检测引擎成功启动后,你很有可能会遇到一个问题就是打开摄像头失败问题。在 openCamera 方法中有一个 Camera.open(cameraId) 方法:

1
2
3
4
private int cameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;

//选择摄像头ID
camera = Camera.open(cameraId);

实际上这个 cameraId 定义的是 前置和后置 摄像头 CAMERA_FACING_FRONT 表示前置摄像头,其值为 1,CAMERA_FACING_BACK 表示后置摄像头,其值为 0。

1
2
3
4
public static class CameraInfo {
    public static final int CAMERA_FACING_BACK = 0;
    public static final int CAMERA_FACING_FRONT = 1;
}

把这里试着改成后置摄像头 CAMERA_FACING_BACK。当你兴奋的发现摄像头正常打开的时候你可能会突然发现视频图像是拉伸的(或者说是反的)也有可能是镜像的。

1
camera.setDisplayOrientation(cameraOri);

可以尝试对 Camera 做如上设置来调整方向。事实上一个摄像头会有一些支持的推荐图像尺寸,我们需要通过如下方法获得。

1
2
3
Camera camera = Camera.open(cameraId);
Camera.Parameters parameters = camera.getParameters();
List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();

例如我所用的摄像头支持如下尺寸:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
12-10 10:26:48.846 31624-31624/? D/VideoSampleActivity: ----------------------------
12-10 10:26:48.846 31624-31624/? D/VideoSampleActivity: supportSize.height = 1944
12-10 10:26:48.846 31624-31624/? D/VideoSampleActivity: supportSize.width = 2592
12-10 10:26:48.846 31624-31624/? D/VideoSampleActivity: ----------------------------
12-10 10:26:48.846 31624-31624/? D/VideoSampleActivity: supportSize.height = 1440
12-10 10:26:48.846 31624-31624/? D/VideoSampleActivity: supportSize.width = 2560
12-10 10:26:48.846 31624-31624/? D/VideoSampleActivity: ----------------------------
12-10 10:26:48.846 31624-31624/? D/VideoSampleActivity: supportSize.height = 1080
12-10 10:26:48.846 31624-31624/? D/VideoSampleActivity: supportSize.width = 1920
12-10 10:26:48.846 31624-31624/? D/VideoSampleActivity: ----------------------------
12-10 10:26:48.846 31624-31624/? D/VideoSampleActivity: supportSize.height = 960
12-10 10:26:48.846 31624-31624/? D/VideoSampleActivity: supportSize.width = 1280
12-10 10:26:48.846 31624-31624/? D/VideoSampleActivity: ----------------------------
12-10 10:26:48.846 31624-31624/? D/VideoSampleActivity: supportSize.height = 720
12-10 10:26:48.846 31624-31624/? D/VideoSampleActivity: supportSize.width = 1280
12-10 10:26:48.846 31624-31624/? D/VideoSampleActivity: ----------------------------
12-10 10:26:48.846 31624-31624/? D/VideoSampleActivity: supportSize.height = 480
12-10 10:26:48.846 31624-31624/? D/VideoSampleActivity: supportSize.width = 640
12-10 10:26:48.846 31624-31624/? D/VideoSampleActivity: ----------------------------
12-10 10:26:48.847 31624-31624/? D/VideoSampleActivity: supportSize.height = 240
12-10 10:26:48.847 31624-31624/? D/VideoSampleActivity: supportSize.width = 320
12-10 10:26:48.847 31624-31624/? D/VideoSampleActivity: ----------------------------

你可以尝试着将这些宽高值都打印出来,选择一个合适的图像分辨率,一般情况下默认选择第一个。可是经过我的实际验证虹软的活体检测不支持 1944 x 2592 的分辨率,所以我选择了 1080p 的分辨率。

最后再提一下关于 Fragment 中使用 SurfaceView 显示摄像数据的坑。打开摄像头并通过 SurfaceHolder 的 callback 渲染,当我们关闭 Fragment 或者切换的时候不会正常执行 surfaceDestroyed,从 API 文档上看此回调函数只有再 SurfaceView 被真正的销毁的时候才会调用,一旦回调此函数则不应该再使用此 SurfaceView. 因此就出现了一个问题,这个函数在 Fragment 销毁的时候不调用导致 SurfaceView 没有被 Activity 真正释放,只有在 Activity 销毁的时候才会被真正释放。解决的办法有很多种,最简单的就是不要使用 Fragment 来注册 SurfaceView 的 SurfaceHolder,还有一些其他的解决办法,有的人说可以重新设置 setContentView 一次,就可以释放,此方法本人没尝试过。