feat: Enhace emotion inference speed on ARM and add FaceAnalyzer, Face classes for ease of use. (#25)

* feat: Update linting and type annotations, return types in detect

* feat: add face analyzer and face classes

* chore: Update the format and clean up some docstrings

* docs: Update usage documentation

* feat: Change AgeGender model output to 0, 1 instead of string (Female, Male)

* test: Update testing code

* feat: Add Apple silicon backend for torchscript inference

* feat: Add face analyzer example and add run emotion for testing
This commit is contained in:
Yakhyokhuja Valikhujaev
2025-11-30 20:32:07 +09:00
committed by GitHub
parent 779952e3f8
commit 0c93598007
51 changed files with 1605 additions and 966 deletions

View File

@@ -16,16 +16,16 @@ def test_create_detector_retinaface():
"""
Test creating a RetinaFace detector using factory function.
"""
detector = create_detector("retinaface")
assert detector is not None, "Failed to create RetinaFace detector"
detector = create_detector('retinaface')
assert detector is not None, 'Failed to create RetinaFace detector'
def test_create_detector_scrfd():
"""
Test creating a SCRFD detector using factory function.
"""
detector = create_detector("scrfd")
assert detector is not None, "Failed to create SCRFD detector"
detector = create_detector('scrfd')
assert detector is not None, 'Failed to create SCRFD detector'
def test_create_detector_with_config():
@@ -33,12 +33,12 @@ def test_create_detector_with_config():
Test creating detector with custom configuration.
"""
detector = create_detector(
"retinaface",
'retinaface',
model_name=RetinaFaceWeights.MNET_V2,
conf_thresh=0.8,
nms_thresh=0.3,
)
assert detector is not None, "Failed to create detector with custom config"
assert detector is not None, 'Failed to create detector with custom config'
def test_create_detector_invalid_method():
@@ -46,15 +46,15 @@ def test_create_detector_invalid_method():
Test that invalid detector method raises an error.
"""
with pytest.raises((ValueError, KeyError)):
create_detector("invalid_method")
create_detector('invalid_method')
def test_create_detector_scrfd_with_model():
"""
Test creating SCRFD detector with specific model.
"""
detector = create_detector("scrfd", model_name=SCRFDWeights.SCRFD_10G_KPS, conf_thresh=0.5)
assert detector is not None, "Failed to create SCRFD with specific model"
detector = create_detector('scrfd', model_name=SCRFDWeights.SCRFD_10G_KPS, conf_thresh=0.5)
assert detector is not None, 'Failed to create SCRFD with specific model'
# create_recognizer tests
@@ -62,24 +62,24 @@ def test_create_recognizer_arcface():
"""
Test creating an ArcFace recognizer using factory function.
"""
recognizer = create_recognizer("arcface")
assert recognizer is not None, "Failed to create ArcFace recognizer"
recognizer = create_recognizer('arcface')
assert recognizer is not None, 'Failed to create ArcFace recognizer'
def test_create_recognizer_mobileface():
"""
Test creating a MobileFace recognizer using factory function.
"""
recognizer = create_recognizer("mobileface")
assert recognizer is not None, "Failed to create MobileFace recognizer"
recognizer = create_recognizer('mobileface')
assert recognizer is not None, 'Failed to create MobileFace recognizer'
def test_create_recognizer_sphereface():
"""
Test creating a SphereFace recognizer using factory function.
"""
recognizer = create_recognizer("sphereface")
assert recognizer is not None, "Failed to create SphereFace recognizer"
recognizer = create_recognizer('sphereface')
assert recognizer is not None, 'Failed to create SphereFace recognizer'
def test_create_recognizer_invalid_method():
@@ -87,7 +87,7 @@ def test_create_recognizer_invalid_method():
Test that invalid recognizer method raises an error.
"""
with pytest.raises((ValueError, KeyError)):
create_recognizer("invalid_method")
create_recognizer('invalid_method')
# create_landmarker tests
@@ -95,8 +95,8 @@ def test_create_landmarker():
"""
Test creating a Landmark106 detector using factory function.
"""
landmarker = create_landmarker("2d106det")
assert landmarker is not None, "Failed to create Landmark106 detector"
landmarker = create_landmarker('2d106det')
assert landmarker is not None, 'Failed to create Landmark106 detector'
def test_create_landmarker_default():
@@ -104,7 +104,7 @@ def test_create_landmarker_default():
Test creating landmarker with default parameters.
"""
landmarker = create_landmarker()
assert landmarker is not None, "Failed to create default landmarker"
assert landmarker is not None, 'Failed to create default landmarker'
def test_create_landmarker_invalid_method():
@@ -112,7 +112,7 @@ def test_create_landmarker_invalid_method():
Test that invalid landmarker method raises an error.
"""
with pytest.raises((ValueError, KeyError)):
create_landmarker("invalid_method")
create_landmarker('invalid_method')
# detect_faces tests
@@ -121,9 +121,9 @@ def test_detect_faces_retinaface():
Test high-level detect_faces function with RetinaFace.
"""
mock_image = np.random.randint(0, 255, (640, 640, 3), dtype=np.uint8)
faces = detect_faces(mock_image, method="retinaface")
faces = detect_faces(mock_image, method='retinaface')
assert isinstance(faces, list), "detect_faces should return a list"
assert isinstance(faces, list), 'detect_faces should return a list'
def test_detect_faces_scrfd():
@@ -131,9 +131,9 @@ def test_detect_faces_scrfd():
Test high-level detect_faces function with SCRFD.
"""
mock_image = np.random.randint(0, 255, (640, 640, 3), dtype=np.uint8)
faces = detect_faces(mock_image, method="scrfd")
faces = detect_faces(mock_image, method='scrfd')
assert isinstance(faces, list), "detect_faces should return a list"
assert isinstance(faces, list), 'detect_faces should return a list'
def test_detect_faces_with_threshold():
@@ -141,13 +141,13 @@ def test_detect_faces_with_threshold():
Test detect_faces with custom confidence threshold.
"""
mock_image = np.random.randint(0, 255, (640, 640, 3), dtype=np.uint8)
faces = detect_faces(mock_image, method="retinaface", conf_thresh=0.8)
faces = detect_faces(mock_image, method='retinaface', conf_thresh=0.8)
assert isinstance(faces, list), "detect_faces should return a list"
assert isinstance(faces, list), 'detect_faces should return a list'
# All detections should respect threshold
for face in faces:
assert face["confidence"] >= 0.8, "All detections should meet confidence threshold"
assert face['confidence'] >= 0.8, 'All detections should meet confidence threshold'
def test_detect_faces_default_method():
@@ -157,7 +157,7 @@ def test_detect_faces_default_method():
mock_image = np.random.randint(0, 255, (640, 640, 3), dtype=np.uint8)
faces = detect_faces(mock_image) # No method specified
assert isinstance(faces, list), "detect_faces should return a list with default method"
assert isinstance(faces, list), 'detect_faces should return a list with default method'
def test_detect_faces_empty_image():
@@ -165,10 +165,10 @@ def test_detect_faces_empty_image():
Test detect_faces on a blank image.
"""
empty_image = np.zeros((640, 640, 3), dtype=np.uint8)
faces = detect_faces(empty_image, method="retinaface")
faces = detect_faces(empty_image, method='retinaface')
assert isinstance(faces, list), "Should return a list even for empty image"
assert len(faces) == 0, "Should detect no faces in blank image"
assert isinstance(faces, list), 'Should return a list even for empty image'
assert len(faces) == 0, 'Should detect no faces in blank image'
# list_available_detectors tests
@@ -178,8 +178,8 @@ def test_list_available_detectors():
"""
detectors = list_available_detectors()
assert isinstance(detectors, dict), "Should return a dictionary of detectors"
assert len(detectors) > 0, "Should have at least one detector available"
assert isinstance(detectors, dict), 'Should return a dictionary of detectors'
assert len(detectors) > 0, 'Should have at least one detector available'
def test_list_available_detectors_contents():
@@ -189,8 +189,8 @@ def test_list_available_detectors_contents():
detectors = list_available_detectors()
# Should include at least these detectors
assert "retinaface" in detectors, "Should include 'retinaface'"
assert "scrfd" in detectors, "Should include 'scrfd'"
assert 'retinaface' in detectors, "Should include 'retinaface'"
assert 'scrfd' in detectors, "Should include 'scrfd'"
# Integration tests
@@ -198,56 +198,56 @@ def test_detector_inference_from_factory():
"""
Test that detector created from factory can perform inference.
"""
detector = create_detector("retinaface")
detector = create_detector('retinaface')
mock_image = np.random.randint(0, 255, (640, 640, 3), dtype=np.uint8)
faces = detector.detect(mock_image)
assert isinstance(faces, list), "Detector should return list of faces"
assert isinstance(faces, list), 'Detector should return list of faces'
def test_recognizer_inference_from_factory():
"""
Test that recognizer created from factory can perform inference.
"""
recognizer = create_recognizer("arcface")
recognizer = create_recognizer('arcface')
mock_image = np.random.randint(0, 255, (112, 112, 3), dtype=np.uint8)
embedding = recognizer.get_embedding(mock_image)
assert embedding is not None, "Recognizer should return embedding"
assert embedding.shape[1] == 512, "Should return 512-dimensional embedding"
assert embedding is not None, 'Recognizer should return embedding'
assert embedding.shape[1] == 512, 'Should return 512-dimensional embedding'
def test_landmarker_inference_from_factory():
"""
Test that landmarker created from factory can perform inference.
"""
landmarker = create_landmarker("2d106det")
landmarker = create_landmarker('2d106det')
mock_image = np.random.randint(0, 255, (640, 640, 3), dtype=np.uint8)
mock_bbox = [100, 100, 300, 300]
landmarks = landmarker.get_landmarks(mock_image, mock_bbox)
assert landmarks is not None, "Landmarker should return landmarks"
assert landmarks.shape == (106, 2), "Should return 106 landmarks"
assert landmarks is not None, 'Landmarker should return landmarks'
assert landmarks.shape == (106, 2), 'Should return 106 landmarks'
def test_multiple_detector_creation():
"""
Test that multiple detectors can be created independently.
"""
detector1 = create_detector("retinaface")
detector2 = create_detector("scrfd")
detector1 = create_detector('retinaface')
detector2 = create_detector('scrfd')
assert detector1 is not None
assert detector2 is not None
assert detector1 is not detector2, "Should create separate instances"
assert detector1 is not detector2, 'Should create separate instances'
def test_detector_with_different_configs():
"""
Test creating multiple detectors with different configurations.
"""
detector_high_thresh = create_detector("retinaface", conf_thresh=0.9)
detector_low_thresh = create_detector("retinaface", conf_thresh=0.3)
detector_high_thresh = create_detector('retinaface', conf_thresh=0.9)
detector_low_thresh = create_detector('retinaface', conf_thresh=0.3)
mock_image = np.random.randint(0, 255, (640, 640, 3), dtype=np.uint8)
@@ -265,10 +265,10 @@ def test_factory_returns_correct_types():
"""
from uniface import RetinaFace, ArcFace, Landmark106
detector = create_detector("retinaface")
recognizer = create_recognizer("arcface")
landmarker = create_landmarker("2d106det")
detector = create_detector('retinaface')
recognizer = create_recognizer('arcface')
landmarker = create_landmarker('2d106det')
assert isinstance(detector, RetinaFace), "Should return RetinaFace instance"
assert isinstance(recognizer, ArcFace), "Should return ArcFace instance"
assert isinstance(landmarker, Landmark106), "Should return Landmark106 instance"
assert isinstance(detector, RetinaFace), 'Should return RetinaFace instance'
assert isinstance(recognizer, ArcFace), 'Should return ArcFace instance'
assert isinstance(landmarker, Landmark106), 'Should return Landmark106 instance'