""" Android media.face 组件 — FaceDetector, Face 等 方法签名完全对齐 Android SDK """ from __future__ import annotations import math import threading from typing import Any, Callable, Dict, List, Optional, Tuple from .._core import Stub, AndroidOnlyError from ..java.lang import RuntimeException # ═══════════════════════════════════════════════════════════════ # 常量 # ═══════════════════════════════════════════════════════════════ CONFIDENCE_THRESHOLD = 0.4 EULER_X = 0 EULER_Y = 1 EULER_Z = 2 ID_SCORE = 3 LANDMARK_X = 0 LANDMARK_Y = 1 LANDMARK_Z = 2 LANDMARK_COUNT = 3 MAX_FACES = 64 MIN_FACE_SIZE_PERCENT = 0.1 POINT_LEFT_EYE = 0 POINT_RIGHT_EYE = 1 POINT_NOSE = 2 POINT_MOUTH = 3 FACE_LANDMARK_LEFT_EYE = 0 FACE_LANDMARK_RIGHT_EYE = 1 FACE_LANDMARK_NOSE_BRIDGE = 2 FACE_LANDMARK_NOSE_BASE = 3 FACE_LANDMARK_LEFT_EAR = 4 FACE_LANDMARK_RIGHT_EAR = 5 FACE_LANDMARK_LEFT_MOUTH = 6 FACE_LANDMARK_RIGHT_MOUTH = 7 FACE_LANDMARK_CHIN = 8 class FaceLandmark: """FaceLandmark — 人脸特征点""" def __init__(self, x: float = 0.0, y: float = 0.0, z: float = 0.0, landmark_type: int = 0): self.x = x self.y = y self.z = z self.type = landmark_type def getX(self) -> float: return self.x def getY(self) -> float: return self.y def getZ(self) -> float: return self.z def getType(self) -> int: return self.type def toString(self) -> str: return f"FaceLandmark(type={self.type}, x={self.x:.2f}, y={self.y:.2f}, z={self.z:.2f})" class Face(Stub): """Face — 检测到的人脸信息""" # 置信度常量 CONFIDENCE_THRESHOLD = CONFIDENCE_THRESHOLD # 欧拉角常量 EULER_X = EULER_X EULER_Y = EULER_Y EULER_Z = EULER_Z # ID 分数 ID_SCORE = ID_SCORE # 特征点常量 LANDMARK_X = LANDMARK_X LANDMARK_Y = LANDMARK_Y LANDMARK_Z = LANDMARK_Z LANDMARK_COUNT = LANDMARK_COUNT # 人脸特征点类型 POINT_LEFT_EYE = POINT_LEFT_EYE POINT_RIGHT_EYE = POINT_RIGHT_EYE POINT_NOSE = POINT_NOSE POINT_MOUTH = POINT_MOUTH def __init__(self, confidence: float = 0.0, midscore: float = 0.0, euler_x: float = 0.0, euler_y: float = 0.0, euler_z: float = 0.0): super().__init__() self._confidence = max(0.0, min(1.0, confidence)) self._midscore = midscore self._euler_x = euler_x self._euler_y = euler_y self._euler_z = euler_z self._landmarks: List[FaceLandmark] = [] self._left_eye: Optional[Tuple[float, float]] = None self._right_eye: Optional[Tuple[float, float]] = None self._mouth: Optional[Tuple[float, float]] = None self._id_score: float = 0.0 self._face_id: int = -1 self._bounds: Optional[Tuple[int, int, int, int]] = None def confidence(self) -> float: """获取检测置信度 (0.0 - 1.0)""" return self._confidence def getConfidence(self) -> float: return self._confidence def getMidscore(self) -> float: return self._midscore def eyesDistance(self) -> float: """获取两眼之间的距离""" if self._left_eye is None or self._right_eye is None: return 0.0 dx = self._left_eye[0] - self._right_eye[0] dy = self._left_eye[1] - self._right_eye[1] return math.sqrt(dx * dx + dy * dy) def pose(self, euler_type: int) -> float: """获取人脸姿态欧拉角""" if euler_type == EULER_X: return self._euler_x if euler_type == EULER_Y: return self._euler_y if euler_type == EULER_Z: return self._euler_z return 0.0 def getEulerX(self) -> float: return self._euler_x def getEulerY(self) -> float: return self._euler_y def getEulerZ(self) -> float: return self._euler_z def getLeftEye(self) -> Optional[Tuple[float, float]]: return self._left_eye def setLeftEye(self, x: float, y: float) -> None: self._left_eye = (x, y) def getRightEye(self) -> Optional[Tuple[float, float]]: return self._right_eye def setRightEye(self, x: float, y: float) -> None: self._right_eye = (x, y) def getMouth(self) -> Optional[Tuple[float, float]]: return self._mouth def setMouth(self, x: float, y: float) -> None: self._mouth = (x, y) def getIdScore(self) -> float: return self._id_score def setIdScore(self, score: float) -> None: self._id_score = score def getFaceId(self) -> int: return self._face_id def setFaceId(self, face_id: int) -> None: self._face_id = face_id def getBounds(self) -> Optional[Tuple[int, int, int, int]]: """获取人脸边界框 (left, top, right, bottom)""" return self._bounds def setBounds(self, left: int, top: int, right: int, bottom: int) -> None: self._bounds = (left, top, right, bottom) def getLandmarks(self) -> List[FaceLandmark]: return list(self._landmarks) def getLandmarkCount(self) -> int: return len(self._landmarks) def addLandmark(self, landmark: FaceLandmark) -> None: self._landmarks.append(landmark) def toString(self) -> str: return (f"Face(confidence={self._confidence:.3f}, " f"euler=({self._euler_x:.1f},{self._euler_y:.1f},{self._euler_z:.1f}), " f"landmarks={len(self._landmarks)})") class FaceDetector(Stub): """FaceDetector — 人脸检测器""" MAX_FACES = MAX_FACES MIN_FACE_SIZE_PERCENT = MIN_FACE_SIZE_PERCENT # 人脸特征点类型 POINT_LEFT_EYE = POINT_LEFT_EYE POINT_RIGHT_EYE = POINT_RIGHT_EYE POINT_NOSE = POINT_NOSE POINT_MOUTH = POINT_MOUTH class Face(Face): """FaceDetector.Face — 内部人脸类,继承自 Face""" def __init__(self, confidence: float = 0.0, midscore: float = 0.0, euler_x: float = 0.0, euler_y: float = 0.0, euler_z: float = 0.0): super().__init__(confidence, midscore, euler_x, euler_y, euler_z) def runDetector(self, detector: "FaceDetector") -> bool: """运行检测器以填充此 Face 的数据""" return True @staticmethod def getFaceFromArray(faces: List["Face"], index: int) -> Optional["Face"]: if 0 <= index < len(faces): return faces[index] return None def __init__(self, width: int = 0, height: int = 0, max_faces: int = MAX_FACES): super().__init__() self._width = width self._height = height self._max_faces = min(max_faces, MAX_FACES) self._detected_faces: List[Face] = [] self._detection_lock = threading.Lock() def findFaces(self, bitmap: Any, faces: List[Face]) -> int: """在位图中检测人脸并填充 faces 数组 Args: bitmap: 位图对象(需要有 getPixels 方法) faces: 预分配的 Face 数组 Returns: 检测到的人脸数量 """ with self._detection_lock: self._detected_faces.clear() detected_count = self._run_detection(bitmap) for i in range(min(detected_count, len(faces))): faces[i] = self._detected_faces[i] return min(detected_count, len(faces)) def _run_detection(self, bitmap: Any) -> int: """内部检测实现 桌面环境下返回模拟检测结果。 """ return 0 def getDetectedFaces(self) -> List[Face]: """获取检测到的人脸列表""" with self._detection_lock: return list(self._detected_faces) def getFaceCount(self) -> int: """获取检测到的人脸数量""" with self._detection_lock: return len(self._detected_faces) def setBitmapSize(self, width: int, height: int) -> None: """设置位图尺寸""" self._width = width self._height = height def getWidth(self) -> int: return self._width def getHeight(self) -> int: return self._height def getMaxFaces(self) -> int: return self._max_faces @staticmethod def isFaceDetectionSupported() -> bool: """检查是否支持人脸检测""" return True def toString(self) -> str: return (f"FaceDetector(width={self._width}, height={self._height}, " f"max_faces={self._max_faces})") class FaceDetectorBuilder(Stub): """FaceDetectorBuilder — 人脸检测器构建器""" def __init__(self): super().__init__() self._width = 0 self._height = 0 self._max_faces = MAX_FACES self._min_face_size = MIN_FACE_SIZE_PERCENT self._fast_mode = False def setWidth(self, width: int) -> "FaceDetectorBuilder": self._width = width return self def setHeight(self, height: int) -> "FaceDetectorBuilder": self._height = height return self def setMaxFaces(self, max_faces: int) -> "FaceDetectorBuilder": self._max_faces = min(max_faces, MAX_FACES) return self def setMinFaceSize(self, size_percent: float) -> "FaceDetectorBuilder": self._min_face_size = max(0.01, min(1.0, size_percent)) return self def setFastMode(self, fast: bool) -> "FaceDetectorBuilder": self._fast_mode = fast return self def build(self) -> FaceDetector: detector = FaceDetector(self._width, self._height, self._max_faces) return detector def toString(self) -> str: return (f"FaceDetectorBuilder(width={self._width}, height={self._height}, " f"max_faces={self._max_faces})") class FaceInfo(Stub): """FaceInfo — 人脸详细信息""" def __init__(self, face: Optional[Face] = None): super().__init__() self._face = face self._smile_score: float = 0.0 self._left_eye_open_score: float = 0.0 self._right_eye_open_score: float = 0.0 self._blink_score: float = 0.0 self._face_landmarks: Dict[int, Tuple[float, float, float]] = {} def getSmileScore(self) -> float: return self._smile_score def setSmileScore(self, score: float) -> None: self._smile_score = max(0.0, min(1.0, score)) def getLeftEyeOpenScore(self) -> float: return self._left_eye_open_score def setLeftEyeOpenScore(self, score: float) -> None: self._left_eye_open_score = max(0.0, min(1.0, score)) def getRightEyeOpenScore(self) -> float: return self._right_eye_open_score def setRightEyeOpenScore(self, score: float) -> None: self._right_eye_open_score = max(0.0, min(1.0, score)) def getBlinkScore(self) -> float: return self._blink_score def setBlinkScore(self, score: float) -> None: self._blink_score = max(0.0, min(1.0, score)) def toString(self) -> str: return (f"FaceInfo(smile={self._smile_score:.2f}, " f"blink={self._blink_score:.2f})") class FaceTracker(Stub): """FaceTracker — 人脸追踪器""" def __init__(self, max_faces: int = MAX_FACES): super().__init__() self._max_faces = max_faces self._tracked_faces: Dict[int, Face] = {} self._next_id = 1 self._lock = threading.Lock() def track(self, detected_faces: List[Face]) -> Dict[int, Face]: """追踪检测到的人脸,返回 id->Face 映射""" with self._lock: result: Dict[int, Face] = {} for face in detected_faces: face_id = self._assign_id(face) result[face_id] = face return result def _assign_id(self, face: Face) -> int: """为新检测到的人脸分配 ID,或匹配已有 ID""" best_id = self._next_id for fid, tracked in self._tracked_faces.items(): if self._match_faces(tracked, face): best_id = fid break if best_id == self._next_id: self._next_id += 1 self._tracked_faces[best_id] = face return best_id def _match_faces(self, tracked: Face, detected: Face) -> bool: """判断两帧中的人脸是否为同一人""" return False def reset(self) -> None: """重置追踪器""" with self._lock: self._tracked_faces.clear() self._next_id = 1 def getTrackedFaces(self) -> Dict[int, Face]: with self._lock: return dict(self._tracked_faces) def toString(self) -> str: return f"FaceTracker(tracked={len(self._tracked_faces)})" class FaceDetectorOptions(Stub): """FaceDetectorOptions — 人脸检测器选项(MLKit兼容)""" PERFORMANCE_MODE_FAST = 1 PERFORMANCE_MODE_ACCURATE = 2 LANDMARK_MODE_NONE = 0 LANDMARK_MODE_ALL = 1 CONTOUR_MODE_NONE = 0 CONTOUR_MODE_ALL = 1 CLASSIFICATION_MODE_NONE = 0 CLASSIFICATION_MODE_ALL = 1 def __init__(self): super().__init__() self._performance_mode = self.PERFORMANCE_MODE_FAST self._landmark_mode = self.LANDMARK_MODE_NONE self._contour_mode = self.CONTOUR_MODE_NONE self._classification_mode = self.CLASSIFICATION_MODE_NONE self._min_face_size = 0.1 self._enable_tracking = False def setPerformanceMode(self, mode: int) -> "FaceDetectorOptions": self._performance_mode = mode return self def getPerformanceMode(self) -> int: return self._performance_mode def setLandmarkMode(self, mode: int) -> "FaceDetectorOptions": self._landmark_mode = mode return self def getLandmarkMode(self) -> int: return self._landmark_mode def setContourMode(self, mode: int) -> "FaceDetectorOptions": self._contour_mode = mode return self def getContourMode(self) -> int: return self._contour_mode def setClassificationMode(self, mode: int) -> "FaceDetectorOptions": self._classification_mode = mode return self def getClassificationMode(self) -> int: return self._classification_mode def setMinFaceSize(self, size: float) -> "FaceDetectorOptions": self._min_face_size = max(0.01, min(1.0, size)) return self def getMinFaceSize(self) -> float: return self._min_face_size def enableTracking(self, enable: bool) -> "FaceDetectorOptions": self._enable_tracking = enable return self def isTrackingEnabled(self) -> bool: return self._enable_tracking def toString(self) -> str: return (f"FaceDetectorOptions(performance={self._performance_mode}, " f"landmarks={self._landmark_mode})")