人臉識別1:1對比 (三)


  本項目采用了 百度人臉識別 第三方接口,實現了自選圖片人臉識別和 兩張圖片的1:1對比,可返回比對相似度信息。

目前百度向個人開發者提供了免費人臉識別接口,QPS限制為2,企業認證后並發數可增至 5,親測可用。

 

 以下是簡單應用:

一 、所需權限

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

 二、第三方app id app key

  可自行去百度 AI 平台申請注冊

三、工具類

  1. http 工具類
     1 /**
     2  * http 工具類
     3  */
     4 public class HttpUtil {
     5 
     6     public static String post(String requestUrl, String accessToken, String params)
     7             throws Exception {
     8         String contentType = "application/x-www-form-urlencoded";
     9         return HttpUtil.post(requestUrl, accessToken, contentType, params);
    10     }
    11 
    12     public static String post(String requestUrl, String accessToken, String contentType, String params)
    13             throws Exception {
    14         String encoding = "UTF-8";
    15         if (requestUrl.contains("nlp")) {
    16             encoding = "GBK";
    17         }
    18         return HttpUtil.post(requestUrl, accessToken, contentType, params, encoding);
    19     }
    20 
    21     public static String post(String requestUrl, String accessToken, String contentType, String params, String encoding)
    22             throws Exception {
    23         String url = requestUrl + "?access_token=" + accessToken;
    24         return HttpUtil.postGeneralUrl(url, contentType, params, encoding);
    25     }
    26 
    27     public static String postGeneralUrl(String generalUrl, String contentType, String params, String encoding)
    28             throws Exception {
    29         URL url = new URL(generalUrl);
    30         // 打開和URL之間的連接
    31         HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    32         connection.setRequestMethod("POST");
    33         // 設置通用的請求屬性
    34         connection.setRequestProperty("Content-Type", contentType);
    35         connection.setRequestProperty("Connection", "Keep-Alive");
    36         connection.setUseCaches(false);
    37         connection.setDoOutput(true);
    38         connection.setDoInput(true);
    39 
    40         // 得到請求的輸出流對象
    41         DataOutputStream out = new DataOutputStream(connection.getOutputStream());
    42         out.write(params.getBytes(encoding));
    43         out.flush();
    44         out.close();
    45 
    46         // 建立實際的連接
    47         connection.connect();
    48         // 獲取所有響應頭字段
    49         Map<String, List<String>> headers = connection.getHeaderFields();
    50         // 遍歷所有的響應頭字段
    51         for (String key : headers.keySet()) {
    52             System.err.println(key + "--->" + headers.get(key));
    53         }
    54         // 定義 BufferedReader輸入流來讀取URL的響應
    55         BufferedReader in = null;
    56         in = new BufferedReader(
    57                 new InputStreamReader(connection.getInputStream(), encoding));
    58         String result = "";
    59         String getLine;
    60         while ((getLine = in.readLine()) != null) {
    61             result += getLine;
    62         }
    63         in.close();
    64         System.err.println("result:" + result);
    65         return result;
    66     }
    67 }
    View Code

     

  2. Base64 工具類
     1 public class Base64Util {
     2     private static final char last2byte = (char) Integer.parseInt("00000011", 2);
     3     private static final char last4byte = (char) Integer.parseInt("00001111", 2);
     4     private static final char last6byte = (char) Integer.parseInt("00111111", 2);
     5     private static final char lead6byte = (char) Integer.parseInt("11111100", 2);
     6     private static final char lead4byte = (char) Integer.parseInt("11110000", 2);
     7     private static final char lead2byte = (char) Integer.parseInt("11000000", 2);
     8     private static final char[] encodeTable = new char[]
     9             {
    10                     'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
    11                     'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
    12                     'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
    13                     'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
    14             };
    15 
    16     public Base64Util() {
    17     }
    18 
    19     public static String encode(byte[] from) {
    20         StringBuilder to = new StringBuilder((int) ((double) from.length * 1.34D) + 3);
    21         int num = 0;
    22         char currentByte = 0;
    23 
    24         int i;
    25         for (i = 0; i < from.length; ++i) {
    26             for (num %= 8; num < 8; num += 6) {
    27                 switch (num) {
    28                     case 0:
    29                         currentByte = (char) (from[i] & lead6byte);
    30                         currentByte = (char) (currentByte >>> 2);
    31                     case 1:
    32                     case 3:
    33                     case 5:
    34                     default:
    35                         break;
    36                     case 2:
    37                         currentByte = (char) (from[i] & last6byte);
    38                         break;
    39                     case 4:
    40                         currentByte = (char) (from[i] & last4byte);
    41                         currentByte = (char) (currentByte << 2);
    42                         if (i + 1 < from.length) {
    43                             currentByte = (char) (currentByte | (from[i + 1] & lead2byte) >>> 6);
    44                         }
    45                         break;
    46                     case 6:
    47                         currentByte = (char) (from[i] & last2byte);
    48                         currentByte = (char) (currentByte << 4);
    49                         if (i + 1 < from.length) {
    50                             currentByte = (char) (currentByte | (from[i + 1] & lead4byte) >>> 4);
    51                         }
    52                 }
    53 
    54                 to.append(encodeTable[currentByte]);
    55             }
    56         }
    57 
    58         if (to.length() % 4 != 0) {
    59             for (i = 4 - to.length() % 4; i > 0; --i) {
    60                 to.append("=");
    61             }
    62         }
    63 
    64         return to.toString();
    65     }
    66 }
    View Code

     

  以上是實現的Base64的加密算法,使用自帶 Base64.encodeToString(); 方法也可以。

  Base64原理可參考這篇博文:http://www.cnblogs.com/jxust-jiege666/p/8590116.html

四、獲取token

主要代碼:

 1 /**
 2  * 獲取token類
 3  */
 4 public class AuthService {
 5 
 6     /**
 7      * 獲取權限token
 8      * @return 返回示例:
 9      * {
10      * "access_token": "24.460da4889caad24cccdb1fea17221975.2592000.1491995545.282335-1234567",
11      * "expires_in": 2592000
12      * }
13      */
14     public static String getAuth() {
15         // 官網獲取的 API Key 更新為你注冊的
16         String clientId = "百度雲應用的AK";
17         // 官網獲取的 Secret Key 更新為你注冊的
18         String clientSecret = "百度雲應用的SK";
19         return getAuth(clientId, clientSecret);
20     }
21 
22     /**
23      * 獲取API訪問token
24      * 該token有一定的有效期,需要自行管理,當失效時需重新獲取.
25      * @param ak - 百度雲官網獲取的 API Key
26      * @param sk - 百度雲官網獲取的 Securet Key
27      * @return assess_token 示例:
28      * "24.460da4889caad24cccdb1fea17221975.2592000.1491995545.282335-1234567"
29      */
30     public static String getAuth(String ak, String sk) {
31         // 獲取token地址
32         String authHost = "https://aip.baidubce.com/oauth/2.0/token?";
33         String getAccessTokenUrl = authHost
34                 // 1. grant_type為固定參數
35                 + "grant_type=client_credentials"
36                 // 2. 官網獲取的 API Key
37                 + "&client_id=" + ak
38                 // 3. 官網獲取的 Secret Key
39                 + "&client_secret=" + sk;
40         try {
41             URL realUrl = new URL(getAccessTokenUrl);
42             // 打開和URL之間的連接
43             HttpURLConnection connection = (HttpURLConnection) realUrl.openConnection();
44             connection.setRequestMethod("GET");
45             connection.connect();
46             // 獲取所有響應頭字段
47             Map<String, List<String>> map = connection.getHeaderFields();
48             // 遍歷所有的響應頭字段
49             for (String key : map.keySet()) {
50                 System.err.println(key + "--->" + map.get(key));
51             }
52             // 定義 BufferedReader輸入流來讀取URL的響應
53             BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
54             String result = "";
55             String line;
56             while ((line = in.readLine()) != null) {
57                 result += line;
58             }
59             /**
60              * 返回結果示例
61              */
62             System.err.println("result:" + result);
63             JSONObject jsonObject = new JSONObject(result);
64             String access_token = jsonObject.getString("access_token");
65             return access_token;
66         } catch (Exception e) {
67             System.err.printf("獲取token失敗!");
68             e.printStackTrace(System.err);
69         }
70         return null;
71     }
72 
73 }
View Code

 

注意:access_token的有效期為30天,切記需要每30天進行定期更換,或者每次請求都拉取新token;

五、驗證請求

主要代碼:

 1 public class FaceMatch {
 2 
 3     /**
 4      * 重要提示代碼中所需工具類
 5      * FileUtil,Base64Util,HttpUtil,GsonUtils請從
 6      * https://ai.baidu.com/file/658A35ABAB2D404FBF903F64D47C1F72
 7      * https://ai.baidu.com/file/C8D81F3301E24D2892968F09AE1AD6E2
 8      * https://ai.baidu.com/file/544D677F5D4E4F17B4122FBD60DB82B3
 9      * https://ai.baidu.com/file/470B3ACCA3FE43788B5A963BF0B625F3
10      * 下載
11      */
12     public static String match(byte[] mImg1,byte[] mImg2,String accessToken) {
13         // 請求url
14         String url = "https://aip.baidubce.com/rest/2.0/face/v2/match";
15         try {
16 //            String imgStr = Base64.encodeToString(mImg1, 0);
17             String imgStr = Base64Util.encode(mImg1);
18             String imgParam = URLEncoder.encode(imgStr, "UTF-8");
19             String imgStr2 = Base64Util.encode(mImg2);
20             String imgParam2 = URLEncoder.encode(imgStr2, "UTF-8");
21 
22             String param = "images=" + imgParam + "," + imgParam2;
23 
24             // 注意這里僅為了簡化編碼每一次請求都去獲取access_token,線上環境access_token有過期時間, 客戶端可自行緩存,過期后重新獲取。
25             String result = HttpUtil.post(url, accessToken, param);
26             System.out.println(result);
27             return result;
28         } catch (Exception e) {
29             e.printStackTrace();
30         }
31         return null;
32     }
33 }
View Code

 

注意事項:

  • 請求體格式化:Content-Type為application/x-www-form-urlencoded,通過urlencode格式化請求體。
  • Base64編碼:請求的圖片需經過Base64編碼,圖片的base64編碼指將圖片數據編碼成一串字符串,使用該字符串代替圖像地址。您可以首先得到圖片的二進制,然后用Base64格式編碼即可。需要注意的是,圖片的base64編碼是不包含圖片頭的,如data:image/jpg;base64,
  • 圖片格式:現支持PNG、JPG、JPEG、BMP,不支持GIF圖片

URL參數:

參數
access_token 通過API Key和Secret Key獲取的access_token

Header:

參數
Content-Type application/x-www-form-urlencoded

Body中放置請求參數,參數詳情如下:

  請求參數

參數 必選 類型 說明
images string 分別base64編碼后的2張圖片數據,需urlencode,半角逗號分隔,單次請求最大不超過20M
ext_fields string 返回質量信息,取值固定,目前支持qualities(質量檢測)(對所有圖片都會做改處理)
image_liveness string 返回的活體信息,“faceliveness,faceliveness” 表示對比對的兩張圖片都做活體檢測;“,faceliveness” 表示對第一張圖片不做活體檢測、第二張圖做活體檢測;“faceliveness,” 表示對第一張圖片做活體檢測、第二張圖不做活體檢測;
注:需要用於判斷活體的圖片,圖片中的人臉像素面積需要不小於100px*100px,人臉長寬與圖片長寬比例,不小於1/3
types string

請求對比的兩張圖片的類型,示例:“7,13”
7表示生活照:通常為手機、相機拍攝的人像圖片、或從網絡獲取的人像圖片等
11表示身份證芯片照:二代身份證內置芯片中的人像照片
12表示帶水印證件照:一般為帶水印的小圖,如公安網小圖
13表示證件照片:如拍攝的身份證、工卡、護照、學生證等證件圖片,注:需要確保人臉部分不可太小,通常為100px*100px

  說明:兩張請求的圖片請分別進行base64編碼。

返回說明

返回參數

字段 必選 類型 說明
log_id uint64 請求唯一標識碼,隨機數
result_num uint32 返回結果數目,即:result數組中元素個數
result array(object) 結果數據,index和請求圖片index對應。數組元素為每張圖片的匹配得分數組,top n。得分范圍[0,100.0]
+index_i uint32 比對圖片1的index
+index_j uint32 比對圖片2的index
+score double 比對得分,推薦80分作為閾值,80分以上可以判斷為同一人,此分值對應萬分之一誤識率
ext_info array(dict) 對應參數中的ext_fields
+qualities string 質量相關的信息,無特殊需求可以不使用
+faceliveness string 活體檢測分數,單幀活體檢測參考閾值0.393241,超過此分值以上則可認為是活體。注意:活體檢測接口主要用於判斷是否為二次翻拍,需要限制用戶為當場拍照獲取圖片;推薦配合客戶端SDK有動作校驗活體使用

返回示例

//請求兩張圖片
{
    "log_id": 73473737,
    "result_num":1,
    "result": [
        {
            "index_i": 0,
            "index_j": 1,
            "score": 44.3
        }
    ]
}

六、主頁面activity

主要代碼:

  1 import android.content.ContentResolver;
  2 import android.content.Intent;
  3 import android.graphics.Bitmap;
  4 import android.graphics.BitmapFactory;
  5 import android.net.Uri;
  6 import android.os.Bundle;
  7 import android.os.Handler;
  8 import android.os.Message;
  9 import android.support.v7.app.AlertDialog;
 10 import android.support.v7.app.AppCompatActivity;
 11 import android.text.TextUtils;
 12 import android.util.Log;
 13 import android.view.View;
 14 import android.widget.Button;
 15 import android.widget.ImageView;
 16 import android.widget.TextView;
 17 import android.widget.Toast;
 18 
 19 import com.example.lifen.baidufacecomparedemo.R;
 20 import com.example.lifen.baidufacecomparedemo.utils.AuthService;
 21 import com.example.lifen.baidufacecomparedemo.utils.FaceMatch;
 22 
 23 import java.io.ByteArrayOutputStream;
 24 import java.io.FileNotFoundException;
 25 
 26 /**
 27  * 人臉對比 1:1
 28  *
 29  * @author LiFen
 30  */
 31 public class MainActivity extends AppCompatActivity {
 32     private static final String TAG = "MainActivity";
 33     private static final int REQUEST_CODE1 = 11;
 34     private static final int REQUEST_CODE2 = 12;
 35     ImageView mImageView1;
 36     ImageView mImageView2;
 37     Button mCompareBtn;
 38     TextView mResultText;
 39     private byte[] mImg1;
 40     private byte[] mImg2;
 41     String key = "";//api_key
 42     String secret ="";//api_secret
 43     private final static int i = 100;
 44 
 45     private Handler handler = new Handler(){
 46         @Override
 47         public void handleMessage(Message msg) {
 48             if(msg.what == i){
 49                 mResultText.setText((String)msg.obj);
 50             }
 51         }
 52     };
 53     @Override
 54     protected void onCreate(Bundle savedInstanceState) {
 55         super.onCreate(savedInstanceState);
 56         setContentView(R.layout.activity_main);
 57 
 58         mImageView1 = (ImageView) findViewById(R.id.img1);
 59         mImageView2 = (ImageView) findViewById(R.id.img2);
 60         mCompareBtn = (Button) findViewById(R.id.compareBtn);
 61         mResultText = (TextView) findViewById(R.id.resultBtn);
 62         if(TextUtils.isEmpty(key) || TextUtils.isEmpty(secret)){
 63             AlertDialog.Builder builder = new AlertDialog.Builder(this);
 64             builder.setMessage("please enter key and secret");
 65             builder.setTitle("");
 66             builder.show();
 67             return;
 68         }
 69         mImageView1.setOnClickListener(new View.OnClickListener() {
 70             @Override
 71             public void onClick(View v) {
 72                 startAlbumActivity(REQUEST_CODE1);
 73             }
 74         });
 75         mImageView2.setOnClickListener(new View.OnClickListener() {
 76             @Override
 77             public void onClick(View v) {
 78                 startAlbumActivity(REQUEST_CODE2);
 79             }
 80         });
 81         mCompareBtn.setOnClickListener(new View.OnClickListener() {
 82             @Override
 83             public void onClick(View v) {
 84                 startCompare();
 85             }
 86         });
 87     }
 88 
 89     private void startCompare() {
 90         if ("".equals(mImg1) || mImg1 == null || "".equals(mImg2) || mImg2 == null) {
 91             Toast.makeText(this, "請選擇圖片再比對", Toast.LENGTH_SHORT).show();
 92             return;
 93         }
 94         mResultText.setText("比對中...");
 95         new Thread(new Runnable() {
 96             @Override
 97             public void run() {
 98                 try{
 99                     String accessToken = AuthService.getAuth(key,secret);
100                     Log.i(TAG, "run: " +accessToken);
101                     Log.i(TAG, "run: " + mImg1.toString());
102                     Log.i(TAG, "run: " + mImg2.toString());
103                     String result = FaceMatch.match(mImg1,mImg2,accessToken);
104                     Message msg = new Message();
105                     msg.what = i;
106                     msg.obj = result;
107                     handler.sendMessage(msg);
108                 }catch (Exception e){
109                     Log.i(TAG, "startCompare: " + e.toString());
110                 }
111             }
112         }).start();
113     }
114 
115     private void startAlbumActivity(int requestCode) {
116         Intent intent = new Intent();
117         intent.setType("image/*");
118         intent.setAction(Intent.ACTION_GET_CONTENT);
119         startActivityForResult(intent, requestCode);
120     }
121 
122     @Override
123     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
124         if (data == null)
125             return;
126         Uri uri = data.getData();
127         Log.e("uri", uri.toString());
128         ContentResolver cr = this.getContentResolver();
129         Bitmap bitmap = null;
130         try {
131             bitmap = BitmapFactory.decodeStream(cr.openInputStream(uri));
132                 /* 將Bitmap設定到ImageView */
133         } catch (FileNotFoundException e) {
134             Log.e("Exception", e.getMessage(), e);
135         }
136         if (resultCode == RESULT_OK && requestCode == REQUEST_CODE1) {
137             mImageView1.setImageBitmap(bitmap);
138             ByteArrayOutputStream baos = new ByteArrayOutputStream();
139             bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
140             byte[] datas = baos.toByteArray();
141             mImg1 = datas;
142         } else if (resultCode == RESULT_OK && requestCode == REQUEST_CODE2) {
143             mImageView2.setImageBitmap(bitmap);
144             ByteArrayOutputStream baos = new ByteArrayOutputStream();
145             bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
146             byte[] datas = baos.toByteArray();
147             mImg2 = datas;
148         }
149         super.onActivityResult(requestCode, resultCode, data);
150     }
151 }
View Code

注意:key自行注冊獲取

七、布局文件

頁面效果:

 

代碼如下:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <ScrollView
 3     xmlns:android="http://schemas.android.com/apk/res/android"
 4     xmlns:tools="http://schemas.android.com/tools"
 5     android:layout_width="match_parent"
 6     android:layout_height="match_parent"
 7     android:paddingBottom="@dimen/activity_vertical_margin"
 8     android:paddingLeft="@dimen/activity_horizontal_margin"
 9     android:paddingRight="@dimen/activity_horizontal_margin"
10     android:paddingTop="@dimen/activity_vertical_margin"
11     tools:context="com.example.lifen.baidufacecomparedemo.activity.MainActivity">
12 
13     <LinearLayout
14         android:layout_width="match_parent"
15         android:layout_height="match_parent"
16         android:orientation="vertical">
17 
18         <LinearLayout
19             android:layout_width="match_parent"
20             android:layout_height="wrap_content"
21             android:orientation="horizontal">
22 
23             <ImageView
24                 android:id="@+id/img1"
25                 android:layout_width="0dp"
26                 android:layout_height="180dp"
27                 android:layout_weight="1"
28                 android:scaleType="centerCrop"
29                 android:src="@drawable/head"/>
30 
31             <TextView
32                 android:layout_width="wrap_content"
33                 android:layout_height="match_parent"
34                 android:gravity="center"
35                 android:text="VS"
36                 android:textColor="@android:color/black"
37                 android:textSize="20dp"/>
38 
39             <ImageView
40                 android:id="@+id/img2"
41                 android:layout_width="0dp"
42                 android:layout_height="180dp"
43                 android:layout_weight="1"
44                 android:scaleType="centerCrop"
45                 android:src="@drawable/head"/>
46 
47         </LinearLayout>
48 
49         <Button
50             android:id="@+id/compareBtn"
51             android:layout_width="match_parent"
52             android:layout_height="wrap_content"
53             android:layout_marginTop="@dimen/activity_horizontal_margin"
54             android:text="比對"/>
55 
56         <TextView
57             android:id="@+id/resultBtn"
58             android:layout_width="match_parent"
59             android:layout_height="wrap_content"
60             android:layout_marginTop="@dimen/activity_horizontal_margin"
61             android:background="#eeeeee"
62             android:padding="6dp"/>
63 
64     </LinearLayout>
65 </ScrollView>
View Code

 

項目源碼地址:https://download.csdn.net/download/qq_36726507/10292513


免責聲明!

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



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