diff --git a/README.md b/README.md index 3095414..544dc6b 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ ### 1. 人脸识别 - **实时检测**:基于 CompreFace API 进行高精度人脸检测 +- **正脸检测**:使用姿态检测技术,只识别正面人脸,过滤侧脸、抬头或低头 - **身份识别**:自动识别员工和访客,支持相似度阈值配置 - **防重复识别**:智能冷却机制,避免短时间内重复识别同一人 - **质量评估**:使用 Laplacian 方差评估图像清晰度 @@ -128,6 +129,15 @@ camera: height: 720 fps: 30 +# 人脸检测配置 +face_detection: + frame_interval: 10 # 检测帧间隔(每N帧检测一次) + quality_threshold: 10 # 图像质量阈值(Laplacian方差) + min_face_size: 80 # 最小人脸尺寸(像素) + face_present_duration: 2.0 # 持续出现时长(秒)才触发识别 + max_yaw: 30.0 # 最大允许的偏航角(度),超过此角度视为侧脸 + max_pitch: 30.0 # 最大允许的俯仰角(度),超过此角度视为抬头或低头 + # 人脸识别配置 face_recognition: recognition_cooldown: 20.0 # 识别冷却时间(秒) @@ -290,6 +300,8 @@ kanda-robot-facial-recognition/ - `quality_threshold`: 图像质量阈值,建议 10-50 - `min_face_size`: 最小人脸尺寸(像素),建议 60-100 - `face_present_duration`: 持续出现时长(秒),建议 1.5-3.0 +- `max_yaw`: 最大允许的偏航角(度),超过此角度视为侧脸,建议 20-40 +- `max_pitch`: 最大允许的俯仰角(度),超过此角度视为抬头或低头,建议 20-40 - `stranger_threshold`: 陌生人阈值,建议 0.95-0.99 - `recognition_cooldown`: 识别冷却时间(秒),建议 15-30 @@ -377,7 +389,14 @@ camera: 3. 确保光线充足 4. 在 CompreFace 中添加更多训练照片 -### Q6: 系统服务无法启动 +### Q6: 侧脸也被识别了 +**A:** +1. 调整 `max_yaw` 和 `max_pitch` 参数,降低角度阈值 +2. 默认设置为 ±30 度,可改为 ±20 度提高严格程度 +3. 检查 CompreFace 服务是否支持 pose 插件 +4. 查看日志中的角度检查信息进行调试 + +### Q7: 系统服务无法启动 **A:** 1. 检查服务文件路径配置 2. 确认 Python 环境和依赖 @@ -455,6 +474,11 @@ python preview_qrcode.py ## 更新日志 +### v1.1.0 (2025-12-18) +- ✅ 新增正脸检测功能,基于姿态检测技术过滤侧脸 +- ✅ 添加人脸角度阈值配置(max_yaw, max_pitch) +- ✅ 优化人脸识别准确性,适用于门禁场景 + ### v1.0.0 (2025-12-03) - ✅ 完整的人脸检测和识别功能 - ✅ WebSocket 双向通信 diff --git a/config.yaml b/config.yaml index ea10028..9a76305 100644 --- a/config.yaml +++ b/config.yaml @@ -42,6 +42,8 @@ face_detection: quality_threshold: 10 # 图像质量阈值(Laplacian方差) min_face_size: 80 # 最小人脸尺寸(像素) face_present_duration: 2.0 # 持续出现时长(秒)才触发识别 + max_yaw: 30.0 # 最大允许的偏航角(度),超过此角度视为侧脸 + max_pitch: 30.0 # 最大允许的俯仰角(度),超过此角度视为抬头或低头 # 人脸识别配置 face_recognition: diff --git a/face_rec.py b/face_rec.py index 6b2a8ef..2136081 100644 --- a/face_rec.py +++ b/face_rec.py @@ -683,16 +683,16 @@ class FaceRecognitionSystem: try: import screeninfo screen = screeninfo.get_monitors()[0] - canvas_width = int(screen.width * 0.8) - canvas_height = int(screen.height * 0.8) - x_pos = (screen.width - canvas_width) // 2 - y_pos = (screen.height - canvas_height) // 2 + canvas_width = int(screen.width) + canvas_height = int(screen.height) + x_pos = 0 + y_pos = 0 except Exception as e: self.logger.warning(f"无法获取屏幕信息,使用默认尺寸: {e}") - canvas_width = max(qr_width * 2, 1400) - canvas_height = max(qr_height, 900) - x_pos = 100 - y_pos = 60 + canvas_width = max(qr_width * 2, 1920) + canvas_height = max(qr_height, 1080) + x_pos = 0 + y_pos = 0 rendered = self._build_qrcode_instruction_canvas(qr_image, (canvas_width, canvas_height)) if rendered is None: @@ -708,6 +708,9 @@ class FaceRecognitionSystem: cv2.resizeWindow(self.qrcode_window_name, canvas_width, canvas_height) cv2.moveWindow(self.qrcode_window_name, x_pos, y_pos) + # 设置窗口全屏显示 + cv2.setWindowProperty(self.qrcode_window_name, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN) + cv2.imshow(self.qrcode_window_name, rendered) # 设置窗口置顶 @@ -1242,19 +1245,40 @@ class FaceRecognitionSystem: # 将帧编码为JPEG _, img_encoded = cv2.imencode('.jpg', frame) - # 调用CompreFace检测API - result = self.detection_service.detect(img_encoded.tobytes()) + # 调用CompreFace检测API,启用pose插件 + result = self.detection_service.detect( + img_encoded.tobytes(), + face_plugins="pose" + ) if result and 'result' in result and len(result['result']) > 0: faces = result['result'] # 过滤掉太小的人脸 min_size = self.config['face_detection']['min_face_size'] - valid_faces = [ - face for face in faces - if face['box']['x_max'] - face['box']['x_min'] >= min_size - and face['box']['y_max'] - face['box']['y_min'] >= min_size - ] + valid_faces = [] + + # 获取角度阈值 + max_yaw = self.config['face_detection'].get('max_yaw', 30.0) + max_pitch = self.config['face_detection'].get('max_pitch', 30.0) + + for face in faces: + # 检查人脸尺寸 + face_width = face['box']['x_max'] - face['box']['x_min'] + face_height = face['box']['y_max'] - face['box']['y_min'] + + if face_width >= min_size and face_height >= min_size: + # 检查人脸角度 + pose = face.get('pose', {}) + yaw = pose.get('yaw', 0.0) + pitch = pose.get('pitch', 0.0) + + # 判断是否为正脸 + if abs(yaw) <= max_yaw and abs(pitch) <= max_pitch: + valid_faces.append(face) + self.logger.debug(f"人脸角度检查通过: yaw={yaw:.1f}°, pitch={pitch:.1f}°") + else: + self.logger.debug(f"人脸角度超出范围,跳过: yaw={yaw:.1f}°(阈值±{max_yaw}°), pitch={pitch:.1f}°(阈值±{max_pitch}°)") if valid_faces: # 返回第一个(最大的)人脸 diff --git a/preview_qrcode.py b/preview_qrcode.py index d727d19..5c6f1f5 100644 --- a/preview_qrcode.py +++ b/preview_qrcode.py @@ -326,8 +326,8 @@ def main(): cv2.destroyAllWindows() # Save for verification - cv2.imwrite("preview_qrcode_sota.jpg", canvas) - print("Saved preview to preview_qrcode_sota.jpg") + cv2.imwrite("preview_qrcode_sota.png", canvas) + print("Saved preview to preview_qrcode_sota.png") if __name__ == "__main__": main() diff --git a/preview_qrcode_sota.png b/preview_qrcode_sota.png new file mode 100644 index 0000000..980756d Binary files /dev/null and b/preview_qrcode_sota.png differ diff --git a/preview_qrcode_sota.jpg b/preview_qrcode_sota_backup.jpg similarity index 100% rename from preview_qrcode_sota.jpg rename to preview_qrcode_sota_backup.jpg