写在前面
最近项目需求需要输入用户真实姓名 身份证号 再去调用人脸识别 效验是否是本人 所以就首先关注了Face++(旷视)的人脸识别,听说是 正确率很高.....所以就集成了.
1.首先去官网去创建应用(其实听说创建应用是比较麻烦的)
创建好应用以后需要上传你项目的包名
2.下载Demo Demo中会有一个扫描身份证的SDK和人脸识别的SDK 我这个需求呢是人脸识别 所以只集成人脸识别(如下图)
您可以按照您项目的需求集成不同的SDK(这里只集成人脸识别SDK)
3.上面的俩个文件夹都有扫描身份证和人脸识别的Demo
建议您,先把申请应用完以后的AppKey 和secret 配置到Face++的Demo中 跑完这个流程
4.开始我们的步骤
5.Face++给的Demo中 就会有 我们今天需要集成的SDK(如下图)
点击sdk文件夹
6.我们需要把meglive_still.aar 导入到 lib文件夹中
并在app下 build.gradle文件下 dependencies中写入
implementation(name: 'meglive_still', ext: 'aar')
在repositories中写入
flatDir { dirs 'libs' }
7.上代码
(1)需要调用人脸识别的Activity
private static final int CAMERA_PERMISSION_REQUEST_CODE = 100;
private static final int EXTERNAL_STORAGE_REQ_WRITE_EXTERNAL_STORAGE_CODE = 101;
private ProgressDialog mProgressDialog;
private MegLiveManager megLiveManager;
private static final String GET_BIZTOKEN_URL = "https://api.megvii.com/faceid/v3/sdk/get_biz_token"; //Face++获取BizToken的url
private static final String VERIFY_URL = "https://api.megvii.com/faceid/v3/sdk/verify"; //Face++人脸效验的url
private static final String API_KEY = "您的AppKey";
private static final String SECRET = "您的Secret";
private String sign = "";
private static final String SIGN_VERSION = "hmac_sha1";
private byte[] imageRef;//底库图片
private int buttonType;
private void init() {megLiveManager=MegLiveManager.getInstance();mProgressDialog = new ProgressDialog(this);mProgressDialog.setCancelable(false);long currtTime = System.currentTimeMillis() / 1000;long expireTime = (System.currentTimeMillis() + 60 * 60 * 100) / 1000;sign = GenerateSign.appSign(API_KEY, SECRET, currtTime, expireTime);requestCameraPerm();}
//获取BizToken的请求
private void getBizToken(String livenessType, int comparisonType, String idcardName, String idcardNum, String uuid, byte[] image) {mProgressDialog.show();HttpRequestManager.getInstance().getBizToken(this, GET_BIZTOKEN_URL, sign, SIGN_VERSION, livenessType, comparisonType, idcardName, idcardNum, uuid, image, new HttpRequestCallBack() {@Overridepublic void onSuccess(String responseBody) {try {JSONObject json = new JSONObject(responseBody);String bizToken = json.optString("biz_token");megLiveManager.preDetect(FaceIdActivity.this, bizToken,FaceIdActivity.this);} catch (JSONException e) {e.printStackTrace();}}@Overridepublic void onFailure(int statusCode, byte[] responseBody) {}});}
@Override
public void onDetectFinish(String token, int errorCode, String errorMessage, String data) {if (errorCode == 1000) {verify(token, data.getBytes());}
}
@Override
public void onPreStart() {showDialogDismiss();
}@Override
public void onPreFinish(String token, int errorCode, String errorMessage) {progressDialogDismiss();if (errorCode == 1000) {megLiveManager.startDetect(this);}
}//人脸识别的url
private void verify(String token, byte[] data) {showDialogDismiss();HttpRequestManager.getInstance().verify(this, VERIFY_URL, sign, SIGN_VERSION, token, data, new HttpRequestCallBack() {@Overridepublic void onSuccess(String responseBody) {Log.w("result", responseBody);progressDialogDismiss();gotoActivity(mContext,FaceIdVerifySuccessActivity.class,null);}@Overridepublic void onFailure(int statusCode, byte[] responseBody) {Log.w("result", new String(responseBody));progressDialogDismiss();gotoActivity(mContext,FaceIdVerifyErrorActivity.class,null);}});
}private void progressDialogDismiss() {runOnUiThread(new Runnable() {@Overridepublic void run() {if (mProgressDialog != null) {mProgressDialog.dismiss();}}});
}private void showDialogDismiss() {runOnUiThread(new Runnable() {@Overridepublic void run() {if (mProgressDialog != null) {mProgressDialog.show();}}});}private void requestCameraPerm() {if (android.os.Build.VERSION.SDK_INT >= M) {if (checkSelfPermission(Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED) {//进行权限请求requestPermissions(new String[]{Manifest.permission.CAMERA},CAMERA_PERMISSION_REQUEST_CODE);} else {requestWriteExternalPerm();}} else {beginDetect();}
}private void requestWriteExternalPerm() {if (android.os.Build.VERSION.SDK_INT >= M) {if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {//进行权限请求requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},EXTERNAL_STORAGE_REQ_WRITE_EXTERNAL_STORAGE_CODE);} else {beginDetect();}} else {beginDetect();
}
}private void beginDetect() {getBizToken("meglive", 1, "您的真实姓名", "您的身份证号", UUID.randomUUID().toString(), null);
}@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {if (grantResults.length < 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {//拒绝了权限申请} else {requestWriteExternalPerm();}} else if (requestCode == EXTERNAL_STORAGE_REQ_WRITE_EXTERNAL_STORAGE_CODE) {if (grantResults.length < 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {//拒绝了权限申请} else {beginDetect();}}
}
(2) CV代码出来的错误
您的人脸识别的Activity需要实现 implements DetectCallback, PreCallback
(3)GenerateSign.java
public class GenerateSign {public static String appSign(String apiKey, String secret, long currtTime,long expireTime) {try {int rdm = Math.abs(new Random().nextInt());String plainText = String.format("a=%s&b=%d&c=%d&d=%d", apiKey, expireTime, currtTime,rdm);byte[] hmacDigest = HmacSha1(plainText, secret);byte[] signContent = new byte[hmacDigest.length + plainText.getBytes().length];System.arraycopy(hmacDigest, 0, signContent, 0, hmacDigest.length);System.arraycopy(plainText.getBytes(), 0, signContent, hmacDigest.length,plainText.getBytes().length);return Base64Encode(signContent).replaceAll("[\\s*\t\n\r]", "");} catch (Exception e) {e.printStackTrace();}return null;}/*** 生成base64编码** @param binaryData* @return*/public static String Base64Encode(byte[] binaryData) {String encodedstr = Base64.encodeToString(binaryData,Base64.DEFAULT);return encodedstr;}/*** 生成hmacsha1签名** @param binaryData* @param key* @return* @throws Exception*/public static byte[] HmacSha1(byte[] binaryData, String key) throws Exception {Mac mac = Mac.getInstance("HmacSHA1");SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "HmacSHA1");mac.init(secretKey);byte[] HmacSha1Digest = mac.doFinal(binaryData);return HmacSha1Digest;}/*** 生成hmacsha1签名** @param plainText* @param key* @return* @throws Exception*/public static byte[] HmacSha1(String plainText, String key) throws Exception {return HmacSha1(plainText.getBytes(), key);}
}
(4)HttpRequestCallBack
public interface HttpRequestCallBack {void onSuccess(String responseBody);void onFailure(int statusCode, byte[] responseBody);
}
(5)HttpRequestManager.java
public class HttpRequestManager {public final static int TIMEOUT = 10000;private static HttpRequestManager instance;public static HttpRequestManager getInstance() {if (instance == null) {instance = new HttpRequestManager();}return instance;}public void verify(Context context,String url,String sign,String signVersion,String bizToken,byte[] megLiveData,HttpRequestCallBack listener){MultipartEntity entity = new MultipartEntity();entity.addStringPart("sign",sign);entity.addStringPart("sign_version",signVersion);entity.addStringPart("biz_token",bizToken);entity.addBinaryPart("meglive_data",megLiveData);sendMultipartRequest(context,url,entity,new HashMap<String, String>(),listener);}public void getBizToken(Context context,String url,String sign,String signVersoin,String livenessType,int comparisonType,String idcardName,String idcardNum,String uuid,byte[] image_ref1,HttpRequestCallBack listener){MultipartEntity entity = new MultipartEntity();entity.addStringPart("sign",sign);entity.addStringPart("sign_version", signVersoin);entity.addStringPart("liveness_type", livenessType);entity.addStringPart("comparison_type", ""+comparisonType);if (comparisonType==1){entity.addStringPart("idcard_name", idcardName);entity.addStringPart("idcard_number", idcardNum);}else if (comparisonType==0){entity.addStringPart("uuid", uuid);entity.addBinaryPart("image_ref1", image_ref1);}sendMultipartRequest(context,url,entity,new HashMap<String, String>(),listener);}private void sendPostRequest(Context context, String url, final Map<String, String> params, final Map<String, String> header, final HttpRequestCallBack listener) {StringRequest request = new StringRequest(Request.Method.POST, url, new Response.Listener<String>() {@Overridepublic void onResponse(String response) {if (listener != null)listener.onSuccess(response);}}, new Response.ErrorListener() {@Overridepublic void onErrorResponse(VolleyError error) {if (error == null) {if (listener != null)listener.onFailure(-1, "timeout exception".getBytes());} else if (error.networkResponse == null) {if (listener != null)listener.onFailure(-1, "timeout exception".getBytes());} else {if (listener != null)listener.onFailure(error.networkResponse.statusCode, error.networkResponse.data);}}}) {@Overrideprotected Map<String, String> getParams() throws AuthFailureError {return params;}@Overridepublic Map<String, String> getHeaders() throws AuthFailureError {return header;}};VolleyHelper.getInstance(context).addToRequestQueue(request);}private void sendGetRequest(Context context, String url, final Map<String, String> header, final HttpRequestCallBack listener) {StringRequest request = new StringRequest(url, new Response.Listener<String>() {@Overridepublic void onResponse(String response) {listener.onSuccess(response);}}, new Response.ErrorListener() {@Overridepublic void onErrorResponse(VolleyError error) {if (error == null) {listener.onFailure(-1, "timeout exception".getBytes());} else if (error.networkResponse == null) {listener.onFailure(-1, "timeout exception".getBytes());} else {listener.onFailure(error.networkResponse.statusCode, error.networkResponse.data);}}}) {@Overridepublic Map<String, String> getHeaders() throws AuthFailureError {return header;}};VolleyHelper.getInstance(context).addToRequestQueue(request);}private void sendMultipartRequest(Context context, String url, MultipartEntity mult, final Map<String, String> header, final HttpRequestCallBack listener) {MultipartRequest multipartRequest = new MultipartRequest(url, new Response.Listener<String>() {@Overridepublic void onResponse(String response) {listener.onSuccess(response);}}, new Response.ErrorListener() {@Overridepublic void onErrorResponse(VolleyError error) {if (error == null) {listener.onFailure(-1, "timeout exception".getBytes());} else if (error.networkResponse == null) {listener.onFailure(-1, "timeout exception".getBytes());} else {listener.onFailure(error.networkResponse.statusCode, error.networkResponse.data);}}}) {@Overridepublic Map<String, String> getHeaders() throws AuthFailureError {return header;}};// 通过MultipartEntity来设置参数multipartRequest.setmMultiPartEntity(mult);VolleyHelper.getInstance(context).addToRequestQueue(multipartRequest);}}
(6)MultipartEntity
public class MultipartEntity implements HttpEntity {private final static char[] MULTIPART_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();/*** 换行符*/private final String NEW_LINE_STR = "\r\n";private final String CONTENT_TYPE = "Content-Type: ";private final String CONTENT_DISPOSITION = "Content-Disposition: ";/*** 文本参数和字符集*/private final String TYPE_TEXT_CHARSET = "text/plain; charset=UTF-8";/*** 字节流参数*/private final String TYPE_OCTET_STREAM = "application/octet-stream";/*** 二进制参数*/private final byte[] BINARY_ENCODING = "Content-Transfer-Encoding: binary\r\n\r\n".getBytes();/*** 文本参数*/private final byte[] BIT_ENCODING = "Content-Transfer-Encoding: 8bit\r\n\r\n".getBytes();/*** 分隔符*/private String mBoundary = null;/*** 输出流*/ByteArrayOutputStream mOutputStream = new ByteArrayOutputStream();public MultipartEntity() {this.mBoundary = generateBoundary();}/*** 生成分隔符** @return*/private final String generateBoundary() {final StringBuffer buf = new StringBuffer();final Random rand = new Random();for (int i = 0; i < 30; i++) {buf.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);}return buf.toString();}/*** 参数开头的分隔符** @throws IOException*/private void writeFirstBoundary() throws IOException {mOutputStream.write(("--" + mBoundary + "\r\n").getBytes());}/*** 添加文本参数** @param paramName* @param value*/public void addStringPart(final String paramName, final String value) {writeToOutputStream(paramName, value.getBytes(), TYPE_TEXT_CHARSET, BIT_ENCODING, "");}/*** 将数据写入到输出流中** @param paramName* @param rawData* @param type* @param encodingBytes* @param fileName*/private void writeToOutputStream(String paramName, byte[] rawData, String type,byte[] encodingBytes,String fileName) {try {writeFirstBoundary();mOutputStream.write((CONTENT_TYPE + type + NEW_LINE_STR).getBytes());mOutputStream.write(getContentDispositionBytes(paramName, fileName));mOutputStream.write(encodingBytes);mOutputStream.write(rawData);mOutputStream.write(NEW_LINE_STR.getBytes());} catch (final IOException e) {e.printStackTrace();}}/*** 添加二进制参数, 例如Bitmap的字节流参数** @param paramName* @param rawData*/public void addBinaryPart(String paramName, final byte[] rawData) {writeToOutputStream(paramName, rawData, TYPE_OCTET_STREAM, BINARY_ENCODING,paramName);}/*** 添加文件参数,可以实现文件上传功能** @param key* @param file*/public void addFilePart(final String key, final File file) {InputStream fin = null;try {fin = new FileInputStream(file);writeFirstBoundary();final String type = CONTENT_TYPE + TYPE_OCTET_STREAM + NEW_LINE_STR;mOutputStream.write(getContentDispositionBytes(key, file.getName()));mOutputStream.write(type.getBytes());mOutputStream.write(BINARY_ENCODING);final byte[] tmp = new byte[4096];int len = 0;while ((len = fin.read(tmp)) != -1) {mOutputStream.write(tmp, 0, len);}mOutputStream.flush();} catch (final IOException e) {e.printStackTrace();} finally {closeSilently(fin);}}private void closeSilently(Closeable closeable) {try {if (closeable != null) {closeable.close();}} catch (final IOException e) {e.printStackTrace();}}private byte[] getContentDispositionBytes(String paramName, String fileName) {StringBuilder stringBuilder = new StringBuilder();stringBuilder.append(CONTENT_DISPOSITION + "form-data; name=\"" + paramName + "\"");// 文本参数没有filename参数,设置为空即可 if (fileName != null && !"".equals(fileName)) {stringBuilder.append("; filename=\""+ fileName + "\"");}return stringBuilder.append(NEW_LINE_STR).toString().getBytes();}@Overridepublic long getContentLength() {return mOutputStream.toByteArray().length;}@Overridepublic Header getContentType() {return new BasicHeader("Content-Type", "multipart/form-data; boundary=" + mBoundary);}@Overridepublic boolean isChunked() {return false;}@Overridepublic boolean isRepeatable() {return false;}@Overridepublic boolean isStreaming() {return false;}@Overridepublic void writeTo(final OutputStream outstream) throws IOException {// 参数最末尾的结束符 final String endString = "--" + mBoundary + "--\r\n";// 写入结束符 mOutputStream.write(endString.getBytes());// outstream.write(mOutputStream.toByteArray());}@Overridepublic Header getContentEncoding() {return null;}@Overridepublic void consumeContent() throws IOException,UnsupportedOperationException {if (isStreaming()) {throw new UnsupportedOperationException("Streaming entity does not implement #consumeContent()");}}@Overridepublic InputStream getContent() {return new ByteArrayInputStream(mOutputStream.toByteArray());}
}
(7)MultipartRequest
public class MultipartRequest extends Request<String> {private MultipartEntity mMultiPartEntity;private Map<String, String> mHeaders = new HashMap<String, String>();private final Response.Listener<String> mListener;/*** Creates a new request with the given url.** @param url URL to fetch the string at* @param listener Listener to receive the String response*/public MultipartRequest(String url, Response.Listener<String> listener) {this(url, listener, null);}/*** Creates a new POST request.** @param url URL to fetch the string at* @param listener Listener to receive the String response* @param errorListener Error listener, or null to ignore errors*/public MultipartRequest(String url, Response.Listener<String> listener, Response.ErrorListener errorListener) {super(Method.POST, url, errorListener);mListener = listener;}/*** @return*/public MultipartEntity getMultiPartEntity() {if (mMultiPartEntity == null) {mMultiPartEntity = new MultipartEntity();}return mMultiPartEntity;}public void setmMultiPartEntity(MultipartEntity mMultiPartEntity) {this.mMultiPartEntity = mMultiPartEntity;}@Overridepublic String getBodyContentType() {return mMultiPartEntity.getContentType().getValue();}public void addHeader(String key, String value) {mHeaders.put(key, value);}@Overridepublic Map<String, String> getHeaders() throws AuthFailureError {return mHeaders;}@Overridepublic byte[] getBody() {ByteArrayOutputStream bos = new ByteArrayOutputStream();try {// multipart body mMultiPartEntity.writeTo(bos);} catch (IOException e) {Log.e("", "IOException writing to ByteArrayOutputStream");}return bos.toByteArray();}@Overrideprotected Response<String> parseNetworkResponse(NetworkResponse response) {String parsed = "";try {parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));} catch (UnsupportedEncodingException e) {parsed = new String(response.data);}return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));}@Overrideprotected void deliverResponse(String response) {if (mListener != null) {mListener.onResponse(response);}}}
(8)VolleyHelper
public class VolleyHelper {private static VolleyHelper mInstance;private RequestQueue mRequestQueue;private static Context mCtx;private VolleyHelper(Context context) {mCtx = context;mRequestQueue = getRequestQueue();}public static synchronized VolleyHelper getInstance(Context context) {if (mInstance == null) {mInstance = new VolleyHelper(context);}return mInstance;}public RequestQueue getRequestQueue() { if (mRequestQueue == null) {// getApplicationContext() is key, it keeps you from leaking the// Activity or BroadcastReceiver if someone passes one in.if (mCtx==null){return null;}mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());}return mRequestQueue; }public <T> boolean addToRequestQueue(Request<T> req) {if (getRequestQueue()==null){return false;}req.setRetryPolicy(new DefaultRetryPolicy(10000, 0, 1.0f));getRequestQueue().add(req);return true;}public void clearRequestQueue(){if (getRequestQueue()!=null){getRequestQueue().cancelAll(mCtx.getApplicationContext());}}}
8.您可能遇到的问题
(1)
现象:
Face++ 底层使用的是Volley网络请求 我使用的时候第一个错就是 Face++SDK中关于Volley的包 全部报错
解决: 在我们的项目中 重新导入 Volley 的依赖
implementation 'com.mcxiaoke.volley:library:1.0.19'
(2)Face++SDK并没有把相关识别的方法弄成API 在CV的时候务必CV完整
(3)Face++集成从看Demo到看文档 集成起来也是蛮顺利的 祝快快集成
(4)我看了关于Face++的博客 本质是 我们用户输入真实姓名和身份证号以后 Face++会调取身份证和我们的人脸进行比对
Face++ 集成就到这里了,希望能给您带来帮助。