基于百度人脸API的Android人脸识别组件

首先,我们提出几个关键词:百度人脸API、人脸图像连续采集、注册人脸库、图像编码、face_token、人脸检测接口、人脸对比接口

需要先注入两个库:

	implementation 'com.google.code.gson:gson:2.8.0'
	implementation 'com.github.zcolin:ZFrame:2.0.1'

1.百度人脸API创建应用

登录百度人脸的官网会有教程教你如何创建应用并获取两个重要的key:ApiKey和SecretKey,每个新应用的这两个key都是不一样的,用这两个key可以请求获取开发者权限access_token,这个token是请求人脸检测和人脸对比接口的必要参数,所以很重要,获取access_token的代码如下:

private static void getAuth(ZParamSubmitListener accessTokenListener) {
    // 获取token地址
    String authHost = "https://aip.baidubce.com/oauth/2.0/token?";
    String getAccessTokenUrl = authHost
            // 1. grant_type为固定参数
            + "grant_type=client_credentials"
            // 2. 官网获取的 API Key
            + "&client_id=" + ApiKey
            // 3. 官网获取的 Secret Key
            + "&client_secret=" + SecretKey;
    ZHttp.get(getAccessTokenUrl, new ZResponse(AccessTokenReply.class) {
        @Override
        public void onSuccess(Response response, AccessTokenReply resObj) {
            if (resObj != null) {
                if (accessTokenListener != null) {
                    accessTokenListener.submit(resObj.access_token);
                }
            }
        }

        @Override
        public void onError(int code, String error) {
            super.onError(code, error);
            if (accessTokenListener != null) {
                accessTokenListener.submit("");
            }
        }
    });
}

AccessTokenReply.java 

public class AccessTokenReply implements ZReply {
    @Override
    public boolean isSuccess() {
        return access_token != null;
    }

    @Override
    public int getReplyCode() {
        return 0;
    }

    @Override
    public String getErrorMessage() {
        return "token获取失败";
    }
    
    public String access_token;
}

需要注意的是这个access_token有使用期限(一个月),切记需要每30天进行定期更换,或者每次请求都拉取新的token

2.绘制圆形预览框

一般app上的人脸扫描预览界面都是一个圆形框,我们也不例外,继承自SurfaceView,重写draw方法绘制圆形区域:

public class CircleSurfaceView extends SurfaceView {

    private int radius;//预览框半径偏移,默认为屏幕高度的四分之一
    private int widthSize;
    private int heightSize;

    public CircleSurfaceView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView();
    }

    public CircleSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public CircleSurfaceView(Context context) {
        super(context);
        initView();
    }

    public void setRadius(int radius) {
        this.radius = radius;
    }

    private void initView() {
        this.setFocusable(true);
        this.setFocusableInTouchMode(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        widthSize = MeasureSpec.getSize(widthMeasureSpec);
        heightSize = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(widthSize, heightSize);
    }

    @Override
    public void draw(Canvas canvas) {
        Path path = new Path();
        path.addCircle(widthSize / 2, heightSize / 2, heightSize / 4 + radius, Path.Direction.CCW);
        canvas.clipPath(path, Region.Op.REPLACE);
        super.draw(canvas);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        int screenWidth = ScreenUtil.getScreenWidth(getContext());
        int screenHeight = ScreenUtil.getScreenHeight(getContext());
        w = screenWidth;
        h = screenHeight;
        super.onSizeChanged(w, h, oldw, oldh);
    }
}

3.连续采集人脸图像

人脸图像的连续采集要设置一堆的camera参数:

private Camera getMcamera() {
    Camera camera;
    try {
        camera = Camera.open(1);//开前置摄像头
        if (camera != null && !isPreview) {
            try {
                Camera.Parameters parameters = camera.getParameters();
                parameters.setPreviewSize(screenWidth, screenHeight); // 设置预览照片的大小
                parameters.setPreviewFpsRange(20, 30); // 每秒显示20~30帧
                parameters.setPictureFormat(ImageFormat.NV21); // 设置图片格式
                parameters.setPictureSize(screenWidth, screenHeight); // 设置照片的大小
                parameters.setPreviewFrameRate(1);// 每秒从摄像头里面获得1个画面 帧率过高可能会导致内存溢出
                camera.setPreviewDisplay(mHolder); // 通过SurfaceView显示取景画面
                camera.setPreviewCallback((data, camera1) -> {
                    Camera.Size size = camera1.getParameters().getPreviewSize();
                    try {
                        // 调用image.compressToJpeg()将YUV格式图像数据data转为jpg格式
                        YuvImage image = new YuvImage(data, ImageFormat.NV21, size.width, size.height, null);
                        if (image != null) {
                            ByteArrayOutputStream outStream = new ByteArrayOutputStream();
                            image.compressToJpeg(new Rect(0, 0, size.width, size.height), 80, outStream);
                            ByteArrayOutputStream stream = new ByteArrayOutputStream();
                            image.compressToJpeg(new Rect(0, 0, size.width, size.height), 80, stream);
                            Bitmap bmp = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());
                            String pictureName = "";
                            if (type == 0) {//人脸录入操作
                                pictureName = "answer.jpg";
                            } else if (type == 1) {//身份验证操作
                                pictureName = "confirm.jpg";
                            }
                            System.out.println(pictureName);
                            saveBitmap(bmp, pictureName);//轮流保存每一张图片,这个方法中包括了录入和验证的功能逻辑
                            outStream.flush();
                        }
                    } catch (Exception ex) {
                        Log.e("Sys", "Error:" + ex.getMessage());
                    }
                });
                camera.autoFocus(null); // 自动对焦
                camera.setDisplayOrientation(getAngel());
                camera.startPreview(); // 开始预览
            } catch (Exception e) {
                e.printStackTrace();
            }
            isPreview = true;
        }
    } catch (Exception e) {
        camera = null;
        e.printStackTrace();
        System.out.println("无法获取摄像头");
    }
    return camera;
}

这个方法中的部分变量是成员变量:

int type = 0;
CircleSurfaceView mPreview;
SurfaceHolder mHolder;
int screenWidth, screenHeight;//640,480
boolean isPreview = false; // 是否在预览中

4.采集之后保存到本地

每一帧的图片名都是一样的,只有检测完一张图片并且失败之后才会检测下一张图片,status是一个成员变量,1代表继续请求接口,2代表停止请求接口,如果录入失败或者校验失败status会重新赋值为1进行下一张图片的数据请求。而有一种例外的情况就是我们请求人脸比对接口返回的相似度值在0-100,取90以上代表为同一个人即身份验证成功,若返回值小于90则不为同一个人即身份校验失败,这种情况下status不会重新赋值为1,它会回调校验失败的接口进行“非用户本人”的身份错误提示信息。录入失败的情况不需要处理,因为录入失败说明没有检测到人脸,失败了就继续检测,直到检测出人脸即为成功,当然你也可以设置时间,检测录入时间过长时你可以判定为超时操作,回调接口就由你自己去写喽

private void saveBitmap(Bitmap bitmap, final String pictureName) {
    final File imgFile = new File(getContext().getCacheDir().getPath() + "/" + pictureName);
    if (imgFile.exists() && imgFile.isFile()) {
        imgFile.delete();
    }
    FileOutputStream out;
    try {
        out = new FileOutputStream(imgFile);
        if (bitmap.compress(Bitmap.CompressFormat.PNG, 60, out)) {
            out.flush();
            out.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    if (type == 0 && status == 1) {
        status = 2;
        tvMsg.setText("录入中,请摇摇头眨眨眼...");
        FaceMgr.detect(getContext().getCacheDir().getPath() + "/" + pictureName, detectResult -> {
            if (detectResult != null) {
                String num = detectResult.get("num");
                String faceToken = detectResult.get("face_token");
                if (Integer.valueOf(detectResult.get("code")) == 0 && num.equals("1")) {
                    SPUtil.putString("face_token", faceToken);
                    FaceMgr.add(faceToken, isSuccess -> false);
                    sendMessage(0, faceToken);
                    releaseCamera();
                } else {
                    status = 1;
                }
            } else {
                status = 1;
            }
            return false;
        });
    } else if (type == 1 && status == 1) {
        tvMsg.setText("验证中,请摇摇头眨眨眼...");
        status = 2;
        FaceMgr.detect(getContext().getCacheDir().getPath() + "/" + pictureName, detectResult -> {
            String num = detectResult.get("num");
            String faceToken = detectResult.get("face_token");
            if (Integer.valueOf(detectResult.get("code")) == 0 && num.equals("1")) {
                FaceMgr.match(SPUtil.getString("face_token", ""), faceToken, matchResult -> {
                    String score = matchResult.get("score");
                    if (Integer.valueOf(matchResult.get("code")) == 0) {
                        if (Double.valueOf(score) >= 90) {
                            sendMessage(1, score);
                            releaseCamera();
                        } else {
                            sendMessage(2, null);
                            releaseCamera();
                        }
                    } else {
                        status = 1;
                    }
                    return false;
                });
            } else {
                status = 1;
            }
            return false;
        });
    }
}

sendMessage方法就是异步实现成功或者失败的响应回调:

private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 0:
                faceEventListener.settingSuccess(msg.obj.toString());
                break;
            case 1:
                faceEventListener.matchingSuccess(msg.obj.toString());
                break;
            case 2:
                faceEventListener.matchingFail();
                break;
            default:
                break;
        }
    }
};
private void sendMessage(int code, String value) {
    Message msg = new Message();
    msg.what = code;
    msg.obj = value;
    mHandler.sendMessage(msg);
}

事件接口:

private FaceEventListener faceEventListener = new FaceEventListener() {
    @Override
    public void settingSuccess(String token) {
        Toast.makeText(getContext(), "录入成功!", Toast.LENGTH_LONG).show();
    }

    @Override
    public void matchingSuccess(String score) {
        Toast.makeText(getContext(), "验证成功!", Toast.LENGTH_LONG).show();
    }

    @Override
    public void matchingFail() {
        Toast.makeText(getContext(), "非用户本人!", Toast.LENGTH_LONG).show();
    }
};
public interface FaceEventListener {
    /**
     * 录入成功后 回调
     */
    void settingSuccess(String token);

    /**
     * 验证成功后 回调
     */
    void matchingSuccess(String score);

    /**
     * 验证失败后 回调
     */
    void matchingFail();
}

5.FaceMgr业务类

这个类下面的操作主要有人脸检测、人脸库注册、人脸比对以及access_token的获取:

public class FaceMgr {

    //从百度人脸API官网创建应用并获取这两个key,这俩key比较隐私,自己去百度官网获取就好了
    private static String ApiKey = "*****************";
    private static String SecretKey = "*****************************";
    private static void getAuth(ZParamSubmitListener accessTokenListener) {
        // 获取token地址
        String authHost = "https://aip.baidubce.com/oauth/2.0/token?";
        String getAccessTokenUrl = authHost
                // 1. grant_type为固定参数
                + "grant_type=client_credentials"
                // 2. 官网获取的 API Key
                + "&client_id=" + ApiKey
                // 3. 官网获取的 Secret Key
                + "&client_secret=" + SecretKey;
        ZHttp.get(getAccessTokenUrl, new ZResponse(AccessTokenReply.class) {
            @Override
            public void onSuccess(Response response, AccessTokenReply resObj) {
                if (resObj != null) {
                    if (accessTokenListener != null) {
                        accessTokenListener.submit(resObj.access_token);
                    }
                }
            }

            @Override
            public void onError(int code, String error) {
                super.onError(code, error);
                if (accessTokenListener != null) {
                    accessTokenListener.submit("");
                }
            }
        });
    }

    /**
     * 注册到人脸库 否则facetoken会失效
     */
    public static void add(String token, ZParamSubmitListener onAddResultListener) {
        getAuth(accessToken -> {
            String url = "https://aip.baidubce.com/rest/2.0/face/v3/faceset/user/add?access_token=" + accessToken;
            try {
                Map map = new HashMap<>();
                map.put("image", token);
                map.put("image_type", "FACE_TOKEN");
                map.put("group_id", "group_demo");
                map.put("user_id", "user1");
                map.put("liveness_control", "NORMAL");
                map.put("quality_control", "LOW");

                ZHttp.post(url, map, new ZResponse(FaceAddReply.class) {

                    @Override
                    public void onSuccess(Response response, FaceAddReply resObj) {
                        onAddResultListener.submit(true);
                    }

                    @Override
                    public void onError(int code, String error) {
                        super.onError(code, error);
                        onAddResultListener.submit(false);
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        });
    }

    /**
     * 人脸对比
     */
        public static void match(String token1, String token2, ZParamSubmitListener> onMatchResultListener) {
            getAuth(accessToken -> { 
                    String url = "https://aip.baidubce.com/rest/2.0/face/v3/match?access_token=" + accessToken;
                    try {
                        List> images = new ArrayList<>();

                        Map map1 = new HashMap<>();
                        map1.put("image", token1);
                        map1.put("image_type", "FACE_TOKEN");
                        map1.put("face_type", "LIVE");
                        map1.put("quality_control", "NONE");
                        map1.put("liveness_control", "HIGH");

                        Map map2 = new HashMap<>();
                        map2.put("image", token2);
                        map2.put("image_type", "FACE_TOKEN");
                        map2.put("face_type", "LIVE");
                        map2.put("quality_control", "NONE");
                        map2.put("liveness_control", "HIGH");

                        images.add(map1);
                        images.add(map2);
                        String params = GsonUtil.beanToString(images);
                        Map resultMap = new HashMap<>();
                        ZHttp.postJson(url, params, new ZResponse(FaceMatchReply.class) {
                            @Override
                            public void onSuccess(Response response, FaceMatchReply resObj) {
                                if (resObj != null) {
                                    resultMap.put("code", String.valueOf(resObj.error_code));
                                    resultMap.put("score", String.valueOf(resObj.result.score));
                                } else {
                                    resultMap.put("code", "");
                                }
                                if (onMatchResultListener != null) {
                                    onMatchResultListener.submit(resultMap);
                                }
                            }

                            @Override
                            public void onError(int code, String error) {
                                super.onError(code, error);
                                if (onMatchResultListener != null) {
                                    resultMap.put("code", String.valueOf(code));
                                    resultMap.put("msg", error);
                                    onMatchResultListener.submit(resultMap);
                                }
                            }
                        });
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return false;
            });
        }

    /**
     * 人脸检测
     */
    public static void detect(String filePath, ZParamSubmitListener> onDetectResultListener) {
        getAuth(accessToken -> {
            String url = "https://aip.baidubce.com/rest/2.0/face/v3/detect?access_token=" + accessToken;
            try {
                byte[] bytes = FileUtil.readToByteArray(new File(filePath));
                String image = Base64Util.encode(bytes);
                Map map = new HashMap<>();
                map.put("image", image);
                map.put("face_field", "faceshape,facetype");
                map.put("image_type", "BASE64");
                String params = GsonUtil.beanToString(map);

                Map resultMap = new HashMap<>();
                ZHttp.postJson(url, params, new ZResponse(FaceDetectReply.class) {
                    @Override
                    public void onSuccess(Response response, FaceDetectReply resObj) {
                        if (resObj != null) {
                            resultMap.put("code", String.valueOf(resObj.error_code));
                            resultMap.put("num", String.valueOf(resObj.result.face_num));
                            resultMap.put("face_token", resObj.result.face_list.get(0).face_token);
                        } else {
                            resultMap.put("code", "");
                        }
                        if (onDetectResultListener != null) {
                            onDetectResultListener.submit(resultMap);
                        }
                    }

                    @Override
                    public void onError(int code, String error) {
                        super.onError(code, error);
                        if (onDetectResultListener != null) {
                            resultMap.put("code", String.valueOf(code));
                            resultMap.put("msg", error);
                            onDetectResultListener.submit(resultMap);
                        }
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        });
    }
}

总结:主体代码就是这些,百度人脸检测里面包括了活体检测,即手机屏幕前进行面部扫描的得是个人,不能是一张照片或是一个雕塑,我也试过用照片去扫描,当然结果肯定是失败的,但是值得考虑的是3D打印和人工智能的发展,以后弄个一模一样的机器人出来这个百度的人脸检测怕是就不好用了,当然这只是一种胡思乱想......

源码下载地址:https://download.csdn.net/download/qq_37159335/10929905

 

 

你可能感兴趣的:(基于百度人脸API的Android人脸识别组件)