已有认证识别基本功能,暂未支持高并发
This commit is contained in:
		
							
								
								
									
										0
									
								
								app/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								app/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										
											BIN
										
									
								
								app/__pycache__/__init__.cpython-310.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/__pycache__/__init__.cpython-310.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								app/__pycache__/__init__.cpython-312.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/__pycache__/__init__.cpython-312.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								app/__pycache__/api.cpython-310.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/__pycache__/api.cpython-310.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								app/__pycache__/database.cpython-310.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/__pycache__/database.cpython-310.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								app/__pycache__/main.cpython-310.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/__pycache__/main.cpython-310.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								app/__pycache__/main.cpython-312.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/__pycache__/main.cpython-312.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								app/__pycache__/schemas.cpython-310.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/__pycache__/schemas.cpython-310.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								app/__pycache__/services.cpython-310.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/__pycache__/services.cpython-310.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										107
									
								
								app/api.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								app/api.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,107 @@ | |||||||
|  |  | ||||||
|  | from fastapi import APIRouter, HTTPException, Body | ||||||
|  | from . import schemas | ||||||
|  | from .services import face_service | ||||||
|  |  | ||||||
|  | router = APIRouter() | ||||||
|  |  | ||||||
|  | @router.post("/register", response_model=schemas.RegisterResponse, summary="注册新的人脸") | ||||||
|  | async def register(request: schemas.RegisterRequest): | ||||||
|  |     """ | ||||||
|  |     为用户注册一张新的人脸。 | ||||||
|  |  | ||||||
|  |     - **功能**: 将一张图片中的人脸与一个用户ID和姓名关联起来。 | ||||||
|  |     - **校验**:  | ||||||
|  |         - 检查用户ID是否已被他人注册。 | ||||||
|  |         - 确保图片中只包含一张清晰可识别的人脸。 | ||||||
|  |     - **操作**: 如果是新用户,会创建用户记录;然后将提取到的人脸特征向量存入数据库。 | ||||||
|  |     """ | ||||||
|  |     try: | ||||||
|  |         if not request.url and not request.face_data: | ||||||
|  |             raise ValueError("必须提供图片的url或face_data。") | ||||||
|  |              | ||||||
|  |         user_info = face_service.register_new_face(request.id, request.name, request) | ||||||
|  |         return schemas.RegisterResponse(data=user_info) | ||||||
|  |          | ||||||
|  |     except ValueError as e: | ||||||
|  |         raise HTTPException(status_code=400, detail=str(e)) | ||||||
|  |     except Exception as e: | ||||||
|  |         raise HTTPException(status_code=500, detail=f"服务器内部错误: {e}") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @router.post("/detect", response_model=schemas.DetectResponse, summary="1:N 人脸识别") | ||||||
|  | async def detect(request: schemas.ImageSource = Body(...)): | ||||||
|  |     """ | ||||||
|  |     在一张图片中检测并识别所有已知的人脸。 | ||||||
|  |  | ||||||
|  |     - **功能**: 对比图片中的人脸与数据库中的所有人脸,返回匹配结果。 | ||||||
|  |     - **阈值**: 内部使用相似度阈值来判断是否匹配成功。 | ||||||
|  |     - **返回**: 返回一个列表,包含所有被识别出的人员信息、位置和置信度。 | ||||||
|  |     """ | ||||||
|  |     try: | ||||||
|  |         if not request.url and not request.face_data: | ||||||
|  |             raise ValueError("必须提供图片的url或face_data。") | ||||||
|  |          | ||||||
|  |         detected_faces = face_service.detect_faces(request) | ||||||
|  |         return schemas.DetectResponse(data=detected_faces) | ||||||
|  |  | ||||||
|  |     except ValueError as e: | ||||||
|  |         raise HTTPException(status_code=400, detail=str(e)) | ||||||
|  |     except Exception as e: | ||||||
|  |         raise HTTPException(status_code=500, detail=f"服务器内部错误: {e}") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @router.post("/verify", response_model=schemas.VerifyResponse, summary="1:1 人脸认证") | ||||||
|  | async def verify(request: schemas.VerifyRequest): | ||||||
|  |     """ | ||||||
|  |     验证一张图片中的人脸是否属于指定的用户ID。 | ||||||
|  |  | ||||||
|  |     - **功能**: 精确比对,判断“这张脸是不是这个人”。 | ||||||
|  |     - **场景**: 用于人脸登录、刷脸支付等高安全要求的场景。 | ||||||
|  |     - **返回**: 返回布尔值 `match` 表示是否匹配,以及具体的相似度 `confidence`。 | ||||||
|  |     """ | ||||||
|  |     try: | ||||||
|  |         if not request.url and not request.face_data: | ||||||
|  |             raise ValueError("必须提供图片的url或face_data。") | ||||||
|  |              | ||||||
|  |         verification_result = face_service.verify_face(request.id, request) | ||||||
|  |         return schemas.VerifyResponse(data=verification_result) | ||||||
|  |  | ||||||
|  |     except ValueError as e: | ||||||
|  |         raise HTTPException(status_code=400, detail=str(e)) | ||||||
|  |     except Exception as e: | ||||||
|  |         raise HTTPException(status_code=500, detail=f"服务器内部错误: {e}") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @router.delete("/users/{user_id}", response_model=schemas.StandardResponse, summary="删除用户") | ||||||
|  | async def delete_user(user_id: int): | ||||||
|  |     """ | ||||||
|  |     根据用户ID,从数据库中删除该用户及其所有注册的人脸信息。 | ||||||
|  |     """ | ||||||
|  |     try: | ||||||
|  |         deleted_user = face_service.delete_user(user_id) | ||||||
|  |         return schemas.StandardResponse(message=f"成功删除用户 {deleted_user['name']} (ID: {user_id})。") | ||||||
|  |     except ValueError as e: | ||||||
|  |         raise HTTPException(status_code=404, detail=str(e)) | ||||||
|  |     except Exception as e: | ||||||
|  |         raise HTTPException(status_code=500, detail=f"服务器内部错误: {e}") | ||||||
|  |  | ||||||
|  | @router.get("/users/{user_id}", response_model=schemas.RegisterResponse, summary="获取单个用户信息") | ||||||
|  | async def get_user(user_id: int): | ||||||
|  |     """ | ||||||
|  |     根据用户ID,查询并返回用户的详细信息。 | ||||||
|  |     """ | ||||||
|  |     user_info = face_service.get_user(user_id) | ||||||
|  |     if not user_info: | ||||||
|  |         raise HTTPException(status_code=404, detail=f"ID为 {user_id} 的用户不存在。") | ||||||
|  |     return schemas.RegisterResponse(data=user_info) | ||||||
|  |  | ||||||
|  | @router.get("/users", response_model=schemas.UserListResponse, summary="获取所有用户列表") | ||||||
|  | async def list_users(skip: int = 0, limit: int = 100): | ||||||
|  |     """ | ||||||
|  |     获取数据库中所有已注册用户的列表,支持分页。 | ||||||
|  |     """ | ||||||
|  |     users_list = face_service.list_all_users(skip=skip, limit=limit) | ||||||
|  |     return schemas.UserListResponse(data=users_list) | ||||||
|  |  | ||||||
|  | # 可以在这里继续添加其他API端点... | ||||||
							
								
								
									
										47
									
								
								app/database.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								app/database.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | |||||||
|  |  | ||||||
|  | import sqlite3 | ||||||
|  | from contextlib import contextmanager | ||||||
|  |  | ||||||
|  | DATABASE_URL = "E:/geminicli/face_recognition_service_v2/face_recognition.db" | ||||||
|  |  | ||||||
|  | @contextmanager | ||||||
|  | def get_db_connection(): | ||||||
|  |     """获取数据库连接,并使用上下文管理器确保连接关闭""" | ||||||
|  |     conn = sqlite3.connect(DATABASE_URL) | ||||||
|  |     conn.row_factory = sqlite3.Row | ||||||
|  |     try: | ||||||
|  |         yield conn | ||||||
|  |     finally: | ||||||
|  |         conn.close() | ||||||
|  |  | ||||||
|  | def initialize_database(): | ||||||
|  |     """初始化数据库,创建所需的表""" | ||||||
|  |     with get_db_connection() as conn: | ||||||
|  |         cursor = conn.cursor() | ||||||
|  |          | ||||||
|  |         # 创建用户表 | ||||||
|  |         cursor.execute(""" | ||||||
|  |         CREATE TABLE IF NOT EXISTS users ( | ||||||
|  |             id INTEGER PRIMARY KEY, | ||||||
|  |             name TEXT NOT NULL | ||||||
|  |         ); | ||||||
|  |         """) | ||||||
|  |          | ||||||
|  |         # 创建人脸特征表 | ||||||
|  |         cursor.execute(""" | ||||||
|  |         CREATE TABLE IF NOT EXISTS face_features ( | ||||||
|  |             feature_id INTEGER PRIMARY KEY AUTOINCREMENT, | ||||||
|  |             user_id INTEGER NOT NULL, | ||||||
|  |             embedding BLOB NOT NULL, | ||||||
|  |             FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE | ||||||
|  |         ); | ||||||
|  |         """) | ||||||
|  |          | ||||||
|  |         # 创建索引以加速查询 | ||||||
|  |         cursor.execute("CREATE INDEX IF NOT EXISTS idx_user_id ON face_features (user_id);") | ||||||
|  |          | ||||||
|  |         conn.commit() | ||||||
|  |     print("数据库初始化完成。") | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     initialize_database() | ||||||
							
								
								
									
										22
									
								
								app/main.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								app/main.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  |  | ||||||
|  | from fastapi import FastAPI | ||||||
|  | from .api import router as api_router | ||||||
|  | from .database import initialize_database | ||||||
|  |  | ||||||
|  | app = FastAPI( | ||||||
|  |     title="人脸识别服务 V2", | ||||||
|  |     description="一个健壮、可靠的人脸识别与认证API服务。", | ||||||
|  |     version="2.0.0" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | @app.on_event("startup") | ||||||
|  | def on_startup(): | ||||||
|  |     """应用启动时,初始化数据库""" | ||||||
|  |     initialize_database() | ||||||
|  |  | ||||||
|  | # 挂载API路由 | ||||||
|  | app.include_router(api_router, prefix="/api", tags=["Face Recognition"]) | ||||||
|  |  | ||||||
|  | @app.get("/", summary="服务健康检查") | ||||||
|  | def read_root(): | ||||||
|  |     return {"status": "ok", "message": "欢迎使用人脸识别服务 V2"} | ||||||
							
								
								
									
										74
									
								
								app/schemas.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								app/schemas.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,74 @@ | |||||||
|  |  | ||||||
|  | from pydantic import BaseModel, Field | ||||||
|  | from typing import Optional, List | ||||||
|  |  | ||||||
|  | # =================================================================== | ||||||
|  | #                       基础模型 (Base Models) | ||||||
|  | # =================================================================== | ||||||
|  |  | ||||||
|  | class UserInfo(BaseModel): | ||||||
|  |     """用户的基本信息""" | ||||||
|  |     id: int = Field(..., description="用户的唯一ID", example=1001) | ||||||
|  |     name: str = Field(..., description="用户的姓名", example="张三") | ||||||
|  |     registered_faces_count: int = Field(..., description="该用户已注册的人脸数量", example=2) | ||||||
|  |  | ||||||
|  | class FaceLocation(BaseModel): | ||||||
|  |     """人脸在图片中的位置和尺寸""" | ||||||
|  |     x: int = Field(..., description="人脸框左上角的X坐标") | ||||||
|  |     y: int = Field(..., description="人脸框左上角的Y坐标") | ||||||
|  |     width: int = Field(..., description="人脸框的宽度") | ||||||
|  |     height: int = Field(..., description="人脸框的高度") | ||||||
|  |  | ||||||
|  | # =================================================================== | ||||||
|  | #                       API 请求模型 (Request Models) | ||||||
|  | # =================================================================== | ||||||
|  |  | ||||||
|  | class ImageSource(BaseModel): | ||||||
|  |     """图片来源,可以是URL或Base64编码的数据""" | ||||||
|  |     url: Optional[str] = Field(None, description="图片的URL地址", example="http://example.com/image.jpg") | ||||||
|  |     face_data: Optional[str] = Field(None, description="图片的Base64编码字符串") | ||||||
|  |  | ||||||
|  | class RegisterRequest(ImageSource): | ||||||
|  |     """注册新用户的请求体""" | ||||||
|  |     id: int = Field(..., description="要注册用户的唯一ID", example=1001) | ||||||
|  |     name: str = Field(..., description="要注册用户的姓名", example="张三") | ||||||
|  |  | ||||||
|  | class VerifyRequest(ImageSource): | ||||||
|  |     """1:1人脸认证的请求体""" | ||||||
|  |     id: int = Field(..., description="要验证的用户ID", example=1001) | ||||||
|  |  | ||||||
|  | # =================================================================== | ||||||
|  | #                       API 响应模型 (Response Models) | ||||||
|  | # =================================================================== | ||||||
|  |  | ||||||
|  | class StandardResponse(BaseModel): | ||||||
|  |     """标准API响应模型""" | ||||||
|  |     code: int = Field(0, description="响应码,0为成功,非0为失败", example=0) | ||||||
|  |     message: str = Field("success", description="响应消息", example="操作成功") | ||||||
|  |     data: Optional[dict] = None | ||||||
|  |  | ||||||
|  | class UserListResponse(StandardResponse): | ||||||
|  |     """获取用户列表的响应""" | ||||||
|  |     data: List[UserInfo] | ||||||
|  |  | ||||||
|  | class RegisterResponse(StandardResponse): | ||||||
|  |     """注册成功后的响应""" | ||||||
|  |     data: UserInfo | ||||||
|  |  | ||||||
|  | class VerificationResult(BaseModel): | ||||||
|  |     """1:1认证结果""" | ||||||
|  |     match: bool = Field(..., description="是否匹配") | ||||||
|  |     confidence: float = Field(..., description="置信度 (0.0 to 1.0)") | ||||||
|  |  | ||||||
|  | class VerifyResponse(StandardResponse): | ||||||
|  |     """1:1人脸认证的响应""" | ||||||
|  |     data: VerificationResult | ||||||
|  |  | ||||||
|  | class DetectedFace(UserInfo): | ||||||
|  |     """1:N识别结果中的单个人脸信息""" | ||||||
|  |     location: FaceLocation | ||||||
|  |     confidence: float = Field(..., description="识别的置信度") | ||||||
|  |  | ||||||
|  | class DetectResponse(StandardResponse): | ||||||
|  |     """1:N人脸识别的响应""" | ||||||
|  |     data: List[DetectedFace] | ||||||
							
								
								
									
										215
									
								
								app/services.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								app/services.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,215 @@ | |||||||
|  |  | ||||||
|  | import cv2 | ||||||
|  | import numpy as np | ||||||
|  | from insightface.app import FaceAnalysis | ||||||
|  | from . import database | ||||||
|  | import base64 | ||||||
|  | import requests | ||||||
|  |  | ||||||
|  | class FaceRecognitionService: | ||||||
|  |     def __init__(self): | ||||||
|  |         # 初始化InsightFace分析器 | ||||||
|  |         self.app = FaceAnalysis(name="buffalo_l", providers=['CPUExecutionProvider']) | ||||||
|  |         self.app.prepare(ctx_id=0, det_size=(640, 640)) | ||||||
|  |  | ||||||
|  |     def _decode_image(self, image_source): | ||||||
|  |         """从URL或Base64解码图片""" | ||||||
|  |         img = None | ||||||
|  |         if image_source.face_data: | ||||||
|  |             try: | ||||||
|  |                 img_data = base64.b64decode(image_source.face_data) | ||||||
|  |                 nparr = np.frombuffer(img_data, np.uint8) | ||||||
|  |                 img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) | ||||||
|  |             except Exception as e: | ||||||
|  |                 raise ValueError(f"Base64解码失败: {e}") | ||||||
|  |         elif image_source.url: | ||||||
|  |             try: | ||||||
|  |                 response = requests.get(image_source.url, timeout=10) | ||||||
|  |                 response.raise_for_status() | ||||||
|  |                 img_array = np.asarray(bytearray(response.content), dtype=np.uint8) | ||||||
|  |                 img = cv2.imdecode(img_array, cv2.IMREAD_COLOR) | ||||||
|  |             except Exception as e: | ||||||
|  |                 raise ValueError(f"从URL获取图片失败: {e}") | ||||||
|  |          | ||||||
|  |         if img is None: | ||||||
|  |             raise ValueError("无法加载图片") | ||||||
|  |         return img | ||||||
|  |  | ||||||
|  |     def register_new_face(self, user_id: int, name: str, image_source): | ||||||
|  |         """注册新的人脸""" | ||||||
|  |         with database.get_db_connection() as conn: | ||||||
|  |             cursor = conn.cursor() | ||||||
|  |              | ||||||
|  |             # 1. 检查ID是否已存在 | ||||||
|  |             cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,)) | ||||||
|  |             user = cursor.fetchone() | ||||||
|  |              | ||||||
|  |             if user and user['name'] != name: | ||||||
|  |                 raise ValueError(f"ID {user_id} 已被 '{user['name']}' 注册,无法更改为 '{name}'。") | ||||||
|  |  | ||||||
|  |             # 2. 解码和处理图片 | ||||||
|  |             image = self._decode_image(image_source) | ||||||
|  |             faces = self.app.get(image) | ||||||
|  |              | ||||||
|  |             if not faces: | ||||||
|  |                 raise ValueError("图片中未检测到人脸。") | ||||||
|  |             if len(faces) > 1: | ||||||
|  |                 raise ValueError("注册图片中只能包含一张人脸。") | ||||||
|  |              | ||||||
|  |             embedding = faces[0].embedding | ||||||
|  |  | ||||||
|  |             # 3. 存储到数据库 | ||||||
|  |             if not user: | ||||||
|  |                 # 如果是新用户,先在users表创建记录 | ||||||
|  |                 cursor.execute("INSERT INTO users (id, name) VALUES (?, ?)", (user_id, name)) | ||||||
|  |              | ||||||
|  |             # 插入新的人脸特征 | ||||||
|  |             cursor.execute("INSERT INTO face_features (user_id, embedding) VALUES (?, ?)", (user_id, embedding.tobytes())) | ||||||
|  |              | ||||||
|  |             conn.commit() | ||||||
|  |  | ||||||
|  |             # 4. 返回用户信息 | ||||||
|  |             cursor.execute("SELECT COUNT(*) FROM face_features WHERE user_id = ?", (user_id,)) | ||||||
|  |             count = cursor.fetchone()[0] | ||||||
|  |              | ||||||
|  |             return {"id": user_id, "name": name, "registered_faces_count": count} | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def detect_faces(self, image_source): | ||||||
|  |         """1:N 人脸识别""" | ||||||
|  |         image = self._decode_image(image_source) | ||||||
|  |         detected_faces = self.app.get(image) | ||||||
|  |          | ||||||
|  |         if not detected_faces: | ||||||
|  |             return [] | ||||||
|  |  | ||||||
|  |         with database.get_db_connection() as conn: | ||||||
|  |             cursor = conn.cursor() | ||||||
|  |             cursor.execute("SELECT user_id, embedding FROM face_features") | ||||||
|  |             db_features = cursor.fetchall() | ||||||
|  |              | ||||||
|  |             if not db_features: | ||||||
|  |                 # 如果数据库为空,所有检测到的人脸都是未知的 | ||||||
|  |                 # (此处简化处理,实际可返回带位置的未知人脸列表) | ||||||
|  |                 return [] | ||||||
|  |  | ||||||
|  |             # 将数据库特征加载到Numpy数组中以便快速计算 | ||||||
|  |             db_user_ids = np.array([f['user_id'] for f in db_features]) | ||||||
|  |             db_embeddings = np.array([np.frombuffer(f['embedding'], dtype=np.float32) for f in db_features]) | ||||||
|  |  | ||||||
|  |             results = [] | ||||||
|  |             for face in detected_faces: | ||||||
|  |                 embedding = face.embedding | ||||||
|  |                  | ||||||
|  |                 # 计算与数据库中所有特征的余弦相似度 | ||||||
|  |                 # (insightface的特征是归一化的,点积等价于余弦相似度) | ||||||
|  |                 similarities = np.dot(db_embeddings, embedding) | ||||||
|  |                  | ||||||
|  |                 best_match_index = np.argmax(similarities) | ||||||
|  |                 best_similarity = similarities[best_match_index] | ||||||
|  |                  | ||||||
|  |                 # 设置一个阈值来判断是否为已知人脸 | ||||||
|  |                 # ArcFace 官方建议的阈值通常在 0.4 到 0.6 之间,这里我们用0.5 | ||||||
|  |                 RECOGNITION_THRESHOLD = 0.5 | ||||||
|  |                  | ||||||
|  |                 if best_similarity > RECOGNITION_THRESHOLD: | ||||||
|  |                     matched_user_id = db_user_ids[best_match_index] | ||||||
|  |                      | ||||||
|  |                     # 查询用户信息 | ||||||
|  |                     cursor.execute("SELECT name, (SELECT COUNT(*) FROM face_features WHERE user_id=?) FROM users WHERE id=?", (matched_user_id, matched_user_id)) | ||||||
|  |                     user_info = cursor.fetchone() | ||||||
|  |                      | ||||||
|  |                     x1, y1, x2, y2 = face.bbox.astype(int) | ||||||
|  |                     results.append({ | ||||||
|  |                         "id": matched_user_id, | ||||||
|  |                         "name": user_info['name'], | ||||||
|  |                         "registered_faces_count": user_info[2], | ||||||
|  |                         "confidence": float(best_similarity), | ||||||
|  |                         "location": {"x": x1, "y": y1, "width": x2 - x1, "height": y2 - y1} | ||||||
|  |                     }) | ||||||
|  |              | ||||||
|  |             return results | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def verify_face(self, user_id: int, image_source): | ||||||
|  |         """1:1 人脸认证""" | ||||||
|  |         # 1. 解码图片并检测人脸 | ||||||
|  |         image = self._decode_image(image_source) | ||||||
|  |         detected_faces = self.app.get(image) | ||||||
|  |  | ||||||
|  |         if not detected_faces: | ||||||
|  |             raise ValueError("图片中未检测到人脸。") | ||||||
|  |         if len(detected_faces) > 1: | ||||||
|  |             raise ValueError("用于认证的图片中只能包含一张人脸。") | ||||||
|  |          | ||||||
|  |         embedding = detected_faces[0].embedding | ||||||
|  |  | ||||||
|  |         # 2. 从数据库获取该ID对应的所有人脸特征 | ||||||
|  |         with database.get_db_connection() as conn: | ||||||
|  |             cursor = conn.cursor() | ||||||
|  |             cursor.execute("SELECT embedding FROM face_features WHERE user_id = ?", (user_id,)) | ||||||
|  |             db_features = cursor.fetchall() | ||||||
|  |  | ||||||
|  |             if not db_features: | ||||||
|  |                 raise ValueError(f"数据库中不存在ID为 {user_id} 的用户,或该用户未注册任何人脸。") | ||||||
|  |  | ||||||
|  |             db_embeddings = np.array([np.frombuffer(f['embedding'], dtype=np.float32) for f in db_features]) | ||||||
|  |  | ||||||
|  |             # 3. 计算与该ID所有特征的相似度,取最高值 | ||||||
|  |             similarities = np.dot(db_embeddings, embedding) | ||||||
|  |             best_similarity = np.max(similarities) | ||||||
|  |  | ||||||
|  |             # 1:1 认证通常使用比 1:N 更严格的阈值 | ||||||
|  |             VERIFICATION_THRESHOLD = 0.6 | ||||||
|  |  | ||||||
|  |             match = bool(best_similarity > VERIFICATION_THRESHOLD) | ||||||
|  |  | ||||||
|  |             return {"match": match, "confidence": float(best_similarity)} | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def delete_user(self, user_id: int): | ||||||
|  |         """根据ID删除用户及其所有的人脸数据""" | ||||||
|  |         with database.get_db_connection() as conn: | ||||||
|  |             cursor = conn.cursor() | ||||||
|  |             cursor.execute("SELECT name FROM users WHERE id = ?", (user_id,)) | ||||||
|  |             user = cursor.fetchone() | ||||||
|  |             if not user: | ||||||
|  |                 raise ValueError(f"ID为 {user_id} 的用户不存在。") | ||||||
|  |              | ||||||
|  |             # 使用了外键的 ON DELETE CASCADE,删除users表中的记录会自动删除face_features中的相关记录 | ||||||
|  |             cursor.execute("DELETE FROM users WHERE id = ?", (user_id,)) | ||||||
|  |             conn.commit() | ||||||
|  |              | ||||||
|  |             # cursor.rowcount 在 SQLite 中可能不总是可靠,我们确认用户存在即认为删除成功 | ||||||
|  |             return {"id": user_id, "name": user['name']} | ||||||
|  |  | ||||||
|  |     def get_user(self, user_id: int): | ||||||
|  |         """根据ID获取用户信息""" | ||||||
|  |         with database.get_db_connection() as conn: | ||||||
|  |             cursor = conn.cursor() | ||||||
|  |             cursor.execute("SELECT id, name, (SELECT COUNT(*) FROM face_features WHERE user_id=?) FROM users WHERE id=?", (user_id, user_id)) | ||||||
|  |             user = cursor.fetchone() | ||||||
|  |             if not user: | ||||||
|  |                 return None | ||||||
|  |             return {"id": user['id'], "name": user['name'], "registered_faces_count": user[2]} | ||||||
|  |  | ||||||
|  |     def list_all_users(self, skip: int = 0, limit: int = 100): | ||||||
|  |         """列出所有已注册的用户,支持分页""" | ||||||
|  |         with database.get_db_connection() as conn: | ||||||
|  |             cursor = conn.cursor() | ||||||
|  |             cursor.execute(""" | ||||||
|  |             SELECT u.id, u.name, COUNT(f.feature_id) as face_count | ||||||
|  |             FROM users u | ||||||
|  |             LEFT JOIN face_features f ON u.id = f.user_id | ||||||
|  |             GROUP BY u.id, u.name | ||||||
|  |             ORDER BY u.id | ||||||
|  |             LIMIT ? OFFSET ? | ||||||
|  |             """, (limit, skip)) | ||||||
|  |             users = cursor.fetchall() | ||||||
|  |             return [{"id": u['id'], "name": u['name'], "registered_faces_count": u['face_count']} for u in users] | ||||||
|  |  | ||||||
|  | # 在文件末尾实例化服务,方便API层调用 | ||||||
|  | face_service = FaceRecognitionService() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										
											BIN
										
									
								
								face_recognition.db
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								face_recognition.db
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										8
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  |  | ||||||
|  | fastapi | ||||||
|  | uvicorn[standard] | ||||||
|  | numpy | ||||||
|  | opencv-python | ||||||
|  | insightface | ||||||
|  | requests | ||||||
|  | pydantic | ||||||
		Reference in New Issue
	
	Block a user