"""
Module contains function for detection faces on images.
"""
from enum import Enum
from typing import Optional, Union, List, NamedTuple, Dict
from FaceEngine import DetectionFloat, FSDKError # pylint: disable=E0611,E0401
from FaceEngine import Landmarks5 as CoreLandmarks5 # pylint: disable=E0611,E0401
from FaceEngine import Landmarks68 as CoreLandmarks68 # pylint: disable=E0611,E0401
from FaceEngine import DetectionType, Face # pylint: disable=E0611,E0401
from FaceEngine import dt5Landmarks, dt68Landmarks # pylint: disable=E0611,E0401
from lunavl.sdk.estimators.base_estimation import BaseEstimation
from ..errors.errors import LunaVLError
from ..errors.exceptions import LunaSDKException, CoreExceptionWarp
from ..image_utils.geometry import Rect, Landmarks
from ..image_utils.image import VLImage, ColorFormat
[docs]class ImageForDetection(NamedTuple):
"""
Structure for the transfer to detector an image and detect an area.
Attributes
image (VLImage): image for detection
detectArea (Rect[float]):
"""
image: VLImage
detectArea: Rect[float]
[docs]class Landmarks5(Landmarks):
"""
Landmarks5
"""
# pylint: disable=W0235
def __init__(self, coreLandmark5: CoreLandmarks5):
"""
Init
Args:
coreLandmark5: core landmarks
"""
super().__init__(coreLandmark5)
[docs]class Landmarks68(Landmarks):
"""
Landmarks68
"""
# pylint: disable=W0235
def __init__(self, coreLandmark68: CoreLandmarks68):
"""
Init
Args:
coreLandmark68: core landmarks
"""
super().__init__(coreLandmark68)
[docs]class BoundingBox(BaseEstimation):
"""
Detection bounding box, it is characterized of rect and score:
- rect (Rect[float]): face bounding box
- score (float): face score (0,1), detection score is the measure of classification confidence
and not the source image quality. It may be used topick the most "*confident*" face of many.
"""
# pylint: disable=W0235
def __init__(self, boundingBox: DetectionFloat):
"""
Init.
Args:
boundingBox: core bounding box
"""
super().__init__(boundingBox)
@property
def score(self) -> float:
"""
Get score
Returns:
number in range [0,1]
"""
return self._coreEstimation.score
@property
def rect(self) -> Rect[float]:
"""
Get rect.
Returns:
float rect
"""
return Rect.fromCoreRect(self._coreEstimation.rect)
[docs] def asDict(self) -> dict:
"""
Convert to dict.
Returns:
{"rect": self.rect, "score": self.score}
"""
return {"rect": self.rect.asDict(), "score": self.score}
[docs]class FaceDetection(BaseEstimation):
"""
Attributes:
boundingBox (BoundingBox): face bounding box
landmarks5 (Optional[Landmarks5]): optional landmarks5
landmarks68 (Optional[Landmarks68]): optional landmarks5
_image (VLImage): source of detection
"""
__slots__ = ("boundingBox", "landmarks5", "landmarks68", "_coreDetection", "_image", "_emotions",
"_quality", "_mouthState")
def __init__(self, coreDetection: Face, image: VLImage):
"""
Init.
Args:
coreDetection: core detection
"""
super().__init__(coreDetection)
self.boundingBox = BoundingBox(coreDetection.detection)
if coreDetection.landmarks5_opt.isValid():
self.landmarks5: Optional[Landmarks5] = Landmarks5(coreDetection.landmarks5_opt.value())
else:
self.landmarks5: Optional[Landmarks5] = None
if coreDetection.landmarks68_opt.isValid():
self.landmarks68: Optional[Landmarks68] = Landmarks68(coreDetection.landmarks68_opt.value())
else:
self.landmarks68: Optional[Landmarks68] = None
self._image = image
self._emotions = None
self._quality = None
self._mouthState = None
@property
def image(self) -> VLImage:
"""
Get source of detection.
Returns:
source image
"""
return self._image
[docs] def asDict(self) -> Dict[str, Union[dict, list]]:
"""
Convert face detection to dict (json).
Returns:
dict. required keys: 'rect', 'score'. optional keys: 'landmarks5', 'landmarks68'
"""
res = {"rect": self.boundingBox.rect.asDict(), "score": self.boundingBox.score}
if self.landmarks5 is not None:
res["landmarks5"] = [point.asDict() for point in self.landmarks5.points]
if self.landmarks68 is not None:
res["landmarks68"] = [point.asDict() for point in self.landmarks68.points]
# TODO: may be nullable landmarks5?
return res
[docs]class FaceDetector:
"""
Face detector.
Attributes:
_detector (IDetectorPtr): core detector
"""
__slots__ = ["_detector", "detectorType"]
def __init__(self, detectorPtr, detectorType: DetectionType):
self._detector = detectorPtr
self.detectorType = detectorType
@staticmethod
def _getDetectionType(detect5Landmarks: bool = True, detect68Landmarks: bool = False) -> DetectionType:
"""
Get core detection type
Args:
detect5Landmarks: detect or not landmarks5
detect68Landmarks: detect or not landmarks68
Returns:
detection type
"""
toDetect = 0
if detect5Landmarks:
toDetect = toDetect | dt5Landmarks
if detect68Landmarks:
toDetect = toDetect | dt68Landmarks
return DetectionType(toDetect)
[docs] @CoreExceptionWarp(LunaVLError.DetectOneFaceError)
def detectOne(self, image: VLImage, detectArea: Optional[Rect[float]] = None, detect5Landmarks: bool = True,
detect68Landmarks: bool = False) -> Union[None, FaceDetection]:
"""
Detect just one best detection on the image.
Args:
image: image. Format must be R8G8B8 (todo check)
detectArea: rectangle area which contains face to detect. If not set will be set image.rect
detect5Landmarks: detect or not landmarks5
detect68Landmarks: detect or not landmarks68
Returns:
face detection if face is found otherwise None
Raises:
LunaSDKException: if detectOne is failed or image format has wrong the format
"""
if image.format != ColorFormat.R8G8B8:
details = "Bad image format for detection, format: {}, image: {}".format(image.format.value,
image.filename)
raise LunaSDKException(LunaVLError.InvalidImageFormat.detalize(details))
if detectArea is None:
_detectArea = image.coreImage.getRect()
else:
_detectArea = detectArea.coreRect
error, detectRes = self._detector.detectOne(image.coreImage, _detectArea,
self._getDetectionType(detect5Landmarks, detect68Landmarks))
if error.isError:
if error.FSDKError == FSDKError.BufferIsEmpty:
return None
raise LunaSDKException(LunaVLError.fromSDKError(error))
coreDetection = detectRes
return FaceDetection(coreDetection, image)
[docs] @CoreExceptionWarp(LunaVLError.DetectFacesError)
def detect(self, images: List[Union[VLImage, ImageForDetection]], limit: int = 5, detect5Landmarks: bool = True,
detect68Landmarks: bool = False) -> List[List[FaceDetection]]:
"""
Batch detect faces on images.
Args:
images: input images list. Format must be R8G8B8
limit: max number of detections per input image
detect5Landmarks: detect or not landmarks5
detect68Landmarks: detect or not landmarks68
Returns:
return list of lists detection, order of detection lists is corresponding to order input images
Raises:
LunaSDKException(LunaVLError.InvalidImageFormat): if any image has bad format or detect is failed
"""
imgs = []
detectAreas = []
for image in images:
if isinstance(image, VLImage):
img = image
detectAreas.append(image.coreImage.getRect())
else:
img = image.image
detectAreas.append(image.detectArea.coreRect)
if img.format != ColorFormat.R8G8B8:
details = "Bad image format for detection, format {}, img {}".format(img.format.value, img.filename)
raise LunaSDKException(LunaVLError.InvalidImageFormat.detalize(details))
imgs.append(img.coreImage)
error, detectRes = self._detector.detect(imgs, detectAreas, limit,
self._getDetectionType(detect5Landmarks, detect68Landmarks))
if error.isError:
raise LunaSDKException(LunaVLError.fromSDKError(error))
res = []
for numberImage, imageDetections in enumerate(detectRes):
res.append([FaceDetection(coreDetection, images[numberImage]) for coreDetection in imageDetections])
return res
[docs] def redetectOne(self):
"""
todo: wtf
Returns:
"""
pass
[docs] def redect(self):
"""
todo: wtf
Returns:
"""
pass
[docs] def setDetectionComparer(self):
"""
todo: wtf
Returns:
"""
pass