热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

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

由于百度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 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 user_list;

        public String getFace_token() {
            return face_token;
        }

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

        public List getUser_list() {
            return user_list;
        }

        public void setUser_list(List 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包,已经在工程中了。


推荐阅读
  • 微信小程序支付官方参数小程序中代码后端发起支付代码支付回调官方参数文档地址:https:developers.weixin.qq.comminiprogramdeva ... [详细]
  • Activity跳转动画 无缝衔接
    Activity跳转动画 无缝衔接 ... [详细]
  • 本文介绍如何通过Java代码调用阿里云短信服务API来实现短信验证码的发送功能,包括必要的依赖添加和关键代码示例。 ... [详细]
  • Hadoop MapReduce 实战案例:手机流量使用统计分析
    本文通过一个具体的Hadoop MapReduce案例,详细介绍了如何利用MapReduce框架来统计和分析手机用户的流量使用情况,包括上行和下行流量的计算以及总流量的汇总。 ... [详细]
  • 深入探讨Web服务器与动态语言的交互机制:CGI、FastCGI与PHP-FPM
    本文详细解析了Web服务器(如Apache、Nginx等)与动态语言(如PHP)之间通过CGI、FastCGI及PHP-FPM进行交互的具体过程,旨在帮助开发者更好地理解这些技术背后的原理。 ... [详细]
  • Python:新浪微博API初试
    {想在微博上抓点数据进行分析,费了一天多的时间,才终于找到点头绪,整理一下。}目录:一注册创建应用获取认证和授权二新浪微博pythonsdk下载和安装三简 ... [详细]
  • BeautifulSoup4 是一个功能强大的HTML和XML解析库,它能够帮助开发者轻松地从网页中提取信息。本文将介绍BeautifulSoup4的基本功能、安装方法、与其他解析工具的对比以及简单的使用示例。 ... [详细]
  • 本文介绍了如何通过安装和配置php_uploadprogress扩展来实现文件上传时的进度条显示功能。通过一个简单的示例,详细解释了从安装扩展到编写具体代码的全过程。 ... [详细]
  • 基于OpenCV的小型图像检索系统开发指南
    本文详细介绍了如何利用OpenCV构建一个高效的小型图像检索系统,涵盖从图像特征提取、视觉词汇表构建到图像数据库创建及在线检索的全过程。 ... [详细]
  • 本文探讨了Java中有效停止线程的多种方法,包括使用标志位、中断机制及处理阻塞I/O操作等,旨在帮助开发者避免使用已废弃的危险方法,确保线程安全和程序稳定性。 ... [详细]
  • 本文详细介绍了如何使用 Python 编程语言中的 Scapy 库执行 DNS 欺骗攻击,包括必要的软件安装、攻击流程及代码示例。 ... [详细]
  • 一键LNMP配置SSL证书实现全站HTTPS访问
    许多网站搭建者选择了便捷的一键LNMP安装包,但在网站部署完成后,配置SSL证书以支持HTTPS访问是一个不可或缺的步骤。本文将详细介绍如何通过简单的步骤完成这一过程。 ... [详细]
  • RabbitMQ 核心组件解析
    本文详细介绍了RabbitMQ的核心概念,包括其基本原理、应用场景及关键组件,如消息、生产者、消费者、信道、交换机、路由键和虚拟主机等。 ... [详细]
  • 探讨在iOS客户端向服务器上传数据流的过程中,若服务器因权限校验失败而未接收流直接响应时,如何有效避免客户端出现超时的问题。 ... [详细]
  • 本文详细介绍了PHP中的几种超全局变量,包括$GLOBAL、$_SERVER、$_POST、$_GET等,并探讨了AJAX的工作原理及其优缺点。通过具体示例,帮助读者更好地理解和应用这些技术。 ... [详细]
author-avatar
冠凯雅友9
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有