Android 人脸识别签到(二)
学习完百度人脸API的调用,现在即可开发自己的人脸识别签到系统,下面作者先贴上部分功能源码来给大家参考和学习
(一)百度人脸库的人脸验证
1° 获取待识别的照片
既然是人脸认证 那么当然首先得向百度人脸库添加你的人脸
然后再把你需要进行人脸识别的照片与百度人脸库的人脸进行校对,如果校对成功,即签到打卡成功
关于获取带人脸识别的照片,作者采取了两种方式获取(即时拍照、从相册导入)
即时拍照:
Camera.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build()); builder.detectFileUriExposure(); //Android7.0拍照必加 且需要在方法类前加@SuppressLint("NewApi") File outputImage = new File(Environment.getExternalStorageDirectory() + File.separator + "face.jpg"); //临时照片存储地 try { if (outputImage.exists()) { outputImage.delete(); //若临时存储地已有照片则delete } outputImage.createNewFile(); //临时存储地创建新文件 } catch (IOException e) { e.printStackTrace(); } imageUri = Uri.fromFile(outputImage); //获取临时存储地Uri ImagePath = outputImage.getAbsolutePath(); //得到绝对路径 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); //跳转相机 intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); //相片输出路径 startActivityForResult(intent, CAMERA); //返回照片路径 } });
从相册直接获取:
getImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent in = new Intent(Intent.ACTION_PICK); //选择数据
in.setType("image/*"); //选择的数据为图片
startActivityForResult(in, Photo_ALBUM);
}
});
获取照片之后需要把照片显示到手机APP界面上,给用户做一个预览 也就是方法startActivityForResult的作用
关于拍照旋转角度问题解释(https://www.jianshu.com/p/6179f16907dc)
protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); // 相册选择图片 if (requestCode == Photo_ALBUM) { if (data != null) { Uri uri = data.getData(); //获取图片uri Cursor cursor = getContentResolver().query(uri, null, null, null, null); cursor.moveToNext(); ImagePath = cursor.getString(cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA)); //获得图片的绝对路径 cursor.close(); resizePhoto(); //将图片变成缩小的bitmap 方法后贴 int degree = getPicRotate(ImagePath); //获取拍照图片的旋转角度 可以查一下我给出的地址 getPicRotate方法在下面 Matrix m = new Matrix(); //对图形处理 m.setRotate(degree); //旋转 lastp = Bitmap.createBitmap(myBitmapImage, 0, 0, myBitmapImage.getWidth(), myBitmapImage.getHeight(), m, true); //获取缩小且旋转好的图片 myPhoto.setImageBitmap(lastp); //显示图片 Log.i("图片路径", ImagePath); } } else if (requestCode == CAMERA) { try { resizePhoto(); int degree = getPicRotate(ImagePath); //获取旋转角度 Matrix m = new Matrix(); //对图形处理的类 m.setRotate(degree); //旋转 lastp = Bitmap.createBitmap(myBitmapImage, 0, 0, myBitmapImage.getWidth(), myBitmapImage.getHeight(), m, true); myPhoto.setImageBitmap(lastp); //显示图片 } catch (Exception e) { e.printStackTrace(); } } }
获取旋转角度
1 public int getPicRotate(String path) { //旋转图片 2 int degree = 0; 3 try { 4 ExifInterface exifInterface = new ExifInterface(path); 5 int orientation = exifInterface.getAttributeInt( 6 ExifInterface.TAG_ORIENTATION, 7 ExifInterface.ORIENTATION_NORMAL); //命名空间 命名空间所属属性 8 switch (orientation) { 9 case ExifInterface.ORIENTATION_ROTATE_90: 10 degree = 90; 11 break; 12 case ExifInterface.ORIENTATION_ROTATE_180: 13 degree = 180; 14 break; 15 case ExifInterface.ORIENTATION_ROTATE_270: 16 degree = 270; 17 break; 18 } 19 } catch (IOException e) { 20 e.printStackTrace(); 21 } 22 return degree; 23 }
2° 上传待查照片到百度开发平台,与百度人脸库里已存的照片进行匹配
既然已经获取了待查照片,那么现在即把照片与人脸库的已有照片进行匹配,若百度人脸库存在此人,那么就匹配成功(即签到成功)
1.当点击识别按钮后,将待查照片上传,并创建一个线程用于处理放回的结果
detect.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { res = null; tip.setVisibility(View.VISIBLE); //识别中提示 在Android界面显示 if (myBitmapImage == null) { Toast.makeText(MainActivity.this, "请添加图片!!!", Toast.LENGTH_LONG).show(); tip.setVisibility(View.GONE); //隐藏 识别中提示 } else if(Class.getText().toString().trim().equals("请输入查课节次")==true){ Toast.makeText(MainActivity.this, "请输入查课节次!!!", Toast.LENGTH_LONG).show(); tip.setVisibility(View.GONE); } else { //对于上传的图片进行处理 int degree = getPicRotate(ImagePath); Matrix m = new Matrix(); //对图形处理 m.setRotate(degree); //旋转 bitmapSmall = Bitmap.createBitmap(myBitmapImage, 0, 0, myBitmapImage.getWidth(), myBitmapImage.getHeight(), m, true); ByteArrayOutputStream stream = new ByteArrayOutputStream(); //图片转数据流 bitmapSmall.compress(Bitmap.CompressFormat.JPEG, 100, stream); //图片压缩格式,压缩率,文件输出流对象 final byte[] arrays = stream.toByteArray(); //转成二进制数组 final String pic = android.util.Base64.encodeToString(arrays, Base64.DEFAULT); //获取图片 Base64格式的String new Thread(new Runnable() { @Override public void run() { HashMap<String, String> options = new HashMap<>(); options.put("quality_control", "NORMAL"); //质量控制 options.put("liveness_control", "LOW"); //活体控制 options.put("max_user_num", "3"); //返回结果的最大个数 String groupId = "ruan1,ruan2"; //查询的人脸组 String imageType = "BASE64"; //上传的图片格式 AipFace client = new AipFace("15119543", "lwxkzZOqjm4bcN2DmHoe8giy", "skYUhhZAfsUCFsBud7VQPIdWPvMt7tOM"); client.setConnectionTimeoutInMillis(2000); //超时设置 client.setSocketTimeoutInMillis(6000); res = client.search(pic, imageType, groupId, options); //获取查询结果集 try { Message message = Message.obtain(); message.what = 1; message.obj = res; handler.sendMessage(message); } catch (Exception e) { e.printStackTrace(); Message message = Message.obtain(); message.what = 2; handler.sendMessage(message); } } }).start(); } } }); }
2.当上传完图片后,即可获取查询结果集,那么将结果集交由线程进行处理
private Handler handler = new Handler() { public void handleMessage(Message msg) { super.handleMessage(msg); JSONObject jsonObject=null; String classes = null; String Record_class = null; String PId = null; String name = null; if (msg.what == 1) { //放回有效的结果集 JSONObject res = (JSONObject) msg.obj; System.out.println(res); error_code = res.optString("error_code"); //看是否查询成功 System.out.println(error_code); if (error_code.equals("0") == true) { JSONArray TEMP = res.optJSONObject("result").optJSONArray("user_list"); //获取查询结果集的 用户列表 try { jsonObject = TEMP.getJSONObject(0); String Tscore = jsonObject.getString("score"); classes = jsonObject.getString("group_id"); //这里获取classes到获取name 是为了后面实现将查询结果同步到服务器数据库做准备 Record_class = Class.getText().toString().trim(); PId = jsonObject.getString("user_id"); name = jsonObject.getString("user_info"); System.out.println(classes+Record_class+ PId+ name); System.out.println(jsonObject); System.out.println(Tscore); score = Double.parseDouble(Tscore); //获取最相似人脸分数 因为最相似人脸会直接放到查询的user_list的第一个 System.out.println(score); // score=Math.ceil(Double.parseDouble(Tscore)); } catch (JSONException SE) { SE.printStackTrace(); } if (score >= 75) { //若相似度大于75 addrecord(classes,Record_class,PId,name); //服务器数据库添加查询成功的结果 } else Toast.makeText(MainActivity.this, "打卡失败,请重新导入照片", Toast.LENGTH_LONG).show(); myBitmapImage = null; InitBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.show, null); tip.setVisibility(View.GONE); myPhoto.setImageBitmap(InitBitmap); } else Toast.makeText(MainActivity.this, "打卡失败,请重新导入照片", Toast.LENGTH_LONG).show(); tip.setVisibility(View.GONE); } } };
关于addrecord方法,我用了Android的volley框架来进行与自己服务器后台的交互
public void addrecord(final String classes,final String Record_class,final String PId,final String name){ Log.d("addrecord",classes+","+PId+","+name); //请求地址,需要换接口 String url="http://47.106.10.15:8080/FtoFserver/FtoFserver/addRecord"; //服务器后台的接口 我接口使用Servelet写的 String tag = "addrecord"; //取得请求队列 RequestQueue requestQueue = Volley.newRequestQueue(MainActivity.this); //传参为当前的context //防止重复请求,所以先取消tag标识的请求队列 requestQueue.cancelAll(tag); //创建StringRequest,定义字符串请求的请求方式为POST(省略第一个参数会默认为GET方式) StringRequest request = new StringRequest(Request.Method.POST, url, new Response.Listener<String>() { @Override public void onResponse(String response) { Log.d("response",response); try { JSONObject jsonObject = new JSONObject(response); //获取的到的后台回应 temp = jsonObject.getString("canLogin"); if(temp.equals("true")){ //等待接口 System.out.println("发布成功"); Toast.makeText(MainActivity.this, "打卡成功!!!", Toast.LENGTH_LONG).show(); }else { System.out.println("发布失败"); Toast.makeText(MainActivity.this, "网络延迟,请重新上传!!!", Toast.LENGTH_LONG).show(); } } catch (JSONException e) { //做自己的请求异常操作,如Toast提示(“无网络连接”等); e.printStackTrace(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { //做自己的响应错误操作,如Toast提示(“请稍后重试”等) Log.d("error",error.toString()); } }) { @Override protected Map<String, String> getParams() throws AuthFailureError { Map<String,String> params = new HashMap<>(); //将打卡成功的人的信息同步到自己的服务器端 params.put("classes", classes); //传参到服务器接口 params.put("Record_class",Record_class); params.put("PId",PId); params.put("name",name); System.out.println(name); return params; } }; //设置Tag标签 request.setTag(tag); request.setRetryPolicy(new DefaultRetryPolicy(20*1000,1,1.0f)); //将请求添加到队列中 requestQueue.add(request); }
3°当验证完所有人脸,又把验证结果同步到自己事先部署的服务器端后,即完成了所以打卡操作
接下来 就可以用普通的servelet来获取服务器中存储的所有总的签到结果
即可导出已签到的人和未签到的人
补充 :
当然百度人脸库的更新、添加、删除也与打卡操作大同小异 也可以编辑类似上面的代码 来直接在安卓端操作
例如:
你可以事先在百度人脸库创建分组,然后往分组添加人脸的时候,同时完成添加人脸和添加自己服务端的人脸花名册信息操作
这样既可以抛去对直接去操作百度控制台的的操作
查询已经签到人:
可以直接查看自己服务器端的签到记录获取
查看未签到人:
可以通过服务器端的 班级花名册 减去签到记录已有的人
既可以获取未签到的人
整个项目我已经上传到github 欢迎大家学习
https://github.com/MrBling/FacetoFace(FacetoFace用Android studio导入项目,FtoFServer是我服务器端的代码 想看的话用IDEA导入项目即可)