java使用face++简单实现人脸识别注册登录
人脸识别,好高大上!!!
理解之后很简单。
支付宝使用的就是face++,
至于face++ 账号信息,apikey….., 本文不做讲述, 网上很多.
寻找图片上的关键点,制作一套算法,分析脸部信息,将得到的数据存入数据库,
登录的时候,通过同样的算法,分析数据,和数据库中存入的信息进行比对……
工作量好大!!!
face++有着它独有的非常优秀的算法,我们可以将我们的图片上传到face++服务器来获取对应的图片数据,剩下的事情就很简单了
这里只介绍用到的api,
作用:创建一个FaceSet,创建一个脸部信息集合,引用官网的描述:
url: https://api-cn.faceplusplus.com/facepp/v3/faceset/create
参数:api_key,api_secret,……
返回值 : faceset_token, outer_id……,这里写的两个返回值需要记住,是这个脸部信息集合的唯一标识,具体返回值信息如下图:
作用:向脸部信息集合faceSet添加一条或多条脸部信息,便于后期搜索
url:https://api-cn.faceplusplus.com/facepp/v3/faceset/addface
参数:不用说,肯定需要,api_key,api_secret,faceSet_token或outer_id(脸部信息唯一标识),还有图片信息,官网截图:
返回值:可以获取插入的结果信息
.
作用:传入一张图片信息到face++服务器,会返回最相似的face_token
url:https://api-cn.faceplusplus.com/facepp/v3/search
参数:api_key,api_secret,image_url或image_file或image_base64或face_token,详细参数列表如下
是否必选 |
参数名 |
类型 |
参数说明 |
必选 |
api_key |
String |
调用此 API 的 API Key |
必选 |
api_secret |
String |
调用此 API 的 API Secret |
必选(四选一) |
face_token |
String |
进行搜索的目标人脸的 face_token,优先使用该参数 |
image_url |
String |
目标人脸所在的图片的 URL |
|
image_file |
File |
目标人脸所在的图片,二进制文件,需要用 post multipart/form-data 的方式上传。 |
|
image_base64 |
String |
base64 编码的二进制图片数据 如果同时传入了 image_url、image_file 和 image_base64 参数,本 API 使用顺序为 image_file 优先,image_url 最低。 |
|
必选(二选一) |
faceset_token |
String |
用来搜索的 FaceSet 的标识 |
outer_id |
String |
用户自定义的 FaceSet 标识 |
|
可选 |
return_result_count |
Int |
控制返回比对置信度最高的结果的数量。合法值为一个范围 [1,5] 的整数。默认值为 1 |
可选(仅正式 API Key 可以使用) |
face_rectangle |
String |
当传入图片进行人脸检测时,是否指定人脸框位置进行检测。 如果此参数传入值为空,或不传入此参数,则不使用此功能。本 API 会自动检测图片内所有区域的所有人脸。 如果使用正式 API Key 对此参数传入符合格式要求的值,则使用此功能。需要传入一个字符串代表人脸框位置,系统会根据此坐标对框内的图像进行人脸检测,以及人脸关键点和人脸属性等后续操作。系统返回的人脸矩形框位置会与传入的 face_rectangle 完全一致。对于此人脸框之外的区域,系统不会进行人脸检测,也不会返回任何其他的人脸信息。 参数规格:四个正整数,用逗号分隔,依次代表人脸框左上角纵坐标(top),左上角横坐标(left),人脸框宽度(width),人脸框高度(height)。例如:70,80,100,100 注:只有在传入 image_url、image_file 和 image_base64 三个参数中任意一个时,本参数才生效。 |
返回值:返回值包含和你传入图片信息最像的图片的face_token,(可以再和数据库中对应的信息进行比较)
传入一张图片信息,获取这张图片的face_token,注意,一张相同图片获取多次的face_token不同
url: https://api-cn.faceplusplus.com/facepp/v3/detect
参数:api_key,api_secret, image_url或image_file或image_base64,
是否必选 |
参数名 |
类型 |
参数说明 |
||||||
必选 |
api_key |
String |
调用此API的API Key |
||||||
必选 |
api_secret |
String |
调用此API的API Secret |
||||||
必选(三选一) |
image_url |
String |
图片的 URL。 注:在下载图片时可能由于网络等原因导致下载图片时间过长,建议使用 image_file 或 image_base64 参数直接上传图片。 |
||||||
image_file |
File |
一个图片,二进制文件,需要用post multipart/form-data的方式上传。 |
|||||||
image_base64 |
String |
base64 编码的二进制图片数据 如果同时传入了 image_url、image_file 和 image_base64 参数,本API使用顺序为 image_file 优先,image_url 最低。 |
|||||||
可选 |
return_landmark |
Int |
是否检测并返回人脸关键点。合法值为:
注:本参数默认值为 0 |
||||||
可选 |
return_attributes |
String |
是否检测并返回根据人脸特征判断出的年龄、性别、情绪等属性。合法值为:
注:本参数默认值为 none |
||||||
可选(仅正式 API Key 可以使用) |
calculate_all |
Int |
是否检测并返回所有人脸的人脸关键点和人脸属性。如果不使用此功能,则本 API 只会对人脸面积最大的五个人脸分析人脸关键点和人脸属性。合法值为:
注:本参数默认值为 0 |
||||||
可选(仅正式 API Key 可以使用) |
face_rectangle |
String |
是否指定人脸框位置进行人脸检测。 如果此参数传入值为空,或不传入此参数,则不使用此功能。本 API 会自动检测图片内所有区域的所有人脸。 如果使用正式 API Key 对此参数传入符合格式要求的值,则使用此功能。需要传入一个字符串代表人脸框位置,系统会根据此坐标对框内的图像进行人脸检测,以及人脸关键点和人脸属性等后续操作。系统返回的人脸矩形框位置会与传入的 face_rectangle 完全一致。对于此人脸框之外的区域,系统不会进行人脸检测,也不会返回任何其他的人脸信息。 参数规格:四个正整数,用逗号分隔,依次代表人脸框左上角纵坐标(top),左上角横坐标(left),人脸框宽度(width),人脸框高度(height)。例如:70,80,100,100 |
||||||
可选 |
beauty_score_min |
Int |
颜值评分分数区间的最小值。默认为0 注:默认颜值评分分数区间为0-100.可通过beauty_score_min和beauty_score_max来调节分数区间,满足您的场景需求。 |
||||||
可选 |
beauty_score_max |
Int |
颜值评分分数区间的最大值。默认为100 |
返回值:图片对应的face_token
字段 |
类型 |
说明 |
request_id |
String |
用于区分每一次请求的唯一的字符串。 |
faces |
Array |
被检测出的人脸数组,具体包含内容见下文。 注:如果没有检测出人脸则为空数组 |
image_id |
String |
被检测的图片在系统中的标识。 |
time_used |
Int |
整个请求所花费的时间,单位为毫秒。 |
error_message |
String |
当请求失败时才会返回此字符串,具体返回内容见后续错误信息章节。否则此字段不存在。 |
在faces中包含face_token
将face_token存入faceSet,(调用addFace api存入)
将face_token存入数据库
返回值为在faceSet中,和传入图片相似度高的face_token
通过返回的face_token,在数据库中进行查询,实现登陆
有了上面的分析,即使使用javaweb也能实现了
本案例使用
maven
java的ssm框架
配上Druid连接池
前端使用了jquery,(不懂前端,通过参考和自己设计写的很low)
实体类:
User{
String username;
String password;
String other; //在本案例中没有作用
String faceToken;
}
技术不高,自己写的一个简单的界面
注册界面:register.html
<!DOCTYPE html >
< html lang= "en" >
< head >
< meta charset= "UTF-8" >
< title >Title</ title >
< script src= "js/jquery-3.3.1.js" ></ script >
< style >
*{
margin: 0;
padding: 0;
}
html, body {
width: 100%;
height: 100%;
}
body{
background: url ( img/bg.jpg ) no-repeat center ;
}
h1{
color: #fff ;
text-align: center ;
line-height: 80 px ;
}
. media {
width: 534 px ;
height: 400 px ;
margin: 40 px auto 0;
transform-style: preserve-3d ;
transform: perspective (600 px ) rotateY (25 deg );
}
#register{
width: 200 px ;
height: 50 px ;
background: #0089ff ;
margin: 60 px auto 0;
text-align: center ;
line-height: 50 px ;
color: #fff ;
/* color: red;*/
border-radius : 16 px ;transform-style: preserve-3d ;
transform: perspective (600 px ) rotateY (25 deg ) rotateZ (-10 deg );
cursor: pointer ;
}
</ style >
</ head >
< body >
<!–autoplay="true"–>
< div class= "media" >< video id= "myVideo" height= "534" width= "400" src= "" autoplay ></ video >
< canvas id= "myCanvas" height= "534" width= "400" ></ canvas >
</ div >
创建一个注册的按钮–>
< form action= "register.do" id= "registerForm" >用户名: < input type= "text" name= "username" >< br >
密码 :< input type= "text" name= "password" >< br >
< input type= "hidden" name= "faceToken" id= "faceToken" value= "" >< br >
备注字段:< input type= "text" name= "other" >< br >
< input type= "button" id= "toUpPic" value= " 上传图片" >
< input type= "button" id= "register" value= " 注册" >
</ form >
注册</div>–>
< script >这里写的是网页脚本
//调用摄像头获取媒体视频流
/***
* 默认的写法:navigator.getUserMedia
* 火狐:navigator.mozGetUserMedia
* 微软:navigator.msGetUserMedia
* 谷歌:navigator.webkitGetUserMedia
*
* @type {((constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback) => void) | *}
*/
var getUserMedia = ( navigator .getUserMedia || navigator .webkitGetUserMedia || navigator .mozGetUserMedia || navigator .msGetUserMedia);var video = document .getElementById( "myVideo" );
/***
四个参数 getUserMedia.call(navigator,{video:true,audio:false},function(){},function(){});
* 1.要调用的对象
* 2.约束对象:eg:只调用视频
* 3.调用成功的方法
* 4.调用失败的方法
*/
getUserMedia .call( navigator , { video : true , audio : false }, function (localMediaStream) {// 这里是调用成功的方法,如果调用成功,将视频流对象传到myVideo,localMediaStream是传入的视频流对象
/*document.getElementById("myVideo").src= window.URL.createObjectURL(localMediaStream);
* 上一行的代码已经过时了
* */
try{
video . src = window .URL. createObjectURL (localMediaStream);
} catch (e) {
执行的是这段代码
video . srcObject = localMediaStream;}
/***
*
下面三行代码可以代替了video的autoplay属性
*/
/* video.onloadedmetadata = function () {
video.play();
}*/
}, function (e) {
通过控制台将我们的错误信息打印
});获取登陆按钮
var btn_register = document .getElementById( "register" );var toUpPic = document .getElementById( "toUpPic" );
获取canvas对象
var canvas = document .getElementById( "myCanvas" );获取上下文对象
var context = canvas .getContext( "2d" );登陆按钮绑定点击事件
toUpPic .onclick = function () {点击登录按钮获取面部信息,(点击登录按钮的时候将图像画到)
// context.drawImage(video,x轴开始位置,y轴开始位置,x轴结束位置,y轴结束位置);
context .drawImage( video , 0, 0, 534, 400);表示画成什么格式
var imgSrc = document .getElementById( "myCanvas" ).toDataURL( "image/png" );alert (imgSrc);
// var Baseimg=imgSrc.split(",")[1];
$ .post( "getFaceToken.do" , { imgSrc : imgSrc}, function (faceToken) {alert (faceToken);
if(faceToken) {
$( "#faceToken" ).val(faceToken);
} else {
alert ( " 登录失败,请重新扫描" );
}
});
}
btn_register .onclick= function () {
$( "#registerForm" ).submit();
}
</ script >
</ body >
</ html >
登录界面:login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/jquery-3.3.1.js"></script>
<style>
* {
margin: 0;
padding: 0;
}
html, body {
width: 100%;
height: 100%;
}
body {
background: url(img/bg.jpg) no-repeat center;
}
h1 {
color: #fff;
text-align: center;
line-height: 80px;
}
.media {
width: 534px;
height: 400px;
margin: 40px auto 0;
transform-style: preserve-3d;
transform: perspective(600px) rotateY(25deg);
}
#login {
width: 200px;
height: 50px;
background: #0089ff;
margin: 60px auto 0;
text-align: center;
line-height: 50px;
color: #fff;
/* color: red;*/
border-radius: 16px;
transform-style: preserve-3d;
transform: perspective(600px) rotateY(25deg) rotateZ(-10deg);
cursor: pointer;
}
</style>
</head>
<body>
<!--autoplay="true"-->
<div class="media">
<video id="myVideo" height="534" width="400" src="" autoplay></video>
<canvas id="myCanvas" height="534" width="400"></canvas>
</div>
<!--创建一个登陆的按钮-->
<div id="login">登陆</div>
<script>
//这里写的是网页脚本
//调用摄像头获取媒体视频流
/***
* 默认的写法:navigator.getUserMedia
* 火狐:navigator.mozGetUserMedia
* 微软:navigator.msGetUserMedia
* 谷歌:navigator.webkitGetUserMedia
*
* @type {((constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback) => void) | *}
*/
var getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia);
var video = document.getElementById("myVideo");
/***
* 四个参数 getUserMedia.call(navigator,{video:true,audio:false},function(){},function(){});
* 1.要调用的对象
* 2.约束对象:eg:只调用视频
* 3.调用成功的方法
* 4.调用失败的方法
*/
getUserMedia.call(navigator, {video: true, audio: false}, function (localMediaStream) {
//这里是调用成功的方法,如果调用成功,将视频流对象传到myVideo,localMediaStream是传入的视频流对象
/*document.getElementById("myVideo").src= window.URL.createObjectURL(localMediaStream);
* 上一行的代码已经过时了
* */
try {
video.src = window.URL.createObjectURL(localMediaStream);
} catch (e) {
//执行的是这段代码
video.srcObject = localMediaStream;
}
/***
* 下面三行代码可以代替了video的autoplay属性
*/
/* video.onloadedmetadata = function () {
video.play();
}*/
}, function (e) {
console.log("获取摄像头失败", e);//通过控制台将我们的错误信息打印
});
//获取登陆按钮
var btn_login = document.getElementById("login");
//获取canvas对象
var canvas=document.getElementById("myCanvas");
//获取上下文对象
var context = canvas.getContext("2d");
//登陆按钮绑定点击事件
btn_login.onclick = function () {
//点击登录按钮获取面部信息,(点击登录按钮的时候将图像画到)
// context.drawImage(video,x轴开始位置,y轴开始位置,x轴结束位置,y轴结束位置);
context.drawImage(video, 0, 0, 534, 400);
//image/png 表示画成什么格式
var imgSrc = document.getElementById("myCanvas").toDataURL("image/png");
alert(imgSrc);
// var Baseimg=imgSrc.split(",")[1];
$.post("login.do",{imgSrc:imgSrc},function (result) {
if(result){
location.href="success.jsp";
}else{
alert("登录失败,请重新扫描");
}
})
}
</script>
</body>
</html>
face相关类,通过face++官网查到一个demo,本案例修改demo并封装了自己的信息,打成实现功能
获取到的demo:
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import javax.net.ssl.SSLException;
public class FaceTest {
public static void main(String[] args) throws Exception{
File file = new File("你的本地图片路径");
byte[] buff = getBytesFromFile(file);
String url = "https://api-cn.faceplusplus.com/facepp/v3/detect";
HashMap<String, String> map = new HashMap<>();
HashMap<String, byte[]> byteMap = new HashMap<>();
map.put("api_key", "你的KEY");
map.put("api_secret", "你的SECRET");
map.put("return_landmark", "1");
map.put("return_attributes", "gender,age,smiling,headpose,facequality,blur,eyestatus,emotion,ethnicity,beauty,mouthstatus,eyegaze,skinstatus");
byteMap.put("image_file", buff);
try{
byte[] bacd = post(url, map, byteMap);
String str = new String(bacd);
System.out.println(str);
}catch (Exception e) {
e.printStackTrace();
}
}
private final static int CONNECT_TIME_OUT = 30000;
private final static int READ_OUT_TIME = 50000;
private static String boundaryString = getBoundary();
protected static byte[] post(String url, HashMap<String, String> map, HashMap<String, byte[]> fileMap) throws Exception {
HttpURLConnection conne;
URL url1 = new URL(url);
conne = (HttpURLConnection) url1.openConnection();
conne.setDoOutput(true);
conne.setUseCaches(false);
conne.setRequestMethod("POST");
conne.setConnectTimeout(CONNECT_TIME_OUT);
conne.setReadTimeout(READ_OUT_TIME);
conne.setRequestProperty("accept", "*/*");
conne.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundaryString);
conne.setRequestProperty("connection", "Keep-Alive");
conne.setRequestProperty("user-agent", "Mozilla/4.0 (compatible;MSIE 6.0;Windows NT 5.1;SV1)");
DataOutputStream obos = new DataOutputStream(conne.getOutputStream());
Iterator iter = map.entrySet().iterator();
while(iter.hasNext()){
Map.Entry<String, String> entry = (Map.Entry) iter.next();
String key = entry.getKey();
String value = entry.getValue();
obos.writeBytes("--" + boundaryString + "/r/n");
obos.writeBytes("Content-Disposition: form-data; name=/"" + key
+ "/"/r/n");
obos.writeBytes("/r/n");
obos.writeBytes(value + "/r/n");
}
if(fileMap != null && fileMap.size() > 0){
Iterator fileIter = fileMap.entrySet().iterator();
while(fileIter.hasNext()){
Map.Entry<String, byte[]> fileEntry = (Map.Entry<String, byte[]>) fileIter.next();
obos.writeBytes("--" + boundaryString + "/r/n");
obos.writeBytes("Content-Disposition: form-data; name=/"" + fileEntry.getKey()
+ "/"; filename=/"" + encode(" ") + "/"/r/n");
obos.writeBytes("/r/n");
obos.write(fileEntry.getValue());
obos.writeBytes("/r/n");
}
}
obos.writeBytes("--" + boundaryString + "--" + "/r/n");
obos.writeBytes("/r/n");
obos.flush();
obos.close();
InputStream ins = null;
int code = conne.getResponseCode();
try{
if(code == 200){
ins = conne.getInputStream();
}else{
ins = conne.getErrorStream();
}
}catch (SSLException e){
e.printStackTrace();
return new byte[0];
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buff = new byte[4096];
int len;
while((len = ins.read(buff)) != -1){
baos.write(buff, 0, len);
}
byte[] bytes = baos.toByteArray();
ins.close();
return bytes;
}
private static String getBoundary() {
StringBuilder sb = new StringBuilder();
Random random = new Random();
for(int i = 0; i < 32; ++i) {
sb.append("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-".charAt(random.nextInt("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_".length())));
}
return sb.toString();
}
private static String encode(String value) throws Exception{
return URLEncoder.encode(value, "UTF-8");
}
public static byte[] getBytesFromFile(File f) {
if (f == null) {
return null;
}
try {
FileInputStream stream = new FileInputStream(f);
ByteArrayOutputStream out = new ByteArrayOutputStream(1000);
byte[] b = new byte[1000];
int n;
while ((n = stream.read(b)) != -1)
out.write(b, 0, n);
stream.close();
out.close();
return out.toByteArray();
} catch (IOException e) {
}
return null;
}
}
人无我有,人有我优
思路很清晰,具体实现很难!!!
实现后感觉很简单