Add the inspireface project to cpp-package.

This commit is contained in:
JingyuYan
2024-05-02 01:27:29 +08:00
parent e90dacb3cf
commit 08d7e96f79
431 changed files with 370534 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
from .test_settings import *
from .test_utilis import *
# Unit module
from .unit import *
from .performance import *

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,59 @@
from test import *
import unittest
import cv2
@optional(ENABLE_LFW_PRECISION_TEST, "LFW dataset precision tests have been closed.")
class LFWPrecisionTestCase(unittest.TestCase):
def setUp(self) -> None:
self.quick = QuickComparison()
def test_lfw_precision(self):
pairs_path = os.path.join(LFW_FUNNELED_DIR_PATH, 'pairs.txt')
pairs = read_pairs(pairs_path)
self.assertEqual(True, len(pairs) > 0)
if os.path.exists(LFW_PREDICT_DATA_CACHE_PATH):
print("Loading results from cache")
cache = np.load(LFW_PREDICT_DATA_CACHE_PATH, allow_pickle=True)
similarities = cache[0]
labels = cache[1]
else:
similarities = []
labels = []
for pair in tqdm(pairs):
if len(pair) == 3:
person, img_num1, img_num2 = pair
img_path1 = os.path.join(LFW_FUNNELED_DIR_PATH, person, f"{person}_{img_num1.zfill(4)}.jpg")
img_path2 = os.path.join(LFW_FUNNELED_DIR_PATH, person, f"{person}_{img_num2.zfill(4)}.jpg")
match = True
else:
person1, img_num1, person2, img_num2 = pair
img_path1 = os.path.join(LFW_FUNNELED_DIR_PATH, person1, f"{person1}_{img_num1.zfill(4)}.jpg")
img_path2 = os.path.join(LFW_FUNNELED_DIR_PATH, person2, f"{person2}_{img_num2.zfill(4)}.jpg")
match = False
img1 = cv2.imread(img_path1)
img2 = cv2.imread(img_path2)
if not self.quick.setup(img1, img2):
print("not detect face")
continue
cosine_similarity = self.quick.comp()
similarities.append(cosine_similarity)
labels.append(match)
similarities = np.array(similarities)
labels = np.array(labels)
# save cache file
np.save(LFW_PREDICT_DATA_CACHE_PATH, [similarities, labels])
# find best threshold
best_threshold, best_accuracy = find_best_threshold(similarities, labels)
print(f"Best Threshold: {best_threshold:.2f}, Best Accuracy: {best_accuracy:.3f}")
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,68 @@
import os
import sys
import inspireface as ifac
# ++ OPTIONAL ++
# Enabling will run all the benchmark tests, which takes time
ENABLE_BENCHMARK_TEST = True
# Enabling will run all the CRUD tests, which will take time
ENABLE_CRUD_TEST = True
# Enabling will run the face search benchmark, which takes time and must be configured with the correct
# 'LFW_FUNNELED_DIR_PATH' parameter
ENABLE_SEARCH_BENCHMARK_TEST = True
# Enabling will run the LFW dataset precision test, which will take time
ENABLE_LFW_PRECISION_TEST = True
# Testing model name
TEST_MODEL_NAME = "Pikachu"
# TEST_MODEL_NAME = "Megatron"
# Testing length of face feature
TEST_MODEL_FACE_FEATURE_LENGTH = 512
# Testing face comparison image threshold
TEST_FACE_COMPARISON_IMAGE_THRESHOLD = 0.45
# ++ END OPTIONAL ++
# Current project path
TEST_PROJECT_PATH = os.path.dirname(os.path.abspath(__file__))
# Current project path
CURRENT_PROJECT_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# Main project path
MAIN_PROJECT_PATH = os.path.dirname(CURRENT_PROJECT_PATH)
# Model zip path
MODEL_ZIP_PATH = os.path.join(MAIN_PROJECT_PATH, "test_res/pack/")
# Testing model full path
TEST_MODEL_PATH = os.path.join(MODEL_ZIP_PATH, TEST_MODEL_NAME)
# Python test data folder
PYTHON_TEST_DATA_FOLDER = os.path.join(TEST_PROJECT_PATH, "data/")
# Stores some temporary file data generated during testing
TMP_FOLDER = os.path.join(CURRENT_PROJECT_PATH, "tmp")
# Default db file path
DEFAULT_DB_PATH = os.path.join(TMP_FOLDER, ".E63520A95DD5B3892C56DA38C3B28E551D8173FD")
# Create tmp if not exist
os.makedirs(TMP_FOLDER, exist_ok=True)
# lfw_funneled Dataset dir path
LFW_FUNNELED_DIR_PATH = "/Users/tunm/datasets/lfw_funneled/"
# The LFW data predicted by the algorithm is used and cached to save time in the next prediction, and it can be
# re-predicted by manually deleting it
LFW_PREDICT_DATA_CACHE_PATH = os.path.join(TMP_FOLDER, "LFW_PRED.npy")
assert os.path.exists(LFW_FUNNELED_DIR_PATH), "'LFW_FUNNELED_DIR_PATH' is not found."
ifac.launch(TEST_MODEL_PATH)

View File

@@ -0,0 +1,280 @@
from test.test_settings import *
import inspireface as ifac
from inspireface.param import *
import numpy as np
import time
from functools import wraps
import cv2
from itertools import cycle
from tqdm import tqdm
from unittest import skipUnless as optional
def title(name: str = None):
print("--" * 35)
print(f" InspireFace Version: {ifac.__version__}")
if name is not None:
print(f" {name}")
print("--" * 35)
def get_test_data(path: str) -> str:
return os.path.join(PYTHON_TEST_DATA_FOLDER, path)
def calculate_overlap(box1, box2):
"""
Calculate the overlap ratio between two rectangular boxes.
Parameters:
- box1: The first rectangle, format ((x1, y1), (x2, y2)), where (x1, y1) is the top left coordinate, and (x2, y2) is the bottom right coordinate.
- box2: The second rectangle, format the same as box1.
Returns:
- The overlap ratio, 0 if the rectangles do not overlap.
"""
# Unpack rectangle coordinates
x1_box1, y1_box1, x2_box1, y2_box1 = box1
x1_box2, y1_box2, x2_box2, y2_box2 = box2
# Calculate the coordinates of the intersection rectangle
x_overlap = max(0, min(x2_box1, x2_box2) - max(x1_box1, x1_box2))
y_overlap = max(0, min(y2_box1, y2_box2) - max(y1_box1, y1_box2))
# Calculate the area of the intersection
overlap_area = x_overlap * y_overlap
# Calculate the area of each rectangle
box1_area = (x2_box1 - x1_box1) * (y2_box1 - y1_box1)
box2_area = (x2_box2 - x1_box2) * (y2_box2 - y1_box2)
# Calculate the total area
total_area = box1_area + box2_area - overlap_area
# Calculate the overlap ratio
overlap_ratio = overlap_area / total_area if total_area > 0 else 0
return overlap_ratio
def restore_rotated_box(original_width, original_height, box, rotation):
"""
Restore the coordinates of a rotated face box based on the original image width, height, and rotation angle.
Parameters:
- original_width: The width of the original image.
- original_height: The height of the original image.
- box: The coordinates of the rotated box, format ((x1, y1), (x2, y2)).
- rotation: The rotation angle, represented by 0, 1, 2, 3 for 0, 90, 180, 270 degrees respectively.
Returns:
- The restored box coordinates, format same as box.
"""
# For 90 or 270 degrees rotation, the image width and height are swapped
if rotation == 1 or rotation == 3:
width, height = original_height, original_width
else:
width, height = original_width, original_height
(x1, y1, x2, y2) = box
if rotation == 0: # No transformation needed for 0 degrees
restored_box = box
elif rotation == 1: # 90 degrees rotation
restored_box = (y1, width - x2, y2, width - x1)
elif rotation == 2: # 180 degrees rotation
restored_box = (width - x2, height - y2, width - x1, height - y1)
elif rotation == 3: # 270 degrees rotation
restored_box = (height - y2, x1, height - y1, x2)
else:
raise ValueError("Rotation must be 0, 1, 2, or 3 representing 0, 90, 180, 270 degrees.")
return restored_box
def read_binary_file_to_ndarray(file_path, width, height):
nv21_size = width * height * 3 // 2 # NV21 size calculation
try:
with open(file_path, 'rb') as file:
file_data = file.read() # Read the entire file
if len(file_data) != nv21_size:
print(f"Expected file size is {nv21_size}, but got {len(file_data)}")
return None
# Assuming the file data is a complete NV21 frame
data = np.frombuffer(file_data, dtype=np.uint8)
return data
except FileNotFoundError:
print(f"File '{file_path}' not found.")
return None
except Exception as e:
print(f"An error occurred while reading the file: {str(e)}")
return None
def print_benchmark_table(benchmark_results):
print("\n")
header_format = "{:<20} | {:<10} | {:<15} | {:<15}"
row_format = "{:<20} | {:<10} | {:>10.2f} ms | {:>10.4f} ms"
print(header_format.format('Benchmark', 'Loops', 'Total Time', 'Avg Time'))
print("-" * 70) # 调整分割线长度以匹配标题长度
for name, loops, total_time in benchmark_results:
avg_time = total_time / loops
print(row_format.format(name, loops, total_time * 1000, avg_time * 1000))
def benchmark(test_name, loop):
def benchmark_decorator(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
# Set the loop property on the test object
setattr(self, 'loop', loop)
start_time = time.time()
try:
result = func(self, *args, **kwargs)
finally:
end_time = time.time()
cost_total = end_time - start_time
self.__class__.benchmark_results.append((test_name, loop, cost_total))
# After the test is complete, delete the loop property to prevent other tests from being affected
delattr(self, 'loop')
return result
return wrapper
return benchmark_decorator
def read_video_generator(video_path):
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
raise IOError(f"Cannot open video {video_path}")
while True:
ret, frame = cap.read()
if not ret:
break
yield frame
cap.release()
def lfw_generator(directory_path):
while True:
for root, dirs, files in os.walk(directory_path):
for file_name in files:
# Be sure to only process JPG images that end in '0001.jpg'
if file_name.endswith('0001.jpg'):
# Extract the name of the person as the last part of the directory name
name = os.path.basename(root)
image_path = os.path.join(root, file_name)
image = cv2.imread(image_path)
assert image is not None, "Error of image data."
yield image, name
def batch_import_lfw_faces(lfw_path, engine: ifac.InspireFaceSession, num_of_faces: int):
engine.set_track_mode(HF_DETECT_MODE_IMAGE)
generator = lfw_generator(lfw_path)
registered_faces = 0
# With the tqdm wrapper generator, unknown totals are used with total=None, and tqdm will run in unknown total mode
for image, name in tqdm(generator, total=num_of_faces, desc="Registering faces"):
faces_info = engine.face_detection(image)
if len(faces_info) == 0:
continue
# Extract features from the first face detected
first_face_info = faces_info[0]
feature = engine.face_feature_extract(image, first_face_info)
# The extracted features are used for face registration
if feature is not None:
face_identity = ifac.FaceIdentity(data=feature, tag=name, custom_id=registered_faces)
ifac.feature_hub_face_insert(face_identity)
registered_faces += 1
if registered_faces >= num_of_faces:
break
print(f"Completed. Total faces registered: {registered_faces}")
class QuickComparison(object):
def __init__(self):
param = ifac.SessionCustomParameter()
param.enable_recognition = True
self.engine = ifac.InspireFaceSession(param)
self.faces_set_1 = None
self.faces_set_2 = None
def setup(self, image1: np.ndarray, image2: np.ndarray) -> bool:
images = [image1, image2]
self.faces_set_1 = list()
self.faces_set_2 = list()
for idx, img in enumerate(images):
results = self.engine.face_detection(img)
vector_list = list()
if len(results) > 0:
for info in results:
feature = self.engine.face_feature_extract(img, info)
vector_list.append(feature)
else:
return False
if idx == 0:
self.faces_set_1 = vector_list
else:
self.faces_set_2 = vector_list
return True
def comp(self) -> float:
"""
Cross-compare one by one, keep the value with the highest score and return it, calling self.recognition.face_comparison1v1(info1, info2)
:return: Maximum matching score
"""
max_score = 0.0
# Each face in faces_set_1 is traversed and compared with each face in faces_set_2
for face1 in self.faces_set_1:
for face2 in self.faces_set_2:
score = ifac.feature_comparison(face1, face2)
if score > max_score:
max_score = score
return max_score
def match(self, threshold) -> bool:
return self.comp() > threshold
def find_best_threshold(similarities, labels):
thresholds = np.arange(0, 1, 0.01)
best_threshold = best_accuracy = 0
for threshold in thresholds:
predictions = (similarities > threshold)
accuracy = np.mean((predictions == labels).astype(int))
if accuracy > best_accuracy:
best_accuracy = accuracy
best_threshold = threshold
return best_threshold, best_accuracy
def read_pairs(pairs_filename):
"""Read the pairs.txt file and return a list of image pairs"""
pairs = []
with open(pairs_filename, 'r') as f:
for line in f.readlines()[1:]:
pair = line.strip().split()
pairs.append(pair)
return pairs

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,51 @@
from test import *
import unittest
import inspireface as ifac
from inspireface.param import *
import cv2
class CameraStreamCase(unittest.TestCase):
def setUp(self) -> None:
"""Shared area for priority execution"""
pass
def test_image_codec(self) -> None:
image = cv2.imread(get_test_data("bulk/kun.jpg"))
self.assertIsNotNone(image)
def test_stream_rotation(self) -> None:
# Prepare material
engine = ifac.InspireFaceSession(HF_ENABLE_NONE, HF_DETECT_MODE_IMAGE)
# Prepare rotation images
rotation_images_filenames = ["rotate/rot_0.jpg", "rotate/rot_90.jpg", "rotate/rot_180.jpg","rotate/rot_270.jpg"]
rotation_images = [cv2.imread(get_test_data(path)) for path in rotation_images_filenames]
self.assertEqual(True, all(isinstance(item, np.ndarray) for item in rotation_images))
# Detecting face images without rotation
rot_0 = rotation_images[0]
h, w, _ = rot_0.shape
self.assertIsNotNone(rot_0, "Image is empty")
rot_0_faces = engine.face_detection(image=rot_0)
self.assertEqual(True, len(rot_0_faces) > 0)
rot_0_face_box = rot_0_faces[0].location
num_of_faces = len(rot_0_faces)
# Detect images with other rotation angles
rotation_tags = [HF_CAMERA_ROTATION_90, HF_CAMERA_ROTATION_180, HF_CAMERA_ROTATION_270]
streams = [ifac.ImageStream.load_from_cv_image(img, rotation=rotation_tags[idx]) for idx, img in enumerate(rotation_images[1:])]
results = [engine.face_detection(stream) for stream in streams]
# No matter how many degrees the image is rotated, the same number of faces should be detected
self.assertEqual(True, all(len(item) == num_of_faces for item in results))
# Select all the first face box
rot_other_faces_boxes = [face[0].location for face in results]
# We need to restore the rotated face box
restored_boxes = [restore_rotated_box(w, h, rot_other_faces_boxes[idx], rotation_tags[idx]) for idx, box in enumerate(rot_other_faces_boxes)]
# IoU is performed with the face box of the original image to calculate the overlap
iou_results = [calculate_overlap(box, rot_0_face_box) for box in restored_boxes]
# The face box position of all rotated images is detected to be consistent with that of the original image
self.assertEqual(all(0.95 < iou < 1.0 for iou in iou_results), True)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,226 @@
import unittest
from test import *
import inspireface as ifac
from inspireface.param import *
import cv2
class FaceRecognitionBaseCase(unittest.TestCase):
"""
This case is mainly used to test the basic functions of face recognition.
"""
def setUp(self) -> None:
# Prepare material
track_mode = HF_DETECT_MODE_IMAGE
param = ifac.SessionCustomParameter()
param.enable_recognition = True
self.engine = ifac.InspireFaceSession(param, track_mode, 10)
def test_face_feature_extraction(self):
self.engine.set_track_mode(mode=HF_DETECT_MODE_IMAGE)
# Prepare a image
image = cv2.imread(get_test_data("bulk/kun.jpg"))
self.assertIsNotNone(image)
# Face detection
faces = self.engine.face_detection(image)
# "kun.jpg" has only one face
self.assertEqual(len(faces), 1)
face = faces[0]
box = face.location
expect_box = (98, 146, 233, 272)
# Calculate the location of the detected box and the expected box
iou = calculate_overlap(box, expect_box)
self.assertAlmostEqual(iou, 1.0, places=3)
# Extract feature
feature = self.engine.face_feature_extract(image, face)
self.assertIsNotNone(feature)
#
def test_face_comparison(self):
self.engine.set_track_mode(mode=HF_DETECT_MODE_IMAGE)
# Prepare two pictures of someone
images_path_list = [get_test_data("bulk/kun.jpg"), get_test_data("bulk/jntm.jpg")]
self.assertEqual(len(images_path_list), 2, "Only 2 photos can be used for the 1v1 scene.")
images = [cv2.imread(pth) for pth in images_path_list]
faces_list = [self.engine.face_detection(img) for img in images]
# Check num of faces detection
self.assertEqual(len(faces_list[0]), 1)
self.assertEqual(len(faces_list[1]), 1)
# Extract features
features = [self.engine.face_feature_extract(images[idx], faces[0]) for idx, faces in enumerate(faces_list)]
self.assertEqual(features[0].size, TEST_MODEL_FACE_FEATURE_LENGTH)
self.assertEqual(features[1].size, TEST_MODEL_FACE_FEATURE_LENGTH)
# Comparison
similarity = ifac.feature_comparison(features[0], features[1])
self.assertEqual(True, similarity > TEST_FACE_COMPARISON_IMAGE_THRESHOLD)
# Prepare a picture of a different person
woman = cv2.imread(get_test_data("bulk/woman.png"))
self.assertIsNotNone(woman)
woman_faces = self.engine.face_detection(woman)
self.assertEqual(len(woman_faces), 1)
face_3 = woman_faces[0]
feature = self.engine.face_feature_extract(woman, face_3)
self.assertEqual(feature.size, TEST_MODEL_FACE_FEATURE_LENGTH)
# Comparison
similarity = ifac.feature_comparison(features[0], feature)
self.assertEqual(True, similarity < TEST_FACE_COMPARISON_IMAGE_THRESHOLD)
similarity = ifac.feature_comparison(features[1], feature)
self.assertEqual(True, similarity < TEST_FACE_COMPARISON_IMAGE_THRESHOLD)
@optional(ENABLE_CRUD_TEST, "All CRUD related tests have been closed.")
class FaceRecognitionCRUDMemoryCase(unittest.TestCase):
"""
This case is mainly used to test the CRUD functions of face recognition.
"""
engine = None
default_faces_num = 10000
@classmethod
def setUpClass(cls):
config = ifac.FeatureHubConfiguration(
feature_block_num=20,
enable_use_db=False,
db_path="",
search_mode=HF_SEARCH_MODE_EAGER,
search_threshold=TEST_FACE_COMPARISON_IMAGE_THRESHOLD,
)
ifac.feature_hub_enable(config)
track_mode = HF_DETECT_MODE_IMAGE
param = ifac.SessionCustomParameter()
param.enable_recognition = True
cls.engine = ifac.InspireFaceSession(param, track_mode)
batch_import_lfw_faces(LFW_FUNNELED_DIR_PATH, cls.engine, cls.default_faces_num)
def test_face_search(self):
num_current = ifac.feature_hub_get_face_count()
registered = cv2.imread(get_test_data("bulk/kun.jpg"))
self.assertIsNotNone(registered)
faces = self.engine.face_detection(registered)
self.assertEqual(len(faces), 1)
face = faces[0]
feature = self.engine.face_feature_extract(registered, face)
self.assertEqual(feature.size, TEST_MODEL_FACE_FEATURE_LENGTH)
# Insert a new face
registered_identity = ifac.FaceIdentity(feature, custom_id=num_current + 1, tag="Kun")
ret = ifac.feature_hub_face_insert(registered_identity)
self.assertEqual(ret, True)
# Prepare a picture of searched face
searched = cv2.imread(get_test_data("bulk/jntm.jpg"))
self.assertIsNotNone(searched)
faces = self.engine.face_detection(searched)
self.assertEqual(len(faces), 1)
searched_face = faces[0]
feature = self.engine.face_feature_extract(searched, searched_face)
self.assertEqual(feature.size, TEST_MODEL_FACE_FEATURE_LENGTH)
searched_result = ifac.feature_hub_face_search(feature)
self.assertEqual(True, searched_result.confidence > TEST_FACE_COMPARISON_IMAGE_THRESHOLD)
self.assertEqual(searched_result.similar_identity.tag, registered_identity.tag)
self.assertEqual(searched_result.similar_identity.custom_id, registered_identity.custom_id)
# Prepare a picture of a stranger's face
stranger = cv2.imread(get_test_data("bulk/woman.png"))
self.assertIsNotNone(stranger)
faces = self.engine.face_detection(stranger)
self.assertEqual(len(faces), 1)
stranger_face = faces[0]
feature = self.engine.face_feature_extract(stranger, stranger_face)
self.assertEqual(feature.size, TEST_MODEL_FACE_FEATURE_LENGTH)
stranger_result = ifac.feature_hub_face_search(feature)
self.assertEqual(True, stranger_result.confidence < TEST_FACE_COMPARISON_IMAGE_THRESHOLD)
self.assertEqual(stranger_result.similar_identity.custom_id, -1)
#
def test_face_remove(self):
query_image = cv2.imread(get_test_data("bulk/Nathalie_Baye_0002.jpg"))
self.assertIsNotNone(query_image)
faces = self.engine.face_detection(query_image)
self.assertEqual(len(faces), 1)
query_face = faces[0]
feature = self.engine.face_feature_extract(query_image, query_face)
self.assertEqual(feature.size, TEST_MODEL_FACE_FEATURE_LENGTH)
# First search
result = ifac.feature_hub_face_search(feature)
self.assertEqual(True, result.confidence > TEST_FACE_COMPARISON_IMAGE_THRESHOLD)
self.assertEqual("Nathalie_Baye", result.similar_identity.tag)
# Remove that
remove_id = result.similar_identity.custom_id
ret = ifac.feature_hub_face_remove(remove_id)
self.assertEqual(ret, True)
# Second search
result = ifac.feature_hub_face_search(feature)
self.assertEqual(True, result.confidence < TEST_FACE_COMPARISON_IMAGE_THRESHOLD)
self.assertEqual(result.similar_identity.custom_id, -1)
# Reusability testing
new_face_image = cv2.imread(get_test_data("bulk/yifei.jpg"))
self.assertIsNotNone(new_face_image)
faces = self.engine.face_detection(new_face_image)
self.assertEqual(len(faces), 1)
new_face = faces[0]
feature = self.engine.face_feature_extract(new_face_image, new_face)
# Insert that
registered_identity = ifac.FaceIdentity(feature, custom_id=remove_id, tag="YF")
ifac.feature_hub_face_insert(registered_identity)
def test_face_update(self):
pass
@optional(ENABLE_BENCHMARK_TEST, "All benchmark related tests have been closed.")
class FaceRecognitionFeatureExtractCase(unittest.TestCase):
benchmark_results = list()
loop = 1
@classmethod
def setUpClass(cls):
cls.benchmark_results = []
def setUp(self) -> None:
# Prepare image
image = cv2.imread(get_test_data("bulk/kun.jpg"))
self.stream = ifac.ImageStream.load_from_cv_image(image)
self.assertIsNotNone(self.stream)
# Prepare material
track_mode = HF_DETECT_MODE_IMAGE
param = ifac.SessionCustomParameter()
param.enable_recognition = True
self.engine = ifac.InspireFaceSession(param, track_mode)
# Prepare a face
faces = self.engine.face_detection(self.stream)
# "kun.jpg" has only one face
self.assertEqual(len(faces), 1)
self.face = faces[0]
box = self.face.location
expect_box = (98, 146, 233, 272)
# Calculate the location of the detected box and the expected box
iou = calculate_overlap(box, expect_box)
self.assertAlmostEqual(iou, 1.0, places=3)
self.feature = self.engine.face_feature_extract(self.stream, self.face)
@benchmark(test_name="Feature Extract", loop=1000)
def test_benchmark_feature_extract(self):
self.engine.set_track_mode(HF_DETECT_MODE_IMAGE)
for _ in range(self.loop):
feature = self.engine.face_feature_extract(self.stream, self.face)
self.assertEqual(TEST_MODEL_FACE_FEATURE_LENGTH, feature.size)
@benchmark(test_name="Face comparison 1v1", loop=1000)
def test_benchmark_face_comparison1v1(self):
for _ in range(self.loop):
ifac.feature_comparison(self.feature, self.feature)
@classmethod
def tearDownClass(cls):
print_benchmark_table(cls.benchmark_results)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,152 @@
import unittest
from test import *
import inspireface as ifac
from inspireface.param import *
import cv2
class FaceTrackerCase(unittest.TestCase):
def setUp(self) -> None:
# Prepare material
track_mode = HF_DETECT_MODE_IMAGE # Use video mode
self.engine = ifac.InspireFaceSession(param=ifac.SessionCustomParameter(),
detect_mode=track_mode)
def test_face_detection_from_image(self):
image = cv2.imread(get_test_data("bulk/kun.jpg"))
self.assertIsNotNone(image)
# Detection
faces = self.engine.face_detection(image)
# "kun.jpg" has only one face
self.assertEqual(len(faces), 1)
face = faces[0]
expect_box = (98, 146, 233, 272)
# Calculate the location of the detected box and the expected box
iou = calculate_overlap(face.location, expect_box)
self.assertAlmostEqual(iou, 1.0, places=3)
# Prepare non-face images
any_image = cv2.imread(get_test_data("bulk/view.jpg"))
self.assertIsNotNone(any_image)
self.assertEqual(len(self.engine.face_detection(any_image)), 0)
def test_face_pose(self):
self.engine.set_track_mode(HF_DETECT_MODE_IMAGE)
# Test yaw (shake one's head)
left_face = cv2.imread(get_test_data("pose/left_face.jpeg"))
self.assertIsNotNone(left_face)
faces = self.engine.face_detection(left_face)
self.assertEqual(len(faces), 1)
left_face_yaw = faces[0].yaw
# The expected value is not completely accurate, it is only a rough estimate
expect_left_shake_range = (-90, -10)
self.assertEqual(True, expect_left_shake_range[0] < left_face_yaw < expect_left_shake_range[1])
right_face = cv2.imread(get_test_data("pose/right_face.png"))
self.assertIsNotNone(right_face)
faces = self.engine.face_detection(right_face)
self.assertEqual(len(faces), 1)
right_face_yaw = faces[0].yaw
expect_right_shake_range = (10, 90)
self.assertEqual(True, expect_right_shake_range[0] < right_face_yaw < expect_right_shake_range[1])
# Test pitch (nod head)
rise_face = cv2.imread(get_test_data("pose/rise_face.jpeg"))
self.assertIsNotNone(rise_face)
faces = self.engine.face_detection(rise_face)
self.assertEqual(len(faces), 1)
left_face_pitch = faces[0].pitch
self.assertEqual(True, left_face_pitch > 5)
lower_face = cv2.imread(get_test_data("pose/lower_face.jpeg"))
self.assertIsNotNone(lower_face)
faces = self.engine.face_detection(lower_face)
self.assertEqual(len(faces), 1)
lower_face_pitch = faces[0].pitch
self.assertEqual(True, lower_face_pitch < -10)
# Test roll (wryneck head)
left_wryneck_face = cv2.imread(get_test_data("pose/left_wryneck.png"))
self.assertIsNotNone(left_wryneck_face)
faces = self.engine.face_detection(left_wryneck_face)
self.assertEqual(len(faces), 1)
left_face_roll = faces[0].roll
self.assertEqual(True, left_face_roll < -30)
right_wryneck_face = cv2.imread(get_test_data("pose/right_wryneck.png"))
self.assertIsNotNone(right_wryneck_face)
faces = self.engine.face_detection(right_wryneck_face)
self.assertEqual(len(faces), 1)
right_face_roll = faces[0].roll
self.assertEqual(True, right_face_roll > 30)
def test_face_track_from_video(self):
self.engine.set_track_mode(HF_DETECT_MODE_VIDEO)
# Read a video file
video_gen = read_video_generator(get_test_data("video/810_1684206192.mp4"))
results = [self.engine.face_detection(frame) for frame in video_gen]
num_of_frame = len(results)
num_of_track_loss = len([faces for faces in results if not faces])
total_track_ids = [faces[0].track_id for faces in results if faces]
num_of_id_switch = len([id_ for id_ in total_track_ids if id_ != 1])
# Calculate the loss rate of trace loss and switching id
track_loss = num_of_track_loss / num_of_frame
id_switch_loss = num_of_id_switch / len(total_track_ids)
# Not rigorous, only for the current test of this video file
self.assertEqual(True, track_loss < 0.05)
self.assertEqual(True, id_switch_loss < 0.1)
@optional(ENABLE_BENCHMARK_TEST, "All benchmark related tests have been closed.")
class FaceTrackerBenchmarkCase(unittest.TestCase):
benchmark_results = list()
loop = 1
@classmethod
def setUpClass(cls):
cls.benchmark_results = []
def setUp(self) -> None:
# Prepare image
self.image = cv2.imread(get_test_data("bulk/kun.jpg"))
self.assertIsNotNone(self.image)
# Prepare material
track_mode = HF_DETECT_MODE_VIDEO # Use video mode
self.engine = ifac.InspireFaceSession(HF_ENABLE_NONE, track_mode, )
# Prepare video data
self.video_gen = read_video_generator(get_test_data("video/810_1684206192.mp4"))
@benchmark(test_name="Face Detect", loop=1000)
def test_benchmark_face_detect(self):
self.engine.set_track_mode(HF_DETECT_MODE_IMAGE)
for _ in range(self.loop):
faces = self.engine.face_detection(self.image)
self.assertEqual(len(faces), 1, "No face detected may have an error, please check.")
@benchmark(test_name="Face Track", loop=1000)
def test_benchmark_face_track(self):
self.engine.set_track_mode(HF_DETECT_MODE_VIDEO)
for _ in range(self.loop):
faces = self.engine.face_detection(self.image)
self.assertEqual(len(faces), 1, "No face detected may have an error, please check.")
@benchmark(test_name="Face Track(Video)", loop=345)
def test_benchmark_face_track_video(self):
self.engine.set_track_mode(HF_DETECT_MODE_VIDEO)
for frame in self.video_gen:
faces = self.engine.face_detection(frame)
self.assertEqual(len(faces), 1, "No face detected may have an error, please check.")
@classmethod
def tearDownClass(cls):
print_benchmark_table(cls.benchmark_results)
if __name__ == '__main__':
unittest.main()