基于Face++,使用Spring Boot+Elemnet-UI实现人脸识别登录。

上一篇文章只是封装了人脸检测的一些工具类,要实现刷脸登录,我们首先得思考一个问题,就是如何将我们的人脸和登录账户信息进行绑定,让它通过人脸就能识别到当前登录的账户是谁的账户。

这个问题我们可以这样解决,我浏览Face++的官网发现它还有人脸比对的一个API,我点进去一看,这不就是实现人脸登录的关键API吗?

 继续查看它需要的参数。

 我们可以看到它需要的是两个图片的face_token,用两个图片的face_token,进行对比,得出它是不是同一个人。那就简单多了。许多问题都迎刃而解。接下来就是我具体的实现过程,大家可以参考一下。

1.准备工作

首先,在我们封装的工具类中,添加人脸比对的方法:

   /**
     * 对比两个人脸的face_token
     * @param face_token1
     * @param face_token2
     * @return 如果为true则是同一个人
     * @throws Exception
     */
    public static boolean compareFace(String face_token1, String face_token2) throws Exception {
        String url = "https://api-cn.faceplusplus.com/facepp/v3/compare";
        HashMap<String, byte[]> byteMap = new HashMap<>();
        map.put("face_token1", face_token1);
        map.put("face_token2", face_token2);
        byte[] bacd = HttpUtils.post(url, map, null);
        String str = new String(bacd);
        System.out.println("str = " + str);
        //转为为对象,获取检索的置信度
        if (str.indexOf("error_message") != -1) {
            return false;
        }
        JSONObject jsonObject = JSONObject.parseObject(str);
        JSONObject thresholds = (JSONObject) jsonObject.get("thresholds");
        //获取到十万份之一的阈值
        Double le5 = thresholds.getDoubleValue("1e-5");
        //获取置信率
        Double confidence = jsonObject.getDoubleValue("confidence");
        if (confidence > le5) {
            //说明存在这个人脸,比对成功
            return true;
        }
        return false;
}

其次,在登录账户的数据库中,添加两个字段,用于保存图片的face_token和图片,由于我配置了一个FastDFS的文件服务器,我的图片都是保存在文件服务器上面,所有这里我就用varchar类型的数据保存我的文件地址即可,我还配置了Nginx服务器,所以可以直接通过保存图片的地址直接预览图片,各位可以根据需求设置自己的字段。

注:如果大家没有配置文件服务器,可以增加blob类型的字段,用于保存图片的二进制数据。

数据中增加了两个字段,我们映射数据库的实体类也得增加相应的属性。注意属性的命名要和数据库中的字段对应。

2.正文开始

完成准备工作之后,开始编写后端的接口。

注:我们在登录时,前端返回的数据是图片的64位编码。(这里有一个坑,也不算坑,就是比较麻烦,因为我们封装的人脸检测的方法,它的参数是File类型,我们需要将图片保存到本地,然后在将图片作为参数去请求接口,后面我发现它的参数除了File类型的文件,还有直接就是Base64编码的图片。如图)

所以为了方便,我们在去多封装一个参数是base64编码的图片的人脸检测的方法:

    /**
     * 通过图片的Base64为编码获取图片的face_token
     * @param base64img
     * @return
     * @throws Exception
     */
    public static String getFaceTokenBase64Img(String base64img) throws Exception {
        //定义要请求接口的地址
        String url = "https://api-cn.faceplusplus.com/facepp/v3/detect";
        //封装参数
        map.put("image_base64",base64img);
        //请求Face++人脸检测的图片,拿到Face++接口返回的数据
        byte[] bacd = HttpUtils.post(url, map, null);
        //拿到接口返回的Json数据,提取出Face_Token
        String str = new String(bacd);
        System.out.println(str);
        //判断接口返回的数据是否出错
        if (str.indexOf("error_message") != -1) {
            System.out.println("请求发送错误!");
            return null;
        }
        //转换为Json对象
        JSONObject jsonObject = JSONObject.parseObject(str);
        Integer face_num = jsonObject.getInteger("face_num");
        if (face_num == 1) {
            //存在人脸,取出face_token
            JSONArray facesArray = (JSONArray) jsonObject.get("faces");
            JSONObject faceJson = (JSONObject) facesArray.get(0);
            String face_token = faceJson.getString("face_token");
            return face_token;
        }
        return null;
    }

2.1   后端人脸注册的接口

  我们需要通过人脸就能知道这个人脸属于那个账户,通过人脸识别就能自动登录账户,所以我们需要将人脸和账户绑定起来。

我们知道Face++中对于人脸检测的图片会返回一个face_token,我们只需要将前端传递过来的图片的base64编码,获取到它的face_token ,然后将他和前端返回过来的用户绑定在一起,实际上就是保存用户的face_token,也就是前面我们在登录表中新增face_token字段。

代码如下,代码中有相应的注释:

  /**
     * 注册人脸,将人脸和账户信息绑定在一起
     * @param base64img
     * @param userId
     * @return
     */
    @Override
    public Result registerFace(String base64img, Integer userId) {
        //根据id获取需要绑定的用户
        User user = userMapper.selectById(userId);
        //获取face_token
        try {
            String base = base64img.split(",")[1];
            String faceToken = FaceUtils.getFaceTokenBase64img(base);
            //保存face_token到用户中
            user.setFaceToken(faceToken);
            //获取base64编码数据
            //将base64转化为byte数组
            byte[] img = Base64.getDecoder().decode(base);
            //设置用户的img
            user.setImg(img);
            //最后将其保存到数据库,即可完成人脸绑定
            int updateById = userMapper.updateById(user);
            //如果受影响的行数为1,则保存成功
            if(updateById==1){
                return Result.success("注册成功!");
            }
            return Result.error("注册失败");

        } catch (Exception e) {
            e.printStackTrace();
            return Result.error("出错了!");
        }
    }
}

2.2 人脸注册前端代码编写:

前端使用Vue2.0+Element-UI进行编写,大家可以随意发挥,主要的功能就是点击注册人脸,能够打开摄像头,拍照传递给后端即可。

比如我们前端页面是这样的:

 我们给他增加一个列来展示注册的人脸,在操作中增加一个采集人脸的按钮,当我们点击采集人脸,会打开摄像头进行拍照,同时将图片和用户id传递给后端。

修改后的效果如下:

 2.3 人脸注册按钮事件编写:

 当我们点击registerFace按钮,我们会打开摄像头,进行拍摄,然后将数据传递给后端。

主要代码如下:

    startCamera() { //打开摄像头
      const constraints = {
        video: {
          width: {exact: 400},
          height: {exact: 400},
        },
      };
      navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
        this.$refs.video.srcObject = stream;
        this.stream = stream;
      });
    },
    takePhoto() { //点击拍照
      const canvas = document.createElement("canvas");
      const video = this.$refs.video;
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
      canvas.getContext("2d").drawImage(video, 0, 0, canvas.width, canvas.height);
      this.photo = canvas.toDataURL("image/png");
    },
    beforeDestroy() { //关闭摄像头
      if (this.stream) {
        this.stream.getTracks().forEach((track) => {
          track.stop();
        });
      }
    },
    uploadPhoto() { 
      //封装参数
      const formData = new FormData();
      formData.append('base64img', this.photo); //图片的base64编码
      formData.append('userId', this.user.id);  //用户id
      // 向后端接口发送请求
      this.request.post("/user/login/face/register",formData).then((data) => {
        if (data) {
         this.$message.success("人脸注册成功")
          this.load() //初始化当前数据
          this.beforeDestroy()  //关闭摄像头
          this.dialogVisible1 = false; //关闭dia标签
        }
      })
    },

2.4   后端刷脸登陆的接口

完成人脸注册,将人脸信息和账户绑定之后,我们就可以进行刷脸登录了。

刷脸登录其实很简单,前端向后端传递拍摄到的图片64编码,后端收到图片的base64编码,然后调用人脸检测的接口,得到face_token,然后查询所有的登录用户,得到一个用户列表,遍历列表,拿到每一个用户的face_token,将它和前端传递过来的图片的face_token调用人脸比对的api进行比对,如果返回true,则说明当前登录的是这个用户。接下来就是生成token返回给前端。

下面是刷脸登录的后端接口:

    @PostMapping("/login/face")
    public Result loginFace(@RequestBody String base64img){
        //获取所有的登录用户
        List<User> list = userService.list();
        boolean isFace=false;
        String username=null;
        try {
            //获取前端传递的人脸的face_token
            String base64 = base64img.split(",")[1];
            String face_token = FaceUtils.getFaceTokenBase64img(base64);
            System.out.println("face_token ====================== " + face_token);
            //遍历所有的用户,对内一个用户的face_token进行比较
            for(User user:list) {
                String userFaceTokenfaceToken = user.getFaceToken();
                try {
                    //调用人脸比对的接口
                    if(userFaceTokenfaceToken!=null) {
                        boolean isEqu = FaceUtils.compareFace(face_token, userFaceTokenfaceToken);
                        //说明找到和人脸匹配的张账户,
                        if (isEqu) {
                            isFace = true;
                            username = user.getUsername(); //提取当前登录的用户
                            break;
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            //如果人脸比对成功,说明前端传递过来的人脸,是绑定了账户的,说明人脸验证成功
            //账户名称就是之前提取的username
            //接下来在if中,我们进行登录成功设置,比如生成token返回给前端,设置全局对象等。
            if(isFace){
                //根据用户名称获取到用户
                User user = userService.getOne(new QueryWrapper<User>().eq("username", username));
                // 设置token,编写你自己的登录成功,也就是账号密码验证成功之后的逻辑。
                //TODO
  
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Result.error("当前人脸没有绑定账户!请注册人脸后重试!");
    }

2.2   前端刷脸登录

在登录页面增加一个刷脸登录的按钮,点击这个按钮会打开摄像头,然后拍摄一张照片发送给后端登录接口。

关键代码如下(其实和人脸注册差不多):

            loginFace(){
                this.dialogVisible=true
                this.startCamera()
                //等待一秒钟进行拍照
                setTimeout(() => {
                    this.takePhoto()
                    const formData = new FormData();
                    formData.append('base64img', this.photo);
                    this.request.post("/user/login/face",formData).then(res=>{
                        console.log(res)
                        if (res.code === '200') {
                            //编写拿到后端返回的数据时的逻辑
                            //比如保存token,保存用户信息等。
    




                                this.$message.success("登录成功")
                                setRoutes()
                                if (res.data.role === 'CUSTOMER') {
                                    // 存储用户信息到浏览器
                                    this.$router.push("/MallHome");
                                } else {
                                    this.$router.push("/")
                                }
                            } else {
                                this.$message.error(res.msg)
                            }
                        })
                }, 1000); // 1000毫秒 = 1秒
            },
            startCamera() {
                const constraints = {
                    video: {
                        width: { exact: 400 },
                        height: { exact: 400 },
                    },
                };
                navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
                    this.$refs.video.srcObject = stream;
                    this.stream = stream;
                });
            },
            takePhoto() {
                const canvas = document.createElement("canvas");
                const video = this.$refs.video;
                canvas.width = video.videoWidth;
                canvas.height = video.videoHeight;
                canvas.getContext("2d").drawImage(video, 0, 0, canvas.width, canvas.height);
                this.photo = canvas.toDataURL("image/png");
            },
            beforeDestroy() {
                if (this.stream) {
                    this.stream.getTracks().forEach((track) => {
                        track.stop();
                    });
                }
            },

这样我们就实现了刷脸登录,以上内容仅供大家参考,主要的是思路。有问题的小伙伴欢迎在后台私信我。