- android/ 167个模块 (app/content/os/view/widget/graphics/media/hardware/net/telephony/provider等) - java/ 35个模块 (lang/io/util/net/nio/security/crypto/sql/beans/rmi/awt等) - javax/ 4个模块 (net/crypto/security/sql) - org/ 5个模块 (json/w3c_dom/xml_sax/xml_pull/apache_http) - dalvik/ 1个模块 (system) - ~3053个类/接口,方法签名完全对齐Android SDK - 桌面跨平台运行 (Windows/macOS/Linux) - 修复所有导入错误和Python关键字冲突
499 lines
15 KiB
Python
499 lines
15 KiB
Python
"""
|
|
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})") |