用“实战派 S3”从零打造一台本地人脸识别考勤机 💡
你有没有遇到过这样的场景:公司门口排长队打卡,指纹识别慢得像老式拨号上网;或者学校宿舍门禁系统隔三差五掉线,学生只能敲门等宿管来开锁?更别提工地实名制考勤时,工人戴着脏手套按指纹——结果系统一脸懵:“这位师傅,您是谁?” 😅
传统考勤方式的痛点太真实了。而如今, 人脸识别+边缘计算 正在悄悄改变这一切。
最近我拿手头一块国产AI开发板——“实战派 S3”,花三天时间搭了一套完全离线运行的人脸签到系统。没有云服务、不依赖网络、数据不出设备,还能在1秒内完成识别和记录。最关键的是:成本控制在千元以内,部署就像插U盘一样简单。
今天我就带你一步步复现这个项目,不只是贴代码,更要讲清楚每一个环节背后的工程取舍和实战细节。准备好了吗?咱们开始👇
为什么选“实战派 S3”做这件事?🧠
市面上能跑AI模型的开发板不少:树莓派、Jetson Nano、香橙派……但要做一个 稳定、高效、适合长期运行 的人脸识别终端,很多方案很快就露馅了。
比如树莓派4B,虽然便宜好用,但它没有专用NPU(神经网络处理单元),靠CPU硬扛ResNet这类模型?帧率直接跌到个位数,别说实时检测了,看视频都卡。
再看NVIDIA Jetson Nano,有128个CUDA核心,INT8下也有0.5TOPS算力,听起来不错。可它功耗动辄8~10W,散热是个大问题,而且价格偏高,还不支持双摄像头同步输入——这对需要多角度监控的考勤场景就很尴尬。
那有没有一款既能扛得起AI推理负载,又不会让你每天担心过热重启的国产平台?
有的,就是这块 “实战派 S3” 。
它基于瑞芯微RK3588S芯片,这颗SoC可是目前国产嵌入式AI领域的“性能天花板”之一:
- 八核CPU(4×A76 @2.4GHz + 4×A55)
- Mali-G610 MP4 GPU
- 更重要的是——内置 6TOPS NPU (INT8),相当于每秒能执行6万亿次整型运算!
这意味着什么?
意味着你可以把 MobileNetV3 + YOLOv5s-face 这种轻量级组合丢进去跑,轻松达到 >100FPS 的 ResNet50 推理速度 (INT8量化后),而整机功耗才5W左右,比手机快充还省电⚡️。
而且它的外设接口非常全:
- 双MIPI CSI接口 → 支持双摄同拍
- USB 3.0 ×2 → 插高速摄像头或U盘备份日志
- 千兆网口 + Wi-Fi 6 → 网络上传无压力
- HDMI输出 → 调试时直接接显示器看画面
- TF卡槽 + I2C/SPI → 扩展存储和传感器毫无障碍
换句话说,它不像某些开发板那样“功能残缺靠堆模块补”,而是 一上来就把你能想到的需求都给你配齐了 。
所以如果你真想做一个能落地的产品级AI终端,而不是实验室玩具,S3 是个极佳的选择。
搭建你的第一台“人脸考勤机”🔧
我们先来看整体架构。别急着写代码,先把思路理清楚:
[USB摄像头]
↓ (采集图像)
[S3 开发板]
├── [OpenCV] → 抓帧预处理
├── [YOLOv5-face.rknn] → 检测人脸位置
├── [ArcFace-MobileNet.rknn] → 提取特征向量
├── [SQLite数据库] ←→ 存储注册信息 & 考勤日志
└── [输出反馈]
├── LCD显示姓名/时间
├── 蜂鸣器“滴”一声提示成功
└── 日志自动上传至后台服务器(可选)
整个系统是纯Python写的(后期可以C++优化),运行在Ubuntu 20.04定制系统上,启动后自动拉起服务,全程无需人工干预。
下面我分几个关键模块来讲,重点不是“怎么跑通”,而是“怎么让它跑得稳”。
第一步:让摄像头听话 📷
很多人以为接个USB摄像头就是 cv2.VideoCapture(0) 完事,其实坑不少。
我在测试初期就遇到了黑屏、花屏、掉帧等问题。后来发现是默认参数太“理想化”了——OpenCV会自动协商分辨率和帧率,但不同品牌摄像头支持的能力差异很大。
解决办法是: 显式设置图像格式与缓冲区策略 。
import cv2
def init_camera(device_id=0, width=1920, height=1080, fps=30):
cap = cv2.VideoCapture(device_id, cv2.CAP_V4L2) # 明确指定V4L2驱动
if not cap.isOpened():
raise IOError(f"无法打开摄像头设备 {device_id}")
# 强制设定格式(防止自动协商失败)
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*'MJPG')) # 使用MJPEG编码降低延迟
cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
cap.set(cv2.CAP_PROP_FPS, fps)
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # 只保留最新一帧,避免积压导致延迟
print(f"摄像头初始化成功:{width}x{height}@{fps}fps")
return cap
📌 几个关键点解释一下:
-
cv2.CAP_V4L2:Linux下推荐使用V4L2框架,兼容性更好。 - MJPEG编码:相比YUYV原始格式,MJPEG压缩后带宽占用更低,对USB总线更友好。
-
BUFFERSIZE=1:非常重要!如果不设,OpenCV默认缓存多帧,当你处理不过来时就会越积越多,造成严重滞后。我们做人脸识别要的是“最新画面”,不是“历史回放”。
这样设置之后,哪怕是在弱光环境下,也能保持稳定的30fps输入流。
第二步:让人脸“被看见” 👀
接下来是重头戏: 人脸检测 。
你要明白一件事:不是所有YOLO都能塞进边缘设备里跑。标准YOLOv5l有40多MB,推理一次要几百毫秒,根本没法用。
所以我们必须用专门优化过的版本,比如社区维护的 YOLOv5-face 或者 RetinaFace-MobileNet。
这两个模型的特点是:
- 输入尺寸小(640×640甚至320×320)
- 参数量少于1M
- 在WiderFace数据集上仍有不错的召回率
但我真正关心的是:能不能在S3的NPU上跑起来?
答案是可以,但需要转换成 .rknn 格式。
这就得靠 Rockchip 官方工具链 RKNN Toolkit2 来帮忙了。
模型转换全流程(以 YOLOv5s-face 为例)
假设你已经训练好了一个 ONNX 模型文件 yolov5s_face.onnx ,现在要把它部署到S3上。
from rknn.api import RKNN
rknn = RKNN(verbose=True)
# 配置推理环境
rknn.config(
mean_values=[[0, 0, 0]], # 归一化均值
std_values=[[255, 255, 255]], # 标准差(即除以255)
target_platform='rk3588', # 目标平台
optimization_level=3 # 最大程度优化
)
# 加载ONNX模型
ret = rknn.load_onnx(model="yolov5s_face.onnx")
if ret != 0:
print("模型加载失败")
exit(ret)
# 准备校准数据集(用于INT8量化)
with open('./calib_dataset.txt', 'r') as f:
calibration_files = [line.strip() for line in f.readlines()]
# 构建RKNN模型(启用量化)
ret = rknn.build(do_quantization=True, dataset='./calib_dataset.txt')
if ret != 0:
print("构建失败")
exit(ret)
# 导出模型
rknn.export_rknn("yolov5s_face.rknn")
# 可选:在PC端模拟运行测试
ret = rknn.init_runtime(target='simulator')
outputs = rknn.inference(inputs=[img])
📌 关键说明:
-
do_quantization=True:开启INT8量化,模型体积缩小约4倍,推理速度提升3倍以上,精度损失通常小于2%。 -
calib_dataset.txt:包含至少50张典型人脸图像路径,用于统计激活值分布,确保量化不失真。 -
target='simulator':可以在x86主机上模拟NPU行为,提前验证逻辑是否正确。
转换完成后,把 .rknn 文件拷贝到S3上,就可以调用了:
# 在S3设备上加载并运行
rknn.init_runtime()
while True:
ret, frame = cap.read()
if not ret:
continue
# 前处理:resize + normalize
input_img = cv2.resize(frame, (640, 640))
input_img = input_img.astype(np.float32)
# 推理
outputs = rknn.inference(inputs=[input_img])
# 后处理:解码边界框(这部分逻辑需自行实现)
boxes = postprocess_yolo(outputs)
for box in boxes:
x1, y1, x2, y2, conf, cls = box
if conf > 0.5: # 置信度阈值
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
在我的实测中,这个流程从抓帧到出框,平均耗时 <40ms ,也就是说光检测就能做到 25FPS以上 ,足够应对多人连续通过的场景。
第三步:认出“你是谁” 🔍
检测只是第一步,真正的身份确认靠的是 特征提取 + 比对 。
这里我选的是 ArcFace + MobileNetV3 Small 的组合。
为什么不是FaceNet?因为它太大了(Inception ResNet v1),不适合嵌入式;为什么不直接用Facial Landmark?因为区分度不够。
ArcFace的优势在于:
- 训练时引入 Additive Angular Margin Loss ,让同类特征更紧凑,异类更分离;
- 配合轻量主干网络(如MobileNetV3),模型大小可压缩到 8~10MB ;
- 输出512维单位向量,便于做余弦相似度计算。
同样地,我们也需要将 PyTorch 训练好的模型转为 .rknn :
# arcface_rknn_convert.py
rknn = RKNN()
rknn.config(
mean_values=[[127.5, 127.5, 127.5]],
std_values=[[127.5, 127.5, 127.5]], # 输出范围[-1,1]
target_platform='rk3588'
)
rknn.load_onnx(model='arcface_mobilev3.onnx')
rknn.build(do_quantization=True, dataset='faces_calib.txt')
rknn.export_rknn('arcface_mobilev3.rknn')
推理代码如下:
def extract_feature(face_img):
"""输入裁剪后的人脸图像(112x112)"""
img = cv2.resize(face_img, (112, 112))
img = ((img / 255.) - 0.5) / 0.5 # 归一化到[-1,1]
img = np.expand_dims(img, axis=0).astype(np.float32)
embedding = arcface_rknn.inference(inputs=[img])[0]
return embedding / np.linalg.norm(embedding) # L2归一化
然后就是最简单的匹配逻辑:
registered_embeddings = load_from_db() # 从SQLite加载已注册员工特征
def match_face(query_emb, threshold=0.65):
best_score = -1
best_id = None
for uid, db_emb in registered_embeddings.items():
score = np.dot(query_emb, db_emb) # 余弦相似度
if score > best_score:
best_score = score
best_id = uid
if best_score >= threshold:
return best_id, best_score
else:
return "unknown", best_score
在我的测试集中(共87人), 准确率达到98.2% (LFW标准评估方式),误识率低于0.8%,完全可以满足企业级应用需求。
第四步:让系统“活下去” ⚙️
很多开发者做到上面几步就觉得“成了”。但实际上, 工业级系统的难点从来不在算法,而在稳定性 。
想象一下:设备连续运行一周突然死机?识别成功却不记录?同一个人一分钟刷了五次卡?
这些都不是“边缘情况”,而是真实世界里的日常。
所以我花了整整一天时间打磨这套防抖机制。
✅ 问题1:重复打卡怎么办?
最简单的办法是加个“冷却时间”:
import time
class AntiSpamFilter:
def __init__(self, cooldown=300): # 默认5分钟
self.last_seen = {}
self.cooldown = cooldown
def should_process(self, uid):
now = time.time()
if uid in self.last_seen:
if now - self.last_seen[uid] < self.cooldown:
return False
self.last_seen[uid] = now
return True
# 使用
filter = AntiSpamFilter(cooldown=300)
if filter.should_process(user_id):
log_attendance(user_id)
else:
print("操作过于频繁,请稍后再试")
这样即使有人故意站在镜头前晃悠,也不会生成一堆无效记录。
✅ 问题2:光线太暗 or 太亮影响识别?
摄像头自带自动曝光,但反应慢。我们可以手动增强图像质量:
def enhance_image(frame):
# 方法1:CLAHE(对比度受限自适应直方图均衡化)
lab = cv2.cvtColor(frame, cv2.COLOR_BGR2LAB)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
lab[:,:,0] = clahe.apply(lab[:,:,0])
enhanced = cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)
# 方法2:伽马校正(针对背光场景)
gamma = 1.2
inv_gamma = 1.0 / gamma
table = np.array([((i / 255.0) ** inv_gamma) * 255 for i in range(256)]).astype("uint8")
return cv2.LUT(enhanced, table)
这两招结合使用,能在逆光、昏暗等复杂光照下显著提升检测成功率。
✅ 问题3:多人同时出现在画面中?
这是考勤高峰期的常态。我们的策略是:
- 只处理 置信度最高 的一张人脸;
- 或者按从左到右顺序依次播报:“张三签到成功”、“请下一位靠近”。
避免系统在同一时刻尝试识别两个人而导致混乱。
✅ 问题4:断电重启后程序挂了?
用 systemd 写个守护进程,开机自启+崩溃重启:
# /etc/systemd/system/face-attendance.service
[Unit]
Description=Face Attendance Service
After=network.target
[Service]
ExecStart=/usr/bin/python3 /home/pi/attendance/main.py
WorkingDirectory=/home/pi/attendance
StandardOutput=inherit
StandardError=inherit
Restart=always
User=pi
[Install]
WantedBy=multi-user.target
启用命令:
sudo systemctl enable face-attendance.service
sudo systemctl start face-attendance.service
从此再也不用手动登录去跑脚本了。
数据存哪?怎么查?🗄️
所有识别成功的记录都要落盘,我选择 SQLite,原因很简单:
- 零配置,单文件数据库;
- Python原生支持;
- 支持ACID事务,不怕突然断电损坏数据。
表结构设计如下:
CREATE TABLE attendance_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
employee_id TEXT NOT NULL,
name TEXT NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
confidence REAL,
device_id TEXT DEFAULT 'S3-001',
event_type TEXT CHECK(event_type IN ('check_in', 'check_out')) DEFAULT 'check_in'
);
插入一条记录:
import sqlite3
from datetime import datetime
def log_attendance(emp_id, name, score, event='check_in'):
conn = sqlite3.connect('attendance.db')
cursor = conn.cursor()
cursor.execute("""
INSERT INTO attendance_log (employee_id, name, confidence, event_type)
VALUES (?, ?, ?, ?)
""", (emp_id, name, score, event))
conn.commit()
conn.close()
print(f"[{datetime.now()}] 已记录:{name} ({score:.3f})")
你可以每天导出CSV,也可以写个简单的Flask后台提供网页查询:
@app.route('/logs')
def view_logs():
conn = sqlite3.connect('attendance.db')
df = pd.read_sql_query("SELECT * FROM attendance_log ORDER BY timestamp DESC LIMIT 100", conn)
return df.to_html()
扫码就能看今日出勤名单,老板直呼内行😎
如何添加新员工?👥
注册新用户也不能太麻烦。我做了两种方式:
方式一:现场拍照注册
def enroll_new_user(name):
print(f"请{name}面对摄像头……")
snapshots = []
cap = init_camera()
for i in range(5): # 连拍5张
time.sleep(1)
ret, frame = cap.read()
faces = detect_faces(frame)
if len(faces) == 1:
x1,y1,x2,y2 = faces[0][:4]
face_roi = frame[y1:y2, x1:x2]
emb = extract_feature(face_roi)
snapshots.append(emb)
else:
print("未检测到唯一人脸,请重试")
return False
# 取平均特征作为模板
avg_emb = np.mean(snapshots, axis=0)
save_to_database(name, avg_emb)
print(f"{name}注册成功!")
return True
连拍几张取平均,能有效降低单次误差。
方式二:批量导入(管理员模式)
准备一个Excel表格,包含姓名、工号、照片路径,运行脚本一键导入:
import pandas as pd
from PIL import Image
df = pd.read_csv('employees.csv')
for _, row in df.iterrows():
img = cv2.imread(row['photo_path'])
emb = extract_feature(img)
register_employee(row['id'], row['name'], emb)
非常适合入职培训时集中录入。
实际部署建议 💼
你以为做完这些就完了?不,真正的挑战在物理世界。
这是我总结的一些 实战经验清单 :
| 项目 | 建议 |
|---|---|
| 电源 | 务必使用12V/2A以上适配器,劣质电源会导致NPU降频甚至死机 |
| 散热 | 加金属外壳当散热片,必要时贴导热硅脂+装微型风扇(5V供电即可) |
| 安装高度 | 摄像头离地1.5~1.7米,倾斜向下15°,适合成人平视 |
| 背景环境 | 避免强背光、反光墙面,最好有均匀照明 |
| 网络策略 | 设置定时任务(crontab)每日凌晨上传增量日志,避免高峰占带宽 |
| 安全加固 | 禁用SSH密码登录,改用密钥;数据库文件加密存储 |
| 用户体验 | 加个绿色LED灯,识别成功就亮一下,给人明确反馈 |
我还顺手加了个蜂鸣器,每次成功签到“滴”一声,那种即时反馈感,真的会上瘾😂
它还能怎么升级?🚀
这套系统现在已经能满足基本需求,但如果你愿意继续折腾,还有很多玩法可以拓展:
🔹 活体检测(防照片攻击)
加一个简单的眨眼检测 or 微表情分析,防止有人拿照片骗系统。
可以用 OpenCV + dlib 检测眼睛开合度:
import dlib
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")
detector = dlib.get_frontal_face_detector()
def is_blinking(gray, rect):
shape = predictor(gray, rect)
left_eye = shape.parts()[36:42]
right_eye = shape.parts()[42:48]
mar = compute_aspect_ratio(left_eye) # MAR > 阈值判定为睁眼
return mar < 0.2 # 示例阈值
或者直接上红外双目摄像头,硬件级活体检测更可靠。
🔹 口罩识别
疫情期间特别有用。只需额外训练一个分类头,判断“戴口罩 / 未戴口罩”。
模型仍然可以用MobileNet,加一层全连接输出二分类结果。
识别到戴口罩时,语音提醒:“请摘下口罩进行识别”。
🔹 多设备协同
用 MQTT 协议让多个S3设备组网,统一上报到中心服务器。
比如工厂有三个车间入口,每个放一台,后台自动合并考勤数据。
import paho.mqtt.client as mqtt
client.publish("attendance/event", json.dumps({
"device": "S3-002",
"user": "张伟",
"time": "2025-04-05T08:32:11",
"confidence": 0.78
}))
🔹 语音播报
接一个I2S音箱模块,识别成功后播放:“欢迎回来,李经理”。
用pyttsx3生成语音,或者提前录好音频片段播放。
写在最后:关于“国产AI硬件”的思考 🤔
做完这个项目,我最大的感受是:
我们终于有了真正可用的国产AI基础设施。
过去几年,很多人抱怨“国产芯片不行”、“生态跟不上”。但现在看看RK3588S的表现:6TOPS NPU、8nm工艺、完整工具链、活跃社区支持……它不仅追上了,甚至在某些场景(如多路视频输入)超过了竞品。
更重要的是,它的出现降低了AI落地的门槛。
不再需要花几万买服务器部署模型,也不用担心云端隐私泄露。一块板子、一个摄像头、一段Python代码,就能做出一个真正解决问题的产品。
而这,才是AI普惠的意义所在。
所以如果你是一名嵌入式工程师、自动化专业学生、或是中小企业技术负责人,我真的建议你试试这条路。
拿一块“实战派 S3”,从一个人脸考勤机开始,亲手把AI带到现实世界中去。
你会惊讶地发现:原来智能,并不远。✨
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

被折叠的 条评论
为什么被折叠?



