3. 人脸检测与识别
任务目标
在Jetson Nano上使用OpenCV进行人脸检测,并通过FaceNet模型提取人脸特征向量,与预注册的人脸特征库比对,实现从摄像头实时画面中识别人脸身份并标注姓名。
环境与依赖
- Jetson Nano(JetPack 4.6+,已安装CUDA)
- Python 3.6+,OpenCV(
pip install opencv-python) - TensorFlow 2.x(JetPack自带或
pip install tensorflow-gpu) - FaceNet模型文件(
facenet_model.h5,可从公开来源下载,输入尺寸160×160,输出128维向量) - Haar级联分类器(OpenCV自带
haarcascade_frontalface_default.xml)
实现思路
程序分为两个阶段:注册阶段——对每个待识别的人拍摄一张照片,提取128维特征向量并保存到本地文件;识别阶段——打开摄像头,对每一帧检测人脸,提取特征并与注册库中所有向量计算欧氏距离,取最小距离且低于阈值者判定为对应身份,否则标记为“Unknown”。
关键完整代码
import cv2
import numpy as np
import os
import tensorflow as tf
from scipy.spatial.distance import euclidean
# ========== 配置 ==========
MODEL_PATH = "facenet_model.h5"
CASCADE_PATH = "haarcascade_frontalface_default.xml"
REGISTER_DIR = "registered_faces" # 存放注册照片
FEATURES_FILE = "face_features.npy" # 保存特征向量
NAMES_FILE = "face_names.npy" # 保存对应姓名
THRESHOLD = 0.9 # 欧氏距离阈值,小于此值判定为同一人
# ========== 加载模型 ==========
model = tf.keras.models.load_model(MODEL_PATH)
face_cascade = cv2.CascadeClassifier(CASCADE_PATH)
def preprocess_face(face_img):
"""将检测到的人脸区域缩放为160x160并归一化到[-1,1]"""
resized = cv2.resize(face_img, (160, 160))
normalized = (resized.astype(np.float32) - 127.5) / 128.0
return np.expand_dims(normalized, axis=0)
def get_embedding(face_img):
"""提取128维人脸特征向量"""
preprocessed = preprocess_face(face_img)
embedding = model.predict(preprocessed, verbose=0)[0]
return embedding / np.linalg.norm(embedding) # L2归一化
# ========== 注册新身份 ==========
def register_person(name, image_path):
"""从一张照片中提取特征并保存"""
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.1, 5)
if len(faces) == 0:
print(f"错误:{image_path} 中未检测到人脸")
return False
x, y, w, h = max(faces, key=lambda f: f[2]*f[3]) # 取最大人脸
face = img[y:y+h, x:x+w]
embedding = get_embedding(face)
# 加载已有特征库
if os.path.exists(FEATURES_FILE):
features = np.load(FEATURES_FILE)
names = np.load(NAMES_FILE, allow_pickle=True)
else:
features = np.empty((0, 128))
names = np.array([])
# 追加新特征
features = np.vstack([features, embedding.reshape(1, -1)])
names = np.append(names, name)
np.save(FEATURES_FILE, features)
np.save(NAMES_FILE, names)
print(f"已注册 {name},特征库现有 {len(names)} 人")
return True
# ========== 识别摄像头画面 ==========
def recognize_face(face_img):
"""返回识别结果 (姓名, 距离)"""
if not os.path.exists(FEATURES_FILE):
return ("Unknown", None)
embedding = get_embedding(face_img)
features = np.load(FEATURES_FILE)
names = np.load(NAMES_FILE, allow_pickle=True)
distances = [euclidean(embedding, f) for f in features]
min_idx = np.argmin(distances)
min_dist = distances[min_idx]
if min_dist < THRESHOLD:
return (names[min_idx], min_dist)
else:
return ("Unknown", min_dist)
def run_camera():
"""主循环:打开摄像头实时识别"""
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("错误:无法打开摄像头")
return
print("按 'q' 退出,按 's' 保存当前帧")
while True:
ret, frame = cap.read()
if not ret:
break
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.1, 5, minSize=(80, 80))
for (x, y, w, h) in faces:
face = frame[y:y+h, x:x+w]
name, dist = recognize_face(face)
# 绘制框和姓名
color = (0, 255, 0) if name != "Unknown" else (0, 0, 255)
cv2.rectangle(frame, (x, y), (x+w, y+h), color, 2)
label = f"{name} ({dist:.2f})" if dist else name
cv2.putText(frame, label, (x, y-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)
cv2.imshow("Face Recognition", frame)
key = cv2.waitKey(1) & 0xFF
if key == ord('q'):
break
elif key == ord('s'):
cv2.imwrite("captured_frame.jpg", frame)
print("已保存当前帧")
cap.release()
cv2.destroyAllWindows()
# ========== 命令行入口 ==========
if __name__ == "__main__":
import sys
if len(sys.argv) > 1 and sys.argv[1] == "register":
# 用法: python face_recognition.py register 姓名 照片路径
if len(sys.argv) != 4:
print("用法: python face_recognition.py register <姓名> <照片路径>")
else:
register_person(sys.argv[2], sys.argv[3])
else:
run_camera()
运行与验证
- 注册身份:准备每个待识别者的正面照片(光线充足、人脸清晰),执行:
注册成功后控制台输出“已注册 xxx,特征库现有 N 人”。python face_recognition.py register "张三" /path/to/zhangsan.jpg python face_recognition.py register "李四" /path/to/lisi.jpg - 启动实时识别:
摄像头窗口打开后,将已注册的人脸对准摄像头,绿色框内应显示对应姓名;未注册人脸显示红色框和“Unknown”。python face_recognition.py - 验证通过标准:
- 已注册人员在正常光照下能被正确标注姓名(距离值小于0.9);
- 未注册人员显示为“Unknown”;
- 程序不崩溃,帧率可接受(Jetson Nano上约5-10 FPS属正常)。
常见问题
- 检测不到人脸:确认摄像头可用(
ls /dev/video0),光线充足,人脸正对镜头。 - 识别错误率高:注册照片与摄像头画面差异过大时,重新注册更接近实际场景的照片;或适当调低
THRESHOLD(如0.8)提高判定严格度。 - 模型加载失败:确认
facenet_model.h5路径正确,且为TensorFlow 2.x可加载的完整模型文件。