案例 1:智能考勤机
案例 1:智能考勤机
教程定位
考勤系统是边缘人工智能中一个典型的端到端问题:摄像头产生连续图像,模型 在设备侧完成检测和特征提取,主机程序负责比对与记录,浏览器则提供管理和 观察入口。它同时涉及模型合同、主机—设备数据传输、线程生命周期、网络接口 和个人数据边界,因而适合作为本书的第一个综合案例。
本案例的目标不是构造可直接用于真实身份认证的产品,而是建立一条可以观察、 拆解和复现实验的技术链路。系统在昇腾 310B 开发板上运行两个离线模型,将 人脸图像转换为可比较的特征向量,再由 SQLite 保存用户记录和考勤事件。FastAPI 提供类型明确的 HTTP 接口,React 界面负责用户注册、浏览器抓拍、手动打卡和 记录查看。板端摄像头产生的预览帧通过 MJPEG 接口送往浏览器。
自动登记陌生人是本案例保留的教学演示策略:它用于展示“检测到人脸—提取 特征—写入数据库”的完整分支,不代表经过同意的身份注册,也不构成生产级 认证方案。真实部署必须增加身份确认、访问控制、活体检测、审计和数据删除 机制;本章不把演示结果表述为识别准确率或考勤有效性结论。

完成本案例后,读者应能够:
- 根据输入、输出和前处理合同检查一个 ONNX/OM 模型,而不是只依据文件名 判断模型是否可用;
- 解释 PyACL 初始化、Host/Device 拷贝、模型执行和资源释放的顺序;
- 设计一个由 FastAPI API、React 浏览器界面和单一硬件工作线程组成的边缘服务;
- 区分模型转换检查、数值烟测、接口烟测、用户体验检查和性能测量等不同证据;
- 讨论生物特征数据的保存范围、访问边界和教学示例与生产系统之间的差异。
问题背景与系统边界
在云端系统中,摄像头数据通常上传到远程服务进行处理;在边缘设备上,图像、 特征和考勤记录可以留在设备本地,从而减少网络依赖并缩短数据离开现场的路径。 代价是设备同时承担推理、采集、存储和服务任务,资源释放、并发控制和故障 可见性必须由应用显式管理。
本案例的边界如下:
| 边界 | 本案例承担的工作 | 不在本案例结论范围内 |
|---|---|---|
| 图像采集 | 板端 USB 摄像头采集;浏览器也可提交单张图像 | 摄像头质量、光照和姿态的全面评测 |
| 模型推理 | 在昇腾 NPU 上执行已转换的检测和特征模型 | 重新训练模型或证明某一公开基准上的精度 |
| 身份比对 | 在主机内存中计算余弦相似度并选取最高候选 | 法律意义上的身份认证或活体认证 |
| 数据保存 | SQLite 保存用户、特征和事件元数据 | 云端同步、多人权限和长期档案管理 |
| Web 交互 | FastAPI API 与 React 界面 | 互联网暴露、生产级认证和高可用集群 |
系统默认只监听本机地址。若在可信的实验局域网内访问开发板,操作者必须显式 指定监听地址,并意识到照片、特征和考勤接口仍属于敏感资源。
系统架构
系统由四个相互约束的层次构成:
- 采集层:板端
VideoCamera读取摄像头帧并维护最近一帧;React 可以 使用浏览器摄像头生成上传图像。两种来源都进入同一图像处理接口。 - 硬件工作层:一个受控的运行线程顺序调用检测、裁剪和特征提取;注册、 打卡等推理任务经同一队列执行,避免多个 HTTP 请求同时使用同一 ACL context、 Device buffer 或摄像头句柄。SQLite 另以进程内锁保护读写。
- 服务层:FastAPI 将注册、抓拍、手动打卡、记录查询和 MJPEG 预览暴露为 HTTP 资源。API 只负责校验请求、提交硬件任务和组织响应,不在请求函数中 复制一套推理流程。
- 界面层:React 调用 API 并呈现状态。界面中的“自动登记”标签必须说明 其教学属性,不能暗示这是经过人工授权的生产注册。

一次图像请求的逻辑顺序
对于注册或手动打卡,一次请求通常经历以下阶段:
- FastAPI 读取 multipart 文件或 base64 图像,并限制请求大小;
- OpenCV 解码图像,检查图像非空且位于允许的处理范围;
- 检测模型定位候选人脸,选择面积最大的候选作为本案例的简化策略;
- 根据边界框裁剪人脸并送入特征模型;
- 将输出向量与数据库中的向量逐一计算余弦相似度;
- 注册请求写入用户和头像,打卡请求在通过阈值时写入事件;
- API 返回结构化结果,界面根据
success、match或错误状态更新视图。
这是一种便于教学的串行流程。它没有声称对多人脸、遮挡、姿态或大规模用户 库具有最佳性能;这些问题应在后续实验中单独设计协议。
模型与张量合同
文件角色
样例脚本使用以下逻辑文件名:
| 逻辑角色 | ONNX 文件(准备阶段) | OM 文件(运行阶段) | 处理结果 |
|---|---|---|---|
| 人脸检测 | models/det_500m.onnx | models/face_detection.om | 候选框与分数,供主机后处理 |
| 特征提取 | models/w600k_mbf.onnx | models/face_recognition.om | 人脸特征向量 |
文件名是工程约定,不足以证明网络的论文名称或训练来源。检测网络的输出解析 必须以实际 ONNX 图、check_onnx.py/check_onnx_out.py 的结果以及板端 数值烟测共同确认。本实现采用三尺度分数、边界框距离和关键点输出的解析路径; 若转换得到的图结构不同,应先修正后处理合同,不能以“模型能加载”替代验证。
输入和前处理
当前实现编码了两组固定输入形状:检测输入为 1×3×640×640,特征输入为 1×3×112×112,数据类型为浮点数组。摄像头帧通常以 BGR 排列进入 OpenCV, 再缩放、归一化并转换为 NCHW。这些尺寸和归一化常数属于运行时合同,必须 与板端 OM 的描述和模型准备记录一致;改变其中任一项都应重新执行输入输出 检查和数值烟测。
特征模型的输出在当前实现中被解释为 float32 向量并写入 SQLite BLOB。向量 维度、字节序和归一化方式是数据库数据的隐含类型信息,迁移或更换模型时不能 只保留文件名而忽略这些字段。
从 ONNX 到 OM
ONNX 是准备和检查阶段的交换格式,OM 是昇腾运行时加载的离线模型。模型准备 脚本负责下载或定位源资产、检查 ONNX 输入输出,并在板端调用 ATC 生成 OM。 ATC、PyACL 和 OM 执行依赖 CANN 环境,开发机上的纯 Python 测试不能替代这些 步骤。模型、照片、数据库和板端报告属于运行资产,均不复制到本书源码。
推荐把证据分成四个独立门槛:
- 文件检查:路径存在、文件可读、ONNX 能被解析;
- 合同检查:输入形状、输出数量、数据类型与后处理实现一致;
- 转换检查:ATC 成功生成目标 OM,并保留命令和日志;
- 运行检查:在目标开发板上完成 ACL 加载、一次推理和资源释放。
只有完成相应门槛,文档才可以使用“已验证”这一表述。没有报告支撑的 FPS、 延迟、准确率和召回率不应写入案例结论。
PyACL 运行时与资源生命周期
初始化
应用启动时由运行时管理器完成以下工作:
- 调用
acl.init,设置目标设备并创建 context 与 stream; - 加载检测和特征 OM,读取模型描述中的输入输出信息;
- 为输入、输出数据集和 Device buffer 分配资源;
- 初始化 SQLite,并启动摄像头采集线程或延迟到首次使用。
FastAPI 的 lifespan 是组织这些步骤的合适位置。它使服务启动失败能够显式呈现 为 degraded 健康状态,而不是让请求在模型不存在时悄悄生成没有语义的向量。
一次执行
对每个模型,主机把连续的 NumPy 数组拷贝到 Device buffer,调用 acl.mdl.execute,再将输出拷回主机并按模型合同解释。检测和特征提取应由 同一个硬件工作队列串行调度;数据库查询和余弦相似度计算在主机侧完成,SQLite 读写由进程内锁与事务边界保护。
释放
服务停止时应先停止接收新任务,再停止 MJPEG 生成器和摄像头线程,随后释放 模型数据集、data buffer、Device buffer、模型句柄、stream、context 和设备,最后 调用 acl.finalize。显式的 stop() 与线程 join() 比依赖 Python 析构函数更 容易测试;若线程未在期限内退出,运行时必须报告失败并禁止再次创建硬件 owner。
FastAPI 与 React 的协作边界
API 资源
下表描述稳定的教学接口。字段由请求校验和运行时合同共同约束;错误响应使用 可读的 error 字段,而不是把 Python 异常堆栈返回给浏览器。
| 方法 | 路径 | 语义 |
|---|---|---|
GET | /api/health | 返回服务、模型和摄像头的就绪状态 |
GET | /api/users | 返回用户列表,不包含特征向量 |
POST | /api/users | 接收姓名与图像,提取特征并注册用户 |
PUT | /api/users/{id} | 修改用户显示名称 |
DELETE | /api/users/{id} | 删除用户及其关联事件 |
POST | /api/camera/capture | 从板端摄像头抓取一张图像 |
POST | /api/clockin | 接收上传或浏览器图像并尝试手动打卡 |
GET | /api/attendance | 返回当天每位用户的最近一条记录 |
GET | /video_feed | 返回缓存帧组成的 multipart/x-mixed-replace 流 |
GET | /uploads/{resource} | 返回已授权的头像或抓拍资源 |
模型缺失、加载失败或输出合同不满足时,健康接口应报告 degraded,注册和 推理接口返回 HTTP 503。这比返回随机特征或空的成功响应更容易定位部署 错误,也不会把无效结果写入考勤数据。
浏览器界面
React 页面通过 fetch 调用上述资源,使用文件上传或浏览器 getUserMedia 产生图像。离开页面、重新抓拍或请求失败时,前端应释放媒体 轨道并清理对象 URL。FastAPI 托管构建后的静态资源;/、/users_page 和 /attendance_page 作为兼容入口回退到同一个 SPA,使旧书签仍然可用。
MJPEG 预览只读取硬件线程维护的最近 JPEG,不在每个 HTTP 客户端中重新执行 检测。这样可以把“采集与推理”和“网络传输”分开,避免浏览器数量改变硬件 工作顺序。它仍然是教学级流式方案,不提供 WebRTC、鉴权和带宽自适应。
同步与异步的选择
FastAPI 采用 ASGI,但 PyACL、OpenCV 和标准库 sqlite3 都是阻塞调用。请求 处理函数不能因为使用 FastAPI 就全部改写为 async def;硬件操作应进入单一 工作队列或受控线程池,异步层只负责等待结果和发送响应。多 worker 启动会复制 模型和摄像头句柄,本案例固定单 worker。
检测、特征与比对
检测与裁剪
检测输出先经过分数阈值筛选、坐标缩放和 NMS,再得到候选框。当前示例在存在 多张人脸时选择面积最大的框,作为单人考勤演示的确定性策略。空框、越界框或 裁剪后尺寸为零时,应返回可解释的业务错误,不应把整张无脸图像当作有效身份 输入。
特征比对
设查询向量为 ,数据库向量为 ,余弦相似度为
系统在候选用户中取最大相似度,并与配置阈值比较。示例阈值是工程参数,不是 经过特定数据集校准的生物识别决策边界;改变模型、摄像头、光照或用户群体后, 应以独立的正负样本协议重新估计阈值,并报告误接受与误拒绝情况。
两种考勤分支
- 板端自动分支:摄像头线程定期取当前帧,检测最大人脸,完成特征比对; 已匹配用户可以写入
camera_auto事件,陌生人按教学策略自动登记并标注为 演示事件。 - 浏览器手动分支:React 上传文件或浏览器抓拍,API 完成同样的检测、 特征和比对过程;只有匹配通过时才写入
manual事件,未匹配结果返回match=false。
GET /api/attendance 的当前语义是“当天每位用户最近一次记录”,不是完整历史 审计。若研究需要多次事件、跨日期查询或导出,应另行设计带日期、用户、分页和 时区参数的接口,不应仅修改页面标题。
数据存储与隐私边界
SQLite 表保存用户名称、特征 BLOB、头像资源标识和考勤事件。特征是敏感的生物 特征数据,即使转换为 BLOB 也没有自动获得加密或匿名性。教学部署应至少做到:
- 上传文件由服务端生成不透明资源名,限制大小和图像类型,校验规范化路径 位于
data/uploads内; - API 列表不返回 embedding,资源下载经过明确的访问边界,不把上传目录 直接作为公开静态目录;
- 数据库、照片、模型和报告保留在设备本地,测试结束按实验协议删除;
- 日志只记录必要的事件标识,不输出完整特征、原始图像或调试堆栈;
- 自动登记仅用于教学观察,正式系统必须有知情同意、人工审核、撤销和删除 流程。
这些措施是最低限度的工程边界,不等同于完整的隐私影响评估或合规认证。
实验步骤与证据记录
开发机上的纯 Python 检查
开发机不具备 CANN、PyACL、OM 和摄像头运行条件。可执行的检查包括目录合同、 Python 语法、FastAPI 请求合同和使用 fake runtime 的生命周期测试:
cd samples/case1
python -m unittest discover -s tests -p 'test_layout.py'
python -m py_compile app.py face_attendance/*.py模型检查脚本可以解析已提供的 ONNX 文件,但解析成功不等于转换成功或推理 正确。测试输出应标明“静态检查”,不能写成板端性能结论。
开发板上的硬件检查
在已加载 CANN 环境的 Ascend 310B 上依次完成:
- 检查设备状态、摄像头节点和 Python
acl导入; - 检查 ONNX 输入输出合同,按目标 SoC 运行 ATC(若 OM 尚未存在);
- 加载两个 OM,执行一次检测和一次特征提取,确认输出形状;
- 启动 FastAPI,检查
/api/health、静态首页、用户 API 和 MJPEG 首帧; - 用合成或经同意的测试图像完成注册、手动打卡和自动分支;
- 发送停止信号,检查摄像头、模型、ACL context 和服务进程均已释放。
每项结果都应保留命令、环境、模型文件名、输入协议和原始日志路径。只有在 具有明确测量脚本和报告时,才可增加延迟、帧率、识别率或资源占用数字。
API 合同示例
以下示例只说明接口形状,不包含真实图像或生产凭据:
curl http://127.0.0.1:5000/api/health
curl http://127.0.0.1:5000/api/users
curl -F 'name=example' -F 'image=@synthetic-face.jpg' \
http://127.0.0.1:5000/api/users
curl -F 'image=@synthetic-face.jpg' \
http://127.0.0.1:5000/api/clockin板端服务若需要局域网访问,应显式传入受控的 --host,并在实验结束后恢复本机 监听。默认命令不启用调试模式、自动重载或多 worker。
局限与可扩展方向
本案例的简化点包括:最大人脸策略、全库线性检索、固定阈值、SQLite 单机写入、 MJPEG 预览和教学性的陌生人自动登记。它们有助于暴露基本数据流,但不能直接 推导出大规模、多摄像头、跨网络或高安全场景的结论。
合理的后续实验包括:
- 用独立的同人/异人样本校准阈值,并报告 ROC、误接受率和误拒绝率;
- 增加活体检测和人工审核,将“未知人脸”与“新用户注册”分离;
- 将向量检索从全库遍历扩展为带版本和重建策略的 ANN 索引;
- 以任务队列和限流机制研究多摄像头、多个浏览器客户端的资源竞争;
- 比较 MJPEG 与其他传输协议的带宽和延迟,但不得把协议比较结果归因于 NPU 推理速度。
小结
案例1把一个看似简单的“摄像头打卡”问题拆解为模型合同、硬件运行时、数据 比对、服务接口和隐私边界五个可以单独验证的对象。FastAPI 与 React 负责清晰 地表达服务和交互,单一硬件工作线程负责稳定地使用摄像头与 NPU,SQLite 则 提供可观察的本地事件存储。学习重点不是记住某个未经测量的延迟数字,而是 建立从资产检查到板端证据的完整推理链,并能说明每个结论的适用范围。
参考文献
- Deng, J., Guo, J., Xue, N., and Zafeiriou, S. ArcFace: Additive Angular Margin Loss for Deep Face Recognition. CVPR, 2019. DOI: 10.1109/CVPR.2019.00482.
- FastAPI documentation, Request files and uploads and Lifespan events.
