3. 人脸识别门禁系统
任务目标
在嵌入式平台(以Raspberry Pi 4B为例)上部署轻量级人脸检测与识别模型,实现本地人脸比对,并根据比对结果控制电磁锁开关。系统需在无云端依赖条件下完成“检测→特征提取→比对→决策→执行”全链路。
硬件与环境依赖
- 硬件:Raspberry Pi 4B(≥2GB RAM)、Pi Camera V2、电磁锁(12V)+ 继电器模块(GPIO控制)、5V/3A电源。
- 系统:Raspberry Pi OS (Bullseye, 64-bit)。
- Python依赖:
opencv-python、numpy、picamera2、tflite-runtime(或tensorflow-lite)。 - 模型文件(需提前下载放入项目目录):
- 人脸检测:mobilefacenet.tflite(或使用ultraface轻量检测模型);
- 人脸识别:facenet.tflite(MobileFaceNet架构,128维特征输出)。
说明:模型文件较大,不随代码提供,需从公开模型库(如Google AI Edge Model Explorer)下载对应TFLite版本。
核心实现逻辑
系统启动后初始化摄像头与GPIO,进入循环:
1. 捕获帧并缩放到检测模型输入尺寸(192×192);
2. 检测人脸框,若置信度>0.7则裁剪人脸区域;
3. 将人脸区域缩放到识别模型输入尺寸(112×112),提取128维特征向量;
4. 与已注册的人脸特征库(本地.npy文件)计算余弦相似度;
5. 若最大相似度>阈值(默认0.75),则判定为授权人员,GPIO输出高电平触发继电器闭合电磁锁5秒;否则不动作并显示“未授权”。
关键完整代码
import cv2
import numpy as np
import tflite_runtime.interpreter as tflite
import time
import os
from picamera2 import Picamera2
import RPi.GPIO as GPIO
# ---------- 配置 ----------
DETECT_MODEL = "mobilefacenet.tflite" # 实际使用ultraface或similar检测模型
RECOG_MODEL = "facenet.tflite"
FACE_DB = "face_db.npy" # 已注册特征库 (N,128)
AUTH_THRESHOLD = 0.75
LOCK_PIN = 17
UNLOCK_SECONDS = 5.0
DETECT_SIZE = 192
RECOG_SIZE = 112
# ---------- GPIO初始化 ----------
GPIO.setmode(GPIO.BCM)
GPIO.setup(LOCK_PIN, GPIO.OUT)
GPIO.output(LOCK_PIN, GPIO.LOW)
# ---------- 加载TFLite模型 ----------
def load_interpreter(model_path):
interp = tflite.Interpreter(model_path=model_path)
interp.allocate_tensors()
return interp
det_interp = load_interpreter(DETECT_MODEL)
recog_interp = load_interpreter(RECOG_MODEL)
# 获取输入输出详情
det_in = det_interp.get_input_details()
det_out = det_interp.get_output_details()
recog_in = recog_interp.get_input_details()
recog_out = recog_interp.get_output_details()
# ---------- 加载人脸特征库 ----------
if os.path.exists(FACE_DB):
face_db = np.load(FACE_DB)
else:
face_db = np.empty((0, 128))
print("警告: 未找到特征库,请先运行注册脚本")
# ---------- 人脸检测 (简化版,实际使用检测模型输出) ----------
def detect_faces(frame):
"""返回人脸边界框列表 [(x,y,w,h), ...]"""
h, w = frame.shape[:2]
img = cv2.resize(frame, (DETECT_SIZE, DETECT_SIZE))
img = img.astype(np.float32) / 127.5 - 1.0
img = np.expand_dims(img, axis=0)
det_interp.set_tensor(det_in[0]['index'], img)
det_interp.invoke()
boxes = det_interp.get_tensor(det_out[0]['index'])[0]
scores = det_interp.get_tensor(det_out[1]['index'])[0]
faces = []
for i in range(len(scores)):
if scores[i] > 0.7:
y1, x1, y2, x2 = boxes[i]
x, y = int(x1*w), int(y1*h)
bw, bh = int((x2-x1)*w), int((y2-y1)*h)
faces.append((x, y, bw, bh))
return faces
# ---------- 提取人脸特征 ----------
def get_embedding(face_img):
"""输入BGR人脸图像,返回128维归一化特征向量"""
img = cv2.resize(face_img, (RECOG_SIZE, RECOG_SIZE))
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = img.astype(np.float32) / 255.0
img = (img - 0.5) / 0.5
img = np.expand_dims(img, axis=0)
recog_interp.set_tensor(recog_in[0]['index'], img)
recog_interp.invoke()
emb = recog_interp.get_tensor(recog_out[0]['index'])[0]
return emb / np.linalg.norm(emb)
# ---------- 比对与开锁 ----------
def verify_and_unlock(embedding):
if face_db.shape[0] == 0:
return False
# 余弦相似度 = 点积 (已归一化)
sims = np.dot(face_db, embedding)
max_idx = np.argmax(sims)
if sims[max_idx] > AUTH_THRESHOLD:
print(f"✅ 授权通过 (相似度: {sims[max_idx]:.3f})")
GPIO.output(LOCK_PIN, GPIO.HIGH)
time.sleep(UNLOCK_SECONDS)
GPIO.output(LOCK_PIN, GPIO.LOW)
return True
else:
print(f"❌ 未授权 (最高相似度: {sims[max_idx]:.3f})")
return False
# ---------- 主循环 ----------
def main():
picam2 = Picamera2()
config = picam2.create_preview_configuration(
main={"size": (640, 480), "format": "RGB888"})
picam2.configure(config)
picam2.start()
try:
while True:
frame = picam2.capture_array()
faces = detect_faces(frame)
for (x, y, w, h) in faces:
# 确保边界不越界
x, y = max(0, x), max(0, y)
face_crop = frame[y:y+h, x:x+w]
if face_crop.size == 0:
continue
emb = get_embedding(face_crop)
verify_and_unlock(emb)
# 绘制框
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
cv2.imshow("Face Access", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
finally:
picam2.stop()
cv2.destroyAllWindows()
GPIO.cleanup()
if __name__ == "__main__":
main()
人脸注册脚本(单独运行)
# register_face.py - 运行前需先执行此脚本录入授权人脸
import cv2
import numpy as np
import tflite_runtime.interpreter as tflite
from picamera2 import Picamera2
RECOG_MODEL = "facenet.tflite"
RECOG_SIZE = 112
FACE_DB = "face_db.npy"
interp = tflite.Interpreter(model_path=RECOG_MODEL)
interp.allocate_tensors()
in_d = interp.get_input_details()
out_d = interp.get_output_details()
picam2 = Picamera2()
config = picam2.create_preview_configuration(main={"size": (640, 480), "format": "RGB888"})
picam2.configure(config)
picam2.start()
print("请正对摄像头,按空格键采集人脸,按q退出")
embeddings = []
if os.path.exists(FACE_DB):
embeddings = list(np.load(FACE_DB))
while True:
frame = picam2.capture_array()
cv2.imshow("Register", frame)
key = cv2.waitKey(1) & 0xFF
if key == ord(' '):
# 简单居中裁剪作为人脸(实际应使用检测模型)
h, w = frame.shape[:2]
cx, cy = w//2, h//2
size = 150
face = frame[cy-size:cy+size, cx-size:cx+size]
if face.size == 0:
continue
img = cv2.resize(face, (RECOG_SIZE, RECOG_SIZE))
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB).astype(np.float32) / 127.5 - 1.0
img = np.expand_dims(img, axis=0)
interp.set_tensor(in_d[0]['index'], img)
interp.invoke()
emb = interp.get_tensor(out_d[0]['index'])[0]
emb = emb / np.linalg.norm(emb)
embeddings.append(emb)
print(f"已采集 {len(embeddings)} 个样本")
elif key == ord('q'):
break
if embeddings:
np.save(FACE_DB, np.array(embeddings))
print(f"特征库已保存: {FACE_DB}, 共 {len(embeddings)} 人")
picam2.stop()
cv2.destroyAllWindows()
运行与验证
- 首次运行:先执行
python3 register_face.py录入授权人脸(按空格采集,按q保存退出)。 - 启动门禁系统:
python3 face_access.py - 验证通过条件:
- 摄像头画面实时显示,授权人脸被绿色框标记;
- 授权人员靠近时,终端输出“✅ 授权通过”,继电器吸合(可听到“咔嗒”声),电磁锁开锁5秒;
- 未注册人脸靠近时,输出“❌ 未授权”,继电器不动作;
- 按q键退出程序,GPIO引脚被清理。
常见问题
- 检测模型输出格式不匹配:不同检测模型输出张量顺序不同,需根据实际模型调整
detect_faces()中的解析逻辑。 - 识别准确率低:确保注册时人脸光照均匀、正对摄像头;可降低
AUTH_THRESHOLD至0.65~0.70,但会降低安全性。 - GPIO无响应:检查继电器IN端是否接GPIO17,且继电器供电为5V;部分继电器模块需跳线设置高电平触发。