2026/10/9 16:38:36

基于Android Studio与百度云的人脸识别学生考勤签到系统源码实战

基于Android Studio与百度云的人脸识别学生考勤签到系统源码实战 简介这是一套基于Android Studio与百度智能云的人脸识别学生考勤签到系统源码面向计算机相关专业学生、教师及企业开发者可用于毕业设计、课程设计或考勤类项目参考。项目采用SpringBoot搭建服务端配合MySQL存储数据管理员可维护人脸信息并同步至百度云平台安卓端调用人脸识别接口完成自动签到默认管理员账号为admin/123456。压缩包共328个文件约9.39MB包含46个Java源文件、31个XML布局、22个JS脚本、30个PNG与13个JPG图片资源以及SQL建库脚本、APK安装包、Gradle构建文件和README说明文档覆盖服务端、安卓端与数据库三部分。该资源为个人毕业设计成果代码经测试运行成功答辩评审平均分96分已有65人学习。下载后可获得完整项目结构、人脸识别对接思路与数据库设计参考建议先阅读README.md仅供学习交流勿用于商业用途。1. 从一张签到表到一套人脸考勤系统这套源码到底能跑通什么如果你带过课、管过实验室门禁或者帮某高校做过考勤工具大概率遇到过这种场景纸质签到表被代签、二维码截图满天飞、指纹机排队排到走廊。基于 Android Studio 和百度云的人脸识别学生考勤签到系统本质就是把「人脸检测 活体判断 云端比对 签到落库」这条链路塞进一台安卓手机里让老师拿手机对着学生扫一下后台自动写一条带时间戳的考勤记录。它解决的不是「识别准不准」这一个点而是「采集端、识别端、存储端怎么串起来」的工程问题。适合两类人一是要交课程设计或毕设的学生需要一套能编译、能演示、有 SQL 文件的完整工程二是想快速验证人脸考勤可行性的开发者想拿现成结构改造成自己的门禁或会议签到。这套东西的价值不在算法多先进而在于它把安卓端调用百度云人脸接口的完整流程、数据库表结构和文档说明都摊开了你照着改就能落地。2. 安卓端调用百度云人脸接口从 SDK 引入到活体检测的完整链路2.1 为什么选百度云人脸识别而不是本地跑模型很多人第一反应是「本地跑个 MobileFaceNet 不香吗」但真到落地阶段本地模型有三个绕不开的坎一是安卓机型碎片化不同芯片对 NNAPI 支持差异大同一套模型在低端机上推理要两三秒学生排队直接炸二是活体检测本地做很容易被照片和视频攻破而云端活体有持续更新的攻击样本库三是本地模型更新要发版云端接口改个参数就生效。百度云人脸识别提供检测、比对、活体、库管理一整套 HTTP 接口安卓端只负责拍照上传和解析 JSON逻辑轻、维护成本低。常见做法是端上做人脸框预览和拍照质量控制云端做特征提取和 1:N 搜索。这套源码走的就是这条路所以你在 Android Studio 里看到的更多是网络请求和相机逻辑而不是推理代码。2.2 在 Android Studio 里接入人脸 SDK 的最小步骤先把百度云控制台建好的应用拿到 API Key、Secret Key注意人脸识别用的是独立的 Access Token 接口不是直接拿 AK/SK 调业务接口。下面这段是获取 token 的核心代码放在工具类里全局复用// 获取百度云 Access Token有效期 30 天建议缓存到 SharedPreferences public static String getAccessToken() { String url https://aip.baidubce.com/oauth/2.0/token; OkHttpClient client new OkHttpClient(); // grant_type 固定为 client_credentials这是服务端应用的标准模式 RequestBody body new FormBody.Builder() .add(grant_type, client_credentials) .add(client_id, API_KEY) // 控制台应用的 API Key .add(client_secret, SECRET_KEY) // 控制台应用的 Secret Key .build(); Request request new Request.Builder().url(url).post(body).build(); try (Response response client.newCall(request).execute()) { JSONObject json new JSONObject(response.body().string()); return json.getString(access_token); // 拿到后缓存别每次请求都调 } catch (Exception e) { Log.e(Token, 获取失败, e); return null; } }逻辑说明token 接口是 POST 表单不是 GET参数名必须严格是grant_type、client_id、client_secret写错一个就返回 400。参数上client_id对应 API Keyclient_secret对应 Secret Key别和 AK/SK 混。返回的access_token默认 30 天有效但实际建议按 25 天缓存刷新避免边界过期导致签到失败。失败时先看返回体里的error和error_description常见是 key 填反或应用未开通人脸识别权限。2.3 人脸注册与签到比对的两段式设计考勤系统的人脸数据不能每次签到都重新注册正确做法是分两段注册阶段把学生人脸存进百度云的人脸库签到阶段用 1:N 搜索。注册时调/face/v3/faceset/user/add把user_id设成学号image传 base64 或 URLimage_type选 BASE64 时注意去掉前缀。签到调/face/v3/search传当前照片和group_id_list返回匹配到的user_id和score。下面是对比逻辑// 签到比对返回匹配学号和相似度分数 public static JSONObject searchFace(String base64Image, String groupId) { String url https://aip.baidubce.com/rest/2.0/face/v3/search?access_token getAccessToken(); OkHttpClient client new OkHttpClient(); // image_type 必须与传入格式一致BASE64 时不要带 data:image 前缀 RequestBody body new FormBody.Builder() .add(image, base64Image) .add(image_type, BASE64) .add(group_id_list, groupId) // 人脸库分组如 class_2024 .add(quality_control, NORMAL) // 质量校验NORMAL 会过滤模糊脸 .add(liveness_control, NORMAL) // 活体检测NORMAL 拒绝照片攻击 .build(); Request request new Request.Builder().url(url).post(body).build(); try (Response response client.newCall(request).execute()) { return new JSONObject(response.body().string()); } catch (Exception e) { Log.e(Search, 比对失败, e); return null; } }逻辑说明group_id_list是你在人脸库里建的分组建议按班级或课程建避免全校一个库导致搜索变慢。quality_control设 NORMAL 会返回质量分低于阈值直接判失败省得拿模糊脸去比对浪费配额。liveness_control设 NORMAL 是活体检测的关键但注意它需要额外算力响应会慢 200 到 500 毫秒签到高峰期要评估。返回结果里score是相似度一般 80 以上算可信但不同场景阈值不同后面避坑章节会细说。2.4 相机采集与图片压缩的参数取舍安卓端最容易翻车的地方不是接口是图片。百度云要求 base64 后不超过 2MB而手机原图动辄 5MB 以上。常见做法是拍照后先按长边缩到 640 像素再压到质量 80这样 base64 后大约 100 到 200KB识别率基本不掉。下面这段是压缩逻辑// 将 Bitmap 压缩为百度云可接受的 base64 字符串 public static String bitmapToBase64(Bitmap bitmap) { ByteArrayOutputStream baos new ByteArrayOutputStream(); // 先缩放到长边 640人脸识别不需要原图分辨率 int maxSide Math.max(bitmap.getWidth(), bitmap.getHeight()); float scale 640f / maxSide; Bitmap scaled Bitmap.createScaledBitmap(bitmap, (int) (bitmap.getWidth() * scale), (int) (bitmap.getHeight() * scale), true); // JPEG 质量 80 是识别率和体积的平衡点再低会丢特征 scaled.compress(Bitmap.CompressFormat.JPEG, 80, baos); byte[] bytes baos.toByteArray(); return Base64.encodeToString(bytes, Base64.NO_WRAP); // NO_WRAP 避免换行符 }逻辑说明createScaledBitmap的 filter 参数设 true 让缩放更平滑减少锯齿对人脸特征的干扰。质量 80 是经验值压到 60 以下暗光环境识别率明显下降。Base64.NO_WRAP必须加默认的换行符会让百度云解析失败这个坑很多人踩过。如果签到环境光线差可以在压缩前做一次直方图均衡但会增加端上耗时按需取舍。3. 数据库表设计与签到落库SQL 文件里每张表在干什么3.1 学生表、人脸库映射表、考勤记录表的三表结构这套源码的 SQL 文件通常包含三张核心表。学生表存学号、姓名、班级、人脸注册状态人脸库映射表存学号和百度云user_id的对应关系因为百度云返回的是user_id你需要反查学号考勤记录表存签到时间、课程 ID、匹配分数、签到状态。下面是一个可用的建表 SQL-- 学生基础信息表 CREATE TABLE student ( student_id VARCHAR(20) PRIMARY KEY, -- 学号与百度云 user_id 保持一致 name VARCHAR(50) NOT NULL, class_name VARCHAR(50), face_registered TINYINT DEFAULT 0, -- 0 未注册 1 已注册 create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 考勤记录表每次签到写一条 CREATE TABLE attendance ( id BIGINT AUTO_INCREMENT PRIMARY KEY, student_id VARCHAR(20), course_id VARCHAR(30), sign_time DATETIME DEFAULT CURRENT_TIMESTAMP, match_score FLOAT, -- 百度云返回的相似度分数 status TINYINT DEFAULT 1, -- 1 正常 2 迟到 3 代签嫌疑 INDEX idx_student_course (student_id, course_id) ); -- 人脸库分组映射支持一个学生多张脸 CREATE TABLE face_mapping ( id BIGINT AUTO_INCREMENT PRIMARY KEY, student_id VARCHAR(20), group_id VARCHAR(50), -- 百度云人脸库分组名 face_token VARCHAR(100), -- 注册时返回的 face_token update_time DATETIME DEFAULT CURRENT_TIMESTAMP );逻辑说明student_id直接当百度云user_id用省掉一层映射但要注意百度云user_id有长度限制学号一般够用。attendance表加match_score是为了事后审计分数异常低的记录可以人工复核。face_mapping支持一个学生注册多张脸比如正脸和侧脸提高识别率。索引idx_student_course是为了查「某学生某课程是否已签到」避免重复签到。3.2 签到接口的幂等处理与重复签到拦截考勤系统最怕同一节课学生连点两次签到写两条记录。正确做法是在插入前先查当天该学生该课程是否已有记录有则更新而不是新增。下面是对应的 SQL 逻辑-- 签到前查询判断是否已签到 SELECT id, sign_time FROM attendance WHERE student_id ? AND course_id ? AND DATE(sign_time) CURDATE(); -- 若已存在则更新签到时间否则插入新记录 INSERT INTO attendance (student_id, course_id, match_score, status) VALUES (?, ?, ?, 1) ON DUPLICATE KEY UPDATE sign_time NOW(), match_score VALUES(match_score);逻辑说明ON DUPLICATE KEY UPDATE依赖唯一索引所以attendance表最好加一个UNIQUE KEY (student_id, course_id, sign_date)其中sign_date是单独的日期字段避免用DATE(sign_time)做唯一索引导致失效。参数上match_score每次更新为最新值方便看识别稳定性。如果业务允许迟到多次签到就把唯一索引去掉改成先查后插但要在应用层加锁防止并发写重。3.3 从百度云返回 JSON 到落库的字段映射百度云/face/v3/search返回的结构是result.user_list[0].user_id和result.user_list[0].score你需要把user_id当学号score存进match_score。下面是一段解析并落库的代码// 解析百度云返回并写入考勤记录 public static void saveAttendance(JSONObject searchResult, String courseId) { try { JSONObject result searchResult.getJSONObject(result); JSONArray userList result.getJSONArray(user_list); if (userList.length() 0) { Log.w(Attendance, 未匹配到人脸); return; // 没人脸匹配不落库 } JSONObject top userList.getJSONObject(0); String studentId top.getString(user_id); double score top.getDouble(score); // score 低于 80 标记为代签嫌疑不直接算有效签到 int status score 80 ? 1 : 3; // 调用 DAO 层执行上面的 INSERT ... ON DUPLICATE KEY UPDATE AttendanceDao.upsert(studentId, courseId, score, status); } catch (JSONException e) { Log.e(Attendance, 解析失败, e); } }逻辑说明user_list可能为空必须先判长度再取第 0 个否则空指针。score阈值 80 是常见起点但实际要按你的库大小调库越大误匹配越多阈值要相应提高。status设 3 表示代签嫌疑后续可以人工复核而不是直接拒绝避免误伤。落库走 DAO 层别在解析里直接拼 SQL方便换数据库。4. 避坑与排查人脸考勤落地时最容易翻车的五个点4.1 相似度阈值设 80 却频繁误识别现象两个长相相近的学生互相被识别成对方签到记录张冠李戴。原因百度云 1:N 搜索的score受库大小影响库里人越多最高分越容易偏高80 分在几百人的库里不够。解决把阈值提到 85 到 90同时开启quality_control为 HIGH过滤低质量脸另外在注册时每人录两张不同角度的脸提高区分度。如果还不行就在应用层加二次确认分数在 85 到 90 之间的弹窗让老师手动确认。4.2 活体检测开了但照片依然能签到现象学生拿手机里存的照片对着摄像头居然签到成功。原因liveness_control设了 NORMAL 但没配合动作活体静态照片在部分光线下仍可能通过。解决改用动作活体让百度云返回动作指令端上提示学生转头或眨眼完成后再调搜索接口。注意动作活体会增加 1 到 2 秒耗时签到高峰期要分流。另外检查image_type是否传错传 URL 时百度云拉取的是原图可能绕过端上压缩导致活体判断异常。4.3 Access Token 过期导致整节课签到失败现象上午还能用下午所有签到返回 401。原因token 缓存没做刷新30 天到期后直接失效或者应用被杀进程后内存缓存丢失。解决把 token 存 SharedPreferences 并记录获取时间每次请求前检查是否超过 25 天超过就重新获取同时加一个失败重试遇到 401 先刷新 token 再重试一次。别在每次请求都调 token 接口百度云有 QPS 限制频繁调用会被限流。4.4 图片 base64 带前缀导致 400 错误现象接口返回error_code: 216101或image format error。原因安卓端用Base64.encodeToString后手动加了data:image/jpeg;base64,前缀百度云不认。解决image_type传 BASE64 时image字段只放纯 base64 字符串不要带任何前缀如果要用 URL就传可公网访问的图片地址别传本地路径。另外检查Base64.NO_WRAP是否加了换行符也会导致解析失败。4.5 签到高峰期接口超时现象上课前五分钟集中签到大量请求超时或返回error_code: 18QPS 超限。原因百度云免费版 QPS 有限并发一高就限流。解决端上加一个简单的队列同一时间只发一个请求前一个返回后再发下一个或者错峰签到按学号分组分批。如果班级人数多建议升级到付费版提高 QPS或者在端上先做本地人脸检测只有检测到人脸才调云端搜索减少无效请求。5. 把识别分数用起来从签到记录反推识别质量与调参技巧签到记录里的match_score不是写完就完事它是你调参的唯一依据。我一般会在系统跑一周后把attendance表按match_score排序看最低的那批记录。如果大量集中在 80 到 85 之间说明阈值设低了误识别风险高如果普遍在 90 以上说明可以适当降低阈值提高通过率。下面这条 SQL 能帮你快速看分布-- 按分数区间统计签到次数判断阈值是否合理 SELECT CASE WHEN match_score 95 THEN 95 WHEN match_score 90 THEN 90-95 WHEN match_score 85 THEN 85-90 WHEN match_score 80 THEN 80-85 ELSE 80以下 END AS score_range, COUNT(*) AS cnt FROM attendance WHERE sign_time DATE_SUB(NOW(), INTERVAL 7 DAY) GROUP BY score_range ORDER BY score_range DESC;逻辑说明DATE_SUB取最近七天避免历史数据干扰。如果80以下占比超过 10%说明要么阈值太低要么注册照片质量差需要重新录脸。85-90区间如果占比很高可以考虑把阈值提到 88牺牲一点通过率换准确率。这个查询建议每周跑一次作为调参依据。另一个技巧是给每个学生记录「历史平均分」如果某学生某次签到分数明显低于自己的历史均值比如低了 15 分以上就标记为可疑。这比全局阈值更细能抓住「本人但状态差」和「他人代签」的区别。实现上可以在student表加一个avg_score字段每次签到后更新移动平均。-- 更新学生历史平均分用于个体化异常检测 UPDATE student s JOIN ( SELECT student_id, AVG(match_score) AS avg_score FROM attendance WHERE student_id ? AND match_score 0 ) a ON s.student_id a.student_id SET s.avg_score a.avg_score;逻辑说明match_score 0过滤掉未匹配的记录避免拉低均值。移动平均可以用0.8 * 旧值 0.2 * 新值做平滑比直接 AVG 更跟得上状态变化。这个字段不参与签到判断只用于事后审计和预警别在签到主流程里查否则每次签到多一次写库高峰期会拖慢。最后说个血泪经验别在签到成功后立刻弹「签到成功」就完事要把匹配到的姓名和分数显示出来让老师肉眼确认。我见过太多系统因为阈值问题把 A 识别成 B老师没看直接过期末对账才发现。多这一秒确认能省掉后面一堆扯皮。希望帮到你。本文还有配套的精品资源点击获取