app端使用活体检测,通过活体检测后,采集人像,base64格式,调接口后台判断人脸对比结果返回给app
1.官网地址:
人脸识别_人脸检测_人脸对比_人脸搜索_活体检测_百度智能云 (baidu.com)
里面有详细的集成步骤和说明
2.申请license
3.下载license拷贝到工程的assets目录
4.将faceplatform-ui以module方式集成项目
4.1在build.gradle 里添加
implementation project(':faceplatform-ui')
4.2在settings.gradle里添加
include ':faceplatform-ui'
faceplatform-ui里集成了faceplatform
5.混淆设置
-keep class com.baidu.vis.unified.license.** {*;}
-keep class com.baidu.liantian.** {*;}
-keep class com.baidu.baidusec.** {*;}
-keep class com.baidu.idl.main.facesdk.** {*;}
6.初始化sdk,前判断权限
if (EasyPermissions.hasPermissions( this, android.Manifest.permission.CAMERA ) ) { //权限判断 startBaiduFace() //初始化百度sdk } else { AlertDialog.Builder(this).setTitle("提示信息") .setCancelable(false) .setMessage("因涉诈安全要求,我们需要您提供相机权限,用于人脸识别。如您拒绝授权,将无法正常登录,是否允许。") .setPositiveButton("开启权限") { _, _ -> ActivityCompat.requestPermissions( this, arrayOf(android.Manifest.permission.CAMERA), 0X12 ) }.setNegativeButton("取消", object : DialogInterface.OnClickListener { override fun onClick(p0: DialogInterface?, p1: Int) { hideDialog() finish() } }).create().show() }
fun startBaiduFace() { //初始化SDK FaceSDKManager.getInstance() .initialize(this, "sunlightat0707-face-android", "idl-license.face-android", object : IInitCallback { override fun initSuccess() { //初始化成功 //设置采集配置 if (!BaiDuFaceConfig.initLicenseSuccess()) { return } //跳转到带活体检测的界面 startActivity( Intent( this, FaceLivenessExpActivity::class.java ) ) } override fun initFailure(errCode: Int, errMsg: String) { //初始化失败提示 CoroutineScope(Dispatchers.Main).launch(Dispatchers.Main) { ToastUtils.showToast( this, "人脸功能初始化失败 = $errCode, $errMsg" ) } hideDialog() finish() } }) }
百度人脸采集配置BaiDuFaceConfig类
class BaiDuFaceConfig { companion object { val sIntance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { BaiDuFaceConfig() } // 动作活体条目集合 public var livenesssList: List<LivenessTypeEnum> = ArrayList() // 活体随机开关 public var isLivenessRandom = false // 语音播报开关 public var isOpenSound = true // 活体检测开关 public var isActionLive = true // 质量等级(0:正常、1:宽松、2:严格、3:自定义) public var qualityLevel: Int = Constants.QUALITY_NORMAL public val destroyMap: MutableMap<String, Activity> = HashMap() /** * 参数配置方法 */ private fun setFaceConfig(): Boolean { val config = FaceSDKManager.getInstance().faceConfig // SDK初始化已经设置完默认参数(推荐参数),也可以根据实际需求进行数值调整 // 质量等级(0:正常、1:宽松、2:严格、3:自定义) // 获取保存的质量等级 val util = SharedPreferencesUtil(App.instance) var qualityLevel = util.getSharedPreference(Constants.KEY_QUALITY_LEVEL_SAVE, -1) as Int if (qualityLevel == -1) { qualityLevel = this.qualityLevel } // 根据质量等级获取相应的质量值(注:第二个参数要与质量等级的set方法参数一致) val manager: QualityConfigManager = QualityConfigManager.getInstance() manager.readQualityFile(App.instance, qualityLevel) val qualityConfig: QualityConfig = manager.getConfig() ?: return false // 设置模糊度阈值 config.blurnessValue = qualityConfig.getBlur() // 设置最小光照阈值(范围0-255) config.brightnessValue = qualityConfig.getMinIllum() // 设置最大光照阈值(范围0-255) config.brightnessMaxValue = qualityConfig.getMaxIllum() // 设置左眼遮挡阈值 config.occlusionLeftEyeValue = qualityConfig.getLeftEyeOcclusion() // 设置右眼遮挡阈值 config.occlusionRightEyeValue = qualityConfig.getRightEyeOcclusion() // 设置鼻子遮挡阈值 config.occlusionNoseValue = qualityConfig.getNoseOcclusion() // 设置嘴巴遮挡阈值 config.occlusionMouthValue = qualityConfig.getMouseOcclusion() // 设置左脸颊遮挡阈值 config.occlusionLeftContourValue = qualityConfig.getLeftContourOcclusion() // 设置右脸颊遮挡阈值 config.occlusionRightContourValue = qualityConfig.getRightContourOcclusion() // 设置下巴遮挡阈值 config.occlusionChinValue = qualityConfig.getChinOcclusion() // 设置人脸姿态角阈值 config.headPitchValue = qualityConfig.getPitch() config.headYawValue = qualityConfig.getYaw() config.headRollValue = qualityConfig.getRoll() // 设置可检测的最小人脸阈值 config.minFaceSize = FaceEnvironment.VALUE_MIN_FACE_SIZE // 设置可检测到人脸的阈值 config.notFaceValue = FaceEnvironment.VALUE_NOT_FACE_THRESHOLD // 设置闭眼阈值 config.eyeClosedValue = FaceEnvironment.VALUE_CLOSE_EYES // 设置图片缓存数量 config.cacheImageNum = FaceEnvironment.VALUE_CACHE_IMAGE_NUM // 设置活体动作,通过设置list,LivenessTypeEunm.Eye, LivenessTypeEunm.Mouth, // LivenessTypeEunm.HeadUp, LivenessTypeEunm.HeadDown, LivenessTypeEunm.HeadLeft, // LivenessTypeEunm.HeadRight config.livenessTypeList = livenesssList // 设置动作活体是否随机 config.isLivenessRandom = isLivenessRandom // 设置开启提示音 config.isSound = isOpenSound // 原图缩放系数 config.scale = FaceEnvironment.VALUE_SCALE // 抠图宽高的设定,为了保证好的抠图效果,建议高宽比是4:3 config.cropHeight = FaceEnvironment.VALUE_CROP_HEIGHT config.cropWidth = FaceEnvironment.VALUE_CROP_WIDTH // 抠图人脸框与背景比例 config.enlargeRatio = FaceEnvironment.VALUE_CROP_ENLARGERATIO // 检测超时设置 config.timeDetectModule = FaceEnvironment.TIME_DETECT_MODULE // 检测框远近比率 config.faceFarRatio = FaceEnvironment.VALUE_FAR_RATIO config.faceClosedRatio = FaceEnvironment.VALUE_CLOSED_RATIO (livenesssList as java.util.ArrayList).clear() //活体动作只用了眨眼 (livenesssList as java.util.ArrayList).add(LivenessTypeEnum.Eye) config.livenessTypeList = livenesssList FaceSDKManager.getInstance().faceConfig = config return true } /* * 初始化参数是否成功 * */ public fun initLicenseSuccess(): Boolean { if (!setFaceConfig()) { ToastUtils.showToast(App.instance, "百度人脸初始化失败,配置文件解析出错。") return false } else { return true; } } } }
活体检测界面
public class FaceLivenessExpActivity extends FaceLivenessActivity { private DefaultDialog mDefaultDialog; @SuppressLint("CheckResult") @Override public void onLivenessCompletion(FaceStatusNewEnum status, String message, HashMap<String, ImageInfo> base64ImageCropMap, HashMap<String, ImageInfo> base64ImageSrcMap, int currentLivenessCount) { super.onLivenessCompletion(status, message, base64ImageCropMap, base64ImageSrcMap, currentLivenessCount); if (status == FaceStatusNewEnum.OK && mIsCompletion) { //获取到最优图片 String firstImg = getBestImage(base64ImageCropMap, base64ImageSrcMap); if (!TextUtils.isEmpty(firstImg)) { //不为空,带图片回调,在回调地方,调接口上传,后台对BaiduFaceSDKManager.INSTANCE.getCallback().onCloseSDK(true, firstImg, null); finish() }else{ runOnUiThread(new Runnable() { @Override public void run() { ToastUtils.showToast(FaceLivenessExpActivity.this,"没有获取到人脸照片"); } }); } //只有两个Timeout } else if (status == FaceStatusNewEnum.FaceLivenessActionCodeTimeout || status == FaceStatusNewEnum.DetectRemindCodeTimeout) { showMessageDialog("活体检测", "采集超时"); } } /** * 获取最优图片 * * @param imageCropMap 抠图集合 * @param imageSrcMap 原图集合 */ private String getBestImage(HashMap<String, ImageInfo> imageCropMap, HashMap<String, ImageInfo> imageSrcMap) { String bmpStr = null; // 将抠图集合中的图片按照质量降序排序,最终选取质量最优的一张抠图图片 if (imageCropMap != null && imageCropMap.size() > 0) { List<Map.Entry<String, ImageInfo>> list1 = new ArrayList<>(imageCropMap.entrySet()); Collections.sort(list1, new Comparator<Map.Entry<String, ImageInfo>>() { @Override public int compare(Map.Entry<String, ImageInfo> o1, Map.Entry<String, ImageInfo> o2) { String[] key1 = o1.getKey().split("_"); String score1 = key1[2]; String[] key2 = o2.getKey().split("_"); String score2 = key2[2]; // 降序排序 return Float.valueOf(score2).compareTo(Float.valueOf(score1)); } }); // 获取抠图中的加密或非加密的base64 // int secType = mFaceConfig.getSecType(); // String base64; // if (secType == 0) { // base64 = list1.get(0).getValue().getBase64(); // } else { // base64 = list1.get(0).getValue().getSecBase64(); // } } // 将原图集合中的图片按照质量降序排序,最终选取质量最优的一张原图图片 if (imageSrcMap != null && imageSrcMap.size() > 0) { List<Map.Entry<String, ImageInfo>> list2 = new ArrayList<>(imageSrcMap.entrySet()); Collections.sort(list2, new Comparator<Map.Entry<String, ImageInfo>>() { @Override public int compare(Map.Entry<String, ImageInfo> o1, Map.Entry<String, ImageInfo> o2) { String[] key1 = o1.getKey().split("_"); String score1 = key1[2]; String[] key2 = o2.getKey().split("_"); String score2 = key2[2]; // 降序排序 return Float.valueOf(score2).compareTo(Float.valueOf(score1)); } }); bmpStr = list2.get(0).getValue().getBase64(); } return bmpStr; } @Override public void onBackPressed() { //进界面就显示采集提示,采集成功,会回调,关闭当前界面;这里回调不成 BaiduFaceSDKManager.INSTANCE.getCallback().onCloseSDK(false, null, null); super.onBackPressed(); } private void showMessageDialog(String title, String message) { if (mDefaultDialog == null) { DefaultDialog.Builder builder = new DefaultDialog.Builder(this); builder.setTitle(title). setMessage(message). setNegativeButton("确认", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { mDefaultDialog.dismiss(); onBackPressed(); } }); mDefaultDialog = builder.create(); mDefaultDialog.setCancelable(true); } mDefaultDialog.dismiss(); mDefaultDialog.show(); } }
活体检测后的结果:
效果: