本项目采用了 百度人脸识别 第三方接口,实现了自选图片人脸识别和 两张图片的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 }
- View Code
- 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原理可参考这篇博文:
四、获取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” |
说明:两张请求的图片请分别进行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