理解刷脸登录的需求
理解刷脸登录的开发流程实现刷脸登录功能
人工智能(Artificial Intelligence),英文缩写为AI。它是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学
人工智能是计算机科学的一个分支,它企图了解智能的实质,并生产出一种新的能以人类智能相似的方式做出反应 的智能机器,该领域的研究包括机器人、语言识别、图像识别、自然语言处理和专家系统等。人工智能从诞生以 来,理论和技术日益成熟,应用领域也不断扩大,可以设想,未来人工智能带来的科技产品,将会是人类智慧
的“容器”。人工智能可以对人的意识、思维的信息过程的模拟。人工智能不是人的智能,但能像人那样思考、也可 能超过人的智能。
随着智能家电、穿戴设备、智能机器人等产物的出现和普及,人工智能技术已经进入到生活的各个领域,引发越来 越多的关注。
刷脸登录是基于人工智能、生物识别、3D传感、大数据风控技术,最新实现的登录形式。用户在无需输入用户名 密码的前提下,凭借“刷脸”完成登录过程。实现刷脸登录的核心是人脸处理,在人脸处理中有两个概念:
作为中小型企业,可以采取世面上流行的人工智能产品快速的实现刷脸登录需求。目前比较流行人脸检测产品如下:
百度人脸识别基于深度学习的人脸识别方案,准确识别图片中的人脸信息,提供如下功能:
打开百度云平台:百度智能云-注册进行账号注册
找到产品-人工智能-人脸识别激活应用,并注册应用
应用创建完成之后,进入刚刚创建的应用获取开发所需的AppID,API Key,Secret Key。
创建工程并导入依赖:
com.baidu.aip
java-sdk
4.8.0
用于从人脸库中新增用户,可以设定多个用户所在组,及组内用户的人脸图片典型应用场景:构建您的人脸库,如会员人脸注册,已有用户补全人脸信息等。
public class FaceTest {
private AipFace client;
@Before
public void init() {
//1.创建java代码和百度云交互的client对象
client = new AipFace("15246423","QFYuYWDlb5mHTA8I9XTGlgjM","mvgF3pPoOLsMadFGiemuzt0ayE3jFIOv");
}
//人脸注册:向百度的人脸库中添加用户人脸照片
@Test
public void testFaceRegister() throws Exception {
//2.参数设置
HashMap options = new HashMap<>();
options.put("quality_control","NORMAL");//图片质量 NONE LOW NORMAL,HIGH
options.put("liveness_control","LOW");//活体检测
options.put("action_type","replace");//活体检测
//3.构造图片
String path = "C:\\Users\\ThinkPad\\Desktop\\002.png";
//上传的图片 两种格式 : url地址,Base64字符串形式
byte[] bytes = Files.readAllBytes(Paths.get(path));
String encode = Base64Util.encode(bytes);
//4.调用api方法完成人脸注册
/**
* 参数一:(图片的url或者图片的Base64字符串),
* 参数二:图片形式(URL,BASE64)
* 参数三:组ID(固定字符串)
* 参数四:用户ID
* 参数五:hashMap中的基本参数配置
*/
JSONObject res = client.addUser(encode, "BASE64", "itcast", "1003", options);
System.out.println(res.toString());
}
}
骚戴理解:可以通过 byte[] bytes = Files.readAllBytes(Paths.get(path));来把一个图片的路径转成二进制数组,参数设置通常只需要设置两个参数即可,图片质量和活体检测的级别!人脸注册其实就是把一个图片上传到百度云的人脸库里,这就跟七牛云的图片上传差不多,甚至更简单,只需要client.addUser,然后补充五个参数即可,addUser方法的五个参数含义:
还需要注意的是AipFace的值要换成自己创建应用的三个参数AppID,API Key,Secret Key!
参数名称 |
是否必选 |
类型 |
默认值 |
说明 |
image |
是 |
String |
图片信息(总数据大小应小于10M),图片上传方式根据 image_type来判断 |
|
image_type |
是 |
String |
图片类型 BASE64:图片的base64值,base64编码后的图片数据,需urlencode,编码后的图片大小不超过2M; URL:图片的 URL地址( 可能由于网络等原因导致下载图片时间过长);FACE_TOKEN: 人脸图片的唯一标识,调用人脸检测接口时,会为每个人脸图片赋予一个唯一的FACE_TOKEN,同一张图片多次检测得到的FACE_TOKEN 是同一个 |
|
group_id |
是 |
String |
用户组id(由数字、字母、下划线组成),长度限制128B |
|
user_id |
是 |
String |
用户id(由数字、字母、下划线组成),长度限制128B |
|
user_info |
否 |
String |
用户资料,长度限制256B |
|
quality_control |
否 |
String |
NONE |
图片质量控制 NONE: 不进行控制 LOW:较低的质量要求NORMAL: 一般的质量要求 HIGH: 较高的质量要求 默认NONE |
liveness_control |
否 |
String |
NONE |
活体检测控制 NONE: 不进行控制 LOW:较低的活体要求 (高通过率 低攻击拒绝率) NORMAL: 一般的活体要求(平衡的攻击拒绝率, 通过率) HIGH: 较高的活体要求(高攻击拒绝率 低通过率) 默认NONE |
骚戴理解:上面表格的参数前面五个都是addUser方法的五个参数,后面的两个是参数设置的两个参数,也就是图片质量和活体检测级别这两个参数
字段 |
必选 |
类型 |
说明 |
log_id |
是 |
uint64 |
请求标识码,随机数,唯一 |
face_token |
是 |
string |
人脸图片的唯一标识 |
location |
是 |
array |
人脸在图片中的位置 |
+left |
是 |
double |
人脸区域离左边界的距离 |
+top |
是 |
double |
人脸区域离上边界的距离 |
+width |
是 |
double |
人脸区域的宽度 |
+height |
是 |
double |
人脸区域的高度 |
+rotation |
是 |
int64 |
人脸框相对于竖直方向的顺时针旋转角,[-180,180] |
用于对人脸库中指定用户,更新其下的人脸图像。
public class FaceTest {
private AipFace client;
@Before
public void init() {
//1.创建java代码和百度云交互的client对象
client = new AipFace("15246423","QFYuYWDlb5mHTA8I9XTGlgjM","mvgF3pPoOLsMadFGiemuzt0ayE3jFIOv");
}
/**
* 人脸更新:更新人脸库中的照片
*/
@Test
public void testFaceUpdate() throws Exception {
//2.参数设置
HashMap options = new HashMap<>();
options.put("quality_control","NORMAL");//图片质量 NONE LOW NORMAL,HIGH
options.put("liveness_control","LOW");//活体检测
options.put("action_type","replace");
//3.构造图片
String path = "C:\\Users\\ThinkPad\\Desktop\\002.png";
//上传的图片 两种格式 : url地址,Base64字符串形式
byte[] bytes = Files.readAllBytes(Paths.get(path));
String encode = Base64Util.encode(bytes);
//4.调用api方法完成人脸更新
/**
* 参数一:(图片的url或者图片的Base64字符串),
* 参数二:图片形式(URL,BASE64)
* 参数三:组ID(固定字符串)
* 参数四:用户ID
* 参数五:hashMap中的基本参数配置
*/
JSONObject res = client.updateUser(encode, "BASE64", "itcast", "10001", options);
System.out.println(res.toString());
}
}
骚戴理解:人脸更新的需求是随着人的年龄增加,人的人脸特征会发生变化,再或者整容了的人的人脸特征都是巨变,所以需要更新人脸库里的人脸信息,这个更新和注册基本一样,唯一个不同就是调用的方法不同,更新调用的是updateUser方法,其他的和人脸注册一模一样!
参数名称 |
是否必选 |
类型 |
默认值 |
说明 |
image |
是 |
String |
图片信息(总数据大小应小于10M),图片上传方式根据 image_type来判断 |
|
image_type |
是 |
String |
图片类型 BASE64:图片的base64值,base64编码后的图片数据,需urlencode,编码后的图片大小不超过2M; URL:图片的 URL地址( 可能由于网络等原因导致下载图片时间过长);FACE_TOKEN: 人脸图片的唯一标识,调用人脸检测接口时,会为每个人脸图片赋予一个唯一的FACE_TOKEN,同一张图片多次检测得到的FACE_TOKEN 是同一个 |
|
group_id |
是 |
String |
更新指定groupid下uid对应的信息 |
|
user_id |
是 |
String |
用户id(由数字、字母、下划线组成),长度限制128B |
|
user_info |
否 |
String |
用户资料,长度限制256B |
|
quality_control |
否 |
String |
NONE |
图片质量控制 NONE: 不进行控制 LOW:较低的质量要求NORMAL: 一般的质量要求 HIGH: 较高的质量要求 默认NONE |
liveness_control |
否 |
String |
NONE |
活体检测控制 NONE: 不进行控制 LOW:较低的活体要求 (高通过率 低攻击拒绝率) NORMAL: 一般的活体要求(平衡的攻击拒绝率, 通过率) HIGH: 较高的活体要求(高攻击拒绝率 低通过率) 默认NONE |
字段 |
必选 |
类型 |
说明 |
log_id |
是 |
uint64 |
请求标识码,随机数,唯一 |
face_token |
是 |
string |
人脸图片的唯一标识 |
location |
是 |
array |
人脸在图片中的位置 |
+left |
是 |
double |
人脸区域离左边界的距离 |
+top |
是 |
double |
人脸区域离上边界的距离 |
+width |
是 |
double |
人脸区域的宽度 |
+height |
是 |
double |
人脸区域的高度 |
+rotation |
是 |
int64 |
人脸框相对于竖直方向的顺时针旋转角,[-180,180] |
人脸检测:检测图片中的人脸并标记出位置信息;
public class FaceTest {
private AipFace client;
@Before
public void init() {
//1.创建java代码和百度云交互的client对象
client = new AipFace("15246423","QFYuYWDlb5mHTA8I9XTGlgjM","mvgF3pPoOLsMadFGiemuzt0ayE3jFIOv");
}
/**
* 人脸检测:判断图片中是否具有面部信息
*/
@Test
public void testFaceCheck() throws Exception {
//构造图片
String path = "C:\\Users\\ThinkPad\\Desktop\\001.png";
//上传的图片 两种格式 : url地址,Base64字符串形式
byte[] bytes = Files.readAllBytes(Paths.get(path));
String image = Base64Util.encode(bytes);
//调用api方法进行人脸检测
//参数一:(图片的url或者图片的Base64字符串)
//参数二:图片形式(URL,BASE64)
//参数三:hashMap中的基本参数配置(null:使用默认配置)
JSONObject res = client.detect(image, "BASE64", null);
System.out.println(res.toString(2));
}
}
骚戴理解:人脸检测其实就是看你上传的图片里面有没有人脸,注意这里不是检验人脸,只是单纯的判断检测的图片有没有人脸信息!同样调用client的detect方法判断即可,detect的三个参数:
参数名称 |
是否必选 |
类型 |
默认值 |
说明 |
image |
是 |
String |
图片信息(总数据大小应小于10M),图片上传方式根据image_type来判断 |
|
image_type |
是 |
String |
图片类型 BASE64:图片的base64值,base64编码后的图片数据,需urlencode,编码后的图片大小不超过2M;URL:图片的 URL地址( 可能由于网络等原因导致下载图片时间过长);FACE_TOKEN: 人脸图片的唯一标识,调用人脸检测接口时,会为每个人脸图片赋予一个唯一的FACE_TOKEN,同一张图片多次检测得到的FACE_TOKEN是同一个 |
|
face_field |
否 |
String |
包括age,beauty,expression,faceshape,gender,glasses,landmark,race,quality,facetype信息 逗号分隔. 默认只返回face_token、人脸框、概率和旋转角度 |
|
max_face_num |
否 |
String |
1 |
最多处理人脸的数目,默认值为1,仅检测图片中面积最大的那个人脸;最大值10,检测图片中面积最大的几张人脸。 |
face_type |
否 |
String |
人脸的类型 LIVE表示生活照:通常为手机、相机拍摄的人像图片、或从网络获取的人像图片等IDCARD表示身份证芯片照:二代身份证内置芯片中的人像照片 WATERMARK表示带水印证件照:一般为带水印的小图,如公安网小图 CERT表示证件照片:如拍摄的身份证、工卡、护照、学生证等证件图片 默认LIVE |
字段 |
必选 |
类型 |
说明 |
face_num |
是 |
int |
检测到的图片中的人脸数量 |
face_list |
是 |
array |
人脸信息列表,具体包含的参数参考下面的列表。 |
+face_token |
是 |
string |
人脸图片的唯一标识 |
+location |
是 |
array |
人脸在图片中的位置 |
++left |
是 |
double |
人脸区域离左边界的距离 |
++top |
是 |
double |
人脸区域离上边界的距离 |
++width |
是 |
double |
人脸区域的宽度 |
++height |
是 |
double |
人脸区域的高度 |
++rotation |
是 |
int64 |
人脸框相对于竖直方向的顺时针旋转角,[-180,180] |
+face_probability |
是 |
double |
人脸置信度,范围【0~1】,代表这是一张人脸的概率,0最小、1 最大。 |
+angel |
是 |
array |
人脸旋转角度参数 |
++yaw |
是 |
double |
三维旋转之左右旋转角[-90(左), 90(右)] |
++pitch |
是 |
double |
三维旋转之俯仰角度[-90(上), 90(下)] |
++roll |
是 |
double |
平面内旋转角[-180(逆时针), 180(顺时针)] |
+age |
否 |
double |
年龄 ,当face_field包含age时返回 |
+beauty |
否 |
int64 |
美丑打分,范围0-100,越大表示越美。当face_fields包含beauty 时返回 |
+expression |
否 |
array |
表情,当 face_field包含expression时返回 |
++type |
否 |
string |
none:不笑;smile:微笑;laugh:大笑 |
++probability |
否 |
double |
表情置信度,范围【0~1】,0最小、1最大。 |
+face_shape |
否 |
array |
脸型,当face_field包含faceshape时返回 |
++type |
否 |
double |
square: 正方形 triangle:三角形 oval: 椭圆 heart: 心形 round: 圆形 |
++probability |
否 |
double |
置信度,范围【0~1】,代表这是人脸形状判断正确的概率,0最小、1最大。 |
+gender |
否 |
array |
性别,face_field包含gender时返回 |
++type |
否 |
string |
male:男性 female:女性 |
++probability |
否 |
double |
性别置信度,范围【0~1】,0代表概率最小、1代表最大。 |
字段 |
必选 |
类型 |
说明 |
+glasses |
否 |
array |
是否带眼镜,face_field包含glasses时返回 |
++type |
否 |
string |
none:无眼镜,common:普通眼镜,sun:墨镜 |
++probability |
否 |
double |
眼镜置信度,范围【0~1】,0代表概率最小、1代表最大。 |
+race |
否 |
array |
人种 face_field包含race时返回 |
++type |
否 |
string |
yellow: 黄种人 white: 白种人 black:黑种人 arabs: 阿拉伯人 |
++probability |
否 |
double |
人种置信度,范围【0~1】,0代表概率最小、1代表最大。 |
+face_type |
否 |
array |
真实人脸/卡通人脸 face_field包含face_type时返回 |
++type |
否 |
string |
human: 真实人脸 cartoon: 卡通人脸 |
++probability |
否 |
double |
人脸类型判断正确的置信度,范围【0~1】,0代表概率最小、1代表最大。 |
+landmark |
否 |
array |
4个关键点位置,左眼中心、右眼中心、鼻尖、嘴中心。face_field 包含landmark时返回 |
+landmark72 |
否 |
array |
72个特征点位置 face_field包含landmark时返回 |
+quality |
否 |
array |
人脸质量信息。face_field包含quality时返回 |
++occlusion |
否 |
array |
人脸各部分遮挡的概率,范围[0~1],0表示完整,1表示不完整 |
+++left_eye |
否 |
double |
左眼遮挡比例 |
+++right_eye |
否 |
double |
右眼遮挡比例 |
+++nose |
否 |
double |
鼻子遮挡比例 |
+++mouth |
否 |
double |
嘴巴遮挡比例 |
+++left_cheek |
否 |
double |
左脸颊遮挡比例 |
+++right_cheek |
否 |
double |
右脸颊遮挡比例 |
+++chin |
否 |
double |
下巴遮挡比例 |
++blur |
否 |
double |
人脸模糊程度,范围[0~1],0表示清晰,1表示模糊 |
++illumination |
否 |
double |
取值范围在[0~255], 表示脸部区域的光照程度 越大表示光照越好 |
++completeness |
否 |
int64 |
人脸完整度,0或1, 0为人脸溢出图像边界,1为人脸都在图像边界内 |
+parsing_info |
否 |
string |
人脸分层结果 结果数据是使用gzip压缩后再base64编码 使用前需base64解码后再解压缩 原数据格式为string 形如0,0,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,… |
在指定人脸集合中,找到最相似的人脸
public class FaceTest {
private AipFace client;
@Before
public void init() {
//1.创建java代码和百度云交互的client对象
client = new AipFace("15246423","QFYuYWDlb5mHTA8I9XTGlgjM","mvgF3pPoOLsMadFGiemuzt0ayE3jFIOv");
}
/**
* 人脸搜索:根据用户上传的图片和指定人脸库中的所有人脸进行比较,
* 获取相似度最高的一个或者某几个的评分
*
* 说明:返回值(数据,只需要第一条,相似度最高的数据)
* score:相似度评分(80分以上可以认为是同一个人)
*/
@Test
public void testFaceSearch() throws Exception {
//构造图片
String path = "C:\\Users\\ThinkPad\\Desktop\\003.png";
byte[] bytes = Files.readAllBytes(Paths.get(path));
String image = Base64Util.encode(bytes);
//人脸搜索
JSONObject res = client.search(image, "BASE64", "itcast", null);
System.out.println(res.toString(2));
}
}
骚戴理解:人脸搜索才是真正的检验上传的图片和图片库里的所有图片的相似度,会返回一个数组,然后按照相似度从高到低排序,这里只需要获取第一个最高的评分,也就在最像的,只要返回的score大于80分就可以认为是同一个人!同样也是调用client的search()方法校验即可,注意search方法的四个参数的含义:
参数名称 |
是否必选 |
类型 |
默认值 |
说明 |
image |
是 |
String |
图片信息(总数据大小应小于10M),图片上传方式根据 image_type来判断 |
|
image_type |
是 |
String |
图片类型 BASE64:图片的base64值,base64编码后的图片数据,需urlencode,编码后的图片大小不超过2M; URL:图片的 URL地址( 可能由于网络等原因导致下载图片时间过长);FACE_TOKEN: 人脸图片的唯一标识,调用人脸检测接口时,会为每个人脸图片赋予一个唯一的FACE_TOKEN,同一张图片多次检测得到的FACE_TOKEN 是同一个 |
|
group_id_list |
是 |
String |
从指定的group中进行查找 用逗号分隔,上限20个 |
|
quality_control |
否 |
String |
NONE |
图片质量控制 NONE: 不进行控制 LOW:较低的质量要求NORMAL: 一般的质量要求 HIGH: 较高的质量要求 默认NONE |
liveness_control |
否 |
String |
NONE |
活体检测控制 NONE: 不进行控制 LOW:较低的活体要求 (高通过率 低攻击拒绝率) NORMAL: 一般的活体要求(平衡的攻击拒绝率, 通过率) HIGH: 较高的活体要求(高攻击拒绝率 低通过率) 默认NONE |
user_id |
否 |
String |
当需要对特定用户进行比对时,指定user_id进行比对。即人脸认证功能。 |
|
max_user_num |
否 |
String |
查找后返回的用户数量。返回相似度最高的几个用户,默认为1,最多返回20个。 |
字段 |
必选 |
类型 |
说明 |
face_token |
是 |
string |
人脸标志 |
user_list |
是 |
array |
匹配的用户信息列表 |
+group_id |
是 |
string |
用户所属的group_id |
+user_id |
是 |
string |
用户的user_id |
+user_info |
是 |
string |
注册用户时携带的user_info |
+score |
是 |
float |
用户的匹配得分,推荐阈值80分 |
com.google.zxing
core
3.2.1
com.google.zxing
javase
3.2.1
package cn.itcast.test;
import com.baidu.aip.util.Base64Util;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.nio.file.Path;
import java.util.Base64;
public class QRCodeTest {
public static void main(String[] args) throws Exception {
//1.二维码中的信息
String content = "http://www.itcast.cn";
//2.通过zxing生成二维码(保存到本地图片,支持以data url的形式体现)
//创建QRCodeWriter对象
QRCodeWriter writer = new QRCodeWriter();
//基本配置
/**
* 1.二维码信息
* 2.图片类型
* 3.宽度
* 4.长度
*/
BitMatrix bt = writer.encode(content, BarcodeFormat.QR_CODE, 200, 200);
//保存二维码到本地
Path path = new File("C:\\Users\\ThinkPad\\Desktop\\test.png").toPath();
MatrixToImageWriter.writeToPath(bt,"png",path);
}
}
骚戴理解:生成二维码的核心就是QRCodeWriter对象的encode方法,只需要知道encode的四个参数的含义即可,BarcodeFormat.QR_CODE表示图片类型是二维码类型,BitMatrix可以理解为就是一个二维码对象,MatrixToImageWriter其实就是把二维码对象转化成图片的输出流工具
package cn.itcast.test;
import com.baidu.aip.util.Base64Util;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.nio.file.Path;
import java.util.Base64;
public class QRCodeTest {
//生成dataUrl形式的二维码
public static void main(String[] args) throws Exception {
//1.二维码中的信息
String content = "http://www.itcast.cn";
//2.通过zxing生成二维码(保存到本地图片,支持以data url的形式体现)
//创建QRCodeWriter对象
QRCodeWriter writer = new QRCodeWriter();
//基本配置
BitMatrix bt = writer.encode(content, BarcodeFormat.QR_CODE, 200, 200);
//创建ByteArrayOutputstream
ByteArrayOutputStream os = new ByteArrayOutputStream();
//将二维码数据以byte数组的形式保存到ByteArrayOutputstream
/**
* 1:image对象
* 2:图片格式
* 3:Outputstream
*/
BufferedImage image = MatrixToImageWriter.toBufferedImage(bt);
ImageIO.write(image,"png",os);
//对byte数组进行base64处理
String encode = Base64Util.encode(os.toByteArray());
System.out.println(new String("data:image/png;base64,"+encode));
}
}
骚戴理解:这段代码的作用是将给定的字符串内容生成一个二维码,并将二维码转换为图片格式,最后将图片以base64编码的形式保存到字符串中。整体思路就是先把BitMatrix转成BufferedImage,然后通过ImageIO工具类的write方法把BufferedImage转成ByteArrayOutputStream,在把ByteArrayOutputStream转成Data url!
具体解释如下:
1. `BitMatrix bt = writer.encode(content, BarcodeFormat.QR_CODE, 200, 200);`:使用ZXing库中的`writer`对象将给定的字符串`content`编码成一个二维码,`BarcodeFormat.QR_CODE`表示生成的二维码类型为QR码,`200`和`200`分别表示生成的二维码的宽度和高度为200像素。
2. `ByteArrayOutputStream os = new ByteArrayOutputStream();`:创建一个`ByteArrayOutputStream`对象,用于将二维码数据以byte数组的形式保存。
3. `BufferedImage image = MatrixToImageWriter.toBufferedImage(bt);`:将`BitMatrix`对象转换为`BufferedImage`对象,以便后续将其保存为图片格式。
4. `ImageIO.write(image,"png",os);`:将`BufferedImage`对象以PNG格式写入到`ByteArrayOutputStream`对象中。
5. `String encode = Base64Util.encode(os.toByteArray());`:将`ByteArrayOutputStream`对象中的byte数组以base64编码的形式保存到字符串`encode`中,最终返回该字符串。其中,`Base64Util`是一个自定义的工具类,用于将byte数组转换为base64编码的字符串。
为了用户登录的便捷,我们在系统中增加刷脸登录的功能,大致流程如下图:
骚戴理解:这里通过上面的图片来更好的理解整个实现流程,简单说就是前端刷脸登录的时候会生成一个唯一的二维码,这个二维码有一个唯一标识,然后把标识存Redis里,初始化为-1,表示状态为未扫描,用户扫描二维码后需要拍照进行人脸识别,然后根据识别结果修改Redis里的状态,识别成功就修改为1,识别失败就修改为0,然后再写一个轮询接口(相当于是一个无限的while循环),隔一会去Redis看看状态,如果状态是1就表示登录成功,如果是0或者-1就登录失败
(1) 引入坐标
com.baidu.aip
java-sdk
4.8.0
com.google.zxing
core
3.2.1
com.google.zxing
javase
3.2.1
ai:
appId: 33876611
apiKey: djvLRHxONnWYUpjX6ykggd3n
secretKey: iw9T8FvStFtHLwqIMWiouulVifC6f9wB
imageType: BASE64
groupId: itcast
qr:
url: http://localhost:8080/#/facelogin
骚戴理解:配置文件里的groupId就是设置的人脸库分组的名称!
在系统微服务中构建基本的Controller代码
package com.ihrm.system.controller;
import com.ihrm.common.entity.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Base64Utils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("/sys/faceLogin")
public class FaceLoginController {
/**
* 获取刷脸登录二维码
返回值:QRCode对象(code,image)
*/
@RequestMapping(value = "/qrcode", method = RequestMethod.GET)
public Result qrcode() throws Exception {
return null;
}
/**
* 检查二维码:登录页面轮询调用此方法,根据唯一标识code判断用户登录情况
* 查询二维码扫描状态
* 返回值:FaceLoginResult
* state :-1,0,1 (userId和token)
*/
@RequestMapping(value = "/qrcode/{code}", method = RequestMethod.GET)
public Result qrcodeCeck(@PathVariable(name = "code") String code) throws Exception {
return null;
}
/**
* 人脸登录:根据落地页随机拍摄的面部头像进行登录
* 根据拍摄的图片调用百度云AI进行检索查找
*/
@RequestMapping(value = "/{code}", method = RequestMethod.POST)
public Result loginByFace(@PathVariable(name = "code") String code, @RequestParam(name = "file") MultipartFile attachment) throws Exception {
return null;
}
/**
* 图像检测,判断图片中是否存在面部头像
*/
@RequestMapping(value = "/checkFace", method = RequestMethod.POST)
public Result checkFace(@RequestParam(name = "file") MultipartFile attachment) throws Exception {
return null;
}
}
package com.ihrm.system.service;
import com.ihrm.domain.system.response.FaceLoginResult;
import com.ihrm.domain.system.response.QRCode;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
@Service
public class FaceLoginService {
@Value("${qr.url}")
private String url;
//创建二维码
public QRCode getQRCode() throws Exception {
return null;
}
//根据唯一标识,查询用户是否登录成功
public FaceLoginResult checkQRCode(String code) {
return null;
}
//扫描二维码之后,使用拍摄照片进行登录
public String loginByFace(String code, MultipartFile attachment) throws Exception {
return null;
}
//构造缓存key
private String getCacheKey(String code) {
return "qrcode_" + code;
}
}
package com.ihrm.domain.system.response;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;
@Data
@NoArgsConstructor
@ToString
public class FaceLoginResult implements Serializable {
private static final long serialVersionUID = -1616426041373762391L;
/**
* 二维码使用状态
*/
private String state;
/**
* 登录信息
*/
private String token;
/**
* 用户ID
*/
private String userId;
public FaceLoginResult(String state, String token, String userId) {
this.state = state;
this.token = token;
this.userId = userId;
}
public FaceLoginResult(String state) {
this.state = state;
}
}
package com.ihrm.domain.system.response;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class QRCode implements Serializable {
private static final long serialVersionUID = 4375387973088123002L;
/**
* 随机生成码
*/
private String code;
/**
* Base64 二维码文件
*/
private String file;
}
package com.ihrm.system.utils;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.sun.org.apache.xml.internal.security.utils.Base64;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
@Component
public class QRCodeUtil {
/**
* 生成Base64 二维码
*/
public String crateQRCode(String content) throws IOException {
System.out.println(content);
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
QRCodeWriter writer = new QRCodeWriter();
BitMatrix bitMatrix = writer.encode(content, BarcodeFormat.QR_CODE, 200, 200);
BufferedImage bufferedImage = MatrixToImageWriter.toBufferedImage(bitMatrix);
ImageIO.write(bufferedImage, "png", os);
//添加图片标识
return new String("data:image/png;base64," + Base64.encode(os.toByteArray()));
} catch (Exception e) {
e.printStackTrace();
} finally {
os.close();
}
return null;
}
}
在QRCodeUtil类头添加 @Component 注解,使用时可通过 @Autowired 来自动装配。
对于百度云AI SDK我们进行一些简单的封装,便于使用时,减少代码冗余。
package com.ihrm.system.utils;
import com.baidu.aip.face.AipFace;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.HashMap;
@Component
public class BaiduAiUtil {
@Value("${ai.appId}")
private String APP_ID;
@Value("${ai.apiKey}")
private String API_KEY;
@Value("${ai.secretKey}")
private String SECRET_KEY;
@Value("${ai.imageType}")
private String IMAGE_TYPE;
@Value("${ai.groupId}")
private String groupId;
private AipFace client;
private HashMap options = new HashMap();
public BaiduAiUtil() {
options.put("quality_control", "NORMAL");
options.put("liveness_control", "LOW");
}
@PostConstruct
public void init() {
client = new AipFace(APP_ID, API_KEY, SECRET_KEY);
}
//判断用户是否已经注册了面部信息
public Boolean faceExist(String userId) {
//返回error_code:0 (存在),非0:不存在
JSONObject res = client.getUser(userId, groupId, null);
Integer errorCode = res.getInt("error_code");
return errorCode == 0 ? true : false;
}
/**
* 人脸注册 :将用户照片存入人脸库中
*/
public Boolean faceRegister(String userId, String image) {
// 人脸注册
JSONObject res = client.addUser(image, IMAGE_TYPE, groupId, userId, options);
Integer errorCode = res.getInt("error_code");
return errorCode == 0 ? true : false;
}
/**
* 人脸更新 :更新人脸库中的用户照片
*/
public Boolean faceUpdate(String userId, String image) {
// 人脸更新
JSONObject res = client.updateUser(image, IMAGE_TYPE, groupId, userId, options);
Integer errorCode = res.getInt("error_code");
return errorCode == 0 ? true : false;
}
/**
* 人脸检测:判断上传图片中是否具有面部头像
*/
public Boolean faceCheck(String image) {
JSONObject res = client.detect(image, IMAGE_TYPE, options);
if (res.has("error_code") && res.getInt("error_code") == 0) {
JSONObject resultObject = res.getJSONObject("result");
Integer faceNum = resultObject.getInt("face_num");//检测的人脸数量
return faceNum == 1?true:false;
}else{
return false;
}
}
/**
* 人脸查找:查找人脸库中最相似的人脸并返回数据
* 处理:用户的匹配得分(score)大于80分,即可认为是同一个用户
*/
public String faceSearch(String image) {
JSONObject res = client.search(image, IMAGE_TYPE, groupId, options);
if (res.has("error_code") && res.getInt("error_code") == 0) {
JSONObject result = res.getJSONObject("result");
JSONArray userList = result.getJSONArray("user_list");
if (userList.length() > 0) {
JSONObject user = userList.getJSONObject(0);
double score = user.getDouble("score");
if(score > 80) {
return user.getString("user_id");
}
}
}
return null;
}
}
骚戴理解:注意百度云API工具类里的image是Base64加密后的,所以在使用工具类之前要对图片进行Base64处理,也就是调用 String encode = Base64.encode(file.getBytes());来处理
同样的,在BaiduAiUtil类头添加 @Component 注解,使用时可通过 @Autowired 来自动装配。在API调用后返回值处理上,进行了简单的解析,如需深化解析,可按需扩展。
完成刷脸登录一共需要我们解决如下5个问题:
在刷脸登录之前,我们首先需要对系统中的用户进行人脸注册,将相关信息提交至人脸库,才可通过人脸识 别的相关接口进行刷脸登录操作。当用户相貌变更较大时,可通过人脸更新进行人脸信息更换。
获取验证码。通过工具生成相关信息后,如特殊标志,将特殊标志写入Redis缓存,并将标记值设为”-1“,我 们认定值为”-1“,即为当前标记尚未使用。调用QRCodeUtil.crateQRCode()生成二维码。
前端获取二维码后,对二维码进行展现,并且前台启动定时器,定时检测特殊标记状态值。当状态值 为“1”时,表明登录成功。
检查图像里面有没有人脸信息
当用户扫码进入落地页,通过落地页打开摄像头,并且定时成像。将成像图片,通过接口提交给后端进行人 脸检测。
检测成功后,即进行人脸登录,人脸登录后,改变特殊标记状态值,成功为“1”,失败为“0”。当登录成功时, 进行自动登录操作,将token和userId存入到redis中。
人脸注册/人脸更新
在刷脸登录之前,我们首先需要对系统中的用户进行人脸注册,将相关信息提交至人脸库,才可通过人脸识别的相关接口进行刷脸登录操作。当用户相貌变更较大时,可通过人脸更新进行人脸信息更换。
/**
* 上传用户头像---使用七牛云存储
* @param id
* @param file
* @return
*/
public String uploadImage(String id, MultipartFile file) throws IOException {
User user = userDao.findById(id).get();
//上传图片到七牛云
String image = new QiniuUploadUtil().upload(user.getId(),file.getBytes());
if (image!=null){
user.setStaffPhoto(image);
userDao.save(user);
}
//上传图片到百度云人脸库
Boolean exist = baiduAiUtil.faceExist(id);
String imgBase64 = Base64.encode(file.getBytes());
if (exist){
baiduAiUtil.faceUpdate(id,imgBase64);
}else {
baiduAiUtil.faceRegister(id,imgBase64);
}
return image;
}
骚戴理解:人脸注册在本系统里是在员工详细信息里面上传员工照片的时候就把照片上传到七牛云的同时还上传到百度云的人脸库里面!还需要注意的一个地方就是调用baiduAiUtil的方法传入的是Base64的url,不用加前缀,所以先要执行String imgBase64 = Base64.encode(file.getBytes());这行代码,一开始我还以为要加一个前缀 "data:image/jpg;base64,",加前缀的话是一个完整的data url路径,可以直接在浏览器访问,但是实际上百度云API只需要Base64后面加密的那部分即可
二维码生成
获取验证码。通过工具生成相关信息后,如特殊标志,将特殊标志写入Redis缓存,并将标记值设为”-1“,我们认定值为”-1“,即为当前标记尚未使用。调用QRCodeUtil.crateQRCode()生成二维码。
FaceLoginController:
@Autowired
private FaceLoginService faceLoginService;
/**
* 获取刷脸登录二维码
*/
@RequestMapping(value = "/qrcode", method = RequestMethod.GET)
public Result qrcode() throws Exception {
QRCode qrCode = faceLoginService.getQRCode();
return new Result(ResultCode.SUCCESS,qrCode);
}
FaceLoginService:
@Value("${qr.url}")
private String url;
@Autowired
private IdWorker idWorker;
@Autowired
private QRCodeUtil qrCodeUtil;
@Autowired
private BaiduAiUtil baiduAiUtil;
@Autowired
private RedisTemplate redisTemplate;
//创建二维码
public QRCode getQRCode() throws Exception {
//1.创建唯一标识
String code = idWorker.nextId() + "";
//2.生成二维码(url地址)
String content = url+"?code="+code;
System.out.println(content);
String file = qrCodeUtil.crateQRCode(content);
System.out.println(file);
//3.存入当前二维码状态(存入redis)
FaceLoginResult result = new FaceLoginResult("-1");
redisTemplate.boundValueOps(getCacheKey(code)).set(result,10, TimeUnit.MINUTES);//状态对象,失效时间,单位
return new QRCode(code,file);
}
//构造缓存key
private String getCacheKey(String code) {
return "qrcode_" + code;
}
骚戴理解:这里注意放到Redis里面的Key是"qrcode_" + code,code是唯一标识,value值是FaceLoginResult对象,表示二维码的状态,需要导入Redis的依赖
org.springframework.boot
spring-boot-starter-data-redis
配置Redis的配置文件
spring:
redis:
host: 127.0.0.1
port: 6379
测试一下二维码生成,直接访问生成的这个路径
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIAQAAAACFI5MzAAABYUlEQVR42u2XwYrDMAxEDfotg35doN8SeGfklE1b9ubZU9yUxn4XeTSW1bH+GuMhD3mIisQYtjKm15zeEw1JPJk1PK+JioRhEo5QLPdERvhwXU3K14Kg05WEcFqBI4ZPrc8RmiJf48s75wgH/BGGVf86C+cIUhYDfm9/UFURKR4sZGwVzpatMBHJtnpBSi7GLYLThB+uwIvtERXpEPZ3XskTEGjq1w/FLRMRJA3GMJa+LDjFVQRTlIpwbrbsruhZAh9ys136yspVpGg+On5OhDBNRGhD65spacib48+SruJGx8emIsJdQlUIipc3Rc8Sbq6nWH1X9CxB5kanDDx3GjWkS4WzdUACdwASgv1F3xhb1hSRVtXpeOQNbyrSN/qqq93KcBFhU0LHd+1jvVCRsKs36ar00ameJbEdz8Z4mpL0kfotsBJC08Mi6E+4V1MROuTleNxQ5SLy/Nd8yEP+jfwAQKalavReoooAAAAASUVORK5CYII=
二维码检测
前端获取二维码后,对二维码进行展现,并且前台启动定时器,定时检测特殊标记状态值。当状态值为“1”时,表明登录成功。
FaceLoginController:
/**
* 检查二维码:登录页面轮询调用此方法,根据唯一标识code判断用户登录情况
* 查询二维码扫描状态
* 返回值:FaceLoginResult
* state :-1,0,1 (userId和token)
*/
@RequestMapping(value = "/qrcode/{code}", method = RequestMethod.GET)
public Result qrcodeCeck(@PathVariable(name = "code") String code) throws Exception {
FaceLoginResult checkQRCode = faceLoginService.checkQRCode(code);
return new Result(ResultCode.SUCCESS,checkQRCode);
}
FaceLoginService:
人脸检测/人脸登录:当用户扫码进入落地页,通过落地页打开摄像头,并且定时成像。将成像图片,通过接 口提交给后端进行人脸检测。
//根据唯一标识,查询用户是否登录成功
public FaceLoginResult checkQRCode(String code) {
String key = getCacheKey(code);
return (FaceLoginResult) redisTemplate.opsForValue().get(key);
}
//构造缓存key
private String getCacheKey(String code) {
return "qrcode_" + code;
}
骚戴理解:二维码检测其实就是前端不断的发这个检测的请求,所谓的检测其实就是从Redis里面去获取唯一的code这个key的值,看这个值的状态是多少,如果是-1就是还未扫二维码,1就是登录成功,0就是登录失败!
图像检测
检测上传的图片有没有人脸信息
FaceLoginController:
/**
* 图像检测,判断图片中是否存在面部头像
*/
@RequestMapping(value = "/checkFace", method = RequestMethod.POST)
public Result checkFace(@RequestParam(name = "file") MultipartFile attachment) throws Exception {
String image = Base64Util.encode(attachment.getBytes());
Boolean aBoolean = baiduAiUtil.faceCheck(image);
if (aBoolean) {
return new Result(ResultCode.SUCCESS);
}else{
return new Result(ResultCode.FAIL);
}
}
人脸检测
检测成功后,即进行人脸登录,人脸登录后,改变特殊标记状态值,成功为“1”,失败为“0”。当登录成功时,进行自动登录操作,将token和userId存入到redis中。
FaceLoginController:
/**
* 人脸登录:根据落地页随机拍摄的面部头像进行登录
* 根据拍摄的图片调用百度云AI进行检索查找
*/
@RequestMapping(value = "/{code}", method = RequestMethod.POST)
public Result loginByFace(@PathVariable(name = "code") String code, @RequestParam(name = "file") MultipartFile attachment) throws Exception {
//人脸登录获取用户id(不为null登录成功)
String userId = faceLoginService.loginByFace(code, attachment);
if(userId != null) {
return new Result(ResultCode.SUCCESS);
}else{
return new Result(ResultCode.FAIL);
}
}
FaceLoginService:
@Autowired
private UserDao userDao;
//扫描二维码之后,使用拍摄照片进行登录
//返回值:登录成功之后返回的用户id
//登录失败:null
public String loginByFace(String code, MultipartFile attachment) throws Exception {
//1.调用百度云AI查询当前的用户
String userId = baiduAiUtil.faceSearch(Base64Util.encode(attachment.getBytes()));
//2.自动登录(token)
FaceLoginResult result = new FaceLoginResult("0");
if(userId != null) {
//自己模拟登录
//查询用户对象
User user = userDao.findById(userId).get();
if(user != null) {
//获取subject
Subject subject = SecurityUtils.getSubject();
//调用login方法登录
subject.login(new UsernamePasswordToken(user.getMobile(),user.getPassword()));
//获取token(sessionId)
String token = subject.getSession().getId()+"";
result = new FaceLoginResult("1",token,userId);
}
}
//3.修改二维码的状态
redisTemplate.boundValueOps(getCacheKey(code)).set(result,10,TimeUnit.MINUTES);
return userId;
}
骚戴理解:理清楚整个刷脸登录的逻辑,首先,生成二维码,把二维码的唯一标识code返回给前端,再把这个code存到Redis里,状态设置为-1,然后前端会把在发其他请求的时候每次带上整个code,前端设置定时任务,不断的发请求轮询查看二维码的状态,每次发这个请求的时候会去Redis里根据code去取对应的值,查看对应的状态,如果是-1就继续轮询,如果是1就表示登录成功,0就表示登录失败,在用户刷脸登录的时候,会调用摄像头刷脸登录,刷脸登录后就调用接口去判断有没有人脸信息,然后去百度云的人脸库里看看有没有这个人脸信息,如果匹配上了就模拟登录,修改Redis里的状态为1,然后就自动登录了,需要注意的是扫描二维码无法登录,只能靠生成的链接,然后访问链接,这个链接要是http请求才能访问,如果是https就无法访问这个链接
前端主要实现的功能是,获取二维码并展示,然后后台轮询检测刷脸登录状态,并且实现落地页相关功能(摄像头 调用、定时成像、发送人脸检测和发送人脸登录请求)
// 二维码
handlecode() {
qrcode().then(res => {
this.param.qrcode = res.data.file
this.centerDialogVisible = true
this.codeCheckInfo = res.data.code
setInterval(() => {
if (this.states === '-1') {
codeCheck({ code: res.data.code }).then(res => {
this.states = res.data.state
this.token = res.data.token
if (this.states === '0') {
// 登录
this.$store
.dispatch('LoginByCode', res.data.token)
.then(() => {
this.$router.push({ path: '/' })
})
.catch(() => {
})
}
if (this.states === '1') {
// 关闭
this.centerDialogVisible = false
}
})
}
}, 1000 * 10)
})
}
handleClick() {
let _this = this
if (!this.vdstate) {
return false
}
if (!_this.states) {
// 注册拍照按钮的单击事件
let video = this.$refs['vd']
let canvas = this.$refs['cav']
// let form = this.$refs["myForm"];
let context = canvas.getContext('2d')
// 绘制画面
context.drawImage(video, 0, 0, 200, 200)
let base64Data = canvas.toDataURL('image/jpg')
// 封装blob对象
let blob = this.dataURItoBlob(base64Data, 'camera.jpg') // base64 转图片file
let formData = new FormData()
formData.append('file', blob)
this.imgUrl = base64Data
checkFace(formData).then(res => {
if (res.data.isSuc) {
axios({
method: 'post',
url: '/api/frame/facelogin/' + this.$route.query.code,
data: formData
})
.then(function(response) {
console.log(response)
_this.states = true
_this.canvasShow = false
_this.tipShow = true
// _this.$message.success('验证通过' + '!')
})
.catch(function(error) {
console.log(error)
})
} else {
return false
}
})
}
},
dataURItoBlob(base64Data) {
var byteString
if (base64Data.split(',')[0].indexOf('base64') >= 0)
byteString = atob(base64Data.split(',')[1])
else byteString = unescape(base64Data.split(',')[1])
var mimeString = base64Data
.split(',')[0]
.split(':')[1]
.split(';')[0]
var ia = new Uint8Array(byteString.length)
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i)
}
return new Blob([ia], { type: mimeString })
}
}
通过上述的步骤,可以实现一个刷脸登录的功能,其核心在于百度云AI的使用。通过合理的使用百度云AI SDK提供的相关API,我们可以很轻松的实现刷脸登录功能。刷脸登录的业务流程有很多种,我们只是实现了一种借助二维码的方式,作为抛砖引玉。更多的流程和实现方式,在此不进行赘述。