本項目采用了 百度人臉識別 第三方接口,實現了自選圖片人臉識別和 兩張圖片的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 平台申請注冊
三、工具類
- 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 }
- 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 }
以上是實現的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 }
注意:
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 }
注意事項:
- 請求體格式化: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” |
說明:兩張請求的圖片請分別進行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 }
注意: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>
項目源碼地址:https://download.csdn.net/download/qq_36726507/10292513