百度人脸识别开发套件5.SDK快速入门
goJhou 发布于2018-12 浏览:20562 回复:0
0
收藏

上节我们初始化了SDK,其余的像质量检测这些我就不详细的介绍了,文档中心给了一张一看就懂的图。这里我就把一些关于质量检测的表格列举出来供参考使用。人脸识别注重的还是用户与组的管理和1:1、1:N检索的功能~

简单的易懂的功能我就简要点一下就过了,如果不懂翻一翻文档中心和sdk工程就懂啦,很简单~
我想尽快带各位进入更深入的地方,所以这里就不再浪费篇幅讲解功能了,争取在最短篇幅里解决掉相关SDK的基本内容。

 

有关质量检测相关代码:
(这里光照范围那个值写错了,取值是从50~255)

参数 名称 默认值 取值范围
brightnessValue 图片爆光度 40f 50~255
blurnessValue 图像模糊度 0.7f 0~1.0f
occlusionValue 人脸遮挡阀值 0.5f 0~1.0f
headPitchValue 低头抬头角度 15 0~45
headYawValue 左右角度 15 0~45
headRollValue 偏头角度 15 0~45
minFaceSize 最小人脸检测值,小于此值的人脸将检测不出来,最小值为80 120 80~200
notFaceValue 人脸置信度 0.8f 0~1.0f
isCheckFaceQuality 是否检测人脸质量 False True/Flase

 

 

关于特征模式的切换,在SDK内有PreferencesUtil的工具类可以用于切换特征模式。

特征模式分2种,一种为生活照模式,一种为身份证模式。各模式针对的是注册照片而言所评定的,会基于选择模式走不同的算法进行识别,所以要根据场景来选。
抠出关键的代码:

public static final String TYPE_MODEL = "TYPE_MODEL";
public static final int RECOGNIZE_LIVE = 1;
public static final int RECOGNIZE_ID_PHOTO = 2;

PreferencesUtil.putInt(TYPE_MODEL, RECOGNIZE_LIVE);
 
PreferencesUtil.putInt(TYPE_MODEL, RECOGNIZE_ID_PHOTO);

 

 

 

 

活检设置也影响着比对和检索的算法,只有单目可以关闭活检,其余的默认就有活检(其实不用担心,只有单目的活检特别耗时)

活检设置用的也是PreferencesUtil。抠出关键代码如下:

public static final int TYPE_NO_LIVENSS = 1;
public static final int TYPE_RGB_LIVENSS = 2;
public static final int TYPE_RGB_IR_LIVENSS = 3;
public static final int TYPE_RGB_DEPTH_LIVENSS = 4;
public static final int TYPE_RGB_IR_DEPTH_LIVENSS = 5;
// 单目无活检:
PreferencesUtil.putInt(TYPE_LIVENSS, TYPE_NO_LIVENSS);
// 单目活检:
PreferencesUtil.putInt(TYPE_LIVENSS, TYPE_RGB_LIVENSS);
// 彩灰(双目)活检:
PreferencesUtil.putInt(TYPE_LIVENSS, TYPE_RGB_IR_LIVENSS);
// 彩深(结构光)活检:
PreferencesUtil.putInt(TYPE_LIVENSS, TYPE_RGB_DEPTH_LIVENSS);
// 彩灰深(红外结构光)活检:
PreferencesUtil.putInt(TYPE_LIVENSS, TYPE_RGB_IR_DEPTH_LIVENSS);

 

如果选择了结构光相关的活检策略,那还需要设置摄像机类型
抠出关键代码如下:

public static final String TYPE_CAMERA = "TYPE_CAMERA";
public static final int ORBBEC = 1;
public static final int IMIMECT = 2;
public static final int ORBBECPRO = 3;

PreferencesUtil.putInt(TYPE_CAMERA, ORBBEC);
PreferencesUtil.putInt(TYPE_CAMERA, IMIMECT);
PreferencesUtil.putInt(TYPE_CAMERA, ORBBECPRO);

 

 

组注册
建议组名用正则匹配一下:

Pattern pattern = Pattern.compile("^[0-9a-zA-Z_-]{1,}$");
Matcher matcher = pattern.matcher(groupId);

Group group = new Group();
group.setGroupId(groupId);
boolean ret = FaceApi.getInstance().groupAdd(group);

 

 

 

照片/流注册

// 获取组列表
List groupList = DBManager.getInstance().queryGroups(0, 1000);
// 仅抽取ID列表
for (Group group : groupList) {
    groupIds.add(group.getGroupId());
}

// 注册时使用人脸图片路径。Intent从android.content中引出
//读写权限请求
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
        != PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
            100);
    return;
}


private static final int REQUEST_CODE_PICK_IMAGE = 1000;
private String faceImagePath;
faceImagePath = null;
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
startActivityForResult(intent, REQUEST_CODE_PICK_IMAGE);

// 从相机识别时使用。
private FaceDetectManager detectManager;
//权限确认
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                    != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest
                    .permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        Manifest.permission.CAMERA}, 100);
                return;
            }
//获取当前所配置的活检类型
int type = PreferencesUtil.getInt(LivenessSettingActivity.TYPE_LIVENSS, LivenessSettingActivity
                    .TYPE_NO_LIVENSS);  

//如果是单目执行以下
Intent intent = new Intent(RegActivity.this, RgbDetectActivity.class);
intent.putExtra("source", SOURCE_REG);
startActivityForResult(intent, REQUEST_CODE_AUTO_DETECT);

//双目调用RgbIrLivenessActivity
//如果是深度还要再判定一下摄像头厂家
int cameraType = PreferencesUtil.getInt(GlobalFaceTypeModel.TYPE_CAMERA, GlobalFaceTypeModel.ORBBEC);
Intent intent3 = null;
if (cameraType == GlobalFaceTypeModel.ORBBEC) {
    intent3 = new Intent(RegActivity.this, OrbbecLivenessDetectActivity.class);
} else if (cameraType == GlobalFaceTypeModel.IMIMECT) {
    intent3 = new Intent(RegActivity.this, IminectLivenessDetectActivity.class);
} else if (cameraType == GlobalFaceTypeModel.ORBBECPRO) {
    intent3 = new Intent(RegActivity.this, OrbbecProLivenessDetectActivity.class);
}
if (intent3 != null) {
    intent3.putExtra("source", SOURCE_REG);
    startActivityForResult(intent3, REQUEST_CODE_AUTO_DETECT);
}

 

 

 

 

这里要着重介绍一下DetectActivity,以单目为例
前端代码剖析如下:




//布局标签
    

//百度封装的TexturePreView标签,用于预览USB流
    
            
//用于绘制人脸框用的TextureView
    
            
////显示检测的图片。用于调试,如果人脸sdk检测的人脸需要朝上,可以通过该图片判断
    
////用于给予一些提示,例如 请正视摄像头
    
            
            //下方是位于底部的布局,为了在UI中显示活体指数和一些tips信息
    
            
            
            
            
            
            
        
                
                
                
                
                
        
                
                
                
                
        
                
                
                
                
    

 

 

后端代码比较重要的抠出如下:

faceDetectManager = new FaceDetectManager(getApplicationContext());
// 从系统相机获取图片帧。
final CameraImageSource cameraImageSource = new CameraImageSource(this);
// 图片越小检测速度越快,闸机场景640 * 480 可以满足需求。实际预览值可能和该值不同。和相机所支持的预览尺寸有关。
// 可以通过 camera.getParameters().getSupportedPreviewSizes()查看支持列表。
cameraImageSource.getCameraControl().setPreferredPreviewSize(1280, 720);
// 设置最小人脸,该值越小,检测距离越远,该值越大,检测性能越好。范围为80-200

previewView.setMirrored(false);
// 设置预览 这里previewView是通过(PreviewView) findViewById(R.id.preview_view);抓取的控件对象
cameraImageSource.setPreviewView(previewView);
// 设置图片源
faceDetectManager.setImageSource(cameraImageSource);
//质检
faceDetectManager.setUseDetect(true);

//可以理解为控件透明。true为不透明
textureView.setOpaque(false);

// 不需要屏幕自动变黑。
textureView.setKeepScreenOn(true);

//获取相机方向
boolean isPortrait = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;

//适配朝向
if (isPortrait) {
    previewView.setScaleType(PreviewView.ScaleType.FIT_WIDTH);
    // 相机坚屏模式
   cameraImageSource.getCameraControl().setDisplayOrientation(CameraView.ORIENTATION_PORTRAIT);
} else {
    previewView.setScaleType(PreviewView.ScaleType.FIT_HEIGHT);
    // 相机横屏模式
    cameraImageSource.getCameraControl().setDisplayOrientation(CameraView.ORIENTATION_HORIZONTAL);
}

//选择摄像头
setCameraType(cameraImageSource);

 

 

 

setCameraType 选择摄像头

private void setCameraType(CameraImageSource cameraImageSource) {
        // TODO 选择使用前置摄像头
//         cameraImageSource.getCameraControl().setCameraFacing(ICameraControl.CAMERA_FACING_FRONT);

        // TODO 选择使用usb摄像头
        cameraImageSource.getCameraControl().setCameraFacing(ICameraControl.CAMERA_USB);
        // 如果不设置,人脸框会镜像,显示不准
//        previewView.getTextureView().setScaleX(-1);

        // TODO 选择使用后置摄像头
//        cameraImageSource.getCameraControl().setCameraFacing(ICameraControl.CAMERA_FACING_BACK);
//        previewView.getTextureView().setScaleX(-1);
    }

 

 

 

listener 设置回调,回调人脸检测结果。

// 设置回调,回调人脸检测结果。
faceDetectManager.setOnFaceDetectListener(new FaceDetectManager.OnFaceDetectListener() {
    @Override
    public void onDetectFace(int retCode, FaceInfo[] infos, ImageFrame frame) {
        // TODO 显示检测的图片。用于调试,如果人脸sdk检测的人脸需要朝上,可以通过该图片判断
         final Bitmap bitmap =
           Bitmap.createBitmap(frame.getArgb(), frame.getWidth(), frame.getHeight(), Bitmap.Config.ARGB_8888);
           //如果需要debug,解开下头
           
          // handler.post(new Runnable() {
            //  @Override
             // public void run() {
             //     testView.setImageBitmap(bitmap);
             // }
          // });
          
        checkFace(retCode, infos, frame);
        showFrame(frame, infos);
    }
});

 

 

 

解析Detect的返回json

//解析质检返回的json结果,处理相关逻辑。这里其中的filter是一个基于各类阈值相关条件给出的参考函数
private void checkFace(int retCode, FaceInfo[] faceInfos, ImageFrame frame) {
        if ( retCode == FaceTracker.ErrCode.OK.ordinal() && faceInfos != null) {
            FaceInfo faceInfo = faceInfos[0];
            String tip = filter(faceInfo, frame);
            displayTip(tip);
        } else {
            String tip = checkFaceCode(retCode);
            displayTip(tip);
        }
    }
    
    private String filter(FaceInfo faceInfo, ImageFrame imageFrame) {

        String tip = "";
        if (faceInfo.mConf < 0.6) {
            tip = "人脸置信度太低";
            return tip;
        }

        float[] headPose = faceInfo.headPose;
        if (Math.abs(headPose[0]) > 20 || Math.abs(headPose[1]) > 20 || Math.abs(headPose[2]) > 20) {
            tip = "人脸置角度太大,请正对屏幕";
            return tip;
        }

        int width = imageFrame.getWidth();
        int height = imageFrame.getHeight();
        // 判断人脸大小,若人脸超过屏幕二分一,则提示文案“人脸离手机太近,请调整与手机的距离”;
        // 若人脸小于屏幕三分一,则提示“人脸离手机太远,请调整与手机的距离”
        float ratio = (float) faceInfo.mWidth / (float) height;
        Log.i("liveness_ratio", "ratio=" + ratio);
        if (ratio > 0.6) {
            tip = "人脸离屏幕太近,请调整与屏幕的距离";
            return tip;
        } else if (ratio < 0.2) {
            tip = "人脸离屏幕太远,请调整与屏幕的距离";
            return tip;
        } else if (faceInfo.mCenter_x > width * 3 / 4 ) {
            tip = "人脸在屏幕中太靠右";
            return tip;
        } else if (faceInfo.mCenter_x < width / 4 ) {
            tip = "人脸在屏幕中太靠左";
            return tip;
        } else if (faceInfo.mCenter_y > height * 3 / 4 ) {
            tip = "人脸在屏幕中太靠下" ;
            return tip;
        } else if (faceInfo.mCenter_x < height / 4 ) {
            tip = "人脸在屏幕中太靠上";
            return tip;
        }
        int liveType = PreferencesUtil.getInt(TYPE_LIVENSS,                 .TYPE_NO_LIVENSS);
        if (liveType ==  TYPE_NO_LIVENSS) {
            saveFace(faceInfo, imageFrame);
        } else if (liveType ==  TYPE_RGB_LIVENSS) {

            if (rgbLiveness(imageFrame, faceInfo) > 0.9) {
                saveFace(faceInfo, imageFrame);
            } else {
                toast("rgb活体分数过低");
            }
        }


        return tip;
    }
    
    //保存照片的方法
    private void saveFace(FaceInfo faceInfo, ImageFrame imageFrame) {
        final Bitmap bitmap = FaceCropper.getFace(imageFrame.getArgb(), faceInfo, imageFrame.getWidth());
        if (source == RegActivity.SOURCE_REG) {
            // 注册来源保存到注册人脸目录
            File faceDir = FileUitls.getFaceDirectory();
            if (faceDir != null) {
                String imageName = UUID.randomUUID().toString();
                File file = new File(faceDir, imageName);
                // 压缩人脸图片至300 * 300,减少网络传输时间
                ImageUtils.resize(bitmap, file, 300, 300);
                Intent intent = new Intent();
                intent.putExtra("file_path", file.getAbsolutePath());
                setResult(Activity.RESULT_OK, intent);
                finish();
            } else {
                toast("注册人脸目录未找到");
            }
        } else {
            try {
                // 其他来源保存到临时目录
                final File file = File.createTempFile(UUID.randomUUID().toString() + "", ".jpg");
                // 人脸识别不需要整张图片。可以对人脸区别进行裁剪。减少流量消耗和,网络传输占用的时间消耗。
                ImageUtils.resize(bitmap, file, 300, 300);Intent intent = new Intent();
                intent.putExtra("file_path", file.getAbsolutePath());
                setResult(Activity.RESULT_OK, intent);
                finish();

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

 

 

 

 

 

绘制人脸框

private void showFrame(ImageFrame imageFrame, FaceInfo[] faceInfos) {
    Canvas canvas = textureView.lockCanvas();
    if (canvas == null) {
        textureView.unlockCanvasAndPost(canvas);
        return;
    }
    if (faceInfos == null || faceInfos.length == 0) {
        // 清空canvas
        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
        textureView.unlockCanvasAndPost(canvas);
        return;
    }
    canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);

    FaceInfo faceInfo = faceInfos[0];


    rectF.set(getFaceRect(faceInfo, imageFrame));

    // 检测图片的坐标和显示的坐标不一样,需要转换。
    previewView.mapFromOriginalRect(rectF);

    float yaw  = Math.abs(faceInfo.headPose[0]);
    float patch  = Math.abs(faceInfo.headPose[1]);
    float roll  = Math.abs(faceInfo.headPose[2]);
    if (yaw > 20 || patch > 20 || roll > 20) {
        // 不符合要求,绘制黄框
        paint.setColor(Color.YELLOW);

        String text = "请正视屏幕";
        float width = paint.measureText(text) + 50;
        float x = rectF.centerX() - width / 2;
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.FILL);
        canvas.drawText(text, x + 25, rectF.top - 20, paint);
        paint.setColor(Color.YELLOW);

    } else {
        // 符合检测要求,绘制绿框
        paint.setColor(Color.GREEN);
    }
    paint.setStyle(Paint.Style.STROKE);

    // 绘制框
    canvas.drawRect(rectF, paint);
    textureView.unlockCanvasAndPost(canvas);
}

 

 

以上代码都是截取自demo中的。可选择性使用。


人脸检索 视频流人脸库检索
因为真正投入到项目中的落地应用以视频流检索人脸库为首,所以先贴出这块代码。
之后如果有1:1和M:N相关的功能届时再抽出代码

这里也优先使用单目,我觉得会用单目了,双目和结构光也只是配置和UI调整上的问题了,因为SDK,一切都变的很简单。

大部分都经过Detect,所以在取流、质检都类似
在listener开始有部分不同

private void addListener() {
    // 设置回调,回调人脸检测结果。
    faceDetectManager.setOnFaceDetectListener(new FaceDetectManager.OnFaceDetectListener() {
        @Override
        public void onDetectFace(int retCode, FaceInfo[] infos, ImageFrame frame) {
            // TODO 显示检测的图片。用于调试,如果人脸sdk检测的人脸需要朝上,可以通过该图片判断
            final Bitmap bitmap =
                    Bitmap.createBitmap(frame.getArgb(), frame.getWidth(), frame.getHeight(), Bitmap.Config
                            .ARGB_8888);
            handler.post(new Runnable() {
                @Override
                public void run() {
                    testView.setImageBitmap(bitmap);
                }
            });
            if (retCode == FaceTracker.ErrCode.OK.ordinal() && infos != null) {
                asyncIdentity(frame, infos);
            }
            showFrame(frame, infos);

        }
    });
}


private void asyncIdentity(final ImageFrame imageFrame, final FaceInfo[] faceInfos) {

    //通过一个标识来解决并发问题
        if (identityStatus != IDENTITY_IDLE) {
            return;
        }

        es.submit(new Runnable() {
            @Override
            public void run() {
                if (faceInfos == null || faceInfos.length == 0) {
                    return;
                }
                int liveType = PreferencesUtil.getInt(LivenessSettingActivity.TYPE_LIVENSS, LivenessSettingActivity
                        .TYPE_NO_LIVENSS);
                if (liveType == LivenessSettingActivity.TYPE_NO_LIVENSS) {
                //这里只尝试去找检测出的第一张脸,后续可以修改实现多脸比对
                    identity(imageFrame, faceInfos[0]);
                } else if (liveType == LivenessSettingActivity.TYPE_RGB_LIVENSS) {

                    if (rgbLiveness(imageFrame, faceInfos[0]) > FaceEnvironment.LIVENESS_RGB_THRESHOLD) {
                        identity(imageFrame, faceInfos[0]);
                    } else {
                        // toast("rgb活体分数过低");
                    }
                }
            }
        });
    }
    
    
    
    private void identity(ImageFrame imageFrame, FaceInfo faceInfo) {
        float raw = Math.abs(faceInfo.headPose[0]);
        float patch = Math.abs(faceInfo.headPose[1]);
        float roll = Math.abs(faceInfo.headPose[2]);
        // 人脸的三个角度大于20不进行识别
        if (raw > 20 || patch > 20 || roll > 20) {
            return;
        }

        identityStatus = IDENTITYING;

        long starttime = System.currentTimeMillis();
        int[] argb = imageFrame.getArgb();
        int rows = imageFrame.getHeight();
        int cols = imageFrame.getWidth();
        int[] landmarks = faceInfo.landmarks;

        int type = PreferencesUtil.getInt(GlobalFaceTypeModel.TYPE_MODEL, GlobalFaceTypeModel.RECOGNIZE_LIVE);
        IdentifyRet identifyRet = null;
        if (type == GlobalFaceTypeModel.RECOGNIZE_LIVE) {
            identifyRet = FaceApi.getInstance().identity(argb, rows, cols, landmarks, groupId);
        } else if (type == GlobalFaceTypeModel.RECOGNIZE_ID_PHOTO) {
            identifyRet = FaceApi.getInstance().identityForIDPhoto(argb, rows, cols, landmarks, groupId);
        }
        if (identifyRet != null) {
            //这里可以拿到ID和分数。可以再对分数进行筛选一次。demo中<80的都滤掉了。
            displayUserOfMaxScore(identifyRet.getUserId(), identifyRet.getScore());
        }
        identityStatus = IDENTITY_IDLE;
        displayTip("特征抽取对比耗时:" + (System.currentTimeMillis() - starttime), featureDurationTv);
    }

 

 

那在本篇中快速的过了一下如何修改SDK的特征模型(生活照、身份证),如何添加组、用户,如何通过相册注册和利用单目视频流进行注册和1:N检索。
本篇的代码也都是从demo中提炼而来的,省去了大部分的View层UI介绍和Controller层的控制,着重介绍了SDK代码。如果对双目和结构光的可以顺着本篇的逻辑进行分析。

之后在介绍落地场景时也会基于不同的摄像头开分篇呢,届时欢迎参考。

下一篇,我将脱离DEMO代码,自己上手写一个从注册完成到视频流识别的代码。
希望能帮助到各位使用者~

收藏
点赞
0
个赞
TOP
切换版块