refactor: 重构服务层代码结构并优化性能
- 提取公共服务方法 _get_face_recognition、_check_result、_call_service - 添加 HTTP Session 池化与 LRU 图片缓存 - 完善 create_person 错误处理,返回统一格式 - 卸载时正确关闭 client 连接释放资源 - 传感器适配 API 返回结构变化 - 全面更新 README 文档,补充服务说明与示例
This commit is contained in:
+162
-245
@@ -33,28 +33,28 @@ class FaceRecognition:
|
||||
"""人脸识别核心功能"""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, client: TencentCloudClient):
|
||||
"""初始化人脸识别功能"""
|
||||
self.hass = hass
|
||||
self.client = client
|
||||
|
||||
async def _get_image_base64_from_source(
|
||||
self,
|
||||
image_url: str = None,
|
||||
image_path: str = None,
|
||||
image_file: str = None,
|
||||
camera_entity_id: str = None,
|
||||
image_url: Optional[str] = None,
|
||||
image_path: Optional[str] = None,
|
||||
image_file: Optional[str] = None,
|
||||
camera_entity_id: Optional[str] = None,
|
||||
) -> str:
|
||||
"""从各种来源获取图片 base64 编码"""
|
||||
if camera_entity_id:
|
||||
return await self._get_camera_image_base64(camera_entity_id)
|
||||
if image_url or image_path or image_file:
|
||||
return await self.hass.async_add_executor_job(
|
||||
self.client._get_image_base64, image_url, image_path, image_file
|
||||
self.client._get_image_base64,
|
||||
image_url,
|
||||
image_path,
|
||||
image_file,
|
||||
)
|
||||
raise ValueError("必须提供image_url、image_path、image_file或camera_entity_id")
|
||||
|
||||
async def _get_camera_image_base64(self, camera_entity_id: str) -> str:
|
||||
"""从摄像头实体获取图片 base64"""
|
||||
try:
|
||||
from homeassistant.components.camera import async_get_image
|
||||
image = await async_get_image(self.hass, camera_entity_id)
|
||||
@@ -66,90 +66,91 @@ class FaceRecognition:
|
||||
|
||||
def _validate_image_params(
|
||||
self,
|
||||
image_url: str = None,
|
||||
image_path: str = None,
|
||||
image_file: str = None,
|
||||
camera_entity_id: str = None,
|
||||
image_url: Optional[str] = None,
|
||||
image_path: Optional[str] = None,
|
||||
image_file: Optional[str] = None,
|
||||
camera_entity_id: Optional[str] = None,
|
||||
) -> None:
|
||||
"""验证图片参数"""
|
||||
if not any([image_url, image_path, image_file, camera_entity_id]):
|
||||
raise ValueError("必须提供image_url、image_path、image_file或camera_entity_id")
|
||||
|
||||
async def async_search_faces(
|
||||
async def _safe_api_call(
|
||||
self,
|
||||
image_url: str = None,
|
||||
image_path: str = None,
|
||||
image_file: str = None,
|
||||
camera_entity_id: str = None,
|
||||
group_ids: List[str] = None,
|
||||
max_face_num: int = 1,
|
||||
min_face_size: int = 34,
|
||||
max_user_num: int = 5,
|
||||
quality_control: int = 1,
|
||||
need_rotate_check: int = 1,
|
||||
face_match_threshold: float = 60.0
|
||||
operation_name: str,
|
||||
client_method,
|
||||
**kwargs,
|
||||
) -> Dict[str, Any]:
|
||||
"""搜索人脸"""
|
||||
try:
|
||||
self._validate_image_params(image_url, image_path, image_file, camera_entity_id)
|
||||
|
||||
image_base64 = await self._get_image_base64_from_source(
|
||||
image_url, image_path, image_file, camera_entity_id
|
||||
)
|
||||
|
||||
result = await self.hass.async_add_executor_job(
|
||||
self.client.search_faces,
|
||||
None, None, None, image_base64,
|
||||
group_ids,
|
||||
max_face_num,
|
||||
min_face_size,
|
||||
max_user_num,
|
||||
quality_control,
|
||||
need_rotate_check,
|
||||
face_match_threshold
|
||||
lambda: client_method(**kwargs)
|
||||
)
|
||||
|
||||
if result.get("success", False):
|
||||
processed_faces = self._process_search_results(result.get("faces", []))
|
||||
return {
|
||||
"success": True,
|
||||
"faces": processed_faces,
|
||||
"error": None,
|
||||
"error_code": None,
|
||||
"error_message": None
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"faces": [],
|
||||
"error": result.get("error"),
|
||||
"error_code": result.get("error_code"),
|
||||
"error_message": result.get("error_message")
|
||||
}
|
||||
|
||||
return result
|
||||
except HomeAssistantError:
|
||||
raise
|
||||
except ValueError as ex:
|
||||
_LOGGER.error("人脸搜索参数错误: %s", ex)
|
||||
_LOGGER.error("%s参数错误: %s", operation_name, ex)
|
||||
return {
|
||||
"success": False,
|
||||
"faces": [],
|
||||
"error": str(ex),
|
||||
"error_code": "invalid_parameter",
|
||||
"error_message": str(ex)
|
||||
"error_message": str(ex),
|
||||
}
|
||||
except HomeAssistantError:
|
||||
raise
|
||||
except Exception as ex:
|
||||
_LOGGER.error("人脸搜索失败: %s", ex)
|
||||
_LOGGER.error("%s失败: %s", operation_name, ex)
|
||||
return {
|
||||
"success": False,
|
||||
"faces": [],
|
||||
"error": str(ex),
|
||||
"error_code": "unknown_error",
|
||||
"error_message": str(ex)
|
||||
"error_message": str(ex),
|
||||
}
|
||||
|
||||
async def async_search_faces(
|
||||
self,
|
||||
image_url: Optional[str] = None,
|
||||
image_path: Optional[str] = None,
|
||||
image_file: Optional[str] = None,
|
||||
camera_entity_id: Optional[str] = None,
|
||||
group_ids: Optional[List[str]] = None,
|
||||
max_face_num: int = 1,
|
||||
min_face_size: int = 34,
|
||||
max_user_num: int = 5,
|
||||
quality_control: int = 1,
|
||||
need_rotate_check: int = 1,
|
||||
face_match_threshold: float = 60.0,
|
||||
) -> Dict[str, Any]:
|
||||
self._validate_image_params(image_url, image_path, image_file, camera_entity_id)
|
||||
|
||||
if not group_ids:
|
||||
raise ValueError("必须提供至少一个人员库ID (group_ids)")
|
||||
|
||||
image_base64 = await self._get_image_base64_from_source(
|
||||
image_url=image_url,
|
||||
image_path=image_path,
|
||||
image_file=image_file,
|
||||
camera_entity_id=camera_entity_id,
|
||||
)
|
||||
|
||||
result = await self._safe_api_call(
|
||||
"人脸搜索",
|
||||
self.client.search_faces,
|
||||
image_base64=image_base64,
|
||||
group_ids=group_ids,
|
||||
max_face_num=max_face_num,
|
||||
min_face_size=min_face_size,
|
||||
max_user_num=max_user_num,
|
||||
quality_control=quality_control,
|
||||
need_rotate_check=need_rotate_check,
|
||||
face_match_threshold=face_match_threshold,
|
||||
)
|
||||
|
||||
if result.get("success", False):
|
||||
result["faces"] = self._process_search_results(result.get("faces", []))
|
||||
|
||||
return result
|
||||
|
||||
def _process_search_results(self, results: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""处理人脸搜索结果"""
|
||||
processed_results = []
|
||||
for result in results:
|
||||
processed_result = {
|
||||
@@ -172,215 +173,131 @@ class FaceRecognition:
|
||||
|
||||
async def async_get_face_attributes(
|
||||
self,
|
||||
image_url: str = None,
|
||||
image_path: str = None,
|
||||
image_file: str = None,
|
||||
camera_entity_id: str = None,
|
||||
image_url: Optional[str] = None,
|
||||
image_path: Optional[str] = None,
|
||||
image_file: Optional[str] = None,
|
||||
camera_entity_id: Optional[str] = None,
|
||||
max_face_num: int = 1,
|
||||
need_rotate_check: int = 1
|
||||
need_rotate_check: int = 1,
|
||||
) -> Dict[str, Any]:
|
||||
"""获取人脸属性"""
|
||||
try:
|
||||
self._validate_image_params(image_url, image_path, image_file, camera_entity_id)
|
||||
self._validate_image_params(image_url, image_path, image_file, camera_entity_id)
|
||||
|
||||
image_base64 = await self._get_image_base64_from_source(
|
||||
image_url, image_path, image_file, camera_entity_id
|
||||
)
|
||||
image_base64 = await self._get_image_base64_from_source(
|
||||
image_url=image_url,
|
||||
image_path=image_path,
|
||||
image_file=image_file,
|
||||
camera_entity_id=camera_entity_id,
|
||||
)
|
||||
|
||||
result = await self.hass.async_add_executor_job(
|
||||
self.client.get_face_attributes,
|
||||
None, None, None, image_base64,
|
||||
max_face_num,
|
||||
need_rotate_check
|
||||
)
|
||||
|
||||
if result.get("success", False):
|
||||
return {
|
||||
"success": True,
|
||||
"faces": result.get("faces", []),
|
||||
"error": None,
|
||||
"error_code": None,
|
||||
"error_message": None
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"faces": [],
|
||||
"error": result.get("error"),
|
||||
"error_code": result.get("error_code"),
|
||||
"error_message": result.get("error_message")
|
||||
}
|
||||
|
||||
except HomeAssistantError:
|
||||
raise
|
||||
except Exception as ex:
|
||||
_LOGGER.error("获取人脸属性失败: %s", ex)
|
||||
return {
|
||||
"success": False,
|
||||
"faces": [],
|
||||
"error": str(ex),
|
||||
"error_code": "unknown_error",
|
||||
"error_message": str(ex)
|
||||
}
|
||||
return await self._safe_api_call(
|
||||
"获取人脸属性",
|
||||
self.client.get_face_attributes,
|
||||
image_base64=image_base64,
|
||||
max_face_num=max_face_num,
|
||||
need_rotate_check=need_rotate_check,
|
||||
)
|
||||
|
||||
async def async_detect_faces(
|
||||
self,
|
||||
image_url: str = None,
|
||||
image_path: str = None,
|
||||
image_file: str = None,
|
||||
camera_entity_id: str = None,
|
||||
image_url: Optional[str] = None,
|
||||
image_path: Optional[str] = None,
|
||||
image_file: Optional[str] = None,
|
||||
camera_entity_id: Optional[str] = None,
|
||||
max_face_num: int = 1,
|
||||
min_face_size: int = 34,
|
||||
need_rotate_check: int = 1
|
||||
need_rotate_check: int = 1,
|
||||
) -> Dict[str, Any]:
|
||||
"""检测人脸"""
|
||||
try:
|
||||
self._validate_image_params(image_url, image_path, image_file, camera_entity_id)
|
||||
self._validate_image_params(image_url, image_path, image_file, camera_entity_id)
|
||||
|
||||
image_base64 = await self._get_image_base64_from_source(
|
||||
image_url, image_path, image_file, camera_entity_id
|
||||
)
|
||||
image_base64 = await self._get_image_base64_from_source(
|
||||
image_url=image_url,
|
||||
image_path=image_path,
|
||||
image_file=image_file,
|
||||
camera_entity_id=camera_entity_id,
|
||||
)
|
||||
|
||||
result = await self.hass.async_add_executor_job(
|
||||
self.client.detect_faces,
|
||||
None, None, None, image_base64,
|
||||
max_face_num,
|
||||
min_face_size,
|
||||
need_rotate_check
|
||||
)
|
||||
|
||||
if result.get("success", False):
|
||||
return {
|
||||
"success": True,
|
||||
"faces": result.get("faces", []),
|
||||
"error": None,
|
||||
"error_code": None,
|
||||
"error_message": None
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"faces": [],
|
||||
"error": result.get("error"),
|
||||
"error_code": result.get("error_code"),
|
||||
"error_message": result.get("error_message")
|
||||
}
|
||||
|
||||
except HomeAssistantError:
|
||||
raise
|
||||
except Exception as ex:
|
||||
_LOGGER.error("人脸检测失败: %s", ex)
|
||||
return {
|
||||
"success": False,
|
||||
"faces": [],
|
||||
"error": str(ex),
|
||||
"error_code": "unknown_error",
|
||||
"error_message": str(ex)
|
||||
}
|
||||
return await self._safe_api_call(
|
||||
"人脸检测",
|
||||
self.client.detect_faces,
|
||||
image_base64=image_base64,
|
||||
max_face_num=max_face_num,
|
||||
min_face_size=min_face_size,
|
||||
need_rotate_check=need_rotate_check,
|
||||
)
|
||||
|
||||
async def async_create_person(
|
||||
self,
|
||||
person_id: str,
|
||||
person_name: str,
|
||||
group_id: str,
|
||||
image_url: str = None,
|
||||
image_path: str = None,
|
||||
image_file: str = None,
|
||||
camera_entity_id: str = None,
|
||||
gender: int = None,
|
||||
person_tag: str = None,
|
||||
image_url: Optional[str] = None,
|
||||
image_path: Optional[str] = None,
|
||||
image_file: Optional[str] = None,
|
||||
camera_entity_id: Optional[str] = None,
|
||||
gender: Optional[int] = None,
|
||||
person_tag: Optional[str] = None,
|
||||
quality_control: int = 1,
|
||||
need_rotate_check: int = 1,
|
||||
) -> Dict[str, Any]:
|
||||
"""创建人员"""
|
||||
try:
|
||||
image_base64 = None
|
||||
if any([image_url, image_path, image_file, camera_entity_id]):
|
||||
image_base64 = await self._get_image_base64_from_source(
|
||||
image_url, image_path, image_file, camera_entity_id
|
||||
)
|
||||
|
||||
result = await self.hass.async_add_executor_job(
|
||||
self.client.create_person,
|
||||
person_id, person_name, group_id,
|
||||
None, None, None, image_base64,
|
||||
gender, person_tag,
|
||||
quality_control, need_rotate_check,
|
||||
image_base64 = None
|
||||
if any([image_url, image_path, image_file, camera_entity_id]):
|
||||
image_base64 = await self._get_image_base64_from_source(
|
||||
image_url=image_url,
|
||||
image_path=image_path,
|
||||
image_file=image_file,
|
||||
camera_entity_id=camera_entity_id,
|
||||
)
|
||||
return result
|
||||
except HomeAssistantError:
|
||||
raise
|
||||
except Exception as ex:
|
||||
_LOGGER.error("创建人员失败: %s", ex)
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(ex),
|
||||
"error_code": "unknown_error",
|
||||
"error_message": str(ex)
|
||||
}
|
||||
|
||||
return await self._safe_api_call(
|
||||
"创建人员",
|
||||
self.client.create_person,
|
||||
person_id=person_id,
|
||||
person_name=person_name,
|
||||
group_id=group_id,
|
||||
image_base64=image_base64,
|
||||
gender=gender,
|
||||
person_tag=person_tag,
|
||||
quality_control=quality_control,
|
||||
need_rotate_check=need_rotate_check,
|
||||
)
|
||||
|
||||
async def async_delete_person(self, person_id: str) -> Dict[str, Any]:
|
||||
"""删除人员"""
|
||||
try:
|
||||
result = await self.hass.async_add_executor_job(
|
||||
self.client.delete_person, person_id
|
||||
)
|
||||
return result
|
||||
except Exception as ex:
|
||||
_LOGGER.error("删除人员失败: %s", ex)
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(ex),
|
||||
"error_code": "unknown_error",
|
||||
"error_message": str(ex)
|
||||
}
|
||||
return await self._safe_api_call(
|
||||
"删除人员",
|
||||
self.client.delete_person,
|
||||
person_id=person_id,
|
||||
)
|
||||
|
||||
async def async_create_face(
|
||||
self,
|
||||
person_id: str,
|
||||
image_url: str = None,
|
||||
image_path: str = None,
|
||||
image_file: str = None,
|
||||
camera_entity_id: str = None,
|
||||
image_url: Optional[str] = None,
|
||||
image_path: Optional[str] = None,
|
||||
image_file: Optional[str] = None,
|
||||
camera_entity_id: Optional[str] = None,
|
||||
quality_control: int = 1,
|
||||
need_rotate_check: int = 1,
|
||||
) -> Dict[str, Any]:
|
||||
"""为人员注册人脸"""
|
||||
try:
|
||||
image_base64 = await self._get_image_base64_from_source(
|
||||
image_url, image_path, image_file, camera_entity_id
|
||||
)
|
||||
image_base64 = await self._get_image_base64_from_source(
|
||||
image_url=image_url,
|
||||
image_path=image_path,
|
||||
image_file=image_file,
|
||||
camera_entity_id=camera_entity_id,
|
||||
)
|
||||
|
||||
result = await self.hass.async_add_executor_job(
|
||||
self.client.create_face,
|
||||
person_id,
|
||||
None, None, None, image_base64,
|
||||
quality_control, need_rotate_check,
|
||||
)
|
||||
return result
|
||||
except HomeAssistantError:
|
||||
raise
|
||||
except Exception as ex:
|
||||
_LOGGER.error("注册人脸失败: %s", ex)
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(ex),
|
||||
"error_code": "unknown_error",
|
||||
"error_message": str(ex)
|
||||
}
|
||||
return await self._safe_api_call(
|
||||
"注册人脸",
|
||||
self.client.create_face,
|
||||
person_id=person_id,
|
||||
image_base64=image_base64,
|
||||
quality_control=quality_control,
|
||||
need_rotate_check=need_rotate_check,
|
||||
)
|
||||
|
||||
async def async_delete_face(self, person_id: str, face_id: str) -> Dict[str, Any]:
|
||||
"""删除人脸"""
|
||||
try:
|
||||
result = await self.hass.async_add_executor_job(
|
||||
self.client.delete_face, person_id, face_id
|
||||
)
|
||||
return result
|
||||
except Exception as ex:
|
||||
_LOGGER.error("删除人脸失败: %s", ex)
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(ex),
|
||||
"error_code": "unknown_error",
|
||||
"error_message": str(ex)
|
||||
}
|
||||
return await self._safe_api_call(
|
||||
"删除人脸",
|
||||
self.client.delete_face,
|
||||
person_id=person_id,
|
||||
face_id=face_id,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user