你们的程序媛女朋友写了个文章《腾讯云神图人脸识别搭配UniApp小程序实战》

207 阅读24分钟

前言

公司最近要搭建一个小程序打卡签到功能需要使用人脸识别进行打卡那么经过调研选择了腾讯云神图人脸识别系统来进行整合业务,刚刚好给大家分享一下本篇文章即可复制到工程当中直接使用哦~

本次项目使用技术栈

后端: SpringBoot3.1.x、Mysql8.0、MybatisPlus

小程序: Uniapp、Vue3

项目案例图

注: 小程序脚手架前往仓库拉取即可

介绍

腾讯云神图·人脸识别(Face Recognition)基于腾讯优图强大的面部分析技术,提供包括人脸检测与分析、比对、搜索、验证、五官定位、活体检测等多种功能,为开发者和企业提供高性能高可用的人脸识别服务。 可应用于在线娱乐、在线身份认证等多种应用场景,充分满足各行业客户的人脸属性识别及用户身份确认等需求。

本文将介绍使用 人脸检测与分析、人脸验证(人员验证、人脸验证)、人脸静态活体检测.

人脸检测与分析

检测给定图片中的人脸(Face)的位置、相应的面部属性和人脸质量信息,位置包括 (x,y,w,h),面部属性包括性别(gender)、年龄(age)、表情(expression)、魅力(beauty)、眼镜(glass)、发型(hair)、口罩(mask)和姿态 (pitch,roll,yaw),人脸质量信息包括整体质量分(score)、模糊分(sharpness)、光照分(brightness)和五官遮挡分(completeness)。

请求参数

参数名称必选类型描述
MaxFaceNumInteger最多处理的人脸数目。默认值为1(仅检测图片中面积最大的那张人脸),最大值为120。 此参数用于控制处理待检测图片中的人脸个数,值越小,处理速度越快。 示例值:1
MinFaceSizeInteger人脸长和宽的最小尺寸,单位为像素。 默认为34。建议不低于34。 低于MinFaceSize值的人脸不会被检测。 示例值:40
ImageString图片 base64 数据,base64 编码后大小不可超过5M。 支持PNG、JPG、JPEG、BMP,不支持 GIF 图片。
UrlString图片的 Url 。对应图片 base64 编码后大小不可超过5M。 Url、Image必须提供一个,如果都提供,只使用 Url。 图片存储于腾讯云的Url可保障更高下载速度和稳定性,建议图片存储于腾讯云。 非腾讯云存储的Url速度和稳定性可能受一定影响。 支持PNG、JPG、JPEG、BMP,不支持 GIF 图片。 示例值:test.image.myqcloud.com/testB.jpg
NeedFaceAttributesInteger是否需要返回人脸属性信息(FaceAttributesInfo)。0 为不需要返回,1 为需要返回。默认为 0。 非 1 值均视为不需要返回,此时 FaceAttributesInfo 不具备参考意义。 最多返回面积最大的 5 张人脸属性信息,超过 5 张人脸(第 6 张及以后的人脸)的 FaceAttributesInfo 不具备参考意义。 提取人脸属性信息较为耗时,如不需要人脸属性信息,建议关闭此项功能,加快人脸检测速度。 示例值:0
NeedQualityDetectionInteger是否开启质量检测。0 为关闭,1 为开启。默认为 0。 非 1 值均视为不进行质量检测。 最多返回面积最大的 30 张人脸质量分信息,超过 30 张人脸(第 31 张及以后的人脸)的 FaceQualityInfo不具备参考意义。 建议:人脸入库操作建议开启此功能。 示例值:0
FaceModelVersionString人脸识别服务所用的算法模型版本。目前入参支持 “2.0”和“3.0“ 两个输入。 2020年4月2日开始,默认为“3.0”,之前使用过本接口的账号若未填写本参数默认为“2.0”。 不同算法模型版本对应的人脸识别算法不同,新版本的整体效果会优于旧版本,建议使用“3.0”版本。 示例值:3.0
NeedRotateDetectionInteger是否开启图片旋转识别支持。0为不开启,1为开启。默认为0。本参数的作用为,当图片中的人脸被旋转且图片没有exif信息时,如果不开启图片旋转识别支持则无法正确检测、识别图片中的人脸。若您确认图片包含exif信息或者您确认输入图中人脸不会出现被旋转情况,请不要开启本参数。开启后,整体耗时将可能增加数百毫秒。 示例值:0

返回参数

人脸信息列表。

名称类型描述
XInteger人脸框左上角横坐标。 人脸框包含人脸五官位置并在此基础上进行一定的扩展,若人脸框超出图片范围,会导致坐标负值。 若需截取完整人脸,可以在完整分completess满足需求的情况下,将负值坐标取0。
YInteger人脸框左上角纵坐标。 人脸框包含人脸五官位置并在此基础上进行一定的扩展,若人脸框超出图片范围,会导致坐标负值。 若需截取完整人脸,可以在完整分completess满足需求的情况下,将负值坐标取0。
WidthInteger人脸框宽度。
HeightInteger人脸框高度。
FaceAttributesInfoFaceAttributesInfo人脸属性信息,包含性别( gender )、年龄( age )、表情( expression )、 魅力( beauty )、眼镜( glass )、口罩(mask)、头发(hair)和姿态 (pitch,roll,yaw )。只有当 NeedFaceAttributes 设为 1 时才返回有效信息。
FaceQualityInfoFaceQualityInfo人脸质量信息,包含质量分(score)、模糊分(sharpness)、光照分(brightness)、遮挡分(completeness)。只有当NeedFaceDetection设为1时才返回有效信息。 注意:此字段可能返回 null,表示取不到有效值。

重要的是 FaceAttributesInfo、FaceQualityInfo 两个对象函数了

人脸验证(人员验证)

给定一张人脸图片和一个 PersonId,判断图片中的人和 PersonId 对应的人是否为同一人。PersonId 请参考人员库管理相关接口。 本接口会将该人员(Person)下的所有人脸(Face)进行融合特征处理,即若某个Person下有4张 Face,本接口会将4张 Face 的特征进行融合处理,生成对应这个 Person 的特征,使人员验证(确定待识别的人脸图片是某人员)更加准确。

和人脸比对相关接口不同的是,人脸验证相关接口用于判断 “此人是否是此人”,“此人”的信息已存于人员库中,“此人”可能存在多张人脸图片;而人脸比对相关接口用于判断两张人脸的相似度。

请求参数

参数名称必选类型描述
PersonIdString待验证的人员ID。人员ID具体信息请参考人员库管理相关接口。
ImageString图片 base64 数据。 若图片中包含多张人脸,只选取其中人脸面积最大的人脸。 支持PNG、JPG、JPEG、BMP,不支持 GIF 图片。
UrlString图片的 Url 。 图片的 Url、Image必须提供一个,如果都提供,只使用 Url。 图片存储于腾讯云的Url可保障更高下载速度和稳定性,建议图片存储于腾讯云。 非腾讯云存储的Url速度和稳定性可能受一定影响。 若图片中包含多张人脸,只选取其中人脸面积最大的人脸。 支持PNG、JPG、JPEG、BMP,不支持 GIF 图片。
QualityControlInteger图片质量控制。 0: 不进行控制; 1:较低的质量要求,图像存在非常模糊,眼睛鼻子嘴巴遮挡至少其中一种或多种的情况; 2: 一般的质量要求,图像存在偏亮,偏暗,模糊或一般模糊,眉毛遮挡,脸颊遮挡,下巴遮挡,至少其中三种的情况; 3: 较高的质量要求,图像存在偏亮,偏暗,一般模糊,眉毛遮挡,脸颊遮挡,下巴遮挡,其中一到两种的情况; 4: 很高的质量要求,各个维度均为最好或最多在某一维度上存在轻微问题; 默认 0。 若图片质量不满足要求,则返回结果中会提示图片质量检测不符要求。
NeedRotateDetectionInteger是否开启图片旋转识别支持。0为不开启,1为开启。默认为0。本参数的作用为,当图片中的人脸被旋转且图片没有exif信息时,如果不开启图片旋转识别支持则无法正确检测、识别图片中的人脸。若您确认图片包含exif信息或者您确认输入图中人脸不会出现被旋转情况,请不要开启本参数。开启后,整体耗时将可能增加数百毫秒。

使用 人员ID、Image 就行了

返回参数

参数名称类型描述
ScoreFloat给定的人脸照片与 PersonId 对应的相似度。若 PersonId 下有多张人脸(Face),会融合多张人脸信息进行验证。
IsMatchBoolean是否为同一人的判断。
FaceModelVersionString人脸识别所用的算法模型版本。
RequestIdString唯一请求 ID,每次请求都会返回。定位问题时需要提供该次请求的 RequestId

人脸验证(人脸验证)

给定一张人脸图片和一个 PersonId,判断图片中的人和 PersonId 对应的人是否为同一人。PersonId 请参考人员库管理相关接口

人脸比对接口不同的是,人脸验证用于判断 此人是否是此人,“此人”的信息已存于人员库中,“此人”可能存在多张人脸图片;而人脸比对用于判断两张人脸的相似度。

人员验证接口不同的是,人脸验证将该人员(Person)下的每个人脸(Face)都作为单独个体进行验证,而人员验证会将该人员(Person)下的所有人脸(Face)进行融合特征处理,即若某个 Person下有4张 Face,人员验证接口会将4张 Face 的特征进行融合处理,生成对应这个 Person 的特征,使人员验证(确定待识别的人脸图片是某人员)更加准确。

请求参数

参数名称必选类型描述
PersonIdString待验证的人员ID。人员ID具体信息请参考人员库管理相关接口。 示例值:11111111
ImageString图片 base64 数据,base64 编码后大小不可超过5M。 若图片中包含多张人脸,只选取其中人脸面积最大的人脸。 支持PNG、JPG、JPEG、BMP,不支持 GIF 图片。
UrlString图片的 Url 。对应图片 base64 编码后大小不可超过5M。 Url、Image必须提供一个,如果都提供,只使用 Url。 图片存储于腾讯云的Url可保障更高下载速度和稳定性,建议图片存储于腾讯云。 非腾讯云存储的Url速度和稳定性可能受一定影响。 若图片中包含多张人脸,只选取其中人脸面积最大的人脸。 支持PNG、JPG、JPEG、BMP,不支持 GIF 图片。 示例值:test.image.myqcloud.com/testA.jpg
QualityControlInteger图片质量控制。 0: 不进行控制; 1:较低的质量要求,图像存在非常模糊,眼睛鼻子嘴巴遮挡至少其中一种或多种的情况; 2: 一般的质量要求,图像存在偏亮,偏暗,模糊或一般模糊,眉毛遮挡,脸颊遮挡,下巴遮挡,至少其中三种的情况; 3: 较高的质量要求,图像存在偏亮,偏暗,一般模糊,眉毛遮挡,脸颊遮挡,下巴遮挡,其中一到两种的情况; 4: 很高的质量要求,各个维度均为最好或最多在某一维度上存在轻微问题; 默认 0。 若图片质量不满足要求,则返回结果中会提示图片质量检测不符要求。 示例值:0
NeedRotateDetectionInteger是否开启图片旋转识别支持。0为不开启,1为开启。默认为0。本参数的作用为,当图片中的人脸被旋转且图片没有exif信息时,如果不开启图片旋转识别支持则无法正确检测、识别图片中的人脸。若您确认图片包含exif信息或者您确认输入图中人脸不会出现被旋转情况,请不要开启本参数。开启后,整体耗时将可能增加数百毫秒。 示例值:0

返回参数

参数名称类型描述
ScoreFloat给定的人脸照片与 PersonId 对应的相似度。若 PersonId 下有多张人脸(Face),会融合多张人脸信息进行验证。
IsMatchBoolean是否为同一人的判断。
FaceModelVersionString人脸识别所用的算法模型版本。
RequestIdString唯一请求 ID,每次请求都会返回。定位问题时需要提供该次请求的 RequestId

人脸验证的参数都一样的明白两个接口的业务场景使用即可

人脸静态活体检测

用于对用户上传的静态图片进行人脸活体检测。与动态活体检测的区别是:静态活体检测中,用户不需要通过唇语或摇头眨眼等动作来识别。

参数名称必选类型描述
ImageString图片 base64 数据,base64 编码后大小不可超过5M(图片的宽高比请接近3:4,不符合宽高比的图片返回的分值不具备参考意义)。 支持PNG、JPG、JPEG、BMP,不支持 GIF 图片。
UrlString图片的 Url 。对应图片 base64 编码后大小不可超过5M。 Url、Image必须提供一个,如果都提供,只使用 Url。 (图片的宽高比请接近 3:4,不符合宽高比的图片返回的分值不具备参考意义) 图片存储于腾讯云的Url可保障更高下载速度和稳定性,建议图片存储于腾讯云。 非腾讯云存储的Url速度和稳定性可能受一定影响。 支持PNG、JPG、JPEG、BMP,不支持 GIF 图片。 示例值:test.image.myqcloud.com/testA.jpg
FaceModelVersionString人脸识别服务所用的算法模型版本。  目前入参支持 “2.0”和“3.0“ 两个输入。  2020年4月2日开始,默认为“3.0”,之前使用过本接口的账号若未填写本参数默认为“2.0”。  2020年11月26日后开通服务的账号仅支持输入“3.0”。  不同算法模型版本对应的人脸识别算法不同,新版本的整体效果会优于旧版本,建议使用“3.0”版本。 示例值:3.0

返回参数

参数名称类型描述
ScoreFloat活体打分,取值范围 [0,100],分数一般落于[80, 100]区间内,0分也为常见值。推荐相大于 87 时可判断为活体。可根据具体场景自行调整阈值。 本字段当且仅当FaceModelVersion为2.0时才具备参考意义。 示例值:99
FaceModelVersionString人脸识别所用的算法模型版本。 示例值:3.0
IsLivenessBoolean活体检测是否通过。 本字段只有FaceModelVersion为3.0时才具备参考意义。 示例值:1
RequestIdString唯一请求 ID,每次请求都会返回。定位问题时需要提供该次请求的 RequestId。

我们已经初步的认识了这三个api接口的参数那么开始实战操作吧

腾讯云神图

先进行在线的操作

进入腾讯云控制台搜索人脸识别功能

人员管理 -> 新增人员

人员库ID 很重要嗷 后续需要此id来查找是哪个人员库当中的

点击 人员库名称 可以进行新增人员信息

人脸搜索

GroupIds 是希望搜索的人员库列表 刚刚我们创建了一个人员库 叫 yby6

上传刚刚的人员 填写组ID 其他的参数默认即可

页面的操作我就带大家玩到这里了,剩下的同学们可以自行玩玩!

搭建后端架构

请参考 从零玩转系列之微信支付实战基础框架搭建 | 技术创作特训营第一期 文章进行搭建即可

如遇到问题请评论区回复我解答问题!

新建数据库表

CREATE TABLE `sys_user_face_info` (
  `id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `group_id` varchar(255) DEFAULT NULL COMMENT '分组id',
  `face_id` varchar(32) NOT NULL COMMENT '人脸唯一Id->对接用户ID',
  `name` varchar(63) DEFAULT NULL COMMENT '名字',
  `age` int(3) DEFAULT NULL COMMENT '年纪',
  `gender` smallint(1) DEFAULT NULL COMMENT '性别,1=男,2=女',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `fpath` text DEFAULT NULL COMMENT '照片路径或者Base64',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `GROUP_ID` (`group_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1704902112020758531 DEFAULT CHARSET=utf8mb4 COMMENT='用户人脸识别';

⚠️ 使用代码生成器自行生成CRUD架构

新增依赖

        <!--腾讯云-->
        <dependency>
            <groupId>com.tencentcloudapi</groupId>
            <artifactId>tencentcloud-sdk-java</artifactId>
            <version>3.1.416</version>
        </dependency>
        <!-- 版本在maven生效需要时间,如获取不到对应的版本,可以调低版本号-->
        <dependency>
            <groupId>com.tencentcloudapi</groupId>
            <artifactId>tencentcloud-sdk-java-iai</artifactId>
            <version>3.1.416</version>
        </dependency>

创建腾讯云配置

新建 config 文件夹 新增 TencentConfig.java 配置文件

package com.yby6.config;

import lombok.Data;
import lombok.Getter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * 腾讯云yml配置
 *
 * @author Yang Buyi
 * Create By 2023/09/22
 * <p>
 */

@Data
@Component
@ConfigurationProperties(prefix = "tencent.cloud")
public class TencentConfig {
    public String appId;
    public String secretId;
    public String secretKey;
    public Face face;

    /**
     * 人脸相关
     */
    @Getter
    public static class Face {
        /**
         * 人员库名称
         */
        private String groupName;
        /**
         * 地域
         */
        private String region;

        public void setGroupName(String groupName) {
            this.groupName = groupName;
        }

        public void setRegion(String region) {
            this.region = region;
        }
    }

}

新增Yml配置

# 腾讯配置
tencent:
  cloud:
    appId: 获取APPID -》 访问管理
    secretId:  获取secretId -》 访问管理
    secretKey: 获取secretKey -》 访问管理
    face:
      groupName: 人员库名称
      region: ap-beijing

代码实现

创建实体类

package com.yby6.controller.form;

import lombok.Data;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 人脸识别参数接收
 *
 * @author Yang Buyi
 * Create By 2023/9/22
 */
@Data
public class CreateFaceModelForm {

    /**
     * 小程序openId 或者 pcId
     */
    private String userId;

    /**
     * 人脸Base64
     */
    private String photo;

}

创建工具类

package com.yby6.utils;

import cn.hutool.json.JSONUtil;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.iai.v20180301.IaiClient;
import com.tencentcloudapi.iai.v20180301.models.*;
import com.yby6.config.TencentConfig;
import com.yby6.controller.form.CreateFaceModelForm;
import com.yby6.domain.UserFaceInfo;
import com.yby6.mapper.UserFaceInfoMapper;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.UUID;


/**
 * 腾讯云服务工具
 *
 * @author Yang Buyi
 * Create By 2023/09/22
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class TencentUtils {
   private static TencentUtils tencentUtils;

    /**
     * 要在静态方法里面调用IOC容器的bean对象
     * PostConstruct 在构造函数执行之后执行。
     * 可以方便的把注入的bean对象给到静态属性
     * 源码: AutowiredAnnotationBeanPostProcessor 的 buildAutowiringMetadata 函数
     * isStatic 绕过了静态不进行注入
     */
    @PostConstruct
    public void postConstruct() {
        tencentUtils = this;
    }

    // 腾讯云配置
    private final TencentConfig tencentConfig;
  
  
  
    /**
     * 认证对象
     */
    private static IaiClient getIaiClient() {
        // 实例化一个认证对象
        Credential credential = new Credential(tencentUtils.tencentConfig.secretId, tencentUtils.tencentConfig.secretKey);
        // 实例化要请求产品的client对象,clientProfile是可选的
        return new IaiClient(credential, tencentUtils.tencentConfig.face.getRegion());
    }

  
  
}

创建人脸识别库



    /**
     * 创建人脸识别库
     * 对应文档:<a href="https://console.cloud.tencent.com/api/explorer?Product=iai&Version=2020-03-03&Action=CreateGroup">...</a>
     */
    public static void CreateGroup() {
        try {
            IaiClient client = getIaiClient();
            // 实例化一个请求对象,每个接口都会对应一个request对象
            CreateGroupRequest req = new CreateGroupRequest();
            req.setGroupId("yangbuyiya");
            req.setGroupName("杨不易呀测试人脸识别");
            // 返回的resp是一个CreateGroupResponse的实例,与请求对象对应
            CreateGroupResponse resp = client.CreateGroup(req);
            // 输出json格式的字符串回包
            System.out.println(CreateGroupResponse.toJsonString(resp));
        } catch (TencentCloudSDKException e) {
            System.out.println(e.toString());
        }
    }

创建成功前往平台查看

后续代码我都是在调试API当中完成的

这里我为大家已经写好了一份工具类直接使用即可!!!!!

完整工具类

package com.yby6.utils;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.json.JSONUtil;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.iai.v20180301.IaiClient;
import com.tencentcloudapi.iai.v20180301.models.*;
import com.yby6.config.TencentConfig;
import com.yby6.controller.form.CreateFaceModelForm;
import com.yby6.domain.UserFaceInfo;
import com.yby6.mapper.UserFaceInfoMapper;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.UUID;


/**
 * 腾讯云服务工具
 *
 * @author Yang Shuai
 * Create By 2023/09/21
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class TencentUtils {
    private static TencentUtils tencentUtils;

    /**
     * 需求是 : 要在静态方法里面调用IOC容器的bean对象
     * PostConstruct 在构造函数执行之后执行。
     * 可以方便的把注入的bean对象给到静态属性
     * 源码: AutowiredAnnotationBeanPostProcessor 的 buildAutowiringMetadata 函数
     * isStatic 绕过了静态不进行注入
     */
    @PostConstruct
    public void postConstruct() {
        tencentUtils = this;
    }

    private final TencentConfig tencentConfig;

    private final UserFaceInfoMapper userFaceInfoMapper;


    /**
     * 认证对象
     */
    private static IaiClient getIaiClient() {
        // 实例化一个认证对象
        Credential credential = new Credential(tencentUtils.tencentConfig.secretId, tencentUtils.tencentConfig.secretKey);
        // 实例化要请求产品的client对象,clientProfile是可选的
        return new IaiClient(credential, tencentUtils.tencentConfig.face.getRegion());
    }

    /**
     * 执行人员验证
     */
    public static VerifyPersonResponse verifyPersonRes(CreateFaceModelForm form) {
        IaiClient client = getIaiClient();
        // 创建人脸识别请求器
        VerifyPersonRequest request = new VerifyPersonRequest();
        request.setPersonId(String.valueOf(form.getUserId()));
        request.setImage(form.getPhoto());
        request.setQualityControl(4L);

        // 验证人员
        try {
            VerifyPersonResponse verifyPerson = client.VerifyPerson(request);
            log.info("人脸识别(人员)验证返回参数: {}", JSONUtil.toJsonStr(verifyPerson));
            // 进行活体检测 - 使用图片Base64转的会导致活体失败,到时候使用前端拍照即可
            // boolean liveFace = detectLiveFace(client, form.getPhoto());
            // verifyPerson.setIsMatch(verifyPerson.getIsMatch() && liveFace);
            return verifyPerson;
        } catch (TencentCloudSDKException e) {
            log.error("人脸验证失败:{0}", e);
            // 人员ID不存在 - 用于在新增人员的时候进行的校验 如果不存在则表示可以新增
            if (e.getErrorCode().equals("InvalidParameterValue.PersonIdNotExist")) {
                final VerifyPersonResponse response = new VerifyPersonResponse();
                response.setIsMatch(false);
                response.setScore(-0F);
                return response;
            }
            return null;
        }

    }

    /**
     * 执行人员识别验证
     */
    public static boolean verifyPerson(CreateFaceModelForm form) {
        // 验证人员
        VerifyPersonResponse verifyPerson = verifyPersonRes(form);
        if (null == verifyPerson) {
            throw new RuntimeException("未查询到该人脸信息!");
        }
        log.info("人脸识别(人员)验证返回参数: {}", JSONUtil.toJsonStr(verifyPerson));
        Boolean isMatch = verifyPerson.getIsMatch();
        if (BooleanUtil.isFalse(isMatch)) {
            return isMatch;
        }
        try {
            IaiClient client = getIaiClient();
            // 进行活体检测
            boolean liveFace = detectLiveFace(client, form.getPhoto());
            return isMatch && liveFace;
        } catch (TencentCloudSDKException e) {
            log.error("人脸验证失败:{0}", e);
            return false;
        }
    }

    /**
     * 执行人脸识别验证
     */
    public static VerifyFaceResponse verifyFaceRes(CreateFaceModelForm form) {
        try {
            VerifyFaceRequest request = new VerifyFaceRequest();
            request.setPersonId(String.valueOf(form.getUserId()));
            request.setImage(form.getPhoto());
            request.setQualityControl(4L);

            IaiClient client = getIaiClient();
            // 返回的resp是一个VerifyFaceResponse的实例,与请求对象对应
            VerifyFaceResponse verifyFace = client.VerifyFace(request);
            if (null == verifyFace) {
                throw new RuntimeException("未查询到该人脸信息!");
            }
            log.info("人脸识别(人脸)验证返回参数: {}", JSONUtil.toJsonStr(verifyFace));
            return verifyFace;
        } catch (TencentCloudSDKException e) {
            log.error("人脸验证失败:{0}", e);
            return null;
        }
    }


    /**
     * 执行人脸识别验证
     */
    public static boolean verifyFace(CreateFaceModelForm form) {
        try {
            // 返回的resp是一个VerifyFaceResponse的实例,与请求对象对应
            VerifyFaceResponse verifyFace = verifyFaceRes(form);
            if (null == verifyFace) {
                throw new RuntimeException("未查询到该人脸信息!");
            }
            log.info("人脸识别(人脸)验证返回参数: {}", JSONUtil.toJsonStr(verifyFace));
            Boolean isMatch = verifyFace.getIsMatch();
            if (BooleanUtil.isFalse(isMatch)) {
                return isMatch;
            }
            // 进行活体检测
            IaiClient client = getIaiClient();
            boolean liveFace = detectLiveFace(client, form.getPhoto());
            return isMatch && liveFace;
        } catch (TencentCloudSDKException e) {
            log.error("人脸验证失败:{0}", e);
            return false;
        }
    }


    /**
     * 活体检测
     */
    public static boolean detectLiveFace(IaiClient client, String image) throws TencentCloudSDKException {
        DetectLiveFaceRequest request = new DetectLiveFaceRequest();
        request.setImage(image);
        DetectLiveFaceResponse detectLiveFace = client.DetectLiveFace(request);
        log.info("活体验证(人脸)验证返回参数: {}", JSONUtil.toJsonStr(detectLiveFace));
        return detectLiveFace.getIsLiveness();
    }

    /**
     * 创建人员到腾讯云人脸识别(人员库)
     */
    public static void createPerson(CreateFaceModelForm form) {
        String userId = form.getUserId();
        String photo = form.getPhoto();
        // 实例化一个认证对象
        IaiClient client = getIaiClient();
        // 查询用户姓名和性别
        CreatePersonRequest request = new CreatePersonRequest();
        request.setGroupId(tencentUtils.tencentConfig.face.getGroupName());
        request.setPersonId(userId);
        try {
            DetectFaceRequest req = new DetectFaceRequest();
            req.setMaxFaceNum(1L);
            req.setImage(form.getPhoto());
            req.setNeedFaceAttributes(1L);
            req.setNeedQualityDetection(1L);
            // 返回的resp是一个DetectFaceResponse的实例,与请求对象对应
            DetectFaceResponse resp = client.DetectFace(req);
            final FaceInfo[] faceInfos = resp.getFaceInfos();
            final FaceAttributesInfo faceAttributesInfo = faceInfos[0].getFaceAttributesInfo();
            final short gender = faceAttributesInfo.getGender().shortValue();
            request.setGender((long) (gender > 2 ? 0 : gender)); // 1 男 2 女
            request.setQualityControl(4L);
            request.setPersonName(UUID.randomUUID().toString());
            request.setImage(photo);

            CreatePersonResponse response = client.CreatePerson(request);
            log.info("创建人脸模型返回参数:{}", JSONUtil.toJsonStr(response));

            // 新增到库当中
            tencentUtils.userFaceInfoMapper.insert(UserFaceInfo.builder()
                    .faceId(userId)
                    .age(faceAttributesInfo.getAge().intValue())
                    .gender(gender > 2 ? 0 : gender)
                    .groupId(tencentUtils.tencentConfig.face.getGroupName())
                    .build());

        } catch (TencentCloudSDKException e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 创建人脸识别库
     * 对应文档:<a href="https://console.cloud.tencent.com/api/explorer?Product=iai&Version=2020-03-03&Action=CreateGroup">...</a>
     */
    public static void CreateGroup() {
        try {
            IaiClient client = getIaiClient();
            // 实例化一个请求对象,每个接口都会对应一个request对象
            CreateGroupRequest req = new CreateGroupRequest();
            req.setGroupId("yby6");
            req.setGroupName("杨不易呀测试人脸识别");
            // 返回的resp是一个CreateGroupResponse的实例,与请求对象对应
            CreateGroupResponse resp = client.CreateGroup(req);
            // 输出json格式的字符串回包
            System.out.println(CreateGroupResponse.toJsonString(resp));
        } catch (TencentCloudSDKException e) {
            System.out.println(e.toString());
        }
    }


    /**
     * 人脸验证
     */
    public static void VerifyFace() {
        try {
            IaiClient client = getIaiClient();
            // 实例化一个请求对象,每个接口都会对应一个request对象
            VerifyFaceRequest req = new VerifyFaceRequest();
            req.setPersonId("6");
            req.setUrl("https://sns-img-hw.xhscdn.com/627e45c8-b78a-d2a5-a262-b9fb1bbe15fc?imageView2/2/w/900/format/webp");
            req.setQualityControl(4L);
            // 返回的resp是一个VerifyFaceResponse的实例,与请求对象对应
            VerifyFaceResponse resp = client.VerifyFace(req);
            // 输出json格式的字符串回包
            System.out.println(VerifyFaceResponse.toJsonString(resp));
        } catch (TencentCloudSDKException e) {
            System.out.println(e.toString());
        }
    }


    /**
     * 人脸检测和分析
     */
    public static void DetectFace(CreateFaceModelForm form) {

        try {
            IaiClient client = getIaiClient();
            // 参数查看: https://console.cloud.tencent.com/api/explorer?Product=iai&Version=2018-03-01&Action=DetectFace
            // 实例化一个请求对象,每个接口都会对应一个request对象
            DetectFaceRequest req = new DetectFaceRequest();
            req.setMaxFaceNum(1L);
            req.setImage(form.getPhoto());
            req.setNeedFaceAttributes(1L);
            req.setNeedQualityDetection(1L);
            // 返回的resp是一个DetectFaceResponse的实例,与请求对象对应
            DetectFaceResponse resp = client.DetectFace(req);
            // 输出json格式的字符串回包
            System.out.println(DetectFaceResponse.toJsonString(resp));
        } catch (TencentCloudSDKException e) {
            System.out.println(e.toString());
        }

    }


}

业务

修改人脸识别表服务 UserFaceInfoService

添加人脸模型记录

这里我们使用人员ID搜索

    /**
     * 添加人脸模型记录
     */
    @Transactional
    public void createFaceModel(CreateFaceModelForm form) {
        // 人员ID搜索验证
        final VerifyPersonResponse verifyPersonResponse = TencentUtils.verifyPersonRes(form);
        // 表示没有存在人员库当中可以新建人员信息
        if (null != verifyPersonResponse && !verifyPersonResponse.getIsMatch()) {
            TencentUtils.createPerson(form);
            return;
        }

        // 纯人脸识别验证
        // 返回参数: {"Score":19.309923,"IsMatch":false,"FaceModelVersion":"3.0","RequestId":"ac4fbfcc-c928-4587-aeb1-af83f44ea765"}
//        final VerifyFaceResponse verifyFaceResponse = TencentUtils.verifyFaceRes(form);
//        // 如果相似度小于97则表示没有存在人员库当中可以新建人员信息
//        if (null != verifyFaceResponse && verifyFaceResponse.getScore() < 97) {
//            TencentUtils.createPerson(form);
//            return;
//        }

        throw new RuntimeException("人脸模型已经存在!");
    }

验证人脸模型


    /**
     * 验证人脸模型
     */
    @Transactional
    public VerifyPersonResponse verifyFaceModel(CreateFaceModelForm form) {
        // 人脸识别验证
        // TencentUtils.verifyFaceRes(form);

        // 人员ID搜索验证
        final VerifyPersonResponse verifyPersonResponse = TencentUtils.verifyPersonRes(form);
        // 如果只是单独的调用
        if (null != verifyPersonResponse && !verifyPersonResponse.getIsMatch()) {
            throw new RuntimeException("人脸模型不存在!");
        }
        return verifyPersonResponse;
    }

新增 FaceAuthController 路由

package com.yby6.controller;

import com.yby6.controller.form.CreateFaceModelForm;
import com.yby6.reponse.R;
import com.yby6.service.UserFaceInfoService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 腾讯云 人脸识别 服务提供
 *
 * @author Yang Buyi
 * Create By 2023/9/22
 */
@RestController
@RequestMapping
@RequiredArgsConstructor
public class FaceAuthController {


    private final UserFaceInfoService userFaceInfoService;


    /**
     * 新增人脸模型
     */
    @PostMapping("/createFaceModel")
    public R<Object> createFaceModel(@RequestBody CreateFaceModelForm form) {
        userFaceInfoService.createFaceModel(form);
        return R.ok();
    }

    /**
     * 验证人脸是否正确并且写入人脸验证数据库
     */
    @PostMapping("/verifyFaceModel")
    public R<Object> verifyFaceModel(@RequestBody CreateFaceModelForm form) {
        return R.ok(userFaceInfoService.verifyFaceModel(form));
    }


}

后端搭建完毕~ 我们进行测试一下接口

API测试

新增人员信息

找一个有露脸的图片去百度找个在线 Base64 转码

复制 Base64 打开接口调试工具

userId 先填个 3

请求成功

再次新增则会提示已经存在不能新增人员信息

校验人员信息

可以看到 score 分数为100 isMatch 为 true 表示识别成功

我们放一张不同的人脸进去试试看

可以看到 score 分数小于100  isMatch 为 fase 表示识别失败不是同一个人

目前我们的接口就已经编写完毕啦~

搭建小程序和页面

小程序搭建请参考 从零玩转系列之小程序微信支付UniApp实战基础项目搭建 来搭建架构

创建人脸识别页面face_camera.vue

<template>
  <view>
    <view class="face-container">
      <camera device-position="front" flash="off" class="camera" @error="error" v-if="showCamera">
        <cover-image :src="img.bg" class="bg"></cover-image>
      </camera>
      <view class="image-container" v-if="showImage">
        <image mode="widthFix" class="photo" :src="photoPath"></image>
        <view class="cover"></view>
      </view>
    </view>
    <view class="desc">
      <!--   验证人脸身份   -->
      <view v-if="mode == 'verificate'">
        <image :src="img.tips" mode="widthFix" @click="toggerMode('create')" class="tips"></image>
        <text>请把面部放在圆圈内</text>
        <text>拍摄脸部来确认身份</text>
      </view>
      <!--   保存人脸模型   -->
      <view v-if="mode == 'create'">
        <image :src="img.face" mode="widthFix"  @click="toggerMode('verificate')"  class="face"></image>
        <text>请把完整面部放在圆圈内</text>
        <text>拍摄脸部来保存身份识别数据</text>
      </view>
    </view>
    <button class="btn" @tap="confirmHandle">{{ mode == 'create' ? '录入面部信息' : '身份核实' }}</button>

    <!-- 菜单栏 -->
    <tabbar selected="3"></tabbar>
  </view>
</template>

<script setup>
import { createFaceModel, verifyFaceModel } from "@/api/face";
import tabbar from "../tabbar/tabbar.vue";
import { ref } from "vue";
import { onHide, onLoad, onShow } from '@dcloudio/uni-app'
import { toast } from "@/utils/common";

let img = ref({
  bg: `https://foruda.gitee.com/images/1695401819717946439/d54d604d_5151444.png`,
  tips: `https://foruda.gitee.com/images/1695401850177434378/28590240_5151444.png`,
  face: `https://foruda.gitee.com/images/1695401861767041335/b4f0ed58_5151444.png`
})

let voice = ref({
  voice_1: ``
})
// 请求模式
let mode = ref('verificate')
// 人脸Base64图
let photoPath = ref('')
// 是否显示照相机
let showCamera = ref(true)
// 是否显示人脸图片
let showImage = ref(false)
// 音频
let audio = ref(null)
// openId 这里你随便填吧
const storageSync = uni.getStorageSync('token');

// 点击图标切换新增人员或者验证人员
const toggerMode = (modeVal) => {
  mode.value = modeVal
}


// 人脸识别操作
const confirmHandle = () => {
  audio.value.stop()
  // 获取摄像头对象
  let cameraContext = uni.createCameraContext();
  // 拍摄图片
  cameraContext.takePhoto({
    quality: 'high',
    success: (resp) => {
      console.log(resp);
      // 照片在小程序上面的临时路径 (小程序关闭后会销毁)
      photoPath.value = resp.tempImagePath
      // 临时图片转换为base64
      uni.getFileSystemManager().readFile({
        filePath: photoPath.value,
        encoding: 'base64',
        success: (res) => {
          let base64 = 'data:image:/png;base64,' + res.data
          if (mode.value === "create") {
            // 创建面部模型
            createFaceModel({
              "userId": storageSync,
              "photo": base64
            }).then(res => {
              console.log(res)
              toast("人脸新增成功", 5)
              // 拍照成功隐藏摄像头
              showCamera.value = false
              // 在页面上显示拍摄的图片
              showImage.value = true
            }).catch(() => {
              showCamera.value = true
              showImage.value = false
              audio.value.play()
            })
          } else {
            // 验证人脸模型
            verifyFaceModel({
              "userId": storageSync,
              "photo": base64
            }).then(res => {
              console.log(res)
              if (res.data != null&&res.data.Score != -0) {
                toast("人脸验证成功", 5)
                // 拍照成功隐藏摄像头
                showCamera.value = false
                // 在页面上显示拍摄的图片
                showImage.value = true
              } else {
                toast("未检测到人脸!", 5)
                showCamera.value = true
                showImage.value = false
                audio.value.play()
              }
            }).catch(() => {
              showCamera.value = true
              showImage.value = false
            })
          }
        }
      })
    }
  })
}

// ============================== 生命周期 ===========================
onLoad((options) => {
  console.log(options);
  // 播放声音
  // 创建audio对象
  let audioContext = uni.createInnerAudioContext();
  audio.value = audioContext
  audioContext.src = voice.value.voice_1
  audioContext.play()
})
onHide(() => {
  // 当小程序挂到手机后台时候 接电话或者切换到其他app则立即停止播放mp3文件
  if (audio.value != null) {
    audio.value.stop()
  }
})
onShow(() => {
  console.log("onShow");
})
</script>

<style lang="less" scoped>
@import url('face_camera.less');
</style>

在同级别目录新增 face_camera.less 样式表

注意⚠️ 记得放开后端代码当中的 活体检测功能 这里我们使用拍照即可识别活体

运行项目测试

记得 userId 必须填写

我把人员库里面的人员全部删除了

新增人员 点击灯泡 💡切换人脸模式

再次测试验证人脸 完美验证成功

本篇文章到此就结束啦~

最后

本期结束咱们下次再见👋~

🌊 关注我不迷路,如果本篇文章对你有所帮助,或者你有什么疑问,欢迎在评论区留言,我一般看到都会回复的。大家点赞支持一下哟~ 💗