已开启
new: 新建文件 1班20.py #102
new: 新建文件 1班20.py #102
已开启
mmahao创建于 2025年12月29日
1 个文件变更+510-0
@@ -0,0 +1,510 @@
1+# face_recognition_system.py
2+import cv2
3+import numpy as np
4+import os
5+import pickle
6+import time
7+import dlib
8+from sklearn.svm import SVC
9+from sklearn.preprocessing import LabelEncoder
10+from sklearn.model_selection import train_test_split
11+from sklearn.metrics import classification_report
12+import face_recognition
13+from imutils import paths
14+import warnings
15+warnings.filterwarnings('ignore')
16+ 
17+class FaceRecognizer:
18+ def __init__(self, method='cnn', tolerance=0.6, model_save_path='models'):
19+ self.method = method
20+ self.tolerance = tolerance
21+ self.model_save_path = model_save_path
22+
23+ # 创建必要的目录
24+ os.makedirs(model_save_path, exist_ok=True)
25+ os.makedirs('dataset', exist_ok=True)
26+ os.makedirs('output', exist_ok=True)
27+
28+ # 初始化变量
29+ self.known_face_encodings = []
30+ self.known_face_names = []
31+ self.face_classifier = None
32+ self.label_encoder = LabelEncoder()
33+
34+ def create_dataset(self, name, num_samples=20):
35+ print(f"[INFO] 开始采集 {name} 的人脸数据...")
36+
37+ # 创建个人文件夹
38+ person_path = os.path.join('dataset', name)
39+ os.makedirs(person_path, exist_ok=True)
40+
41+ # 初始化摄像头
42+ cap = cv2.VideoCapture(0)
43+ if not cap.isOpened():
44+ print("[ERROR] 无法打开摄像头")
45+ return
46+
47+ face_cascade = cv2.CascadeClassifier(
48+ cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
49+ )
50+
51+ count = 0
52+ while count < num_samples:
53+ ret, frame = cap.read()
54+ if not ret:
55+ continue
56+
57+ # 转换为灰度图
58+ gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
59+
60+ # 人脸检测
61+ faces = face_cascade.detectMultiScale(
62+ gray,
63+ scaleFactor=1.1,
64+ minNeighbors=5,
65+ minSize=(30, 30)
66+ )
67+
68+ for (x, y, w, h) in faces:
69+ # 绘制矩形框
70+ cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
71+
72+ # 保存人脸图片
73+ face_img = frame[y:y+h, x:x+w]
74+ face_img = cv2.resize(face_img, (160, 160))
75+
76+ # 每隔5帧保存一次
77+ if count % 5 == 0:
78+ img_path = os.path.join(person_path, f"{name}_{count}.jpg")
79+ cv2.imwrite(img_path, face_img)
80+ count += 1
81+ print(f"[INFO] 已保存 {count}/{num_samples} 张图片")
82+
83+ # 显示视频
84+ cv2.putText(frame, f"采集进度: {count}/{num_samples}",
85+ (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
86+ cv2.imshow('创建数据集 - 按ESC退出', frame)
87+
88+ if cv2.waitKey(1) & 0xFF == 27: # ESC键退出
89+ break
90+
91+ cap.release()
92+ cv2.destroyAllWindows()
93+ print(f"[INFO] {name} 的数据采集完成")
94+
95+ def extract_face_encodings(self, dataset_path='dataset'):
96+ image_paths = list(paths.list_images(dataset_path))
97+
98+ if len(image_paths) == 0:
99+ print("[ERROR] 数据集中没有找到图片")
100+ return [], []
101+
102+ encodings = []
103+ names = []
104+
105+ for (i, image_path) in enumerate(image_paths):
106+ print(f"[INFO] 处理图片 {i+1}/{len(image_paths)}")
107+
108+ # 从路径中提取人名
109+ name = image_path.split(os.path.sep)[-2]
110+
111+ # 加载图片
112+ image = cv2.imread(image_path)
113+ rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
114+
115+ # 检测人脸
116+ face_locations = face_recognition.face_locations(
117+ rgb, model=self.method
118+ )
119+
120+ if len(face_locations) > 0:
121+ # 提取人脸编码
122+ face_encoding = face_recognition.face_encodings(
123+ rgb, face_locations
124+ )[0]
125+
126+ encodings.append(face_encoding)
127+ names.append(name)
128+
129+ print(f"[INFO] 成功提取 {len(encodings)} 个人脸编码")
130+ return encodings, names
131+
132+ def train_classifier(self, save_model=True):
133+ # 提取人脸编码
134+ encodings, names = self.extract_face_encodings()
135+
136+ if len(encodings) == 0:
137+ print("[ERROR] 没有可用的数据进行训练")
138+ return
139+
140+ # 编码标签
141+ labels = self.label_encoder.fit_transform(names)
142+
143+ # 使用SVM分类器
144+ self.face_classifier = SVC(kernel='linear', probability=True, C=1.0)
145+ self.face_classifier.fit(encodings, labels)
146+
147+ # 保存已知的人脸编码
148+ self.known_face_encodings = encodings
149+ self.known_face_names = names
150+
151+ if save_model:
152+ # 保存模型
153+ model_data = {
154+ 'encodings': encodings,
155+ 'names': names,
156+ 'classifier': self.face_classifier,
157+ 'label_encoder': self.label_encoder
158+ }
159+
160+ model_path = os.path.join(self.model_save_path, 'face_recognizer.pkl')
161+ with open(model_path, 'wb') as f:
162+ pickle.dump(model_data, f)
163+
164+ print(f"[INFO] 模型已保存到 {model_path}")
165+
166+ print("[INFO] 分类器训练完成")
167+
168+ def load_model(self, model_path='models/face_recognizer.pkl'):
169+ if not os.path.exists(model_path):
170+ print(f"[ERROR] 模型文件不存在: {model_path}")
171+ return False
172+
173+ print("[INFO] 加载模型...")
174+ with open(model_path, 'rb') as f:
175+ model_data = pickle.load(f)
176+
177+ self.known_face_encodings = model_data['encodings']
178+ self.known_face_names = model_data['names']
179+ self.face_classifier = model_data['classifier']
180+ self.label_encoder = model_data['label_encoder']
181+
182+ print(f"[INFO] 模型加载成功,包含 {len(self.known_face_names)} 个人的数据")
183+ return True
184+
185+ def recognize_faces(self, frame, draw_landmarks=False):
186+ if len(self.known_face_encodings) == 0:
187+ return frame, []
188+
189+ # 调整图片大小以提高处理速度
190+ small_frame = cv2.resize(frame, (0, 0), fx=0.5, fy=0.5)
191+ rgb_small_frame = cv2.cvtColor(small_frame, cv2.COLOR_BGR2RGB)
192+
193+ # 检测人脸位置
194+ face_locations = face_recognition.face_locations(
195+ rgb_small_frame, model='hog'
196+ )
197+
198+ # 提取人脸编码
199+ face_encodings = face_recognition.face_encodings(
200+ rgb_small_frame, face_locations
201+ )
202+
203+ face_names = []
204+ face_boxes = []
205+
206+ for face_encoding, face_location in zip(face_encodings, face_locations):
207+ # 比较人脸
208+ matches = face_recognition.compare_faces(
209+ self.known_face_encodings,
210+ face_encoding,
211+ tolerance=self.tolerance
212+ )
213+
214+ # 计算距离
215+ face_distances = face_recognition.face_distance(
216+ self.known_face_encodings,
217+ face_encoding
218+ )
219+
220+ # 找到最佳匹配
221+ best_match_index = np.argmin(face_distances)
222+
223+ if matches[best_match_index]:
224+ name = self.known_face_names[best_match_index]
225+ distance = face_distances[best_match_index]
226+
227+ if distance < self.tolerance:
228+ label = f"{name} ({distance:.2f})"
229+ else:
230+ label = "Unknown"
231+ else:
232+ label = "Unknown"
233+
234+ # 调整坐标(因为之前缩小了图片)
235+ (top, right, bottom, left) = face_location
236+ top *= 2; right *= 2; bottom *= 2; left *= 2
237+
238+ face_names.append(label)
239+ face_boxes.append((left, top, right, bottom))
240+
241+ return self.draw_face_info(frame, face_boxes, face_names, draw_landmarks)
242+
243+ def draw_face_info(self, frame, face_boxes, face_names, draw_landmarks=False):
244+ for (left, top, right, bottom), name in zip(face_boxes, face_names):
245+ # 绘制人脸框
246+ color = (0, 255, 0) if "Unknown" not in name else (0, 0, 255)
247+ cv2.rectangle(frame, (left, top), (right, bottom), color, 2)
248+
249+ # 绘制姓名标签
250+ label_bg = (left, top - 35, right - left, 35)
251+ cv2.rectangle(frame,
252+ (label_bg[0], label_bg[1]),
253+ (label_bg[0] + label_bg[2], label_bg[1] + label_bg[3]),
254+ color, -1)
255+
256+ cv2.putText(frame, name, (left + 6, top - 6),
257+ cv2.FONT_HERSHEY_DUPLEX, 0.8, (255, 255, 255), 1)
258+
259+ if draw_landmarks:
260+ # 绘制面部特征点
261+ face_image = frame[top:bottom, left:right]
262+ if face_image.size > 0:
263+ rgb_face = cv2.cvtColor(face_image, cv2.COLOR_BGR2RGB)
264+ landmarks = face_recognition.face_landmarks(rgb_face)
265+
266+ for face_landmarks in landmarks:
267+ for facial_feature in face_landmarks.values():
268+ for point in facial_feature:
269+ px, py = point
270+ cv2.circle(face_image, (px, py), 2, (255, 0, 0), -1)
271+
272+ return frame, list(zip(face_names, face_boxes))
273+
274+ def realtime_recognition(self, show_fps=True, record_video=False):
275+ print("[INFO] 启动实时人脸识别...")
276+ print("[INFO] 按 'q' 键退出")
277+ print("[INFO] 按 's' 键截图")
278+
279+ cap = cv2.VideoCapture(0)
280+ if not cap.isOpened():
281+ print("[ERROR] 无法打开摄像头")
282+ return
283+
284+ # 设置视频参数
285+ cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
286+ cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
287+
288+ # 视频录制设置
289+ writer = None
290+ if record_video:
291+ fourcc = cv2.VideoWriter_fourcc(*'XVID')
292+ timestamp = time.strftime("%Y%m%d_%H%M%S")
293+ output_path = f"output/recording_{timestamp}.avi"
294+ writer = cv2.VideoWriter(output_path, fourcc, 20.0, (640, 480))
295+
296+ fps_time = time.time()
297+ frame_count = 0
298+ fps = 0
299+
300+ while True:
301+ ret, frame = cap.read()
302+ if not ret:
303+ break
304+
305+ # 人脸识别
306+ processed_frame, results = self.recognize_faces(frame)
307+
308+ # 计算FPS
309+ frame_count += 1
310+ if time.time() - fps_time >= 1.0:
311+ fps = frame_count
312+ frame_count = 0
313+ fps_time = time.time()
314+
315+ if show_fps:
316+ cv2.putText(processed_frame, f"FPS: {fps}",
317+ (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
318+
319+ # 显示检测到的人数
320+ cv2.putText(processed_frame, f"人数: {len(results)}",
321+ (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
322+
323+ # 显示视频
324+ cv2.imshow('人脸识别系统 - 按q退出', processed_frame)
325+
326+ # 录制视频
327+ if writer is not None:
328+ writer.write(processed_frame)
329+
330+ # 键盘控制
331+ key = cv2.waitKey(1) & 0xFF
332+ if key == ord('q'):
333+ break
334+ elif key == ord('s'):
335+ # 截图保存
336+ timestamp = time.strftime("%Y%m%d_%H%M%S")
337+ screenshot_path = f"output/screenshot_{timestamp}.jpg"
338+ cv2.imwrite(screenshot_path, processed_frame)
339+ print(f"[INFO] 截图已保存: {screenshot_path}")
340+
341+ # 清理资源
342+ cap.release()
343+ if writer is not None:
344+ writer.release()
345+ cv2.destroyAllWindows()
346+ print("[INFO] 实时识别已停止")
347+ 
348+class AdvancedFaceAnalyzer:
349+
350+ def __init__(self):
351+ self.detector = dlib.get_frontal_face_detector()
352+ self.predictor = dlib.shape_predictor(
353+ 'shape_predictor_68_face_landmarks.dat'
354+ )
355+
356+ def download_landmark_model(self):
357+ import urllib.request
358+ model_url = "http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2"
359+ model_path = 'shape_predictor_68_face_landmarks.dat.bz2'
360+
361+ if not os.path.exists('shape_predictor_68_face_landmarks.dat'):
362+ print("[INFO] 下载面部特征点模型...")
363+ urllib.request.urlretrieve(model_url, model_path)
364+
365+ import bz2
366+ with bz2.BZ2File(model_path, 'rb') as fr, \
367+ open('shape_predictor_68_face_landmarks.dat', 'wb') as fw:
368+ fw.write(fr.read())
369+
370+ os.remove(model_path)
371+ print("[INFO] 模型下载完成")
372+
373+ def analyze_face_attributes(self, image):
374+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
375+ faces = self.detector(gray)
376+
377+ results = []
378+ for face in faces:
379+ landmarks = self.predictor(gray, face)
380+
381+ # 计算各种特征
382+ attributes = {
383+ 'face_rect': (face.left(), face.top(), face.right(), face.bottom()),
384+ 'eye_aspect_ratio': self.eye_aspect_ratio(landmarks),
385+ 'mouth_aspect_ratio': self.mouth_aspect_ratio(landmarks),
386+ 'head_pose': self.estimate_head_pose(landmarks)
387+ }
388+ results.append(attributes)
389+
390+ return results
391+
392+ def eye_aspect_ratio(self, landmarks):
393+ # 左眼
394+ left_eye = landmarks.part(36), landmarks.part(37), landmarks.part(38), \
395+ landmarks.part(39), landmarks.part(40), landmarks.part(41)
396+
397+ # 右眼
398+ right_eye = landmarks.part(42), landmarks.part(43), landmarks.part(44), \
399+ landmarks.part(45), landmarks.part(46), landmarks.part(47)
400+
401+ def eye_ratio(eye):
402+ # 计算垂直距离
403+ A = np.linalg.norm(np.array([eye[1].x, eye[1].y]) -
404+ np.array([eye[5].x, eye[5].y]))
405+ B = np.linalg.norm(np.array([eye[2].x, eye[2].y]) -
406+ np.array([eye[4].x, eye[4].y]))
407+
408+ # 计算水平距离
409+ C = np.linalg.norm(np.array([eye[0].x, eye[0].y]) -
410+ np.array([eye[3].x, eye[3].y]))
411+
412+ return (A + B) / (2.0 * C)
413+
414+ return (eye_ratio(left_eye) + eye_ratio(right_eye)) / 2.0
415+
416+ def mouth_aspect_ratio(self, landmarks):
417+ """计算嘴巴纵横比(用于检测是否张嘴)"""
418+ mouth_points = [
419+ landmarks.part(i) for i in range(48, 68)
420+ ]
421+
422+ # 计算垂直距离
423+ vertical = np.linalg.norm(
424+ np.array([mouth_points[2].x, mouth_points[2].y]) -
425+ np.array([mouth_points[10].x, mouth_points[10].y])
426+ )
427+
428+ # 计算水平距离
429+ horizontal = np.linalg.norm(
430+ np.array([mouth_points[0].x, mouth_points[0].y]) -
431+ np.array([mouth_points[6].x, mouth_points[6].y])
432+ )
433+
434+ return vertical / horizontal
435+
436+ def estimate_head_pose(self, landmarks):
437+ """估计头部姿态"""
438+ # 简化的头部姿态估计
439+ image_points = np.array([
440+ [landmarks.part(30).x, landmarks.part(30).y], # 鼻尖
441+ [landmarks.part(8).x, landmarks.part(8).y], # 下巴
442+ [landmarks.part(36).x, landmarks.part(36).y], # 左眼眼角
443+ [landmarks.part(45).x, landmarks.part(45).y], # 右眼眼角
444+ [landmarks.part(48).x, landmarks.part(48).y], # 左嘴角
445+ [landmarks.part(54).x, landmarks.part(54).y] # 右嘴角
446+ ], dtype="double")
447+
448+ # 3D模型点
449+ model_points = np.array([
450+ (0.0, 0.0, 0.0), # 鼻尖
451+ (0.0, -330.0, -65.0), # 下巴
452+ (-165.0, 170.0, -135.0), # 左眼眼角
453+ (165.0, 170.0, -135.0), # 右眼眼角
454+ ])
455+
456+ # 这里简化处理,实际需要相机参数
457+ return "head_pose_estimation_placeholder"
458+ 
459+def main():
460+ """主函数"""
461+ print("=" * 50)
462+ print("人脸识别系统")
463+ print("=" * 50)
464+
465+ # 创建识别器
466+ recognizer = FaceRecognizer(method='hog', tolerance=0.6)
467+
468+ while True:
469+ print("\n请选择操作:")
470+ print("1. 创建新的人脸数据集")
471+ print("2. 训练人脸识别模型")
472+ print("3. 实时人脸识别")
473+ print("4. 加载已有模型并识别")
474+ print("5. 退出")
475+
476+ choice = input("请输入选项 (1-5): ").strip()
477+
478+ if choice == '1':
479+ name = input("请输入姓名: ").strip()
480+ if name:
481+ recognizer.create_dataset(name, num_samples=20)
482+
483+ elif choice == '2':
484+ recognizer.train_classifier(save_model=True)
485+
486+ elif choice == '3':
487+ if not recognizer.load_model():
488+ print("[INFO] 未找到模型,请先训练模型")
489+ continue
490+
491+ show_fps = input("显示FPS? (y/n): ").lower() == 'y'
492+ record = input("录制视频? (y/n): ").lower() == 'y'
493+ recognizer.realtime_recognition(show_fps=show_fps, record_video=record)
494+
495+ elif choice == '4':
496+ if recognizer.load_model():
497+ show_fps = input("显示FPS? (y/n): ").lower() == 'y'
498+ record = input("录制视频? (y/n): ").lower() == 'y'
499+ recognizer.realtime_recognition(show_fps=show_fps, record_video=record)
500+
501+ elif choice == '5':
502+ print("[INFO] 退出系统")
503+ break
504+
505+ else:
506+ print("[ERROR] 无效的选项,请重新输入")
507+ 
508+if __name__ == "__main__":
509+
510+ main()