first program
This commit is contained in:
commit
053eaa3107
39
README.md
Normal file
39
README.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# 基于YOLOv5-Face的人脸识别系统
|
||||||
|
|
||||||
|
这个系统使用YOLOv5-Face进行人脸检测和特征提取,使用SQLite数据库存储人脸特征以便实时比对,在人脸识别成功后会提供语音提示。
|
||||||
|
|
||||||
|
## 功能特点
|
||||||
|
|
||||||
|
- 人脸检测和识别
|
||||||
|
- 人脸特征提取和存储
|
||||||
|
- 人脸特征比对
|
||||||
|
- 识别成功语音提示
|
||||||
|
- 本地SQLite数据库
|
||||||
|
|
||||||
|
## 安装依赖
|
||||||
|
|
||||||
|
```
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用说明
|
||||||
|
|
||||||
|
1. 运行人脸注册程序添加人脸到数据库:
|
||||||
|
```
|
||||||
|
python register_face.py
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 运行实时人脸识别程序:
|
||||||
|
```
|
||||||
|
python face_recognition_app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
- `face_recognition_app.py`: 主应用程序
|
||||||
|
- `register_face.py`: 人脸注册程序
|
||||||
|
- `face_db.py`: 数据库操作模块
|
||||||
|
- `face_utils.py`: 人脸处理工具函数
|
||||||
|
- `voice_prompt.py`: 语音提示模块
|
||||||
|
- `models/`: YOLOv5-Face模型文件夹
|
||||||
|
- `data/`: 存储数据库和临时文件
|
BIN
__pycache__/face_db.cpython-310.pyc
Normal file
BIN
__pycache__/face_db.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/face_utils.cpython-310.pyc
Normal file
BIN
__pycache__/face_utils.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/voice_prompt.cpython-310.pyc
Normal file
BIN
__pycache__/voice_prompt.cpython-310.pyc
Normal file
Binary file not shown.
BIN
data/face_database.db
Normal file
BIN
data/face_database.db
Normal file
Binary file not shown.
BIN
dlib-19.22.99-cp310-cp310-win_amd64.whl
Normal file
BIN
dlib-19.22.99-cp310-cp310-win_amd64.whl
Normal file
Binary file not shown.
123
face_db.py
Normal file
123
face_db.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import sqlite3
|
||||||
|
import numpy as np
|
||||||
|
import pickle
|
||||||
|
import os
|
||||||
|
|
||||||
|
class FaceDatabase:
|
||||||
|
def __init__(self, db_path='data/face_database.db'):
|
||||||
|
"""初始化人脸数据库
|
||||||
|
|
||||||
|
Args:
|
||||||
|
db_path: 数据库文件路径
|
||||||
|
"""
|
||||||
|
# 确保数据目录存在
|
||||||
|
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
||||||
|
|
||||||
|
self.db_path = db_path
|
||||||
|
self.conn = sqlite3.connect(db_path)
|
||||||
|
self.create_tables()
|
||||||
|
|
||||||
|
def create_tables(self):
|
||||||
|
"""创建必要的数据表"""
|
||||||
|
cursor = self.conn.cursor()
|
||||||
|
|
||||||
|
# 创建人员表
|
||||||
|
cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS persons (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
register_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
# 创建人脸特征表
|
||||||
|
cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS face_features (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
person_id INTEGER NOT NULL,
|
||||||
|
feature_vector BLOB NOT NULL,
|
||||||
|
FOREIGN KEY (person_id) REFERENCES persons (id)
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
self.conn.commit()
|
||||||
|
|
||||||
|
def add_person(self, name):
|
||||||
|
"""添加人员信息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: 人员姓名
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
person_id: 新增人员的ID
|
||||||
|
"""
|
||||||
|
cursor = self.conn.cursor()
|
||||||
|
cursor.execute("INSERT INTO persons (name) VALUES (?)", (name,))
|
||||||
|
self.conn.commit()
|
||||||
|
return cursor.lastrowid
|
||||||
|
|
||||||
|
def add_face_feature(self, person_id, feature_vector):
|
||||||
|
"""添加人脸特征向量
|
||||||
|
|
||||||
|
Args:
|
||||||
|
person_id: 人员ID
|
||||||
|
feature_vector: 人脸特征向量(numpy数组)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
feature_id: 新增特征的ID
|
||||||
|
"""
|
||||||
|
# 将numpy数组序列化为二进制数据
|
||||||
|
serialized_feature = pickle.dumps(feature_vector)
|
||||||
|
|
||||||
|
cursor = self.conn.cursor()
|
||||||
|
cursor.execute(
|
||||||
|
"INSERT INTO face_features (person_id, feature_vector) VALUES (?, ?)",
|
||||||
|
(person_id, serialized_feature)
|
||||||
|
)
|
||||||
|
self.conn.commit()
|
||||||
|
return cursor.lastrowid
|
||||||
|
|
||||||
|
def get_all_features(self):
|
||||||
|
"""获取所有人脸特征
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list of tuples: [(person_id, name, feature_vector), ...]
|
||||||
|
"""
|
||||||
|
cursor = self.conn.cursor()
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT p.id, p.name, f.feature_vector
|
||||||
|
FROM persons p
|
||||||
|
JOIN face_features f ON p.id = f.person_id
|
||||||
|
""")
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for row in cursor.fetchall():
|
||||||
|
person_id, name, serialized_feature = row
|
||||||
|
# 反序列化特征向量
|
||||||
|
feature_vector = pickle.loads(serialized_feature)
|
||||||
|
results.append((person_id, name, feature_vector))
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def get_person_by_id(self, person_id):
|
||||||
|
"""根据ID获取人员信息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
person_id: 人员ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict or None: 人员信息
|
||||||
|
"""
|
||||||
|
cursor = self.conn.cursor()
|
||||||
|
cursor.execute("SELECT id, name FROM persons WHERE id = ?", (person_id,))
|
||||||
|
result = cursor.fetchone()
|
||||||
|
|
||||||
|
if result:
|
||||||
|
return {"id": result[0], "name": result[1]}
|
||||||
|
return None
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""关闭数据库连接"""
|
||||||
|
if self.conn:
|
||||||
|
self.conn.close()
|
||||||
|
self.conn = None
|
176
face_recognition_app.py
Normal file
176
face_recognition_app.py
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
import cv2
|
||||||
|
import time
|
||||||
|
import argparse
|
||||||
|
import numpy as np
|
||||||
|
from face_utils import FaceDetector, FaceRecognizer, draw_face_box
|
||||||
|
from face_db import FaceDatabase
|
||||||
|
from voice_prompt import VoicePrompt
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
"""解析命令行参数"""
|
||||||
|
parser = argparse.ArgumentParser(description='人脸识别应用')
|
||||||
|
parser.add_argument('--camera', type=int, default=0, help='摄像头索引')
|
||||||
|
parser.add_argument('--confidence', type=float, default=0.5, help='人脸检测置信度阈值')
|
||||||
|
parser.add_argument('--similarity', type=float, default=0.6, help='人脸相似度阈值')
|
||||||
|
parser.add_argument('--display_width', type=int, default=640, help='显示窗口宽度')
|
||||||
|
parser.add_argument('--display_height', type=int, default=480, help='显示窗口高度')
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""人脸识别应用主函数"""
|
||||||
|
args = parse_args()
|
||||||
|
|
||||||
|
# 初始化组件
|
||||||
|
print("正在初始化人脸识别系统...")
|
||||||
|
voice = VoicePrompt()
|
||||||
|
detector = FaceDetector(conf_thres=args.confidence)
|
||||||
|
recognizer = FaceRecognizer(detector=detector, similarity_threshold=args.similarity)
|
||||||
|
db = FaceDatabase()
|
||||||
|
|
||||||
|
# 加载已知人脸特征
|
||||||
|
known_faces = db.get_all_features()
|
||||||
|
if not known_faces:
|
||||||
|
print("警告: 数据库中没有注册的人脸,请先运行注册程序")
|
||||||
|
voice.speak("数据库中没有注册的人脸,请先运行注册程序", block=True)
|
||||||
|
else:
|
||||||
|
print(f"已加载 {len(known_faces)} 个人脸特征")
|
||||||
|
|
||||||
|
# 打开摄像头
|
||||||
|
cap = cv2.VideoCapture(args.camera)
|
||||||
|
if not cap.isOpened():
|
||||||
|
print("无法打开摄像头")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 设置摄像头分辨率
|
||||||
|
cap.set(cv2.CAP_PROP_FRAME_WIDTH, args.display_width)
|
||||||
|
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, args.display_height)
|
||||||
|
|
||||||
|
print("系统初始化完成,开始人脸识别...")
|
||||||
|
voice.speak("人脸识别系统已启动", block=True)
|
||||||
|
|
||||||
|
# 跟踪识别状态,避免重复语音提示
|
||||||
|
recognized_persons = {} # {person_id: last_recognition_time}
|
||||||
|
recognition_cooldown = 5.0 # 同一个人识别成功后的冷却时间(秒)
|
||||||
|
|
||||||
|
# 绘制UI相关
|
||||||
|
font = cv2.FONT_HERSHEY_SIMPLEX
|
||||||
|
font_scale = 0.6
|
||||||
|
line_thickness = 2
|
||||||
|
fps_history = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
# 计时(用于计算FPS)
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
# 读取摄像头帧
|
||||||
|
ret, frame = cap.read()
|
||||||
|
if not ret:
|
||||||
|
print("无法获取摄像头画面")
|
||||||
|
break
|
||||||
|
|
||||||
|
# 镜像翻转以便更直观
|
||||||
|
frame = cv2.flip(frame, 1)
|
||||||
|
|
||||||
|
# 创建显示帧
|
||||||
|
display_frame = frame.copy()
|
||||||
|
|
||||||
|
# 检测人脸
|
||||||
|
faces = detector.detect_faces(frame)
|
||||||
|
|
||||||
|
# 处理每个检测到的人脸
|
||||||
|
for face_box in faces:
|
||||||
|
# 识别人脸
|
||||||
|
result = recognizer.identify_face(frame, known_faces, face_box[:4])
|
||||||
|
|
||||||
|
# 如果识别成功
|
||||||
|
if result:
|
||||||
|
person_id, name, similarity = result
|
||||||
|
|
||||||
|
# 绘制人脸框和标签(绿色表示识别成功)
|
||||||
|
draw_face_box(display_frame, face_box, name, similarity, color=(0, 255, 0))
|
||||||
|
|
||||||
|
# 检查是否需要播放语音提示(避免频繁重复)
|
||||||
|
current_time = time.time()
|
||||||
|
if (person_id not in recognized_persons or
|
||||||
|
current_time - recognized_persons[person_id] > recognition_cooldown):
|
||||||
|
|
||||||
|
# 更新识别时间
|
||||||
|
recognized_persons[person_id] = current_time
|
||||||
|
|
||||||
|
# 播放语音提示
|
||||||
|
voice.speak(f"您好,{name}", block=False)
|
||||||
|
print(f"识别成功: {name} (相似度: {similarity:.2f})")
|
||||||
|
else:
|
||||||
|
# 未识别的人脸使用红色框
|
||||||
|
draw_face_box(display_frame, face_box, "未知", color=(0, 0, 255))
|
||||||
|
|
||||||
|
# 计算并显示FPS
|
||||||
|
end_time = time.time()
|
||||||
|
fps = 1.0 / (end_time - start_time)
|
||||||
|
fps_history.append(fps)
|
||||||
|
if len(fps_history) > 30:
|
||||||
|
fps_history.pop(0)
|
||||||
|
avg_fps = sum(fps_history) / len(fps_history)
|
||||||
|
|
||||||
|
cv2.putText(
|
||||||
|
display_frame,
|
||||||
|
f"FPS: {avg_fps:.1f}",
|
||||||
|
(10, 30),
|
||||||
|
font,
|
||||||
|
font_scale,
|
||||||
|
(0, 255, 255),
|
||||||
|
line_thickness
|
||||||
|
)
|
||||||
|
|
||||||
|
# 显示数据库信息
|
||||||
|
cv2.putText(
|
||||||
|
display_frame,
|
||||||
|
f"数据库: {len(known_faces)} 个人脸",
|
||||||
|
(10, 60),
|
||||||
|
font,
|
||||||
|
font_scale,
|
||||||
|
(0, 255, 255),
|
||||||
|
line_thickness
|
||||||
|
)
|
||||||
|
|
||||||
|
# 显示操作指南
|
||||||
|
cv2.putText(
|
||||||
|
display_frame,
|
||||||
|
"按ESC退出, 按R刷新数据库",
|
||||||
|
(10, display_frame.shape[0] - 10),
|
||||||
|
font,
|
||||||
|
font_scale,
|
||||||
|
(255, 255, 255),
|
||||||
|
line_thickness
|
||||||
|
)
|
||||||
|
|
||||||
|
# 显示图像
|
||||||
|
cv2.imshow("人脸识别系统", display_frame)
|
||||||
|
|
||||||
|
# 按键处理
|
||||||
|
key = cv2.waitKey(1) & 0xFF
|
||||||
|
|
||||||
|
# 按ESC键退出
|
||||||
|
if key == 27:
|
||||||
|
break
|
||||||
|
|
||||||
|
# 按R键刷新数据库
|
||||||
|
elif key == ord('r'):
|
||||||
|
print("正在刷新人脸数据库...")
|
||||||
|
known_faces = db.get_all_features()
|
||||||
|
print(f"已刷新: 加载了 {len(known_faces)} 个人脸特征")
|
||||||
|
voice.speak("已刷新人脸数据库", block=False)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"发生错误: {e}")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# 释放资源
|
||||||
|
cap.release()
|
||||||
|
cv2.destroyAllWindows()
|
||||||
|
db.close()
|
||||||
|
print("人脸识别系统已关闭")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
224
face_utils.py
Normal file
224
face_utils.py
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
import os
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
import face_recognition
|
||||||
|
from pathlib import Path
|
||||||
|
import time
|
||||||
|
|
||||||
|
class FaceDetector:
|
||||||
|
def __init__(self, conf_thres=0.5):
|
||||||
|
"""初始化人脸检测器,使用face_recognition库
|
||||||
|
|
||||||
|
Args:
|
||||||
|
conf_thres: 置信度阈值
|
||||||
|
"""
|
||||||
|
self.conf_thres = conf_thres
|
||||||
|
|
||||||
|
def _load_model(self):
|
||||||
|
"""不再需要加载模型,使用face_recognition库"""
|
||||||
|
print("使用face_recognition库进行人脸检测,无需加载模型")
|
||||||
|
pass
|
||||||
|
|
||||||
|
def detect_faces(self, image):
|
||||||
|
"""使用face_recognition库检测图像中的人脸
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image: 输入图像 (numpy数组,BGR格式)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: 检测到的人脸边界框列表 [x1, y1, x2, y2, confidence]
|
||||||
|
"""
|
||||||
|
boxes = []
|
||||||
|
|
||||||
|
# 使用face_recognition库检测人脸位置
|
||||||
|
# 将BGR图像转换为RGB格式(face_recognition需要RGB格式)
|
||||||
|
rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
||||||
|
|
||||||
|
# 使用face_recognition检测人脸位置
|
||||||
|
face_locations = face_recognition.face_locations(rgb_image)
|
||||||
|
|
||||||
|
# 将检测结果转换为所需的格式 [x1, y1, x2, y2, confidence]
|
||||||
|
for face_location in face_locations:
|
||||||
|
# face_location格式为(top, right, bottom, left)
|
||||||
|
top, right, bottom, left = face_location
|
||||||
|
|
||||||
|
# 转换为[x1, y1, x2, y2, confidence]格式
|
||||||
|
# 由于face_recognition不提供置信度,我们使用1.0作为默认值
|
||||||
|
boxes.append([left, top, right, bottom, 1.0])
|
||||||
|
|
||||||
|
print(f"检测到 {len(boxes)} 个人脸")
|
||||||
|
|
||||||
|
# 如果未检测到人脸,尝试使用OpenCV的人脸检测器作为备份
|
||||||
|
if len(boxes) == 0:
|
||||||
|
try:
|
||||||
|
print("使用OpenCV备用人脸检测...")
|
||||||
|
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
|
||||||
|
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
||||||
|
faces = face_cascade.detectMultiScale(gray, 1.1, 5)
|
||||||
|
|
||||||
|
for (x, y, w, h) in faces:
|
||||||
|
boxes.append([x, y, x+w, y+h, 0.8]) # 使用默认置信度0.8
|
||||||
|
print(f"OpenCV检测到 {len(faces)} 个人脸")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"OpenCV备用人脸检测失败: {e}")
|
||||||
|
|
||||||
|
return boxes
|
||||||
|
|
||||||
|
class FaceRecognizer:
|
||||||
|
def __init__(self, detector=None, similarity_threshold=0.6):
|
||||||
|
"""初始化人脸识别器
|
||||||
|
|
||||||
|
Args:
|
||||||
|
detector: 人脸检测器实例,如果为None则创建默认检测器
|
||||||
|
similarity_threshold: 人脸相似度阈值
|
||||||
|
"""
|
||||||
|
self.detector = detector if detector else FaceDetector()
|
||||||
|
self.similarity_threshold = similarity_threshold
|
||||||
|
# 使用OpenCV内置的人脸识别器
|
||||||
|
self.face_recognizer = cv2.face.LBPHFaceRecognizer_create()
|
||||||
|
self.face_size = (128, 128) # 标准化人脸大小
|
||||||
|
|
||||||
|
def extract_face_features(self, image, box=None):
|
||||||
|
"""提取人脸特征
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image: 输入图像
|
||||||
|
box: 可选的人脸边界框 [x1, y1, x2, y2],如果未提供则自动检测
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
numpy.ndarray: 人脸特征向量
|
||||||
|
"""
|
||||||
|
# 如果没有提供边界框,使用检测器检测人脸
|
||||||
|
if box is None:
|
||||||
|
boxes = self.detector.detect_faces(image)
|
||||||
|
if not boxes:
|
||||||
|
return None
|
||||||
|
box = boxes[0][:4] # 使用第一个检测到的人脸
|
||||||
|
|
||||||
|
# 裁剪人脸区域
|
||||||
|
x1, y1, x2, y2 = box
|
||||||
|
face_img = image[y1:y2, x1:x2]
|
||||||
|
|
||||||
|
# 转换为灰度图像
|
||||||
|
face_gray = cv2.cvtColor(face_img, cv2.COLOR_BGR2GRAY)
|
||||||
|
|
||||||
|
# 调整大小为标准尺寸
|
||||||
|
face_resized = cv2.resize(face_gray, self.face_size)
|
||||||
|
|
||||||
|
# 使用直方图均衡化增强对比度
|
||||||
|
face_normalized = cv2.equalizeHist(face_resized)
|
||||||
|
|
||||||
|
# 简单特征提取:将图像展平为向量
|
||||||
|
# 在实际应用中,可以使用更复杂的特征提取方法,如HOG或深度特征
|
||||||
|
feature_vector = face_normalized.flatten().astype(np.float32)
|
||||||
|
|
||||||
|
# 对特征向量进行标准化
|
||||||
|
if np.linalg.norm(feature_vector) > 0:
|
||||||
|
feature_vector = feature_vector / np.linalg.norm(feature_vector)
|
||||||
|
|
||||||
|
return feature_vector
|
||||||
|
|
||||||
|
def compare_faces(self, known_feature, unknown_feature):
|
||||||
|
"""比较两个人脸特征的相似度
|
||||||
|
|
||||||
|
Args:
|
||||||
|
known_feature: 已知人脸特征
|
||||||
|
unknown_feature: 待比对人脸特征
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: 相似度 (0-1之间,越大表示越相似)
|
||||||
|
"""
|
||||||
|
if known_feature is None or unknown_feature is None:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
# 计算欧氏距离
|
||||||
|
face_distance = np.linalg.norm(known_feature - unknown_feature)
|
||||||
|
# 将距离转换为相似度
|
||||||
|
similarity = 1.0 / (1.0 + face_distance)
|
||||||
|
|
||||||
|
return similarity
|
||||||
|
|
||||||
|
def identify_face(self, image, known_faces, box=None):
|
||||||
|
"""识别人脸
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image: 输入图像
|
||||||
|
known_faces: 已知人脸特征列表 [(person_id, name, feature_vector), ...]
|
||||||
|
box: 可选的人脸边界框
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple or None: (person_id, name, similarity) 或 None(如果未匹配)
|
||||||
|
"""
|
||||||
|
# 提取人脸特征
|
||||||
|
face_feature = self.extract_face_features(image, box)
|
||||||
|
if face_feature is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 寻找最佳匹配
|
||||||
|
best_match = None
|
||||||
|
best_similarity = 0
|
||||||
|
|
||||||
|
for person_id, name, known_feature in known_faces:
|
||||||
|
similarity = self.compare_faces(known_feature, face_feature)
|
||||||
|
|
||||||
|
if similarity > best_similarity:
|
||||||
|
best_similarity = similarity
|
||||||
|
best_match = (person_id, name, similarity)
|
||||||
|
|
||||||
|
# 如果最佳匹配的相似度低于阈值,返回None
|
||||||
|
if best_match and best_match[2] >= self.similarity_threshold:
|
||||||
|
return best_match
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def draw_face_box(image, box, name=None, similarity=None, color=(0, 255, 0), thickness=2):
|
||||||
|
"""在图像上绘制人脸边界框和标签
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image: 输入图像
|
||||||
|
box: 人脸边界框 [x1, y1, x2, y2, ...]
|
||||||
|
name: 可选的名称标签
|
||||||
|
similarity: 可选的相似度标签
|
||||||
|
color: 边界框颜色 (BGR格式)
|
||||||
|
thickness: 边界框线条粗细
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
numpy.ndarray: 绘制了边界框的图像
|
||||||
|
"""
|
||||||
|
x1, y1, x2, y2 = box[:4]
|
||||||
|
|
||||||
|
# 绘制边界框
|
||||||
|
cv2.rectangle(image, (x1, y1), (x2, y2), color, thickness)
|
||||||
|
|
||||||
|
# 如果提供了名称,则绘制标签
|
||||||
|
if name or similarity is not None:
|
||||||
|
label = ""
|
||||||
|
if name:
|
||||||
|
label += name
|
||||||
|
if similarity is not None:
|
||||||
|
label += f" ({similarity:.2f})"
|
||||||
|
|
||||||
|
# 设置标签背景
|
||||||
|
(label_width, label_height), baseline = cv2.getTextSize(
|
||||||
|
label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1
|
||||||
|
)
|
||||||
|
cv2.rectangle(
|
||||||
|
image,
|
||||||
|
(x1, y1 - label_height - 5),
|
||||||
|
(x1 + label_width, y1),
|
||||||
|
color,
|
||||||
|
-1 # 填充矩形
|
||||||
|
)
|
||||||
|
|
||||||
|
# 绘制标签文本
|
||||||
|
cv2.putText(
|
||||||
|
image,
|
||||||
|
label,
|
||||||
|
(x1, y1 - 5),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX,
|
||||||
|
0.5,
|
||||||
|
(0, 0, 0), # 黑色文本
|
||||||
|
1
|
||||||
|
)
|
||||||
|
|
||||||
|
return image
|
BIN
models/yolov5l.pt
Normal file
BIN
models/yolov5l.pt
Normal file
Binary file not shown.
118
register_face.py
Normal file
118
register_face.py
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import cv2
|
||||||
|
import time
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
from face_utils import FaceDetector, FaceRecognizer, draw_face_box
|
||||||
|
from face_db import FaceDatabase
|
||||||
|
from voice_prompt import VoicePrompt
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
"""解析命令行参数"""
|
||||||
|
parser = argparse.ArgumentParser(description='人脸注册程序')
|
||||||
|
parser.add_argument('--name', type=str, help='要注册的人员姓名')
|
||||||
|
parser.add_argument('--camera', type=int, default=0, help='摄像头索引')
|
||||||
|
parser.add_argument('--threshold', type=float, default=0.5, help='人脸检测置信度阈值')
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
def register_face():
|
||||||
|
"""人脸注册主函数"""
|
||||||
|
args = parse_args()
|
||||||
|
|
||||||
|
# 初始化组件
|
||||||
|
voice = VoicePrompt()
|
||||||
|
detector = FaceDetector(conf_thres=args.threshold)
|
||||||
|
recognizer = FaceRecognizer(detector=detector)
|
||||||
|
db = FaceDatabase()
|
||||||
|
|
||||||
|
# 打开摄像头
|
||||||
|
cap = cv2.VideoCapture(args.camera)
|
||||||
|
if not cap.isOpened():
|
||||||
|
print("无法打开摄像头")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 获取姓名(如果命令行没有提供,则交互输入)
|
||||||
|
name = args.name
|
||||||
|
if name is None:
|
||||||
|
name = input("请输入姓名: ")
|
||||||
|
|
||||||
|
# 创建新的人员记录
|
||||||
|
person_id = db.add_person(name)
|
||||||
|
print(f"已创建人员记录: {name} (ID: {person_id})")
|
||||||
|
voice.speak(f"开始为{name}注册人脸信息,请正视摄像头", block=True)
|
||||||
|
|
||||||
|
# 采集计数器和状态
|
||||||
|
face_count = 0
|
||||||
|
target_count = 5 # 需要采集的人脸图像数量
|
||||||
|
last_capture_time = 0
|
||||||
|
capture_interval = 1.0 # 每次采集间隔秒数
|
||||||
|
|
||||||
|
print(f"请看向摄像头,将采集{target_count}张人脸图像...")
|
||||||
|
|
||||||
|
while face_count < target_count:
|
||||||
|
# 读取摄像头帧
|
||||||
|
ret, frame = cap.read()
|
||||||
|
if not ret:
|
||||||
|
print("无法获取摄像头画面")
|
||||||
|
break
|
||||||
|
|
||||||
|
# 镜像翻转以便更直观
|
||||||
|
frame = cv2.flip(frame, 1)
|
||||||
|
|
||||||
|
# 显示帧
|
||||||
|
display_frame = frame.copy()
|
||||||
|
cv2.putText(
|
||||||
|
display_frame,
|
||||||
|
f"请直视摄像头 ({face_count}/{target_count})",
|
||||||
|
(10, 30),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX,
|
||||||
|
0.7,
|
||||||
|
(0, 255, 0),
|
||||||
|
2
|
||||||
|
)
|
||||||
|
|
||||||
|
# 检测人脸
|
||||||
|
faces = detector.detect_faces(frame)
|
||||||
|
|
||||||
|
# 如果检测到人脸
|
||||||
|
current_time = time.time()
|
||||||
|
if faces and (current_time - last_capture_time) >= capture_interval:
|
||||||
|
# 只处理最大的一个人脸
|
||||||
|
faces.sort(key=lambda x: (x[2]-x[0])*(x[3]-x[1]), reverse=True)
|
||||||
|
face_box = faces[0]
|
||||||
|
|
||||||
|
# 绘制人脸框
|
||||||
|
draw_face_box(display_frame, face_box)
|
||||||
|
|
||||||
|
# 提取人脸特征
|
||||||
|
face_feature = recognizer.extract_face_features(frame, face_box[:4])
|
||||||
|
|
||||||
|
if face_feature is not None:
|
||||||
|
# 保存人脸特征到数据库
|
||||||
|
feature_id = db.add_face_feature(person_id, face_feature)
|
||||||
|
|
||||||
|
face_count += 1
|
||||||
|
last_capture_time = current_time
|
||||||
|
print(f"已采集第 {face_count}/{target_count} 张人脸")
|
||||||
|
voice.speak(f"已完成第{face_count}次采集", block=False)
|
||||||
|
|
||||||
|
# 显示图像
|
||||||
|
cv2.imshow("人脸注册", display_frame)
|
||||||
|
|
||||||
|
# 按ESC键退出
|
||||||
|
key = cv2.waitKey(1) & 0xFF
|
||||||
|
if key == 27:
|
||||||
|
break
|
||||||
|
|
||||||
|
# 释放资源
|
||||||
|
cap.release()
|
||||||
|
cv2.destroyAllWindows()
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
if face_count >= target_count:
|
||||||
|
print(f"成功完成{name}的人脸注册!")
|
||||||
|
voice.speak(f"已完成{name}的人脸信息注册", block=True)
|
||||||
|
else:
|
||||||
|
print("人脸注册未完成")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
register_face()
|
13
requirements.txt
Normal file
13
requirements.txt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
torch>=1.7.0
|
||||||
|
torchvision>=0.8.1
|
||||||
|
opencv-python>=4.1.2
|
||||||
|
numpy>=1.18.5
|
||||||
|
Pillow>=9.0.0
|
||||||
|
PyYAML>=5.3.1
|
||||||
|
tqdm>=4.41.0
|
||||||
|
matplotlib>=3.2.2
|
||||||
|
scipy>=1.4.1
|
||||||
|
pyttsx3>=2.90
|
||||||
|
face-recognition>=1.3.0
|
||||||
|
scikit-learn>=0.24.2
|
||||||
|
sqlite3-wrapper>=0.1.2
|
BIN
ultralytics-yolov5-v7.0-411-gf4d8a84.zip
Normal file
BIN
ultralytics-yolov5-v7.0-411-gf4d8a84.zip
Normal file
Binary file not shown.
120
voice_prompt.py
Normal file
120
voice_prompt.py
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import pyttsx3
|
||||||
|
import threading
|
||||||
|
import queue
|
||||||
|
import time
|
||||||
|
|
||||||
|
class VoicePrompt:
|
||||||
|
def __init__(self, rate=150, volume=1.0, voice_id=None):
|
||||||
|
"""初始化语音提示组件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
rate: 语速
|
||||||
|
volume: 音量 (0.0 到 1.0)
|
||||||
|
voice_id: 语音ID (None表示使用默认值)
|
||||||
|
"""
|
||||||
|
self.rate = rate
|
||||||
|
self.volume = volume
|
||||||
|
self.voice_id = voice_id
|
||||||
|
|
||||||
|
# 创建语音消息队列
|
||||||
|
self.speech_queue = queue.Queue()
|
||||||
|
|
||||||
|
# 启动单独的线程来处理语音播报
|
||||||
|
self.speech_thread = threading.Thread(target=self._speech_worker, daemon=True)
|
||||||
|
self.speech_thread.start()
|
||||||
|
|
||||||
|
# 防止多次初始化
|
||||||
|
self.initialized = False
|
||||||
|
|
||||||
|
def _init_engine(self):
|
||||||
|
"""初始化语音引擎"""
|
||||||
|
if not hasattr(self, 'engine') or self.engine is None:
|
||||||
|
try:
|
||||||
|
self.engine = pyttsx3.init()
|
||||||
|
self.engine.setProperty('rate', self.rate)
|
||||||
|
self.engine.setProperty('volume', self.volume)
|
||||||
|
|
||||||
|
# 设置语音(如果提供了voice_id)
|
||||||
|
if self.voice_id:
|
||||||
|
self.engine.setProperty('voice', self.voice_id)
|
||||||
|
else:
|
||||||
|
# 尝试设置中文语音(如果有的话)
|
||||||
|
voices = self.engine.getProperty('voices')
|
||||||
|
for voice in voices:
|
||||||
|
if 'chinese' in voice.id.lower() or 'zh' in voice.id.lower():
|
||||||
|
self.engine.setProperty('voice', voice.id)
|
||||||
|
break
|
||||||
|
self.initialized = True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"初始化语音引擎失败: {e}")
|
||||||
|
|
||||||
|
def _speech_worker(self):
|
||||||
|
"""后台线程,从队列中获取文本并播报"""
|
||||||
|
# 初始化引擎(仅在这个区域创建和使用引擎)
|
||||||
|
self._init_engine()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# 从队列中读取文本
|
||||||
|
text = self.speech_queue.get()
|
||||||
|
if text == "__EXIT__": # 退出信号
|
||||||
|
break
|
||||||
|
|
||||||
|
if not self.initialized:
|
||||||
|
self._init_engine()
|
||||||
|
|
||||||
|
# 仅当初始化成功时播放语音
|
||||||
|
if self.initialized:
|
||||||
|
try:
|
||||||
|
self.engine.say(text)
|
||||||
|
self.engine.runAndWait()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"语音播报错误: {e}")
|
||||||
|
# 重新初始化引擎
|
||||||
|
try:
|
||||||
|
self.engine = None
|
||||||
|
self._init_engine()
|
||||||
|
except Exception as e2:
|
||||||
|
print(f"重新初始化引擎失败: {e2}")
|
||||||
|
time.sleep(1) # 避免循环过快
|
||||||
|
|
||||||
|
# 通知队列任务完成
|
||||||
|
self.speech_queue.task_done()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"语音工作线程错误: {e}")
|
||||||
|
time.sleep(0.5) # 防止错误时CPU资源过度消耗
|
||||||
|
|
||||||
|
def speak(self, text, block=False):
|
||||||
|
"""将要播报的文本添加到队列
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: 要播报的文本
|
||||||
|
block: 是否等待播报完成
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 将文本加入队列
|
||||||
|
self.speech_queue.put(text)
|
||||||
|
|
||||||
|
# 如果是阻塞模式,等待这个任务完成
|
||||||
|
if block:
|
||||||
|
self.speech_queue.join()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"添加语音文本到队列失败: {e}")
|
||||||
|
|
||||||
|
def get_available_voices(self):
|
||||||
|
"""获取所有可用的语音
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: 可用语音列表
|
||||||
|
"""
|
||||||
|
voices = self.engine.getProperty('voices')
|
||||||
|
return [(voice.id, voice.name) for voice in voices]
|
||||||
|
|
||||||
|
# 测试代码
|
||||||
|
if __name__ == "__main__":
|
||||||
|
voice = VoicePrompt()
|
||||||
|
print("可用语音:")
|
||||||
|
for voice_id, name in voice.get_available_voices():
|
||||||
|
print(f" - {name} ({voice_id})")
|
||||||
|
|
||||||
|
voice.speak("您好,欢迎使用人脸识别系统", block=True)
|
Loading…
x
Reference in New Issue
Block a user