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()

运行与验证

  1. 首次运行:先执行 python3 register_face.py 录入授权人脸(按空格采集,按q保存退出)。
  2. 启动门禁系统:python3 face_access.py
  3. 验证通过条件:
    - 摄像头画面实时显示,授权人脸被绿色框标记;
    - 授权人员靠近时,终端输出“✅ 授权通过”,继电器吸合(可听到“咔嗒”声),电磁锁开锁5秒;
    - 未注册人脸靠近时,输出“❌ 未授权”,继电器不动作;
    - 按q键退出程序,GPIO引脚被清理。

常见问题

  • 检测模型输出格式不匹配:不同检测模型输出张量顺序不同,需根据实际模型调整detect_faces()中的解析逻辑。
  • 识别准确率低:确保注册时人脸光照均匀、正对摄像头;可降低AUTH_THRESHOLD至0.65~0.70,但会降低安全性。
  • GPIO无响应:检查继电器IN端是否接GPIO17,且继电器供电为5V;部分继电器模块需跳线设置高电平触发。