欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

java使用face++简单实现人脸识别注册登录

程序员文章站 2023-11-25 23:58:40
java使用face++简单实现人脸识别注册登录 前言 人脸识别,好高大上!!! 理解之后很简单。 支付宝使用的就是face++, 至于face++账号信息,apikey…..,本文不做讲述,网上很多. 一.设计思想 1. 先想一想,如果让你实现人脸识别,你会怎么做? 寻找图片上的关键点,制作一套算 ......

 

java使用face++简单实现人脸识别注册登录

前言

人脸识别,好高大上!!!

理解之后很简单。

 

支付宝使用的就是face++,

 

至于face++账号信息,apikey…..,本文不做讲述,网上很多.

 

一.设计思想

1.    先想一想,如果让你实现人脸识别,你会怎么做?

寻找图片上的关键点,制作一套算法,分析脸部信息,将得到的数据存入数据库,

 

登录的时候,通过同样的算法,分析数据,和数据库中存入的信息进行比对……

 

工作量好大!!!

 

face++有着它独有的非常优秀的算法,我们可以将我们的图片上传到face++服务器来获取对应的图片数据,剩下的事情就很简单了

2.    四个face++  api简介:

这里只介绍用到的api,

2.1 使用api肯定需要先注册一下信息,获取api_key和api_secret,可以注册试用的进行获取

 java使用face++简单实现人脸识别注册登录

 

 

 

2.2 create

作用: 创建一个faceset,创建一个脸部信息集合,引用官网的描述:

 java使用face++简单实现人脸识别注册登录

 

 

url:

参数:api_key,api_secret,……

 java使用face++简单实现人脸识别注册登录

 

 

返回值: faceset_token, outer_id……,这里写的两个返回值需要记住,是这个脸部信息集合的唯一标识,具体返回值信息如下图:

 java使用face++简单实现人脸识别注册登录

 

 

 

2.3   addface

作用:向脸部信息集合faceset添加一条或多条脸部信息,便于后期搜索

url: https://api-cn.faceplusplus.com/facepp/v3/faceset/addface

参数: 不用说,肯定需要,api_key,api_secret,faceset_token或outer_id(脸部信息唯一标识),还有图片信息,官网截图:

 java使用face++简单实现人脸识别注册登录

 

 

返回值:可以获取插入的结果信息

 java使用face++简单实现人脸识别注册登录

 

.

java使用face++简单实现人脸识别注册登录

 

 

 

2.4 search

作用: 传入一张图片信息到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,(可以再和数据库中对应的信息进行比较)

 java使用face++简单实现人脸识别注册登录

 

 

 

2.4   detect

作用:

传入一张图片信息,获取这张图片的face_token,注意,一张相同图片获取多次的face_token不同

 

url:

参数: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

是否检测并返回人脸关键点。合法值为:

检测。返回 106 个人脸关键点。

1

检测。返回 83 个人脸关键点。

0

不检测

注:本参数默认值为 0

可选

return_attributes

string

是否检测并返回根据人脸特征判断出的年龄、性别、情绪等属性。合法值为:

none

不检测属性

  • gender
  • age
  • smiling
  • headpose
  • facequality
  • blur
  • eyestatus
  • emotion
  • ethnicity
  • beauty
  • mouthstatus
  • eyegaze
  • skinstatus

希望检测并返回的属性。

需要将属性组成一个用逗号分隔的字符串,属性之间的顺序没有要求。

关于各属性的详细描述,参见下文“返回值”说明的 "attributes" 部分。

注:在此参数中的传入参数smiling,对应在返回值的attributes中参数名为smile。在使用时请注意。

注:本参数默认值为 none

可选(仅正式 api key 可以使用)

calculate_all

int

是否检测并返回所有人脸的人脸关键点和人脸属性。如果不使用此功能,则本 api 只会对人脸面积最大的五个人脸分析人脸关键点和人脸属性。合法值为:

1

0

注:本参数默认值为 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

3.    设计分析

  1. 创建调用create api创建faceset,取得faceset_token,对应你的一张用户信息表
  2. 注册时:调用detect api传入用户注册的图片信息,获取face_token,

将face_token存入faceset,(调用addface api存入)

将face_token存入数据库

  1. 登录: 从前端获取用户图片,将图片编码为base64作为参数image_base64调用search api

返回值为在faceset中,和传入图片相似度高的face_token

通过返回的face_token,在数据库中进行查询,实现登陆

二.用到的技术

有了上面的分析,即使使用javaweb也能实现了

 

本案例使用

maven

java的ssm框架

配上druid连接池

 

前端使用了jquery,(不懂前端,通过参考和自己设计写的很low)

 

 

三.实现

3.1前端界面:

实体类:

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: 80px;
        }

        .media {
            width: 534px;
            height: 400px;
            margin: 40px auto 0;
            transform-style: preserve-3d;
            transform: perspective(600px) rotatey(25deg);
        }

        #register {
            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>

<!--创建一个注册的按钮-->
<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 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_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);
        //image/png 表示画成什么格式
       
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>

 

 

 

3.2后端界面

face相关类,通过face++官网查到一个demo,本案例修改demo并封装了自己的信息,打成实现功能

 java使用face++简单实现人脸识别注册登录

 

 

 

获取到的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;
    }
}

 

 

哪里需要改?

 java使用face++简单实现人脸识别注册登录

 

 

四.总结

 

人无我有,人有我优

 

思路很清晰,具体实现很难!!!

 

实现后感觉很简单