本次的博客是自己软件工程课设的作业,写博客是为了记录自己的学习过程,然后总结,同时可以给需要的人作参考。
1、在做这个项目的时候,也是个大三学生,所以懂得并不多,因此在网上看见了一些相关的资源,但是都不是很清醒比较混乱,因此做完这个项目就想发表下自己的意见,以帮助需要的人(这里我们可以用数据库,也可以不用数据库,因为我做的这个项目并不是仅仅一个人脸识别,还有其他的业务,所以我用到了数据库,如果仅仅想弄个人脸注册登录的业务,可以不需要数据库)
让我们进入正题吧:
一、Web端人脸识别主要有三个技术思路:
1.前端的人脸识别,例如使用Tensorflow.js,
2.后台人脸识别,有很多开源或者免费的SDK可以使用
3.前后端结合,即结合以上两种方法,虽然系统复杂度提高,但对于系统的安全性,以及减轻服务器负担都有很大提升。
(本次的实验是调用百度云的Api以及前后端结合的方式去实现人脸注册以及登录的功能)
让我们先看下效果图吧:
1、登录的首页:
2、注册页面,因为我们想要人脸登录,首先要注册你自己的人脸保存百度云脸库中去
3、人脸识别登录
在这个登录页面我们要好好讲一讲,因为在做这个项目的时候,自己花了很多时间。
1、首先我们要明白,我这个前端页面调用电脑摄像头是自动截取图片的每1秒截取一张,所以不用我们点击确认登录(这里我们也没有这个按钮),它把我们的图片以base64字节码的形式采用异步Ajax的形式传到后台。
··
4、遇到错误上后台查找原因(遇到最多的就是你的脸不在摄像头的正中间,也就是有效范围)
二、因为在百度云上申请自己的账号(免费),注册自己的人脸库
1、我的百度云人脸库
2、如何创建人脸库(刚开始你们自己肯定没有所以需要创建,创建成功会给你一个API key 及Secret Key,这回头我们在程序中调用人脸库,这个就是地址)
3、连接百度云人脸库,无论是人脸检测、人脸搜索、人脸对比,身份验证、都需要首先获取access_token(这个类百度云Api文档里面都有,我知道大家懒,所以直接复制在这里面了)
package com.baidu.ai.aip.auth;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.Map;
/**
* 获取token类
*/
public class AuthService {
/**
* 获取权限token
* @return 返回示例:
* {
* "access_token": "24.460da4889caad24cccdb1fea17221975.2592000.1491995545.282335-1234567",
* "expires_in": 2592000
* }
*/
public static String getAuth() {
// 官网获取的 API Key 更新为你注册的
String clientId = "百度云应用的AK";
// 官网获取的 Secret Key 更新为你注册的
String clientSecret = "百度云应用的SK";
return getAuth(clientId, clientSecret);
}
/**
* 获取API访问token
* 该token有一定的有效期,需要自行管理,当失效时需重新获取.
* @param ak - 百度云官网获取的 API Key
* @param sk - 百度云官网获取的 Securet Key
* @return assess_token 示例:
* "24.460da4889caad24cccdb1fea17221975.2592000.1491995545.282335-1234567"
*/
public static String getAuth(String ak, String sk) {
// 获取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=" + ak
// 3. 官网获取的 Secret Key
+ "&client_secret=" + sk;
try {
URL realUrl = new URL(getAccessTokenUrl);
// 打开和URL之间的连接
HttpURLConnection connection = (HttpURLConnection) realUrl.openConnection();
connection.setRequestMethod("GET");
connection.connect();
// 获取所有响应头字段
Map<String, List<String>> map = connection.getHeaderFields();
// 遍历所有的响应头字段
for (String key : map.keySet()) {
System.err.println(key + "--->" + map.get(key));
}
// 定义 BufferedReader输入流来读取URL的响应
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String result = "";
String line;
while ((line = in.readLine()) != null) {
result += line;
}
/**
* 返回结果示例
*/
System.err.println("result:" + result);
JSONObject jsonObject = new JSONObject(result);
String access_token = jsonObject.getString("access_token");
return access_token;
} catch (Exception e) {
System.err.printf("获取token失败!");
e.printStackTrace(System.err);
}
return null;
}
}
在这里也很容易出错,我就在获取**access_token** 中吃了亏,如果获取不到,则控制台上面不会有 access_token的字符串,如果成功获取access_token的字符串,就会默认在控制台下打印,
- 正常的access_token的样子:
三、前端如何获取我们的摄像头(在这里我花了很多时间,因为自己做的是后端开发,前端还没能力写出【有能力的同学Pass这句话!!】,只能翻阅大量资料,但是网上都是不足的,我在这里将给大家一个完整的前端Jsp代码)
3.1、注册的页面(注册页面大家可以自己写其实就是文件上传,我自己这个业务比较复杂)
<%--
Document : index
Created on : 2020-5-10, 9:49:31
Author : Administrator
--%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<title>欢迎使用</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css/pintuer.css">
<script src="js/jquery.js"></script>
<script src="js/pintuer.js"></script>
</head>
<body>
<!--头部开始-->
<%-- <%@include file="WEB-INF/jspf/header.jspf" %>--%>
<!--头部结束-->
<!--内容开始-->
<div class="container padding-big-top padding-big-bottom">
<div class="line-big">
<div class="xl12 xs3 xm3 xb7">
<form method="post" action="/faceinsert" enctype="multipart/form-data">
<div class="form-group">
<div class="label">
<label for="yno">
用户编号</label>
</div>
<div class="field">
<input type="text" class="input" id="sno" name="yno" size="50" placeholder="用户编号" value="${yonghu.yno}" data-validate="required:必填" />
</div>
</div>
<div class="form-group">
<div class="label">
<label for="username">
用户名</label>
</div>
<div class="field">
<input type="text" class="input" id="snmae" name="username" size="50" placeholder="用户名" value="${yonghu.username}" data-validate="required:必填"/>
</div>
</div>
<div class="form-group">
<div class="label">
<label for="userloginname">
用户登录名</label>
</div>
<div class="field">
<input type="text" class="input" id="ssex" name="userloginname" size="50" placeholder="用户登录名" value="${yonghu.userloginname}"/>
</div>
</div>
<div class="form-group">
<div class="label">
<label for="password">
密码</label>
</div>
<div class="field">
<input type="text" class="input" id="ssex" name="password" size="50" placeholder="密码" value="${yonghu.userloginname}"/>
</div>
</div>
<div class="form-group">
<div class="label">
<label for="apassword">
确认密码</label>
</div>
<div class="field">
<input type="text" class="input" id="ssex" name="apassword" size="50" placeholder="确认密码" value="${yonghu.userloginname}"/>
</div>
</div>
<div class="form-group">
<div class="label">
<label for="name">
姓名</label>
</div>
<div class="field">
<input type="text" class="input" id="sage" name="name" size="50" placeholder="姓名" value="${yonghu.name}" data-validate="required:必填"/>
</div>
</div>
<div class="form-group">
<div class="label">
<label for="sex">
性别</label>
</div>
<div class="field">
<input type="text" class="input" id="sdept" name="sex" size="50" placeholder="性别" value="${yonghu.sex}" data-validate="required:必填"/>
</div>
</div>
<div class="form-group">
<div class="label">
<label for="username">
电话</label>
</div>
<div class="field">
<input type="text" class="input" id="phone" name="tel" size="50" placeholder="电话" value="${yonghu.tel}" data-validate="required:必填"/>
</div>
</div>
<div class="form-group">
<div class="label">
<label for="username">
邮箱</label>
</div>
<div class="field">
<input type="text" class="input" id="phone" name="email" size="50" placeholder="电话" value="${yonghu.email}" data-validate="required:必填"/>
</div>
</div>
<%-- <div> <!--验证码-->--%>
<%-- <input type="text" name="checkCode"/>--%>
<%-- <img alt="验证码" id="imagecode" src="/ImageServlet"/>--%>
<%-- <a href="javascript:void(0)" οnclick="myReload()">看不清楚</a><br>--%>
<%-- <script type="text/javascript">--%>
<%-- function myReload()--%>
<%-- {--%>
<%-- document.getElementById("imagecode").src ="/ImageServlet?"+Math.random();--%>
<%-- }--%>
<%-- </script>--%>
<%-- </div>--%>
<p style="color: red">${err}</p>
<div>
<input placeholder="请选择头像" type="file" name="image"/>
</div>
<div class="form-button">
<button class="button" type="submit">
提交</button>
</div>
</form>
</div>
<div class="xl12 xs9 xm9 xb5 padding-small-top">
<img src="images/1.jpg" alt="学生信息管理系统" width="500px" height="350px"/>
</div>
</div>
</div>
<!--内容结束-->
<!--尾部开始-->
<%-- <%@include file="WEB-INF/jspf/footer.jspf" %>--%>
<!--尾部结束-->
</body>
</html>
3.2获取getface.Js的代码
$(function () {
let mediaStreamTrack = null;
openMedia();
setTimeout("tishi()", "1000"); <!---缓存1000毫秒-->
setTimeout("tishi2()", "3000");
setTimeout("takePhoto()", "5000");
})
var number = 0;
function tishi() {
$("#flag").html("正在打开摄像头")
}
function tishi2() {
$("#flag").html("请正视摄像头")
}
function tishi3() {
window.location.href = "login.jsp";
}
function openMedia() {
let constraints = {
video: {width: 500, height: 500},
audio: false
};
//获得video摄像头
let video = document.getElementById('video');
let promise = navigator.mediaDevices.getUserMedia(constraints);
promise.then((mediaStream) => {
mediaStreamTrack = typeof mediaStream.stop === 'function' ? mediaStream : mediaStream.getTracks()[1];
video.srcObject = mediaStream;
video.play();
});
}
// 拍照
function takePhoto() {
//获得Canvas对象
number++;
let video = document.getElementById('video');
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0, 500, 500);
// toDataURL --- 可传入'image/png'---默认, 'image/jpeg'
let img = document.getElementById('canvas').toDataURL();
// 这里的img就是得到的图片
console.log('img-----', img);
document.getElementById('imgTag').src = img;
$("#flag").html("正在识别");
$.ajax({
url: "/faceloginServlet", //请求的url地址
dataType: "text", //返回格式为json
async: true,//请求是否异步,默认为异步,这也是ajax重要特性
// contentType:"application/json",
data: {"imagebast64": img}, //参数值
type: "POST", //请求方式
success: function (data) {
// if(data=="fail"||data.score.substr(0,2)<80){
// $("#flag").html("识别失败,请保持人像处于框内 2秒后重新识别");
// if(number<3){
// setTimeout("takePhoto()","3000");
// }else {
// $("#flag").html("识别失败请使用账号密码登录 三秒后回到主页");
// setTimeout("tishi3()","3000");
// }
// }
// if(data.score.substr(0,2)>80){
// window.location.href="/display"
// }
//
//
//
//
// }
alert(data);
var scoreMatch = data.split(".")[0];
if (scoreMatch > 72) {
console.log("cheng---gong");
window.location.href = "/display";
} else {
window.location.href = "login.jsp";
}
}
})
}
// 关闭摄像头
function closeMedia() {
mediaStreamTrack.stop();
}
3.3 facelogin.jsp文件
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" %>
<!doctype html>
<html lang="zh">
<head>
<title>js调用摄像头拍照上传图片</title>
<meta charset="utf-8">
</head>
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/Getface.js"></script>
<style>
.getface{
position: absolute;
top: 20%;
left: 35%;
}
.tishi{
font-size: 20px;
}
</style>
<body>
<div align="center">
<p id="flag" class="tishi"></p>
</div>
<div class="getface">
<video id="video" width="400px" height="400px" autoplay="autoplay"></video>
<canvas id="canvas" width="400px" height="400px" style="display: none;"></canvas>
<img id="imgTag" src="" alt="imgTag" style="display: none;">
</div>
</body>
ok!!!,按照上面的原封不动你就已经把前端的代码搭建好了
四、百度云人脸技术有好几项技术,这里我们的项目只用到了人脸搜索和人脸库管理中(人脸注册)这两个技术就够了(如果对人脸对比,和人脸检测有兴趣,可以自己观看文档)
4.1、在后端的开发中我们首先要在百度云人脸识别的文档中下载几个类,这是给你准备好的直接在地址栏复制就可下载
/**
* 重要提示代码中所需工具类
* FileUtil,Base64Util,HttpUtil,GsonUtils请从
* https://ai.baidu.com/file/658A35ABAB2D404FBF903F64D47C1F72
* https://ai.baidu.com/file/C8D81F3301E24D2892968F09AE1AD6E2
* https://ai.baidu.com/file/544D677F5D4E4F17B4122FBD60DB82B3
* https://ai.baidu.com/file/470B3ACCA3FE43788B5A963BF0B625F3
* 下载
*/
我们来解释下这几个类是干什么用的:
1、FileUtil:这个类是帮助我们读取前端传来登录头像的数据,作为字符串返回
2、Base64Util:这个类辅助上面的一个类,帮我们解析传过来的数据(我们知道前端我们使用Base64字节码传的,所以就必须用BaseUtil这个工具类去解析)
3、HttpUtil:这个类让我们可以在程序中,就可以请求百度云的Ip,感觉很牛皮,至今不知道为什么,知道的欢迎在评论告诉我
4.这个学过Json数据格式的都知道,这个是处理Json与字符串之间转换的工具类
4.2、前期的准备工作已经做好,让我们写后端代码吧
4.2.1、我们知道想要用人脸识别技术就首先注册自己的人脸到百度云人脸库中去
package com.baidu.ai.aip;
import com.baidu.ai.aip.utils.HttpUtil;
import com.baidu.ai.aip.utils.GsonUtils;
import java.util.*;
/**
* 人脸注册
*/
public class FaceAdd {
/**
* 重要提示代码中所需工具类
* FileUtil,Base64Util,HttpUtil,GsonUtils请从
* https://ai.baidu.com/file/658A35ABAB2D404FBF903F64D47C1F72
* https://ai.baidu.com/file/C8D81F3301E24D2892968F09AE1AD6E2
* https://ai.baidu.com/file/544D677F5D4E4F17B4122FBD60DB82B3
* https://ai.baidu.com/file/470B3ACCA3FE43788B5A963BF0B625F3
* 下载
*/
public static String add() {
// 请求url
String url = "https://aip.baidubce.com/rest/2.0/face/v3/faceset/user/add";
try {
Map<String, Object> map = new HashMap<>();
map.put("image", "027d8308a2ec665acb1bdf63e513bcb9");
map.put("group_id", "group_repeat");
map.put("user_id", "user1");
map.put("user_info", "abc");
map.put("liveness_control", "NORMAL");
map.put("image_type", "FACE_TOKEN");
map.put("quality_control", "LOW");
String param = GsonUtils.toJson(map);
// 注意这里仅为了简化编码每一次请求都去获取access_token,线上环境access_token有过期时间, 客户端可自行缓存,过期后重新获取。
String accessToken = "[调用鉴权接口获取的token]";
String result = HttpUtil.post(url, accessToken, "application/json", param);
System.out.println(result);
return result;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
FaceAdd.add();
}
}
这是官方给的代码,通过上面我自己的代码,我相信照这两个你们会搭建好自己的业务代码(这里可以根据前端实现图片上传功能,再将图片转base64数据上传到人脸库,这里group_id为注册人脸库用户组时自己设置的,以实现注册功能,)
4.2.2、然后我们就可以人脸登录,然后调用百度云人脸搜索的功能了,如何搜索到你目前登录的和你刚刚注册的一样,就会登录进去,否则失败
package utils;
/**
* @author Administrator
*
*/
import java.util.*;
import entity.AuthService;
import utils.GsonUtils;
import utils.HttpUtil;
/**
* 人脸搜索
*/
public class faceSearch {
/**
* 重要提示代码中所需工具类
* FileUtil,Base64Util,HttpUtil,GsonUtils请从
* https://ai.baidu.com/file/658A35ABAB2D404FBF903F64D47C1F72
* https://ai.baidu.com/file/C8D81F3301E24D2892968F09AE1AD6E2
* https://ai.baidu.com/file/544D677F5D4E4F17B4122FBD60DB82B3
* https://ai.baidu.com/file/470B3ACCA3FE43788B5A963BF0B625F3
* 下载
*/
public static String search(String image) {
// 请求url
String url = "https://aip.baidubce.com/rest/2.0/face/v3/search";
try {
Map<String, Object> map = new HashMap<>();
String substring = image.substring(image.indexOf(",")+1, image.length());
map.put("image", substring); //图片base64数据
map.put("liveness_control", "NONE"); //活体检测控制无
map.put("group_id_list", "testFaceLogin"); //指定用户组group 人脸库总已经存在的用户组
map.put("image_type", "BASE64"); //图片类型,这里转化过的base64
map.put("quality_control", "LOW"); //图片质量控制
String param = GsonUtils.toJson(map);
AuthService auth = new AuthService();
String accessToken = auth.getAuth();
// 注意这里仅为了简化编码每一次请求都去获取access_token,线上环境access_token有过期时间, 客户端可自行缓存,过期后重新获取。
String result = HttpUtil.post(url, accessToken, "application/json", param);
String score = result.split(",")[9].split(":")[1];
System.out.println(result);
System.out.println(score);
return score;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
上面的图片是我自己人脸登录的Servlet,结合下面官方给的,我相信你们就理解了人脸搜索功能的意思,通过人脸搜索类任何去匹配人脸库中最像的一个给出分数,然后把这个分数用输出流传递给前端,在前段设置个阀值(我设置的是75),如何大于75分说明在人脸库中找到了你目前登录人的信息,则给予通过,否则相反
这是刚刚Getface.js文件中的