java使用face++簡單實現人臉識別注冊登錄


 

java使用face++簡單實現人臉識別注冊登錄

前言

人臉識別,好高大上!!!

理解之后很簡單。

 

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

 

至於face++賬號信息,apikey…..,本文不做講述,網上很多.

 

一.設計思想

1.    先想一想,如果讓你實現人臉識別,你會怎么做?

尋找圖片上的關鍵點,制作一套算法,分析臉部信息,將得到的數據存入數據庫,

 

登錄的時候,通過同樣的算法,分析數據,和數據庫中存入的信息進行比對……

 

工作量好大!!!

 

face++有着它獨有的非常優秀的算法,我們可以將我們的圖片上傳到face++服務器來獲取對應的圖片數據,剩下的事情就很簡單了

2.    四個face++  api簡介:

這里只介紹用到的api,

2.1 使用api肯定需要先注冊一下信息,獲取api_key和api_secret,可以注冊試用的進行獲取

 

 

 

 

2.2 create

作用: 創建一個FaceSet,創建一個臉部信息集合,引用官網的描述:

 

 

 

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

參數:api_key,api_secret,……

 

 

 

返回值: faceset_token, outer_id……,這里寫的兩個返回值需要記住,是這個臉部信息集合的唯一標識,具體返回值信息如下圖:

 

 

 

 

2.3   addFace

作用:向臉部信息集合faceSet添加一條或多條臉部信息,便於后期搜索

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

參數: 不用說,肯定需要,api_key,api_secret,faceSet_token或outer_id(臉部信息唯一標識),還有圖片信息,官網截圖:

 

 

 

返回值:可以獲取插入的結果信息

 

 

.

 

 

 

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,(可以再和數據庫中對應的信息進行比較)

 

 

 

 

2.4   Detect

作用:

傳入一張圖片信息,獲取這張圖片的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

是否檢測並返回人臉關鍵點。合法值為:

檢測。返回 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並封裝了自己的信息,打成實現功能

 

 

 

 

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

 

 

哪里需要改?

 

 

 

四.總結

 

人無我有,人有我優

 

思路很清晰,具體實現很難!!!

 

實現后感覺很簡單


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM