人脸识别考勤系统安卓APP(手把手教学-手动滑稽)

news/2024/11/20 11:28:51/

由于百度API开始收费,所以这个应用暂时无法使用了,代码部分仍然有参考价值,建议大家自行更换token去验证。个人博客的下载验证码bug,已经fix。可以继续下载了。

        更新于2022.02.13

————————————————————我是分割线————————————————————————————

App已经更新,现在可以永久使用。下载链接不变                       

百度云经常和谐我的下载,大家可以去我的个人博客下载,App和完整工程均有。https://sumtudou.cn/download.html

个人博客欢迎留言~~~   个人博客欢迎留言~~~   个人博客欢迎留言~~~

                                                                                                                                                                   更新于2019.6.14

————————————————————我是分割线————————————————————————————

经私信提醒,已更新,解决了删除人脸的BUG。已经上传了新的APP和完整工程包。下载链接见上。图片见下。

码农第二痛,看自己之前写的代码~~~简直惨不忍睹。稍微优化了目录结构,至于其他操作。加滚动条,加进度条对话框等,各位自己搞定。新的目录结构见下:

还有,大家不要白嫖吧~虽然不能三连。好歹点个赞再走啊~~~

还有,大家不要白嫖吧~虽然不能三连。好歹点个赞再走啊~~~

还有,大家不要白嫖吧~虽然不能三连。好歹点个赞再走啊~~~

                                                                                                                                                更新于:2019/12/13

————————————————————我是分割线————————————————————————————

    

题目十三 人脸识别考勤 3

一、目的与要求 3

1. 学会如何使用 Activity 及常见控件。 3

2. 学会使用数据库存储及网络通信。 3

3. 学会网上提供的现成 SDK。 3

二、功能需求 3

三、 实验内容 3

3.1百度SDK的使用 3

3.2具体实现 4

3.2.1获取手机的相机及存储权限 4

3.2.2进行拍照或者从相册选取并返回路径 5

3.2.3数据回转 6

3.2.4线程实现照片的上传 6

3.2.5使用GSON对JSON解析 9

3.2.6本地数据库的建立及操作 15

3.3部分结果展示 19

四、 实验总结 21

五、 实验源码及工程 21

题目十三 人脸识别考勤 

一、目的与要求 

  1. 学会如何使用 Activity 及常见控件。 
  2. 学会使用数据库存储及网络通信。 
  3. 学会网上提供的现成 SDK。 

二、功能需求 

针对课堂上采用 APP 来进行考勤这一需求,开发一套系统。

具体的功能有:

  1. 人脸录入:通过拍摄学生照片,或者上传现有照片。
  2. 人脸识别考勤:通过人脸识别,每个学生来考勤。
  3. 考勤统计及数据查询:制定查询条件(按学生、按时间),输出统计结果。

    注:请自行研究人脸识别 API 及各种接口。

  • 实验内容

3.1百度SDK的使用

首先进入百度的人脸识别口http://ai.baidu.com/tech/face

进入立即使用


之后我们打开技术文档查看,本应用,使用的最新的V3接口,在获取了Access百度会分配一个KEY和ID,在通过这两个参数,就可以连接到人脸库。

_token之后,就可以调用百度提供的,人脸库的增删改查等功能。

本报告的重点内容不在此,具体的内容请看百度的技术文档。对于本应用需要的jar包,见后页的链接。

3.2具体实现

3.2.1获取手机的相机及存储权限

显然,我们要实现的功能是手机打卡,自然需要拍照和读取手机的照片,之后返回它的路径。再通过路径将照片传入云端。进行必要的操作。

在安卓高版本中,相机是敏感权限,必须要动态申请,且安卓版本纷杂,所以获取相机权限真是一把辛酸泪……

首先在AndroidManifest中获取一次权限

之后,在Mainactivity中调用以下语句:

/**当检测到没有权限时,一次获取读写加拍照权限**/

3.2.2进行拍照或者从相册选取并返回路径

之后,对动态读取拍照权限,且将路径用Intent回传给上一个Activity进行解析。代码加了大量注释。不太需要解释了。这儿有一个坑,就是第一个获取的照片路径并不是真实的,需要再获取他的绝对路径。


在这里,我们要实现数据的回转,将uri传回上一个界面。之后将图片传入服务器,来接收返回的信息,这里依旧以打卡功能来举例子。

3.2.3数据回转

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 cursor = getContentResolver().query(uri, null, null, null, null);
            cursor.moveToNext();
            ImagePath = cursor.getString(cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA));   //获得图片的绝对路径
            cursor.close();
            Log.i("图片路径", ImagePath);
            runthreaad();      //开启线程,传入图片
        }
    } else if (requestCode == CAMERA) {
        runthreaad();  //开启线程,传入图片
    }
}

3.2.4线程实现照片的上传

Android靠后一点的版本,出现了一个问题。就是关于网络的POST必须用子线程来实现。答主在这里纠结了许久,才发现问题所在。这里对于上个功能的线程的具体解释。
void runthreaad() {    //新建线程
    new Thread(new Runnable() {
        @Override
        public void run() {    //开启
            String url = "https://aip.baidubce.com/rest/2.0/face/v3/search";//人脸查找的地址
            try {

//将我们的图片以二进制读取并转码为BASE64
                byte[] bytes1 = FileUtil.readFileByBytes(ImagePath);
                String image1 = Base64Util.encode(bytes1);
                Map<String, Object> map = new HashMap<>();//用MAp将要上传的信息来导入。
                map.put("image", image1);
                map.put("liveness_control", "NORMAL");
                map.put("group_id_list", "face");
                map.put("image_type", "BASE64");
                map.put("quality_control", "LOW");

                String param = GsonUtils.toJson(map);//转为json格式

                // 注意这里仅为了简化编码每一次请求都去获取access_token,线上环境access_token有过期时间, 客户端可自行缓存,过期后重新获取。
                String accessToken = "24.395dd6c10e2314d5886451ea8a44ff26.2592000.1548158423.282335-15236904";
                result = HttpUtil.post(url, accessToken, "application/json", param);
                System.out.println("hehehe:" + result);
                Gson gson=new Gson();
                Search_result_bean  Result_bean=gson.fromJson(result,Search_result_bean.class);
                System.out.println("哈哈哈哈哈哈哈哈"+ Result_bean.getError_code());
                int Error_code=Result_bean.getError_code();
                if(Error_code==0){
                    double score=Result_bean.getResult().getUser_list().get(0).getScore();
                    String user=Result_bean.getResult().getUser_list().get(0).getUser_id();
                    System.out.println("分数:"+score);
                    if(score>=75.0){
                        SQLiteDatabase db;
                        MyHelper ggg= new MyHelper(opt.this);
                        db=ggg.getWritableDatabase();
                        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
                      //  System.out.println(df.format(new Date()));// new Date()为获取当前系统时间
                        ggg.Insert_two(db,"time_id",df.format(new Date()),user);
                        Looper.prepare();
                        Toast.makeText(opt.this,"打卡成功!" , Toast.LENGTH_LONG).show();
                        Looper.loop();
                    }else{
                        Looper.prepare();
                        Toast.makeText(opt.this,"打卡失败!照片不在人脸库" , Toast.LENGTH_LONG).show();
                        Looper.loop();
                    }
                }else{
                    String error_message="打卡失败:"+Result_bean.getError_msg();
                    System.out.println("xixixixixixi"+ error_message);
                    Looper.prepare();
                    Toast.makeText(opt.this,error_message , Toast.LENGTH_LONG).show();
                    Looper.loop();
                }
            } catch (Exception e) {
                Log.i("错误", "hahaha");
                e.printStackTrace();
            }
        }
    }).start();
}

3.2.5使用GSON对JSON解析

首先我们来了解一下GSON,GSON是由Google开发的一款小型的解析包。可以用实现类的方式,对JSON数据逐层解析。

首先导入GSON的包。

当然,也可以在libs里加入本地拷入的包,再在depends里加入依赖。

第二步就使用GsonFormat,可以帮我们直接将Json格式的字符串自动生成实体类参数。

安装完毕之后

新建一个类:然后:Code → Generate..。 → GsonFormat ,然后将JSON字符串粘贴进去即可。

比如,我的查找JSON:
error_code : 0
error_msg : SUCCESS
log_id : 304592859128123651
timestamp : 1545912812
cached : 0
result : {"face_token":"b677eee8e44242db81a4063d0ba01efd","user_list":[{"group_id":"face","user_id":"li","user_info":"","score":95.235778808594}]}

返回的类:

package com.example.a11630.face_new;

import java.util.List;

public class Search_result_bean {
    private int error_code;
    private String error_msg;
    private long log_id;
    private int timestamp;
    private int cached;
    private ResultBean result;

    public int getError_code() {
        return error_code;
    }

    public void setError_code(int error_code) {
        this.error_code = error_code;
    }

    public String getError_msg() {
        return error_msg;
    }

    public void setError_msg(String error_msg) {
        this.error_msg = error_msg;
    }

    public long getLog_id() {
        return log_id;
    }

    public void setLog_id(long log_id) {
        this.log_id = log_id;
    }

    public int getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(int timestamp) {
        this.timestamp = timestamp;
    }

    public int getCached() {
        return cached;
    }

    public void setCached(int cached) {
        this.cached = cached;
    }

    public ResultBean getResult() {
        return result;
    }

    public void setResult(ResultBean result) {
        this.result = result;
    }

    public static class ResultBean {
        /**
         * face_token : b677eee8e44242db81a4063d0ba01efd
         * user_list : [{"group_id":"face","user_id":"li","user_info":"","score":95.235778808594}]
         */

        private String face_token;
        private List<UserListBean> user_list;

        public String getFace_token() {
            return face_token;
        }

        public void setFace_token(String face_token) {
            this.face_token = face_token;
        }

        public List<UserListBean> getUser_list() {
            return user_list;
        }

        public void setUser_list(List<UserListBean> user_list) {
            this.user_list = user_list;
        }

        public static class UserListBean {
            /**
             * group_id : face
             * user_id : li
             * user_info :
             * score : 95.235778808594
             */

            private String group_id;
            private String user_id;
            private String user_info;
            private double score;

            public String getGroup_id() {
                return group_id;
            }

            public void setGroup_id(String group_id) {
                this.group_id = group_id;
            }

            public String getUser_id() {
                return user_id;
            }

            public void setUser_id(String user_id) {
                this.user_id = user_id;
            }

            public String getUser_info() {
                return user_info;
            }

            public void setUser_info(String user_info) {
                this.user_info = user_info;
            }

            public double getScore() {
                return score;
            }

            public void setScore(double score) {
                this.score = score;
            }
        }
    }
}

这样我们进行层层解析,就可以轻松地获得我们需要的JSON数据了。

 

 Gson gson = new Gson();                      //新建GSON
  Search_result_bean Result_bean = gson.fromJson(result, Search_result_bean.class); //GSON与我的工具类绑定
//  System.out.println("哈哈哈哈哈哈哈哈" + Result_bean.getError_code());
  int Error_code = Result_bean.getError_code();          
  if (Error_code == 0) {                     //返回值为零,就是打卡识别成功

      double score = Result_bean.getResult().getUser_list().get(0).getScore();   //一层层进入,获取到score
      String user = Result_bean.getResult().getUser_list().get(0).getUser_id();   //获取用户名
   //   System.out.println("分数:" + score);
      if (score >= 78.0) {     //分数大于78.0分,判断为同一个人,提示打卡成功

/**…………具体操作**/

}
 

3.2.6本地数据库的建立及操作

这个,首先我的思路是建立了两个表,一个是time_id表存储打卡信息,包括了当前时间和打卡的id号。

另一个表是name_id表,存储了人脸信息,即姓名和id号。这样两个表之前没有冲突,且不会影响。且两个表可以通过id外键连接。

表名

name_id

time_id

Name,id(KEY)

Time,id

3.2.6.1数据库的创建及增删操作类的实现

package com.example.a11630.face_new;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class MyHelper extends SQLiteOpenHelper {
    public MyHelper(Context context) {
        super(context, "facedata.db", null, 2);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String stu_table = "create table name_id (id text primary key ,name text)"; //name----id
        String stu_table1 = "create table time_id(id text ,time text)"; //time----id
//执行SQL语句
        db.execSQL(stu_table);
        db.execSQL(stu_table1);
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }

  public  void Insert(SQLiteDatabase db, String table, String name, String id) {   ///db,table,name,id
        ContentValues cValue = new ContentValues();
        cValue.put("id", id);
        cValue.put("name", name);
        db.insert(table, null, cValue);
        System.out.println("插入成功:"+id+"   "+name);
    }
    public  void Insert_two(SQLiteDatabase db, String table, String time, String id) {   ///db,table,time,id
        ContentValues cValue = new ContentValues();
        cValue.put("id", id);
        cValue.put("time", time);
        db.insert(table, null, cValue);
        System.out.println("插入time成功:"+id+"   "+time);
    }
    public void Delete(SQLiteDatabase db, String table, String message) {
        String sql = "delete from " + table + " where  " + message;
        System.out.println("sqi语句:"+sql);
        db.execSQL(sql);
    }
}

3.2.6.2数据库的查询操作

这个有点难办,作者这一块代码写了很久(不好找bug),因为我们还要实现条件查找,即某个人,或者某天和某人某天的打卡记录要解决。

首先我们从简单的入手,只查找某个人的打卡记录,一句SQL语句解决。

见下面代码。

 String id = IDS.getText().toString().trim();    //获取id

 SQLiteDatabase db;
 MyHelper ggg = new MyHelper(change.this);
 db = ggg.getWritableDatabase();

 StringBuffer sum = new StringBuffer();

 String sql = "select * from time_id where id=\"" + id + "\"";
// System.out.println("差部分:" + sql);
 Cursor cursor = db.rawQuery(sql, null);
 while (cursor.moveToNext()) {
     String ID = cursor.getString(0); //获取第一列的值,第一列的索引从0开始
     String TIME = cursor.getString(1);//获取第二列的值
     sum.append("    ID:" + ID + "        time:" + TIME + "\n");
 }
 cursor.close();
 db.close();
 search_sum.setText(sum.toString());

这一段并没有什么好说的,之后就是查询某天的打卡记录,由于数据库的日期已经精确到了秒,这个时候用SQL语句显然不能实现了。(我们不可能每一秒都来查询一次)

这个时候我采用的是将数据库的所有打卡信息遍历一遍,将时间与我规定的日期进行对比。筛选出某天的打卡记录。具体如下。

String TIMES = btn_choosetime.getText().toString().trim();   //日期在此.
System.out.println("截取时间在此:" + TIMES);

StringBuffer sum = new StringBuffer();
SQLiteDatabase db;
MyHelper ggg = new MyHelper(change.this);
db = ggg.getWritableDatabase();
Cursor cursor = db.query("time_id", null,
        null, null, null, null, null);

sum.append("结果如下:");
if (cursor.getCount() != 0) {
    cursor.moveToFirst();
    String id = cursor.getString(0);
    String name_time = cursor.getString(1);

    if (TIMES.substring(0, TIMES.length()).equals
            (name_time.substring(0, TIMES.length()))) {    //截取前面的部分比较。如2018-12-31
        System.out.println("查询结果:\n");
        System.out.println(id + ":" + name_time);
        sum.append("\n    ID:" + id + "        time:" + name_time + "\n");
    }

    while (cursor.moveToNext()) {
        String id1 = cursor.getString(0);
        String name_time1 = cursor.getString(1);

        if (TIMES.substring(0, TIMES.length()).equals
                (name_time1.substring(0, TIMES.length()))) {
            System.out.println(id + ":" + name_time);
            sum.append("    ID:" + id1 + "        time:" + name_time1 + "\n");
        }
    }
}
cursor.close();
db.close();
search_sum.setText(sum.toString());

3.3部分结果展示

  • 实验总结

本报告也接近了尾声,其实本人比较烦这种总结。不知该怎么写。

在本应用制作初期,只用了很短的时间就可以用Java实现了图片上传和结果的接收。本以为改成安卓App是一件很容易的事。

然而现实狠狠给了我一耳光……安卓版本繁杂,每一个大版本对之前的改动都很大,光是一个获取拍照的权限,就可以在网上找到N个博客,且80%都是错的……

再就是安卓对于网络的控制,必须要在子线程里实现发送,这儿就又卡了小久。

最后就是数据库了,可以看到我的代码中充满了System.out.println(),因为Android studio3.1版本删除了DDMS,打不开数据库的文件夹,所以每一步调试都有点艰辛。

还好最后应用总算成形了,其实有很多细节可以去优化,比如上传图片中可以加一个进度条(这个答主实验过,要么就是开了关不了,要不就是秒开秒关)来提高用户体验。拍照时返回拍摄的图片给用户预览(这个也实验过,但是图片会自动旋转,修正后就是一片黑……)。

其他细节,在之后我会继续进行优化,总结结束。

  • 实验源码及工程

GitHub:GitHub - Sumtudou/Face-recognition-for-android: 安卓端的人脸识别app,支持从手机上传和即时拍照打卡,并带有SQLite数据库,可以保存打卡信息。

开发工具:Android studio3.1版本

Libs内有jar包,已经在工程中了。


http://www.ppmy.cn/news/836828.html

相关文章

Android 人脸识别 活体检测 人脸对比

人脸识别技术应用于支付、门禁、以及手机解锁在当下已经非常成熟&#xff0c;作为一个Android 开发者&#xff0c;掌握人脸识别技术已经是必须做的事情了&#xff0c;市面上比较流行的人脸识别API主要有阿里云&#xff08;5000条以内免费&#xff09;、腾讯云&#xff08;收费&…

Android开发实现人脸识别

之前看到有人在博客写用face做人脸识别app&#xff0c;后来我也照着教程去试了一遍&#xff0c;发现根本行不通&#xff0c;原因在于他调用的库是旧版本&#xff0c;face已经全面更新了版本.后来我照着face官网新版本的API文档打了一遍代码&#xff0c;发现识别的结果还算差强人…

Android自带的人脸识别

1. Android自带的人脸识别Android自带的人脸识别只能识别出人脸在画面中的位置&#xff0c;中点&#xff0c;眼间距&#xff0c;角度等基本特性&#xff0c;提供给拍照性质的应用使用。从基本功能中不能得出明显的特征数据 2. 底层库支持external/neven/* 3. 接口…

Android 实现人脸识别

引言 基于虹软人脸识别引擎&#xff0c;在Android平台上实现人脸识别功能&#xff0c;即使在离线的情况下依旧运行&#xff0c;不被人采集个人照片的感觉&#xff0c;还是爽爽的。经过整个测试过来&#xff0c;虹软的人脸识别还是很强大的&#xff0c;人脸检测可以控制在20ms之…

Android 人脸识别

1.人脸识别测试&#xff0c;如下&#xff1a; 2.导入新项目如下&#xff1a; 3.翻译 template:模板 tracker:跟踪器 lock:锁定 purge:清除 frame:框架我同事发给我这项目&#xff0c;叫我改目录才能查看&#xff0c;他说这项目的人脸识别可以注册人脸图和特征&#xff0c;注册…

APK的数字签名检测

检测命令&#xff1a; C:\Program Files\Java\jdk1.8.0_111\bin\jarsigner.exe -verify APK 文 件 路 径 -verbose –certs 当输出结果为“jar 已验证”时&#xff0c;表示签名正常 检测签名的 CN 及其他字段是否正确标识客户端程序的来源和发布者身份 如上图&#xff0c;说明…

laravel 上传后的文件如何符合安全标准

WEB应用为了满足“安全标准” - 上传文件的保存 Laravel 静态文件过滤中间件 <?phpnamespace App\Http\Middleware;use Closure;class StaticResourceInterceptor {/*** Handle an incoming request.** param \Illuminate\Http\Request $request* param \Closure $ne…

flask-apscheduler实现定时任务

秋风阁-北溪入江流 flask-apscheduler是一个支持apscheduler的flask插件&#xff0c;通过flask-apscheduler可以很方便的定义定时任务。 安装flask-apscheduler pip install flask-apschedulerflask-apscheduler组件 apscheduler库包含有组件&#xff1a; triggers&#xf…