Files
micro_servers/aibox/skills/facerec.py
T
2026-07-27 21:39:53 +08:00

399 lines
16 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""人脸检测和识别工具 - 检测图像中的人脸并保存裁剪的人脸图像。"""
import os
import sys
import argparse
import cv2
import numpy as np
from datetime import datetime
try:
import face_recognition
except ImportError:
print("❌ 缺少依赖库: face_recognition。请运行: pip install face_recognition")
sys.exit(1)
class FaceDetector:
"""人脸检测和识别工具"""
def __init__(self):
self.faces_dir = os.path.expanduser("~/.aibox/faces")
self.known_faces_dir = os.path.expanduser("~/.aibox/known_faces")
self._ensure_dirs()
self.known_face_encodings = []
self.known_face_names = []
self._load_known_faces()
def _ensure_dirs(self):
os.makedirs(self.faces_dir, exist_ok=True)
os.makedirs(self.known_faces_dir, exist_ok=True)
def _load_known_faces(self):
"""加载已知人脸编码"""
self.known_face_encodings = []
self.known_face_names = []
if os.path.exists(self.known_faces_dir):
for filename in os.listdir(self.known_faces_dir):
if filename.endswith(('.jpg', '.jpeg', '.png')):
filepath = os.path.join(self.known_faces_dir, filename)
try:
image = face_recognition.load_image_file(filepath)
encodings = face_recognition.face_encodings(image)
if encodings:
name = os.path.splitext(filename)[0]
self.known_face_encodings.append(encodings[0])
self.known_face_names.append(name)
except Exception as e:
print(f"加载已知人脸失败 {filename}: {e}", file=sys.stderr)
def detect_faces(self, image_path: str, save_cropped: bool = True,
recognize: bool = True, tolerance: float = 0.6) -> str:
"""检测图像中的人脸并保存裁剪的人脸"""
try:
if not os.path.exists(image_path):
return f"❌ 错误:文件不存在 - {image_path}"
image = face_recognition.load_image_file(image_path)
face_locations = face_recognition.face_locations(image)
if not face_locations:
return "📭 未检测到人脸"
face_encodings = face_recognition.face_encodings(image, face_locations)
results = []
saved_files = []
for i, (top, right, bottom, left) in enumerate(face_locations):
face_data = {
"index": i + 1,
"position": {"top": top, "right": right, "bottom": bottom, "left": left},
"size": {"width": right - left, "height": bottom - top}
}
if recognize and self.known_face_encodings and i < len(face_encodings):
matches = face_recognition.compare_faces(
self.known_face_encodings,
face_encodings[i],
tolerance=tolerance
)
if True in matches:
match_index = matches.index(True)
name = self.known_face_names[match_index]
face_data["name"] = name
face_data["match"] = True
distances = face_recognition.face_distance(
self.known_face_encodings,
face_encodings[i]
)
face_data["similarity"] = f"{1 - distances[match_index]:.3f}"
else:
face_data["name"] = "Unknown"
face_data["match"] = False
else:
face_data["name"] = f"Face_{i+1}"
face_data["match"] = False
if save_cropped:
image_array = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
margin = 20
top_crop = max(0, top - margin)
bottom_crop = min(image_array.shape[0], bottom + margin)
left_crop = max(0, left - margin)
right_crop = min(image_array.shape[1], right + margin)
face_crop = image_array[top_crop:bottom_crop, left_crop:right_crop]
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
safe_name = face_data['name'].replace(' ', '_')
filename = f"{timestamp}_face_{i+1}_{safe_name}.jpg"
filepath = os.path.join(self.faces_dir, filename)
cv2.imwrite(filepath, face_crop)
face_data["saved_file"] = filename
saved_files.append(filename)
results.append(face_data)
output = []
output.append(f"✅ 检测到 {len(face_locations)} 个人脸")
output.append(f"📁 人脸图像保存位置: {self.faces_dir}")
output.append("")
for face in results:
output.append(f"人脸 #{face['index']}:")
output.append(f" 位置: 左={face['position']['left']}, 上={face['position']['top']}, "
f"右={face['position']['right']}, 下={face['position']['bottom']}")
output.append(f" 大小: {face['size']['width']}x{face['size']['height']} 像素")
output.append(f" 识别结果: {face['name']}")
if 'similarity' in face:
output.append(f" 相似度: {face['similarity']}")
if 'saved_file' in face:
output.append(f" 保存文件: {face['saved_file']}")
output.append("")
if saved_files:
output.append("📋 保存的文件列表:")
for f in saved_files:
output.append(f" • {f}")
return "\n".join(output)
except Exception as e:
return f"❌ 人脸检测失败: {str(e)}"
def list_saved_faces(self, limit: int = 20) -> str:
"""列出已保存的人脸图像"""
try:
files = []
if os.path.exists(self.faces_dir):
for filename in sorted(os.listdir(self.faces_dir), reverse=True):
if filename.endswith(('.jpg', '.jpeg', '.png')):
files.append(filename)
if len(files) >= limit:
break
if files:
output = [f"📁 已保存的人脸图像 (最近 {len(files)} 张):"]
for f in files:
output.append(f" • {f}")
output.append(f"\n📂 保存位置: {self.faces_dir}")
return "\n".join(output)
else:
return "📭 尚未保存任何人脸图像"
except Exception as e:
return f"❌ 列出失败: {str(e)}"
def list_known_faces(self) -> str:
"""列出已知人脸库中的人脸"""
try:
faces = []
if os.path.exists(self.known_faces_dir):
for filename in os.listdir(self.known_faces_dir):
if filename.endswith(('.jpg', '.jpeg', '.png')):
name = os.path.splitext(filename)[0]
faces.append(name)
if faces:
output = [f"👥 已知人脸库 (共 {len(faces)} 人):"]
for name in faces:
output.append(f" • {name}")
output.append(f"\n📂 数据库位置: {self.known_faces_dir}")
return "\n".join(output)
else:
return "📭 已知人脸库为空"
except Exception as e:
return f"❌ 列出失败: {str(e)}"
def add_known_face(self, image_path: str, person_name: str) -> str:
"""添加已知人脸到数据库"""
try:
if not os.path.exists(image_path):
return f"❌ 文件不存在: {image_path}"
image = face_recognition.load_image_file(image_path)
face_locations = face_recognition.face_locations(image)
if not face_locations:
return "❌ 未检测到人脸,无法添加"
if len(face_locations) > 1:
return f"⚠️ 检测到 {len(face_locations)} 个人脸,请使用单人脸图片"
filepath = os.path.join(self.known_faces_dir, f"{person_name}.jpg")
if os.path.exists(filepath):
os.remove(filepath)
image_array = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
top, right, bottom, left = face_locations[0]
margin = 20
top_crop = max(0, top - margin)
bottom_crop = min(image_array.shape[0], bottom + margin)
left_crop = max(0, left - margin)
right_crop = min(image_array.shape[1], right + margin)
face_crop = image_array[top_crop:bottom_crop, left_crop:right_crop]
cv2.imwrite(filepath, face_crop)
self._load_known_faces()
return f"✅ 已添加已知人脸: {person_name}\n📁 保存位置: {filepath}"
except Exception as e:
return f"❌ 添加失败: {str(e)}"
def remove_known_face(self, person_name: str) -> str:
"""从已知人脸库中删除人脸"""
try:
filepath = os.path.join(self.known_faces_dir, f"{person_name}.jpg")
if not os.path.exists(filepath):
for filename in os.listdir(self.known_faces_dir):
if filename.endswith(('.jpg', '.jpeg', '.png')):
name = os.path.splitext(filename)[0]
if name.lower() == person_name.lower():
filepath = os.path.join(self.known_faces_dir, filename)
break
else:
return f"❌ 未找到已知人脸: {person_name}"
os.remove(filepath)
self._load_known_faces()
return f"✅ 已删除已知人脸: {person_name}"
except Exception as e:
return f"❌ 删除失败: {str(e)}"
def get_face_info(self, person_name: str) -> str:
"""获取已知人脸信息"""
try:
filepath = os.path.join(self.known_faces_dir, f"{person_name}.jpg")
if not os.path.exists(filepath):
for filename in os.listdir(self.known_faces_dir):
if filename.endswith(('.jpg', '.jpeg', '.png')):
name = os.path.splitext(filename)[0]
if name.lower() == person_name.lower():
filepath = os.path.join(self.known_faces_dir, filename)
person_name = name
break
else:
return f"❌ 未找到人脸: {person_name}"
file_size = os.path.getsize(filepath)
modified_time = datetime.fromtimestamp(os.path.getmtime(filepath))
output = []
output.append(f"👤 人脸信息: {person_name}")
output.append(f"📁 文件路径: {filepath}")
output.append(f"📊 文件大小: {file_size} 字节 ({file_size/1024:.1f}KB)")
output.append(f"🕐 最后修改: {modified_time.strftime('%Y-%m-%d %H:%M:%S')}")
return "\n".join(output)
except Exception as e:
return f"❌ 获取人脸信息失败: {str(e)}"
def main():
parser = argparse.ArgumentParser(
description="人脸检测和识别工具 - 检测图像中的人脸并保存裁剪的人脸图像。",
epilog="""
使用示例:
# 检测人脸
face-detector --action detect --image-path photo.jpg
# 检测人脸(不保存裁剪)
face-detector --action detect --image-path photo.jpg --no-save
# 检测人脸(不进行识别)
face-detector --action detect --image-path photo.jpg --no-recognize
# 列出已保存的人脸图像
face-detector --action list-faces --limit 10
# 列出已知人脸库
face-detector --action list-known
# 添加已知人脸
face-detector --action add-known --image-path face.jpg --person-name "张三"
# 删除已知人脸
face-detector --action remove-known --person-name "张三"
# 查看人脸信息
face-detector --action get-info --person-name "张三"
目录结构:
~/.aibox/faces/ - 人脸图像保存位置
~/.aibox/known_faces/ - 已知人脸数据库
需要安装:
pip install face_recognition opencv-python
"""
)
parser.add_argument("--action", "-a", required=True,
choices=["detect", "list-faces", "list-known", "add-known", "remove-known", "get-info"],
help="操作类型")
# 检测参数
parser.add_argument("--image-path", "-i", help="图片文件路径")
parser.add_argument("--no-save", action="store_true", help="不保存裁剪的人脸图像")
parser.add_argument("--no-recognize", action="store_true", help="不进行人脸识别")
parser.add_argument("--tolerance", "-t", type=float, default=0.6, help="识别容差(默认: 0.6)")
# 列表参数
parser.add_argument("--limit", "-l", type=int, default=20, help="列出文件数量限制(默认: 20)")
# 已知人脸参数
parser.add_argument("--person-name", "-n", help="人名标识")
args = parser.parse_args()
detector = FaceDetector()
try:
if args.action == "detect":
if not args.image_path:
print("❌ 需要提供 --image-path 参数")
sys.exit(1)
result = detector.detect_faces(
image_path=args.image_path,
save_cropped=not args.no_save,
recognize=not args.no_recognize,
tolerance=args.tolerance
)
print(result)
elif args.action == "list-faces":
print(detector.list_saved_faces(args.limit))
elif args.action == "list-known":
print(detector.list_known_faces())
elif args.action == "add-known":
if not args.image_path or not args.person_name:
print("❌ 需要提供 --image-path 和 --person-name 参数")
sys.exit(1)
result = detector.add_known_face(args.image_path, args.person_name)
print(result)
elif args.action == "remove-known":
if not args.person_name:
print("❌ 需要提供 --person-name 参数")
sys.exit(1)
result = detector.remove_known_face(args.person_name)
print(result)
elif args.action == "get-info":
if not args.person_name:
print("❌ 需要提供 --person-name 参数")
sys.exit(1)
result = detector.get_face_info(args.person_name)
print(result)
except Exception as e:
print(f"❌ 执行错误: {str(e)}")
sys.exit(1)
if __name__ == "__main__":
main()