first program

This commit is contained in:
zhugaoliang 2025-04-07 08:08:39 +08:00
commit 053eaa3107
14 changed files with 813 additions and 0 deletions

39
README.md Normal file
View 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/`: 存储数据库和临时文件

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
data/face_database.db Normal file

Binary file not shown.

Binary file not shown.

123
face_db.py Normal file
View 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
View 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
View 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

Binary file not shown.

118
register_face.py Normal file
View 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
View 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

Binary file not shown.

120
voice_prompt.py Normal file
View 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)