From 7e25e4e4829559a8764a66013e10fdb144cb59db Mon Sep 17 00:00:00 2001 From: tunm Date: Thu, 22 May 2025 16:07:26 +0800 Subject: [PATCH] Update --- cpp-package/inspireface/.gitignore | 2 +- cpp-package/inspireface/CMakeLists.txt | 43 +- cpp-package/inspireface/README.md | 163 ++++-- .../inspireface_example/MainActivity.java | 2 +- .../ci/quick_test_linux_x86_usual.sh | 1 + ...linux_x86_usual_python_native_interface.sh | 1 + .../inspireface/ci/quick_test_local.sh | 1 + cpp-package/inspireface/command/build.sh | 5 +- .../inspireface/command/build_android.sh | 25 +- .../command/build_android_rk356x_rk3588.sh | 4 +- .../command/build_cross_aarch64.sh | 2 + .../command/build_cross_armv7_armhf.sh | 2 + .../build_cross_rk356x_rk3588_aarch64.sh | 4 +- .../build_cross_rv1106_armhf_uclibc.sh | 8 +- .../command/build_cross_rv1109rv1126_armhf.sh | 2 + .../inspireface/command/build_ios_coreml.sh | 157 ++++++ .../inspireface/command/build_linux_cuda.sh | 2 + .../command/build_linux_cuda_dev.sh | 2 + .../command/build_linux_manylinux2014.sh | 2 + .../command/build_linux_tensorrt.sh | 5 +- .../command/build_linux_ubuntu18.sh | 7 +- .../inspireface/command/build_macos_arm64.sh | 5 +- .../command/build_macos_coreml_arm64.sh | 54 ++ .../command/build_macos_coreml_x86.sh | 54 ++ .../inspireface/command/build_macos_x86.sh | 5 +- .../command/build_wheel_macos_arm64.sh | 5 +- .../command/build_wheel_macos_x86.sh | 5 +- .../command/build_wheel_manylinux2014_x86.sh | 5 +- cpp-package/inspireface/command/test.sh | 66 --- .../cpp/inspireface/CMakeLists.txt | 18 +- .../Initialization_module/launch.cpp | 179 ------ .../cpp/inspireface/c_api/inspireface.cc | 246 +++++++-- .../cpp/inspireface/c_api/inspireface.h | 109 +++- .../inspireface/c_api/inspireface_internal.h | 6 +- .../common/face_data/face_serialize_tools.h | 44 +- .../common/face_info/face_action_data.h | 6 +- .../common/face_info/face_object_internal.h | 5 +- .../inspireface/{ => engine}/face_session.cpp | 83 ++- .../inspireface/{ => engine}/face_session.h | 76 ++- .../feature_hub/embedding_db/embedding_db.cpp | 41 +- .../feature_hub/embedding_db/embedding_db.h | 8 +- .../feature_hub/feature_hub_db.cpp | 184 ++++--- .../image_process/frame_process.cpp | 395 +++++++++++++ .../nexus_processor/image_processor.cpp | 0 .../nexus_processor/image_processor.h | 0 .../image_processor_general.cpp | 0 .../nexus_processor/image_processor_general.h | 0 .../nexus_processor/image_processor_rga.cpp | 0 .../nexus_processor/image_processor_rga.h | 4 +- .../nexus_processor/rga/dma_alloc.cpp | 0 .../nexus_processor/rga/dma_alloc.h | 0 .../nexus_processor/rga/utils.cpp | 0 .../nexus_processor/rga/utils.h | 0 .../include/inspireface/cuda_toolkit.h | 22 + .../{ => include/inspireface}/data_type.h | 107 +++- .../inspireface/face_warpper.h} | 11 +- .../inspireface}/feature_hub_db.h | 139 ++--- .../include/inspireface/frame_process.h | 198 +++++++ .../{ => include/inspireface}/herror.h | 0 .../include/inspireface/inspireface.hpp | 14 + .../include/inspireface/isf_check.h | 33 ++ .../inspireface}/launch.h | 89 +-- .../cpp/inspireface/include/inspireface/log.h | 89 +++ .../inspireface/include/inspireface/session.h | 199 +++++++ .../inspireface}/similarity_converter.h | 3 +- .../include/inspireface/spend_timer.h | 51 ++ .../inspireface/cpp/inspireface/information.h | 15 - .../inspireface/cpp/inspireface/isf_check.h | 42 -- .../inspireface/cpp/inspireface/log.cpp | 143 ++++- cpp-package/inspireface/cpp/inspireface/log.h | 184 ------- .../inspireface/middleware/any_net_adapter.h | 18 +- .../{cuda_toolkit.h => cuda_toolkit.cpp} | 39 +- .../inference_wrapper/inference_wrapper.h | 6 + .../inference_wrapper_tensorrt.cpp | 2 +- .../middleware/inspirecv_image_process.h | 392 ------------- .../model_archive/inspire_archive.h | 39 +- .../cpp/inspireface/middleware/timer.cpp | 111 ++++ .../pipeline_module/face_pipeline_module.cpp | 112 ++-- .../pipeline_module/face_pipeline_module.h | 13 +- .../platform/jni/android/inspireface_jni.cpp | 50 +- .../face_feature_extraction_module.cpp | 36 +- .../face_feature_extraction_module.h | 34 +- .../cpp/inspireface/runtime_module/launch.cpp | 294 ++++++++++ .../resource_manage.cpp | 0 .../resource_manage.h | 45 ++ .../inspireface/cpp/inspireface/session.cpp | 277 ++++++++++ .../face_detect/face_detect_adapt.cpp | 12 +- .../face_detect/face_detect_adapt.h | 10 +- .../track_module/face_detect/rnet_adapt.h | 2 +- .../track_module/face_track_module.cpp | 247 +++++---- .../track_module/face_track_module.h | 69 ++- .../inspireface/track_module/landmark/all.h | 3 + .../landmark/face_landmark_adapt.cpp | 10 +- .../landmark/face_landmark_adapt.h | 12 +- .../track_module/landmark/landmark_param.h | 127 +++++ .../track_module/landmark/landmark_tools.h | 126 +++++ .../track_module/landmark/mean_shape.h | 3 +- .../landmark}/order_of_hyper_landmark.h | 4 +- .../quality/face_pose_quality_adapt.cpp | 9 +- .../quality/face_pose_quality_adapt.h | 2 +- .../inspireface/cpp/inspireface/version.txt | 2 +- .../inspireface/cpp/sample/CMakeLists.txt | 398 ++++++++------ .../cpp/sample/api/{leak.cpp => leak.c} | 3 +- .../sample/api/sample_cpp_resource_pool.cpp | 100 ++++ ...omparison.cpp => sample_face_comparison.c} | 165 +++--- .../cpp/sample/api/sample_face_crud.c | 178 ++++++ ...ple_face_track.cpp => sample_face_track.c} | 174 +++--- ...mark.cpp => sample_face_track_benchmark.c} | 49 +- ...e_feature_hub.cpp => sample_feature_hub.c} | 15 +- .../api/sample_feature_hub_persistence.c | 52 ++ ...e_load_reload.cpp => sample_load_reload.c} | 5 +- .../cluttered/rk_sample/debug_rk_rec.cpp | 2 +- .../rk_sample/rk_face_det_sample.cpp | 2 +- .../rk_sample/rk_face_recognize_sample.cpp | 2 +- .../rk_sample/rk_simple_net_sample.cpp | 12 +- .../sample/cluttered/standard/net_sample.cpp | 2 +- .../sample/cluttered/standard/test_sample.cpp | 4 +- .../cpp/sample/cpp_api/cpp_sample_affine.cpp | 84 +++ .../cpp_api/cpp_sample_face_comparison.cpp | 78 +++ .../sample/cpp_api/cpp_sample_face_crud.cpp | 80 +++ .../sample/cpp_api/cpp_sample_face_track.cpp | 56 ++ .../sample/cpp_api/cpp_sample_inspirecv.cpp | 200 +++++++ .../cpp/sample/rv1106/face_attribute.cpp | 13 +- .../cpp/sample/rv1106/face_detect.cpp | 15 +- .../cpp/sample/rv1106/rga_image.cpp | 17 +- .../cpp/sample/source/expansion_load.cpp | 33 +- .../cpp/sample/source/feature_hub_sample.cpp | 14 +- .../cpp/sample/source/landmark_sample.cpp | 10 +- .../cpp/sample/source/tracker_pipeline.cpp | 10 +- .../cpp/sample/source/tracker_sample.cpp | 10 +- .../inspireface/cpp/test/CMakeLists.txt | 51 +- .../cpp/test/settings/test_settings.h | 1 + cpp-package/inspireface/cpp/test/test.cpp | 2 +- .../inspireface/cpp/test/test_base.cpp | 4 +- cpp-package/inspireface/cpp/test/test_cpp.cpp | 138 +++++ .../cpp/test/unit/api/test_benchmark.cpp | 24 +- .../cpp/test/unit/api/test_face_pipeline.cpp | 6 +- .../cpp/test/unit/api/test_face_track.cpp | 153 +++++- .../test/unit/api/test_session_parallel.cpp | 6 +- .../unit/api/test_similarity_converter.cpp | 2 +- .../cpp/test/unit/api/test_system.cpp | 23 +- .../cpp/test/unit/base/test_face_session.cpp | 25 +- .../unit/base/test_module_feature_hub.cpp | 74 +-- .../cpp/test/unit/base/test_module_track.cpp | 29 +- .../unit/cpp_api/test_session_face_track.cpp | 118 ++++ cpp-package/inspireface/doc/CMake-Option.md | 4 + .../docker/Dockerfile.arm-linux-aarch64 | 9 +- .../docker/Dockerfile.arm-linux-gnueabihf | 9 +- ...kerfile.arm-linux-rockchip830-armhf-uclibc | 9 +- .../docker/Dockerfile.cuda.ubuntu18 | 15 +- .../docker/Dockerfile.cuda.ubuntu20 | 16 +- .../docker/Dockerfile.cuda12_ubuntu22 | 10 +- .../inspireface/docker/Dockerfile.ubuntu18 | 45 +- .../python/inspireface/modules/__init__.py | 6 +- .../python/inspireface/modules/core/native.py | 519 ++++++++++-------- .../python/inspireface/modules/inspireface.py | 66 ++- .../inspireface/modules/utils/resource.py | 17 +- .../inspireface/python/inspireface/param.py | 3 +- cpp-package/inspireface/python/read_nv21.py | 71 +++ .../python/sample_face_detection.py | 74 +-- .../python/sample_face_track_from_video.py | 14 +- .../inspireface/python/sample_feature_hub.py | 68 +-- cpp-package/inspireface/python/setup.py | 30 +- .../test_res/data/bulk/kun_cartoon_crop.jpg | Bin 0 -> 36746 bytes .../data/bulk/kun_cartoon_crop_r90.jpg | Bin 0 -> 38091 bytes .../test_res/data/bulk/kun_crop.jpg | Bin 0 -> 106983 bytes .../tools/generate_release_models_info.py | 46 ++ .../inspireface/tools/get_model_md5.py | 3 + 168 files changed, 6434 insertions(+), 2527 deletions(-) create mode 100644 cpp-package/inspireface/command/build_ios_coreml.sh create mode 100644 cpp-package/inspireface/command/build_macos_coreml_arm64.sh create mode 100644 cpp-package/inspireface/command/build_macos_coreml_x86.sh delete mode 100644 cpp-package/inspireface/command/test.sh delete mode 100644 cpp-package/inspireface/cpp/inspireface/Initialization_module/launch.cpp rename cpp-package/inspireface/cpp/inspireface/{ => engine}/face_session.cpp (84%) rename cpp-package/inspireface/cpp/inspireface/{ => engine}/face_session.h (87%) create mode 100644 cpp-package/inspireface/cpp/inspireface/image_process/frame_process.cpp rename cpp-package/inspireface/cpp/inspireface/{middleware => image_process}/nexus_processor/image_processor.cpp (100%) rename cpp-package/inspireface/cpp/inspireface/{middleware => image_process}/nexus_processor/image_processor.h (100%) rename cpp-package/inspireface/cpp/inspireface/{middleware => image_process}/nexus_processor/image_processor_general.cpp (100%) rename cpp-package/inspireface/cpp/inspireface/{middleware => image_process}/nexus_processor/image_processor_general.h (100%) rename cpp-package/inspireface/cpp/inspireface/{middleware => image_process}/nexus_processor/image_processor_rga.cpp (100%) rename cpp-package/inspireface/cpp/inspireface/{middleware => image_process}/nexus_processor/image_processor_rga.h (97%) rename cpp-package/inspireface/cpp/inspireface/{middleware => image_process}/nexus_processor/rga/dma_alloc.cpp (100%) rename cpp-package/inspireface/cpp/inspireface/{middleware => image_process}/nexus_processor/rga/dma_alloc.h (100%) rename cpp-package/inspireface/cpp/inspireface/{middleware => image_process}/nexus_processor/rga/utils.cpp (100%) rename cpp-package/inspireface/cpp/inspireface/{middleware => image_process}/nexus_processor/rga/utils.h (100%) create mode 100644 cpp-package/inspireface/cpp/inspireface/include/inspireface/cuda_toolkit.h rename cpp-package/inspireface/cpp/inspireface/{ => include/inspireface}/data_type.h (52%) rename cpp-package/inspireface/cpp/inspireface/{common/face_data/face_data_type.h => include/inspireface/face_warpper.h} (88%) rename cpp-package/inspireface/cpp/inspireface/{feature_hub => include/inspireface}/feature_hub_db.h (64%) create mode 100644 cpp-package/inspireface/cpp/inspireface/include/inspireface/frame_process.h rename cpp-package/inspireface/cpp/inspireface/{ => include/inspireface}/herror.h (100%) create mode 100644 cpp-package/inspireface/cpp/inspireface/include/inspireface/inspireface.hpp create mode 100644 cpp-package/inspireface/cpp/inspireface/include/inspireface/isf_check.h rename cpp-package/inspireface/cpp/inspireface/{Initialization_module => include/inspireface}/launch.h (54%) create mode 100755 cpp-package/inspireface/cpp/inspireface/include/inspireface/log.h create mode 100644 cpp-package/inspireface/cpp/inspireface/include/inspireface/session.h rename cpp-package/inspireface/cpp/inspireface/{recognition_module => include/inspireface}/similarity_converter.h (98%) create mode 100644 cpp-package/inspireface/cpp/inspireface/include/inspireface/spend_timer.h delete mode 100644 cpp-package/inspireface/cpp/inspireface/information.h delete mode 100644 cpp-package/inspireface/cpp/inspireface/isf_check.h delete mode 100755 cpp-package/inspireface/cpp/inspireface/log.h rename cpp-package/inspireface/cpp/inspireface/middleware/{cuda_toolkit.h => cuda_toolkit.cpp} (84%) delete mode 100644 cpp-package/inspireface/cpp/inspireface/middleware/inspirecv_image_process.h create mode 100644 cpp-package/inspireface/cpp/inspireface/middleware/timer.cpp create mode 100644 cpp-package/inspireface/cpp/inspireface/runtime_module/launch.cpp rename cpp-package/inspireface/cpp/inspireface/{Initialization_module => runtime_module}/resource_manage.cpp (100%) rename cpp-package/inspireface/cpp/inspireface/{Initialization_module => runtime_module}/resource_manage.h (77%) create mode 100644 cpp-package/inspireface/cpp/inspireface/session.cpp create mode 100644 cpp-package/inspireface/cpp/inspireface/track_module/landmark/landmark_param.h create mode 100644 cpp-package/inspireface/cpp/inspireface/track_module/landmark/landmark_tools.h rename cpp-package/inspireface/cpp/inspireface/{pipeline_module/liveness => track_module/landmark}/order_of_hyper_landmark.h (74%) rename cpp-package/inspireface/cpp/sample/api/{leak.cpp => leak.c} (63%) create mode 100644 cpp-package/inspireface/cpp/sample/api/sample_cpp_resource_pool.cpp rename cpp-package/inspireface/cpp/sample/api/{sample_face_comparison.cpp => sample_face_comparison.c} (53%) create mode 100644 cpp-package/inspireface/cpp/sample/api/sample_face_crud.c rename cpp-package/inspireface/cpp/sample/api/{sample_face_track.cpp => sample_face_track.c} (59%) rename cpp-package/inspireface/cpp/sample/api/{sample_face_track_benchmark.cpp => sample_face_track_benchmark.c} (76%) rename cpp-package/inspireface/cpp/sample/api/{sample_feature_hub.cpp => sample_feature_hub.c} (96%) create mode 100644 cpp-package/inspireface/cpp/sample/api/sample_feature_hub_persistence.c rename cpp-package/inspireface/cpp/sample/api/{sample_load_reload.cpp => sample_load_reload.c} (74%) create mode 100644 cpp-package/inspireface/cpp/sample/cpp_api/cpp_sample_affine.cpp create mode 100644 cpp-package/inspireface/cpp/sample/cpp_api/cpp_sample_face_comparison.cpp create mode 100644 cpp-package/inspireface/cpp/sample/cpp_api/cpp_sample_face_crud.cpp create mode 100644 cpp-package/inspireface/cpp/sample/cpp_api/cpp_sample_face_track.cpp create mode 100644 cpp-package/inspireface/cpp/sample/cpp_api/cpp_sample_inspirecv.cpp create mode 100644 cpp-package/inspireface/cpp/test/test_cpp.cpp create mode 100644 cpp-package/inspireface/cpp/test/unit/cpp_api/test_session_face_track.cpp create mode 100644 cpp-package/inspireface/python/read_nv21.py create mode 100644 cpp-package/inspireface/test_res/data/bulk/kun_cartoon_crop.jpg create mode 100644 cpp-package/inspireface/test_res/data/bulk/kun_cartoon_crop_r90.jpg create mode 100644 cpp-package/inspireface/test_res/data/bulk/kun_crop.jpg create mode 100644 cpp-package/inspireface/tools/generate_release_models_info.py diff --git a/cpp-package/inspireface/.gitignore b/cpp-package/inspireface/.gitignore index 0f997e9..1619271 100644 --- a/cpp-package/inspireface/.gitignore +++ b/cpp-package/inspireface/.gitignore @@ -19,7 +19,7 @@ pack/* .vscode/* build_local/* local_build/* -cpp/inspireface/information.h +cpp/inspireface/include/inspireface/information.h cpp/inspireface/version.txt .DS_Store ._.DS_Store diff --git a/cpp-package/inspireface/CMakeLists.txt b/cpp-package/inspireface/CMakeLists.txt index 567069e..fd01d09 100644 --- a/cpp-package/inspireface/CMakeLists.txt +++ b/cpp-package/inspireface/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.20) project(InspireFace) set(CMAKE_CXX_STANDARD 14) @@ -7,16 +7,30 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3") +# Hide symbols +option(ISF_ENABLE_SYMBOL_HIDING "Enable symbol hiding." ON) +if(ISF_ENABLE_SYMBOL_HIDING) + if(NOT WIN32) + set(CMAKE_C_VISIBILITY_PRESET hidden) + set(CMAKE_CXX_VISIBILITY_PRESET hidden) + set(CMAKE_VISIBILITY_INLINES_HIDDEN YES) + endif() +else() + set(CMAKE_C_VISIBILITY_PRESET default) + set(CMAKE_CXX_VISIBILITY_PRESET default) + set(CMAKE_VISIBILITY_INLINES_HIDDEN NO) +endif() + # Current version set(INSPIRE_FACE_VERSION_MAJOR 1) set(INSPIRE_FACE_VERSION_MINOR 2) -set(INSPIRE_FACE_VERSION_PATCH 0) +set(INSPIRE_FACE_VERSION_PATCH 1) # Converts the version number to a string string(CONCAT INSPIRE_FACE_VERSION_MAJOR_STR ${INSPIRE_FACE_VERSION_MAJOR}) string(CONCAT INSPIRE_FACE_VERSION_MINOR_STR ${INSPIRE_FACE_VERSION_MINOR}) string(CONCAT INSPIRE_FACE_VERSION_PATCH_STR ${INSPIRE_FACE_VERSION_PATCH}) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cpp/inspireface/information.h.in ${CMAKE_CURRENT_SOURCE_DIR}/cpp/inspireface/information.h) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cpp/inspireface/information.h.in ${CMAKE_CURRENT_SOURCE_DIR}/cpp/inspireface/include/inspireface/information.h) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cpp/inspireface/version.txt.in ${CMAKE_CURRENT_SOURCE_DIR}/cpp/inspireface/version.txt) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/python/version.txt.in ${CMAKE_CURRENT_SOURCE_DIR}/python/version.txt) @@ -40,6 +54,9 @@ else() message(STATUS "3rdparty directory already exists") endif() +# Install cpp api header file +option(ISF_INSTALL_CPP_HEADER "Install cpp api header file." ON) + # Set the ISF_THIRD_PARTY_DIR variable to allow it to be set externally from the command line, or use the default path if it is not set set(ISF_THIRD_PARTY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/3rdparty" CACHE PATH "Path to the third-party libraries directory") @@ -214,13 +231,11 @@ if(ISF_ENABLE_OPENCV) if (ISF_BUILD_LINUX_ARM7 OR ISF_BUILD_LINUX_AARCH64) set(DISABLE_GUI ON) add_definitions("-DDISABLE_GUI") - # set(OpenCV_DIR ${ISF_THIRD_PARTY_DIR}/opencv/opencv-linux-armhf/share/OpenCV) - # set(OpenCV_STATIC_INCLUDE_DIR ${PATH_3RDPARTY}/opencv/opencv-linux-armhf/include/) if (ISF_RK_DEVICE_TYPE STREQUAL "RV1109RV1126" AND ISF_ENABLE_RKNN) # In special cases, specialize for that version message("The OpenCV that builds the RV1109RV1126 version depends on is specialized!") - set(OpenCV_DIR ${ISF_THIRD_PARTY_DIR}/inspireface-precompile/opencv/3.4.5/opencv-linux-armhf/share/OpenCV) - set(OpenCV_STATIC_INCLUDE_DIR ${PATH_3RDPARTY}/inspireface-precompile/opencv/3.4.5/opencv-linux-armhf/include/) + set(OpenCV_DIR ${OPENCV_PRECOMPILED_DIR}/opencv/3.4.5/opencv-linux-armhf/share/OpenCV) + set(OpenCV_STATIC_INCLUDE_DIR ${OPENCV_PRECOMPILED_DIR}/opencv/3.4.5/opencv-linux-armhf/include/) set(PLAT linux-arm7) else() if (VERSION_MAJOR STREQUAL "3") @@ -231,13 +246,13 @@ if(ISF_ENABLE_OPENCV) if(ISF_BUILD_LINUX_ARM7) set(PLAT linux-arm7) message("The OpenCV that builds the gnueabihf version depends on is specialized!") - set(OpenCV_DIR ${ISF_THIRD_PARTY_DIR}/inspireface-precompile/opencv/3.4.5/opencv-linux-armhf/share/OpenCV) - set(OpenCV_STATIC_INCLUDE_DIR ${PATH_3RDPARTY}/inspireface-precompile/opencv/3.4.5/opencv-linux-armhf/include/) + set(OpenCV_DIR ${OPENCV_PRECOMPILED_DIR}/opencv/3.4.5/opencv-linux-armhf/share/OpenCV) + set(OpenCV_STATIC_INCLUDE_DIR ${OPENCV_PRECOMPILED_DIR}/opencv/3.4.5/opencv-linux-armhf/include/) elseif(ISF_BUILD_LINUX_AARCH64) set(PLAT linux-aarch64) message("The OpenCV that builds the aarch64 version depends on is specialized!") - set(OpenCV_DIR ${ISF_THIRD_PARTY_DIR}/inspireface-precompile/opencv/3.4.5/opencv-linux-aarch64/share/OpenCV) - set(OpenCV_STATIC_INCLUDE_DIR ${PATH_3RDPARTY}/inspireface-precompile/opencv/3.4.5/opencv-linux-aarch64/include/) + set(OpenCV_DIR ${OPENCV_PRECOMPILED_DIR}/opencv/3.4.5/opencv-linux-aarch64/share/OpenCV) + set(OpenCV_STATIC_INCLUDE_DIR ${OPENCV_PRECOMPILED_DIR}/opencv/3.4.5/opencv-linux-aarch64/include/) endif() endif() else () @@ -350,4 +365,10 @@ if(ISF_ENABLE_APPLE_EXTENSION) message(STATUS "\t ISF_ENABLE_APPLE_EXTENSION: ${ISF_ENABLE_APPLE_EXTENSION}") endif() +# Install cpp api header file +if(ISF_INSTALL_CPP_HEADER) + message(STATUS "\t ISF_INSTALL_CPP_HEADER: ${ISF_INSTALL_CPP_HEADER}") +endif() + + message(STATUS "\t CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}") diff --git a/cpp-package/inspireface/README.md b/cpp-package/inspireface/README.md index 37990c9..6405b0f 100644 --- a/cpp-package/inspireface/README.md +++ b/cpp-package/inspireface/README.md @@ -5,6 +5,8 @@ [![JitPack](https://img.shields.io/jitpack/v/github/HyperInspire/inspireface-android-sdk?style=for-the-badge&color=green&label=JitPack&logo=android)](https://jitpack.io/#HyperInspire/inspireface-android-sdk) [![build](https://img.shields.io/github/actions/workflow/status/HyperInspire/InspireFace/release-sdks.yaml?&style=for-the-badge&label=building&logo=cmake)](https://github.com/HyperInspire/InspireFace/actions/workflows/release-sdks.yaml) [![test](https://img.shields.io/github/actions/workflow/status/HyperInspire/InspireFace/release-sdks.yaml?&style=for-the-badge&label=testing&logo=c)](https://github.com/HyperInspire/InspireFace/actions/workflows/test_ubuntu_x86_Pikachu.yaml) +[![Document](https://img.shields.io/badge/Document-Building-blue?style=for-the-badge&logo=readthedocs)](https://doc.inspireface.online/) + InspireFace is a cross-platform face recognition SDK developed in C/C++, supporting multiple operating systems and various backend types for inference, such as CPU, GPU, and NPU. @@ -15,8 +17,15 @@ Please contact [contact@insightface.ai](mailto:contact@insightface.ai?subject=In banner +--- + +📘 [Documentation](https://doc.inspireface.online/) is a **work in progress**. +We welcome your questions💬, they help guide and accelerate its development. + ## Change Logs +**`2025-04-27`** Optimize some issues and provide a stable version. + **`2025-03-16`** Acceleration using NVIDIA-GPU (**CUDA**) devices is already supported. **`2025-03-09`** Release of android sdk in JitPack. @@ -49,26 +58,18 @@ Please contact [contact@insightface.ai](mailto:contact@insightface.ai?subject=In **`2024-07-02`** Fixed several bugs in the face detector with multi-level input. -**`2024-06-27`** Verified iOS usability and fixed some bugs. - -**`2024-06-18`** Added face detection feature with tracking-by-detection mode. - - ## License The licensing of the open-source models employed by InspireFace adheres to the same requirements as InsightFace, specifying their use solely for academic purposes and explicitly prohibiting commercial applications. - ## Quick Start -For Python users on Linux and MacOS, InspireFace can be quickly installed via pip: +For Python users on **Linux and MacOS**, InspireFace can be quickly installed via pip: ```bash pip install inspireface ``` -_⚠️Windows support is **not available yet**, but will be coming soon!_ - After installation, you can use inspireface like this: ```Python @@ -176,7 +177,7 @@ The '**3rdparty**' directory already includes the MNN library and specifies a pa ### Requirements -- CMake (version 3.10 or higher) +- CMake (version 3.20 or higher) - NDK (version 16 or higher, only required for Android) [**Optional**] - MNN (version 1.4.0 or higher) - C++ Compiler @@ -210,7 +211,10 @@ After compilation, you can find the local file in the build directory, which con inspireface-linux ├── include │ ├── herror.h - │ └── inspireface.h + │ ├── intypedef.h + │ ├── inspireface.h + │ ├── inspirecv/ + │ └── inspireface/ └── lib └── libInspireFace.so ``` @@ -218,6 +222,9 @@ inspireface-linux - **libInspireFace.so**:Compiled dynamic linking library. - **inspireface.h**:Header file definition. - **herror.h**:Reference error number definition. +- **intypedef.h**: Type definition file. +- **inspirecv**: Simple cv library CPP header file folder. +- **inspireface**: inspireface cpp header folder. ### Cross Compilation Cross compilation requires you to prepare the target platform's cross-compilation toolchain on the host machine in advance. Here, compiling for Rockchip's embedded devices RV1106 is used as an example: ```bash @@ -272,6 +279,10 @@ docker-compose up build-tensorrt-cuda12-ubuntu22 If you want to use pre-compiled libraries, you can use **[FindTensorRT.cmake](toolchain/FindTensorRT.cmake)** to create links to CUDA and TensorRT. +### React Native + +For Android and iOS, in addition to the native interface, you can use the React Native library powered by Nitro Modules and JSI—providing ultra-fast, seamless bindings to the InspireFace SDK. For more details, check out the [react-native-nitro-inspire-face](https://github.com/ronickg/react-native-nitro-inspire-face) repository or the [documentation](https://ronickg.github.io/react-native-nitro-inspire-face). Author: ronickg. + ### Supported Platforms and Architectures We have completed the adaptation and testing of the software across various operating systems and CPU architectures. This includes compatibility verification for platforms such as Linux, macOS, iOS, and Android, as well as testing for specific hardware support to ensure stable operation in diverse environments. @@ -292,10 +303,11 @@ We have completed the adaptation and testing of the software across various oper | 12 | **iOS** | ARM | CPU/Metal/**ANE** | [![](https://img.shields.io/badge/%E2%9C%93-green)](#) | [![](https://img.shields.io/badge/%E2%9C%93-green)](#) | [![build](https://img.shields.io/github/actions/workflow/status/HyperInspire/InspireFace/release-sdks.yaml?label=✓&labelColor=success&color=success&failedLabel=✗&failedColor=critical&logo=github&logoColor=white)](https://github.com/HyperInspire/InspireFace/actions/workflows/release-sdks.yaml) | | 13 | **Android** | ARMv7 | - | [![](https://img.shields.io/badge/%E2%9C%93-green)](#) | [![](https://img.shields.io/badge/%E2%9C%93-green)](#) | [![build](https://img.shields.io/github/actions/workflow/status/HyperInspire/InspireFace/release-sdks.yaml?label=✓&labelColor=success&color=success&failedLabel=✗&failedColor=critical&logo=github&logoColor=white)](https://github.com/HyperInspire/InspireFace/actions/workflows/release-sdks.yaml) | | 14 | | ARMv8 | - | [![](https://img.shields.io/badge/%E2%9C%93-green)](#) | [![](https://img.shields.io/badge/%E2%9C%93-green)](#) | [![build](https://img.shields.io/github/actions/workflow/status/HyperInspire/InspireFace/release-sdks.yaml?label=✓&labelColor=success&color=success&failedLabel=✗&failedColor=critical&logo=github&logoColor=white)](https://github.com/HyperInspire/InspireFace/actions/workflows/release-sdks.yaml) | -| 15 | **Android**
(Rockchip) | ARMv8 | RK3566/RK3568 | [![](https://img.shields.io/badge/%E2%9C%93-green)](#) | [![](https://img.shields.io/badge/%E2%9C%93-green)](#) | [![build](https://img.shields.io/github/actions/workflow/status/HyperInspire/InspireFace/release-sdks.yaml?label=✓&labelColor=success&color=success&failedLabel=✗&failedColor=critical&logo=github&logoColor=white)](https://github.com/HyperInspire/InspireFace/actions/workflows/release-sdks.yaml) | -| 16 | | ARMv8 | RK3588 | [![](https://img.shields.io/badge/%E2%9C%93-green)](#) | [![](https://img.shields.io/badge/%E2%9C%93-green)](#) | [![build](https://img.shields.io/github/actions/workflow/status/HyperInspire/InspireFace/release-sdks.yaml?label=✓&labelColor=success&color=success&failedLabel=✗&failedColor=critical&logo=github&logoColor=white)](https://github.com/HyperInspire/InspireFace/actions/workflows/release-sdks.yaml) | -| 17 | **HarmonyOS** | ARMv8 | - | - | - | - | -| 18 | **Linux**
(Jetson series) | ARMv8 | Jetson series | - | - | - | +| 15 | | x86_64 | - | [![](https://img.shields.io/badge/%E2%9C%93-green)](#) | [![](https://img.shields.io/badge/%E2%9C%93-green)](#) | [![build](https://img.shields.io/github/actions/workflow/status/HyperInspire/InspireFace/release-sdks.yaml?label=✓&labelColor=success&color=success&failedLabel=✗&failedColor=critical&logo=github&logoColor=white)](https://github.com/HyperInspire/InspireFace/actions/workflows/release-sdks.yaml) | +| 16 | **Android**
(Rockchip) | ARMv8 | RK3566/RK3568 | [![](https://img.shields.io/badge/%E2%9C%93-green)](#) | [![](https://img.shields.io/badge/%E2%9C%93-green)](#) | [![build](https://img.shields.io/github/actions/workflow/status/HyperInspire/InspireFace/release-sdks.yaml?label=✓&labelColor=success&color=success&failedLabel=✗&failedColor=critical&logo=github&logoColor=white)](https://github.com/HyperInspire/InspireFace/actions/workflows/release-sdks.yaml) | +| 17 | | ARMv8 | RK3588 | [![](https://img.shields.io/badge/%E2%9C%93-green)](#) | [![](https://img.shields.io/badge/%E2%9C%93-green)](#) | [![build](https://img.shields.io/github/actions/workflow/status/HyperInspire/InspireFace/release-sdks.yaml?label=✓&labelColor=success&color=success&failedLabel=✗&failedColor=critical&logo=github&logoColor=white)](https://github.com/HyperInspire/InspireFace/actions/workflows/release-sdks.yaml) | +| 18 | **HarmonyOS** | ARMv8 | - | - | - | - | +| 19 | **Linux**
(Jetson series) | ARMv8 | Jetson series | - | - | - | - **Device**: Some special device support, primarily focused on computing power devices. - **Supported**: The solution has been fully developed and successfully verified on offline devices. @@ -332,15 +344,20 @@ docker-compose up ``` ## Example -### C/C++ Sample -To integrate InspireFace into a C/C++ project, you simply need to link the InspireFace library and include the appropriate header files. Below is a basic example demonstrating face detection: +### C/C++ Sample: Use the recommended CAPI interface +To integrate InspireFace into a C/C++ project, you simply need to link the InspireFace library and include the appropriate header files(We recommend using the more compatible **CAPI** headers). Below is a basic example demonstrating face detection: ```c +#include +#include + +... + HResult ret; // The resource file must be loaded before it can be used ret = HFLaunchInspireFace(packPath); if (ret != HSUCCEED) { - std::cout << "Load Resource error: " << ret << std::endl; + HFLogPrint(HF_LOG_ERROR, "Load Resource error: %d", ret); return ret; } @@ -358,10 +375,11 @@ HInt32 detectPixelLevel = 160; HFSession session = {0}; ret = HFCreateInspireFaceSessionOptional(option, detMode, maxDetectNum, detectPixelLevel, -1, &session); if (ret != HSUCCEED) { - std::cout << "Create FaceContext error: " << ret << std::endl; + HFLogPrint(HF_LOG_ERROR, "Create FaceContext error: %d", ret); return ret; } +// Configure some detection parameters HFSessionSetTrackPreviewSize(session, detectPixelLevel); HFSessionSetFilterMinimumFacePixelSize(session, 4); @@ -369,14 +387,14 @@ HFSessionSetFilterMinimumFacePixelSize(session, 4); HFImageBitmap image; ret = HFCreateImageBitmapFromFilePath(sourcePath, 3, &image); if (ret != HSUCCEED) { - std::cout << "The source entered is not a picture or read error." << std::endl; + HFLogPrint(HF_LOG_ERROR, "The source entered is not a picture or read error."); return ret; } // Prepare an image parameter structure for configuration HFImageStream imageHandle = {0}; ret = HFCreateImageStreamFromImageBitmap(image, rotation_enum, &imageHandle); if (ret != HSUCCEED) { - std::cout << "Create ImageStream error: " << ret << std::endl; + HFLogPrint(HF_LOG_ERROR, "Create ImageStream error: %d", ret); return ret; } @@ -384,36 +402,117 @@ if (ret != HSUCCEED) { HFMultipleFaceData multipleFaceData = {0}; ret = HFExecuteFaceTrack(session, imageHandle, &multipleFaceData); if (ret != HSUCCEED) { - std::cout << "Execute HFExecuteFaceTrack error: " << ret << std::endl; + HFLogPrint(HF_LOG_ERROR, "Execute HFExecuteFaceTrack error: %d", ret); return ret; } + // Print the number of faces detected auto faceNum = multipleFaceData.detectedNum; -std::cout << "Num of face: " << faceNum << std::endl; +HFLogPrint(HF_LOG_INFO, "Num of face: %d", faceNum); // The memory must be freed at the end of the program ret = HFReleaseImageBitmap(image); if (ret != HSUCCEED) { - printf("Release image bitmap error: %lu\n", ret); + HFLogPrint(HF_LOG_ERROR, "Release image bitmap error: %d", ret); return ret; } ret = HFReleaseImageStream(imageHandle); if (ret != HSUCCEED) { - printf("Release image stream error: %lu\n", ret); + HFLogPrint(HF_LOG_ERROR, "Release image stream error: %d", ret); } + ret = HFReleaseInspireFaceSession(session); if (ret != HSUCCEED) { - printf("Release session error: %lu\n", ret); + HFLogPrint(HF_LOG_ERROR, "Release session error: %d", ret); return ret; } + +... ``` For more examples, you can refer to the `cpp/sample` sub-project located in the root directory. You can compile these sample executables by enabling the `ISF_BUILD_WITH_SAMPLE` option during the compilation process. -- **More detailed cases**: [C/C++ Sample](cpp/sample/api/) - **Note**: For each error code feedback, you can click on this [link](doc/Error-Feedback-Codes.md) to view detailed explanations. +### C++ Sample: Use the C++ version of the header files + +If you want to use C++ header files, then you need to enable **ISF_INSTALL_CPP_HEADER** during compilation. Executing the install command will add the C++ header files. + +```c++ +#include +#include +#include + +... + +// Set log level to info +INSPIRE_SET_LOG_LEVEL(inspire::LogLevel::ISF_LOG_INFO); + +int32_t ret = 0; +// Global init(you only need to call once) +ret = INSPIREFACE_CONTEXT->Load("Pikachu"); +INSPIREFACE_CHECK_MSG(ret == HSUCCEED, "Load model failed"); + +// Create face algorithm session +inspire::ContextCustomParameter custom_param; +custom_param.enable_recognition = true; +auto max_detect_face = 5; +auto detect_level_px = 320; // 160, 320, 640 + +// Create a face algorithm session +std::shared_ptr session( + inspire::Session::CreatePtr(inspire::DETECT_MODE_ALWAYS_DETECT, max_detect_face, custom_param, detect_level_px)); + +// Load image(default format is BGR) +inspirecv::Image image = inspirecv::Image::Create("face.jpg"); + +// Create frame process +inspirecv::FrameProcess process = + inspirecv::FrameProcess::Create(image, inspirecv::BGR, inspirecv::ROTATION_0); + +// Detect face +std::vector detect_results; +ret = session->FaceDetectAndTrack(process, detect_results); +INSPIRE_LOGI("Number of faces detected: %d", detect_results.size()); +if (detect_results.size() == 0) +{ + INSPIRE_LOGW("No face detected"); + return -1; +} + +// Copy image +inspirecv::Image image_copy = image.Clone(); +// Draw face +auto thickness = 2; +for (auto &face : detect_results) +{ + auto rect = session->GetFaceBoundingBox(face); + auto lmk = session->GetNumOfFaceDenseLandmark(face); + image_copy.DrawRect(rect, inspirecv::Color::Red, thickness); + for (auto &point : lmk) + { + image_copy.DrawCircle(point.As(), 0, inspirecv::Color::Orange, thickness); + } +} +// Save draw image +image_copy.Write("result.jpg"); + +// Face Embedding extract +inspire::FaceEmbedding face_embedding; +// Extract the first face feature +ret = session->FaceFeatureExtract(process, detect_results[0], face_embedding); +INSPIRE_LOGI("Length of face embedding: %d", face_embedding.embedding.size()); + +... +``` + +Please note that the C++ interface has not been fully tested. It is recommended to use the **CAPI** interface as the primary option. + +**More detailed cases**: + +- [C Sample](cpp/sample/api/) +- [C/C++ Sample](cpp/sample/cpp_api/) + ### Python Native Sample The Python implementation is compiled based on InspireFace source code, and is integrated using a native interface approach. @@ -453,7 +552,7 @@ ret = isf.reload() assert ret, "Launch failure. Please ensure the resource path is correct." # Optional features, loaded during session creation based on the modules specified. -opt = isf.HF_ENABLE_NONE +opt = isf.HF_ENABLE_FACE_POSE session = isf.InspireFaceSession(opt, isf.HF_DETECT_MODE_ALWAYS_DETECT) # Load the image using OpenCV. @@ -488,6 +587,7 @@ We have an [Android SDK project](https://github.com/HyperInspire/inspireface-and Precompiled library support: - arm64-v8a - armeabi-v7a +- x86_64 #### a. Quick to use in Android @@ -652,7 +752,7 @@ For different scenarios, we currently provide several Packs, each containing mul | --- | --- | --- | --- | --- | | Pikachu | CPU | Lightweight edge-side models | Feb 20, 2025 | [Download](https://github.com/HyperInspire/InspireFace/releases/download/v1.x/Pikachu) | | Megatron | CPU, GPU | Mobile and server models | Feb 20, 2025 | [Download](https://github.com/HyperInspire/InspireFace/releases/download/v1.x/Megatron) | -| Megatron_TRT | GPU | Cuda-based server models | Mar 16, 2025 | [Download](https://github.com/HyperInspire/InspireFace/releases/download/v1.x/Megatron_TRT) | +| Megatron_TRT | GPU | CUDA-based server models | Mar 16, 2025 | [Download](https://github.com/HyperInspire/InspireFace/releases/download/v1.x/Megatron_TRT) | | Gundam-RV1109 | RKNPU | Supports RK1109 and RK1126 | Feb 20, 2025 | [Download](https://github.com/HyperInspire/InspireFace/releases/download/v1.x/Gundam_RV1109) | | Gundam-RV1106 | RKNPU | Supports RV1103 and RV1106 | Feb 20, 2025 | [Download](https://github.com/HyperInspire/InspireFace/releases/download/v1.x/Gundam_RV1106) | | Gundam-RK356X | RKNPU | Supports RK3566 and RK3568 | Feb 20, 2025 | [Download](https://github.com/HyperInspire/InspireFace/releases/download/v1.x/Gundam_RK356X) | @@ -661,7 +761,8 @@ For different scenarios, we currently provide several Packs, each containing mul ## Short-Term Plan - [x] Add TensorRT backend support. -- [ ] Add the RKNPU backend support for Android . +- [x] Add Add c++ style header files. +- [x] Add the RKNPU backend support for Android . - [ ] Example app project for Android and iOS samples. - [ ] Add the batch forward feature. diff --git a/cpp-package/inspireface/android/InspireFaceExample/app/src/main/java/com/example/inspireface_example/MainActivity.java b/cpp-package/inspireface/android/InspireFaceExample/app/src/main/java/com/example/inspireface_example/MainActivity.java index efaac12..1414fcd 100644 --- a/cpp-package/inspireface/android/InspireFaceExample/app/src/main/java/com/example/inspireface_example/MainActivity.java +++ b/cpp-package/inspireface/android/InspireFaceExample/app/src/main/java/com/example/inspireface_example/MainActivity.java @@ -25,7 +25,7 @@ public class MainActivity extends AppCompatActivity { void test() { InspireFaceVersion version = InspireFace.QueryInspireFaceVersion(); - Log.i(TAG, "InspireFace Version: " + version.major + "." + version.minor + "." + version.patch + " " + version.information); + Log.i(TAG, "InspireFace Version: " + version.major + "." + version.minor + "." + version.patch); String dbPath = "/storage/emulated/0/Android/data/com.example.inspireface_example/files/f.db"; FeatureHubConfiguration configuration = InspireFace.CreateFeatureHubConfiguration() .setEnablePersistence(false) diff --git a/cpp-package/inspireface/ci/quick_test_linux_x86_usual.sh b/cpp-package/inspireface/ci/quick_test_linux_x86_usual.sh index 6ff58cc..d82e908 100644 --- a/cpp-package/inspireface/ci/quick_test_linux_x86_usual.sh +++ b/cpp-package/inspireface/ci/quick_test_linux_x86_usual.sh @@ -27,6 +27,7 @@ cd build/${BUILD_DIRNAME}/ # Configure the CMake build system cmake -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DISF_BUILD_WITH_SAMPLE=OFF \ -DISF_BUILD_WITH_TEST=ON \ -DISF_ENABLE_BENCHMARK=ON \ diff --git a/cpp-package/inspireface/ci/quick_test_linux_x86_usual_python_native_interface.sh b/cpp-package/inspireface/ci/quick_test_linux_x86_usual_python_native_interface.sh index 140c6c2..aeb2ef2 100644 --- a/cpp-package/inspireface/ci/quick_test_linux_x86_usual_python_native_interface.sh +++ b/cpp-package/inspireface/ci/quick_test_linux_x86_usual_python_native_interface.sh @@ -16,6 +16,7 @@ cd build/${BUILD_DIRNAME}/ # Configure the CMake build system cmake -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DISF_BUILD_WITH_SAMPLE=OFF \ -DISF_BUILD_WITH_TEST=OFF \ -DISF_ENABLE_BENCHMARK=OFF \ diff --git a/cpp-package/inspireface/ci/quick_test_local.sh b/cpp-package/inspireface/ci/quick_test_local.sh index d167d05..8b975df 100644 --- a/cpp-package/inspireface/ci/quick_test_local.sh +++ b/cpp-package/inspireface/ci/quick_test_local.sh @@ -43,6 +43,7 @@ cd build/${BUILD_DIRNAME}/ # Configure the CMake build system cmake -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DISF_BUILD_WITH_SAMPLE=OFF \ -DISF_BUILD_WITH_TEST=ON \ -DISF_ENABLE_BENCHMARK=ON \ diff --git a/cpp-package/inspireface/command/build.sh b/cpp-package/inspireface/command/build.sh index 45b9e15..f55c724 100644 --- a/cpp-package/inspireface/command/build.sh +++ b/cpp-package/inspireface/command/build.sh @@ -75,12 +75,15 @@ cd "$BUILD_DIR" || exit 1 # Run CMake configuration (adjust the options as needed) cmake -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DISF_BUILD_WITH_SAMPLE=ON \ -DISF_BUILD_WITH_TEST=OFF \ -DISF_ENABLE_BENCHMARK=OFF \ -DISF_ENABLE_USE_LFW_DATA=OFF \ -DISF_ENABLE_TEST_EVALUATION=OFF \ - -DISF_BUILD_SHARED_LIBS=ON "$SCRIPT_DIR" + -DISF_BUILD_SHARED_LIBS=ON \ + -Wno-dev \ + "$SCRIPT_DIR" # Compile and install make -j4 diff --git a/cpp-package/inspireface/command/build_android.sh b/cpp-package/inspireface/command/build_android.sh index 7fa961b..3dbf507 100644 --- a/cpp-package/inspireface/command/build_android.sh +++ b/cpp-package/inspireface/command/build_android.sh @@ -18,7 +18,14 @@ reorganize_structure() { done # Find all architecture directories (e.g., arm64-v8a, armeabi-v7a) - local arch_dirs=($(find "$base_path" -maxdepth 1 -type d -name "arm*")) + local arch_dirs=() + for d in "$base_path"/*; do + [[ -d "$d" ]] || continue + name=$(basename "$d") + if [[ "$name" != "lib" && "$name" != "sample" && "$name" != "test" && "$name" != "." && "$name" != ".." ]]; then + arch_dirs+=("$d") + fi + done for arch_dir in "${arch_dirs[@]}"; do # Get the architecture name (e.g., arm64-v8a) @@ -58,11 +65,22 @@ reorganize_structure() { fi done + # Copy include once from a valid arch + for arch_dir in "${arch_dirs[@]}"; do + if [ -d "$arch_dir/InspireFace/include" ]; then + mkdir -p "$base_path/include" + cp -r "$arch_dir/InspireFace/include/"* "$base_path/include/" + echo "Copied include from $arch_dir" + break + fi + done + # Delete the original architecture directories for arch_dir in "${arch_dirs[@]}"; do rm -rf "$arch_dir" done + echo "Reorganization complete." } @@ -98,6 +116,7 @@ build() { cmake ${SCRIPT_DIR} \ -G "Unix Makefiles" \ -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DCMAKE_C_FLAGS="-g0 ${CMAKE_C_FLAGS}" \ -DCMAKE_CXX_FLAGS="-g0 ${CMAKE_CXX_FLAGS}" \ -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK}/build/cmake/android.toolchain.cmake \ @@ -111,7 +130,8 @@ build() { -DISF_ENABLE_BENCHMARK=OFF \ -DISF_ENABLE_USE_LFW_DATA=OFF \ -DISF_ENABLE_TEST_EVALUATION=OFF \ - -DISF_BUILD_SHARED_LIBS=ON + -DISF_BUILD_SHARED_LIBS=ON \ + -Wno-dev make -j4 make install popd @@ -129,6 +149,7 @@ BUILD_FOLDER_PATH="build/inspireface-android${TAG}" build arm64-v8a 21 build armeabi-v7a 21 +build x86_64 21 reorganize_structure "${BUILD_FOLDER_PATH}" diff --git a/cpp-package/inspireface/command/build_android_rk356x_rk3588.sh b/cpp-package/inspireface/command/build_android_rk356x_rk3588.sh index 91f4e73..98726e6 100644 --- a/cpp-package/inspireface/command/build_android_rk356x_rk3588.sh +++ b/cpp-package/inspireface/command/build_android_rk356x_rk3588.sh @@ -99,6 +99,7 @@ build() { cmake ${SCRIPT_DIR} \ -G "Unix Makefiles" \ -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DCMAKE_C_FLAGS="-g0 ${CMAKE_C_FLAGS}" \ -DCMAKE_CXX_FLAGS="-g0 ${CMAKE_CXX_FLAGS}" \ -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK}/build/cmake/android.toolchain.cmake \ @@ -117,7 +118,8 @@ build() { -DISF_ENABLE_BENCHMARK=OFF \ -DISF_ENABLE_USE_LFW_DATA=OFF \ -DISF_ENABLE_TEST_EVALUATION=OFF \ - -DISF_BUILD_SHARED_LIBS=ON + -DISF_BUILD_SHARED_LIBS=ON \ + -Wno-dev make -j4 make install popd diff --git a/cpp-package/inspireface/command/build_cross_aarch64.sh b/cpp-package/inspireface/command/build_cross_aarch64.sh index 8889f70..014e3e0 100644 --- a/cpp-package/inspireface/command/build_cross_aarch64.sh +++ b/cpp-package/inspireface/command/build_cross_aarch64.sh @@ -38,6 +38,7 @@ cd ${BUILD_FOLDER_PATH} # export cross_compile_toolchain=/home/jingyuyan/software/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu cmake -DCMAKE_SYSTEM_NAME=Linux \ -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DCMAKE_SYSTEM_VERSION=1 \ -DCMAKE_SYSTEM_PROCESSOR=aarch64 \ -DCMAKE_C_COMPILER=$ARM_CROSS_COMPILE_TOOLCHAIN/bin/aarch64-linux-gnu-gcc \ @@ -51,6 +52,7 @@ cmake -DCMAKE_SYSTEM_NAME=Linux \ -DISF_ENABLE_BENCHMARK=OFF \ -DISF_ENABLE_USE_LFW_DATA=OFF \ -DISF_ENABLE_TEST_EVALUATION=OFF \ + -Wno-dev \ -DISF_BUILD_SHARED_LIBS=ON ${SCRIPT_DIR} make -j4 diff --git a/cpp-package/inspireface/command/build_cross_armv7_armhf.sh b/cpp-package/inspireface/command/build_cross_armv7_armhf.sh index 3cbf97b..c6d75ca 100644 --- a/cpp-package/inspireface/command/build_cross_armv7_armhf.sh +++ b/cpp-package/inspireface/command/build_cross_armv7_armhf.sh @@ -38,6 +38,7 @@ cd ${BUILD_FOLDER_PATH} # export cross_compile_toolchain=/home/jingyuyan/software/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf cmake -DCMAKE_SYSTEM_NAME=Linux \ -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DCMAKE_SYSTEM_VERSION=1 \ -DCMAKE_SYSTEM_PROCESSOR=armv7 \ -DCMAKE_C_COMPILER=$ARM_CROSS_COMPILE_TOOLCHAIN/bin/arm-linux-gnueabihf-gcc \ @@ -50,6 +51,7 @@ cmake -DCMAKE_SYSTEM_NAME=Linux \ -DISF_ENABLE_BENCHMARK=OFF \ -DISF_ENABLE_USE_LFW_DATA=OFF \ -DISF_ENABLE_TEST_EVALUATION=OFF \ + -Wno-dev \ -DISF_BUILD_SHARED_LIBS=ON ${SCRIPT_DIR} make -j4 diff --git a/cpp-package/inspireface/command/build_cross_rk356x_rk3588_aarch64.sh b/cpp-package/inspireface/command/build_cross_rk356x_rk3588_aarch64.sh index 13241bc..aa01bb1 100644 --- a/cpp-package/inspireface/command/build_cross_rk356x_rk3588_aarch64.sh +++ b/cpp-package/inspireface/command/build_cross_rk356x_rk3588_aarch64.sh @@ -46,6 +46,7 @@ cd ${BUILD_FOLDER_PATH} cmake -DCMAKE_SYSTEM_NAME=Linux \ -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DCMAKE_SYSTEM_VERSION=1 \ -DCMAKE_SYSTEM_PROCESSOR=aarch64 \ -DCMAKE_C_COMPILER=$ARM_CROSS_COMPILE_TOOLCHAIN/bin/aarch64-linux-gnu-gcc \ @@ -66,7 +67,8 @@ cmake -DCMAKE_SYSTEM_NAME=Linux \ -DISF_ENABLE_BENCHMARK=OFF \ -DISF_ENABLE_USE_LFW_DATA=OFF \ -DISF_ENABLE_TEST_EVALUATION=OFF \ - -DISF_BUILD_SHARED_LIBS=OFF ${SCRIPT_DIR} + -Wno-dev \ + -DISF_BUILD_SHARED_LIBS=ON ${SCRIPT_DIR} make -j4 make install diff --git a/cpp-package/inspireface/command/build_cross_rv1106_armhf_uclibc.sh b/cpp-package/inspireface/command/build_cross_rv1106_armhf_uclibc.sh index 6b71495..db858a7 100644 --- a/cpp-package/inspireface/command/build_cross_rv1106_armhf_uclibc.sh +++ b/cpp-package/inspireface/command/build_cross_rv1106_armhf_uclibc.sh @@ -78,6 +78,7 @@ cd ${BUILD_FOLDER_PATH} cmake -DCMAKE_SYSTEM_NAME=Linux \ -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DCMAKE_SYSTEM_VERSION=1 \ -DCMAKE_SYSTEM_PROCESSOR=armv7 \ -DCMAKE_C_COMPILER=$ARM_CROSS_COMPILE_TOOLCHAIN/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc \ @@ -98,9 +99,10 @@ cmake -DCMAKE_SYSTEM_NAME=Linux \ -DISF_ENABLE_BENCHMARK=OFF \ -DISF_ENABLE_USE_LFW_DATA=OFF \ -DISF_ENABLE_TEST_EVALUATION=OFF \ - -DISF_BUILD_SHARED_LIBS=OFF ${SCRIPT_DIR} + -Wno-dev \ + -DISF_BUILD_SHARED_LIBS=ON ${SCRIPT_DIR} make -j4 -# make install +make install -# move_install_files "$(pwd)" \ No newline at end of file +move_install_files "$(pwd)" \ No newline at end of file diff --git a/cpp-package/inspireface/command/build_cross_rv1109rv1126_armhf.sh b/cpp-package/inspireface/command/build_cross_rv1109rv1126_armhf.sh index 5a201c4..6f7973e 100644 --- a/cpp-package/inspireface/command/build_cross_rv1109rv1126_armhf.sh +++ b/cpp-package/inspireface/command/build_cross_rv1109rv1126_armhf.sh @@ -39,6 +39,7 @@ cd ${BUILD_FOLDER_PATH} # export cross_compile_toolchain=/home/jingyuyan/software/gcc-arm-8.3-2019.03-x86_64-arm-linux-gnueabihf cmake -DCMAKE_SYSTEM_NAME=Linux \ -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DCMAKE_SYSTEM_VERSION=1 \ -DCMAKE_SYSTEM_PROCESSOR=armv7 \ -DCMAKE_C_COMPILER=$ARM_CROSS_COMPILE_TOOLCHAIN/bin/arm-linux-gnueabihf-gcc \ @@ -53,6 +54,7 @@ cmake -DCMAKE_SYSTEM_NAME=Linux \ -DISF_ENABLE_BENCHMARK=OFF \ -DISF_ENABLE_USE_LFW_DATA=OFF \ -DISF_ENABLE_TEST_EVALUATION=OFF \ + -Wno-dev \ -DISF_BUILD_SHARED_LIBS=ON ${SCRIPT_DIR} make -j4 diff --git a/cpp-package/inspireface/command/build_ios_coreml.sh b/cpp-package/inspireface/command/build_ios_coreml.sh new file mode 100644 index 0000000..601615d --- /dev/null +++ b/cpp-package/inspireface/command/build_ios_coreml.sh @@ -0,0 +1,157 @@ +#!/bin/bash + +# Reusable function to handle 'install' directory operations +move_install_files() { + local root_dir="$1" + local install_dir="$root_dir/install" + + # Step 1: Check if the 'install' directory exists + if [ ! -d "$install_dir" ]; then + echo "Error: 'install' directory does not exist in $root_dir" + exit 1 + fi + + # Step 2: Delete all other files/folders except 'install' + find "$root_dir" -mindepth 1 -maxdepth 1 -not -name "install" -exec rm -rf {} + + + # Step 3: Move all files from 'install' to the root directory + mv "$install_dir"/* "$root_dir" 2>/dev/null + + # Step 4: Remove the empty 'install' directory + rmdir "$install_dir" + + echo "Files from 'install' moved to $root_dir, and 'install' directory deleted." +} + +# Define download URLs +MNN_IOS_URL="https://github.com/alibaba/MNN/releases/download/2.8.1/mnn_2.8.1_ios_armv82_cpu_metal_coreml.zip" + +# Set the cache directory +MACOS_CACHE="$PWD/.macos_cache/" + +# Create the directory if it does not exist +mkdir -p "${MACOS_CACHE}" + +# Function to download and unzip a file if the required framework does not exist +download_and_unzip() { + local url=$1 + local dir=$2 + local framework_name=$3 # Name of the framework directory to check + + # Check if the framework already exists + if [ ! -d "${dir}${framework_name}" ]; then + local file_name=$(basename "$url") + local full_path="${dir}${file_name}" + + # Check if the zip file already exists + if [ ! -f "$full_path" ]; then + echo "Downloading ${file_name}..." + # Download the file + curl -sL "$url" -o "$full_path" + else + echo "${file_name} already downloaded. Proceeding to unzip." + fi + + # Unzip the file to a temporary directory + echo "Unzipping ${file_name}..." + unzip -q "$full_path" -d "${dir}" + rm "$full_path" + + # Move the framework if it's in a subdirectory specific to the iOS build + if [ "${framework_name}" == "MNN.framework" ]; then + mv "${dir}ios_build/Release-iphoneos/${framework_name}" "${dir}" + rm -rf "${dir}ios_build" # Clean up the subdirectory + fi + + echo "${framework_name} has been set up." + else + echo "${framework_name} already exists in ${dir}. Skipping download and unzip." + fi +} + +# Download and unzip MNN iOS package +download_and_unzip "$MNN_IOS_URL" "$MACOS_CACHE" "MNN.framework" + +# Download and unzip OpenCV iOS package + +if [ -n "$VERSION" ]; then + TAG="-$VERSION" +else + TAG="" +fi + + +TOOLCHAIN="$PWD/toolchain/ios.toolchain.cmake" + +BUILD_DIR="build/inspireface-ios-coreml-arm64$TAG" + +mkdir -p "$BUILD_DIR" + +cd "$BUILD_DIR" + +cmake \ + -DIOS_3RDPARTY="${MACOS_CACHE}" \ + -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN} \ + -DCMAKE_OSX_ARCHITECTURES=arm64 \ + -DENABLE_BITCODE=0 \ + -DIOS_DEPLOYMENT_TARGET=11.0 \ + -DISF_ENABLE_APPLE_EXTENSION=ON \ + -DISF_BUILD_WITH_SAMPLE=OFF \ + -DISF_BUILD_WITH_TEST=OFF \ + -DISF_BUILD_SHARED_LIBS=OFF \ + ../.. + +make -j8 + +make install + +move_install_files "$(pwd)" + +# Set the framework name +FRAMEWORK_NAME=InspireFace + +# Specify the version of the framework +FRAMEWORK_VERSION=1.0.0 + +# Root build directory +BUILD_DIR="$(pwd)" + +BUILD_LIB_DIR="$BUILD_DIR/InspireFace" + +# Create the framework structure +FRAMEWORK_DIR=$BUILD_DIR/$FRAMEWORK_NAME.framework +mkdir -p $FRAMEWORK_DIR +mkdir -p $FRAMEWORK_DIR/Headers +mkdir -p $FRAMEWORK_DIR/Resources + +# Copy the static library to the framework directory +cp $BUILD_LIB_DIR/lib/libInspireFace.a $FRAMEWORK_DIR/$FRAMEWORK_NAME + +# Copy header files to the framework's Headers directory +cp $BUILD_LIB_DIR/include/*.h $FRAMEWORK_DIR/Headers/ + +# Create Info.plist +cat <$FRAMEWORK_DIR/Resources/Info.plist + + + + + CFBundleExecutable + $FRAMEWORK_NAME + CFBundleIdentifier + com.example.$FRAMEWORK_NAME + CFBundleName + $FRAMEWORK_NAME + CFBundleVersion + $FRAMEWORK_VERSION + CFBundleShortVersionString + $FRAMEWORK_VERSION + CFBundlePackageType + FMWK + + +EOF + +echo "Framework $FRAMEWORK_NAME.framework has been created at $FRAMEWORK_DIR" + +cp -r $MACOS_CACHE/MNN.framework $BUILD_DIR/ diff --git a/cpp-package/inspireface/command/build_linux_cuda.sh b/cpp-package/inspireface/command/build_linux_cuda.sh index 6526fd9..fd030f2 100644 --- a/cpp-package/inspireface/command/build_linux_cuda.sh +++ b/cpp-package/inspireface/command/build_linux_cuda.sh @@ -38,6 +38,7 @@ cd ${BUILD_FOLDER_PATH} cmake -DCMAKE_SYSTEM_NAME=Linux \ -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DISF_BUILD_WITH_SAMPLE=ON \ -DISF_BUILD_WITH_TEST=ON \ -DISF_ENABLE_BENCHMARK=ON \ @@ -45,6 +46,7 @@ cmake -DCMAKE_SYSTEM_NAME=Linux \ -DISF_ENABLE_TEST_EVALUATION=OFF \ -DMNN_CUDA=ON \ -DISF_GLOBAL_INFERENCE_BACKEND_USE_MNN_CUDA=ON \ + -Wno-dev \ -DISF_LINUX_MNN_CUDA=/home/tunm/softwate/MNN-2.7.2/build_cuda ${SCRIPT_DIR} make -j4 diff --git a/cpp-package/inspireface/command/build_linux_cuda_dev.sh b/cpp-package/inspireface/command/build_linux_cuda_dev.sh index 4e8820e..0295bb8 100644 --- a/cpp-package/inspireface/command/build_linux_cuda_dev.sh +++ b/cpp-package/inspireface/command/build_linux_cuda_dev.sh @@ -38,6 +38,7 @@ cd ${BUILD_FOLDER_PATH} cmake -DCMAKE_SYSTEM_NAME=Linux \ -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DISF_BUILD_WITH_SAMPLE=ON \ -DISF_BUILD_WITH_TEST=ON \ -DISF_ENABLE_BENCHMARK=ON \ @@ -45,6 +46,7 @@ cmake -DCMAKE_SYSTEM_NAME=Linux \ -DISF_ENABLE_TEST_EVALUATION=ON \ -DMNN_CUDA=ON \ -DISF_GLOBAL_INFERENCE_BACKEND_USE_MNN_CUDA=ON \ + -Wno-dev \ -DISF_LINUX_MNN_CUDA=/host/softwate/MNN-2.7.2/build_cuda ${SCRIPT_DIR} make -j4 diff --git a/cpp-package/inspireface/command/build_linux_manylinux2014.sh b/cpp-package/inspireface/command/build_linux_manylinux2014.sh index c6ec090..c4ac8d6 100644 --- a/cpp-package/inspireface/command/build_linux_manylinux2014.sh +++ b/cpp-package/inspireface/command/build_linux_manylinux2014.sh @@ -39,10 +39,12 @@ cd ${BUILD_FOLDER_PATH} cmake -DCMAKE_BUILD_TYPE=Release \ -DISF_BUILD_WITH_SAMPLE=OFF \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DISF_BUILD_WITH_TEST=OFF \ -DISF_ENABLE_BENCHMARK=OFF \ -DISF_ENABLE_USE_LFW_DATA=OFF \ -DISF_ENABLE_TEST_EVALUATION=OFF \ + -Wno-dev \ -DISF_BUILD_SHARED_LIBS=ON ${SCRIPT_DIR} make -j4 diff --git a/cpp-package/inspireface/command/build_linux_tensorrt.sh b/cpp-package/inspireface/command/build_linux_tensorrt.sh index 741b4a0..3c30e73 100644 --- a/cpp-package/inspireface/command/build_linux_tensorrt.sh +++ b/cpp-package/inspireface/command/build_linux_tensorrt.sh @@ -99,12 +99,15 @@ echo "TENSORRT_ROOT: ${TENSORRT_ROOT}" cmake \ -DCMAKE_BUILD_TYPE=Release \ -DISF_BUILD_WITH_SAMPLE=ON \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DISF_BUILD_WITH_TEST=ON \ -DISF_ENABLE_BENCHMARK=ON \ -DISF_ENABLE_USE_LFW_DATA=OFF \ -DISF_ENABLE_TEST_EVALUATION=OFF \ -DTENSORRT_ROOT=${TENSORRT_ROOT} \ - -DISF_ENABLE_TENSORRT=ON ../.. + -DISF_ENABLE_TENSORRT=ON \ + -Wno-dev \ + ../.. make -j4 diff --git a/cpp-package/inspireface/command/build_linux_ubuntu18.sh b/cpp-package/inspireface/command/build_linux_ubuntu18.sh index 9827a0f..88862b8 100644 --- a/cpp-package/inspireface/command/build_linux_ubuntu18.sh +++ b/cpp-package/inspireface/command/build_linux_ubuntu18.sh @@ -36,13 +36,18 @@ mkdir -p ${BUILD_FOLDER_PATH} # shellcheck disable=SC2164 cd ${BUILD_FOLDER_PATH} +cmake --version + cmake -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DISF_BUILD_WITH_SAMPLE=OFF \ -DISF_BUILD_WITH_TEST=OFF \ -DISF_ENABLE_BENCHMARK=OFF \ -DISF_ENABLE_USE_LFW_DATA=OFF \ -DISF_ENABLE_TEST_EVALUATION=OFF \ - -DISF_BUILD_SHARED_LIBS=ON ${SCRIPT_DIR} + -DISF_BUILD_SHARED_LIBS=ON \ + -Wno-dev \ + ${SCRIPT_DIR} make -j4 make install diff --git a/cpp-package/inspireface/command/build_macos_arm64.sh b/cpp-package/inspireface/command/build_macos_arm64.sh index 840bfae..b5734d1 100644 --- a/cpp-package/inspireface/command/build_macos_arm64.sh +++ b/cpp-package/inspireface/command/build_macos_arm64.sh @@ -37,12 +37,15 @@ mkdir -p ${BUILD_FOLDER_PATH} cd ${BUILD_FOLDER_PATH} cmake -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DISF_BUILD_WITH_SAMPLE=OFF \ -DISF_BUILD_WITH_TEST=OFF \ -DISF_ENABLE_BENCHMARK=OFF \ -DISF_ENABLE_USE_LFW_DATA=OFF \ -DISF_ENABLE_TEST_EVALUATION=OFF \ - -DISF_BUILD_SHARED_LIBS=ON ${SCRIPT_DIR} + -DISF_BUILD_SHARED_LIBS=ON \ + -Wno-dev \ + ${SCRIPT_DIR} make -j4 make install diff --git a/cpp-package/inspireface/command/build_macos_coreml_arm64.sh b/cpp-package/inspireface/command/build_macos_coreml_arm64.sh new file mode 100644 index 0000000..87491d4 --- /dev/null +++ b/cpp-package/inspireface/command/build_macos_coreml_arm64.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +# Reusable function to handle 'install' directory operations +move_install_files() { + local root_dir="$1" + local install_dir="$root_dir/install" + + # Step 1: Check if the 'install' directory exists + if [ ! -d "$install_dir" ]; then + echo "Error: 'install' directory does not exist in $root_dir" + exit 1 + fi + + # Step 2: Delete all other files/folders except 'install' + find "$root_dir" -mindepth 1 -maxdepth 1 -not -name "install" -exec rm -rf {} + + + # Step 3: Move all files from 'install' to the root directory + mv "$install_dir"/* "$root_dir" 2>/dev/null + + # Step 4: Remove the empty 'install' directory + rmdir "$install_dir" + + echo "Files from 'install' moved to $root_dir, and 'install' directory deleted." +} + +if [ -n "$VERSION" ]; then + TAG="-$VERSION" +else + TAG="" +fi + +BUILD_FOLDER_PATH="build/inspireface-macos-coreml-apple-silicon-arm64${TAG}/" +SCRIPT_DIR=$(pwd) # Project dir + +mkdir -p ${BUILD_FOLDER_PATH} +# shellcheck disable=SC2164 +cd ${BUILD_FOLDER_PATH} + +cmake -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ + -DISF_ENABLE_APPLE_EXTENSION=ON \ + -DISF_BUILD_WITH_SAMPLE=OFF \ + -DISF_BUILD_WITH_TEST=OFF \ + -DISF_ENABLE_BENCHMARK=OFF \ + -DISF_ENABLE_USE_LFW_DATA=OFF \ + -DISF_ENABLE_TEST_EVALUATION=OFF \ + -DISF_BUILD_SHARED_LIBS=OFF \ + -Wno-dev \ + ${SCRIPT_DIR} + +make -j4 +make install + +move_install_files "$(pwd)" \ No newline at end of file diff --git a/cpp-package/inspireface/command/build_macos_coreml_x86.sh b/cpp-package/inspireface/command/build_macos_coreml_x86.sh new file mode 100644 index 0000000..675bfef --- /dev/null +++ b/cpp-package/inspireface/command/build_macos_coreml_x86.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +# Reusable function to handle 'install' directory operations +move_install_files() { + local root_dir="$1" + local install_dir="$root_dir/install" + + # Step 1: Check if the 'install' directory exists + if [ ! -d "$install_dir" ]; then + echo "Error: 'install' directory does not exist in $root_dir" + exit 1 + fi + + # Step 2: Delete all other files/folders except 'install' + find "$root_dir" -mindepth 1 -maxdepth 1 -not -name "install" -exec rm -rf {} + + + # Step 3: Move all files from 'install' to the root directory + mv "$install_dir"/* "$root_dir" 2>/dev/null + + # Step 4: Remove the empty 'install' directory + rmdir "$install_dir" + + echo "Files from 'install' moved to $root_dir, and 'install' directory deleted." +} + +if [ -n "$VERSION" ]; then + TAG="-$VERSION" +else + TAG="" +fi + +BUILD_FOLDER_PATH="build/inspireface-macos-coreml-intel-x86-64${TAG}/" +SCRIPT_DIR=$(pwd) # Project dir + +mkdir -p ${BUILD_FOLDER_PATH} +# shellcheck disable=SC2164 +cd ${BUILD_FOLDER_PATH} + +cmake -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ + -DISF_ENABLE_APPLE_EXTENSION=ON \ + -DISF_BUILD_WITH_SAMPLE=OFF \ + -DISF_BUILD_WITH_TEST=OFF \ + -DISF_ENABLE_BENCHMARK=OFF \ + -DISF_ENABLE_USE_LFW_DATA=OFF \ + -DISF_ENABLE_TEST_EVALUATION=OFF \ + -DISF_BUILD_SHARED_LIBS=ON \ + -Wno-dev \ + ${SCRIPT_DIR} + +make -j4 +make install + +move_install_files "$(pwd)" \ No newline at end of file diff --git a/cpp-package/inspireface/command/build_macos_x86.sh b/cpp-package/inspireface/command/build_macos_x86.sh index e009e56..d6215b1 100644 --- a/cpp-package/inspireface/command/build_macos_x86.sh +++ b/cpp-package/inspireface/command/build_macos_x86.sh @@ -37,12 +37,15 @@ mkdir -p ${BUILD_FOLDER_PATH} cd ${BUILD_FOLDER_PATH} cmake -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DISF_BUILD_WITH_SAMPLE=OFF \ -DISF_BUILD_WITH_TEST=OFF \ -DISF_ENABLE_BENCHMARK=OFF \ -DISF_ENABLE_USE_LFW_DATA=OFF \ -DISF_ENABLE_TEST_EVALUATION=OFF \ - -DISF_BUILD_SHARED_LIBS=ON ${SCRIPT_DIR} + -DISF_BUILD_SHARED_LIBS=ON \ + -Wno-dev \ + ${SCRIPT_DIR} make -j4 make install diff --git a/cpp-package/inspireface/command/build_wheel_macos_arm64.sh b/cpp-package/inspireface/command/build_wheel_macos_arm64.sh index 1b6ef7b..ccbea36 100644 --- a/cpp-package/inspireface/command/build_wheel_macos_arm64.sh +++ b/cpp-package/inspireface/command/build_wheel_macos_arm64.sh @@ -37,12 +37,15 @@ mkdir -p ${BUILD_FOLDER_PATH} cd ${BUILD_FOLDER_PATH} cmake -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DISF_BUILD_WITH_SAMPLE=OFF \ -DISF_BUILD_WITH_TEST=OFF \ -DISF_ENABLE_BENCHMARK=OFF \ -DISF_ENABLE_USE_LFW_DATA=OFF \ -DISF_ENABLE_TEST_EVALUATION=OFF \ - -DISF_BUILD_SHARED_LIBS=ON ${SCRIPT_DIR} + -DISF_BUILD_SHARED_LIBS=ON \ + -Wno-dev \ + ${SCRIPT_DIR} make -j4 make install diff --git a/cpp-package/inspireface/command/build_wheel_macos_x86.sh b/cpp-package/inspireface/command/build_wheel_macos_x86.sh index 27e4460..ae13d1b 100644 --- a/cpp-package/inspireface/command/build_wheel_macos_x86.sh +++ b/cpp-package/inspireface/command/build_wheel_macos_x86.sh @@ -37,12 +37,15 @@ mkdir -p ${BUILD_FOLDER_PATH} cd ${BUILD_FOLDER_PATH} cmake -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DISF_BUILD_WITH_SAMPLE=OFF \ -DISF_BUILD_WITH_TEST=OFF \ -DISF_ENABLE_BENCHMARK=OFF \ -DISF_ENABLE_USE_LFW_DATA=OFF \ -DISF_ENABLE_TEST_EVALUATION=OFF \ - -DISF_BUILD_SHARED_LIBS=ON ${SCRIPT_DIR} + -DISF_BUILD_SHARED_LIBS=ON \ + -Wno-dev \ + ${SCRIPT_DIR} make -j4 make install diff --git a/cpp-package/inspireface/command/build_wheel_manylinux2014_x86.sh b/cpp-package/inspireface/command/build_wheel_manylinux2014_x86.sh index ce6f14b..a19b659 100644 --- a/cpp-package/inspireface/command/build_wheel_manylinux2014_x86.sh +++ b/cpp-package/inspireface/command/build_wheel_manylinux2014_x86.sh @@ -37,12 +37,15 @@ mkdir -p ${BUILD_FOLDER_PATH} cd ${BUILD_FOLDER_PATH} cmake -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DISF_BUILD_WITH_SAMPLE=OFF \ -DISF_BUILD_WITH_TEST=OFF \ -DISF_ENABLE_BENCHMARK=OFF \ -DISF_ENABLE_USE_LFW_DATA=OFF \ -DISF_ENABLE_TEST_EVALUATION=OFF \ - -DISF_BUILD_SHARED_LIBS=ON ${SCRIPT_DIR} + -DISF_BUILD_SHARED_LIBS=ON \ + -Wno-dev \ + ${SCRIPT_DIR} make -j4 make install diff --git a/cpp-package/inspireface/command/test.sh b/cpp-package/inspireface/command/test.sh deleted file mode 100644 index 097b1c9..0000000 --- a/cpp-package/inspireface/command/test.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/bash - -# 函数:获取CUDA和Ubuntu版本标签 -# 如果CUDA_TAG环境变量已设置,则使用该值 -# 否则自动检测CUDA和Ubuntu版本并生成标签 -# 格式: cudaXX_ubuntuXX.XX -# 如果检测不到某个版本,则用"none"代替 -get_cuda_ubuntu_tag() { - # 如果CUDA_TAG已设置,则直接返回 - if [ -n "${CUDA_TAG}" ]; then - echo "${CUDA_TAG}" - return 0 - fi - - # 获取CUDA版本 - CUDA_VERSION="_none" - if command -v nvcc &> /dev/null; then - # 尝试从nvcc获取版本 - CUDA_VERSION=$(nvcc --version 2>/dev/null | grep "release" | awk '{print $6}' | cut -d',' -f1 | tr -d '.') - if [ -z "${CUDA_VERSION}" ]; then - CUDA_VERSION="_none" - else - CUDA_VERSION="${CUDA_VERSION}" - fi - elif [ -f "/usr/local/cuda/version.txt" ]; then - # 尝试从CUDA安装目录获取版本 - CUDA_VERSION=$(cat /usr/local/cuda/version.txt 2>/dev/null | grep "CUDA Version" | awk '{print $3}' | tr -d '.') - if [ -z "${CUDA_VERSION}" ]; then - CUDA_VERSION="_none" - fi - elif [ -d "/usr/local/cuda" ] && ls -l /usr/local/cuda 2>/dev/null | grep -q "cuda-"; then - # 尝试从符号链接获取版本 - CUDA_LINK=$(ls -l /usr/local/cuda 2>/dev/null | grep -o "cuda-[0-9.]*" | head -n 1) - CUDA_VERSION=$(echo "${CUDA_LINK}" | cut -d'-' -f2 | tr -d '.') - if [ -z "${CUDA_VERSION}" ]; then - CUDA_VERSION="_none" - fi - fi - - # 获取Ubuntu版本 - UBUNTU_VERSION="_none" - if [ -f "/etc/os-release" ]; then - # 检查是否是Ubuntu - if grep -q "Ubuntu" /etc/os-release 2>/dev/null; then - UBUNTU_VERSION=$(grep "VERSION_ID" /etc/os-release 2>/dev/null | cut -d'"' -f2) - if [ -z "${UBUNTU_VERSION}" ]; then - UBUNTU_VERSION="_none" - fi - fi - elif [ -f "/etc/lsb-release" ]; then - # 尝试从lsb-release获取版本 - if grep -q "Ubuntu" /etc/lsb-release 2>/dev/null; then - UBUNTU_VERSION=$(grep "DISTRIB_RELEASE" /etc/lsb-release 2>/dev/null | cut -d'=' -f2) - if [ -z "${UBUNTU_VERSION}" ]; then - UBUNTU_VERSION="_none" - fi - fi - fi - - # 生成并返回标签 - echo "cuda${CUDA_VERSION}_ubuntu${UBUNTU_VERSION}" -} - -# 使用示例 -CUDA_TAG=$(get_cuda_ubuntu_tag) -echo "Generated tag: ${CUDA_TAG}" \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/inspireface/CMakeLists.txt b/cpp-package/inspireface/cpp/inspireface/CMakeLists.txt index 61094ff..03729c1 100644 --- a/cpp-package/inspireface/cpp/inspireface/CMakeLists.txt +++ b/cpp-package/inspireface/cpp/inspireface/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.20) project(InspireFaceSDK) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") @@ -15,7 +15,7 @@ else() set(EXTENDED_INFORMATION "${EXTENDED_INFORMATION}@General") endif() set(EXTENDED_INFORMATION "${EXTENDED_INFORMATION} - Build Time: ${BUILD_TIMESTAMP}") -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/information.h.in ${CMAKE_CURRENT_SOURCE_DIR}/information.h) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/information.h.in ${CMAKE_CURRENT_SOURCE_DIR}/include/inspireface/information.h) file(GLOB_RECURSE SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) set(SOURCE_FILES ${SOURCE_FILES} ${CMAKE_CURRENT_SOURCE_DIR}/c_api/inspireface.cc) # Add C_API file @@ -97,7 +97,6 @@ if(ISF_ENABLE_APPLE_EXTENSION) find_library(FOUNDATION_LIBRARY Foundation) find_library(COREML_LIBRARY CoreML) find_library(ACCELERATE_LIBRARY Accelerate) - find_package(OpenCV REQUIRED) set(LINK_THIRD_LIBS ${LINK_THIRD_LIBS} ${FOUNDATION_LIBRARY} ${COREML_LIBRARY} ${ACCELERATE_LIBRARY}) # Add objective-c files @@ -128,6 +127,9 @@ if (ISF_ENABLE_RKNN) endif() endif () +# Add include directory +set(NEED_INCLUDE ${NEED_INCLUDE} ${CMAKE_CURRENT_SOURCE_DIR}/include/inspireface) + if (ISF_BUILD_LINUX_ARM7 OR ANDROID) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpu=neon") endif() @@ -221,7 +223,6 @@ if (ISF_ENABLE_TENSORRT) message(STATUS "\t CUDA_RUNTIME_LIBRARY: ${CUDA_RUNTIME_LIBRARY}") endif() - # Install lib install(TARGETS InspireFace LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/InspireFace/lib @@ -233,10 +234,17 @@ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/c_api/inspireface.h DESTINATION ${CMAK install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/c_api/intypedef.h DESTINATION ${CMAKE_INSTALL_PREFIX}/InspireFace/include) -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/herror.h DESTINATION ${CMAKE_INSTALL_PREFIX}/InspireFace/include) +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/inspireface/herror.h DESTINATION ${CMAKE_INSTALL_PREFIX}/InspireFace/include) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/version.txt DESTINATION ${CMAKE_INSTALL_PREFIX}/) +if(ISF_INSTALL_CPP_HEADER) + # Install cpp api header file + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/inspireface/ DESTINATION ${CMAKE_INSTALL_PREFIX}/InspireFace/include/inspireface/) + + install(DIRECTORY ${INSPIRECV_INCLUDE_PATH}/inspirecv DESTINATION ${CMAKE_INSTALL_PREFIX}/InspireFace/include/) +endif() + if (ISF_ENABLE_RKNN AND ISF_RKNPU_MAJOR STREQUAL "rknpu1") # Install rknn 3rd lib install(FILES ${ISF_RKNN_API_LIB}/librknn_api.so DESTINATION ${CMAKE_INSTALL_PREFIX}/InspireFace/lib) diff --git a/cpp-package/inspireface/cpp/inspireface/Initialization_module/launch.cpp b/cpp-package/inspireface/cpp/inspireface/Initialization_module/launch.cpp deleted file mode 100644 index 9a1dad6..0000000 --- a/cpp-package/inspireface/cpp/inspireface/Initialization_module/launch.cpp +++ /dev/null @@ -1,179 +0,0 @@ -/** - * Created by Jingyu Yan - * @date 2024-10-01 - */ - -#include "launch.h" -#include "log.h" -#include "herror.h" -#include "isf_check.h" -#include "middleware/cuda_toolkit.h" -#if defined(ISF_ENABLE_TENSORRT) -#include "middleware/cuda_toolkit.h" -#endif - -#define APPLE_EXTENSION_SUFFIX ".bundle" - -namespace inspire { - -std::mutex Launch::mutex_; -std::shared_ptr Launch::instance_ = nullptr; - -InspireArchive& Launch::getMArchive() { - std::lock_guard lock(mutex_); - if (!m_archive_) { - throw std::runtime_error("Archive not initialized"); - } - return *m_archive_; -} - -std::shared_ptr Launch::GetInstance() { - std::lock_guard lock(mutex_); - if (!instance_) { - instance_ = std::shared_ptr(new Launch()); - } - return instance_; -} - -int32_t Launch::Load(const std::string& path) { - std::lock_guard lock(mutex_); -#if defined(ISF_ENABLE_TENSORRT) - int32_t support_cuda; - auto ret = CheckCudaUsability(&support_cuda); - if (ret != HSUCCEED) { - INSPIRE_LOGE("An error occurred while checking CUDA device support. Please ensure that your environment supports CUDA!"); - return ret; - } - if (!support_cuda) { - INSPIRE_LOGE("Your environment does not support CUDA! Please ensure that your environment supports CUDA!"); - return HERR_DEVICE_CUDA_NOT_SUPPORT; - } -#endif - INSPIREFACE_CHECK_MSG(os::IsExists(path), "The package path does not exist because the launch failed."); -#if defined(ISF_ENABLE_APPLE_EXTENSION) - BuildAppleExtensionPath(path); -#endif - if (!m_load_) { - try { - m_archive_ = std::make_unique(); - m_archive_->ReLoad(path); - - if (m_archive_->QueryStatus() == SARC_SUCCESS) { - m_load_ = true; - INSPIRE_LOGI("Successfully loaded resources"); - return HSUCCEED; - } else { - m_archive_.reset(); - INSPIRE_LOGE("Failed to load resources"); - return HERR_ARCHIVE_LOAD_MODEL_FAILURE; - } - } catch (const std::exception& e) { - m_archive_.reset(); - INSPIRE_LOGE("Exception during resource loading: %s", e.what()); - return HERR_ARCHIVE_LOAD_MODEL_FAILURE; - } - } else { - INSPIRE_LOGW("There is no need to call launch more than once, as subsequent calls will not affect the initialization."); - return HSUCCEED; - } -} - -int32_t Launch::Reload(const std::string& path) { - std::lock_guard lock(mutex_); - INSPIREFACE_CHECK_MSG(os::IsExists(path), "The package path does not exist because the launch failed."); -#if defined(ISF_ENABLE_APPLE_EXTENSION) - BuildAppleExtensionPath(path); -#endif - try { - // Clean up existing archive if it exists - if (m_archive_) { - m_archive_.reset(); - m_load_ = false; - } - - // Create and load new archive - m_archive_ = std::make_unique(); - m_archive_->ReLoad(path); - - if (m_archive_->QueryStatus() == SARC_SUCCESS) { - m_load_ = true; - INSPIRE_LOGI("Successfully reloaded resources"); - return HSUCCEED; - } else { - m_archive_.reset(); - INSPIRE_LOGE("Failed to reload resources"); - return HERR_ARCHIVE_LOAD_MODEL_FAILURE; - } - } catch (const std::exception& e) { - m_archive_.reset(); - INSPIRE_LOGE("Exception during resource reloading: %s", e.what()); - return HERR_ARCHIVE_LOAD_MODEL_FAILURE; - } -} - -bool Launch::isMLoad() const { - return m_load_; -} - -void Launch::Unload() { - std::lock_guard lock(mutex_); - if (m_load_) { - m_archive_.reset(); - m_load_ = false; - INSPIRE_LOGI("All resources have been successfully unloaded and system is reset."); - } else { - INSPIRE_LOGW("Unload called but system was not loaded."); - } -} - -void Launch::SetRockchipDmaHeapPath(const std::string& path) { - m_rockchip_dma_heap_path_ = path; -} - -std::string Launch::GetRockchipDmaHeapPath() const { - return m_rockchip_dma_heap_path_; -} - -void Launch::ConfigurationExtensionPath(const std::string& path) { -#if defined(ISF_ENABLE_APPLE_EXTENSION) - INSPIREFACE_CHECK_MSG(os::IsDir(path), "The apple extension path is not a directory, please check."); -#endif - INSPIREFACE_CHECK_MSG(os::IsExists(path), "The extension path is not exists, please check."); - m_extension_path_ = path; -} - -std::string Launch::GetExtensionPath() const { - return m_extension_path_; -} - -void Launch::SetGlobalCoreMLInferenceMode(InferenceWrapper::SpecialBackend mode) { - m_global_coreml_inference_mode_ = mode; - if (m_global_coreml_inference_mode_ == InferenceWrapper::COREML_CPU) { - INSPIRE_LOGW("Global CoreML Compute Units set to CPU Only."); - } else if (m_global_coreml_inference_mode_ == InferenceWrapper::COREML_GPU) { - INSPIRE_LOGW("Global CoreML Compute Units set to CPU and GPU."); - } else if (m_global_coreml_inference_mode_ == InferenceWrapper::COREML_ANE) { - INSPIRE_LOGW("Global CoreML Compute Units set to Auto Switch (ANE, GPU, CPU)."); - } -} - -InferenceWrapper::SpecialBackend Launch::GetGlobalCoreMLInferenceMode() const { - return m_global_coreml_inference_mode_; -} - -void Launch::BuildAppleExtensionPath(const std::string& resource_path) { - std::string basename = os::Basename(resource_path); - m_extension_path_ = os::PathJoin(os::Dirname(resource_path), basename + APPLE_EXTENSION_SUFFIX); - INSPIREFACE_CHECK_MSG(os::IsExists(m_extension_path_), "The apple extension path is not exists, please check."); - INSPIREFACE_CHECK_MSG(os::IsDir(m_extension_path_), "The apple extension path is not a directory, please check."); -} - -void Launch::SetCudaDeviceId(int32_t device_id) { - m_cuda_device_id_ = device_id; -} - -int32_t Launch::GetCudaDeviceId() const { - return m_cuda_device_id_; -} - -} // namespace inspire \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/inspireface/c_api/inspireface.cc b/cpp-package/inspireface/cpp/inspireface/c_api/inspireface.cc index eec6af6..8f9db46 100644 --- a/cpp-package/inspireface/cpp/inspireface/c_api/inspireface.cc +++ b/cpp-package/inspireface/cpp/inspireface/c_api/inspireface.cc @@ -7,14 +7,17 @@ #include "intypedef.h" #include "inspireface_internal.h" #include "information.h" -#include "feature_hub/feature_hub_db.h" -#include "initialization_module/launch.h" -#include "initialization_module/resource_manage.h" -#include "recognition_module/similarity_converter.h" +#include "feature_hub_db.h" +#include +#include "runtime_module/resource_manage.h" +#include "similarity_converter.h" #include "middleware/inference_wrapper/inference_wrapper.h" #if defined(ISF_ENABLE_TENSORRT) -#include "middleware/cuda_toolkit.h" +#include "cuda_toolkit.h" #endif +#include + +#define FACE_FEATURE_SIZE 512 ///< Temporary setup using namespace inspire; @@ -345,6 +348,31 @@ HResult HFReleaseInspireFaceSession(HFSession handle) { return HSUCCEED; } +HResult HFSwitchLandmarkEngine(HFSessionLandmarkEngine engine) { + inspire::Launch::LandmarkEngine type; + if (engine == HF_LANDMARK_HYPLMV2_0_25) { + type = inspire::Launch::LANDMARK_HYPLMV2_0_25; + } else if (engine == HF_LANDMARK_HYPLMV2_0_50) { + type = inspire::Launch::LANDMARK_HYPLMV2_0_50; + } else if (engine == HF_LANDMARK_INSIGHTFACE_2D106_TRACK) { + type = inspire::Launch::LANDMARK_INSIGHTFACE_2D106_TRACK; + } else { + INSPIRE_LOGE("Unsupported Landmark engine."); + return HERR_INVALID_PARAM; + } + INSPIREFACE_CONTEXT->SwitchLandmarkEngine(type); + return HSUCCEED; +} + +HResult HFQuerySupportedPixelLevelsForFaceDetection(PHFFaceDetectPixelList pixel_levels) { + auto ret = INSPIREFACE_CONTEXT->GetFaceDetectPixelList(); + pixel_levels->size = ret.size(); + for (int i = 0; i < ret.size(); i++) { + pixel_levels->pixel_level[i] = ret[i]; + } + return HSUCCEED; +} + HResult HFCreateInspireFaceSession(HFSessionCustomParameter parameter, HFDetectMode detectMode, HInt32 maxDetectFaceNum, HInt32 detectPixelLevel, HInt32 trackByDetectModeFPS, HFSession *handle) { inspire::ContextCustomParameter param; @@ -355,7 +383,7 @@ HResult HFCreateInspireFaceSession(HFSessionCustomParameter parameter, HFDetectM param.enable_ir_liveness = parameter.enable_ir_liveness; param.enable_recognition = parameter.enable_recognition; param.enable_face_attribute = parameter.enable_face_attribute; - param.enable_detect_mode_landmark = parameter.enable_detect_mode_landmark; + param.enable_face_pose = parameter.enable_face_pose; inspire::DetectModuleMode detMode = inspire::DETECT_MODE_ALWAYS_DETECT; if (detectMode == HF_DETECT_MODE_LIGHT_TRACK) { detMode = inspire::DETECT_MODE_LIGHT_TRACK; @@ -401,8 +429,8 @@ HResult HFCreateInspireFaceSessionOptional(HOption customOption, HFDetectMode de if (customOption & HF_ENABLE_INTERACTION) { param.enable_interaction_liveness = true; } - if (customOption & HF_ENABLE_DETECT_MODE_LANDMARK) { - param.enable_detect_mode_landmark = true; + if (customOption & HF_ENABLE_FACE_POSE) { + param.enable_face_pose = true; } inspire::DetectModuleMode detMode = inspire::DETECT_MODE_ALWAYS_DETECT; if (detectMode == HF_DETECT_MODE_LIGHT_TRACK) { @@ -427,56 +455,59 @@ HResult HFCreateInspireFaceSessionOptional(HOption customOption, HFDetectMode de HResult HFLaunchInspireFace(HPath resourcePath) { std::string path(resourcePath); - return INSPIRE_LAUNCH->Load(resourcePath); + return INSPIREFACE_CONTEXT->Load(resourcePath); } HResult HFReloadInspireFace(HPath resourcePath) { std::string path(resourcePath); - return INSPIRE_LAUNCH->Reload(resourcePath); + return INSPIREFACE_CONTEXT->Reload(resourcePath); } HResult HFTerminateInspireFace() { - INSPIRE_LAUNCH->Unload(); + INSPIREFACE_CONTEXT->Unload(); return HSUCCEED; } HResult HFQueryInspireFaceLaunchStatus(HInt32 *status) { - *status = INSPIRE_LAUNCH->isMLoad(); + *status = INSPIREFACE_CONTEXT->isMLoad(); return HSUCCEED; } HResult HFFeatureHubDataDisable() { - return FEATURE_HUB_DB->DisableHub(); + return INSPIREFACE_FEATURE_HUB->DisableHub(); } HResult HFSetExpansiveHardwareRockchipDmaHeapPath(HPath path) { - INSPIRE_LAUNCH->SetRockchipDmaHeapPath(path); + INSPIREFACE_CONTEXT->SetRockchipDmaHeapPath(path); return HSUCCEED; } HResult HFQueryExpansiveHardwareRockchipDmaHeapPath(HString path) { - strcpy(path, INSPIRE_LAUNCH->GetRockchipDmaHeapPath().c_str()); + strcpy(path, INSPIREFACE_CONTEXT->GetRockchipDmaHeapPath().c_str()); return HSUCCEED; } HResult HFSetAppleCoreMLInferenceMode(HFAppleCoreMLInferenceMode mode) { if (mode == HF_APPLE_COREML_INFERENCE_MODE_CPU) { - INSPIRE_LAUNCH->SetGlobalCoreMLInferenceMode(InferenceWrapper::COREML_CPU); + INSPIREFACE_CONTEXT->SetGlobalCoreMLInferenceMode(inspire::Launch::NN_INFERENCE_CPU); } else if (mode == HF_APPLE_COREML_INFERENCE_MODE_GPU) { - INSPIRE_LAUNCH->SetGlobalCoreMLInferenceMode(InferenceWrapper::COREML_GPU); + INSPIREFACE_CONTEXT->SetGlobalCoreMLInferenceMode(inspire::Launch::NN_INFERENCE_COREML_GPU); } else if (mode == HF_APPLE_COREML_INFERENCE_MODE_ANE) { - INSPIRE_LAUNCH->SetGlobalCoreMLInferenceMode(InferenceWrapper::COREML_ANE); + INSPIREFACE_CONTEXT->SetGlobalCoreMLInferenceMode(inspire::Launch::NN_INFERENCE_COREML_ANE); + } else { + INSPIRE_LOGE("Unsupported Apple CoreML inference mode."); + return HERR_INVALID_PARAM; } return HSUCCEED; } HResult HFSetCudaDeviceId(int32_t device_id) { - INSPIRE_LAUNCH->SetCudaDeviceId(device_id); + INSPIREFACE_CONTEXT->SetCudaDeviceId(device_id); return HSUCCEED; } HResult HFGetCudaDeviceId(int32_t *device_id) { - *device_id = INSPIRE_LAUNCH->GetCudaDeviceId(); + *device_id = INSPIREFACE_CONTEXT->GetCudaDeviceId(); return HSUCCEED; } @@ -530,7 +561,7 @@ HResult HFFeatureHubDataEnable(HFFeatureHubConfiguration configuration) { param.enable_persistence = configuration.enablePersistence; param.recognition_threshold = configuration.searchThreshold; param.search_mode = (inspire::SearchMode)configuration.searchMode; - auto ret = FEATURE_HUB_DB->EnableHub(param); + auto ret = INSPIREFACE_FEATURE_HUB->EnableHub(param); return ret; } @@ -545,6 +576,20 @@ HResult HFSessionSetTrackPreviewSize(HFSession session, HInt32 previewSize) { return ctx->impl.SetTrackPreviewSize(previewSize); } +HResult HFSessionGetTrackPreviewSize(HFSession session, HInt32 *previewSize) { + if (session == nullptr) { + return HERR_INVALID_CONTEXT_HANDLE; + } + HF_FaceAlgorithmSession *ctx = (HF_FaceAlgorithmSession *)session; + if (ctx == nullptr) { + return HERR_INVALID_CONTEXT_HANDLE; + } + *previewSize = ctx->impl.GetTrackPreviewSize(); + return HSUCCEED; +} + + + HResult HFSessionSetFilterMinimumFacePixelSize(HFSession session, HInt32 minSize) { if (session == nullptr) { return HERR_INVALID_CONTEXT_HANDLE; @@ -615,6 +660,8 @@ HResult HFSessionSetTrackModeDetectInterval(HFSession session, HInt32 num) { return ctx->impl.SetTrackModeDetectInterval(num); } + + HResult HFExecuteFaceTrack(HFSession session, HFImageStream streamHandle, PHFMultipleFaceData results) { if (session == nullptr) { return HERR_INVALID_CONTEXT_HANDLE; @@ -643,16 +690,28 @@ HResult HFExecuteFaceTrack(HFSession session, HFImageStream streamHandle, PHFMul return ret; } +HResult HFSessionLastFaceDetectionGetDebugPreviewImageSize(HFSession session, HInt32 *size) { + if (session == nullptr) { + return HERR_INVALID_CONTEXT_HANDLE; + } + HF_FaceAlgorithmSession *ctx = (HF_FaceAlgorithmSession *)session; + if (ctx == nullptr) { + return HERR_INVALID_CONTEXT_HANDLE; + } + *size = ctx->impl.GetDebugPreviewImageSize(); + return HSUCCEED; +} + HResult HFCopyFaceBasicToken(HFFaceBasicToken token, HPBuffer buffer, HInt32 bufferSize) { - if (bufferSize < sizeof(inspire::HyperFaceData)) { + if (bufferSize < sizeof(inspire::FaceTrackWrap)) { return HERR_INVALID_BUFFER_SIZE; } - std::memcpy(buffer, token.data, sizeof(inspire::HyperFaceData)); + std::memcpy(buffer, token.data, sizeof(inspire::FaceTrackWrap)); return HSUCCEED; } HResult HFGetFaceBasicTokenSize(HPInt32 bufferSize) { - *bufferSize = sizeof(inspire::HyperFaceData); + *bufferSize = sizeof(inspire::FaceTrackWrap); return HSUCCEED; } @@ -668,7 +727,7 @@ HResult HFGetFaceDenseLandmarkFromFaceToken(HFFaceBasicToken singleFace, HPoint2 inspire::FaceBasicData data; data.dataSize = singleFace.size; data.data = singleFace.data; - HyperFaceData face = {0}; + FaceTrackWrap face = {0}; HInt32 ret; ret = RunDeserializeHyperFaceData((char *)data.data, data.dataSize, face); if (ret != HSUCCEED) { @@ -692,7 +751,7 @@ HResult HFGetFaceFiveKeyPointsFromFaceToken(HFFaceBasicToken singleFace, HPoint2 inspire::FaceBasicData data; data.dataSize = singleFace.size; data.data = singleFace.data; - HyperFaceData face = {0}; + FaceTrackWrap face = {0}; HInt32 ret; ret = RunDeserializeHyperFaceData((char *)data.data, data.dataSize, face); if (ret != HSUCCEED) { @@ -730,7 +789,7 @@ HResult HFSessionPrintTrackCostSpend(HFSession session) { } HResult HFFeatureHubFaceSearchThresholdSetting(float threshold) { - FEATURE_HUB_DB->SetRecognitionThreshold(threshold); + INSPIREFACE_FEATURE_HUB->SetRecognitionThreshold(threshold); return HSUCCEED; } @@ -762,6 +821,35 @@ HResult HFFaceFeatureExtract(HFSession session, HFImageStream streamHandle, HFFa return ret; } +HResult HFFaceFeatureExtractTo(HFSession session, HFImageStream streamHandle, HFFaceBasicToken singleFace, HFFaceFeature feature) { + if (session == nullptr) { + return HERR_INVALID_CONTEXT_HANDLE; + } + if (streamHandle == nullptr) { + return HERR_INVALID_IMAGE_STREAM_HANDLE; + } + HF_FaceAlgorithmSession *ctx = (HF_FaceAlgorithmSession *)session; + if (ctx == nullptr) { + return HERR_INVALID_CONTEXT_HANDLE; + } + HF_CameraStream *stream = (HF_CameraStream *)streamHandle; + if (stream == nullptr) { + return HERR_INVALID_IMAGE_STREAM_HANDLE; + } + if (singleFace.data == nullptr || singleFace.size <= 0) { + return HERR_INVALID_FACE_TOKEN; + } + inspire::FaceBasicData data; + data.dataSize = singleFace.size; + data.data = singleFace.data; + auto ret = ctx->impl.FaceFeatureExtract(stream->impl, data); + for (int i = 0; i < ctx->impl.GetFaceFeatureCache().size(); ++i) { + feature.data[i] = ctx->impl.GetFaceFeatureCache()[i]; + } + + return HSUCCEED; +} + HResult HFFaceFeatureExtractCpy(HFSession session, HFImageStream streamHandle, HFFaceBasicToken singleFace, HPFloat feature) { if (session == nullptr) { return HERR_INVALID_CONTEXT_HANDLE; @@ -791,6 +879,25 @@ HResult HFFaceFeatureExtractCpy(HFSession session, HFImageStream streamHandle, H return ret; } +HResult HFCreateFaceFeature(PHFFaceFeature feature) { + if (feature == nullptr) { + return HERR_INVALID_FACE_FEATURE; + } + feature->size = FACE_FEATURE_SIZE; + feature->data = new HFloat[FACE_FEATURE_SIZE]; + RESOURCE_MANAGE->createFaceFeature((long)feature); + return HSUCCEED; +} + +HResult HFReleaseFaceFeature(PHFFaceFeature feature) { + if (feature == nullptr) { + return HERR_INVALID_FACE_FEATURE; + } + delete[] feature->data; + RESOURCE_MANAGE->releaseFaceFeature((long)feature); + return HSUCCEED; +} + HResult HFFaceGetFaceAlignmentImage(HFSession session, HFImageStream streamHandle, HFFaceBasicToken singleFace, HFImageBitmap *handle) { if (session == nullptr) { return HERR_INVALID_CONTEXT_HANDLE; @@ -824,6 +931,30 @@ HResult HFFaceGetFaceAlignmentImage(HFSession session, HFImageStream streamHandl return HSUCCEED; } +HResult HFFaceFeatureExtractWithAlignmentImage(HFSession session, HFImageStream streamHandle, HFFaceFeature feature) { + if (session == nullptr) { + return HERR_INVALID_CONTEXT_HANDLE; + } + if (streamHandle == nullptr) { + return HERR_INVALID_IMAGE_STREAM_HANDLE; + } + HF_FaceAlgorithmSession *ctx = (HF_FaceAlgorithmSession *)session; + if (ctx == nullptr) { + return HERR_INVALID_CONTEXT_HANDLE; + } + HF_CameraStream *stream = (HF_CameraStream *)streamHandle; + if (stream == nullptr) { + return HERR_INVALID_IMAGE_STREAM_HANDLE; + } + Embedded embedded; + float norm; + auto ret = ctx->impl.FaceRecognitionModule()->FaceExtractWithAlignmentImage(stream->impl, embedded, norm); + for (int i = 0; i < embedded.size(); ++i) { + feature.data[i] = embedded[i]; + } + return ret; +} + HResult HFFaceComparison(HFFaceFeature feature1, HFFaceFeature feature2, HPFloat result) { if (feature1.data == nullptr || feature2.data == nullptr) { return HERR_INVALID_FACE_FEATURE; @@ -834,14 +965,14 @@ HResult HFFaceComparison(HFFaceFeature feature1, HFFaceFeature feature2, HPFloat } *result = 0.0f; float res = -1.0f; - auto ret = FEATURE_HUB_DB->CosineSimilarity(feature1.data, feature2.data, feature1.size, res); + auto ret = INSPIREFACE_FEATURE_HUB->CosineSimilarity(feature1.data, feature2.data, feature1.size, res); *result = res; return ret; } HResult HFGetRecommendedCosineThreshold(HPFloat threshold) { - if (!INSPIRE_LAUNCH->isMLoad()) { + if (!INSPIREFACE_CONTEXT->isMLoad()) { INSPIRE_LOGW("Inspireface is not launched, using default threshold 0.48"); } *threshold = SIMILARITY_CONVERTER_GET_RECOMMENDED_COSINE_THRESHOLD(); @@ -849,7 +980,7 @@ HResult HFGetRecommendedCosineThreshold(HPFloat threshold) { } HResult HFCosineSimilarityConvertToPercentage(HFloat similarity, HPFloat result) { - if (!INSPIRE_LAUNCH->isMLoad()) { + if (!INSPIREFACE_CONTEXT->isMLoad()) { INSPIRE_LOGW("Inspireface is not launched."); } *result = SIMILARITY_CONVERTER_RUN(similarity); @@ -857,7 +988,7 @@ HResult HFCosineSimilarityConvertToPercentage(HFloat similarity, HPFloat result) } HResult HFUpdateCosineSimilarityConverter(HFSimilarityConverterConfig config) { - if (!INSPIRE_LAUNCH->isMLoad()) { + if (!INSPIREFACE_CONTEXT->isMLoad()) { INSPIRE_LOGW("Inspireface is not launched."); } inspire::SimilarityConverterConfig cfg; @@ -871,7 +1002,7 @@ HResult HFUpdateCosineSimilarityConverter(HFSimilarityConverterConfig config) { } HResult HFGetCosineSimilarityConverter(PHFSimilarityConverterConfig config) { - if (!INSPIRE_LAUNCH->isMLoad()) { + if (!INSPIREFACE_CONTEXT->isMLoad()) { INSPIRE_LOGW("Inspireface is not launched."); } inspire::SimilarityConverterConfig cfg = SIMILARITY_CONVERTER_GET_CONFIG(); @@ -884,7 +1015,7 @@ HResult HFGetCosineSimilarityConverter(PHFSimilarityConverterConfig config) { } HResult HFGetFeatureLength(HPInt32 num) { - *num = 512; + *num = FACE_FEATURE_SIZE; return HSUCCEED; } @@ -898,7 +1029,7 @@ HResult HFFeatureHubInsertFeature(HFFaceFeatureIdentity featureIdentity, HPFaceI for (int i = 0; i < featureIdentity.feature->size; ++i) { feat.push_back(featureIdentity.feature->data[i]); } - HInt32 ret = FEATURE_HUB_DB->FaceFeatureInsert(feat, featureIdentity.id, *allocId); + HInt32 ret = INSPIREFACE_FEATURE_HUB->FaceFeatureInsert(feat, featureIdentity.id, *allocId); return ret; } @@ -914,10 +1045,10 @@ HResult HFFeatureHubFaceSearch(HFFaceFeature searchFeature, HPFloat confidence, } *confidence = -1.0f; inspire::FaceSearchResult result; - HInt32 ret = FEATURE_HUB_DB->SearchFaceFeature(feat, result); - mostSimilar->feature = (HFFaceFeature *)FEATURE_HUB_DB->GetFaceFeaturePtrCache().get(); - mostSimilar->feature->data = (HFloat *)FEATURE_HUB_DB->GetSearchFaceFeatureCache().data(); - mostSimilar->feature->size = FEATURE_HUB_DB->GetSearchFaceFeatureCache().size(); + HInt32 ret = INSPIREFACE_FEATURE_HUB->SearchFaceFeature(feat, result); + mostSimilar->feature = (HFFaceFeature *)INSPIREFACE_FEATURE_HUB->GetFaceFeaturePtrCache().get(); + mostSimilar->feature->data = (HFloat *)INSPIREFACE_FEATURE_HUB->GetSearchFaceFeatureCache().data(); + mostSimilar->feature->size = INSPIREFACE_FEATURE_HUB->GetSearchFaceFeatureCache().size(); mostSimilar->id = result.id; if (mostSimilar->id != -1) { *confidence = result.similarity; @@ -935,18 +1066,18 @@ HResult HFFeatureHubFaceSearchTopK(HFFaceFeature searchFeature, HInt32 topK, PHF for (int i = 0; i < searchFeature.size; ++i) { feat.push_back(searchFeature.data[i]); } - HInt32 ret = FEATURE_HUB_DB->SearchFaceFeatureTopKCache(feat, topK); + HInt32 ret = INSPIREFACE_FEATURE_HUB->SearchFaceFeatureTopKCache(feat, topK); if (ret == HSUCCEED) { - results->size = FEATURE_HUB_DB->GetTopKConfidence().size(); - results->confidence = FEATURE_HUB_DB->GetTopKConfidence().data(); - results->ids = FEATURE_HUB_DB->GetTopKCustomIdsCache().data(); + results->size = INSPIREFACE_FEATURE_HUB->GetTopKConfidence().size(); + results->confidence = INSPIREFACE_FEATURE_HUB->GetTopKConfidence().data(); + results->ids = INSPIREFACE_FEATURE_HUB->GetTopKCustomIdsCache().data(); } return ret; } HResult HFFeatureHubFaceRemove(HFaceId id) { - auto ret = FEATURE_HUB_DB->FaceFeatureRemove(id); + auto ret = INSPIREFACE_FEATURE_HUB->FaceFeatureRemove(id); return ret; } @@ -960,18 +1091,18 @@ HResult HFFeatureHubFaceUpdate(HFFaceFeatureIdentity featureIdentity) { feat.push_back(featureIdentity.feature->data[i]); } - auto ret = FEATURE_HUB_DB->FaceFeatureUpdate(feat, featureIdentity.id); + auto ret = INSPIREFACE_FEATURE_HUB->FaceFeatureUpdate(feat, featureIdentity.id); return ret; } HResult HFFeatureHubGetFaceIdentity(HFaceId id, PHFFaceFeatureIdentity identity) { - auto ret = FEATURE_HUB_DB->GetFaceFeature(id); + auto ret = INSPIREFACE_FEATURE_HUB->GetFaceFeature(id); if (ret == HSUCCEED) { identity->id = id; - identity->feature = (HFFaceFeature *)FEATURE_HUB_DB->GetFaceFeaturePtrCache().get(); - identity->feature->data = (HFloat *)FEATURE_HUB_DB->GetFaceFeaturePtrCache()->data; - identity->feature->size = FEATURE_HUB_DB->GetFaceFeaturePtrCache()->dataSize; + identity->feature = (HFFaceFeature *)INSPIREFACE_FEATURE_HUB->GetFaceFeaturePtrCache().get(); + identity->feature->data = (HFloat *)INSPIREFACE_FEATURE_HUB->GetFaceFeaturePtrCache()->data; + identity->feature->size = INSPIREFACE_FEATURE_HUB->GetFaceFeaturePtrCache()->dataSize; } else { identity->id = -1; } @@ -1010,10 +1141,9 @@ HResult HFMultipleFacePipelineProcess(HFSession session, HFImageStream streamHan param.enable_ir_liveness = parameter.enable_ir_liveness; param.enable_recognition = parameter.enable_recognition; param.enable_face_attribute = parameter.enable_face_attribute; - param.enable_detect_mode_landmark = parameter.enable_detect_mode_landmark; HResult ret; - std::vector data; + std::vector data; data.resize(faces->detectedNum); for (int i = 0; i < faces->detectedNum; ++i) { auto &face = data[i]; @@ -1072,12 +1202,12 @@ HResult HFMultipleFacePipelineProcessOptional(HFSession session, HFImageStream s if (customOption & HF_ENABLE_INTERACTION) { param.enable_interaction_liveness = true; } - if (customOption & HF_ENABLE_DETECT_MODE_LANDMARK) { - param.enable_detect_mode_landmark = true; + if (customOption & HF_ENABLE_FACE_POSE) { + param.enable_face_pose = true; } HResult ret; - std::vector data; + std::vector data; data.resize(faces->detectedNum); for (int i = 0; i < faces->detectedNum; ++i) { auto &face = data[i]; @@ -1206,20 +1336,20 @@ HResult HFGetFaceAttributeResult(HFSession session, PHFFaceAttributeResult resul } HResult HFFeatureHubGetFaceCount(HInt32 *count) { - *count = FEATURE_HUB_DB->GetFaceFeatureCount(); + *count = INSPIREFACE_FEATURE_HUB->GetFaceFeatureCount(); return HSUCCEED; } HResult HFFeatureHubViewDBTable() { - FEATURE_HUB_DB->ViewDBTable(); + INSPIREFACE_FEATURE_HUB->ViewDBTable(); return HSUCCEED; } HResult HFFeatureHubGetExistingIds(PHFFeatureHubExistingIds ids) { - auto ret = FEATURE_HUB_DB->GetAllIds(); + auto ret = INSPIREFACE_FEATURE_HUB->GetAllIds(); if (ret == HSUCCEED) { - ids->size = FEATURE_HUB_DB->GetExistingIds().size(); - ids->ids = FEATURE_HUB_DB->GetExistingIds().data(); + ids->size = INSPIREFACE_FEATURE_HUB->GetExistingIds().size(); + ids->ids = INSPIREFACE_FEATURE_HUB->GetExistingIds().data(); } return ret; } diff --git a/cpp-package/inspireface/cpp/inspireface/c_api/inspireface.h b/cpp-package/inspireface/cpp/inspireface/c_api/inspireface.h index 65b3432..675cf80 100644 --- a/cpp-package/inspireface/cpp/inspireface/c_api/inspireface.h +++ b/cpp-package/inspireface/cpp/inspireface/c_api/inspireface.h @@ -24,16 +24,19 @@ extern "C" { #endif -#define HF_ENABLE_NONE 0x00000000 ///< Flag to enable no features. -#define HF_ENABLE_FACE_RECOGNITION 0x00000002 ///< Flag to enable face recognition feature. -#define HF_ENABLE_LIVENESS 0x00000004 ///< Flag to enable RGB liveness detection feature. -#define HF_ENABLE_IR_LIVENESS 0x00000008 ///< Flag to enable IR (Infrared) liveness detection feature. -#define HF_ENABLE_MASK_DETECT 0x00000010 ///< Flag to enable mask detection feature. -#define HF_ENABLE_FACE_ATTRIBUTE 0x00000020 ///< Flag to enable face attribute prediction feature. -#define HF_ENABLE_PLACEHOLDER_ 0x00000040 ///< - -#define HF_ENABLE_QUALITY 0x00000080 ///< Flag to enable face quality assessment feature. -#define HF_ENABLE_INTERACTION 0x00000100 ///< Flag to enable interaction feature. -#define HF_ENABLE_DETECT_MODE_LANDMARK 0x00000200 ///< Flag to enable landmark detection in detection mode +#define HF_STATUS_ENABLE 1 ///< The status of the feature is enabled. +#define HF_STATUS_DISABLE 0 ///< The status of the feature is disabled. + +#define HF_ENABLE_NONE 0x00000000 ///< Flag to enable no features. +#define HF_ENABLE_FACE_RECOGNITION 0x00000002 ///< Flag to enable face recognition feature. +#define HF_ENABLE_LIVENESS 0x00000004 ///< Flag to enable RGB liveness detection feature. +#define HF_ENABLE_IR_LIVENESS 0x00000008 ///< Flag to enable IR (Infrared) liveness detection feature. +#define HF_ENABLE_MASK_DETECT 0x00000010 ///< Flag to enable mask detection feature. +#define HF_ENABLE_FACE_ATTRIBUTE 0x00000020 ///< Flag to enable face attribute prediction feature. +#define HF_ENABLE_PLACEHOLDER_ 0x00000040 ///< - +#define HF_ENABLE_QUALITY 0x00000080 ///< Flag to enable face quality assessment feature. +#define HF_ENABLE_INTERACTION 0x00000100 ///< Flag to enable interaction feature. +#define HF_ENABLE_FACE_POSE 0x00000200 ///< Flag to enable face pose estimation feature. /** * Camera stream format. @@ -385,6 +388,7 @@ typedef struct HFSessionCustomParameter { HInt32 enable_face_attribute; ///< Enable face attribute prediction feature. HInt32 enable_interaction_liveness; ///< Enable interaction for liveness detection feature. HInt32 enable_detect_mode_landmark; ///< Enable landmark detection in detection mode + HInt32 enable_face_pose; ///< Enable face pose estimation feature. } HFSessionCustomParameter, *PHFSessionCustomParameter; /** @@ -400,6 +404,38 @@ typedef enum HFDetectMode { // to use it). } HFDetectMode; +/** + * @brief Enum for landmark engine. + */ +typedef enum HFSessionLandmarkEngine { + HF_LANDMARK_HYPLMV2_0_25 = 0, ///< Hyplmkv2 0.25, default + HF_LANDMARK_HYPLMV2_0_50 = 1, ///< Hyplmkv2 0.50 + HF_LANDMARK_INSIGHTFACE_2D106_TRACK = 2, ///< InsightFace 2d106 track +} HFSessionLandmarkEngine; + +/** + * @brief Global switch the landmark engine. Set it globally before creating a session. + * If it is changed, a new session needs to be created for it to be effective. + * @param engine The landmark engine to be set. + * @return HResult indicating the success or failure of the operation. + * */ +HYPER_CAPI_EXPORT extern HResult HFSwitchLandmarkEngine(HFSessionLandmarkEngine engine); + +/** + * @brief Enum for supported pixel levels for face detection. + */ +typedef struct HFFaceDetectPixelList { + HInt32 pixel_level[20]; + HInt32 size; +} HFFaceDetectPixelList, *PHFFaceDetectPixelList; + +/** + * @brief Query the supported pixel levels for face detection. It must be used before starting. + * @param pixel_levels Pointer to the array of supported pixel levels. + * @return HResult indicating the success or failure of the operation. + * */ +HYPER_CAPI_EXPORT extern HResult HFQuerySupportedPixelLevelsForFaceDetection(PHFFaceDetectPixelList pixel_levels); + /** * @brief Create a session from a resource file. * @@ -488,6 +524,14 @@ typedef struct HFMultipleFaceData { */ HYPER_CAPI_EXPORT extern HResult HFSessionSetTrackPreviewSize(HFSession session, HInt32 previewSize); +/** + * @brief Get the track preview size in the session. + * @param session Handle to the session. + * @param previewSize The size of the preview for tracking. + * @return HResult indicating the success or failure of the operation. + */ +HYPER_CAPI_EXPORT extern HResult HFSessionGetTrackPreviewSize(HFSession session, HInt32 *previewSize); + /** * @brief Set the minimum number of face pixels that the face detector can capture, and people below * this number will be filtered. @@ -534,6 +578,7 @@ HYPER_CAPI_EXPORT extern HResult HFSessionSetTrackModeNumSmoothCacheFrame(HFSess */ HYPER_CAPI_EXPORT extern HResult HFSessionSetTrackModeDetectInterval(HFSession session, HInt32 num); + /** * @brief Run face tracking in the session. * @@ -544,6 +589,14 @@ HYPER_CAPI_EXPORT extern HResult HFSessionSetTrackModeDetectInterval(HFSession s */ HYPER_CAPI_EXPORT extern HResult HFExecuteFaceTrack(HFSession session, HFImageStream streamHandle, PHFMultipleFaceData results); +/** + * @brief Gets the size of the debug preview image for the last face detection in the session. + * @param session Handle to the session. + * @param size The size of the preview for tracking. + * @return HResult indicating the success or failure of the operation. + */ +HYPER_CAPI_EXPORT extern HResult HFSessionLastFaceDetectionGetDebugPreviewImageSize(HFSession session, HInt32 *size); + /** * @brief Copies the data from a HF_FaceBasicToken to a specified buffer. * @@ -641,6 +694,17 @@ typedef struct HFFaceFeature { HYPER_CAPI_EXPORT extern HResult HFFaceFeatureExtract(HFSession session, HFImageStream streamHandle, HFFaceBasicToken singleFace, PHFFaceFeature feature); +/** + * @brief Extract face features to the HFFaceFeature that has applied for memory in advance. + * @param session Handle to the session. + * @param streamHandle Handle to the data buffer representing the camera stream component. + * @param singleFace Basic token representing a single face. + * @param feature Pointer to the buffer where the extracted feature will be copied. + * @return HResult indicating the success or failure of the operation. + */ +HYPER_CAPI_EXPORT extern HResult HFFaceFeatureExtractTo(HFSession session, HFImageStream streamHandle, HFFaceBasicToken singleFace, + HFFaceFeature feature); + /** * @brief Extract a face feature from a given face and copy it to the provided feature buffer. * @@ -652,6 +716,20 @@ HYPER_CAPI_EXPORT extern HResult HFFaceFeatureExtract(HFSession session, HFImage */ HYPER_CAPI_EXPORT extern HResult HFFaceFeatureExtractCpy(HFSession session, HFImageStream streamHandle, HFFaceBasicToken singleFace, HPFloat feature); +/** + * @brief Create a face feature. Will allocate memory. + * @param feature Pointer to the face feature. + * @return HResult indicating the success or failure of the operation. + */ +HYPER_CAPI_EXPORT extern HResult HFCreateFaceFeature(PHFFaceFeature feature); + +/** + * @brief Release a face feature. Only the features created through the HFCreateFaceFeature need to be processed. + * @param feature Pointer to the face feature. + * @return HResult indicating the success or failure of the operation. + */ +HYPER_CAPI_EXPORT extern HResult HFReleaseFaceFeature(PHFFaceFeature feature); + /** * @brief Get the face alignment image. * @param session Handle to the session. @@ -663,6 +741,15 @@ HYPER_CAPI_EXPORT extern HResult HFFaceFeatureExtractCpy(HFSession session, HFIm HYPER_CAPI_EXPORT extern HResult HFFaceGetFaceAlignmentImage(HFSession session, HFImageStream streamHandle, HFFaceBasicToken singleFace, HFImageBitmap *handle); +/** + * @brief Use the aligned face image to extract face features to the HFFaceFeature that has applied memory in advance. + * @param session Handle to the session. + * @param streamHandle Handle to the data buffer representing the camera stream component. + * @param feature Pointer to the buffer where the extracted feature will be copied. + * @return HResult indicating the success or failure of the operation. + */ +HYPER_CAPI_EXPORT extern HResult HFFaceFeatureExtractWithAlignmentImage(HFSession session, HFImageStream streamHandle, HFFaceFeature feature); + /************************************************************************ * Feature Hub ************************************************************************/ @@ -851,7 +938,7 @@ HYPER_CAPI_EXPORT extern HResult HFFeatureHubFaceSearchTopK(HFFaceFeature search /** * @brief Remove a face feature from the features group based on custom ID. * - * @param customId The custom ID of the feature to be removed. + * @param ID The custom ID of the feature to be removed. * @return HResult indicating the success or failure of the operation. */ HYPER_CAPI_EXPORT extern HResult HFFeatureHubFaceRemove(HFaceId id); diff --git a/cpp-package/inspireface/cpp/inspireface/c_api/inspireface_internal.h b/cpp-package/inspireface/cpp/inspireface/c_api/inspireface_internal.h index cf770c9..7d92f58 100644 --- a/cpp-package/inspireface/cpp/inspireface/c_api/inspireface_internal.h +++ b/cpp-package/inspireface/cpp/inspireface/c_api/inspireface_internal.h @@ -6,15 +6,15 @@ #ifndef INSPIREFACE_INTERNAL_H #define INSPIREFACE_INTERNAL_H -#include "face_session.h" +#include "engine/face_session.h" typedef struct HF_FaceAlgorithmSession { inspire::FaceSession impl; ///< Implementation of the face context. } HF_FaceAlgorithmSession; ///< Handle for managing face context. typedef struct HF_CameraStream { - inspirecv::InspireImageProcess impl; ///< Implementation of the camera stream. -} HF_CameraStream; ///< Handle for managing camera stream. + inspirecv::FrameProcess impl; ///< Implementation of the camera stream. +} HF_CameraStream; ///< Handle for managing camera stream. typedef struct HF_ImageBitmap { inspirecv::Image impl; ///< Implementation of the image bitmap. diff --git a/cpp-package/inspireface/cpp/inspireface/common/face_data/face_serialize_tools.h b/cpp-package/inspireface/cpp/inspireface/common/face_data/face_serialize_tools.h index bf9dd93..dd53df7 100644 --- a/cpp-package/inspireface/cpp/inspireface/common/face_data/face_serialize_tools.h +++ b/cpp-package/inspireface/cpp/inspireface/common/face_data/face_serialize_tools.h @@ -6,7 +6,7 @@ #ifndef INSPIRE_FACE_SERIALIZE_TOOLS_H #define INSPIRE_FACE_SERIALIZE_TOOLS_H -#include "face_data_type.h" +#include "face_warpper.h" #include "../face_info/face_object_internal.h" #include "herror.h" #include "data_type.h" @@ -27,10 +27,10 @@ inline void PrintTransformMatrix(const TransMatrix& matrix) { } /** - * @brief Print HyperFaceData structure. - * @param data The HyperFaceData structure to print. + * @brief Print FaceTrackWrap structure. + * @param data The FaceTrackWrap structure to print. */ -inline void INSPIRE_API PrintHyperFaceDataDetail(const HyperFaceData& data) { +inline void INSPIRE_API PrintHyperFaceDataDetail(const FaceTrackWrap& data) { INSPIRE_LOGI("Track State: %d", data.trackState); INSPIRE_LOGI("In Group Index: %d", data.inGroupIndex); INSPIRE_LOGI("Track ID: %d", data.trackId); @@ -43,13 +43,13 @@ inline void INSPIRE_API PrintHyperFaceDataDetail(const HyperFaceData& data) { } /** - * @brief Convert a FaceObject to HyperFaceData. + * @brief Convert a FaceObject to FaceTrackWrap. * @param obj The FaceObject to convert. * @param group_index The group index. - * @return The converted HyperFaceData structure. + * @return The converted FaceTrackWrap structure. */ -inline HyperFaceData INSPIRE_API FaceObjectInternalToHyperFaceData(const FaceObjectInternal& obj, int group_index = -1) { - HyperFaceData data; +inline FaceTrackWrap INSPIRE_API FaceObjectInternalToHyperFaceData(const FaceObjectInternal& obj, int group_index = -1) { + FaceTrackWrap data; // Face rect data.rect.x = obj.bbox_.GetX(); data.rect.y = obj.bbox_.GetY(); @@ -134,15 +134,15 @@ inline inspirecv::Point2f INSPIRE_API HPointToInternalPoint2f(const Point2F& poi } /** - * @brief Serialize HyperFaceData to a byte stream. - * @param data The HyperFaceData to serialize. + * @brief Serialize FaceTrackWrap to a byte stream. + * @param data The FaceTrackWrap to serialize. * @param byteArray The output byte stream. * @return The result code. */ -inline int32_t INSPIRE_API RunSerializeHyperFaceData(const HyperFaceData& data, ByteArray& byteArray) { +inline int32_t INSPIRE_API RunSerializeHyperFaceData(const FaceTrackWrap& data, ByteArray& byteArray) { byteArray.reserve(sizeof(data)); - // Serialize the HyperFaceData structure itself + // Serialize the FaceTrackWrap structure itself const char* dataBytes = reinterpret_cast(&data); byteArray.insert(byteArray.end(), dataBytes, dataBytes + sizeof(data)); @@ -150,18 +150,18 @@ inline int32_t INSPIRE_API RunSerializeHyperFaceData(const HyperFaceData& data, } /** - * @brief Deserialize a byte stream to HyperFaceData. + * @brief Deserialize a byte stream to FaceTrackWrap. * @param byteArray The input byte stream. - * @param data The output HyperFaceData structure. + * @param data The output FaceTrackWrap structure. * @return The result code. */ -inline int32_t INSPIRE_API RunDeserializeHyperFaceData(const ByteArray& byteArray, HyperFaceData& data) { +inline int32_t INSPIRE_API RunDeserializeHyperFaceData(const ByteArray& byteArray, FaceTrackWrap& data) { // Check if the byte stream size is sufficient if (byteArray.size() >= sizeof(data)) { - // Copy data from the byte stream to the HyperFaceData structure + // Copy data from the byte stream to the FaceTrackWrap structure std::memcpy(&data, byteArray.data(), sizeof(data)); } else { - INSPIRE_LOGE("The byte stream size is insufficient to restore HyperFaceData"); + INSPIRE_LOGE("The byte stream size is insufficient to restore FaceTrackWrap"); return HERR_SESS_FACE_DATA_ERROR; } @@ -169,19 +169,19 @@ inline int32_t INSPIRE_API RunDeserializeHyperFaceData(const ByteArray& byteArra } /** - * @brief Deserialize a byte stream to HyperFaceData. + * @brief Deserialize a byte stream to FaceTrackWrap. * @param byteArray The input byte stream as a character array. * @param byteCount The size of the byte stream. - * @param data The output HyperFaceData structure. + * @param data The output FaceTrackWrap structure. * @return The result code. */ -inline int32_t INSPIRE_API RunDeserializeHyperFaceData(const char* byteArray, size_t byteCount, HyperFaceData& data) { +inline int32_t INSPIRE_API RunDeserializeHyperFaceData(const char* byteArray, size_t byteCount, FaceTrackWrap& data) { // Check if the byte stream size is sufficient if (byteCount >= sizeof(data)) { - // Copy data from the byte stream to the HyperFaceData structure + // Copy data from the byte stream to the FaceTrackWrap structure std::memcpy(&data, byteArray, sizeof(data)); } else { - INSPIRE_LOGE("The byte stream size is insufficient to restore HyperFaceData"); + INSPIRE_LOGE("The byte stream size is insufficient to restore FaceTrackWrap"); return HERR_SESS_FACE_DATA_ERROR; } diff --git a/cpp-package/inspireface/cpp/inspireface/common/face_info/face_action_data.h b/cpp-package/inspireface/cpp/inspireface/common/face_info/face_action_data.h index 178096b..189a47c 100644 --- a/cpp-package/inspireface/cpp/inspireface/common/face_info/face_action_data.h +++ b/cpp-package/inspireface/cpp/inspireface/common/face_info/face_action_data.h @@ -48,7 +48,7 @@ public: index = 0; } - FaceActionList AnalysisFaceAction() { + FaceActionList AnalysisFaceAction(const SemanticIndex& semantic_index) { FaceActionList actionRecord; actions.clear(); eye_state_list.clear(); @@ -64,8 +64,8 @@ public: // count mouth aspect ratio - float mouth_widthwise_d = record_list[0][FaceLandmarkAdapt::MOUTH_LEFT_CORNER].Distance(record_list[0][FaceLandmarkAdapt::MOUTH_RIGHT_CORNER]); - float mouth_heightwise_d = record_list[0][FaceLandmarkAdapt::MOUTH_UPPER].Distance(record_list[0][FaceLandmarkAdapt::MOUTH_LOWER]); + float mouth_widthwise_d = record_list[0][semantic_index.mouth_left_corner].Distance(record_list[0][semantic_index.mouth_right_corner]); + float mouth_heightwise_d = record_list[0][semantic_index.mouth_upper].Distance(record_list[0][semantic_index.mouth_lower]); float mouth_aspect_ratio = mouth_heightwise_d / mouth_widthwise_d; if (mouth_aspect_ratio > 0.3) { actions.push_back(ACT_JAW_OPEN); diff --git a/cpp-package/inspireface/cpp/inspireface/common/face_info/face_object_internal.h b/cpp-package/inspireface/cpp/inspireface/common/face_info/face_object_internal.h index 7ea130d..0449298 100755 --- a/cpp-package/inspireface/cpp/inspireface/common/face_info/face_object_internal.h +++ b/cpp-package/inspireface/cpp/inspireface/common/face_info/face_object_internal.h @@ -9,6 +9,7 @@ #include "face_process.h" #include "face_action_data.h" #include "track_module/quality/face_pose_quality_adapt.h" +#include "track_module/landmark/landmark_param.h" namespace inspire { @@ -109,11 +110,11 @@ public: return box_square; } - FaceActionList UpdateFaceAction() { + FaceActionList UpdateFaceAction(const SemanticIndex& semantic_index) { inspirecv::Vec3f euler{high_result.pitch, high_result.yaw, high_result.roll}; inspirecv::Vec2f eyes{left_eye_status_.back(), right_eye_status_.back()}; face_action_->RecordActionFrame(landmark_, euler, eyes); - return face_action_->AnalysisFaceAction(); + return face_action_->AnalysisFaceAction(semantic_index); } void DisableTracking() { diff --git a/cpp-package/inspireface/cpp/inspireface/face_session.cpp b/cpp-package/inspireface/cpp/inspireface/engine/face_session.cpp similarity index 84% rename from cpp-package/inspireface/cpp/inspireface/face_session.cpp rename to cpp-package/inspireface/cpp/inspireface/engine/face_session.cpp index 4df93fc..2c94415 100644 --- a/cpp-package/inspireface/cpp/inspireface/face_session.cpp +++ b/cpp-package/inspireface/cpp/inspireface/engine/face_session.cpp @@ -4,7 +4,7 @@ */ #include "face_session.h" -#include "initialization_module/launch.h" +#include #include #include "log.h" #include "herror.h" @@ -20,35 +20,30 @@ int32_t FaceSession::Configuration(DetectModuleMode detect_mode, int32_t max_det m_detect_mode_ = detect_mode; m_max_detect_face_ = max_detect_face; m_parameter_ = param; - if (!INSPIRE_LAUNCH->isMLoad()) { + if (!INSPIREFACE_CONTEXT->isMLoad()) { return HERR_ARCHIVE_NOT_LOAD; } - if (INSPIRE_LAUNCH->getMArchive().QueryStatus() != SARC_SUCCESS) { + if (INSPIREFACE_CONTEXT->getMArchive().QueryStatus() != SARC_SUCCESS) { return HERR_ARCHIVE_LOAD_FAILURE; } - if (m_parameter_.enable_interaction_liveness) { - m_parameter_.enable_detect_mode_landmark = true; - } - - m_face_track_ = std::make_shared(m_detect_mode_, m_max_detect_face_, 20, 192, detect_level_px, track_by_detect_mode_fps, - m_parameter_.enable_detect_mode_landmark); - m_face_track_->Configuration(INSPIRE_LAUNCH->getMArchive()); + m_face_track_ = std::make_shared(m_detect_mode_, m_max_detect_face_, 20, 192, detect_level_px, track_by_detect_mode_fps, true); + m_face_track_->Configuration(INSPIREFACE_CONTEXT->getMArchive(), "", m_parameter_.enable_face_pose || m_parameter_.enable_face_quality); // SetDetectMode(m_detect_mode_); - m_face_recognition_ = std::make_shared(INSPIRE_LAUNCH->getMArchive(), m_parameter_.enable_recognition); + m_face_recognition_ = std::make_shared(INSPIREFACE_CONTEXT->getMArchive(), m_parameter_.enable_recognition); if (m_face_recognition_->QueryStatus() != HSUCCEED) { return m_face_recognition_->QueryStatus(); } - m_face_pipeline_ = std::make_shared(INSPIRE_LAUNCH->getMArchive(), param.enable_liveness, param.enable_mask_detect, + m_face_pipeline_ = std::make_shared(INSPIREFACE_CONTEXT->getMArchive(), param.enable_liveness, param.enable_mask_detect, param.enable_face_attribute, param.enable_interaction_liveness); - m_face_track_cost_ = std::make_shared("FaceTrack"); + m_face_track_cost_ = std::make_shared("FaceTrack"); return HSUCCEED; } -int32_t FaceSession::FaceDetectAndTrack(inspirecv::InspireImageProcess& process) { +int32_t FaceSession::FaceDetectAndTrack(inspirecv::FrameProcess& process) { std::lock_guard lock(m_mtx_); if (m_enable_track_cost_spend_) { m_face_track_cost_->Start(); @@ -81,7 +76,7 @@ int32_t FaceSession::FaceDetectAndTrack(inspirecv::InspireImageProcess& process) m_face_track_->UpdateStream(process); for (int i = 0; i < m_face_track_->trackingFace.size(); ++i) { auto& face = m_face_track_->trackingFace[i]; - HyperFaceData data = FaceObjectInternalToHyperFaceData(face, i); + FaceTrackWrap data = FaceObjectInternalToHyperFaceData(face, i); ByteArray byteArray; auto ret = RunSerializeHyperFaceData(data, byteArray); if (ret != HSUCCEED) { @@ -118,6 +113,11 @@ int32_t FaceSession::FaceDetectAndTrack(inspirecv::InspireImageProcess& process) return HSUCCEED; } +int32_t FaceSession::SetLandmarkLoop(int32_t value) { + // TODO: implement this function + return HSUCCEED; +} + int32_t FaceSession::SetFaceDetectThreshold(float value) { m_face_track_->SetDetectThreshold(value); return HSUCCEED; @@ -139,8 +139,7 @@ const int32_t FaceSession::GetNumberOfFacesCurrentlyDetected() const { return m_face_track_->trackingFace.size(); } -int32_t FaceSession::FacesProcess(inspirecv::InspireImageProcess& process, const std::vector& faces, - const CustomPipelineParameter& param) { +int32_t FaceSession::FacesProcess(inspirecv::FrameProcess& process, const std::vector& faces, const CustomPipelineParameter& param) { std::lock_guard lock(m_mtx_); m_mask_results_cache_.resize(faces.size(), -1.0f); m_rgb_liveness_results_cache_.resize(faces.size(), -1.0f); @@ -205,7 +204,7 @@ int32_t FaceSession::FacesProcess(inspirecv::InspireImageProcess& process, const m_react_left_eye_results_cache_[i] = new_eye_left; m_react_right_eye_results_cache_[i] = new_eye_right; } - const auto actions = target.UpdateFaceAction(); + const auto actions = target.UpdateFaceAction(INSPIREFACE_CONTEXT->getMArchive().GetLandmarkParam()->semantic_index); m_action_normal_results_cache_[i] = actions.normal; m_action_jaw_open_results_cache_[i] = actions.jawOpen; m_action_blink_results_cache_[i] = actions.blink; @@ -324,24 +323,52 @@ const std::vector& FaceSession::GetFaceRaiseHeadAactionsResultCache() const return m_action_raise_head_results_cache_; } -int32_t FaceSession::FaceFeatureExtract(inspirecv::InspireImageProcess& process, FaceBasicData& data) { +int32_t FaceSession::FaceFeatureExtract(inspirecv::FrameProcess& process, FaceBasicData& data, bool normalize) { std::lock_guard lock(m_mtx_); int32_t ret; - HyperFaceData face = {0}; + FaceTrackWrap face = {0}; ret = RunDeserializeHyperFaceData((char*)data.data, data.dataSize, face); if (ret != HSUCCEED) { return ret; } m_face_feature_cache_.clear(); - ret = m_face_recognition_->FaceExtract(process, face, m_face_feature_cache_, m_face_feature_norm_); + ret = m_face_recognition_->FaceExtract(process, face, m_face_feature_cache_, m_face_feature_norm_, normalize); return ret; } -int32_t FaceSession::FaceGetFaceAlignmentImage(inspirecv::InspireImageProcess& process, FaceBasicData& data, inspirecv::Image& image) { +int32_t FaceSession::FaceFeatureExtract(inspirecv::FrameProcess& process, FaceTrackWrap& data, bool normalize) { std::lock_guard lock(m_mtx_); int32_t ret; - HyperFaceData face = {0}; + m_face_feature_cache_.clear(); + ret = m_face_recognition_->FaceExtract(process, data, m_face_feature_cache_, m_face_feature_norm_, normalize); + if (ret != HSUCCEED) { + return ret; + } + + return ret; +} + +int32_t FaceSession::FaceFeatureExtractWithAlignmentImage(inspirecv::FrameProcess& process, Embedded& embedding, float& norm, bool normalize) { + std::lock_guard lock(m_mtx_); + int32_t ret; + m_face_feature_cache_.clear(); + ret = m_face_recognition_->FaceExtractWithAlignmentImage(process, embedding, norm, normalize); + + return ret; +} + +int32_t FaceSession::FaceFeatureExtractWithAlignmentImage(const inspirecv::Image& wrapped, FaceEmbedding& embedding, float& norm, bool normalize) { + std::lock_guard lock(m_mtx_); + int32_t ret; + ret = m_face_recognition_->FaceExtractWithAlignmentImage(wrapped, embedding.embedding, norm, normalize); + return ret; +} + +int32_t FaceSession::FaceGetFaceAlignmentImage(inspirecv::FrameProcess& process, FaceBasicData& data, inspirecv::Image& image) { + std::lock_guard lock(m_mtx_); + int32_t ret; + FaceTrackWrap face = {0}; ret = RunDeserializeHyperFaceData((char*)data.data, data.dataSize, face); if (ret != HSUCCEED) { return ret; @@ -361,7 +388,7 @@ const CustomPipelineParameter& FaceSession::getMParameter() const { int32_t FaceSession::FaceQualityDetect(FaceBasicData& data, float& result) { int32_t ret; - HyperFaceData face = {0}; + FaceTrackWrap face = {0}; ret = RunDeserializeHyperFaceData((char*)data.data, data.dataSize, face); // PrintHyperFaceData(face); if (ret != HSUCCEED) { @@ -396,6 +423,10 @@ int32_t FaceSession::SetTrackPreviewSize(const int32_t preview_size) { return HSUCCEED; } +int32_t FaceSession::GetTrackPreviewSize() const { + return m_face_track_->GetTrackPreviewSize(); +} + int32_t FaceSession::SetTrackFaceMinimumSize(int32_t minSize) { m_face_track_->SetMinimumFacePxSize(minSize); return HSUCCEED; @@ -428,4 +459,8 @@ void FaceSession::PrintTrackCostSpend() { } } +int32_t FaceSession::GetDebugPreviewImageSize() const { + return m_face_track_->GetDebugPreviewImageSize(); +} + } // namespace inspire diff --git a/cpp-package/inspireface/cpp/inspireface/face_session.h b/cpp-package/inspireface/cpp/inspireface/engine/face_session.h similarity index 87% rename from cpp-package/inspireface/cpp/inspireface/face_session.h rename to cpp-package/inspireface/cpp/inspireface/engine/face_session.h index b20a64f..a0fe46c 100644 --- a/cpp-package/inspireface/cpp/inspireface/face_session.h +++ b/cpp-package/inspireface/cpp/inspireface/engine/face_session.h @@ -14,29 +14,12 @@ #include "pipeline_module/face_pipeline_module.h" #include "middleware/model_archive/inspire_archive.h" #include "recognition_module/face_feature_extraction_module.h" -#include "middleware/inspirecv_image_process.h" +#include "frame_process.h" #include "common/face_data/face_serialize_tools.h" +#include "spend_timer.h" namespace inspire { -/** - * @struct CustomPipelineParameter - * @brief Structure to hold custom parameters for the face detection and processing pipeline. - * - * Includes options for enabling various features such as recognition, liveness detection, and quality assessment. - */ -typedef struct CustomPipelineParameter { - bool enable_recognition = false; ///< Enable face recognition feature - bool enable_liveness = false; ///< Enable RGB liveness detection feature - bool enable_ir_liveness = false; ///< Enable IR (Infrared) liveness detection feature - bool enable_mask_detect = false; ///< Enable mask detection feature - bool enable_face_attribute = false; ///< Enable face attribute prediction feature - bool enable_face_quality = false; ///< Enable face quality assessment feature - bool enable_interaction_liveness = false; ///< Enable interactive liveness detection feature - bool enable_detect_mode_landmark = false; ///< Enable landmark detection in detection mode - -} ContextCustomParameter; - /** * @class FaceContext * @brief Manages the context for face detection, tracking, and feature extraction in the HyperFaceRepo project. @@ -68,7 +51,14 @@ public: * @param image The camera stream to process for face detection and tracking. * @return int32_t Returns the number of faces detected and tracked. */// Method for face detection and tracking - int32_t FaceDetectAndTrack(inspirecv::InspireImageProcess& process); + int32_t FaceDetectAndTrack(inspirecv::FrameProcess& process); + + /** + * @brief Set the face landmark loop + * @param value The landmark loop value + * @return int32_t Status code of the operation. + * */ + int32_t SetLandmarkLoop(int32_t value); /** * @brief Set the threshold of face detection function, which only acts on the detection model @@ -86,11 +76,11 @@ public: /** * @brief Processes faces using the provided pipeline parameters. * @param image Camera stream containing faces. - * @param faces Vector of HyperFaceData for detected faces. + * @param faces Vector of FaceTrackWrap for detected faces. * @param param Custom pipeline parameters. * @return int32_t Status code of the processing. */ - int32_t FacesProcess(inspirecv::InspireImageProcess& process, const std::vector& faces, const CustomPipelineParameter& param); + int32_t FacesProcess(inspirecv::FrameProcess& process, const std::vector& faces, const CustomPipelineParameter& param); /** * @brief Retrieves the face recognition module. @@ -116,7 +106,31 @@ public: * @param data FaceBasicData to store extracted features. * @return int32_t Status code of the feature extraction. */ - int32_t FaceFeatureExtract(inspirecv::InspireImageProcess& process, FaceBasicData& data); + int32_t FaceFeatureExtract(inspirecv::FrameProcess& process, FaceBasicData& data, bool normalize = true); + + /** + * @brief Extracts features of a face from an image. + * @param image Camera stream containing the face. + * @param data FaceTrackWrap to store extracted features. + * @return int32_t Status code of the feature extraction. + */ + int32_t FaceFeatureExtract(inspirecv::FrameProcess& process, FaceTrackWrap& data, bool normalize = true); + + /** + * @brief Extracts features of a face from an image. + * @param image Camera stream containing the face. + * @param data FaceTrackWrap to store extracted features. + * @return int32_t Status code of the feature extraction. + */ + int32_t FaceFeatureExtractWithAlignmentImage(inspirecv::FrameProcess& process, Embedded& embedding, float& norm, bool normalize = true); + + /** + * @brief Extracts features of a face from an image. + * @param image Camera stream containing the face. + * @param data FaceTrackWrap to store extracted features. + * @return int32_t Status code of the feature extraction. + */ + int32_t FaceFeatureExtractWithAlignmentImage(const inspirecv::Image& wrapped, FaceEmbedding& embedding, float& norm, bool normalize = true); /** * @brief Gets the face alignment image. @@ -125,7 +139,7 @@ public: * @param image The output image. * @return int32_t The status code of the operation. */ - int32_t FaceGetFaceAlignmentImage(inspirecv::InspireImageProcess& process, FaceBasicData& data, inspirecv::Image& image); + int32_t FaceGetFaceAlignmentImage(inspirecv::FrameProcess& process, FaceBasicData& data, inspirecv::Image& image); /** * @brief Retrieves the custom pipeline parameters. @@ -148,6 +162,12 @@ public: */ int32_t SetTrackPreviewSize(int32_t preview_size); + /** + * @brief Gets the preview size for face tracking. + * @return int32_t The preview size. + */ + int32_t GetTrackPreviewSize() const; + /** * @brief Filter the minimum face pixel size. * @param minSize The minimum pixel value. @@ -347,6 +367,12 @@ public: * */ void PrintTrackCostSpend(); + /** + * @brief Get the debug preview image size + * @return int32_t The debug preview image size + * */ + int32_t GetDebugPreviewImageSize() const; + private: // Private member variables CustomPipelineParameter m_parameter_; ///< Stores custom parameters for the pipeline @@ -390,7 +416,7 @@ private: std::mutex m_mtx_; ///< Mutex for thread safety. // cost spend - std::shared_ptr m_face_track_cost_; + std::shared_ptr m_face_track_cost_; int m_enable_track_cost_spend_ = 0; }; diff --git a/cpp-package/inspireface/cpp/inspireface/feature_hub/embedding_db/embedding_db.cpp b/cpp-package/inspireface/cpp/inspireface/feature_hub/embedding_db/embedding_db.cpp index 66ed5b7..3b06937 100644 --- a/cpp-package/inspireface/cpp/inspireface/feature_hub/embedding_db/embedding_db.cpp +++ b/cpp-package/inspireface/cpp/inspireface/feature_hub/embedding_db/embedding_db.cpp @@ -2,6 +2,9 @@ #include "sqlite-vec.h" #include "isf_check.h" #include +#if defined(__ANDROID__) +#include +#endif namespace inspire { @@ -118,20 +121,13 @@ std::vector EmbeddingDB::BatchInsertVectors(const std::vector insertedIds; insertedIds.reserve(vectors.size()); - try { - for (const auto &data : vectors) { - int64_t id = 0; - bool ret = InsertVector(data.id, data.vector, id); - if (!ret) { - throw std::runtime_error("Failed to insert vector"); - } - insertedIds.push_back(id); - } - ExecuteSQL("COMMIT"); - } catch (...) { - ExecuteSQL("ROLLBACK"); - throw; + for (const auto &data : vectors) { + int64_t id = 0; + bool ret = InsertVector(data.id, data.vector, id); + INSPIREFACE_CHECK_MSG(ret, "Failed to insert vector"); + insertedIds.push_back(id); } + ExecuteSQL("COMMIT"); return insertedIds; } @@ -141,20 +137,13 @@ std::vector EmbeddingDB::BatchInsertVectors(const std::vector insertedIds; insertedIds.reserve(vectors.size()); - try { - for (const auto &vector : vectors) { - int64_t id = 0; - bool ret = InsertVector(0, vector, id); - if (!ret) { - throw std::runtime_error("Failed to insert vector"); - } - insertedIds.push_back(id); - } - ExecuteSQL("COMMIT"); - } catch (...) { - ExecuteSQL("ROLLBACK"); - throw; + for (const auto &vector : vectors) { + int64_t id = 0; + bool ret = InsertVector(0, vector, id); + INSPIREFACE_CHECK_MSG(ret, "Failed to insert vector"); + insertedIds.push_back(id); } + ExecuteSQL("COMMIT"); return insertedIds; } diff --git a/cpp-package/inspireface/cpp/inspireface/feature_hub/embedding_db/embedding_db.h b/cpp-package/inspireface/cpp/inspireface/feature_hub/embedding_db/embedding_db.h index d4c50f0..e1605fd 100644 --- a/cpp-package/inspireface/cpp/inspireface/feature_hub/embedding_db/embedding_db.h +++ b/cpp-package/inspireface/cpp/inspireface/feature_hub/embedding_db/embedding_db.h @@ -19,18 +19,12 @@ #include #include #include +#include "data_type.h" #define EMBEDDING_DB inspire::EmbeddingDB namespace inspire { -// Search for most similar vectors -struct FaceSearchResult { - int64_t id; - double similarity; - std::vector feature; -}; - // Vector data structure struct VectorData { int64_t id; // This field is ignored in auto-increment mode diff --git a/cpp-package/inspireface/cpp/inspireface/feature_hub/feature_hub_db.cpp b/cpp-package/inspireface/cpp/inspireface/feature_hub/feature_hub_db.cpp index b015ad6..2adbea8 100644 --- a/cpp-package/inspireface/cpp/inspireface/feature_hub/feature_hub_db.cpp +++ b/cpp-package/inspireface/cpp/inspireface/feature_hub/feature_hub_db.cpp @@ -9,13 +9,42 @@ #include #include "middleware/utils.h" #include "middleware/system.h" +#include "log.h" +#include "feature_hub/embedding_db/embedding_db.h" + +#define DB_FILE_NAME ".feature_hub_db_v0" namespace inspire { +class FeatureHubDB::Impl { +public: + Impl() : m_enable_(false), m_recognition_threshold_(0.48f), m_search_mode_(SEARCH_MODE_EAGER) {} + + Embedded m_search_face_feature_cache_; + Embedded m_getter_face_feature_cache_; + std::shared_ptr m_face_feature_ptr_cache_; + + std::vector m_search_top_k_cache_; + std::vector m_top_k_confidence_; + std::vector m_top_k_custom_ids_cache_; + + std::vector m_all_ids_; + + DatabaseConfiguration m_db_configuration_; + float m_recognition_threshold_; + SearchMode m_search_mode_; + + bool m_enable_; + + std::mutex m_res_mtx_; +}; + std::mutex FeatureHubDB::mutex_; std::shared_ptr FeatureHubDB::instance_ = nullptr; -FeatureHubDB::FeatureHubDB() {} +FeatureHubDB::FeatureHubDB() : pImpl(new Impl()) {} + +FeatureHubDB::~FeatureHubDB() = default; std::shared_ptr FeatureHubDB::GetInstance() { std::lock_guard lock(mutex_); @@ -26,79 +55,76 @@ std::shared_ptr FeatureHubDB::GetInstance() { } int32_t FeatureHubDB::DisableHub() { - if (!m_enable_) { + if (!pImpl->m_enable_) { INSPIRE_LOGW("FeatureHub is already disabled."); return HSUCCEED; } - // Close the database if it starts + if (EMBEDDING_DB::GetInstance().IsInitialized()) { EMBEDDING_DB::Deinit(); - // if (ret != HSUCCEED) { - // INSPIRE_LOGE("Failed to close the database: %d", ret); - // return ret; - // } - // m_db_.reset(); } - m_search_face_feature_cache_.clear(); + pImpl->m_search_face_feature_cache_.clear(); - m_db_configuration_ = DatabaseConfiguration(); // Reset using the default constructor - m_recognition_threshold_ = 0.0f; - m_search_mode_ = SEARCH_MODE_EAGER; + pImpl->m_db_configuration_ = DatabaseConfiguration(); + pImpl->m_recognition_threshold_ = 0.0f; + pImpl->m_search_mode_ = SEARCH_MODE_EAGER; - m_face_feature_ptr_cache_.reset(); - m_enable_ = false; + pImpl->m_face_feature_ptr_cache_.reset(); + pImpl->m_enable_ = false; return HSUCCEED; } int32_t FeatureHubDB::GetAllIds() { - if (!m_enable_) { + if (!pImpl->m_enable_) { INSPIRE_LOGE("FeatureHub is disabled, please enable it before it can be served"); return HERR_FT_HUB_DISABLE; } - m_all_ids_ = EMBEDDING_DB::GetInstance().GetAllIds(); + pImpl->m_all_ids_ = EMBEDDING_DB::GetInstance().GetAllIds(); return HSUCCEED; } int32_t FeatureHubDB::EnableHub(const DatabaseConfiguration &configuration) { - int32_t ret; - if (m_enable_) { + if (pImpl->m_enable_) { INSPIRE_LOGW("You have enabled the FeatureHub feature. It is not valid to do so again"); return HSUCCEED; } - // Config - m_db_configuration_ = configuration; - m_recognition_threshold_ = m_db_configuration_.recognition_threshold; - if (m_recognition_threshold_ < -1.0f || m_recognition_threshold_ > 1.0f) { + + pImpl->m_db_configuration_ = configuration; + pImpl->m_recognition_threshold_ = pImpl->m_db_configuration_.recognition_threshold; + if (pImpl->m_recognition_threshold_ < -1.0f || pImpl->m_recognition_threshold_ > 1.0f) { INSPIRE_LOGW("The search threshold entered does not fit the required range (-1.0f, 1.0f) and has been set to 0.5 by default"); - m_recognition_threshold_ = 0.5f; + pImpl->m_recognition_threshold_ = 0.5f; } + std::string dbFile = ":memory:"; - if (m_db_configuration_.enable_persistence) { - if (IsDirectory(m_db_configuration_.persistence_db_path)) { - dbFile = os::PathJoin(m_db_configuration_.persistence_db_path, DB_FILE_NAME); + if (pImpl->m_db_configuration_.enable_persistence) { + if (IsDirectory(pImpl->m_db_configuration_.persistence_db_path)) { + dbFile = os::PathJoin(pImpl->m_db_configuration_.persistence_db_path, DB_FILE_NAME); } else { - dbFile = m_db_configuration_.persistence_db_path; + dbFile = pImpl->m_db_configuration_.persistence_db_path; } } EMBEDDING_DB::Init(dbFile, 512, IdMode(configuration.primary_key_mode)); - m_enable_ = true; - m_face_feature_ptr_cache_ = std::make_shared(); + pImpl->m_enable_ = true; + pImpl->m_face_feature_ptr_cache_ = std::make_shared(); return HSUCCEED; } int32_t FeatureHubDB::CosineSimilarity(const std::vector &v1, const std::vector &v2, float &res, bool normalize) { if (v1.size() != v2.size() || v1.empty()) { - return HERR_SESS_REC_CONTRAST_FEAT_ERR; // The similarity cannot be calculated if the vector lengths are not equal + return HERR_SESS_REC_CONTRAST_FEAT_ERR; } + if (normalize) { std::vector v1_norm = v1; std::vector v2_norm = v2; float mse1 = 0.0f; float mse2 = 0.0f; + for (const auto &one : v1_norm) { mse1 += one * one; } @@ -114,9 +140,9 @@ int32_t FeatureHubDB::CosineSimilarity(const std::vector &v1, const std:: for (float &one : v2_norm) { one /= mse2; } + res = simd_dot(v1_norm.data(), v2_norm.data(), v1_norm.size()); } else { - // Calculate the cosine similarity res = simd_dot(v1.data(), v2.data(), v1.size()); } @@ -129,6 +155,7 @@ int32_t FeatureHubDB::CosineSimilarity(const float *v1, const float *v2, int32_t std::vector v2_norm(v2, v2 + size); float mse1 = 0.0f; float mse2 = 0.0f; + for (const auto &one : v1_norm) { mse1 += one * one; } @@ -136,6 +163,7 @@ int32_t FeatureHubDB::CosineSimilarity(const float *v1, const float *v2, int32_t for (float &one : v1_norm) { one /= mse1; } + for (const auto &one : v2_norm) { mse2 += one * one; } @@ -143,6 +171,7 @@ int32_t FeatureHubDB::CosineSimilarity(const float *v1, const float *v2, int32_t for (float &one : v2_norm) { one /= mse2; } + res = simd_dot(v1_norm.data(), v2_norm.data(), v1_norm.size()); } else { res = simd_dot(v1, v2, size); @@ -152,37 +181,34 @@ int32_t FeatureHubDB::CosineSimilarity(const float *v1, const float *v2, int32_t } int32_t FeatureHubDB::GetFaceFeatureCount() { - if (!m_enable_) { + if (!pImpl->m_enable_) { INSPIRE_LOGW("FeatureHub is disabled, please enable it before it can be served"); return 0; } - int totalFeatureCount = 0; - // Iterate over all FeatureBlocks and add up the number of feature vectors used - totalFeatureCount = EMBEDDING_DB::GetInstance().GetVectorCount(); - - return totalFeatureCount; + return EMBEDDING_DB::GetInstance().GetVectorCount(); } int32_t FeatureHubDB::SearchFaceFeature(const Embedded &queryFeature, FaceSearchResult &searchResult, bool returnFeature) { std::lock_guard lock(mutex_); - if (!m_enable_) { + if (!pImpl->m_enable_) { INSPIRE_LOGE("FeatureHub is disabled, please enable it before it can be served"); return HSUCCEED; } - m_search_face_feature_cache_.clear(); - auto results = EMBEDDING_DB::GetInstance().SearchSimilarVectors(queryFeature, 1, m_recognition_threshold_, returnFeature); + + pImpl->m_search_face_feature_cache_.clear(); + auto results = EMBEDDING_DB::GetInstance().SearchSimilarVectors(queryFeature, 1, pImpl->m_recognition_threshold_, returnFeature); searchResult.id = -1; + if (!results.empty()) { auto &searched = results[0]; searchResult.similarity = searched.similarity; searchResult.id = searched.id; if (returnFeature) { searchResult.feature = searched.feature; - // copy feature to cache - m_search_face_feature_cache_ = searched.feature; - m_face_feature_ptr_cache_->data = m_search_face_feature_cache_.data(); - m_face_feature_ptr_cache_->dataSize = m_search_face_feature_cache_.size(); + pImpl->m_search_face_feature_cache_ = searched.feature; + pImpl->m_face_feature_ptr_cache_->data = pImpl->m_search_face_feature_cache_.data(); + pImpl->m_face_feature_ptr_cache_->dataSize = pImpl->m_search_face_feature_cache_.size(); } } @@ -191,16 +217,18 @@ int32_t FeatureHubDB::SearchFaceFeature(const Embedded &queryFeature, FaceSearch int32_t FeatureHubDB::SearchFaceFeatureTopKCache(const Embedded &queryFeature, size_t topK) { std::lock_guard lock(mutex_); - if (!m_enable_) { + if (!pImpl->m_enable_) { INSPIRE_LOGE("FeatureHub is disabled, please enable it before it can be served"); return HERR_FT_HUB_DISABLE; } - m_top_k_confidence_.clear(); - m_top_k_custom_ids_cache_.clear(); - auto results = EMBEDDING_DB::GetInstance().SearchSimilarVectors(queryFeature, topK, m_recognition_threshold_, false); + + pImpl->m_top_k_confidence_.clear(); + pImpl->m_top_k_custom_ids_cache_.clear(); + auto results = EMBEDDING_DB::GetInstance().SearchSimilarVectors(queryFeature, topK, pImpl->m_recognition_threshold_, false); + for (size_t i = 0; i < results.size(); i++) { - m_top_k_custom_ids_cache_.push_back(results[i].id); - m_top_k_confidence_.push_back(results[i].similarity); + pImpl->m_top_k_custom_ids_cache_.push_back(results[i].id); + pImpl->m_top_k_confidence_.push_back(results[i].similarity); } return HSUCCEED; @@ -209,17 +237,18 @@ int32_t FeatureHubDB::SearchFaceFeatureTopKCache(const Embedded &queryFeature, s int32_t FeatureHubDB::SearchFaceFeatureTopK(const Embedded &queryFeature, std::vector &searchResult, size_t topK, bool returnFeature) { std::lock_guard lock(mutex_); - if (!m_enable_) { + if (!pImpl->m_enable_) { INSPIRE_LOGW("FeatureHub is disabled, please enable it before it can be served"); return HERR_FT_HUB_DISABLE; } - searchResult = EMBEDDING_DB::GetInstance().SearchSimilarVectors(queryFeature, topK, m_recognition_threshold_, returnFeature); + + searchResult = EMBEDDING_DB::GetInstance().SearchSimilarVectors(queryFeature, topK, pImpl->m_recognition_threshold_, returnFeature); return HSUCCEED; } int32_t FeatureHubDB::FaceFeatureInsert(const std::vector &feature, int32_t id, int64_t &result_id) { std::lock_guard lock(mutex_); - if (!m_enable_) { + if (!pImpl->m_enable_) { INSPIRE_LOGE("FeatureHub is disabled, please enable it before it can be served"); return HERR_FT_HUB_DISABLE; } @@ -235,65 +264,62 @@ int32_t FeatureHubDB::FaceFeatureInsert(const std::vector &feature, int32 int32_t FeatureHubDB::FaceFeatureRemove(int32_t id) { std::lock_guard lock(mutex_); - if (!m_enable_) { + if (!pImpl->m_enable_) { INSPIRE_LOGE("FeatureHub is disabled, please enable it before it can be served"); return HERR_FT_HUB_DISABLE; } - EMBEDDING_DB::GetInstance().DeleteVector(id); + EMBEDDING_DB::GetInstance().DeleteVector(id); return HSUCCEED; } int32_t FeatureHubDB::FaceFeatureUpdate(const std::vector &feature, int32_t customId) { std::lock_guard lock(mutex_); - if (!m_enable_) { + if (!pImpl->m_enable_) { INSPIRE_LOGE("FeatureHub is disabled, please enable it before it can be served"); return HERR_FT_HUB_DISABLE; } - try { - EMBEDDING_DB::GetInstance().UpdateVector(customId, feature); - } catch (const std::exception &e) { - INSPIRE_LOGW("Failed to update face feature, id: %d", customId); - return HERR_FT_HUB_NOT_FOUND_FEATURE; - } + EMBEDDING_DB::GetInstance().UpdateVector(customId, feature); return HSUCCEED; } int32_t FeatureHubDB::GetFaceFeature(int32_t id) { std::lock_guard lock(mutex_); - if (!m_enable_) { + if (!pImpl->m_enable_) { INSPIRE_LOGE("FeatureHub is disabled, please enable it before it can be served"); return HERR_FT_HUB_DISABLE; } + auto vec = EMBEDDING_DB::GetInstance().GetVector(id); if (vec.empty()) { return HERR_FT_HUB_NOT_FOUND_FEATURE; } - m_getter_face_feature_cache_ = vec; - m_face_feature_ptr_cache_->data = m_getter_face_feature_cache_.data(); - m_face_feature_ptr_cache_->dataSize = m_getter_face_feature_cache_.size(); + + pImpl->m_getter_face_feature_cache_ = vec; + pImpl->m_face_feature_ptr_cache_->data = pImpl->m_getter_face_feature_cache_.data(); + pImpl->m_face_feature_ptr_cache_->dataSize = pImpl->m_getter_face_feature_cache_.size(); return HSUCCEED; } int32_t FeatureHubDB::GetFaceFeature(int32_t id, std::vector &feature) { std::lock_guard lock(mutex_); - if (!m_enable_) { + if (!pImpl->m_enable_) { INSPIRE_LOGW("FeatureHub is disabled, please enable it before it can be served"); return HERR_FT_HUB_DISABLE; } - try { - feature = EMBEDDING_DB::GetInstance().GetVector(id); - } catch (const std::exception &e) { - INSPIRE_LOGW("Failed to get face feature, id: %d", id); + + feature = EMBEDDING_DB::GetInstance().GetVector(id); + if (feature.empty()) { return HERR_FT_HUB_NOT_FOUND_FEATURE; } + return HSUCCEED; } int32_t FeatureHubDB::ViewDBTable() { - if (!m_enable_) { + if (!pImpl->m_enable_) { INSPIRE_LOGE("FeatureHub is disabled, please enable it before it can be served"); return HERR_FT_HUB_DISABLE; } @@ -302,33 +328,33 @@ int32_t FeatureHubDB::ViewDBTable() { } void FeatureHubDB::SetRecognitionThreshold(float threshold) { - m_recognition_threshold_ = threshold; + pImpl->m_recognition_threshold_ = threshold; } void FeatureHubDB::SetRecognitionSearchMode(SearchMode mode) { - m_search_mode_ = mode; + pImpl->m_search_mode_ = mode; } // =========== Getter =========== const Embedded &FeatureHubDB::GetSearchFaceFeatureCache() const { - return m_search_face_feature_cache_; + return pImpl->m_search_face_feature_cache_; } const std::shared_ptr &FeatureHubDB::GetFaceFeaturePtrCache() const { - return m_face_feature_ptr_cache_; + return pImpl->m_face_feature_ptr_cache_; } std::vector &FeatureHubDB::GetTopKConfidence() { - return m_top_k_confidence_; + return pImpl->m_top_k_confidence_; } std::vector &FeatureHubDB::GetTopKCustomIdsCache() { - return m_top_k_custom_ids_cache_; + return pImpl->m_top_k_custom_ids_cache_; } std::vector &FeatureHubDB::GetExistingIds() { - return m_all_ids_; + return pImpl->m_all_ids_; } } // namespace inspire diff --git a/cpp-package/inspireface/cpp/inspireface/image_process/frame_process.cpp b/cpp-package/inspireface/cpp/inspireface/image_process/frame_process.cpp new file mode 100644 index 0000000..0d462f1 --- /dev/null +++ b/cpp-package/inspireface/cpp/inspireface/image_process/frame_process.cpp @@ -0,0 +1,395 @@ +#include "frame_process.h" +#include +#include +#include "isf_check.h" + +namespace inspirecv { + +class FrameProcess::Impl { +public: + Impl() : buffer_(nullptr), height_(0), width_(0), preview_scale_(0), preview_size_(192), rotation_mode_(ROTATION_0) { + SetDataFormat(NV21); + SetDestFormat(BGR); + config_.filterType = MNN::CV::BILINEAR; + config_.wrap = MNN::CV::ZERO; + } + + void SetDataFormat(DATA_FORMAT data_format) { + if (data_format == NV21) { + config_.sourceFormat = MNN::CV::YUV_NV21; + } + if (data_format == NV12) { + config_.sourceFormat = MNN::CV::YUV_NV12; + } + if (data_format == RGBA) { + config_.sourceFormat = MNN::CV::RGBA; + } + if (data_format == RGB) { + config_.sourceFormat = MNN::CV::RGB; + } + if (data_format == BGR) { + config_.sourceFormat = MNN::CV::BGR; + } + if (data_format == BGRA) { + config_.sourceFormat = MNN::CV::BGRA; + } + } + + void SetDestFormat(DATA_FORMAT data_format) { + if (data_format == NV21) { + config_.destFormat = MNN::CV::YUV_NV21; + } + if (data_format == NV12) { + config_.destFormat = MNN::CV::YUV_NV12; + } + if (data_format == RGBA) { + config_.destFormat = MNN::CV::RGBA; + } + if (data_format == RGB) { + config_.destFormat = MNN::CV::RGB; + } + if (data_format == BGR) { + config_.destFormat = MNN::CV::BGR; + } + if (data_format == BGRA) { + config_.destFormat = MNN::CV::BGRA; + } + } + + void UpdateTransformMatrix() { + float srcPoints[] = {0.0f, 0.0f, 0.0f, (float)(height_ - 1), (float)(width_ - 1), 0.0f, (float)(width_ - 1), (float)(height_ - 1)}; + + float dstPoints[8]; + if (rotation_mode_ == ROTATION_270) { + float points[] = {(float)(height_ * preview_scale_ - 1), + 0.0f, + 0.0f, + 0.0f, + (float)(height_ * preview_scale_ - 1), + (float)(width_ * preview_scale_ - 1), + 0.0f, + (float)(width_ * preview_scale_ - 1)}; + memcpy(dstPoints, points, sizeof(points)); + } else if (rotation_mode_ == ROTATION_90) { + float points[] = {0.0f, + (float)(width_ * preview_scale_ - 1), + (float)(height_ * preview_scale_ - 1), + (float)(width_ * preview_scale_ - 1), + 0.0f, + 0.0f, + (float)(height_ * preview_scale_ - 1), + 0.0f}; + memcpy(dstPoints, points, sizeof(points)); + } else if (rotation_mode_ == ROTATION_180) { + float points[] = {(float)(width_ * preview_scale_ - 1), + (float)(height_ * preview_scale_ - 1), + (float)(width_ * preview_scale_ - 1), + 0.0f, + 0.0f, + (float)(height_ * preview_scale_ - 1), + 0.0f, + 0.0f}; + memcpy(dstPoints, points, sizeof(points)); + } else { // ROTATION_0 + float points[] = {0.0f, + 0.0f, + 0.0f, + (float)(height_ * preview_scale_ - 1), + (float)(width_ * preview_scale_ - 1), + 0.0f, + (float)(width_ * preview_scale_ - 1), + (float)(height_ * preview_scale_ - 1)}; + memcpy(dstPoints, points, sizeof(points)); + } + + tr_.setPolyToPoly((MNN::CV::Point *)dstPoints, (MNN::CV::Point *)srcPoints, 4); + } + + const uint8_t *buffer_; // Pointer to the data buffer. + int height_; // Height of the camera stream image. + int width_; // Width of the camera stream image. + float preview_scale_; // Scaling factor for the preview image. + int preview_size_; // Size of the preview image. + MNN::CV::Matrix tr_; // Affine transformation matrix. + ROTATION_MODE rotation_mode_; // Current rotation mode. + MNN::CV::ImageProcess::Config config_; // Image processing configuration. +}; + +FrameProcess FrameProcess::Create(const uint8_t *data_buffer, int height, int width, DATA_FORMAT data_format, ROTATION_MODE rotation_mode) { + FrameProcess process; + process.SetDataBuffer(data_buffer, height, width); + process.SetDataFormat(data_format); + process.SetRotationMode(rotation_mode); + return process; +} + +FrameProcess FrameProcess::Create(const inspirecv::Image &image, DATA_FORMAT data_format, ROTATION_MODE rotation_mode) { + return Create(image.Data(), image.Height(), image.Width(), data_format, rotation_mode); +} + +FrameProcess::FrameProcess() : pImpl(std::make_unique()) { + pImpl->UpdateTransformMatrix(); +} + +FrameProcess::~FrameProcess() = default; + +FrameProcess::FrameProcess(const FrameProcess &other) : pImpl(std::make_unique(*other.pImpl)) {} + +FrameProcess::FrameProcess(FrameProcess &&other) noexcept = default; + +FrameProcess &FrameProcess::operator=(const FrameProcess &other) { + if (this != &other) { + *pImpl = *other.pImpl; + } + return *this; +} + +FrameProcess &FrameProcess::operator=(FrameProcess &&other) noexcept = default; + +void FrameProcess::SetDataBuffer(const uint8_t *data_buffer, int height, int width) { + pImpl->buffer_ = data_buffer; + pImpl->height_ = height; + pImpl->width_ = width; + pImpl->preview_scale_ = pImpl->preview_size_ / static_cast(std::max(height, width)); + pImpl->UpdateTransformMatrix(); +} + +void FrameProcess::SetPreviewSize(const int size) { + pImpl->preview_size_ = size; + pImpl->preview_scale_ = pImpl->preview_size_ / static_cast(std::max(pImpl->height_, pImpl->width_)); + pImpl->UpdateTransformMatrix(); +} + +void FrameProcess::SetPreviewScale(const float scale) { + pImpl->preview_scale_ = scale; + pImpl->preview_size_ = static_cast(pImpl->preview_scale_ * std::max(pImpl->height_, pImpl->width_)); + pImpl->UpdateTransformMatrix(); +} + +void FrameProcess::SetRotationMode(ROTATION_MODE mode) { + pImpl->rotation_mode_ = mode; + pImpl->UpdateTransformMatrix(); +} + +void FrameProcess::SetDataFormat(DATA_FORMAT data_format) { + pImpl->SetDataFormat(data_format); +} + +void FrameProcess::SetDestFormat(DATA_FORMAT data_format) { + pImpl->SetDestFormat(data_format); +} + +float FrameProcess::GetPreviewScale() { + return pImpl->preview_scale_; +} + +inspirecv::TransformMatrix FrameProcess::GetAffineMatrix() const { + auto affine_matrix = inspirecv::TransformMatrix::Create(); + affine_matrix[0] = pImpl->tr_[0]; + affine_matrix[1] = pImpl->tr_[1]; + affine_matrix[2] = pImpl->tr_[2]; + affine_matrix[3] = pImpl->tr_[3]; + affine_matrix[4] = pImpl->tr_[4]; + affine_matrix[5] = pImpl->tr_[5]; + return affine_matrix; +} + +int FrameProcess::GetHeight() const { + return pImpl->height_; +} + +int FrameProcess::GetWidth() const { + return pImpl->width_; +} + +ROTATION_MODE FrameProcess::getRotationMode() const { + return pImpl->rotation_mode_; +} + +inspirecv::Image FrameProcess::ExecuteImageAffineProcessing(inspirecv::TransformMatrix &affine_matrix, const int width_out, + const int height_out) const { + int sw = pImpl->width_; + int sh = pImpl->height_; + int rot_sw = sw; + int rot_sh = sh; + MNN::CV::Matrix tr; + std::vector tr_cv({1, 0, 0, 0, 1, 0, 0, 0, 1}); + memcpy(tr_cv.data(), affine_matrix.Squeeze().data(), sizeof(float) * 6); + tr.set9(tr_cv.data()); + MNN::CV::Matrix tr_inv; + tr.invert(&tr_inv); + std::shared_ptr process(MNN::CV::ImageProcess::create(pImpl->config_)); + process->setMatrix(tr_inv); + auto img_out = inspirecv::Image::Create(width_out, height_out, 3); + std::shared_ptr tensor(MNN::Tensor::create(std::vector{1, height_out, width_out, 3}, (uint8_t *)img_out.Data())); + auto ret = process->convert(pImpl->buffer_, sw, sh, 0, tensor.get()); + INSPIREFACE_CHECK_MSG(ret == MNN::ErrorCode::NO_ERROR, "ImageProcess::convert failed"); + return img_out; +} + +inspirecv::Image FrameProcess::ExecutePreviewImageProcessing(bool with_rotation) { + return ExecuteImageScaleProcessing(pImpl->preview_scale_, with_rotation); +} + +inspirecv::Image FrameProcess::ExecuteImageScaleProcessing(const float scale, bool with_rotation) { + int sw = pImpl->width_; + int sh = pImpl->height_; + int rot_sw = sw; + int rot_sh = sh; + // MNN::CV::Matrix tr; + std::shared_ptr process(MNN::CV::ImageProcess::create(pImpl->config_)); + if (pImpl->rotation_mode_ == ROTATION_270 && with_rotation) { + float srcPoints[] = { + 0.0f, 0.0f, 0.0f, (float)(pImpl->height_ - 1), (float)(pImpl->width_ - 1), 0.0f, (float)(pImpl->width_ - 1), (float)(pImpl->height_ - 1), + }; + float dstPoints[] = { + (float)(pImpl->height_ * scale - 1), 0.0f, 0.0f, 0.0f, (float)(pImpl->height_ * scale - 1), (float)(pImpl->width_ * scale - 1), 0.0f, + (float)(pImpl->width_ * scale - 1)}; + + pImpl->tr_.setPolyToPoly((MNN::CV::Point *)dstPoints, (MNN::CV::Point *)srcPoints, 4); + process->setMatrix(pImpl->tr_); + int scaled_height = static_cast(pImpl->width_ * scale); + int scaled_width = static_cast(pImpl->height_ * scale); + inspirecv::Image img_out(scaled_width, scaled_height, 3); + std::shared_ptr tensor( + MNN::Tensor::create(std::vector{1, scaled_height, scaled_width, 3}, (uint8_t *)img_out.Data())); + auto ret = process->convert(pImpl->buffer_, sw, sh, 0, tensor.get()); + INSPIREFACE_CHECK_MSG(ret == MNN::ErrorCode::NO_ERROR, "ImageProcess::convert failed"); + return img_out; + } else if (pImpl->rotation_mode_ == ROTATION_90 && with_rotation) { + float srcPoints[] = { + 0.0f, 0.0f, 0.0f, (float)(pImpl->height_ - 1), (float)(pImpl->width_ - 1), 0.0f, (float)(pImpl->width_ - 1), (float)(pImpl->height_ - 1), + }; + float dstPoints[] = { + 0.0f, + (float)(pImpl->width_ * scale - 1), + (float)(pImpl->height_ * scale - 1), + (float)(pImpl->width_ * scale - 1), + 0.0f, + 0.0f, + (float)(pImpl->height_ * scale - 1), + 0.0f, + }; + pImpl->tr_.setPolyToPoly((MNN::CV::Point *)dstPoints, (MNN::CV::Point *)srcPoints, 4); + process->setMatrix(pImpl->tr_); + int scaled_height = static_cast(pImpl->width_ * scale); + int scaled_width = static_cast(pImpl->height_ * scale); + inspirecv::Image img_out(scaled_width, scaled_height, 3); + std::shared_ptr tensor( + MNN::Tensor::create(std::vector{1, scaled_height, scaled_width, 3}, (uint8_t *)img_out.Data())); + auto ret = process->convert(pImpl->buffer_, sw, sh, 0, tensor.get()); + INSPIREFACE_CHECK_MSG(ret == MNN::ErrorCode::NO_ERROR, "ImageProcess::convert failed"); + return img_out; + } else if (pImpl->rotation_mode_ == ROTATION_180 && with_rotation) { + float srcPoints[] = { + 0.0f, 0.0f, 0.0f, (float)(pImpl->height_ - 1), (float)(pImpl->width_ - 1), 0.0f, (float)(pImpl->width_ - 1), (float)(pImpl->height_ - 1), + }; + float dstPoints[] = { + (float)(pImpl->width_ * scale - 1), + (float)(pImpl->height_ * scale - 1), + (float)(pImpl->width_ * scale - 1), + 0.0f, + 0.0f, + (float)(pImpl->height_ * scale - 1), + 0.0f, + 0.0f, + }; + pImpl->tr_.setPolyToPoly((MNN::CV::Point *)dstPoints, (MNN::CV::Point *)srcPoints, 4); + process->setMatrix(pImpl->tr_); + int scaled_height = static_cast(pImpl->height_ * scale); + int scaled_width = static_cast(pImpl->width_ * scale); + inspirecv::Image img_out(scaled_width, scaled_height, 3); + std::shared_ptr tensor( + MNN::Tensor::create(std::vector{1, scaled_height, scaled_width, 3}, (uint8_t *)img_out.Data())); + auto ret = process->convert(pImpl->buffer_, sw, sh, 0, tensor.get()); + INSPIREFACE_CHECK_MSG(ret == MNN::ErrorCode::NO_ERROR, "ImageProcess::convert failed"); + return img_out; + } else { + float srcPoints[] = { + 0.0f, 0.0f, 0.0f, (float)(pImpl->height_ - 1), (float)(pImpl->width_ - 1), 0.0f, (float)(pImpl->width_ - 1), (float)(pImpl->height_ - 1), + }; + float dstPoints[] = { + 0.0f, + 0.0f, + 0.0f, + (float)(pImpl->height_ * scale - 1), + (float)(pImpl->width_ * scale - 1), + 0.0f, + (float)(pImpl->width_ * scale - 1), + (float)(pImpl->height_ * scale - 1), + }; + pImpl->tr_.setPolyToPoly((MNN::CV::Point *)dstPoints, (MNN::CV::Point *)srcPoints, 4); + process->setMatrix(pImpl->tr_); + int scaled_height = static_cast(pImpl->height_ * scale); + int scaled_width = static_cast(pImpl->width_ * scale); + + inspirecv::Image img_out(scaled_width, scaled_height, 3); + std::shared_ptr tensor( + MNN::Tensor::create(std::vector{1, scaled_height, scaled_width, 3}, (uint8_t *)img_out.Data())); + auto ret = process->convert(pImpl->buffer_, sw, sh, 0, tensor.get()); + INSPIREFACE_CHECK_MSG(ret == MNN::ErrorCode::NO_ERROR, "ImageProcess::convert failed"); + return img_out; + } +} + +inspirecv::TransformMatrix FrameProcess::GetRotationModeAffineMatrix() const { + float srcPoints[] = {0.0f, 0.0f, 0.0f, (float)(pImpl->height_ - 1), (float)(pImpl->width_ - 1), 0.0f, (float)(pImpl->width_ - 1), (float)(pImpl->height_ - 1)}; + float dstPoints[8]; + + if (pImpl->rotation_mode_ == ROTATION_270) { + float points[] = {(float)(pImpl->height_ - 1), + 0.0f, + 0.0f, + 0.0f, + (float)(pImpl->height_ - 1), + (float)(pImpl->width_ - 1), + 0.0f, + (float)(pImpl->width_ - 1)}; + memcpy(dstPoints, points, sizeof(points)); + } else if (pImpl->rotation_mode_ == ROTATION_90) { + float points[] = {0.0f, + (float)(pImpl->width_ - 1), + (float)(pImpl->height_ - 1), + (float)(pImpl->width_ - 1), + 0.0f, + 0.0f, + (float)(pImpl->height_ - 1), + 0.0f}; + memcpy(dstPoints, points, sizeof(points)); + } else if (pImpl->rotation_mode_ == ROTATION_180) { + float points[] = {(float)(pImpl->width_ - 1), + (float)(pImpl->height_ - 1), + (float)(pImpl->width_ - 1), + 0.0f, + 0.0f, + (float)(pImpl->height_ - 1), + 0.0f, + 0.0f}; + memcpy(dstPoints, points, sizeof(points)); + } else { // ROTATION_0 + float points[] = {0.0f, + 0.0f, + 0.0f, + (float)(pImpl->height_ - 1), + (float)(pImpl->width_ - 1), + 0.0f, + (float)(pImpl->width_ - 1), + (float)(pImpl->height_ - 1)}; + memcpy(dstPoints, points, sizeof(points)); + } + + MNN::CV::Matrix tr; + tr.setPolyToPoly((MNN::CV::Point *)dstPoints, (MNN::CV::Point *)srcPoints, 4); + + auto affine_matrix = inspirecv::TransformMatrix::Create(); + affine_matrix[0] = tr[0]; + affine_matrix[1] = tr[1]; + affine_matrix[2] = tr[2]; + affine_matrix[3] = tr[3]; + affine_matrix[4] = tr[4]; + affine_matrix[5] = tr[5]; + + return affine_matrix; +} + +} // namespace inspirecv \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/inspireface/middleware/nexus_processor/image_processor.cpp b/cpp-package/inspireface/cpp/inspireface/image_process/nexus_processor/image_processor.cpp similarity index 100% rename from cpp-package/inspireface/cpp/inspireface/middleware/nexus_processor/image_processor.cpp rename to cpp-package/inspireface/cpp/inspireface/image_process/nexus_processor/image_processor.cpp diff --git a/cpp-package/inspireface/cpp/inspireface/middleware/nexus_processor/image_processor.h b/cpp-package/inspireface/cpp/inspireface/image_process/nexus_processor/image_processor.h similarity index 100% rename from cpp-package/inspireface/cpp/inspireface/middleware/nexus_processor/image_processor.h rename to cpp-package/inspireface/cpp/inspireface/image_process/nexus_processor/image_processor.h diff --git a/cpp-package/inspireface/cpp/inspireface/middleware/nexus_processor/image_processor_general.cpp b/cpp-package/inspireface/cpp/inspireface/image_process/nexus_processor/image_processor_general.cpp similarity index 100% rename from cpp-package/inspireface/cpp/inspireface/middleware/nexus_processor/image_processor_general.cpp rename to cpp-package/inspireface/cpp/inspireface/image_process/nexus_processor/image_processor_general.cpp diff --git a/cpp-package/inspireface/cpp/inspireface/middleware/nexus_processor/image_processor_general.h b/cpp-package/inspireface/cpp/inspireface/image_process/nexus_processor/image_processor_general.h similarity index 100% rename from cpp-package/inspireface/cpp/inspireface/middleware/nexus_processor/image_processor_general.h rename to cpp-package/inspireface/cpp/inspireface/image_process/nexus_processor/image_processor_general.h diff --git a/cpp-package/inspireface/cpp/inspireface/middleware/nexus_processor/image_processor_rga.cpp b/cpp-package/inspireface/cpp/inspireface/image_process/nexus_processor/image_processor_rga.cpp similarity index 100% rename from cpp-package/inspireface/cpp/inspireface/middleware/nexus_processor/image_processor_rga.cpp rename to cpp-package/inspireface/cpp/inspireface/image_process/nexus_processor/image_processor_rga.cpp diff --git a/cpp-package/inspireface/cpp/inspireface/middleware/nexus_processor/image_processor_rga.h b/cpp-package/inspireface/cpp/inspireface/image_process/nexus_processor/image_processor_rga.h similarity index 97% rename from cpp-package/inspireface/cpp/inspireface/middleware/nexus_processor/image_processor_rga.h rename to cpp-package/inspireface/cpp/inspireface/image_process/nexus_processor/image_processor_rga.h index 00e85b2..fb16a3b 100644 --- a/cpp-package/inspireface/cpp/inspireface/middleware/nexus_processor/image_processor_rga.h +++ b/cpp-package/inspireface/cpp/inspireface/image_process/nexus_processor/image_processor_rga.h @@ -25,7 +25,7 @@ #include "RgaUtils.h" #include "rga/utils.h" #include "rga/dma_alloc.h" -#include "initialization_module/launch.h" +#include namespace inspire { @@ -109,7 +109,7 @@ private: channels = c; buffer_size = width * height * channels; - int ret = dma_buf_alloc(INSPIRE_LAUNCH->GetRockchipDmaHeapPath().c_str(), buffer_size, &dma_fd, &virtual_addr); + int ret = dma_buf_alloc(INSPIREFACE_CONTEXT->GetRockchipDmaHeapPath().c_str(), buffer_size, &dma_fd, &virtual_addr); if (ret < 0) { INSPIRECV_LOG(ERROR) << "Failed to allocate DMA buffer: " << ret; return false; diff --git a/cpp-package/inspireface/cpp/inspireface/middleware/nexus_processor/rga/dma_alloc.cpp b/cpp-package/inspireface/cpp/inspireface/image_process/nexus_processor/rga/dma_alloc.cpp similarity index 100% rename from cpp-package/inspireface/cpp/inspireface/middleware/nexus_processor/rga/dma_alloc.cpp rename to cpp-package/inspireface/cpp/inspireface/image_process/nexus_processor/rga/dma_alloc.cpp diff --git a/cpp-package/inspireface/cpp/inspireface/middleware/nexus_processor/rga/dma_alloc.h b/cpp-package/inspireface/cpp/inspireface/image_process/nexus_processor/rga/dma_alloc.h similarity index 100% rename from cpp-package/inspireface/cpp/inspireface/middleware/nexus_processor/rga/dma_alloc.h rename to cpp-package/inspireface/cpp/inspireface/image_process/nexus_processor/rga/dma_alloc.h diff --git a/cpp-package/inspireface/cpp/inspireface/middleware/nexus_processor/rga/utils.cpp b/cpp-package/inspireface/cpp/inspireface/image_process/nexus_processor/rga/utils.cpp similarity index 100% rename from cpp-package/inspireface/cpp/inspireface/middleware/nexus_processor/rga/utils.cpp rename to cpp-package/inspireface/cpp/inspireface/image_process/nexus_processor/rga/utils.cpp diff --git a/cpp-package/inspireface/cpp/inspireface/middleware/nexus_processor/rga/utils.h b/cpp-package/inspireface/cpp/inspireface/image_process/nexus_processor/rga/utils.h similarity index 100% rename from cpp-package/inspireface/cpp/inspireface/middleware/nexus_processor/rga/utils.h rename to cpp-package/inspireface/cpp/inspireface/image_process/nexus_processor/rga/utils.h diff --git a/cpp-package/inspireface/cpp/inspireface/include/inspireface/cuda_toolkit.h b/cpp-package/inspireface/cpp/inspireface/include/inspireface/cuda_toolkit.h new file mode 100644 index 0000000..14cf649 --- /dev/null +++ b/cpp-package/inspireface/cpp/inspireface/include/inspireface/cuda_toolkit.h @@ -0,0 +1,22 @@ +#ifndef INSPIRE_CUDA_TOOLKIT_H +#define INSPIRE_CUDA_TOOLKIT_H + +#include "data_type.h" + +namespace inspire { + +// Get the number of CUDA devices +int32_t INSPIRE_API_EXPORT GetCudaDeviceCount(int32_t *device_count); + +// Check the availability of CUDA +int32_t INSPIRE_API_EXPORT CheckCudaUsability(int32_t *is_support); + +// Internal function, print detailed information of CUDA devices +int32_t INSPIRE_API_EXPORT _PrintCudaDeviceInfo(); + +// Wrapper function to print CUDA device information +int32_t INSPIRE_API_EXPORT PrintCudaDeviceInfo(); + +} // namespace inspire + +#endif // INSPIRE_CUDA_TOOLKIT_H \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/inspireface/data_type.h b/cpp-package/inspireface/cpp/inspireface/include/inspireface/data_type.h similarity index 52% rename from cpp-package/inspireface/cpp/inspireface/data_type.h rename to cpp-package/inspireface/cpp/inspireface/include/inspireface/data_type.h index 7792909..e9c46d9 100644 --- a/cpp-package/inspireface/cpp/inspireface/data_type.h +++ b/cpp-package/inspireface/cpp/inspireface/include/inspireface/data_type.h @@ -6,6 +6,8 @@ #ifndef INSPIRE_FACE_DATATYPE_H #define INSPIRE_FACE_DATATYPE_H +#include + #include #if defined(_WIN32) && (defined(_DEBUG) || defined(DEBUG)) #define _CRTDBG_MAP_ALLOC @@ -16,7 +18,15 @@ #define INSPIRE_API #endif -#include +#if defined(_WIN32) +#ifdef ISF_BUILD_SHARED_LIBS +#define INSPIRE_API_EXPORT __declspec(dllexport) +#else +#define INSPIRE_API_EXPORT +#endif +#else +#define INSPIRE_API_EXPORT __attribute__((visibility("default"))) +#endif // _WIN32 #ifndef M_PI #define M_PI 3.14159265358979323846264338327950288 @@ -152,6 +162,33 @@ typedef std::string String; */ typedef std::vector IndexList; +/** + * @enum DetectMode + * @brief Enumeration for different detection modes. + */ +enum DetectModuleMode { + DETECT_MODE_ALWAYS_DETECT = 0, ///< Detection mode: Always detect + DETECT_MODE_LIGHT_TRACK, ///< Detection mode: Light face track + DETECT_MODE_TRACK_BY_DETECT, ///< Detection mode: Tracking by detection +}; + +/** + * @struct CustomPipelineParameter + * @brief Structure to hold custom parameters for the face detection and processing pipeline. + * + * Includes options for enabling various features such as recognition, liveness detection, and quality assessment. + */ +typedef struct CustomPipelineParameter { + bool enable_recognition = false; ///< Enable face recognition feature + bool enable_liveness = false; ///< Enable RGB liveness detection feature + bool enable_ir_liveness = false; ///< Enable IR (Infrared) liveness detection feature + bool enable_mask_detect = false; ///< Enable mask detection feature + bool enable_face_attribute = false; ///< Enable face attribute prediction feature + bool enable_face_quality = false; ///< Enable face quality assessment feature + bool enable_interaction_liveness = false; ///< Enable interactive liveness detection feature + bool enable_face_pose = false; ///< Enable face pose estimation feature +} ContextCustomParameter; + /** @struct FaceLoc * @brief Struct representing standardized face landmarks for detection. * @@ -191,6 +228,74 @@ typedef struct FaceFeatureEntity { float* data; } FaceFeaturePtr; +// Search for most similar vectors +struct FaceSearchResult { + int64_t id; + double similarity; + std::vector feature; +}; + +/** @struct FaceEmbedding + * @brief Struct for face embedding data. + * + * Contains the isNormal flag and the embedding vector. + */ +struct FaceEmbedding { + int32_t isNormal; + float norm; + Embedded embedding; +}; + +/** @struct FaceInteractionState + * @brief Struct for face interaction state data. + * + * Contains the confidence scores for face interaction. + */ +struct FaceInteractionState { + float left_eye_status_confidence; + float right_eye_status_confidence; +}; + +/** @struct FaceInteractionAction + * @brief Struct for face interaction action data. + * + * Contains the actions for face interaction. + */ +struct FaceInteractionAction { + int32_t normal; ///< Normal action. + int32_t shake; ///< Shake action. + int32_t jawOpen; ///< Jaw open action. + int32_t headRaise; ///< Head raise action. + int32_t blink; ///< Blink action. +}; + +/** @struct FaceAttributeResult + * @brief Struct for face attribute result data. + * + * Contains the results for face attribute. + */ +struct FaceAttributeResult { + int32_t race; ///< Race of the detected face. + ///< 0: Black; + ///< 1: Asian; + ///< 2: Latino/Hispanic; + ///< 3: Middle Eastern; + ///< 4: White; + int32_t gender; ///< Gender of the detected face. + ///< 0: Female; + ///< 1: Male; + int32_t ageBracket; ///< Age bracket of the detected face. + ///< 0: 0-2 years old; + ///< 1: 3-9 years old; + ///< 2: 10-19 years old; + ///< 3: 20-29 years old; + ///< 4: 30-39 years old; + ///< 5: 40-49 years old; + ///< 6: 50-59 years old; + ///< 7: 60-69 years old; + ///< 8: more than 70 years old; +}; + /** @} */ } // namespace inspire diff --git a/cpp-package/inspireface/cpp/inspireface/common/face_data/face_data_type.h b/cpp-package/inspireface/cpp/inspireface/include/inspireface/face_warpper.h similarity index 88% rename from cpp-package/inspireface/cpp/inspireface/common/face_data/face_data_type.h rename to cpp-package/inspireface/cpp/inspireface/include/inspireface/face_warpper.h index bb81cd8..96c3635 100644 --- a/cpp-package/inspireface/cpp/inspireface/common/face_data/face_data_type.h +++ b/cpp-package/inspireface/cpp/inspireface/include/inspireface/face_warpper.h @@ -8,11 +8,8 @@ #ifndef INSPIRE_FACE_FACEDATATYPE_H #define INSPIRE_FACE_FACEDATATYPE_H -// Include the necessary header files -#include "../../data_type.h" -#include "../face_info/face_object_internal.h" +#include "data_type.h" -// Define the namespace "inspire" for encapsulation namespace inspire { /** @@ -55,9 +52,9 @@ typedef struct TransMatrix { } TransMatrix; /** - * Struct to represent hyper face data. + * Struct to represent basic face data. */ -typedef struct HyperFaceData { +typedef struct FaceTrackWrap { int trackState; ///< Track state int inGroupIndex; ///< Index within a group int trackId; ///< Track ID @@ -69,7 +66,7 @@ typedef struct HyperFaceData { float quality[5]; ///< Quality values for key points Point2F densityLandmark[106]; ///< Face density landmark int densityLandmarkEnable; ///< Density landmark enable -} HyperFaceData; +} FaceTrackWrap; } // namespace inspire diff --git a/cpp-package/inspireface/cpp/inspireface/feature_hub/feature_hub_db.h b/cpp-package/inspireface/cpp/inspireface/include/inspireface/feature_hub_db.h similarity index 64% rename from cpp-package/inspireface/cpp/inspireface/feature_hub/feature_hub_db.h rename to cpp-package/inspireface/cpp/inspireface/include/inspireface/feature_hub_db.h index 8c33ce7..a683b7e 100644 --- a/cpp-package/inspireface/cpp/inspireface/feature_hub/feature_hub_db.h +++ b/cpp-package/inspireface/cpp/inspireface/include/inspireface/feature_hub_db.h @@ -6,28 +6,17 @@ #ifndef INSPIRE_FEATURE_HUB_DB_H #define INSPIRE_FEATURE_HUB_DB_H -#include +#include #include #include -#include #include "data_type.h" -#include "feature_hub/embedding_db/embedding_db.h" -#include "log.h" +#include -// Default database file name used in the FaceContext. -#define DB_FILE_NAME ".feature_hub_db_v0" - -#define FEATURE_HUB_DB FeatureHubDB::GetInstance() +#define INSPIREFACE_FEATURE_HUB inspire::FeatureHubDB::GetInstance() +#define INSPIRE_INVALID_ID -1 namespace inspire { -// Comparator function object to sort SearchResult by score (descending order) -struct CompareByScore { - bool operator()(const FaceSearchResult& a, const FaceSearchResult& b) const { - return a.similarity > b.similarity; - } -}; - typedef enum SearchMode { SEARCH_MODE_EAGER = 0, // Eager mode: Stops when a vector meets the threshold. SEARCH_MODE_EXHAUSTIVE, // Exhaustive mode: Searches until the best match is found. @@ -43,7 +32,7 @@ typedef enum PrimaryKeyMode { * @brief Structure to configure database settings for FaceRecognition. */ using DatabaseConfiguration = struct DatabaseConfiguration { - PrimaryKeyMode primary_key_mode = PrimaryKeyMode::AUTO_INCREMENT; ///< + PrimaryKeyMode primary_key_mode = PrimaryKeyMode::AUTO_INCREMENT; ///< Primary key mode bool enable_persistence = false; ///< Whether to enable data persistence. std::string persistence_db_path; ///< Path to the database file. float recognition_threshold = 0.48f; ///< Face search threshold @@ -51,72 +40,75 @@ using DatabaseConfiguration = struct DatabaseConfiguration { }; /** - * @class FeatureHub + * @class FeatureHubDB * @brief Service for internal feature vector storage. * * This class provides methods for face feature extraction, registration, update, search, and more. + * It uses the PIMPL (Pointer to Implementation) pattern to hide implementation details. */ -class INSPIRE_API FeatureHubDB { -private: - static std::mutex mutex_; ///< Mutex lock - static std::shared_ptr instance_; ///< FeatureHub Instance +class INSPIRE_API_EXPORT FeatureHubDB { +public: + /** + * @brief Constructor for FeatureHubDB class. + */ + FeatureHubDB(); + + /** + * @brief Destructor for FeatureHubDB class. + */ + ~FeatureHubDB(); FeatureHubDB(const FeatureHubDB&) = delete; FeatureHubDB& operator=(const FeatureHubDB&) = delete; -public: /** - * @brief Enables the feature hub with the specified configuration and matrix core. - * - * This function initializes and configures the feature hub based on the provided database - * configuration and the specified matrix processing core. It prepares the hub for operation, - * setting up necessary resources such as database connections and data processing pipelines. - * - * @param configuration The database configuration settings used to configure the hub. - * @param core The matrix core used for processing, defaulting to OpenCV if not specified. + * @brief Gets the singleton instance of FeatureHubDB. + * @return Shared pointer to the FeatureHubDB instance. + */ + static std::shared_ptr GetInstance(); + + /** + * @brief Enables the feature hub with the specified configuration. + * @param configuration The database configuration settings. * @return int32_t Returns a status code indicating success (0) or failure (non-zero). */ int32_t EnableHub(const DatabaseConfiguration& configuration); /** * @brief Disables the feature hub, freeing all associated resources. - * - * This function stops all operations within the hub, releases all occupied resources, - * such as database connections and internal data structures. It is used to safely - * shutdown the hub when it is no longer needed or before the application exits, ensuring - * that all resources are properly cleaned up. - * * @return int32_t Returns a status code indicating success (0) or failure (non-zero). */ int32_t DisableHub(); /** * @brief Get all ids in the database. - * @param ids Output parameter to store the ids. * @return int32_t Status code of the operation. */ int32_t GetAllIds(); - static std::shared_ptr GetInstance(); - /** * @brief Searches for a face feature within stored data. * @param queryFeature Embedded feature to search for. * @param searchResult SearchResult object to store search results. + * @param returnFeature Whether to return the feature data. * @return int32_t Status code of the search operation. */ int32_t SearchFaceFeature(const Embedded& queryFeature, FaceSearchResult& searchResult, bool returnFeature = true); /** * @brief Search the stored data for the top k facial features that are most similar. - * @param topK Maximum search + * @param queryFeature Embedded feature to search for. + * @param topK Maximum number of results to return. * @return int32_t Status code of the search operation. */ int32_t SearchFaceFeatureTopKCache(const Embedded& queryFeature, size_t topK); /** * @brief Search the stored data for the top k facial features that are most similar. - * @param topK Maximum search + * @param queryFeature Embedded feature to search for. + * @param searchResult Vector to store search results. + * @param topK Maximum number of results to return. + * @param returnFeature Whether to return the feature data. * @return int32_t Status code of the search operation. */ int32_t SearchFaceFeatureTopK(const Embedded& queryFeature, std::vector& searchResult, size_t topK, bool returnFeature = false); @@ -124,39 +116,38 @@ public: /** * @brief Inserts a face feature with a custom ID. * @param feature Vector of floats representing the face feature. - * @param tag String tag associated with the feature. - * @param customId Custom ID for the feature. + * @param id ID for the feature. + * @param result_id Output parameter to store the resulting ID. * @return int32_t Status code of the insertion operation. */ int32_t FaceFeatureInsert(const std::vector& feature, int32_t id, int64_t& result_id); /** - * @brief Removes a face feature by its custom ID. - * @param customId Custom ID of the feature to remove. + * @brief Removes a face feature by its ID. + * @param id ID of the feature to remove. * @return int32_t Status code of the removal operation. */ int32_t FaceFeatureRemove(int32_t id); /** - * @brief Updates a face feature by its custom ID. + * @brief Updates a face feature by its ID. * @param feature Vector of floats representing the new face feature. - * @param tag String tag associated with the feature. - * @param customId Custom ID of the feature to update. + * @param customId ID of the feature to update. * @return int32_t Status code of the update operation. */ int32_t FaceFeatureUpdate(const std::vector& feature, int32_t customId); /** - * @brief Retrieves a face feature by its custom ID. - * @param customId Custom ID of the feature to retrieve. + * @brief Retrieves a face feature by its ID. + * @param id ID of the feature to retrieve. * @return int32_t Status code of the retrieval operation. */ int32_t GetFaceFeature(int32_t id); /** - * @brief Retrieves a face feature by its custom ID. - * @param customId Custom ID of the feature to retrieve. - * @param feature Vector of floats representing the face feature. + * @brief Retrieves a face feature by its ID. + * @param id ID of the feature to retrieve. + * @param feature Vector to store the retrieved feature. * @return int32_t Status code of the retrieval operation. */ int32_t GetFaceFeature(int32_t id, std::vector& feature); @@ -181,27 +172,26 @@ public: /** * @brief Computes the cosine similarity between two feature vectors. - * * @param v1 First feature vector. * @param v2 Second feature vector. * @param res Output parameter to store the cosine similarity result. + * @param normalize Whether to normalize the vectors before computing similarity. * @return int32_t Status code indicating success (0) or failure. */ static int32_t CosineSimilarity(const std::vector& v1, const std::vector& v2, float& res, bool normalize = false); /** * @brief Computes the cosine similarity between two feature vectors. - * * @param v1 Pointer to the first feature vector. * @param v2 Pointer to the second feature vector. * @param size Size of the feature vectors. * @param res Output parameter to store the cosine similarity result. + * @param normalize Whether to normalize the vectors before computing similarity. * @return int32_t Status code indicating success (0) or failure. */ static int32_t CosineSimilarity(const float* v1, const float* v2, int32_t size, float& res, bool normalize = true); -public: - // Getter Function + // Getter methods /** * @brief Gets the cache used for search operations in face feature data. @@ -216,8 +206,7 @@ public: const std::shared_ptr& GetFaceFeaturePtrCache() const; /** - * @brief Retrieves the total number of facial features stored in the feature block. - * + * @brief Retrieves the total number of facial features stored. * @return int32_t Total number of facial features. */ int32_t GetFaceFeatureCount(); @@ -240,37 +229,15 @@ public: */ std::vector& GetExistingIds(); - /** - * @brief Constructor for FeatureHub class. - */ - FeatureHubDB(); - - /** - * @brief Prints information about the feature matrix. - */ - void PrintFeatureMatrixInfo(); - private: - Embedded m_search_face_feature_cache_; ///< Cache for face feature data used in search operations - Embedded m_getter_face_feature_cache_; ///< Cache for face feature data used in search operations - std::shared_ptr m_face_feature_ptr_cache_; ///< Shared pointer to cache of face feature pointers + class Impl; - std::vector m_search_top_k_cache_; ///< Cache for top k search results - std::vector m_top_k_confidence_; ///< Cache for top k confidence scores - std::vector m_top_k_custom_ids_cache_; ///< Cache for top k custom ids + std::unique_ptr pImpl; - std::vector m_all_ids_; ///< Cache for all ids - -private: - DatabaseConfiguration m_db_configuration_; ///< Configuration settings for the database - float m_recognition_threshold_{0.48f}; ///< Threshold value for face recognition - SearchMode m_search_mode_{SEARCH_MODE_EAGER}; ///< Flag to determine if the search should find the most similar feature - - bool m_enable_{false}; ///< Running status - - std::mutex m_res_mtx_; ///< Mutex for thread safety. + static std::mutex mutex_; + static std::shared_ptr instance_; }; } // namespace inspire -#endif // INSPIRE_FEATURE_HUB_DB_H +#endif // INSPIRE_FEATURE_HUB_DB_H \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/inspireface/include/inspireface/frame_process.h b/cpp-package/inspireface/cpp/inspireface/include/inspireface/frame_process.h new file mode 100644 index 0000000..17ed60d --- /dev/null +++ b/cpp-package/inspireface/cpp/inspireface/include/inspireface/frame_process.h @@ -0,0 +1,198 @@ +#ifndef INSPIREFACE_FRAME_PROCESS_H +#define INSPIREFACE_FRAME_PROCESS_H + +#include +#include +#include "data_type.h" + +namespace inspirecv { + +/** + * @brief Enum to represent rotation modes. + */ +enum ROTATION_MODE { ROTATION_0 = 0, ROTATION_90 = 1, ROTATION_180 = 2, ROTATION_270 = 3 }; + +/** + * @brief Enum to represent data formats. + */ +enum DATA_FORMAT { NV21 = 0, NV12 = 1, RGBA = 2, RGB = 3, BGR = 4, BGRA = 5 }; + +/** + * @brief A class to handle camera stream and image processing. + */ +class INSPIRE_API_EXPORT FrameProcess { +public: + /** + * @brief Create a FrameProcess instance. + * + * @param data_buffer Pointer to the data buffer. + * @param height Height of the image. + * @param width Width of the image. + * @param data_format Data format (e.g., NV21, RGBA). + * @param rotation_mode Rotation mode (e.g., ROTATION_0, ROTATION_90). + * @return FrameProcess instance. + */ + static FrameProcess Create(const uint8_t* data_buffer, int height, int width, DATA_FORMAT data_format = BGR, + ROTATION_MODE rotation_mode = ROTATION_0); + + /** + * @brief Create a FrameProcess instance from an inspirecv::Image. + * + * @param image The image to process. + * @param data_format Data format (e.g., NV21, RGBA). + * @param rotation_mode Rotation mode (e.g., ROTATION_0, ROTATION_90). + * @return FrameProcess instance. + */ + static FrameProcess Create(const inspirecv::Image& image, DATA_FORMAT data_format = BGR, ROTATION_MODE rotation_mode = ROTATION_0); + + /** + * @brief Default constructor. + */ + FrameProcess(); + + /** + * @brief Destructor. + */ + ~FrameProcess(); + + /** + * @brief Copy constructor. + */ + FrameProcess(const FrameProcess& other); + + /** + * @brief Move constructor. + */ + FrameProcess(FrameProcess&& other) noexcept; + + /** + * @brief Copy assignment operator. + */ + FrameProcess& operator=(const FrameProcess& other); + + /** + * @brief Move assignment operator. + */ + FrameProcess& operator=(FrameProcess&& other) noexcept; + + /** + * @brief Set the data buffer, height, and width of the camera stream. + * + * @param data_buffer Pointer to the data buffer. + * @param height Height of the image. + * @param width Width of the image. + */ + void SetDataBuffer(const uint8_t* data_buffer, int height, int width); + + /** + * @brief Set the preview size. + * + * @param size Preview size. + */ + void SetPreviewSize(const int size); + + /** + * @brief Set the preview scale. + * + * @param scale Preview scale. + */ + void SetPreviewScale(const float scale); + + /** + * @brief Set the rotation mode. + * + * @param mode Rotation mode (e.g., ROTATION_0, ROTATION_90). + */ + void SetRotationMode(ROTATION_MODE mode); + + /** + * @brief Set the data format. + * + * @param data_format Data format (e.g., NV21, RGBA). + */ + void SetDataFormat(DATA_FORMAT data_format); + + /** + * @brief Set the destination format. + * + * @param data_format Data format (e.g., NV21, RGBA). + */ + void SetDestFormat(DATA_FORMAT data_format); + + /** + * @brief Get an affine-transformed image. + * + * @param affine_matrix Affine transformation matrix. + * @param width_out Width of the output image. + * @param height_out Height of the output image. + * @return inspirecv::Image Affine-transformed image. + */ + inspirecv::Image ExecuteImageAffineProcessing(inspirecv::TransformMatrix& affine_matrix, const int width_out, const int height_out) const; + + /** + * @brief Get a preview image with optional rotation. + * + * @param with_rotation True if rotation is applied, false otherwise. + * @return inspirecv::Image Preview image. + */ + inspirecv::Image ExecutePreviewImageProcessing(bool with_rotation); + + /** + * @brief Get the preview scale. + * + * @return float Preview scale. + */ + float GetPreviewScale(); + + /** + * @brief Execute image scale processing. + * + * @param scale Scale factor. + * @param with_rotation True if rotation is applied, false otherwise. + * @return inspirecv::Image Scaled image. + */ + inspirecv::Image ExecuteImageScaleProcessing(const float scale, bool with_rotation); + + /** + * @brief Get the affine transformation matrix. + * + * @return inspirecv::TransformMatrix Affine transformation matrix. + */ + inspirecv::TransformMatrix GetAffineMatrix() const; + + /** + * @brief Get the rotation mode affine transformation matrix, scale coefficient is not included. + * + * @return inspirecv::TransformMatrix Rotation mode affine transformation matrix. + */ + inspirecv::TransformMatrix GetRotationModeAffineMatrix() const; + + /** + * @brief Get the height of the camera stream image. + * + * @return int Height. + */ + int GetHeight() const; + + /** + * @brief Get the width of the camera stream image. + * + * @return int Width. + */ + int GetWidth() const; + + /** + * @brief Get the current rotation mode. + * + * @return ROTATION_MODE Current rotation mode. + */ + ROTATION_MODE getRotationMode() const; + +private: + class Impl; + std::unique_ptr pImpl; +}; + +} // namespace inspirecv + +#endif // INSPIREFACE_FRAME_PROCESS_H \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/inspireface/herror.h b/cpp-package/inspireface/cpp/inspireface/include/inspireface/herror.h similarity index 100% rename from cpp-package/inspireface/cpp/inspireface/herror.h rename to cpp-package/inspireface/cpp/inspireface/include/inspireface/herror.h diff --git a/cpp-package/inspireface/cpp/inspireface/include/inspireface/inspireface.hpp b/cpp-package/inspireface/cpp/inspireface/include/inspireface/inspireface.hpp new file mode 100644 index 0000000..482607e --- /dev/null +++ b/cpp-package/inspireface/cpp/inspireface/include/inspireface/inspireface.hpp @@ -0,0 +1,14 @@ +#include "session.h" +#include "cuda_toolkit.h" +#include "data_type.h" +#include "log.h" +#include "herror.h" +#include "feature_hub_db.h" +#include "frame_process.h" +#include "isf_check.h" +#include "launch.h" +#include "log.h" +#include "similarity_converter.h" +#include "spend_timer.h" +#include "information.h" +#include "face_warpper.h" \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/inspireface/include/inspireface/isf_check.h b/cpp-package/inspireface/cpp/inspireface/include/inspireface/isf_check.h new file mode 100644 index 0000000..44692d1 --- /dev/null +++ b/cpp-package/inspireface/cpp/inspireface/include/inspireface/isf_check.h @@ -0,0 +1,33 @@ +#ifndef INSPIRE_FACE_CHECK_H +#define INSPIRE_FACE_CHECK_H +#include "log.h" +#include "herror.h" + +#define INSPIREFACE_RETURN_IF_ERROR(...) \ + do { \ + const int32_t _status = (__VA_ARGS__); \ + if (_status != HSUCCEED) { \ + INSPIRE_LOGE("Error code: %d", _status); \ + return _status; \ + } \ + } while (0) + +#define INSPIREFACE_LOG_IF(severity, condition) \ + if (condition) \ + INSPIRE_LOG##severity + +#define INSPIREFACE_CHECK(condition) \ + do { \ + if (!(condition)) { \ + INSPIRE_LOGF("Check failed: (%s)", #condition); \ + } \ + } while (0) + +#define INSPIREFACE_CHECK_MSG(condition, message) \ + do { \ + if (!(condition)) { \ + INSPIRE_LOGF("Check failed: (%s) %s", #condition, message); \ + } \ + } while (0) + +#endif // INSPIRE_FACE_CHECK_H diff --git a/cpp-package/inspireface/cpp/inspireface/Initialization_module/launch.h b/cpp-package/inspireface/cpp/inspireface/include/inspireface/launch.h similarity index 54% rename from cpp-package/inspireface/cpp/inspireface/Initialization_module/launch.h rename to cpp-package/inspireface/cpp/inspireface/include/inspireface/launch.h index 7e593dc..f546b6a 100644 --- a/cpp-package/inspireface/cpp/inspireface/Initialization_module/launch.h +++ b/cpp-package/inspireface/cpp/inspireface/include/inspireface/launch.h @@ -5,28 +5,42 @@ #pragma once #ifndef INSPIREFACE_LAUNCH_H #define INSPIREFACE_LAUNCH_H -#include "middleware/model_archive/inspire_archive.h" -#if defined(ISF_ENABLE_RGA) -#include "middleware/nexus_processor/rga/dma_alloc.h" -#endif -#include -#include "middleware/inference_wrapper/inference_wrapper.h" -#include "middleware/system.h" -#ifndef INSPIRE_API -#define INSPIRE_API -#endif +#include +#include +#include +#include "data_type.h" -#define INSPIRE_LAUNCH inspire::Launch::GetInstance() +#define INSPIREFACE_CONTEXT inspire::Launch::GetInstance() namespace inspire { +// Forward declarations +class InspireArchive; + // The Launch class acts as the main entry point for the InspireFace system. // It is responsible for loading static resources such as models, configurations, and parameters. -class INSPIRE_API Launch { +class INSPIRE_API_EXPORT Launch { public: + // Special Backend enum for CoreML + enum NNInferenceBackend { + NN_INFERENCE_CPU = 0, + NN_INFERENCE_MMM_CUDA, + NN_INFERENCE_COREML_CPU, + NN_INFERENCE_COREML_GPU, + NN_INFERENCE_COREML_ANE, + NN_INFERENCE_TENSORRT_CUDA, + }; + + enum LandmarkEngine { + LANDMARK_HYPLMV2_0_25 = 0, + LANDMARK_HYPLMV2_0_50, + LANDMARK_INSIGHTFACE_2D106_TRACK, + }; + Launch(const Launch&) = delete; // Delete the copy constructor to prevent copying. Launch& operator=(const Launch&) = delete; // Delete the assignment operator to prevent assignment. + ~Launch(); // Destructor needs to be defined where the implementation is complete // Retrieves the singleton instance of Launch, ensuring that only one instance exists. static std::shared_ptr GetInstance(); @@ -61,10 +75,10 @@ public: std::string GetExtensionPath() const; // Set the global coreml inference mode - void SetGlobalCoreMLInferenceMode(InferenceWrapper::SpecialBackend mode); + void SetGlobalCoreMLInferenceMode(NNInferenceBackend mode); // Get the global coreml inference mode - InferenceWrapper::SpecialBackend GetGlobalCoreMLInferenceMode() const; + NNInferenceBackend GetGlobalCoreMLInferenceMode() const; // Build the extension path void BuildAppleExtensionPath(const std::string& resource_path); @@ -75,35 +89,30 @@ public: // Get the cuda device id int32_t GetCudaDeviceId() const; + // Set the face detect pixel list + void SetFaceDetectPixelList(const std::vector& pixel_list); + + // Get the face detect pixel list + std::vector GetFaceDetectPixelList() const; + + // Set the face detect model list + void SetFaceDetectModelList(const std::vector& model_list); + + // Get the face detect model list + std::vector GetFaceDetectModelList() const; + + // Switch the landmark engine + void SwitchLandmarkEngine(LandmarkEngine engine); + private: - // Parameters - std::string m_rockchip_dma_heap_path_; + // Private constructor for the singleton pattern + Launch(); - // Constructor - Launch() : m_load_(false), m_archive_(nullptr) { -#if defined(ISF_ENABLE_RGA) -#if defined(ISF_RKNPU_RV1106) - m_rockchip_dma_heap_path_ = RV1106_CMA_HEAP_PATH; -#else - m_rockchip_dma_heap_path_ = DMA_HEAP_DMA32_UNCACHE_PATCH; -#endif - INSPIRE_LOGW("Rockchip dma heap configured path: %s", m_rockchip_dma_heap_path_.c_str()); -#endif - } ///< Private constructor for the singleton pattern. - - static std::mutex mutex_; ///< Mutex for synchronizing access to the singleton instance. - static std::shared_ptr instance_; ///< The singleton instance of Launch. - - std::string m_extension_path_; - - std::unique_ptr m_archive_; ///< The archive containing all necessary resources. - bool m_load_; ///< Flag indicating whether the resources have been successfully loaded. - - int32_t m_cuda_device_id_{0}; - - InferenceWrapper::SpecialBackend m_global_coreml_inference_mode_{InferenceWrapper::COREML_ANE}; ///< The global coreml inference mode + // Private implementation class + class Impl; + std::unique_ptr pImpl; }; } // namespace inspire -#endif // INSPIREFACE_LAUNCH_H +#endif // INSPIREFACE_LAUNCH_H \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/inspireface/include/inspireface/log.h b/cpp-package/inspireface/cpp/inspireface/include/inspireface/log.h new file mode 100755 index 0000000..1dcd445 --- /dev/null +++ b/cpp-package/inspireface/cpp/inspireface/include/inspireface/log.h @@ -0,0 +1,89 @@ +#ifndef INSPIRE_FACE_LOG_H +#define INSPIRE_FACE_LOG_H + +#include +#include +#include +#include "data_type.h" + +// Macro to extract the filename from the full path +#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) + +#ifdef ANDROID +// Android platform log macros +#define INSPIRE_ANDROID_LOG_TAG "InspireFace" +#define INSPIRE_LOGD(...) inspire::LogManager::getInstance()->logAndroid(inspire::LogLevel::ISF_LOG_DEBUG, INSPIRE_ANDROID_LOG_TAG, __VA_ARGS__) +#define INSPIRE_LOGI(...) inspire::LogManager::getInstance()->logAndroid(inspire::LogLevel::ISF_LOG_INFO, INSPIRE_ANDROID_LOG_TAG, __VA_ARGS__) +#define INSPIRE_LOGW(...) inspire::LogManager::getInstance()->logAndroid(inspire::LogLevel::ISF_LOG_WARN, INSPIRE_ANDROID_LOG_TAG, __VA_ARGS__) +#define INSPIRE_LOGE(...) inspire::LogManager::getInstance()->logAndroid(inspire::LogLevel::ISF_LOG_ERROR, INSPIRE_ANDROID_LOG_TAG, __VA_ARGS__) +#define INSPIRE_LOGF(...) inspire::LogManager::getInstance()->logAndroid(inspire::LogLevel::ISF_LOG_FATAL, INSPIRE_ANDROID_LOG_TAG, __VA_ARGS__) +#else +// Standard platform log macros +#define INSPIRE_LOGD(...) \ + inspire::LogManager::getInstance()->logStandard(inspire::LogLevel::ISF_LOG_DEBUG, __FILENAME__, __FUNCTION__, __LINE__, __VA_ARGS__) +#define INSPIRE_LOGI(...) inspire::LogManager::getInstance()->logStandard(inspire::LogLevel::ISF_LOG_INFO, "", "", -1, __VA_ARGS__) +#define INSPIRE_LOGW(...) inspire::LogManager::getInstance()->logStandard(inspire::LogLevel::ISF_LOG_WARN, "", "", -1, __VA_ARGS__) +#define INSPIRE_LOGE(...) inspire::LogManager::getInstance()->logStandard(inspire::LogLevel::ISF_LOG_ERROR, "", "", -1, __VA_ARGS__) +#define INSPIRE_LOGF(...) inspire::LogManager::getInstance()->logStandard(inspire::LogLevel::ISF_LOG_FATAL, "", "", -1, __VA_ARGS__) +#endif + +// Macro to set the global log level +#define INSPIRE_SET_LOG_LEVEL(level) inspire::LogManager::getInstance()->setLogLevel(level) + +namespace inspire { + +// Log levels +enum LogLevel { ISF_LOG_NONE = 0, ISF_LOG_DEBUG, ISF_LOG_INFO, ISF_LOG_WARN, ISF_LOG_ERROR, ISF_LOG_FATAL }; + +/** + * @class LogManager + * @brief A singleton class for logging messages to the console or Android logcat. + * + * This class provides methods to log messages of different severity levels (DEBUG, INFO, WARN, ERROR, FATAL) + * to the console or Android logcat based on the current log level setting. + * + * Implementation details are hidden using the PIMPL (Pointer to Implementation) pattern. + */ +class INSPIRE_API_EXPORT LogManager { +public: + // Get the singleton instance + static LogManager* getInstance(); + + // Destructor + ~LogManager(); + + // Set the log level + void setLogLevel(LogLevel level); + + // Get the current log level + LogLevel getLogLevel() const; + +#ifdef ANDROID + // Method for logging on the Android platform + void logAndroid(LogLevel level, const char* tag, const char* format, ...) const; +#else + // Method for standard platform logging + void logStandard(LogLevel level, const char* filename, const char* function, int line, const char* format, ...) const; +#endif + +private: + // Private constructor for singleton pattern + LogManager(); + + // Disable copy construction and assignment + LogManager(const LogManager&) = delete; + LogManager& operator=(const LogManager&) = delete; + + // Forward declaration of the implementation class + class Impl; + + // Pointer to implementation + std::unique_ptr pImpl; + + // Static instance for singleton pattern + static LogManager* instance; +}; + +} // namespace inspire + +#endif // INSPIRE_FACE_LOG_H \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/inspireface/include/inspireface/session.h b/cpp-package/inspireface/cpp/inspireface/include/inspireface/session.h new file mode 100644 index 0000000..6a977f1 --- /dev/null +++ b/cpp-package/inspireface/cpp/inspireface/include/inspireface/session.h @@ -0,0 +1,199 @@ +#ifndef INSPIRE_FACE_SESSION_H +#define INSPIRE_FACE_SESSION_H +#include +#include "data_type.h" +#include "frame_process.h" +#include "face_warpper.h" + +namespace inspire { + +/** + * @brief The face algorithm session class. + */ +class INSPIRE_API_EXPORT Session { +public: + Session(); + ~Session(); + + Session(Session&&) noexcept; + Session& operator=(Session&&) noexcept; + + Session(const Session&) = delete; + Session& operator=(const Session&) = delete; + + /** + * @brief Create a new session with the given parameters. + * @param detect_mode The mode of face detection. + * @param max_detect_face The maximum number of faces to detect. + * @param param The custom pipeline parameter. + * @param detect_level_px The detection level in pixels. + * @param track_by_detect_mode_fps The tracking frame rate. + * @return A new session. + */ + static Session Create(DetectModuleMode detect_mode, int32_t max_detect_face, const CustomPipelineParameter& param, int32_t detect_level_px = -1, + int32_t track_by_detect_mode_fps = -1); + + /** + * @brief Create a new session pointer with the given parameters. + * @param detect_mode The mode of face detection. + * @param max_detect_face The maximum number of faces to detect. + * @param param The custom pipeline parameter. + * @param detect_level_px The detection level in pixels. + * @param track_by_detect_mode_fps The tracking frame rate. + * @return A raw pointer to new session. The caller is responsible for memory management. + */ + static Session* CreatePtr(DetectModuleMode detect_mode, int32_t max_detect_face, const CustomPipelineParameter& param, + int32_t detect_level_px = -1, int32_t track_by_detect_mode_fps = -1) { + return new Session(Create(detect_mode, max_detect_face, param, detect_level_px, track_by_detect_mode_fps)); + } + + /** + * @brief Set the track preview size. + * @param preview_size The preview size. + */ + void SetTrackPreviewSize(int32_t preview_size); + + /** + * @brief Set the minimum face pixel size. + * @param min_face_pixel_size The minimum face pixel size. + */ + void SetFilterMinimumFacePixelSize(int32_t min_face_pixel_size); + + /** + * @brief Set the face detect threshold. + * @param threshold The face detect threshold. + */ + void SetFaceDetectThreshold(float threshold); + + /** + * @brief Set the track mode smooth ratio. + * @param smooth_ratio The track mode smooth ratio. + */ + void SetTrackModeSmoothRatio(int32_t smooth_ratio); + + /** + * @brief Set the track mode num smooth cache frame. + * @param num_smooth_cache_frame The track mode num smooth cache frame. + */ + void SetTrackModeNumSmoothCacheFrame(int32_t num_smooth_cache_frame); + + /** + * @brief Set the track mode detect interval. + * @param detect_interval The track mode detect interval. + */ + void SetTrackModeDetectInterval(int32_t detect_interval); + + /** + * @brief Detect and track the faces in the frame. + * @param process The frame process. + * @param results The detected faces. + */ + int32_t FaceDetectAndTrack(inspirecv::FrameProcess& process, std::vector& results); + + /** + * @brief Get the face bounding box. + * @param face_data The face data. + * @return The face bounding box. + */ + inspirecv::Rect2i GetFaceBoundingBox(const FaceTrackWrap& face_data); + + /** + * @brief Get the face dense landmark. + * @param face_data The face data. + * @return The face dense landmark. + */ + std::vector GetFaceDenseLandmark(const FaceTrackWrap& face_data); + + /** + * @brief Get the face five key points. + * @param face_data The face data. + * @return The face five key points. + */ + std::vector GetFaceFiveKeyPoints(const FaceTrackWrap& face_data); + + /** + * @brief Extract the face feature. + * @param process The frame process. + * @param data The face data. + * @param embedding The face embedding. + * @param normalize The normalize flag. + */ + int32_t FaceFeatureExtract(inspirecv::FrameProcess& process, FaceTrackWrap& data, FaceEmbedding& embedding, bool normalize = true); + + /** + * @brief Get the face alignment image. + * @param process The frame process. + * @param data The face data. + * @param wrapped The wrapped image. + */ + void GetFaceAlignmentImage(inspirecv::FrameProcess& process, FaceTrackWrap& data, inspirecv::Image& wrapped); + + /** + * @brief Extract the face feature with alignment image. + * @param process The frame process. + * @param embedding The face embedding. + * @param normalize The normalize flag. + */ + int32_t FaceFeatureExtractWithAlignmentImage(inspirecv::FrameProcess& process, FaceEmbedding& embedding, bool normalize = true); + + /** + * @brief Extract the face feature with alignment image. + * @param wrapped The wrapped image. + * @param embedding The face embedding. + * @param normalize The normalize flag. + */ + int32_t FaceFeatureExtractWithAlignmentImage(const inspirecv::Image& wrapped, FaceEmbedding& embedding, bool normalize = true); + + /** + * @brief Multiple face pipeline process. + * @param process The frame process. + * @param param The custom pipeline parameter. + * @param face_data_list The face data list. + */ + int32_t MultipleFacePipelineProcess(inspirecv::FrameProcess& process, const CustomPipelineParameter& param, + const std::vector& face_data_list); + + /** + * @brief Get the RGB liveness confidence. + * @return The RGB liveness confidence. + */ + std::vector GetRGBLivenessConfidence(); + + /** + * @brief Get the face mask confidence. + * @return The face mask confidence. + */ + std::vector GetFaceMaskConfidence(); + + /** + * @brief Get the face quality confidence. + * @return The face quality confidence. + */ + std::vector GetFaceQualityConfidence(); + + /** + * @brief Get the face interaction state. + * @return The face interaction state. + */ + std::vector GetFaceInteractionState(); + + /** + * @brief Get the face interaction action. + * @return The face interaction action. + */ + std::vector GetFaceInteractionAction(); + + /** + * @brief Get the face attribute result. + * @return The face attribute result. + */ + std::vector GetFaceAttributeResult(); + +private: + class Impl; + std::unique_ptr pImpl; +}; + +} // namespace inspire + +#endif // INSPIRE_FACE_SESSION_H \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/inspireface/recognition_module/similarity_converter.h b/cpp-package/inspireface/cpp/inspireface/include/inspireface/similarity_converter.h similarity index 98% rename from cpp-package/inspireface/cpp/inspireface/recognition_module/similarity_converter.h rename to cpp-package/inspireface/cpp/inspireface/include/inspireface/similarity_converter.h index 7f9c853..f6dbae9 100644 --- a/cpp-package/inspireface/cpp/inspireface/recognition_module/similarity_converter.h +++ b/cpp-package/inspireface/cpp/inspireface/include/inspireface/similarity_converter.h @@ -4,6 +4,7 @@ #include #include #include +#include "data_type.h" #define SIMILARITY_CONVERTER_UPDATE_CONFIG(config) inspire::SimilarityConverter::getInstance().updateConfig(config) #define SIMILARITY_CONVERTER_RUN(cosine) inspire::SimilarityConverter::getInstance().convert(cosine) @@ -22,7 +23,7 @@ struct SimilarityConverterConfig { double outputMax = 1.0; // Maximum value of output range }; -class SimilarityConverter { +class INSPIRE_API_EXPORT SimilarityConverter { private: SimilarityConverterConfig config; double outputScale; // Scale of output range diff --git a/cpp-package/inspireface/cpp/inspireface/include/inspireface/spend_timer.h b/cpp-package/inspireface/cpp/inspireface/include/inspireface/spend_timer.h new file mode 100644 index 0000000..cdc2746 --- /dev/null +++ b/cpp-package/inspireface/cpp/inspireface/include/inspireface/spend_timer.h @@ -0,0 +1,51 @@ +#ifndef INSPIRE_FACE_TIMER_H +#define INSPIRE_FACE_TIMER_H + +#include "data_type.h" + +namespace inspire { + +// Get the current time in microseconds. +uint64_t INSPIRE_API_EXPORT _now(); + +/** + * @brief A class to measure the cost of a block of code. + */ +class INSPIRE_API_EXPORT SpendTimer { +public: + SpendTimer(); + explicit SpendTimer(const std::string &name); + + void Start(); + void Stop(); + void Reset(); + + uint64_t Get() const; + uint64_t Average() const; + uint64_t Total() const; + uint64_t Count() const; + uint64_t Min() const; + uint64_t Max() const; + const std::string &name() const; + std::string Report() const; + + static void Disable(); + +protected: + uint64_t start_; + uint64_t stop_; + uint64_t total_; + uint64_t count_; + uint64_t min_; + uint64_t max_; + std::string name_; + + static int is_enable; +}; + +INSPIRE_API_EXPORT std::ostream &operator<<(std::ostream &os, const SpendTimer &timer); + +#define TIME_NOW inspirecv::_now() + +} // namespace inspire +#endif // INSPIRE_FACE_TIMER_H diff --git a/cpp-package/inspireface/cpp/inspireface/information.h b/cpp-package/inspireface/cpp/inspireface/information.h deleted file mode 100644 index 6815ff0..0000000 --- a/cpp-package/inspireface/cpp/inspireface/information.h +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Created by Jingyu Yan - * @date 2024-10-01 - */ - -#ifndef INSPIRE_FACE_INFORMATION_H -#define INSPIRE_FACE_INFORMATION_H - -#define INSPIRE_FACE_VERSION_MAJOR_STR "1" -#define INSPIRE_FACE_VERSION_MINOR_STR "2" -#define INSPIRE_FACE_VERSION_PATCH_STR "0" - -#define INSPIRE_FACE_EXTENDED_INFORMATION "InspireFace[Community Edition]@General - Build Time: 2025-03-25" - -#endif // INSPIRE_FACE_INFORMATION_H diff --git a/cpp-package/inspireface/cpp/inspireface/isf_check.h b/cpp-package/inspireface/cpp/inspireface/isf_check.h deleted file mode 100644 index f72633f..0000000 --- a/cpp-package/inspireface/cpp/inspireface/isf_check.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef INSPIRE_FACE_CHECK_H -#define INSPIRE_FACE_CHECK_H -#include "log.h" -#include "herror.h" - -#define INSPIREFACE_RETURN_IF_ERROR(...) \ - do { \ - const int32_t _status = (__VA_ARGS__); \ - if (_status != HSUCCEED) { \ - INSPIRE_LOGE("Error code: %d", _status); \ - return _status; \ - } \ - } while (0) - - -#define INSPIREFACE_LOG_IF(severity, condition) \ - if (condition) \ - INSPIRE_LOG##severity - - -#define INSPIREFACE_CHECK(condition) \ - do { \ - if (!(condition)) { \ - INSPIRE_LOGF("Check failed: (%s)", #condition); \ - } \ - } while (0) - -#define INSPIREFACE_CHECK_MSG(condition, message) \ - do { \ - if (!(condition)) { \ - INSPIRE_LOGF("Check failed: (%s) %s", #condition, message); \ - } \ - } while (0) - -#define INSPIREFACE_CHECK_EQ(a, b) INSPIREFACE_CHECK((a) == (b)) << "Expected equality of these values: " << #a << " vs " << #b -#define INSPIREFACE_CHECK_NE(a, b) INSPIREFACE_CHECK((a) != (b)) << "Expected inequality of these values: " << #a << " vs " << #b -#define INSPIREFACE_CHECK_LE(a, b) INSPIREFACE_CHECK((a) <= (b)) << "Expected " << #a << " <= " << #b -#define INSPIREFAFECE_CHECK_LT(a, b) INSPIREFACE_CHECK((a) < (b)) << "Expected " << #a << " < " << #b -#define INSPIREFAFECE_CHECK_GE(a, b) INSPIREFACE_CHECK((a) >= (b)) << "Expected " << #a << " >= " << #b -#define INSPIREFAFECE_CHECK_GT(a, b) INSPIREFACE_CHECK((a) > (b)) << "Expected " << #a << " > " << #b - -#endif // INSPIRE_FACE_CHECK_H diff --git a/cpp-package/inspireface/cpp/inspireface/log.cpp b/cpp-package/inspireface/cpp/inspireface/log.cpp index d7c9f76..4dd0af1 100644 --- a/cpp-package/inspireface/cpp/inspireface/log.cpp +++ b/cpp-package/inspireface/cpp/inspireface/log.cpp @@ -3,11 +3,150 @@ * @date 2024-10-01 */ #include "log.h" +#include +#include +#include +#include + +#ifdef ANDROID +#include +#endif namespace inspire { -// Static Logger initialization +// Implementation class for LogManager +class LogManager::Impl { +public: + Impl() : currentLevel(LogLevel::ISF_LOG_INFO) {} + + LogLevel currentLevel; + static std::mutex mutex; +}; + +// Static initialization +std::mutex LogManager::Impl::mutex; LogManager* LogManager::instance = nullptr; -std::mutex LogManager::mutex; + +// Constructor +LogManager::LogManager() : pImpl(std::make_unique()) {} + +// Destructor +LogManager::~LogManager() = default; + +// Get singleton instance +LogManager* LogManager::getInstance() { + std::lock_guard lock(Impl::mutex); + if (instance == nullptr) { + instance = new LogManager(); + } + return instance; +} + +// Set log level +void LogManager::setLogLevel(LogLevel level) { + pImpl->currentLevel = level; +} + +// Get log level +LogLevel LogManager::getLogLevel() const { + return pImpl->currentLevel; +} + +#ifdef ANDROID +// Android logging implementation +void LogManager::logAndroid(LogLevel level, const char* tag, const char* format, ...) const { + if (pImpl->currentLevel == LogLevel::ISF_LOG_NONE || level < pImpl->currentLevel) + return; + + int androidLevel; + switch (level) { + case LogLevel::ISF_LOG_DEBUG: + androidLevel = ANDROID_LOG_DEBUG; + break; + case LogLevel::ISF_LOG_INFO: + androidLevel = ANDROID_LOG_INFO; + break; + case LogLevel::ISF_LOG_WARN: + androidLevel = ANDROID_LOG_WARN; + break; + case LogLevel::ISF_LOG_ERROR: + androidLevel = ANDROID_LOG_ERROR; + break; + case LogLevel::ISF_LOG_FATAL: + androidLevel = ANDROID_LOG_FATAL; + break; + default: + androidLevel = ANDROID_LOG_DEFAULT; + } + + va_list args; + va_start(args, format); + __android_log_vprint(androidLevel, tag, format, args); + va_end(args); + + // If the log level is fatal, flush the error stream and abort the program + if (level == LogLevel::ISF_LOG_FATAL) { + std::flush(std::cerr); + abort(); + } +} +#else +// Standard logging implementation +void LogManager::logStandard(LogLevel level, const char* filename, const char* function, int line, const char* format, ...) const { + // Check whether the current level is LOG NONE or the log level is not enough to log + if (pImpl->currentLevel == LogLevel::ISF_LOG_NONE || level < pImpl->currentLevel) + return; + + // Build log prefix dynamically based on available data + bool hasPrintedPrefix = false; + if (filename && strlen(filename) > 0) { + printf("[%s]", filename); + hasPrintedPrefix = true; + } + if (function && strlen(function) > 0) { + printf("[%s]", function); + hasPrintedPrefix = true; + } + if (line != -1) { + printf("[%d]", line); + hasPrintedPrefix = true; + } + + // Only add colon and space if any prefix was printed + if (hasPrintedPrefix) { + printf(": "); + } + + // Set text color for different log levels, but only if not on iOS +#ifndef TARGET_OS_IOS + if (level == LogLevel::ISF_LOG_ERROR || level == LogLevel::ISF_LOG_FATAL) { + printf("\033[1;31m"); // Red color for errors and fatal issues + } else if (level == LogLevel::ISF_LOG_WARN) { + printf("\033[1;33m"); // Yellow color for warnings + } +#endif + + // Print the actual log message + va_list args; + va_start(args, format); + vprintf(format, args); + va_end(args); + + // Reset text color if needed, but only if not on iOS +#ifndef TARGET_OS_IOS + if (level == LogLevel::ISF_LOG_ERROR || level == LogLevel::ISF_LOG_WARN || level == LogLevel::ISF_LOG_FATAL) { + printf("\033[0m"); // Reset color + } +#endif + + printf("\n"); // New line after log message + + // If the log level is fatal, flush the error stream and abort the program + if (level == LogLevel::ISF_LOG_FATAL) { + std::flush(std::cerr); + abort(); + } +} +#endif } // namespace inspire \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/inspireface/log.h b/cpp-package/inspireface/cpp/inspireface/log.h deleted file mode 100755 index 563e3c9..0000000 --- a/cpp-package/inspireface/cpp/inspireface/log.h +++ /dev/null @@ -1,184 +0,0 @@ -#ifndef INSPIRE_FACE_LOG_H -#define INSPIRE_FACE_LOG_H - -#include -#include -#include -#include -#include - -#ifndef INSPIRE_API -#define INSPIRE_API -#endif - -// Macro to extract the filename from the full path -#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) - -#ifdef ANDROID -// Android platform log macros -#include -#define INSPIRE_ANDROID_LOG_TAG "InspireFace" -#define INSPIRE_LOGD(...) inspire::LogManager::getInstance()->logAndroid(inspire::ISF_LOG_DEBUG, INSPIRE_ANDROID_LOG_TAG, __VA_ARGS__) -#define INSPIRE_LOGI(...) inspire::LogManager::getInstance()->logAndroid(inspire::ISF_LOG_INFO, INSPIRE_ANDROID_LOG_TAG, __VA_ARGS__) -#define INSPIRE_LOGW(...) inspire::LogManager::getInstance()->logAndroid(inspire::ISF_LOG_WARN, INSPIRE_ANDROID_LOG_TAG, __VA_ARGS__) -#define INSPIRE_LOGE(...) inspire::LogManager::getInstance()->logAndroid(inspire::ISF_LOG_ERROR, INSPIRE_ANDROID_LOG_TAG, __VA_ARGS__) -#define INSPIRE_LOGF(...) inspire::LogManager::getInstance()->logAndroid(inspire::ISF_LOG_FATAL, INSPIRE_ANDROID_LOG_TAG, __VA_ARGS__) -#else -// Standard platform log macros -#define INSPIRE_LOGD(...) inspire::LogManager::getInstance()->logStandard(inspire::ISF_LOG_DEBUG, __FILENAME__, __FUNCTION__, __LINE__, __VA_ARGS__) -#define INSPIRE_LOGI(...) inspire::LogManager::getInstance()->logStandard(inspire::ISF_LOG_INFO, "", "", -1, __VA_ARGS__) -#define INSPIRE_LOGW(...) inspire::LogManager::getInstance()->logStandard(inspire::ISF_LOG_WARN, "", "", -1, __VA_ARGS__) -#define INSPIRE_LOGE(...) inspire::LogManager::getInstance()->logStandard(inspire::ISF_LOG_ERROR, "", "", -1, __VA_ARGS__) -#define INSPIRE_LOGF(...) inspire::LogManager::getInstance()->logStandard(inspire::ISF_LOG_FATAL, "", "", -1, __VA_ARGS__) -#endif - -// Macro to set the global log level -#define INSPIRE_SET_LOG_LEVEL(level) inspire::LogManager::getInstance()->setLogLevel(level) - -namespace inspire { - -// Log levels -enum LogLevel { ISF_LOG_NONE = 0, ISF_LOG_DEBUG, ISF_LOG_INFO, ISF_LOG_WARN, ISF_LOG_ERROR, ISF_LOG_FATAL }; - -/** - * @class LogManager - * @brief A singleton class for logging messages to the console or Android logcat. - * - * This class provides methods to log messages of different severity levels (DEBUG, INFO, WARN, ERROR, FATAL) - * to the console or Android logcat based on the current log level setting. - */ -class INSPIRE_API LogManager { -private: - LogLevel currentLevel; - static LogManager* instance; - static std::mutex mutex; - - // Private constructor - LogManager() : currentLevel(ISF_LOG_INFO) {} // Default log level is INFO - -public: - // Disable copy construction and assignment - LogManager(const LogManager&) = delete; - LogManager& operator=(const LogManager&) = delete; - - // Get the singleton instance - static LogManager* getInstance() { - std::lock_guard lock(mutex); - if (instance == nullptr) { - instance = new LogManager(); - } - return instance; - } - - // Set the log level - void setLogLevel(LogLevel level) { - currentLevel = level; - } - - // Get the current log level - LogLevel getLogLevel() const { - return currentLevel; - } - -#ifdef ANDROID - // Method for logging on the Android platform - void logAndroid(LogLevel level, const char* tag, const char* format, ...) const { - if (currentLevel == ISF_LOG_NONE || level < currentLevel) - return; - - int androidLevel; - switch (level) { - case ISF_LOG_DEBUG: - androidLevel = ANDROID_LOG_DEBUG; - break; - case ISF_LOG_INFO: - androidLevel = ANDROID_LOG_INFO; - break; - case ISF_LOG_WARN: - androidLevel = ANDROID_LOG_WARN; - break; - case ISF_LOG_ERROR: - androidLevel = ANDROID_LOG_ERROR; - break; - case ISF_LOG_FATAL: - androidLevel = ANDROID_LOG_FATAL; - break; - default: - androidLevel = ANDROID_LOG_DEFAULT; - } - - va_list args; - va_start(args, format); - __android_log_vprint(androidLevel, tag, format, args); - va_end(args); - - // If the log level is fatal, flush the error stream and abort the program - if (level == ISF_LOG_FATAL) { - std::flush(std::cerr); - abort(); - } - } -#else - // Method for standard platform logging - void logStandard(LogLevel level, const char* filename, const char* function, int line, const char* format, ...) const { - // Check whether the current level is LOG NONE or the log level is not enough to log - if (currentLevel == ISF_LOG_NONE || level < currentLevel) - return; - - // Build log prefix dynamically based on available data - bool hasPrintedPrefix = false; - if (filename && strlen(filename) > 0) { - printf("[%s]", filename); - hasPrintedPrefix = true; - } - if (function && strlen(function) > 0) { - printf("[%s]", function); - hasPrintedPrefix = true; - } - if (line != -1) { - printf("[%d]", line); - hasPrintedPrefix = true; - } - - // Only add colon and space if any prefix was printed - if (hasPrintedPrefix) { - printf(": "); - } - - // Set text color for different log levels, but only if not on iOS -#ifndef TARGET_OS_IOS - if (level == ISF_LOG_ERROR || level == ISF_LOG_FATAL) { - printf("\033[1;31m"); // Red color for errors and fatal issues - } else if (level == ISF_LOG_WARN) { - printf("\033[1;33m"); // Yellow color for warnings - } -#endif - - // Print the actual log message - va_list args; - va_start(args, format); - vprintf(format, args); - va_end(args); - - // Reset text color if needed, but only if not on iOS -#ifndef TARGET_OS_IOS - if (level == ISF_LOG_ERROR || level == ISF_LOG_WARN || level == ISF_LOG_FATAL) { - printf("\033[0m"); // Reset color - } -#endif - - printf("\n"); // New line after log message - - // If the log level is fatal, flush the error stream and abort the program - if (level == ISF_LOG_FATAL) { - std::flush(std::cerr); - abort(); - } - } - -#endif -}; - -} // namespace inspire - -#endif // INSPIRE_FACE_LOG_H \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/inspireface/middleware/any_net_adapter.h b/cpp-package/inspireface/cpp/inspireface/middleware/any_net_adapter.h index 43b06e5..8824d51 100644 --- a/cpp-package/inspireface/cpp/inspireface/middleware/any_net_adapter.h +++ b/cpp-package/inspireface/cpp/inspireface/middleware/any_net_adapter.h @@ -13,8 +13,8 @@ #include "configurable.h" #include "log.h" #include "model_archive/inspire_archive.h" -#include "nexus_processor/image_processor.h" -#include "initialization_module/launch.h" +#include "image_process/nexus_processor/image_processor.h" +#include #include "system.h" namespace inspire { @@ -51,7 +51,7 @@ public: * @param type Type of the inference helper (default: INFER_MNN). * @return int32_t Status of the loading and initialization process. */ - int32_t loadData(InspireModel &model, InferenceWrapper::EngineType type = InferenceWrapper::INFER_MNN, bool dynamic = false) { + int32_t LoadData(InspireModel &model, InferenceWrapper::EngineType type = InferenceWrapper::INFER_MNN, bool dynamic = false) { m_infer_type_ = type; // must pushData(model.Config(), "model_index", 0); @@ -78,7 +78,7 @@ public: m_nn_inference_->SetNumThreads(getData("threads")); if (m_infer_type_ == InferenceWrapper::INFER_TENSORRT) { - m_nn_inference_->SetDevice(INSPIRE_LAUNCH->GetCudaDeviceId()); + m_nn_inference_->SetDevice(INSPIREFACE_CONTEXT->GetCudaDeviceId()); } #if defined(ISF_GLOBAL_INFERENCE_BACKEND_USE_MNN_CUDA) && !defined(ISF_ENABLE_RKNN) @@ -87,7 +87,13 @@ public: #endif #if defined(ISF_ENABLE_APPLE_EXTENSION) - m_nn_inference_->SetSpecialBackend(INSPIRE_LAUNCH->GetGlobalCoreMLInferenceMode()); + if (INSPIREFACE_CONTEXT->GetGlobalCoreMLInferenceMode() == InferenceWrapper::COREML_CPU) { + m_nn_inference_->SetSpecialBackend(InferenceWrapper::COREML_CPU); + } else if (INSPIREFACE_CONTEXT->GetGlobalCoreMLInferenceMode() == InferenceWrapper::COREML_GPU) { + m_nn_inference_->SetSpecialBackend(InferenceWrapper::COREML_GPU); + } else if (INSPIREFACE_CONTEXT->GetGlobalCoreMLInferenceMode() == InferenceWrapper::COREML_ANE) { + m_nn_inference_->SetSpecialBackend(InferenceWrapper::COREML_ANE); + } #endif m_output_tensor_info_list_.clear(); @@ -99,7 +105,7 @@ public: } int32_t ret; if (model.loadFilePath) { - auto extensionPath = INSPIRE_LAUNCH->GetExtensionPath(); + auto extensionPath = INSPIREFACE_CONTEXT->GetExtensionPath(); if (extensionPath.empty()) { INSPIRE_LOGE("Extension path is empty"); return InferenceWrapper::WrapperError; diff --git a/cpp-package/inspireface/cpp/inspireface/middleware/cuda_toolkit.h b/cpp-package/inspireface/cpp/inspireface/middleware/cuda_toolkit.cpp similarity index 84% rename from cpp-package/inspireface/cpp/inspireface/middleware/cuda_toolkit.h rename to cpp-package/inspireface/cpp/inspireface/middleware/cuda_toolkit.cpp index 40a7a99..413fe15 100644 --- a/cpp-package/inspireface/cpp/inspireface/middleware/cuda_toolkit.h +++ b/cpp-package/inspireface/cpp/inspireface/middleware/cuda_toolkit.cpp @@ -1,23 +1,29 @@ #ifdef ISF_ENABLE_TENSORRT -#ifndef INSPIRE_CUDA_TOOLKIT_H -#define INSPIRE_CUDA_TOOLKIT_H +#include #include #include +#endif // ISF_ENABLE_TENSORRT #include -#include "herror.h" +#include namespace inspire { -inline static int32_t GetCudaDeviceCount(int32_t *device_count) { +int32_t INSPIRE_API_EXPORT GetCudaDeviceCount(int32_t *device_count) { +#ifdef ISF_ENABLE_TENSORRT cudaError_t error = cudaGetDeviceCount(device_count); if (error != cudaSuccess) { INSPIRE_LOGE("CUDA error: %s", cudaGetErrorString(error)); return HERR_DEVICE_CUDA_UNKNOWN_ERROR; } return HSUCCEED; +#else + *device_count = 0; + return HERR_DEVICE_CUDA_NOT_SUPPORT; +#endif } -inline static int32_t CheckCudaUsability(int32_t *is_support) { +int32_t INSPIRE_API_EXPORT CheckCudaUsability(int32_t *is_support) { +#ifdef ISF_ENABLE_TENSORRT int device_count; auto ret = GetCudaDeviceCount(&device_count); if (ret != HSUCCEED) { @@ -30,9 +36,14 @@ inline static int32_t CheckCudaUsability(int32_t *is_support) { } *is_support = device_count > 0; return HSUCCEED; +#else + *is_support = 0; + return HERR_DEVICE_CUDA_NOT_SUPPORT; +#endif } -inline static int32_t _PrintCudaDeviceInfo() { +int32_t INSPIRE_API_EXPORT _PrintCudaDeviceInfo() { +#ifdef ISF_ENABLE_TENSORRT try { INSPIRE_LOGW("TensorRT version: %d.%d.%d", NV_TENSORRT_MAJOR, NV_TENSORRT_MINOR, NV_TENSORRT_PATCH); @@ -98,16 +109,22 @@ inline static int32_t _PrintCudaDeviceInfo() { INSPIRE_LOGE("error when printing CUDA device info: %s", e.what()); return HERR_DEVICE_CUDA_UNKNOWN_ERROR; } +#else + INSPIRE_LOGE("CUDA/TensorRT support is not enabled"); + return HERR_DEVICE_CUDA_NOT_SUPPORT; +#endif } -inline static int32_t PrintCudaDeviceInfo() { +int32_t INSPIRE_API_EXPORT PrintCudaDeviceInfo() { +#ifdef ISF_ENABLE_TENSORRT INSPIRE_LOGW("================================================"); auto ret = _PrintCudaDeviceInfo(); INSPIRE_LOGW("================================================"); return ret; +#else + INSPIRE_LOGE("CUDA/TensorRT support is not enabled"); + return HERR_DEVICE_CUDA_NOT_SUPPORT; +#endif } -} // namespace inspire - -#endif // INSPIRE_CUDA_TOOLKIT_H -#endif // ISF_ENABLE_TENSORRT +} // namespace inspire \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/inspireface/middleware/inference_wrapper/inference_wrapper.h b/cpp-package/inspireface/cpp/inspireface/middleware/inference_wrapper/inference_wrapper.h index 648eb62..3196243 100644 --- a/cpp-package/inspireface/cpp/inspireface/middleware/inference_wrapper/inference_wrapper.h +++ b/cpp-package/inspireface/cpp/inspireface/middleware/inference_wrapper/inference_wrapper.h @@ -223,6 +223,12 @@ public: virtual int32_t Process(std::vector& output_tensor_info_list) = 0; virtual int32_t ParameterInitialization(std::vector& input_tensor_info_list, std::vector& output_tensor_info_list) = 0; + +#ifdef BATCH_FORWARD_IMPLEMENTED + virtual int32_t PreProcessBatch(const std::vector>& input_tensor_info_list) = 0; + virtual int32_t ProcessBatch(std::vector>& output_tensor_info_list) = 0; + virtual int32_t PostProcessBatch(std::vector>& output_tensor_info_list) = 0; +#endif virtual int32_t SetSpecialBackend(SpecialBackend backend) { special_backend_ = backend; diff --git a/cpp-package/inspireface/cpp/inspireface/middleware/inference_wrapper/inference_wrapper_tensorrt.cpp b/cpp-package/inspireface/cpp/inspireface/middleware/inference_wrapper/inference_wrapper_tensorrt.cpp index 6cab4f4..ebedd23 100644 --- a/cpp-package/inspireface/cpp/inspireface/middleware/inference_wrapper/inference_wrapper_tensorrt.cpp +++ b/cpp-package/inspireface/cpp/inspireface/middleware/inference_wrapper/inference_wrapper_tensorrt.cpp @@ -1,4 +1,5 @@ #if INFERENCE_WRAPPER_ENABLE_TENSORRT +#include #include #include #include @@ -40,7 +41,6 @@ int32_t InferenceWrapperTensorRT::Initialize(char* model_buffer, int model_size, net_->setDevice(device_id_); auto ret = net_->readFromBin(model_buffer, model_size); if (ret != WrapperOk) { - std::cout << "model_size: " << model_size << std::endl; PRINT_E("Failed to load TensorRT model\n"); return WrapperError; } diff --git a/cpp-package/inspireface/cpp/inspireface/middleware/inspirecv_image_process.h b/cpp-package/inspireface/cpp/inspireface/middleware/inspirecv_image_process.h deleted file mode 100644 index d796b70..0000000 --- a/cpp-package/inspireface/cpp/inspireface/middleware/inspirecv_image_process.h +++ /dev/null @@ -1,392 +0,0 @@ -#ifndef INSPIRECV_IMAGE_PROCESS_H -#define INSPIRECV_IMAGE_PROCESS_H - -#include -#include -#include -#include "isf_check.h" - -// using namespace inspire; -namespace inspirecv { - -/** - * @brief Enum to represent rotation modes. - */ -enum ROTATION_MODE { ROTATION_0 = 0, ROTATION_90 = 1, ROTATION_180 = 2, ROTATION_270 = 3 }; - -/** - * @brief Enum to represent data formats. - */ -enum DATA_FORMAT { NV21 = 0, NV12 = 1, RGBA = 2, RGB = 3, BGR = 4, BGRA = 5 }; - -/** - * @brief A class to handle camera stream and image processing. - */ -class InspireImageProcess { -public: - static InspireImageProcess Create(const uint8_t *data_buffer, int height, int width, DATA_FORMAT data_format = BGR, - ROTATION_MODE rotation_mode = ROTATION_0) { - InspireImageProcess process; - process.SetDataBuffer(data_buffer, height, width); - process.SetDataFormat(data_format); - process.SetRotationMode(rotation_mode); - return process; - } - - InspireImageProcess() { - SetDataFormat(NV21); - SetDestFormat(BGR); - config_.filterType = MNN::CV::BILINEAR; - config_.wrap = MNN::CV::ZERO; - rotation_mode_ = ROTATION_0; - preview_size_ = 192; - UpdateTransformMatrix(); - } - - /** - * @brief Set the data buffer, height, and width of the camera stream. - * - * @param data_buffer Pointer to the data buffer. - * @param height Height of the image. - * @param width Width of the image. - */ - void SetDataBuffer(const uint8_t *data_buffer, int height, int width) { - this->buffer_ = data_buffer; - this->height_ = height; - this->width_ = width; - preview_scale_ = preview_size_ / static_cast(std::max(height, width)); - UpdateTransformMatrix(); - } - - /** - * @brief Set the preview size. - * - * @param size Preview size. - */ - void SetPreviewSize(const int size) { - preview_size_ = size; - preview_scale_ = preview_size_ / static_cast(std::max(this->height_, this->width_)); - UpdateTransformMatrix(); - } - - void SetPreviewScale(const float scale) { - preview_scale_ = scale; - preview_size_ = static_cast(preview_scale_ * std::max(this->height_, this->width_)); - UpdateTransformMatrix(); - } - - /** - * @brief Set the rotation mode. - * - * @param mode Rotation mode (e.g., ROTATION_0, ROTATION_90). - */ - void SetRotationMode(ROTATION_MODE mode) { - rotation_mode_ = mode; - UpdateTransformMatrix(); - } - - /** - * @brief Set the data format. - * - * @param data_format Data format (e.g., NV21, RGBA). - */ - void SetDataFormat(DATA_FORMAT data_format) { - if (data_format == NV21) { - config_.sourceFormat = MNN::CV::YUV_NV21; - } - if (data_format == NV12) { - config_.sourceFormat = MNN::CV::YUV_NV12; - } - if (data_format == RGBA) { - config_.sourceFormat = MNN::CV::RGBA; - } - if (data_format == RGB) { - config_.sourceFormat = MNN::CV::RGB; - } - if (data_format == BGR) { - config_.sourceFormat = MNN::CV::BGR; - } - if (data_format == BGRA) { - config_.sourceFormat = MNN::CV::BGRA; - } - } - - /** - * @brief Set the destination format. - * - * @param data_format Data format (e.g., NV21, RGBA). - */ - void SetDestFormat(DATA_FORMAT data_format) { - if (data_format == NV21) { - config_.destFormat = MNN::CV::YUV_NV21; - } - if (data_format == NV12) { - config_.destFormat = MNN::CV::YUV_NV12; - } - if (data_format == RGBA) { - config_.destFormat = MNN::CV::RGBA; - } - if (data_format == RGB) { - config_.destFormat = MNN::CV::RGB; - } - if (data_format == BGR) { - config_.destFormat = MNN::CV::BGR; - } - if (data_format == BGRA) { - config_.destFormat = MNN::CV::BGRA; - } - } - - /** - * @brief Get an affine-transformed image. - * - * @param affine_matrix Affine transformation matrix. - * @param width_out Width of the output image. - * @param height_out Height of the output image. - * @return cv::Mat Affine-transformed image. - */ - inspirecv::Image ExecuteImageAffineProcessing(inspirecv::TransformMatrix &affine_matrix, const int width_out, const int height_out) const { - int sw = width_; - int sh = height_; - int rot_sw = sw; - int rot_sh = sh; - MNN::CV::Matrix tr; - std::vector tr_cv({1, 0, 0, 0, 1, 0, 0, 0, 1}); - memcpy(tr_cv.data(), affine_matrix.Squeeze().data(), sizeof(float) * 6); - tr.set9(tr_cv.data()); - MNN::CV::Matrix tr_inv; - tr.invert(&tr_inv); - std::shared_ptr process(MNN::CV::ImageProcess::create(config_)); - process->setMatrix(tr_inv); - auto img_out = inspirecv::Image::Create(width_out, height_out, 3); - std::shared_ptr tensor(MNN::Tensor::create(std::vector{1, height_out, width_out, 3}, (uint8_t *)img_out.Data())); - auto ret = process->convert(buffer_, sw, sh, 0, tensor.get()); - INSPIREFACE_CHECK_MSG(ret == MNN::ErrorCode::NO_ERROR, "ImageProcess::convert failed"); - return img_out; - } - - /** - * @brief Get a preview image with optional rotation. - * - * @param with_rotation True if rotation is applied, false otherwise. - * @return cv::Mat Preview image. - */ - inspirecv::Image ExecutePreviewImageProcessing(bool with_rotation) { - return ExecuteImageScaleProcessing(preview_scale_, with_rotation); - } - - /** - * @brief Get the preview scale. - * - * @return float Preview scale. - */ - float GetPreviewScale() { - return preview_scale_; - } - - /** - * @brief Execute image scale processing. - * - * @param scale Scale factor. - * @param with_rotation True if rotation is applied, false otherwise. - * @return inspirecv::Image Scaled image. - */ - inspirecv::Image ExecuteImageScaleProcessing(const float scale, bool with_rotation) { - int sw = width_; - int sh = height_; - int rot_sw = sw; - int rot_sh = sh; - // MNN::CV::Matrix tr; - std::shared_ptr process(MNN::CV::ImageProcess::create(config_)); - if (rotation_mode_ == ROTATION_270 && with_rotation) { - float srcPoints[] = { - 0.0f, 0.0f, 0.0f, (float)(height_ - 1), (float)(width_ - 1), 0.0f, (float)(width_ - 1), (float)(height_ - 1), - }; - float dstPoints[] = {(float)(height_ * scale - 1), 0.0f, 0.0f, 0.0f, (float)(height_ * scale - 1), (float)(width_ * scale - 1), 0.0f, - (float)(width_ * scale - 1)}; - - tr_.setPolyToPoly((MNN::CV::Point *)dstPoints, (MNN::CV::Point *)srcPoints, 4); - process->setMatrix(tr_); - int scaled_height = static_cast(width_ * scale); - int scaled_width = static_cast(height_ * scale); - inspirecv::Image img_out(scaled_width, scaled_height, 3); - std::shared_ptr tensor( - MNN::Tensor::create(std::vector{1, scaled_height, scaled_width, 3}, (uint8_t *)img_out.Data())); - auto ret = process->convert(buffer_, sw, sh, 0, tensor.get()); - INSPIREFACE_CHECK_MSG(ret == MNN::ErrorCode::NO_ERROR, "ImageProcess::convert failed"); - return img_out; - } else if (rotation_mode_ == ROTATION_90 && with_rotation) { - float srcPoints[] = { - 0.0f, 0.0f, 0.0f, (float)(height_ - 1), (float)(width_ - 1), 0.0f, (float)(width_ - 1), (float)(height_ - 1), - }; - float dstPoints[] = { - 0.0f, (float)(width_ * scale - 1), (float)(height_ * scale - 1), (float)(width_ * scale - 1), 0.0f, 0.0f, (float)(height_ * scale - 1), - 0.0f, - }; - tr_.setPolyToPoly((MNN::CV::Point *)dstPoints, (MNN::CV::Point *)srcPoints, 4); - process->setMatrix(tr_); - int scaled_height = static_cast(width_ * scale); - int scaled_width = static_cast(height_ * scale); - inspirecv::Image img_out(scaled_width, scaled_height, 3); - std::shared_ptr tensor( - MNN::Tensor::create(std::vector{1, scaled_height, scaled_width, 3}, (uint8_t *)img_out.Data())); - auto ret = process->convert(buffer_, sw, sh, 0, tensor.get()); - INSPIREFACE_CHECK_MSG(ret == MNN::ErrorCode::NO_ERROR, "ImageProcess::convert failed"); - return img_out; - } else if (rotation_mode_ == ROTATION_180 && with_rotation) { - float srcPoints[] = { - 0.0f, 0.0f, 0.0f, (float)(height_ - 1), (float)(width_ - 1), 0.0f, (float)(width_ - 1), (float)(height_ - 1), - }; - float dstPoints[] = { - (float)(width_ * scale - 1), - (float)(height_ * scale - 1), - (float)(width_ * scale - 1), - 0.0f, - 0.0f, - (float)(height_ * scale - 1), - 0.0f, - 0.0f, - }; - tr_.setPolyToPoly((MNN::CV::Point *)dstPoints, (MNN::CV::Point *)srcPoints, 4); - process->setMatrix(tr_); - int scaled_height = static_cast(height_ * scale); - int scaled_width = static_cast(width_ * scale); - inspirecv::Image img_out(scaled_width, scaled_height, 3); - std::shared_ptr tensor( - MNN::Tensor::create(std::vector{1, scaled_height, scaled_width, 3}, (uint8_t *)img_out.Data())); - auto ret = process->convert(buffer_, sw, sh, 0, tensor.get()); - INSPIREFACE_CHECK_MSG(ret == MNN::ErrorCode::NO_ERROR, "ImageProcess::convert failed"); - return img_out; - } else { - float srcPoints[] = { - 0.0f, 0.0f, 0.0f, (float)(height_ - 1), (float)(width_ - 1), 0.0f, (float)(width_ - 1), (float)(height_ - 1), - }; - float dstPoints[] = { - 0.0f, - 0.0f, - 0.0f, - (float)(height_ * scale - 1), - (float)(width_ * scale - 1), - 0.0f, - (float)(width_ * scale - 1), - (float)(height_ * scale - 1), - }; - tr_.setPolyToPoly((MNN::CV::Point *)dstPoints, (MNN::CV::Point *)srcPoints, 4); - process->setMatrix(tr_); - int scaled_height = static_cast(height_ * scale); - int scaled_width = static_cast(width_ * scale); - - inspirecv::Image img_out(scaled_width, scaled_height, 3); - std::shared_ptr tensor( - MNN::Tensor::create(std::vector{1, scaled_height, scaled_width, 3}, (uint8_t *)img_out.Data())); - auto ret = process->convert(buffer_, sw, sh, 0, tensor.get()); - INSPIREFACE_CHECK_MSG(ret == MNN::ErrorCode::NO_ERROR, "ImageProcess::convert failed"); - return img_out; - } - } - - inspirecv::TransformMatrix GetAffineMatrix() const { - auto affine_matrix = inspirecv::TransformMatrix::Create(); - affine_matrix[0] = tr_[0]; - affine_matrix[1] = tr_[1]; - affine_matrix[2] = tr_[2]; - affine_matrix[3] = tr_[3]; - affine_matrix[4] = tr_[4]; - affine_matrix[5] = tr_[5]; - return affine_matrix; - } - - /** - * @brief Get the height of the camera stream image. - * - * @return int Height. - */ - int GetHeight() const { - return height_; - } - - /** - * @brief Get the width of the camera stream image. - * - * @return int Width. - */ - int GetWidth() const { - return width_; - } - - /** - * @brief Get the current rotation mode. - * - * @return ROTATION_MODE Current rotation mode. - */ - ROTATION_MODE getRotationMode() const { - return rotation_mode_; - } - -private: - void UpdateTransformMatrix() { - float srcPoints[] = {0.0f, 0.0f, 0.0f, (float)(height_ - 1), (float)(width_ - 1), 0.0f, (float)(width_ - 1), (float)(height_ - 1)}; - - float dstPoints[8]; - if (rotation_mode_ == ROTATION_270) { - float points[] = {(float)(height_ * preview_scale_ - 1), - 0.0f, - 0.0f, - 0.0f, - (float)(height_ * preview_scale_ - 1), - (float)(width_ * preview_scale_ - 1), - 0.0f, - (float)(width_ * preview_scale_ - 1)}; - memcpy(dstPoints, points, sizeof(points)); - } else if (rotation_mode_ == ROTATION_90) { - float points[] = {0.0f, - (float)(width_ * preview_scale_ - 1), - (float)(height_ * preview_scale_ - 1), - (float)(width_ * preview_scale_ - 1), - 0.0f, - 0.0f, - (float)(height_ * preview_scale_ - 1), - 0.0f}; - memcpy(dstPoints, points, sizeof(points)); - } else if (rotation_mode_ == ROTATION_180) { - float points[] = {(float)(width_ * preview_scale_ - 1), - (float)(height_ * preview_scale_ - 1), - (float)(width_ * preview_scale_ - 1), - 0.0f, - 0.0f, - (float)(height_ * preview_scale_ - 1), - 0.0f, - 0.0f}; - memcpy(dstPoints, points, sizeof(points)); - } else { // ROTATION_0 - float points[] = {0.0f, - 0.0f, - 0.0f, - (float)(height_ * preview_scale_ - 1), - (float)(width_ * preview_scale_ - 1), - 0.0f, - (float)(width_ * preview_scale_ - 1), - (float)(height_ * preview_scale_ - 1)}; - memcpy(dstPoints, points, sizeof(points)); - } - - tr_.setPolyToPoly((MNN::CV::Point *)dstPoints, (MNN::CV::Point *)srcPoints, 4); - } - -private: - const uint8_t *buffer_; ///< Pointer to the data buffer. - int buffer_size_; ///< Size of the data buffer. - std::vector rotation_matrix; ///< Rotation matrix. - int height_; ///< Height of the camera stream image. - int width_; ///< Width of the camera stream image. - float preview_scale_; ///< Scaling factor for the preview image. - int preview_size_; ///< Size of the preview image. - MNN::CV::Matrix tr_; ///< Affine transformation matrix. - ROTATION_MODE rotation_mode_; ///< Current rotation mode. - MNN::CV::ImageProcess::Config config_; ///< Configuration for image processing. - std::shared_ptr process_; ///< Image processing instance. -}; - -} // namespace inspirecv - -#endif // INSPIRECV_IMAGE_PROCESS_H \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/inspireface/middleware/model_archive/inspire_archive.h b/cpp-package/inspireface/cpp/inspireface/middleware/model_archive/inspire_archive.h index 4c29966..57e1684 100644 --- a/cpp-package/inspireface/cpp/inspireface/middleware/model_archive/inspire_archive.h +++ b/cpp-package/inspireface/cpp/inspireface/middleware/model_archive/inspire_archive.h @@ -10,7 +10,9 @@ #include "inspire_model/inspire_model.h" #include "yaml-cpp/yaml.h" #include "fstream" -#include "recognition_module/similarity_converter.h" +#include "similarity_converter.h" +#include "launch.h" +#include "track_module/landmark/landmark_param.h" namespace inspire { @@ -110,6 +112,17 @@ public: return m_archive_->GetFileContent(filename); } + const std::vector& GetFaceDetectPixelList() const { + return m_face_detect_pixel_list_; + } + + const std::vector& GetFaceDetectModelList() const { + return m_face_detect_model_list_; + } + + const std::shared_ptr& GetLandmarkParam() const { + return m_landmark_param_; + } private: int32_t loadManifestFile() { if (m_archive_->QueryLoadStatus() == SARC_SUCCESS) { @@ -156,10 +169,29 @@ private: config.middleScore, config.steepness, config.outputMin, config.outputMax); SIMILARITY_CONVERTER_SET_RECOMMENDED_COSINE_THRESHOLD(config.threshold); } + // Load face detect model + if (m_config_["face_detect_pixel_list"] && m_config_["face_detect_model_list"]) { + auto node_face_detect_pixel_list = m_config_["face_detect_pixel_list"]; + for (std::size_t i = 0; i < node_face_detect_pixel_list.size(); ++i) { + m_face_detect_pixel_list_.push_back(node_face_detect_pixel_list[i].as()); + } + auto node_face_detect_model_list = m_config_["face_detect_model_list"]; + for (std::size_t i = 0; i < node_face_detect_model_list.size(); ++i) { + m_face_detect_model_list_.push_back(node_face_detect_model_list[i].as()); + } + if (m_face_detect_pixel_list_.size() != m_face_detect_model_list_.size()) { + return FORMAT_ERROR; + } + } else { + m_face_detect_pixel_list_ = {160, 320, 640}; + m_face_detect_model_list_ = {"face_detect_160", "face_detect_320", "face_detect_640"}; + } + m_landmark_param_ = std::make_shared(m_config_["landmark_table"]); } return 0; } + private: std::shared_ptr m_archive_; YAML::Node m_config_; @@ -172,6 +204,11 @@ private: std::string m_version_; std::string m_major_; std::string m_release_time_; + + std::vector m_face_detect_pixel_list_; + std::vector m_face_detect_model_list_; + + std::shared_ptr m_landmark_param_; }; } // namespace inspire diff --git a/cpp-package/inspireface/cpp/inspireface/middleware/timer.cpp b/cpp-package/inspireface/cpp/inspireface/middleware/timer.cpp new file mode 100644 index 0000000..693d34c --- /dev/null +++ b/cpp-package/inspireface/cpp/inspireface/middleware/timer.cpp @@ -0,0 +1,111 @@ +#include "spend_timer.h" +#include +#include +#include "log.h" + +#if defined(_MSC_VER) +#include // NOLINT +#else +#include +#endif + +namespace inspire { + +#if defined(_MSC_VER) + +uint64_t INSPIRE_API_EXPORT _now() { + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); +} + +#else + +uint64_t INSPIRE_API_EXPORT _now() { + struct timeval tv; + gettimeofday(&tv, nullptr); + return static_cast(tv.tv_sec) * 1000000 + tv.tv_usec; +} + +#endif // defined(_MSC_VER) + +int SpendTimer::is_enable = true; + +SpendTimer::SpendTimer() { + Reset(); +} + +SpendTimer::SpendTimer(const std::string &name) { + name_ = name; + Reset(); +} + +void SpendTimer::Start() { + start_ = _now(); +} + +void SpendTimer::Stop() { + stop_ = _now(); + uint64_t d = stop_ - start_; + total_ += d; + ++count_; + min_ = std::min(min_, d); + max_ = std::max(max_, d); +} + +void SpendTimer::Reset() { + start_ = 0; + stop_ = 0; + total_ = 0; + count_ = 0; + min_ = UINT64_MAX; + max_ = 0; +} + +uint64_t SpendTimer::Get() const { + return stop_ - start_; +} + +uint64_t SpendTimer::Average() const { + return count_ == 0 ? 0 : total_ / count_; +} + +uint64_t SpendTimer::Total() const { + return total_; +} + +uint64_t SpendTimer::Count() const { + return count_; +} + +uint64_t SpendTimer::Min() const { + return count_ == 0 ? 0 : min_; +} + +uint64_t SpendTimer::Max() const { + return max_; +} + +const std::string &SpendTimer::name() const { + return name_; +} + +std::string SpendTimer::Report() const { + std::stringstream ss; + if (is_enable) { + ss << "[Time(us) Total:" << Total() << " Ave:" << Average() << " Min:" << Min() << " Max:" << Max() << " Count:" << Count() << " " << name_ + << "]"; + } else { + ss << "Timer Disabled."; + } + return ss.str(); +} + +void SpendTimer::Disable() { + is_enable = false; +} + +std::ostream &operator<<(std::ostream &os, const SpendTimer &timer) { + os << timer.Report(); + return os; +} + +} // namespace inspire diff --git a/cpp-package/inspireface/cpp/inspireface/pipeline_module/face_pipeline_module.cpp b/cpp-package/inspireface/cpp/inspireface/pipeline_module/face_pipeline_module.cpp index 2c82197..77a2cda 100644 --- a/cpp-package/inspireface/cpp/inspireface/pipeline_module/face_pipeline_module.cpp +++ b/cpp-package/inspireface/cpp/inspireface/pipeline_module/face_pipeline_module.cpp @@ -7,9 +7,9 @@ #include "log.h" #include "track_module/landmark/face_landmark_adapt.h" +#include "track_module/landmark/landmark_param.h" #include "recognition_module/dest_const.h" #include "herror.h" -#include "liveness/order_of_hyper_landmark.h" namespace inspire { @@ -31,7 +31,7 @@ FacePipelineModule::FacePipelineModule(InspireArchive &archive, bool enableLiven INSPIRE_LOGE("InitAgePredict error."); } } - + m_landmark_param_ = archive.GetLandmarkParam(); // Initialize the mask detection model if (m_enable_mask_detect_) { InspireModel maskModel; @@ -73,9 +73,11 @@ FacePipelineModule::FacePipelineModule(InspireArchive &archive, bool enableLiven } } -int32_t FacePipelineModule::Process(inspirecv::InspireImageProcess &processor, const HyperFaceData &face, FaceProcessFunctionOption proc) { +int32_t FacePipelineModule::Process(inspirecv::FrameProcess &processor, const FaceTrackWrap &face, FaceProcessFunctionOption proc) { + // Original image inspirecv::Image originImage; inspirecv::Image scaleImage; + std::vector stand_lmk; switch (proc) { case PROCESS_MASK: { if (m_mask_predict_ == nullptr) { @@ -97,8 +99,29 @@ int32_t FacePipelineModule::Process(inspirecv::InspireImageProcess &processor, c if (m_rgb_anti_spoofing_ == nullptr) { return HERR_SESS_PIPELINE_FAILURE; // uninitialized } + // New scheme: padding differences cause errors in inference results + // inspirecv::TransformMatrix rotation_mode_affine = processor.GetAffineMatrix(); + // if (stand_lmk.empty()) { + // std::vector lmk; + // for (const auto &p : face.densityLandmark) { + // lmk.emplace_back(p.x, p.y); + // } + // stand_lmk = inspirecv::ApplyTransformToPoints(lmk, rotation_mode_affine.GetInverse()); + // } + + // auto rect_face = inspirecv::MinBoundingRect(stand_lmk); + // auto rect_pts = rect_face.Square(2.7f).As().ToFourVertices(); + // std::vector dst_pts = {{0, 0}, {112, 0}, {112, 112}, {0, 112}}; + // std::vector camera_pts = inspirecv::ApplyTransformToPoints(rect_pts, rotation_mode_affine); + + // auto affine = inspirecv::SimilarityTransformEstimate(camera_pts, dst_pts); + // auto image_affine = processor.ExecuteImageAffineProcessing(affine, 112, 112); + // image_affine.Write("liveness_affine.jpg"); if (originImage.Empty()) { + // This is a poor approach that impacts performance, + // but in order to capture clearer images and improve liveness detection accuracy, + // we have to keep it. originImage = processor.ExecuteImageScaleProcessing(1.0, true); } inspirecv::Rect2i oriRect(face.rect.x, face.rect.y, face.rect.width, face.rect.height); @@ -106,6 +129,7 @@ int32_t FacePipelineModule::Process(inspirecv::InspireImageProcess &processor, c auto crop = originImage.Crop(rect); auto score = (*m_rgb_anti_spoofing_)(crop); // crop.Show(); + // crop.Resize(112, 112).Write("liveness.jpg"); faceLivenessCache = score; break; } @@ -113,61 +137,37 @@ int32_t FacePipelineModule::Process(inspirecv::InspireImageProcess &processor, c if (m_blink_predict_ == nullptr) { return HERR_SESS_PIPELINE_FAILURE; // uninitialized } - if (originImage.Empty()) { - originImage = processor.ExecuteImageScaleProcessing(1.0, true); - } - std::vector> order_list = {HLMK_LEFT_EYE_POINTS_INDEX, HLMK_RIGHT_EYE_POINTS_INDEX}; + std::vector> order_list = {m_landmark_param_->semantic_index.left_eye_region, m_landmark_param_->semantic_index.right_eye_region}; eyesStatusCache = {0, 0}; inspirecv::Point2f left_eye = inspirecv::Point2f(face.keyPoints[0].x, face.keyPoints[0].y); inspirecv::Point2f right_eye = inspirecv::Point2f(face.keyPoints[1].x, face.keyPoints[1].y); std::vector eyes = {left_eye, right_eye}; - auto new_eyes_points = inspirecv::ApplyTransformToPoints(eyes, processor.GetAffineMatrix().GetInverse()); + // Get affine matrix + inspirecv::TransformMatrix rotation_mode_affine = processor.GetAffineMatrix(); + // Get stand landmark + if (stand_lmk.empty()) { + std::vector lmk; + for (const auto &p : face.densityLandmark) { + lmk.emplace_back(p.x, p.y); + } + stand_lmk = inspirecv::ApplyTransformToPoints(lmk, rotation_mode_affine.GetInverse()); + } for (size_t i = 0; i < order_list.size(); i++) { const auto &index = order_list[i]; std::vector points; for (const auto &idx : index) { - points.emplace_back(face.densityLandmark[idx].x, face.densityLandmark[idx].y); + points.emplace_back(stand_lmk[idx].GetX(), stand_lmk[idx].GetY()); } - auto rect = inspirecv::MinBoundingRect(points); - auto mat = processor.GetAffineMatrix(); - auto new_rect = inspirecv::ApplyTransformToRect(rect, mat.GetInverse()).Square(1.3f); - // Use more accurate 5 key point calibration - auto cx = new_eyes_points[i].GetX(); - auto cy = new_eyes_points[i].GetY(); - new_rect.SetX(cx - new_rect.GetWidth() / 2); - new_rect.SetY(cy - new_rect.GetHeight() / 2); + auto rect_eye = inspirecv::MinBoundingRect(points).Square(1.5f); + auto rect_pts_eye = rect_eye.As().ToFourVertices(); + std::vector dst_pts_eye = {{0, 0}, {64, 0}, {64, 64}, {0, 64}}; + std::vector camera_pts_eye = inspirecv::ApplyTransformToPoints(rect_pts_eye, rotation_mode_affine); - // Ensure rect stays within image bounds while maintaining aspect ratio - float originalAspectRatio = new_rect.GetWidth() / new_rect.GetHeight(); - - // Adjust position and size to fit within image bounds - if (new_rect.GetX() < 0) { - new_rect.SetWidth(new_rect.GetWidth() + new_rect.GetX()); // Reduce width by overflow amount - new_rect.SetX(0); - } - if (new_rect.GetY() < 0) { - new_rect.SetHeight(new_rect.GetHeight() + new_rect.GetY()); // Reduce height by overflow amount - new_rect.SetY(0); - } - - float rightOverflow = (new_rect.GetX() + new_rect.GetWidth()) - originImage.Width(); - if (rightOverflow > 0) { - new_rect.SetWidth(new_rect.GetWidth() - rightOverflow); - } - - float bottomOverflow = (new_rect.GetY() + new_rect.GetHeight()) - originImage.Height(); - if (bottomOverflow > 0) { - new_rect.SetHeight(new_rect.GetHeight() - bottomOverflow); - } - - // Maintain minimum size (e.g., 20x20 ixels) - const float minSize = 20.0f; - if (new_rect.GetWidth() < minSize || new_rect.GetHeight() < minSize) { - continue; // Skip this eye if the crop region is too small - } - - auto crop = originImage.Crop(new_rect); - auto score = (*m_blink_predict_)(crop); + auto affine_eye = inspirecv::SimilarityTransformEstimate(camera_pts_eye, dst_pts_eye); + auto eye_affine = processor.ExecuteImageAffineProcessing(affine_eye, 64, 64); + // eye_affine.Write("eye_"+std::to_string(i)+".jpg"); + // auto crop = originImage.Crop(new_rect); + auto score = (*m_blink_predict_)(eye_affine); eyesStatusCache[i] = score; } break; @@ -190,12 +190,12 @@ int32_t FacePipelineModule::Process(inspirecv::InspireImageProcess &processor, c return HSUCCEED; } -int32_t FacePipelineModule::Process(inspirecv::InspireImageProcess &processor, FaceObjectInternal &face) { +int32_t FacePipelineModule::Process(inspirecv::FrameProcess &processor, FaceObjectInternal &face) { // In the tracking state, the count meets the requirements or the pipeline is executed in the detection state auto lmk = face.keyPointFive; - std::vector lmk_5 = {lmk[FaceLandmarkAdapt::LEFT_EYE_CENTER], lmk[FaceLandmarkAdapt::RIGHT_EYE_CENTER], - lmk[FaceLandmarkAdapt::NOSE_CORNER], lmk[FaceLandmarkAdapt::MOUTH_LEFT_CORNER], - lmk[FaceLandmarkAdapt::MOUTH_RIGHT_CORNER]}; + std::vector lmk_5 = {lmk[m_landmark_param_->semantic_index.left_eye_center], lmk[m_landmark_param_->semantic_index.right_eye_center], + lmk[m_landmark_param_->semantic_index.nose_corner], lmk[m_landmark_param_->semantic_index.mouth_left_corner], + lmk[m_landmark_param_->semantic_index.mouth_right_corner]}; auto trans = inspirecv::SimilarityTransformEstimateUmeyama(SIMILARITY_TRANSFORM_DEST, lmk_5); auto crop = processor.ExecuteImageAffineProcessing(trans, FACE_CROP_SIZE, FACE_CROP_SIZE); if (m_mask_predict_ != nullptr) { @@ -225,7 +225,7 @@ int32_t FacePipelineModule::Process(inspirecv::InspireImageProcess &processor, F int32_t FacePipelineModule::InitFaceAttributePredict(InspireModel &model) { m_attribute_predict_ = std::make_shared(); - auto ret = m_attribute_predict_->loadData(model, model.modelType); + auto ret = m_attribute_predict_->LoadData(model, model.modelType); if (ret != InferenceWrapper::WrapperOk) { return HERR_ARCHIVE_LOAD_FAILURE; } @@ -234,7 +234,7 @@ int32_t FacePipelineModule::InitFaceAttributePredict(InspireModel &model) { int32_t FacePipelineModule::InitMaskPredict(InspireModel &model) { m_mask_predict_ = std::make_shared(); - auto ret = m_mask_predict_->loadData(model, model.modelType); + auto ret = m_mask_predict_->LoadData(model, model.modelType); if (ret != InferenceWrapper::WrapperOk) { return HERR_ARCHIVE_LOAD_FAILURE; } @@ -248,7 +248,7 @@ int32_t FacePipelineModule::InitRBGAntiSpoofing(InspireModel &model) { #else m_rgb_anti_spoofing_ = std::make_shared(input_size[0]); #endif - auto ret = m_rgb_anti_spoofing_->loadData(model, model.modelType); + auto ret = m_rgb_anti_spoofing_->LoadData(model, model.modelType); if (ret != InferenceWrapper::WrapperOk) { return HERR_ARCHIVE_LOAD_FAILURE; } @@ -257,7 +257,7 @@ int32_t FacePipelineModule::InitRBGAntiSpoofing(InspireModel &model) { int32_t FacePipelineModule::InitBlinkFromLivenessInteraction(InspireModel &model) { m_blink_predict_ = std::make_shared(); - auto ret = m_blink_predict_->loadData(model, model.modelType); + auto ret = m_blink_predict_->LoadData(model, model.modelType); if (ret != InferenceWrapper::WrapperOk) { return HERR_ARCHIVE_LOAD_FAILURE; } diff --git a/cpp-package/inspireface/cpp/inspireface/pipeline_module/face_pipeline_module.h b/cpp-package/inspireface/cpp/inspireface/pipeline_module/face_pipeline_module.h index f6f5da4..8736913 100644 --- a/cpp-package/inspireface/cpp/inspireface/pipeline_module/face_pipeline_module.h +++ b/cpp-package/inspireface/cpp/inspireface/pipeline_module/face_pipeline_module.h @@ -6,15 +6,15 @@ #ifndef INSPIRE_FACE_PIPELINE_MODULE_H #define INSPIRE_FACE_PIPELINE_MODULE_H -#include "middleware/inspirecv_image_process.h" +#include "frame_process.h" #include "common/face_info/face_object_internal.h" #include "attribute/face_attribute_adapt.h" #include "attribute/mask_predict_adapt.h" #include "liveness/rgb_anti_spoofing_adapt.h" #include "liveness/blink_predict_adapt.h" #include "middleware/model_archive/inspire_archive.h" -#include "common/face_data/face_data_type.h" - +#include "face_warpper.h" +#include "track_module/landmark/landmark_param.h" namespace inspire { /** @@ -56,17 +56,17 @@ public: * @param face FaceObject representing the detected face. * @return int32_t Status code indicating success (0) or failure. */ - int32_t Process(inspirecv::InspireImageProcess &processor, FaceObjectInternal &face); + int32_t Process(inspirecv::FrameProcess &processor, FaceObjectInternal &face); /** * @brief Processes a face using the specified FaceProcessFunction. * * @param image CameraStream instance containing the image. - * @param face HyperFaceData representing the detected face. + * @param face FaceTrackWrap representing the detected face. * @param proc The FaceProcessFunction to apply to the face. * @return int32_t Status code indicating success (0) or failure. */ - int32_t Process(inspirecv::InspireImageProcess &processor, const HyperFaceData &face, FaceProcessFunctionOption proc); + int32_t Process(inspirecv::FrameProcess &processor, const FaceTrackWrap &face, FaceProcessFunctionOption proc); /** * @brief Get Rgb AntiSpoofing module @@ -125,6 +125,7 @@ private: std::shared_ptr m_mask_predict_; ///< Pointer to MaskPredict instance. std::shared_ptr m_rgb_anti_spoofing_; ///< Pointer to RBGAntiSpoofing instance. std::shared_ptr m_blink_predict_; ///< Pointer to Blink predict instance. + std::shared_ptr m_landmark_param_; ///< Pointer to LandmarkParam instance. public: float faceMaskCache; ///< Cache for face mask detection result. diff --git a/cpp-package/inspireface/cpp/inspireface/platform/jni/android/inspireface_jni.cpp b/cpp-package/inspireface/cpp/inspireface/platform/jni/android/inspireface_jni.cpp index abc6eea..d52dc0e 100644 --- a/cpp-package/inspireface/cpp/inspireface/platform/jni/android/inspireface_jni.cpp +++ b/cpp-package/inspireface/cpp/inspireface/platform/jni/android/inspireface_jni.cpp @@ -96,6 +96,7 @@ JNIEXPORT jobject INSPIRE_FACE_JNI(InspireFace_CreateSession)(JNIEnv *env, jobje jfieldID enableFaceQualityField = env->GetFieldID(customParamClass, "enableFaceQuality", "I"); jfieldID enableFaceAttributeField = env->GetFieldID(customParamClass, "enableFaceAttribute", "I"); jfieldID enableInteractionLivenessField = env->GetFieldID(customParamClass, "enableInteractionLiveness", "I"); + jfieldID enableFacePoseField = env->GetFieldID(customParamClass, "enableFacePose", "I"); // Create HFSessionCustomParameter struct HFSessionCustomParameter parameter; @@ -106,6 +107,7 @@ JNIEXPORT jobject INSPIRE_FACE_JNI(InspireFace_CreateSession)(JNIEnv *env, jobje parameter.enable_face_quality = env->GetIntField(customParameter, enableFaceQualityField); parameter.enable_face_attribute = env->GetIntField(customParameter, enableFaceAttributeField); parameter.enable_interaction_liveness = env->GetIntField(customParameter, enableInteractionLivenessField); + parameter.enable_face_pose = env->GetIntField(customParameter, enableFacePoseField); // Create session HFSession handle; @@ -164,6 +166,7 @@ JNIEXPORT jobject INSPIRE_FACE_JNI(InspireFace_CreateImageStreamFromBitmap)(JNIE return nullptr; } if (AndroidBitmap_lockPixels(env, bitmap, &pixels) < 0) { + AndroidBitmap_unlockPixels(env, bitmap); INSPIRE_LOGE("Failed to lock pixels"); return nullptr; } @@ -236,6 +239,7 @@ JNIEXPORT jobject INSPIRE_FACE_JNI(InspireFace_CreateImageStreamFromByteBuffer)( jfieldID streamHandleField = env->GetFieldID(streamClass, "handle", "J"); jobject imageStreamObj = env->NewObject(streamClass, constructor); env->SetLongField(imageStreamObj, streamHandleField, (jlong)streamHandle); + env->ReleaseByteArrayElements(data, (jbyte *)buf, JNI_ABORT); return imageStreamObj; } @@ -365,6 +369,9 @@ JNIEXPORT jobject INSPIRE_FACE_JNI(InspireFace_ExecuteFaceTrack)(JNIEnv *env, jo env->SetLongField(token, tokenHandleField, (jlong)results.tokens[i].data); env->SetIntField(token, sizeField, results.tokens[i].size); env->SetObjectArrayElement(tokenArray, i, token); + env->DeleteLocalRef(rect); + env->DeleteLocalRef(angle); + env->DeleteLocalRef(token); } // Set arrays to MultipleFaceData @@ -1311,7 +1318,7 @@ JNIEXPORT jboolean INSPIRE_FACE_JNI(InspireFace_MultipleFacePipelineProcess)(JNI jfieldID enableFaceQualityField = env->GetFieldID(paramClass, "enableFaceQuality", "I"); jfieldID enableFaceAttributeField = env->GetFieldID(paramClass, "enableFaceAttribute", "I"); jfieldID enableInteractionLivenessField = env->GetFieldID(paramClass, "enableInteractionLiveness", "I"); - jfieldID enableDetectModeLandmarkField = env->GetFieldID(paramClass, "enableDetectModeLandmark", "I"); + jfieldID enableFacePoseField = env->GetFieldID(paramClass, "enableFacePose", "I"); // Get parameter values HFSessionCustomParameter customParam; customParam.enable_recognition = env->GetIntField(parameter, enableRecognitionField); @@ -1321,7 +1328,7 @@ JNIEXPORT jboolean INSPIRE_FACE_JNI(InspireFace_MultipleFacePipelineProcess)(JNI customParam.enable_face_quality = env->GetIntField(parameter, enableFaceQualityField); customParam.enable_face_attribute = env->GetIntField(parameter, enableFaceAttributeField); customParam.enable_interaction_liveness = env->GetIntField(parameter, enableInteractionLivenessField); - customParam.enable_detect_mode_landmark = env->GetIntField(parameter, enableDetectModeLandmarkField); + customParam.enable_face_pose = env->GetIntField(parameter, enableFacePoseField); // Call native function HResult ret = HFMultipleFacePipelineProcess((HFSession)sessionHandle, (HFImageStream)streamHandleValue, &faceData, customParam); @@ -1645,6 +1652,12 @@ JNIEXPORT jobject INSPIRE_FACE_JNI(InspireFace_GetFaceAttributeResult)(JNIEnv *e if (!raceArray || !genderArray || !ageBracketArray) { INSPIRE_LOGE("Failed to create arrays"); + if (raceArray) + env->DeleteLocalRef(raceArray); + if (genderArray) + env->DeleteLocalRef(genderArray); + if (ageBracketArray) + env->DeleteLocalRef(ageBracketArray); return nullptr; } @@ -1656,6 +1669,10 @@ JNIEXPORT jobject INSPIRE_FACE_JNI(InspireFace_GetFaceAttributeResult)(JNIEnv *e env->SetObjectField(attributeObj, genderField, genderArray); env->SetObjectField(attributeObj, ageBracketField, ageBracketArray); + env->DeleteLocalRef(raceArray); + env->DeleteLocalRef(genderArray); + env->DeleteLocalRef(ageBracketArray); + return attributeObj; } @@ -1689,9 +1706,8 @@ JNIEXPORT jobject INSPIRE_FACE_JNI(InspireFace_QueryInspireFaceVersion)(JNIEnv * jfieldID majorField = env->GetFieldID(versionClass, "major", "I"); jfieldID minorField = env->GetFieldID(versionClass, "minor", "I"); jfieldID patchField = env->GetFieldID(versionClass, "patch", "I"); - jfieldID infoField = env->GetFieldID(versionClass, "information", "Ljava/lang/String;"); - if (!majorField || !minorField || !patchField || !infoField) { + if (!majorField || !minorField || !patchField) { INSPIRE_LOGE("Failed to get InspireFaceVersion field IDs"); return nullptr; } @@ -1701,32 +1717,6 @@ JNIEXPORT jobject INSPIRE_FACE_JNI(InspireFace_QueryInspireFaceVersion)(JNIEnv * env->SetIntField(version, minorField, versionInfo.minor); env->SetIntField(version, patchField, versionInfo.patch); - // Get extended information - HFInspireFaceExtendedInformation extendedInfo; - HFQueryInspireFaceExtendedInformation(&extendedInfo); - - // Sanitize the information string to ensure valid UTF-8 - std::string sanitizedInfo; - const char *rawInfo = extendedInfo.information; - while (*rawInfo) { - unsigned char c = static_cast(*rawInfo); - if (c < 0x80 || (c >= 0xC0 && c <= 0xF4)) { - // Valid UTF-8 start byte - sanitizedInfo += *rawInfo; - } - rawInfo++; - } - - // Convert sanitized string to Java string - jstring infoString = env->NewStringUTF(sanitizedInfo.c_str()); - if (infoString) { - env->SetObjectField(version, infoField, infoString); - } else { - // Fallback to a safe string if conversion fails - jstring fallbackString = env->NewStringUTF("Version information unavailable"); - env->SetObjectField(version, infoField, fallbackString); - } - return version; } diff --git a/cpp-package/inspireface/cpp/inspireface/recognition_module/face_feature_extraction_module.cpp b/cpp-package/inspireface/cpp/inspireface/recognition_module/face_feature_extraction_module.cpp index b8f9319..7e84138 100644 --- a/cpp-package/inspireface/cpp/inspireface/recognition_module/face_feature_extraction_module.cpp +++ b/cpp-package/inspireface/cpp/inspireface/recognition_module/face_feature_extraction_module.cpp @@ -23,13 +23,14 @@ FeatureExtractionModule::FeatureExtractionModule(InspireArchive &archive, bool e INSPIRE_LOGE("FaceRecognition error."); } } + m_landmark_param_ = archive.GetLandmarkParam(); } int32_t FeatureExtractionModule::InitExtractInteraction(InspireModel &model) { try { auto input_size = model.Config().get>("input_size"); m_extract_ = std::make_shared(); - auto ret = m_extract_->loadData(model, model.modelType); + auto ret = m_extract_->LoadData(model, model.modelType); if (ret != InferenceWrapper::WrapperOk) { return HERR_ARCHIVE_LOAD_FAILURE; } @@ -45,7 +46,7 @@ int32_t FeatureExtractionModule::QueryStatus() const { return m_status_code_; } -int32_t FeatureExtractionModule::FaceExtract(inspirecv::InspireImageProcess &processor, const HyperFaceData &face, Embedded &embedded, float &norm, +int32_t FeatureExtractionModule::FaceExtract(inspirecv::FrameProcess &processor, const FaceTrackWrap &face, Embedded &embedded, float &norm, bool normalize) { if (m_extract_ == nullptr) { return HERR_SESS_REC_EXTRACT_FAILURE; @@ -64,16 +65,37 @@ int32_t FeatureExtractionModule::FaceExtract(inspirecv::InspireImageProcess &pro return 0; } -int32_t FeatureExtractionModule::FaceExtract(inspirecv::InspireImageProcess &processor, const FaceObjectInternal &face, Embedded &embedded, - float &norm, bool normalize) { +int32_t FeatureExtractionModule::FaceExtractWithAlignmentImage(inspirecv::FrameProcess &processor, Embedded &embedded, float &norm, + bool normalize) { + if (m_extract_ == nullptr) { + return HERR_SESS_REC_EXTRACT_FAILURE; + } + auto crop = processor.ExecuteImageScaleProcessing(1.0f, false); + embedded = (*m_extract_)(crop, norm, normalize); + + return 0; +} + +int32_t FeatureExtractionModule::FaceExtractWithAlignmentImage(const inspirecv::Image& wrapped, Embedded &embedded, float &norm, + bool normalize) { + if (m_extract_ == nullptr) { + return HERR_SESS_REC_EXTRACT_FAILURE; + } + embedded = (*m_extract_)(wrapped, norm, normalize); + + return 0; +} + +int32_t FeatureExtractionModule::FaceExtract(inspirecv::FrameProcess &processor, const FaceObjectInternal &face, Embedded &embedded, float &norm, + bool normalize) { if (m_extract_ == nullptr) { return HERR_SESS_REC_EXTRACT_FAILURE; } auto lmk = face.landmark_; - std::vector lmk_5 = {lmk[FaceLandmarkAdapt::LEFT_EYE_CENTER], lmk[FaceLandmarkAdapt::RIGHT_EYE_CENTER], - lmk[FaceLandmarkAdapt::NOSE_CORNER], lmk[FaceLandmarkAdapt::MOUTH_LEFT_CORNER], - lmk[FaceLandmarkAdapt::MOUTH_RIGHT_CORNER]}; + std::vector lmk_5 = {lmk[m_landmark_param_->semantic_index.left_eye_center], lmk[m_landmark_param_->semantic_index.right_eye_center], + lmk[m_landmark_param_->semantic_index.nose_corner], lmk[m_landmark_param_->semantic_index.mouth_left_corner], + lmk[m_landmark_param_->semantic_index.mouth_right_corner]}; auto trans = inspirecv::SimilarityTransformEstimateUmeyama(SIMILARITY_TRANSFORM_DEST, lmk_5); auto crop = processor.ExecuteImageAffineProcessing(trans, FACE_CROP_SIZE, FACE_CROP_SIZE); diff --git a/cpp-package/inspireface/cpp/inspireface/recognition_module/face_feature_extraction_module.h b/cpp-package/inspireface/cpp/inspireface/recognition_module/face_feature_extraction_module.h index 2220f7b..709205d 100644 --- a/cpp-package/inspireface/cpp/inspireface/recognition_module/face_feature_extraction_module.h +++ b/cpp-package/inspireface/cpp/inspireface/recognition_module/face_feature_extraction_module.h @@ -8,9 +8,9 @@ #include #include "extract/extract_adapt.h" #include "common/face_info/face_object_internal.h" -#include "common/face_data/face_data_type.h" +#include "face_warpper.h" #include "middleware/model_archive/inspire_archive.h" -#include "middleware/inspirecv_image_process.h" +#include "frame_process.h" namespace inspire { @@ -35,23 +35,40 @@ public: /** * @brief Extracts a facial feature from an image and stores it in the provided 'embedded'. * - * @param processor inspirecv::InspireImageProcess instance containing the image. + * @param processor inspirecv::FrameProcess instance containing the image. * @param face FaceObject representing the detected face. * @param embedded Output parameter to store the extracted facial feature. * @return int32_t Status code indicating success (0) or failure. */ - int32_t FaceExtract(inspirecv::InspireImageProcess &processor, const FaceObjectInternal &face, Embedded &embedded, float &norm, - bool normalize = false); + int32_t FaceExtract(inspirecv::FrameProcess &processor, const FaceObjectInternal &face, Embedded &embedded, float &norm, bool normalize = false); /** * @brief Extracts a facial feature from an image and stores it in the provided 'embedded'. * - * @param processor inspirecv::InspireImageProcess instance containing the image. - * @param face HyperFaceData representing the detected face. + * @param processor inspirecv::FrameProcess instance containing the image. + * @param face FaceTrackWrap representing the detected face. * @param embedded Output parameter to store the extracted facial feature. * @return int32_t Status code indicating success (0) or failure. */ - int32_t FaceExtract(inspirecv::InspireImageProcess &processor, const HyperFaceData &face, Embedded &embedded, float &norm, bool normalize = true); + int32_t FaceExtract(inspirecv::FrameProcess &processor, const FaceTrackWrap &face, Embedded &embedded, float &norm, bool normalize = true); + + /** + * @brief Extracts a facial feature from an image and stores it in the provided 'embedded'. + * + * @param processor inspirecv::FrameProcess instance containing the image. + * @param embedded Output parameter to store the extracted facial feature. + * @return int32_t Status code indicating success (0) or failure. + */ + int32_t FaceExtractWithAlignmentImage(inspirecv::FrameProcess &processor, Embedded &embedding, float &norm, bool normalize = true); + + /** + * @brief Extracts a facial feature from an image and stores it in the provided 'embedding'. + * + * @param wrapped inspirecv::Image instance containing the image. + * @param embedding Output parameter to store the extracted facial feature. + * @return int32_t Status code indicating success (0) or failure. + */ + int32_t FaceExtractWithAlignmentImage(const inspirecv::Image& wrapped, Embedded &embedding, float &norm, bool normalize = true); /** * @brief Gets the Extract instance associated with this FaceRecognition. @@ -71,6 +88,7 @@ private: private: std::shared_ptr m_extract_; ///< Pointer to the Extract instance. + std::shared_ptr m_landmark_param_; ///< Pointer to the LandmarkParam instance. int32_t m_status_code_; ///< Status code }; diff --git a/cpp-package/inspireface/cpp/inspireface/runtime_module/launch.cpp b/cpp-package/inspireface/cpp/inspireface/runtime_module/launch.cpp new file mode 100644 index 0000000..be24903 --- /dev/null +++ b/cpp-package/inspireface/cpp/inspireface/runtime_module/launch.cpp @@ -0,0 +1,294 @@ +/** + * Created by Jingyu Yan + * @date 2024-10-01 + */ + +#include "launch.h" +#include "log.h" +#include "herror.h" +#include "isf_check.h" + +// Include the implementation details here, hidden from public header +#include "middleware/model_archive/inspire_archive.h" +#if defined(ISF_ENABLE_RGA) +#include "image_process/nexus_processor/rga/dma_alloc.h" +#endif +#include +#include "middleware/inference_wrapper/inference_wrapper.h" +#include "middleware/system.h" +#if defined(ISF_ENABLE_TENSORRT) +#include "cuda_toolkit.h" +#endif + +#define APPLE_EXTENSION_SUFFIX ".bundle" + +namespace inspire { + +// Implementation class definition +class Launch::Impl { +public: + Impl() : m_load_(false), m_archive_(nullptr), m_cuda_device_id_(0), m_global_coreml_inference_mode_(InferenceWrapper::COREML_ANE) { +#if defined(ISF_ENABLE_RGA) +#if defined(ISF_RKNPU_RV1106) + m_rockchip_dma_heap_path_ = RV1106_CMA_HEAP_PATH; +#else + m_rockchip_dma_heap_path_ = DMA_HEAP_DMA32_UNCACHE_PATCH; +#endif + INSPIRE_LOGW("Rockchip dma heap configured path: %s", m_rockchip_dma_heap_path_.c_str()); +#endif + m_face_detect_pixel_list_ = {160, 320, 640}; + m_face_detect_model_list_ = {"face_detect_160", "face_detect_320", "face_detect_640"}; + } + // Face Detection pixel size + std::vector m_face_detect_pixel_list_; + + // Face Detection model list + std::vector m_face_detect_model_list_; + + // Static members + static std::mutex mutex_; + static std::shared_ptr instance_; + + // Data members + std::string m_rockchip_dma_heap_path_; + std::string m_extension_path_; + std::unique_ptr m_archive_; + bool m_load_; + int32_t m_cuda_device_id_; + InferenceWrapper::SpecialBackend m_global_coreml_inference_mode_; +}; + +// Initialize static members +std::mutex Launch::Impl::mutex_; +std::shared_ptr Launch::Impl::instance_ = nullptr; + +// Constructor implementation +Launch::Launch() : pImpl(std::make_unique()) {} + +// Destructor implementation +Launch::~Launch() = default; + +std::shared_ptr Launch::GetInstance() { + std::lock_guard lock(Impl::mutex_); + if (!Impl::instance_) { + Impl::instance_ = std::shared_ptr(new Launch()); + } + return Impl::instance_; +} + +InspireArchive& Launch::getMArchive() { + std::lock_guard lock(pImpl->mutex_); + if (!pImpl->m_archive_) { + throw std::runtime_error("Archive not initialized"); + } + return *(pImpl->m_archive_); +} + +int32_t Launch::Load(const std::string& path) { + std::lock_guard lock(pImpl->mutex_); +#if defined(ISF_ENABLE_TENSORRT) + int32_t support_cuda; + auto ret = CheckCudaUsability(&support_cuda); + if (ret != HSUCCEED) { + INSPIRE_LOGE("An error occurred while checking CUDA device support. Please ensure that your environment supports CUDA!"); + return ret; + } + if (!support_cuda) { + INSPIRE_LOGE("Your environment does not support CUDA! Please ensure that your environment supports CUDA!"); + return HERR_DEVICE_CUDA_NOT_SUPPORT; + } +#endif + INSPIREFACE_CHECK_MSG(os::IsExists(path), "The package path does not exist because the launch failed."); +#if defined(ISF_ENABLE_APPLE_EXTENSION) + BuildAppleExtensionPath(path); +#endif + if (!pImpl->m_load_) { + try { + pImpl->m_archive_ = std::make_unique(); + pImpl->m_archive_->ReLoad(path); + + // Update face detect pixel list and model list + pImpl->m_face_detect_pixel_list_ = pImpl->m_archive_->GetFaceDetectPixelList(); + pImpl->m_face_detect_model_list_ = pImpl->m_archive_->GetFaceDetectModelList(); + + if (pImpl->m_archive_->QueryStatus() == SARC_SUCCESS) { + pImpl->m_load_ = true; + INSPIRE_LOGI("Successfully loaded resources"); + return HSUCCEED; + } else { + pImpl->m_archive_.reset(); + INSPIRE_LOGE("Failed to load resources"); + return HERR_ARCHIVE_LOAD_MODEL_FAILURE; + } + } catch (const std::exception& e) { + pImpl->m_archive_.reset(); + INSPIRE_LOGE("Exception during resource loading: %s", e.what()); + return HERR_ARCHIVE_LOAD_MODEL_FAILURE; + } + } else { + INSPIRE_LOGW("There is no need to call launch more than once, as subsequent calls will not affect the initialization."); + return HSUCCEED; + } +} + +int32_t Launch::Reload(const std::string& path) { + std::lock_guard lock(pImpl->mutex_); + INSPIREFACE_CHECK_MSG(os::IsExists(path), "The package path does not exist because the launch failed."); +#if defined(ISF_ENABLE_APPLE_EXTENSION) + BuildAppleExtensionPath(path); +#endif + try { + // Clean up existing archive if it exists + if (pImpl->m_archive_) { + pImpl->m_archive_.reset(); + pImpl->m_load_ = false; + } + + // Create and load new archive + pImpl->m_archive_ = std::make_unique(); + pImpl->m_archive_->ReLoad(path); + + if (pImpl->m_archive_->QueryStatus() == SARC_SUCCESS) { + pImpl->m_load_ = true; + INSPIRE_LOGI("Successfully reloaded resources"); + return HSUCCEED; + } else { + pImpl->m_archive_.reset(); + INSPIRE_LOGE("Failed to reload resources"); + return HERR_ARCHIVE_LOAD_MODEL_FAILURE; + } + } catch (const std::exception& e) { + pImpl->m_archive_.reset(); + INSPIRE_LOGE("Exception during resource reloading: %s", e.what()); + return HERR_ARCHIVE_LOAD_MODEL_FAILURE; + } +} + +bool Launch::isMLoad() const { + return pImpl->m_load_; +} + +void Launch::Unload() { + std::lock_guard lock(pImpl->mutex_); + if (pImpl->m_load_) { + pImpl->m_archive_.reset(); + pImpl->m_load_ = false; + INSPIRE_LOGI("All resources have been successfully unloaded and system is reset."); + } else { + INSPIRE_LOGW("Unload called but system was not loaded."); + } +} + +void Launch::SetRockchipDmaHeapPath(const std::string& path) { + std::lock_guard lock(pImpl->mutex_); + pImpl->m_rockchip_dma_heap_path_ = path; +} + +std::string Launch::GetRockchipDmaHeapPath() const { + std::lock_guard lock(pImpl->mutex_); + return pImpl->m_rockchip_dma_heap_path_; +} + +void Launch::ConfigurationExtensionPath(const std::string& path) { +#if defined(ISF_ENABLE_APPLE_EXTENSION) + INSPIREFACE_CHECK_MSG(os::IsDir(path), "The apple extension path is not a directory, please check."); +#endif + INSPIREFACE_CHECK_MSG(os::IsExists(path), "The extension path is not exists, please check."); + pImpl->m_extension_path_ = path; +} + +std::string Launch::GetExtensionPath() const { + std::lock_guard lock(pImpl->mutex_); + return pImpl->m_extension_path_; +} + +void Launch::SetGlobalCoreMLInferenceMode(NNInferenceBackend mode) { + std::lock_guard lock(pImpl->mutex_); + if (mode == NN_INFERENCE_CPU) { + pImpl->m_global_coreml_inference_mode_ = InferenceWrapper::COREML_CPU; + } else if (mode == NN_INFERENCE_COREML_GPU) { + pImpl->m_global_coreml_inference_mode_ = InferenceWrapper::COREML_GPU; + } else if (mode == NN_INFERENCE_COREML_ANE) { + pImpl->m_global_coreml_inference_mode_ = InferenceWrapper::COREML_ANE; + } else { + INSPIRE_LOGE("Invalid CoreML inference mode"); + } + if (pImpl->m_global_coreml_inference_mode_ == InferenceWrapper::COREML_CPU) { + INSPIRE_LOGW("Global CoreML Compute Units set to CPU Only."); + } else if (pImpl->m_global_coreml_inference_mode_ == InferenceWrapper::COREML_GPU) { + INSPIRE_LOGW("Global CoreML Compute Units set to CPU and GPU."); + } else if (pImpl->m_global_coreml_inference_mode_ == InferenceWrapper::COREML_ANE) { + INSPIRE_LOGW("Global CoreML Compute Units set to Auto Switch (ANE, GPU, CPU)."); + } +} + +Launch::NNInferenceBackend Launch::GetGlobalCoreMLInferenceMode() const { + std::lock_guard lock(pImpl->mutex_); + if (pImpl->m_global_coreml_inference_mode_ == InferenceWrapper::COREML_CPU) { + return NN_INFERENCE_CPU; + } else if (pImpl->m_global_coreml_inference_mode_ == InferenceWrapper::COREML_GPU) { + return NN_INFERENCE_COREML_GPU; + } else if (pImpl->m_global_coreml_inference_mode_ == InferenceWrapper::COREML_ANE) { + return NN_INFERENCE_COREML_ANE; + } else { + INSPIRE_LOGE("Invalid CoreML inference mode"); + return NN_INFERENCE_CPU; + } +} + +void Launch::BuildAppleExtensionPath(const std::string& resource_path) { + std::string basename = os::Basename(resource_path); + pImpl->m_extension_path_ = os::PathJoin(os::Dirname(resource_path), basename + APPLE_EXTENSION_SUFFIX); + INSPIREFACE_CHECK_MSG(os::IsExists(pImpl->m_extension_path_), "The apple extension path is not exists, please check."); + INSPIREFACE_CHECK_MSG(os::IsDir(pImpl->m_extension_path_), "The apple extension path is not a directory, please check."); +} + +void Launch::SetCudaDeviceId(int32_t device_id) { + std::lock_guard lock(pImpl->mutex_); + pImpl->m_cuda_device_id_ = device_id; +} + +int32_t Launch::GetCudaDeviceId() const { + std::lock_guard lock(pImpl->mutex_); + return pImpl->m_cuda_device_id_; +} + +void Launch::SetFaceDetectPixelList(const std::vector& pixel_list) { + std::lock_guard lock(pImpl->mutex_); + pImpl->m_face_detect_pixel_list_ = pixel_list; +} + +std::vector Launch::GetFaceDetectPixelList() const { + std::lock_guard lock(pImpl->mutex_); + return pImpl->m_face_detect_pixel_list_; +} + +void Launch::SetFaceDetectModelList(const std::vector& model_list) { + std::lock_guard lock(pImpl->mutex_); + pImpl->m_face_detect_model_list_ = model_list; +} + +std::vector Launch::GetFaceDetectModelList() const { + std::lock_guard lock(pImpl->mutex_); + return pImpl->m_face_detect_model_list_; +} + +void Launch::SwitchLandmarkEngine(LandmarkEngine engine) { + std::lock_guard lock(pImpl->mutex_); + if (pImpl->m_archive_->QueryStatus() != SARC_SUCCESS) { + INSPIRE_LOGE("The InspireFace is not initialized, please call launch first."); + return; + } + auto landmark_param = pImpl->m_archive_->GetLandmarkParam(); + bool ret = false; + if (engine == LANDMARK_HYPLMV2_0_25) { + ret = landmark_param->ReLoad("landmark"); + } else if (engine == LANDMARK_HYPLMV2_0_50) { + ret = landmark_param->ReLoad("landmark_0_50"); + } else if (engine == LANDMARK_INSIGHTFACE_2D106_TRACK) { + ret = landmark_param->ReLoad("landmark_insightface_2d106"); + } + INSPIREFACE_CHECK_MSG(ret, "Failed to switch landmark engine"); +} + +} // namespace inspire \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/inspireface/Initialization_module/resource_manage.cpp b/cpp-package/inspireface/cpp/inspireface/runtime_module/resource_manage.cpp similarity index 100% rename from cpp-package/inspireface/cpp/inspireface/Initialization_module/resource_manage.cpp rename to cpp-package/inspireface/cpp/inspireface/runtime_module/resource_manage.cpp diff --git a/cpp-package/inspireface/cpp/inspireface/Initialization_module/resource_manage.h b/cpp-package/inspireface/cpp/inspireface/runtime_module/resource_manage.h similarity index 77% rename from cpp-package/inspireface/cpp/inspireface/Initialization_module/resource_manage.h rename to cpp-package/inspireface/cpp/inspireface/runtime_module/resource_manage.h index 7915c27..0897ebe 100644 --- a/cpp-package/inspireface/cpp/inspireface/Initialization_module/resource_manage.h +++ b/cpp-package/inspireface/cpp/inspireface/runtime_module/resource_manage.h @@ -35,6 +35,7 @@ private: std::unordered_map sessionMap; std::unordered_map streamMap; std::unordered_map imageBitmapMap; + std::unordered_map faceFeatureMap; // The private constructor guarantees singletons ResourceManager() {} @@ -106,6 +107,23 @@ public: return false; // Release failed, possibly because the handle could not be found or was released } + // Create and record face features + void createFaceFeature(long handle) { + std::lock_guard lock(mutex); + faceFeatureMap[handle] = false; // false indicates that it is not released + } + + // Release face feature + bool releaseFaceFeature(long handle) { + std::lock_guard lock(mutex); + auto it = faceFeatureMap.find(handle); + if (it != faceFeatureMap.end() && !it->second) { + it->second = true; // Mark as released + return true; + } + return false; // Release failed, possibly because the handle could not be found or was released + } + // Gets a list of unreleased session handles std::vector getUnreleasedSessions() { std::lock_guard lock(mutex); @@ -142,10 +160,24 @@ public: return unreleasedImageBitmaps; } + // Gets a list of unreleased face feature handles + std::vector getUnreleasedFaceFeatures() { + std::lock_guard lock(mutex); + std::vector unreleasedFaceFeatures; + for (const auto& entry : faceFeatureMap) { + if (!entry.second) { + unreleasedFaceFeatures.push_back(entry.first); + } + } + return unreleasedFaceFeatures; + } + // Method to print resource management statistics void printResourceStatistics() { std::lock_guard lock(mutex); + INSPIRE_LOGI("================================================================"); INSPIRE_LOGI("%-15s%-15s%-15s%-15s", "Resource Name", "Total Created", "Total Released", "Not Released"); + INSPIRE_LOGI("----------------------------------------------------------------"); // Print session statistics int totalSessionsCreated = sessionMap.size(); @@ -182,6 +214,19 @@ public: ++bitmapsNotReleased; } INSPIRE_LOGI("%-15s%-15d%-15d%-15d", "Bitmap", totalBitmapsCreated, totalBitmapsReleased, bitmapsNotReleased); + + // Print face feature statistics + int totalFeaturesCreated = faceFeatureMap.size(); + int totalFeaturesReleased = 0; + int featuresNotReleased = 0; + for (const auto& entry : faceFeatureMap) { + if (entry.second) + ++totalFeaturesReleased; + if (!entry.second) + ++featuresNotReleased; + } + INSPIRE_LOGI("%-15s%-15d%-15d%-15d", "FaceFeature", totalFeaturesCreated, totalFeaturesReleased, featuresNotReleased); + INSPIRE_LOGI("================================================================"); } }; diff --git a/cpp-package/inspireface/cpp/inspireface/session.cpp b/cpp-package/inspireface/cpp/inspireface/session.cpp new file mode 100644 index 0000000..9b2c369 --- /dev/null +++ b/cpp-package/inspireface/cpp/inspireface/session.cpp @@ -0,0 +1,277 @@ +#include +#include "session.h" +#include "engine/face_session.h" +#include "recognition_module/dest_const.h" + +namespace inspire { + +class Session::Impl { +public: + Impl() : m_face_session_(std::make_unique()) {} + + int32_t Configure(DetectModuleMode detect_mode, int32_t max_detect_face, CustomPipelineParameter param, int32_t detect_level_px, + int32_t track_by_detect_mode_fps) { + return m_face_session_->Configuration(detect_mode, max_detect_face, param, detect_level_px, track_by_detect_mode_fps); + } + + ~Impl() = default; + + void SetTrackPreviewSize(int32_t preview_size) { + m_face_session_->SetTrackPreviewSize(preview_size); + } + + void SetFilterMinimumFacePixelSize(int32_t min_face_pixel_size) { + m_face_session_->SetTrackFaceMinimumSize(min_face_pixel_size); + } + + void SetFaceDetectThreshold(float threshold) { + m_face_session_->SetFaceDetectThreshold(threshold); + } + + void SetTrackModeSmoothRatio(int32_t smooth_ratio) { + m_face_session_->SetTrackModeSmoothRatio(smooth_ratio); + } + + void SetTrackModeNumSmoothCacheFrame(int32_t num_smooth_cache_frame) { + m_face_session_->SetTrackModeNumSmoothCacheFrame(num_smooth_cache_frame); + } + + void SetTrackModeDetectInterval(int32_t detect_interval) { + m_face_session_->SetTrackModeDetectInterval(detect_interval); + } + + int32_t FaceDetectAndTrack(inspirecv::FrameProcess& process, std::vector& results) { + int32_t ret = m_face_session_->FaceDetectAndTrack(process); + if (ret < 0) { + return ret; + } + results.clear(); + const auto& face_data = m_face_session_->GetDetectCache(); + for (const auto& data : face_data) { + FaceTrackWrap hyper_face_data; + RunDeserializeHyperFaceData(data, hyper_face_data); + results.emplace_back(hyper_face_data); + } + + return ret; + } + + inspirecv::Rect2i GetFaceBoundingBox(const FaceTrackWrap& face_data) { + return inspirecv::Rect2i{face_data.rect.x, face_data.rect.y, face_data.rect.width, face_data.rect.height}; + } + + std::vector GetFaceDenseLandmark(const FaceTrackWrap& face_data) { + std::vector points; + for (const auto& p : face_data.densityLandmark) { + points.emplace_back(inspirecv::Point2f(p.x, p.y)); + } + return points; + } + + std::vector GetFaceFiveKeyPoints(const FaceTrackWrap& face_data) { + std::vector points; + for (const auto& p : face_data.keyPoints) { + points.emplace_back(inspirecv::Point2f(p.x, p.y)); + } + return points; + } + + int32_t FaceFeatureExtract(inspirecv::FrameProcess& process, FaceTrackWrap& data, FaceEmbedding& embedding, bool normalize) { + int32_t ret = m_face_session_->FaceFeatureExtract(process, data, normalize); + if (ret < 0) { + return ret; + } + embedding.isNormal = normalize; + embedding.embedding = m_face_session_->GetFaceFeatureCache(); + embedding.norm = m_face_session_->GetFaceFeatureNormCache(); + + return ret; + } + + int32_t FaceFeatureExtractWithAlignmentImage(inspirecv::FrameProcess& process, FaceEmbedding& embedding, bool normalize) { + int32_t ret = m_face_session_->FaceFeatureExtractWithAlignmentImage(process, embedding.embedding, embedding.norm, normalize); + if (ret < 0) { + return ret; + } + embedding.isNormal = normalize; + embedding.norm = embedding.norm; + return ret; + } + + int32_t FaceFeatureExtractWithAlignmentImage(const inspirecv::Image& wrapped, FaceEmbedding& embedding, bool normalize) { + int32_t ret = m_face_session_->FaceFeatureExtractWithAlignmentImage(wrapped, embedding, embedding.norm, normalize); + if (ret < 0) { + return ret; + } + embedding.isNormal = normalize; + embedding.norm = embedding.norm; + return ret; + } + + void GetFaceAlignmentImage(inspirecv::FrameProcess& process, FaceTrackWrap& data, inspirecv::Image& wrapped) { + std::vector pointsFive; + for (const auto& p : data.keyPoints) { + pointsFive.push_back(inspirecv::Point2f(p.x, p.y)); + } + auto trans = inspirecv::SimilarityTransformEstimateUmeyama(SIMILARITY_TRANSFORM_DEST, pointsFive); + wrapped = process.ExecuteImageAffineProcessing(trans, FACE_CROP_SIZE, FACE_CROP_SIZE); + } + + int32_t MultipleFacePipelineProcess(inspirecv::FrameProcess& process, const CustomPipelineParameter& param, + const std::vector& face_data_list) { + int32_t ret = m_face_session_->FacesProcess(process, face_data_list, param); + return ret; + } + + std::vector GetRGBLivenessConfidence() { + return m_face_session_->GetDetConfidenceCache(); + } + + std::vector GetFaceMaskConfidence() { + return m_face_session_->GetMaskResultsCache(); + } + + std::vector GetFaceQualityConfidence() { + return m_face_session_->GetFaceQualityScoresResultsCache(); + } + + std::vector GetFaceInteractionState() { + auto left_eyes_confidence = m_face_session_->GetFaceInteractionLeftEyeStatusCache(); + auto right_eyes_confidence = m_face_session_->GetFaceInteractionRightEyeStatusCache(); + std::vector face_interaction_state; + for (size_t i = 0; i < left_eyes_confidence.size(); ++i) { + face_interaction_state.emplace_back(FaceInteractionState{left_eyes_confidence[i], right_eyes_confidence[i]}); + } + return face_interaction_state; + } + + std::vector GetFaceInteractionAction() { + auto num = m_face_session_->GetFaceNormalAactionsResultCache().size(); + std::vector face_interaction_action; + face_interaction_action.resize(num); + for (size_t i = 0; i < num; ++i) { + face_interaction_action[i].normal = m_face_session_->GetFaceNormalAactionsResultCache()[i]; + face_interaction_action[i].shake = m_face_session_->GetFaceShakeAactionsResultCache()[i]; + face_interaction_action[i].jawOpen = m_face_session_->GetFaceJawOpenAactionsResultCache()[i]; + face_interaction_action[i].headRaise = m_face_session_->GetFaceRaiseHeadAactionsResultCache()[i]; + face_interaction_action[i].blink = m_face_session_->GetFaceBlinkAactionsResultCache()[i]; + } + return face_interaction_action; + } + + std::vector GetFaceAttributeResult() { + auto num = m_face_session_->GetFaceNormalAactionsResultCache().size(); + std::vector face_attribute_result; + face_attribute_result.resize(num); + for (size_t i = 0; i < num; ++i) { + face_attribute_result[i].race = m_face_session_->GetFaceRaceResultsCache()[i]; + face_attribute_result[i].gender = m_face_session_->GetFaceGenderResultsCache()[i]; + face_attribute_result[i].ageBracket = m_face_session_->GetFaceAgeBracketResultsCache()[i]; + } + return face_attribute_result; + } + + std::unique_ptr m_face_session_; +}; + +Session::Session() : pImpl(std::make_unique()) {} + +Session::~Session() = default; + +Session::Session(Session&&) noexcept = default; + +Session& Session::operator=(Session&&) noexcept = default; + +Session Session::Create(DetectModuleMode detect_mode, int32_t max_detect_face, const CustomPipelineParameter& param, int32_t detect_level_px, + int32_t track_by_detect_mode_fps) { + Session session; + session.pImpl->Configure(detect_mode, max_detect_face, param, detect_level_px, track_by_detect_mode_fps); + return session; +} + +void Session::SetTrackPreviewSize(int32_t preview_size) { + pImpl->SetTrackPreviewSize(preview_size); +} + +void Session::SetFilterMinimumFacePixelSize(int32_t min_face_pixel_size) { + pImpl->SetFilterMinimumFacePixelSize(min_face_pixel_size); +} + +void Session::SetFaceDetectThreshold(float threshold) { + pImpl->SetFaceDetectThreshold(threshold); +} + +void Session::SetTrackModeSmoothRatio(int32_t smooth_ratio) { + pImpl->SetTrackModeSmoothRatio(smooth_ratio); +} + +void Session::SetTrackModeNumSmoothCacheFrame(int32_t num_smooth_cache_frame) { + pImpl->SetTrackModeNumSmoothCacheFrame(num_smooth_cache_frame); +} + +void Session::SetTrackModeDetectInterval(int32_t detect_interval) { + pImpl->SetTrackModeDetectInterval(detect_interval); +} + +int32_t Session::FaceDetectAndTrack(inspirecv::FrameProcess& process, std::vector& results) { + return pImpl->FaceDetectAndTrack(process, results); +} + +inspirecv::Rect2i Session::GetFaceBoundingBox(const FaceTrackWrap& face_data) { + return pImpl->GetFaceBoundingBox(face_data); +} + +std::vector Session::GetFaceDenseLandmark(const FaceTrackWrap& face_data) { + return pImpl->GetFaceDenseLandmark(face_data); +} + +std::vector Session::GetFaceFiveKeyPoints(const FaceTrackWrap& face_data) { + return pImpl->GetFaceFiveKeyPoints(face_data); +} + +int32_t Session::FaceFeatureExtract(inspirecv::FrameProcess& process, FaceTrackWrap& data, FaceEmbedding& embedding, bool normalize) { + return pImpl->FaceFeatureExtract(process, data, embedding, normalize); +} + +int32_t Session::FaceFeatureExtractWithAlignmentImage(inspirecv::FrameProcess& process, FaceEmbedding& embedding, bool normalize) { + return pImpl->FaceFeatureExtractWithAlignmentImage(process, embedding, normalize); +} + +int32_t Session::FaceFeatureExtractWithAlignmentImage(const inspirecv::Image& wrapped, FaceEmbedding& embedding, bool normalize) { + return pImpl->FaceFeatureExtractWithAlignmentImage(wrapped, embedding, normalize); +} + +void Session::GetFaceAlignmentImage(inspirecv::FrameProcess& process, FaceTrackWrap& data, inspirecv::Image& wrapped) { + pImpl->GetFaceAlignmentImage(process, data, wrapped); +} + +int32_t Session::MultipleFacePipelineProcess(inspirecv::FrameProcess& process, const CustomPipelineParameter& param, + const std::vector& face_data_list) { + return pImpl->MultipleFacePipelineProcess(process, param, face_data_list); +} + +std::vector Session::GetRGBLivenessConfidence() { + return pImpl->GetRGBLivenessConfidence(); +} + +std::vector Session::GetFaceMaskConfidence() { + return pImpl->GetFaceMaskConfidence(); +} + +std::vector Session::GetFaceQualityConfidence() { + return pImpl->GetFaceQualityConfidence(); +} + +std::vector Session::GetFaceInteractionState() { + return pImpl->GetFaceInteractionState(); +} + +std::vector Session::GetFaceInteractionAction() { + return pImpl->GetFaceInteractionAction(); +} + +std::vector Session::GetFaceAttributeResult() { + return pImpl->GetFaceAttributeResult(); +} + +} // namespace inspire diff --git a/cpp-package/inspireface/cpp/inspireface/track_module/face_detect/face_detect_adapt.cpp b/cpp-package/inspireface/cpp/inspireface/track_module/face_detect/face_detect_adapt.cpp index 1d98c00..18b618a 100644 --- a/cpp-package/inspireface/cpp/inspireface/track_module/face_detect/face_detect_adapt.cpp +++ b/cpp-package/inspireface/cpp/inspireface/track_module/face_detect/face_detect_adapt.cpp @@ -5,7 +5,7 @@ #include "face_detect_adapt.h" #include "cost_time.h" -#include +#include "spend_timer.h" namespace inspire { @@ -13,7 +13,7 @@ FaceDetectAdapt::FaceDetectAdapt(int input_size, float nms_threshold, float cls_ : AnyNetAdapter("FaceDetectAdapt"), m_nms_threshold_(nms_threshold), m_cls_threshold_(cls_threshold), m_input_size_(input_size) {} FaceLocList FaceDetectAdapt::operator()(const inspirecv::Image &bgr) { - inspirecv::TimeSpend time_image_process("Image process"); + inspire::SpendTimer time_image_process("Image process"); time_image_process.Start(); int ori_w = bgr.Width(); int ori_h = bgr.Height(); @@ -31,14 +31,14 @@ FaceLocList FaceDetectAdapt::operator()(const inspirecv::Image &bgr) { // pad.Write("pad.jpg"); // LOGD("Prepare"); AnyTensorOutputs outputs; - inspirecv::TimeSpend time_forward("Forward"); + inspire::SpendTimer time_forward("Forward"); time_forward.Start(); Forward(pad, outputs); time_forward.Stop(); // std::cout << time_forward << std::endl; // LOGD("Forward"); - inspirecv::TimeSpend time_decode("Decode"); + inspire::SpendTimer time_decode("Decode"); time_decode.Start(); std::vector results; std::vector strides = {8, 16, 32}; @@ -154,4 +154,8 @@ bool SortBoxSizeAdapt(const FaceLoc &a, const FaceLoc &b) { return sq_a > sq_b; } +int FaceDetectAdapt::GetInputSize() const { + return m_input_size_; +} + } // namespace inspire \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/inspireface/track_module/face_detect/face_detect_adapt.h b/cpp-package/inspireface/cpp/inspireface/track_module/face_detect/face_detect_adapt.h index ebe9dbc..6c8d91c 100644 --- a/cpp-package/inspireface/cpp/inspireface/track_module/face_detect/face_detect_adapt.h +++ b/cpp-package/inspireface/cpp/inspireface/track_module/face_detect/face_detect_adapt.h @@ -6,9 +6,9 @@ #pragma once #ifndef INSPIRE_FACE_TRACK_MODULE_FACE_DETECT_FACE_DETECT_ADAPT_H #define INSPIRE_FACE_TRACK_MODULE_FACE_DETECT_FACE_DETECT_ADAPT_H -#include "../../data_type.h" +#include "data_type.h" #include "middleware/any_net_adapter.h" -#include "middleware/nexus_processor/image_processor.h" +#include "image_process/nexus_processor/image_processor.h" namespace inspire { @@ -41,6 +41,12 @@ public: /** @brief Set face classification threshold */ void SetClsThreshold(float mClsThreshold); + /** + * @brief Get the input size + * @return int The input size + */ + int GetInputSize() const; + private: /** * @brief Applies non-maximum suppression to reduce overlapping detected faces. diff --git a/cpp-package/inspireface/cpp/inspireface/track_module/face_detect/rnet_adapt.h b/cpp-package/inspireface/cpp/inspireface/track_module/face_detect/rnet_adapt.h index ad33cde..efe3cad 100644 --- a/cpp-package/inspireface/cpp/inspireface/track_module/face_detect/rnet_adapt.h +++ b/cpp-package/inspireface/cpp/inspireface/track_module/face_detect/rnet_adapt.h @@ -5,7 +5,7 @@ #pragma once #ifndef INSPIRE_FACE_TRACK_MODULE_FACE_DETECT_RNET_ADAPT_H #define INSPIRE_FACE_TRACK_MODULE_FACE_DETECT_RNET_ADAPT_H -#include "../../data_type.h" +#include "data_type.h" #include "middleware/any_net_adapter.h" namespace inspire { diff --git a/cpp-package/inspireface/cpp/inspireface/track_module/face_track_module.cpp b/cpp-package/inspireface/cpp/inspireface/track_module/face_track_module.cpp index 33f3b19..565be6e 100644 --- a/cpp-package/inspireface/cpp/inspireface/track_module/face_track_module.cpp +++ b/cpp-package/inspireface/cpp/inspireface/track_module/face_track_module.cpp @@ -5,7 +5,6 @@ #include "face_track_module.h" #include "log.h" -#include "landmark/mean_shape.h" #include #include #include "middleware/costman.h" @@ -14,7 +13,8 @@ #include "herror.h" #include "middleware/costman.h" #include "cost_time.h" -#include +#include "spend_timer.h" +#include "launch.h" namespace inspire { @@ -35,7 +35,8 @@ FaceTrackModule::FaceTrackModule(DetectModuleMode mode, int max_detected_faces, // In lightweight tracking mode, landmark detection is always required m_detect_mode_landmark_ = true; } else { - m_detect_mode_landmark_ = detect_mode_landmark; + // This version uses lmk106 to replace five key points, so lmk must be forcibly enabled! + m_detect_mode_landmark_ = true; } if (m_mode_ == DETECT_MODE_TRACK_BY_DETECT) { m_TbD_tracker_ = std::make_shared(TbD_mode_fps, 30); @@ -45,17 +46,20 @@ FaceTrackModule::FaceTrackModule(DetectModuleMode mode, int max_detected_faces, void FaceTrackModule::SparseLandmarkPredict(const inspirecv::Image &raw_face_crop, std::vector &landmarks_output, float &score, float size) { COST_TIME_SIMPLE(SparseLandmarkPredict); - landmarks_output.resize(FaceLandmarkAdapt::NUM_OF_LANDMARK); + landmarks_output.resize(m_landmark_param_->num_of_landmark); std::vector lmk_out = (*m_landmark_predictor_)(raw_face_crop); - for (int i = 0; i < FaceLandmarkAdapt::NUM_OF_LANDMARK; ++i) { + for (int i = 0; i < m_landmark_param_->num_of_landmark; ++i) { float x = lmk_out[i * 2 + 0] * size; float y = lmk_out[i * 2 + 1] * size; landmarks_output[i] = inspirecv::Point(x, y); } - score = (*m_refine_net_)(raw_face_crop); } -bool FaceTrackModule::TrackFace(inspirecv::InspireImageProcess &image, FaceObjectInternal &face) { +float FaceTrackModule::PredictTrackScore(const inspirecv::Image &raw_face_crop) { + return (*m_refine_net_)(raw_face_crop); +} + +bool FaceTrackModule::TrackFace(inspirecv::FrameProcess &image, FaceObjectInternal &face) { COST_TIME_SIMPLE(TrackFace); // If the face confidence level is below 0.1, disable tracking if (face.GetConfidence() < 0.1) { @@ -79,21 +83,13 @@ bool FaceTrackModule::TrackFace(inspirecv::InspireImageProcess &image, FaceObjec inspirecv::TransformMatrix rotation_mode_affine = image.GetAffineMatrix(); std::vector camera_pts = ApplyTransformToPoints(rect_pts, rotation_mode_affine); // camera_pts.erase(camera_pts.end() - 1); - std::vector dst_pts = {{0, 0}, {112, 0}, {112, 112}, {0, 112}}; + std::vector dst_pts = {{0, 0}, + {(float)m_landmark_param_->input_size, 0}, + {(float)m_landmark_param_->input_size, (float)m_landmark_param_->input_size}, + {0, (float)m_landmark_param_->input_size}}; affine = inspirecv::SimilarityTransformEstimate(camera_pts, dst_pts); face.setTransMatrix(affine); - std::vector dst_pts_extensive = {{0, 0}, - {(float)m_crop_extensive_size_, 0}, - {(float)m_crop_extensive_size_, (float)m_crop_extensive_size_}, - {0, (float)m_crop_extensive_size_}}; - // Add extensive rect - inspirecv::Rect2i extensive_rect = rect_square.Square(m_crop_extensive_ratio_); - auto extensive_rect_pts = extensive_rect.As().ToFourVertices(); - std::vector camera_pts_extensive = ApplyTransformToPoints(extensive_rect_pts, rotation_mode_affine); - inspirecv::TransformMatrix extensive_affine = inspirecv::SimilarityTransformEstimate(camera_pts_extensive, dst_pts); - face.setTransMatrixExtensive(extensive_affine); - if (!m_detect_mode_landmark_) { /*If landmark is not extracted, the detection frame of the preview image needs to be changed back to the coordinate system of the original image */ @@ -105,41 +101,72 @@ bool FaceTrackModule::TrackFace(inspirecv::InspireImageProcess &image, FaceObjec if (m_face_quality_ != nullptr) { COST_TIME_SIMPLE(FaceQuality); - auto affine_extensive = face.getTransMatrixExtensive(); - auto pre_crop = image.ExecuteImageAffineProcessing(affine_extensive, m_crop_extensive_size_, m_crop_extensive_size_); + auto affine_extensive = face.getTransMatrix(); + auto trans_e = ScaleAffineMatrixPreserveCenter(affine_extensive, m_crop_extensive_ratio_, m_landmark_param_->input_size); + auto pre_crop = image.ExecuteImageAffineProcessing(trans_e, m_landmark_param_->input_size, m_landmark_param_->input_size); auto res = (*m_face_quality_)(pre_crop); + // pre_crop.Show("pre_crop"); auto affine_extensive_inv = affine_extensive.GetInverse(); std::vector lmk_extensive = ApplyTransformToPoints(res.lmk, affine_extensive_inv); res.lmk = lmk_extensive; face.high_result = res; + } else { + // If face pose and quality model is not initialized, set the default value + FacePoseQualityAdaptResult empty_result; + empty_result.lmk = std::vector(5, inspirecv::Point2f(0, 0)); + empty_result.lmk_quality = std::vector(5, 2.0f); + empty_result.pitch = 0.0f; + empty_result.yaw = 0.0f; + empty_result.roll = 0.0f; + face.high_result = empty_result; } if (m_detect_mode_landmark_) { // If Landmark need to be extracted in detection mode, // Landmark must be detected when fast tracing is enabled affine = face.getTransMatrix(); - inspirecv::Image crop; - // Get the RGB image after affine transformation - crop = image.ExecuteImageAffineProcessing(affine, 112, 112); inspirecv::TransformMatrix affine_inv = affine.GetInverse(); - std::vector landmark_rawout; - std::vector bbox; + std::vector> multiscale_landmark_back; + + auto track_crop = image.ExecuteImageAffineProcessing(affine, m_landmark_param_->input_size, m_landmark_param_->input_size); + score = PredictTrackScore(track_crop); + // track_crop.Show("track_crop"); + + for (int i = 0; i < m_multiscale_landmark_scales_.size(); i++) { + inspirecv::Image crop; + // Get the RGB image after affine transformation + auto affine_scale = ScaleAffineMatrixPreserveCenter(affine, m_multiscale_landmark_scales_[i], m_landmark_param_->input_size); + crop = image.ExecuteImageAffineProcessing(affine_scale, m_landmark_param_->input_size, m_landmark_param_->input_size); + + std::vector lmk_predict; + + // Predicted sparse key point + SparseLandmarkPredict(crop, lmk_predict, score, m_landmark_param_->input_size); + + // Save the first scale landmark + if (i == 0) { + landmark_rawout = lmk_predict; + } + + std::vector lmk_back; + // Convert key points back to the original coordinate system + lmk_back.resize(lmk_predict.size()); + lmk_back = inspirecv::ApplyTransformToPoints(lmk_predict, affine_scale.GetInverse()); + + multiscale_landmark_back.push_back(lmk_back); + } + + landmark_back = MultiFrameLandmarkMean(multiscale_landmark_back); - Timer lmk_cost_time; - // Predicted sparse key point - SparseLandmarkPredict(crop, landmark_rawout, score, 112); // Extract 5 key points std::vector lmk_5 = { - landmark_rawout[FaceLandmarkAdapt::LEFT_EYE_CENTER], landmark_rawout[FaceLandmarkAdapt::RIGHT_EYE_CENTER], - landmark_rawout[FaceLandmarkAdapt::NOSE_CORNER], landmark_rawout[FaceLandmarkAdapt::MOUTH_LEFT_CORNER], - landmark_rawout[FaceLandmarkAdapt::MOUTH_RIGHT_CORNER]}; + landmark_rawout[m_landmark_param_->semantic_index.left_eye_center], landmark_rawout[m_landmark_param_->semantic_index.right_eye_center], + landmark_rawout[m_landmark_param_->semantic_index.nose_corner], landmark_rawout[m_landmark_param_->semantic_index.mouth_left_corner], + landmark_rawout[m_landmark_param_->semantic_index.mouth_right_corner]}; face.setAlignMeanSquareError(lmk_5); - // Convert key points back to the original coordinate system - landmark_back.resize(landmark_rawout.size()); - landmark_back = inspirecv::ApplyTransformToPoints(landmark_rawout, affine_inv); int MODE = 1; if (MODE > 0) { @@ -148,15 +175,16 @@ bool FaceTrackModule::TrackFace(inspirecv::InspireImageProcess &image, FaceObjec } else if (face.TrackingState() == ISF_READY || face.TrackingState() == ISF_TRACKING) { COST_TIME_SIMPLE(LandmarkBack); inspirecv::TransformMatrix trans_m; - inspirecv::TransformMatrix tmp = face.getTransMatrix(); - std::vector inside_points = landmark_rawout; - - std::vector mean_shape_(FaceLandmarkAdapt::NUM_OF_LANDMARK); - for (int k = 0; k < FaceLandmarkAdapt::NUM_OF_LANDMARK; k++) { - mean_shape_[k].SetX(mean_shape[k * 2]); - mean_shape_[k].SetY(mean_shape[k * 2 + 1]); + // inspirecv::TransformMatrix tmp = face.getTransMatrix(); + std::vector inside_points; + if (m_landmark_param_->input_size == 112) { + inside_points = landmark_rawout; + } else { + inside_points = LandmarkCropped(landmark_rawout); } + auto &mean_shape_ = m_landmark_param_->mean_shape_points; + auto _affine = inspirecv::SimilarityTransformEstimate(inside_points, mean_shape_); auto mid_inside_points = ApplyTransformToPoints(inside_points, _affine); inside_points = FixPointsMeanshape(mid_inside_points, mean_shape_); @@ -164,44 +192,19 @@ bool FaceTrackModule::TrackFace(inspirecv::InspireImageProcess &image, FaceObjec trans_m = inspirecv::SimilarityTransformEstimate(landmark_back, inside_points); face.setTransMatrix(trans_m); face.EnableTracking(); - - Timer extensive_cost_time; - // Add extensive rect - // Calculate center point of landmarks - inspirecv::Point2f center(0.0f, 0.0f); - for (const auto &pt : landmark_back) { - center.SetX(center.GetX() + pt.GetX()); - center.SetY(center.GetY() + pt.GetY()); - } - center.SetX(center.GetX() / landmark_back.size()); - center.SetY(center.GetY() / landmark_back.size()); - - // Create expanded points by scaling from center by 1.3 - std::vector lmk_back_rect = landmark_back; - for (auto &pt : lmk_back_rect) { - pt.SetX(center.GetX() + (pt.GetX() - center.GetX()) * m_crop_extensive_ratio_); - pt.SetY(center.GetY() + (pt.GetY() - center.GetY()) * m_crop_extensive_ratio_); - } - inspirecv::TransformMatrix extensive_affine = inspirecv::SimilarityTransformEstimate(lmk_back_rect, mid_inside_points); - face.setTransMatrixExtensive(extensive_affine); - // INSPIRE_LOGD("Extensive Affine Cost %f", extensive_cost_time.GetCostTimeUpdate()); } } - // Add five key points to landmark_back - for (int i = 0; i < 5; i++) { - landmark_back.push_back(face.high_result.lmk[i]); - } // Update face key points face.SetLandmark(landmark_back, true, true, m_track_mode_smooth_ratio_, m_track_mode_num_smooth_cache_frame_, - (FaceLandmarkAdapt::NUM_OF_LANDMARK + 10) * 2); + m_landmark_param_->num_of_landmark * 2); // Get the smoothed landmark auto &landmark_smooth = face.landmark_smooth_aux_.back(); // Update the face key points - face.high_result.lmk[0] = landmark_smooth[FaceLandmarkAdapt::NUM_OF_LANDMARK + 0]; - face.high_result.lmk[1] = landmark_smooth[FaceLandmarkAdapt::NUM_OF_LANDMARK + 1]; - face.high_result.lmk[2] = landmark_smooth[FaceLandmarkAdapt::NUM_OF_LANDMARK + 2]; - face.high_result.lmk[3] = landmark_smooth[FaceLandmarkAdapt::NUM_OF_LANDMARK + 3]; - face.high_result.lmk[4] = landmark_smooth[FaceLandmarkAdapt::NUM_OF_LANDMARK + 4]; + face.high_result.lmk[0] = landmark_smooth[m_landmark_param_->semantic_index.left_eye_center]; + face.high_result.lmk[1] = landmark_smooth[m_landmark_param_->semantic_index.right_eye_center]; + face.high_result.lmk[2] = landmark_smooth[m_landmark_param_->semantic_index.nose_corner]; + face.high_result.lmk[3] = landmark_smooth[m_landmark_param_->semantic_index.mouth_left_corner]; + face.high_result.lmk[4] = landmark_smooth[m_landmark_param_->semantic_index.mouth_right_corner]; } // If tracking status, update the confidence level @@ -212,17 +215,18 @@ bool FaceTrackModule::TrackFace(inspirecv::InspireImageProcess &image, FaceObjec return true; } -void FaceTrackModule::UpdateStream(inspirecv::InspireImageProcess &image) { - inspirecv::TimeSpend total("UpdateStream"); +void FaceTrackModule::UpdateStream(inspirecv::FrameProcess &image) { + inspire::SpendTimer total("UpdateStream"); total.Start(); COST_TIME_SIMPLE(FaceTrackUpdateStream); detection_index_ += 1; if (m_mode_ == DETECT_MODE_ALWAYS_DETECT || m_mode_ == DETECT_MODE_TRACK_BY_DETECT) trackingFace.clear(); - if (trackingFace.empty() || detection_index_ % detection_interval_ == 0 || m_mode_ == DETECT_MODE_ALWAYS_DETECT || + if (trackingFace.empty() || (detection_interval_ > 0 && detection_index_ % detection_interval_ == 0) || m_mode_ == DETECT_MODE_ALWAYS_DETECT || m_mode_ == DETECT_MODE_TRACK_BY_DETECT) { image.SetPreviewSize(track_preview_size_); inspirecv::Image image_detect = image.ExecutePreviewImageProcessing(true); + m_debug_preview_image_size_ = image_detect.Width(); nms(); for (auto const &face : trackingFace) { @@ -336,7 +340,7 @@ void FaceTrackModule::DetectFace(const inspirecv::Image &input, float scale) { tracking_idx_ = tracking_idx_ + 1; } - FaceObjectInternal faceinfo(tracking_idx_, bbox[i], FaceLandmarkAdapt::NUM_OF_LANDMARK + 10); + FaceObjectInternal faceinfo(tracking_idx_, bbox[i], m_landmark_param_->num_of_landmark + 10); faceinfo.detect_bbox_ = bbox[i]; faceinfo.SetConfidence(boxes[i].score); @@ -350,11 +354,12 @@ void FaceTrackModule::DetectFace(const inspirecv::Image &input, float scale) { } } -int FaceTrackModule::Configuration(inspire::InspireArchive &archive, const std::string &expansion_path) { +int FaceTrackModule::Configuration(inspire::InspireArchive &archive, const std::string &expansion_path, bool enable_face_pose_and_quality) { // Initialize the detection model + m_landmark_param_ = archive.GetLandmarkParam(); m_expansion_path_ = std::move(expansion_path); InspireModel detModel; - auto scheme = ChoiceMultiLevelDetectModel(m_dynamic_detection_input_level_); + auto scheme = ChoiceMultiLevelDetectModel(m_dynamic_detection_input_level_, track_preview_size_); auto ret = archive.LoadModel(scheme, detModel); if (ret != SARC_SUCCESS) { INSPIRE_LOGE("Load %s error: %d", scheme.c_str(), ret); @@ -364,9 +369,9 @@ int FaceTrackModule::Configuration(inspire::InspireArchive &archive, const std:: // Initialize the landmark model InspireModel lmkModel; - ret = archive.LoadModel("landmark", lmkModel); + ret = archive.LoadModel(m_landmark_param_->landmark_engine_name, lmkModel); if (ret != SARC_SUCCESS) { - INSPIRE_LOGE("Load %s error: %d", "landmark", ret); + INSPIRE_LOGE("Load %s error: %d", m_landmark_param_->landmark_engine_name.c_str(), ret); return HERR_ARCHIVE_LOAD_MODEL_FAILURE; } InitLandmarkModel(lmkModel); @@ -379,22 +384,25 @@ int FaceTrackModule::Configuration(inspire::InspireArchive &archive, const std:: return HERR_ARCHIVE_LOAD_MODEL_FAILURE; } InitRNetModel(rnetModel); - - // Initialize the pose quality model - InspireModel pquModel; - ret = archive.LoadModel("pose_quality", pquModel); - if (ret != SARC_SUCCESS) { - INSPIRE_LOGE("Load %s error: %d", "pose_quality", ret); - return HERR_ARCHIVE_LOAD_MODEL_FAILURE; + if (enable_face_pose_and_quality) { + // Initialize the pose quality model + InspireModel pquModel; + ret = archive.LoadModel("pose_quality", pquModel); + if (ret != SARC_SUCCESS) { + INSPIRE_LOGE("Load %s error: %d", "pose_quality", ret); + return HERR_ARCHIVE_LOAD_MODEL_FAILURE; + } + InitFacePoseAndQualityModel(pquModel); } - InitFacePoseModel(pquModel); - + m_landmark_crop_ratio_ = m_landmark_param_->expansion_scale; + m_multiscale_landmark_scales_ = GenerateCropScales(m_landmark_crop_ratio_, m_multiscale_landmark_loop_num_); return 0; } int FaceTrackModule::InitLandmarkModel(InspireModel &model) { - m_landmark_predictor_ = std::make_shared(112); - auto ret = m_landmark_predictor_->loadData(model, model.modelType); + m_landmark_predictor_ = + std::make_shared(m_landmark_param_->input_size, m_landmark_param_->normalization_mode == "CenterScaling"); + auto ret = m_landmark_predictor_->LoadData(model, model.modelType); if (ret != InferenceWrapper::WrapperOk) { return HERR_ARCHIVE_LOAD_FAILURE; } @@ -406,7 +414,7 @@ int FaceTrackModule::InitDetectModel(InspireModel &model) { input_size = model.Config().get>("input_size"); m_face_detector_ = std::make_shared(input_size[0]); - auto ret = m_face_detector_->loadData(model, model.modelType, false); + auto ret = m_face_detector_->LoadData(model, model.modelType, false); if (ret != InferenceWrapper::WrapperOk) { return HERR_ARCHIVE_LOAD_FAILURE; } @@ -415,16 +423,16 @@ int FaceTrackModule::InitDetectModel(InspireModel &model) { int FaceTrackModule::InitRNetModel(InspireModel &model) { m_refine_net_ = std::make_shared(); - auto ret = m_refine_net_->loadData(model, model.modelType); + auto ret = m_refine_net_->LoadData(model, model.modelType); if (ret != InferenceWrapper::WrapperOk) { return HERR_ARCHIVE_LOAD_FAILURE; } return HSUCCEED; } -int FaceTrackModule::InitFacePoseModel(InspireModel &model) { +int FaceTrackModule::InitFacePoseAndQualityModel(InspireModel &model) { m_face_quality_ = std::make_shared(); - auto ret = m_face_quality_->loadData(model, model.modelType); + auto ret = m_face_quality_->LoadData(model, model.modelType); if (ret != InferenceWrapper::WrapperOk) { return HERR_ARCHIVE_LOAD_FAILURE; } @@ -441,35 +449,46 @@ void FaceTrackModule::SetMinimumFacePxSize(float value) { void FaceTrackModule::SetTrackPreviewSize(int preview_size) { track_preview_size_ = preview_size; + if (track_preview_size_ == -1) { + track_preview_size_ = m_face_detector_->GetInputSize(); + } else if (track_preview_size_ < 192) { + INSPIRE_LOGW("Track preview size %d is less than the minimum input size %d", track_preview_size_, 192); + track_preview_size_ = 192; + } } -std::string FaceTrackModule::ChoiceMultiLevelDetectModel(const int32_t pixel_size) { - const int32_t supported_sizes[] = {160, 320, 640}; - const std::string scheme_names[] = {"face_detect_160", "face_detect_320", "face_detect_640"}; - const int32_t num_sizes = sizeof(supported_sizes) / sizeof(supported_sizes[0]); +int32_t FaceTrackModule::GetTrackPreviewSize() const { + return track_preview_size_; +} +std::string FaceTrackModule::ChoiceMultiLevelDetectModel(const int32_t pixel_size, int32_t &final_size) { + const auto face_detect_pixel_list = Launch::GetInstance()->GetFaceDetectPixelList(); + const auto face_detect_model_list = Launch::GetInstance()->GetFaceDetectModelList(); + const int32_t num_sizes = face_detect_pixel_list.size(); if (pixel_size == -1) { - return scheme_names[1]; + final_size = face_detect_pixel_list[1]; + return face_detect_model_list[1]; } // Check for exact match for (int i = 0; i < num_sizes; ++i) { - if (pixel_size == supported_sizes[i]) { - return scheme_names[i]; + if (pixel_size == face_detect_pixel_list[i]) { + final_size = face_detect_pixel_list[i]; + return face_detect_model_list[i]; } } // Find the closest match - int32_t closest_size = supported_sizes[0]; - std::string closest_scheme = scheme_names[0]; - int32_t min_diff = std::abs(pixel_size - supported_sizes[0]); + int32_t closest_size = face_detect_pixel_list[0]; + std::string closest_scheme = face_detect_model_list[0]; + int32_t min_diff = std::abs(pixel_size - face_detect_pixel_list[0]); for (int i = 1; i < num_sizes; ++i) { - int32_t diff = std::abs(pixel_size - supported_sizes[i]); + int32_t diff = std::abs(pixel_size - face_detect_pixel_list[i]); if (diff < min_diff) { min_diff = diff; - closest_size = supported_sizes[i]; - closest_scheme = scheme_names[i]; + closest_size = face_detect_pixel_list[i]; + closest_scheme = face_detect_model_list[i]; } } @@ -477,6 +496,7 @@ std::string FaceTrackModule::ChoiceMultiLevelDetectModel(const int32_t pixel_siz "Input pixel size %d is not supported. Choosing the closest scheme: %s closest_scheme for " "size %d.", pixel_size, closest_scheme.c_str(), closest_size); + final_size = closest_size; return closest_scheme; } @@ -497,4 +517,13 @@ void FaceTrackModule::SetTrackModeDetectInterval(int value) { detection_interval_ = value; } +void FaceTrackModule::SetMultiscaleLandmarkLoop(int value) { + m_multiscale_landmark_loop_num_ = value; + m_multiscale_landmark_scales_ = GenerateCropScales(m_landmark_crop_ratio_, m_multiscale_landmark_loop_num_); +} + +int32_t FaceTrackModule::GetDebugPreviewImageSize() const { + return m_debug_preview_image_size_; +} + } // namespace inspire diff --git a/cpp-package/inspireface/cpp/inspireface/track_module/face_track_module.h b/cpp-package/inspireface/cpp/inspireface/track_module/face_track_module.h index d4864ff..8674bcb 100644 --- a/cpp-package/inspireface/cpp/inspireface/track_module/face_track_module.h +++ b/cpp-package/inspireface/cpp/inspireface/track_module/face_track_module.h @@ -8,26 +8,17 @@ #include #include "face_detect/face_detect_adapt.h" #include "face_detect/rnet_adapt.h" -#include "landmark/face_landmark_adapt.h" +#include "landmark/all.h" #include "common/face_info/face_object_internal.h" -#include "middleware/inspirecv_image_process.h" +#include "frame_process.h" #include "quality/face_pose_quality_adapt.h" #include "middleware/model_archive/inspire_archive.h" #include "tracker_optional/bytetrack/BYTETracker.h" +#include +#include "landmark/landmark_param.h" namespace inspire { -/** - * @enum DetectMode - * @brief Enumeration for different detection modes. - */ -enum DetectModuleMode { - DETECT_MODE_ALWAYS_DETECT = 0, ///< Detection mode: Always detect - DETECT_MODE_LIGHT_TRACK, ///< Detection mode: Light face track - DETECT_MODE_TRACK_BY_DETECT, ///< Detection mode: Tracking by detection - -}; - /** * @class FaceTrack * @brief Class for tracking faces in video streams. @@ -44,7 +35,7 @@ public: * @param track_preview_size Size of the preview for tracking. * @param dynamic_detection_input_level Change the detector input size. */ - explicit FaceTrackModule(DetectModuleMode mode, int max_detected_faces = 1, int detection_interval = 20, int track_preview_size = 192, + explicit FaceTrackModule(DetectModuleMode mode, int max_detected_faces = 1, int detection_interval = 20, int track_preview_size = -1, int dynamic_detection_input_level = -1, int TbD_mode_fps = 30, bool detect_mode_landmark = true); /** @@ -53,20 +44,26 @@ public: * @param expansion_path Expand the path if you need it. * @return int Status of the configuration. */ - int Configuration(InspireArchive &archive, const std::string &expansion_path = ""); + int Configuration(InspireArchive &archive, const std::string &expansion_path = "", bool enable_face_pose_and_quality = false); /** * @brief Updates the video stream for face tracking. * @param image Camera stream to process. * @param is_detect Flag to enable/disable face detection. */ - void UpdateStream(inspirecv::InspireImageProcess &image); + void UpdateStream(inspirecv::FrameProcess &image); /** * @brief Sets the preview size for tracking. * @param preview_size Size of the preview for tracking. */ - void SetTrackPreviewSize(int preview_size = 192); + void SetTrackPreviewSize(int preview_size = -1); + + /** + * @brief Gets the preview size for tracking. + * @return Size of the preview for tracking. + */ + int32_t GetTrackPreviewSize() const; private: /** @@ -79,13 +76,20 @@ private: void SparseLandmarkPredict(const inspirecv::Image &raw_face_crop, std::vector &landmarks_output, float &score, float size = 112.0); + /** + * @brief Predicts the tracking score for a cropped face image. + * @param raw_face_crop Cropped face image. + * @return float Tracking score. + */ + float PredictTrackScore(const inspirecv::Image &raw_face_crop); + /** * @brief Tracks a face in the given image stream. * @param image Camera stream containing the face. * @param face FaceObject to be tracked. * @return bool Status of face tracking. */ - bool TrackFace(inspirecv::InspireImageProcess &image, FaceObjectInternal &face); + bool TrackFace(inspirecv::FrameProcess &image, FaceObjectInternal &face); /** * @brief Blacks out the region specified in the image for tracking. @@ -129,18 +133,18 @@ private: int InitRNetModel(InspireModel &model); /** - * @brief Initializes the face pose estimation model. - * @param model Pointer to the face pose model to be initialized. + * @brief Initializes the face pose and quality estimation model. + * @param model Pointer to the face pose and quality model to be initialized. * @return int Status of the initialization process. Returns 0 for success. */ - int InitFacePoseModel(InspireModel &model); + int InitFacePoseAndQualityModel(InspireModel &model); /** * @brief Select the detection model scheme to be used according to the input pixel level. * @param pixel_size Currently, only 160, 320, and 640 pixel sizes are supported. * @return Return the corresponding scheme name, only ”face_detect_160”, ”face_detect_320”, ”face_detect_640” are supported. */ - std::string ChoiceMultiLevelDetectModel(const int32_t pixel_size); + std::string ChoiceMultiLevelDetectModel(const int32_t pixel_size, int32_t &final_size); public: /** @@ -177,9 +181,18 @@ public: */ void SetTrackModeDetectInterval(int value); + /** + * @brief Set the multiscale landmark loop num + * @param value Multiscale landmark loop num + */ + void SetMultiscaleLandmarkLoop(int value); + public: std::vector trackingFace; ///< Vector of FaceObjects currently being tracked. +public: + int32_t GetDebugPreviewImageSize() const; + private: const int max_detected_faces_; ///< Maximum number of faces to detect. std::vector candidate_faces_; ///< Vector of candidate FaceObjects for tracking. @@ -189,6 +202,10 @@ private: int track_preview_size_; ///< Size of the tracking preview. int filter_minimum_face_px_size = 0; ///< Minimum face pixel allowed to be retained (take the edge with the smallest Rect). +private: + // Debug cache + int32_t m_debug_preview_image_size_{0}; ///< Debug preview image size + private: std::shared_ptr m_face_detector_; ///< Shared pointer to the face detector. std::shared_ptr m_landmark_predictor_; ///< Shared pointer to the landmark predictor. @@ -212,6 +229,14 @@ private: int m_track_mode_num_smooth_cache_frame_ = 5; ///< Track mode number of smooth cache frame float m_track_mode_smooth_ratio_ = 0.05; ///< Track mode smooth ratio + + int m_multiscale_landmark_loop_num_ = 1; ///< Multiscale landmark loop num + + float m_landmark_crop_ratio_ = 1.1f; + + std::vector m_multiscale_landmark_scales_; + + std::shared_ptr m_landmark_param_; }; } // namespace inspire diff --git a/cpp-package/inspireface/cpp/inspireface/track_module/landmark/all.h b/cpp-package/inspireface/cpp/inspireface/track_module/landmark/all.h index 8d06474..c733fb5 100644 --- a/cpp-package/inspireface/cpp/inspireface/track_module/landmark/all.h +++ b/cpp-package/inspireface/cpp/inspireface/track_module/landmark/all.h @@ -8,5 +8,8 @@ #include "face_landmark_adapt.h" #include "mean_shape.h" +#include "order_of_hyper_landmark.h" +#include "face_landmark_adapt.h" +#include "landmark_tools.h" #endif // INSPIREFACE_LMK_ALL_H diff --git a/cpp-package/inspireface/cpp/inspireface/track_module/landmark/face_landmark_adapt.cpp b/cpp-package/inspireface/cpp/inspireface/track_module/landmark/face_landmark_adapt.cpp index fb10944..46dd6dd 100644 --- a/cpp-package/inspireface/cpp/inspireface/track_module/landmark/face_landmark_adapt.cpp +++ b/cpp-package/inspireface/cpp/inspireface/track_module/landmark/face_landmark_adapt.cpp @@ -12,12 +12,18 @@ std::vector FaceLandmarkAdapt::operator()(const inspirecv::Image& bgr_aff COST_TIME_SIMPLE(FaceLandmarkAdapt); AnyTensorOutputs outputs; Forward(bgr_affine, outputs); - const auto& out = outputs[0].second; + auto& out = outputs[0].second; + if (m_is_center_scaling_) { + for (int i = 0; i < out.size(); ++i) { + out[i] = (out[i] + 1) / 2; + } + } return out; } -FaceLandmarkAdapt::FaceLandmarkAdapt(int input_size) : AnyNetAdapter("FaceLandmarkAdapt"), m_input_size_(input_size) {} +FaceLandmarkAdapt::FaceLandmarkAdapt(int input_size, bool is_center_scaling) + : AnyNetAdapter("FaceLandmarkAdapt"), m_input_size_(input_size), m_is_center_scaling_(is_center_scaling) {} int FaceLandmarkAdapt::getInputSize() const { return m_input_size_; diff --git a/cpp-package/inspireface/cpp/inspireface/track_module/landmark/face_landmark_adapt.h b/cpp-package/inspireface/cpp/inspireface/track_module/landmark/face_landmark_adapt.h index 0569bc8..8eca207 100644 --- a/cpp-package/inspireface/cpp/inspireface/track_module/landmark/face_landmark_adapt.h +++ b/cpp-package/inspireface/cpp/inspireface/track_module/landmark/face_landmark_adapt.h @@ -5,7 +5,7 @@ #pragma once #ifndef INSPIRE_FACE_TRACK_MODULE_LANDMARK_FACE_LANDMARK_ADAPT_H #define INSPIRE_FACE_TRACK_MODULE_LANDMARK_FACE_LANDMARK_ADAPT_H -#include "../../data_type.h" +#include "data_type.h" #include "middleware/any_net_adapter.h" namespace inspire { @@ -29,7 +29,7 @@ public: * @brief Constructor for the FaceLandmark class. * @param input_size The size of the input image for the neural network. */ - explicit FaceLandmarkAdapt(int input_size = 112); + explicit FaceLandmarkAdapt(int input_size = 112, bool is_center_scaling = false); /** * @brief Gets the input size for the neural network model. @@ -38,18 +38,12 @@ public: int getInputSize() const; public: - const static int LEFT_EYE_CENTER = 67; ///< Landmark index for the center of the left eye. - const static int RIGHT_EYE_CENTER = 68; ///< Landmark index for the center of the right eye. - const static int NOSE_CORNER = 100; ///< Landmark index for the tip of the nose. - const static int MOUTH_LEFT_CORNER = 104; ///< Landmark index for the left corner of the mouth. - const static int MOUTH_RIGHT_CORNER = 105; ///< Landmark index for the right corner of the mouth. - const static int MOUTH_LOWER = 84; ///< Landmark index for the lower corner of the mouth. - const static int MOUTH_UPPER = 87; ///< Landmark index for the upper corner of the mouth. const static int NUM_OF_LANDMARK = 106; ///< Total number of landmarks detected. private: const int m_input_size_; ///< The input size for the neural network model. + bool m_is_center_scaling_; ///< Whether to use center scaling. }; } // namespace inspire diff --git a/cpp-package/inspireface/cpp/inspireface/track_module/landmark/landmark_param.h b/cpp-package/inspireface/cpp/inspireface/track_module/landmark/landmark_param.h new file mode 100644 index 0000000..20ed52b --- /dev/null +++ b/cpp-package/inspireface/cpp/inspireface/track_module/landmark/landmark_param.h @@ -0,0 +1,127 @@ +#ifndef INSPIRE_LANDMARK_PARAM_H +#define INSPIRE_LANDMARK_PARAM_H + +#include "data_type.h" +#include "yaml-cpp/yaml.h" +#include "mean_shape.h" +#include "log.h" +#include "landmark_tools.h" +#include "order_of_hyper_landmark.h" + +namespace inspire { + +typedef struct { + int32_t left_eye_center = 67; + int32_t right_eye_center = 68; + int32_t nose_corner = 100; + int32_t mouth_left_corner = 104; + int32_t mouth_right_corner = 105; + int32_t mouth_lower = 84; + int32_t mouth_upper = 87; + std::vector left_eye_region = HLMK_LEFT_EYE_POINTS_INDEX; + std::vector right_eye_region = HLMK_RIGHT_EYE_POINTS_INDEX; +} SemanticIndex; + +class INSPIRE_API LandmarkParam { +public: + LandmarkParam(const YAML::Node &config) { + LoadDefaultMeshShape(); + if (!config) { + } else { + // TODO: parse config + m_is_available_ = true; + } + m_table_ = config; + } + + void LoadDefaultMeshShape() { + mean_shape_points.clear(); + mean_shape_points.resize(num_of_landmark); + for (int k = 0; k < num_of_landmark; k++) { + mean_shape_points[k].SetX(HYPLMK_MESH_SHAPE[k * 2]); + mean_shape_points[k].SetY(HYPLMK_MESH_SHAPE[k * 2 + 1]); + } + } + + bool ReLoad(const std::string &name) { + if (!m_is_available_) { + landmark_engine_name = name; + return true; + } + // parse config + auto landmark_table = m_table_[name]; + if (!landmark_table) { + INSPIRE_LOGE("landmark config not found: %s", name.c_str()); + return false; + } + num_of_landmark = landmark_table["num_of_landmark"].as(); + expansion_scale = landmark_table["expansion_scale"].as(); + input_size = landmark_table["input_size"].as(); + auto semanic_index = landmark_table["semantic_index"]; + if (!semanic_index) { + INSPIRE_LOGE("semantic_index not found: %s", name.c_str()); + return false; + } + semantic_index.left_eye_center = semanic_index["left_eye_center"].as(); + semantic_index.right_eye_center = semanic_index["right_eye_center"].as(); + semantic_index.nose_corner = semanic_index["nose_corner"].as(); + semantic_index.mouth_left_corner = semanic_index["mouth_left_corner"].as(); + semantic_index.mouth_right_corner = semanic_index["mouth_right_corner"].as(); + semantic_index.mouth_lower = semanic_index["mouth_lower"].as(); + semantic_index.mouth_upper = semanic_index["mouth_upper"].as(); + auto left_eye_region = semanic_index["left_eye_region"]; + if (left_eye_region) { + semantic_index.left_eye_region = left_eye_region.as>(); + } + auto right_eye_region = semanic_index["right_eye_region"]; + if (right_eye_region) { + semantic_index.right_eye_region = right_eye_region.as>(); + } + auto mesh_shape = landmark_table["mesh_shape"]; + if (mesh_shape.size() > 0) { + std::vector mesh_shape_data = mesh_shape.as>(); + mean_shape_points.clear(); + mean_shape_points.resize(num_of_landmark); + if (mesh_shape_data.size() == num_of_landmark * 2) { + for (int i = 0; i < num_of_landmark; i++) { + mean_shape_points[i].SetX(mesh_shape_data[i * 2]); + mean_shape_points[i].SetY(mesh_shape_data[i * 2 + 1]); + } + mean_shape_points = LandmarkCropped(mean_shape_points); + // auto img = inspirecv::Image::Create(192, 192, 3); + // img.Fill(0); + // for (int i = 0; i < num_of_landmark; i++) { + // auto point = mean_shape_points[i]; + // img.DrawCircle(inspirecv::Point2i(point.GetX(), point.GetY()), 2, inspirecv::Color::Red); + // } + // img.Show("mean_shape"); + } else { + INSPIRE_LOGE("norm_track_index_from_112x size is not equal to num_of_landmark: %s", name.c_str()); + return false; + } + } else { + LoadDefaultMeshShape(); + } + normalization_mode = landmark_table["normalization_mode"].as(); + landmark_engine_name = name; + + return true; + } + +public: + int num_of_landmark{106}; + float expansion_scale{1.1f}; + int input_size{112}; + std::vector mean_shape_points; + SemanticIndex semantic_index; + std::string landmark_engine_name{"landmark"}; + std::string normalization_mode{"MinMax"}; + +private: + YAML::Node m_table_; + bool m_is_available_{false}; +}; + +} // namespace inspire + +#endif // INSPIRE_LANDMARK_PARAM_H diff --git a/cpp-package/inspireface/cpp/inspireface/track_module/landmark/landmark_tools.h b/cpp-package/inspireface/cpp/inspireface/track_module/landmark/landmark_tools.h new file mode 100644 index 0000000..fa5ed7e --- /dev/null +++ b/cpp-package/inspireface/cpp/inspireface/track_module/landmark/landmark_tools.h @@ -0,0 +1,126 @@ +/** + * Created by Jingyu Yan + * @date 2025-04-26 + */ +#pragma once +#ifndef INSPIRE_FACE_TRACK_MODULE_LANDMARK_TOOLS_H +#define INSPIRE_FACE_TRACK_MODULE_LANDMARK_TOOLS_H + +#include +#include +#include +#include + +namespace inspire { + +// Generate crop scales +inline std::vector GenerateCropScales(float start_scale, int N) { + std::vector result; + if (N <= 0) + return result; + + result.push_back(start_scale); + + float delta_step = 0.02f; // Initial step size + int direction = -1; // First decrease + int expand_count = 1; // Current expansion count + + for (int i = 1; i < N; ++i) { + // The step size increases slightly every two expansions, e.g. 0.02 -> 0.04 -> 0.06 + float current_delta = delta_step * (static_cast((expand_count + 1) / 2)); + + float new_scale = start_scale + direction * current_delta; + + // Keep 5 decimal places + new_scale = std::round(new_scale * 100000.0f) / 100000.0f; + + result.push_back(new_scale); + + direction *= -1; // Alternating positive and negative + expand_count++; + } + + return result; +} + +inline inspirecv::TransformMatrix ScaleAffineMatrixPreserveCenter(const inspirecv::TransformMatrix& affine, float scale, int output_size = 112) { + // The center point in the output image is (cx, cy). + float cx = output_size / 2.0f; + float cy = output_size / 2.0f; + + // 1. Obtain the position of the current center point in the original image (inverse affine transformation) + inspirecv::TransformMatrix inv_affine = affine.GetInverse(); + float center_x = inv_affine.Get(0, 0) * cx + inv_affine.Get(0, 1) * cy + inv_affine.Get(0, 2); + float center_y = inv_affine.Get(1, 0) * cx + inv_affine.Get(1, 1) * cy + inv_affine.Get(1, 2); + + // 2. Create a new scaling matrix (note that the scale value should be reduced → to "expand the cropping area") + float inv_scale = 1.0f / scale; + float new_a11 = affine.Get(0, 0) * inv_scale; + float new_a12 = affine.Get(0, 1) * inv_scale; + float new_a21 = affine.Get(1, 0) * inv_scale; + float new_a22 = affine.Get(1, 1) * inv_scale; + + // 3. Calculate the new offset to ensure that the center of the scaled image is still mapped to (cx, cy) + float new_b1 = cx - (new_a11 * center_x + new_a12 * center_y); + float new_b2 = cy - (new_a21 * center_x + new_a22 * center_y); + + return inspirecv::TransformMatrix::Create(new_a11, new_a12, new_b1, new_a21, new_a22, new_b2); +} + +inline std::vector MultiFrameLandmarkMean(const std::vector>& points) { + std::vector mean_points; + + if (points.empty()) { + return mean_points; + } + + if (points.size() == 1) { + return points[0]; + } + + size_t num_frames = points.size(); + size_t num_points = points[0].size(); + + // Initialize to 0 + mean_points.resize(num_points, inspirecv::Point2f(0.0f, 0.0f)); + + // Accumulate the coordinates of each point + for (const auto& frame : points) { + if (frame.size() != num_points) + continue; // skip invalid frame size + + for (size_t i = 0; i < num_points; ++i) { + mean_points[i].SetX(mean_points[i].GetX() + frame[i].GetX()); + mean_points[i].SetY(mean_points[i].GetY() + frame[i].GetY()); + } + } + + // Calculate the average + for (auto& pt : mean_points) { + pt.SetX(pt.GetX() / static_cast(num_frames)); + pt.SetY(pt.GetY() / static_cast(num_frames)); + } + + return mean_points; +} + +inline std::vector LandmarkCropped(const std::vector& points, int output_size = 192) { + inspirecv::Rect2f rect = inspirecv::MinBoundingRect(points); + + // Calculate the scale + float scale = output_size / std::max(rect.GetWidth(), rect.GetHeight()); + + // Create the result vector and scale each point + std::vector result; + result.reserve(points.size()); + for (const auto& pt : points) { + // Translate to the origin (subtract the top-left corner coordinates), then scale + result.push_back(inspirecv::Point2f((pt.GetX() - rect.GetX()) * scale, (pt.GetY() - rect.GetY()) * scale)); + } + + return result; +} + +} // namespace inspire + +#endif // INSPIRE_FACE_TRACK_MODULE_LANDMARK_TOOLS_H diff --git a/cpp-package/inspireface/cpp/inspireface/track_module/landmark/mean_shape.h b/cpp-package/inspireface/cpp/inspireface/track_module/landmark/mean_shape.h index 2da69c4..b1f8331 100644 --- a/cpp-package/inspireface/cpp/inspireface/track_module/landmark/mean_shape.h +++ b/cpp-package/inspireface/cpp/inspireface/track_module/landmark/mean_shape.h @@ -6,7 +6,8 @@ #ifndef INSPIREFACE_MEAN_SHAPE_H #define INSPIREFACE_MEAN_SHAPE_H -static const float mean_shape[212] = { +// The mean shape of the hyperlandmarkv2 model +static const float HYPLMK_MESH_SHAPE[212] = { 108.38947968990857, 25.872507850440652, 107.83061691599185, 32.740667888401575, 107.39614893771035, 39.61981246234094, 106.91122964874452, 46.49403692042678, 106.2010288700007, 53.34741525135486, 105.09197874965457, 60.15582154225913, 103.42464830472807, 66.83258235371402, 101.06766895276914, 73.25011372147983, 97.9868709520959, 79.30544663628336, 94.26571910204586, 84.93503057741032, 90.00562918536812, diff --git a/cpp-package/inspireface/cpp/inspireface/pipeline_module/liveness/order_of_hyper_landmark.h b/cpp-package/inspireface/cpp/inspireface/track_module/landmark/order_of_hyper_landmark.h similarity index 74% rename from cpp-package/inspireface/cpp/inspireface/pipeline_module/liveness/order_of_hyper_landmark.h rename to cpp-package/inspireface/cpp/inspireface/track_module/landmark/order_of_hyper_landmark.h index dfdd284..501a51e 100644 --- a/cpp-package/inspireface/cpp/inspireface/pipeline_module/liveness/order_of_hyper_landmark.h +++ b/cpp-package/inspireface/cpp/inspireface/track_module/landmark/order_of_hyper_landmark.h @@ -10,10 +10,10 @@ namespace inspire { -// HyperLandmark left eye contour points sequence of dense facial landmarks. +// HyperLandmarkV2 left eye contour points sequence of dense facial landmarks. const std::vector HLMK_LEFT_EYE_POINTS_INDEX = {51, 52, 53, 54, 55, 56, 57, 58}; -// HyperLandmark right eye contour points sequence of dense facial landmarks. +// HyperLandmarkV2 right eye contour points sequence of dense facial landmarks. const std::vector HLMK_RIGHT_EYE_POINTS_INDEX = {59, 60, 61, 62, 63, 64, 65, 66}; } // namespace inspire diff --git a/cpp-package/inspireface/cpp/inspireface/track_module/quality/face_pose_quality_adapt.cpp b/cpp-package/inspireface/cpp/inspireface/track_module/quality/face_pose_quality_adapt.cpp index d0d05fe..889efad 100644 --- a/cpp-package/inspireface/cpp/inspireface/track_module/quality/face_pose_quality_adapt.cpp +++ b/cpp-package/inspireface/cpp/inspireface/track_module/quality/face_pose_quality_adapt.cpp @@ -13,7 +13,14 @@ FacePoseQualityAdapt::FacePoseQualityAdapt() : AnyNetAdapter("FacePoseQuality") FacePoseQualityAdaptResult FacePoseQualityAdapt::operator()(const inspirecv::Image &img) { FacePoseQualityAdaptResult res; AnyTensorOutputs outputs; - Forward(img, outputs); + if (img.Width() != INPUT_WIDTH || img.Height() != INPUT_HEIGHT) { + uint8_t* resized_data = nullptr; + m_processor_->Resize(img.Data(), img.Width(), img.Height(), img.Channels(), &resized_data, INPUT_WIDTH, INPUT_HEIGHT); + auto resized = inspirecv::Image::Create(INPUT_WIDTH, INPUT_HEIGHT, img.Channels(), resized_data, false); + Forward(resized, outputs); + } else { + Forward(img, outputs); + } const auto &output = outputs[0].second; res.pitch = output[0] * 90; res.yaw = output[1] * 90; diff --git a/cpp-package/inspireface/cpp/inspireface/track_module/quality/face_pose_quality_adapt.h b/cpp-package/inspireface/cpp/inspireface/track_module/quality/face_pose_quality_adapt.h index a014926..54dd3c3 100644 --- a/cpp-package/inspireface/cpp/inspireface/track_module/quality/face_pose_quality_adapt.h +++ b/cpp-package/inspireface/cpp/inspireface/track_module/quality/face_pose_quality_adapt.h @@ -6,7 +6,7 @@ #ifndef INSPIRE_FACE_TRACK_MODULE_QUALITY_FACE_POSE_QUALITY_ADAPT_H #define INSPIRE_FACE_TRACK_MODULE_QUALITY_FACE_POSE_QUALITY_ADAPT_H -#include "../../data_type.h" +#include "data_type.h" #include "middleware/any_net_adapter.h" namespace inspire { diff --git a/cpp-package/inspireface/cpp/inspireface/version.txt b/cpp-package/inspireface/cpp/inspireface/version.txt index 7a9bf19..0dfc7b3 100644 --- a/cpp-package/inspireface/cpp/inspireface/version.txt +++ b/cpp-package/inspireface/cpp/inspireface/version.txt @@ -1 +1 @@ -InspireFace Version: 1.2.0 +InspireFace Version: 1.2.1 diff --git a/cpp-package/inspireface/cpp/sample/CMakeLists.txt b/cpp-package/inspireface/cpp/sample/CMakeLists.txt index 17950b4..82c6933 100644 --- a/cpp-package/inspireface/cpp/sample/CMakeLists.txt +++ b/cpp-package/inspireface/cpp/sample/CMakeLists.txt @@ -1,10 +1,12 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.20) project(InspireFaceSample) option(ISF_BUILD_SAMPLE_CLUTTERED "Whether to compile the cluttered sample program (debug code during development)" OFF) +option(ISF_BUILD_SAMPLE_INTERNAL "Whether to compile the internal sample program (debug code during development)" OFF) include_directories(${SRC_DIR}) include_directories(${SRC_DIR}/inspireface/c_api) +include_directories(${SRC_DIR}/inspireface/include) if (ISF_ENABLE_RKNN AND ISF_RKNPU_MAJOR STREQUAL "rknpu1") set(ISF_RKNN_API_LIB ${ISF_THIRD_PARTY_DIR}/inspireface-precompile-lite/rknn/${ISF_RKNPU_MAJOR}/runtime/${ISF_RK_DEVICE_TYPE}/Linux/librknn_api/${CPU_ARCH}/) @@ -24,75 +26,129 @@ if (ISF_ENABLE_RKNN AND ISF_RKNPU_MAJOR STREQUAL "rknpu2" AND ISF_RK_COMPILER_TY set(ext rknnrt dl) endif () -add_executable(Leak api/leak.cpp) +add_executable(Leak api/leak.c) target_link_libraries(Leak InspireFace ${ext}) set_target_properties(Leak PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/api/" ) # # Examples of face detection and tracking -add_executable(FaceTrackSample api/sample_face_track.cpp) +add_executable(FaceTrackSample api/sample_face_track.c) target_link_libraries(FaceTrackSample InspireFace ${ext}) set_target_properties(FaceTrackSample PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/api/" ) -add_executable(FaceTrackBenchmarkSample api/sample_face_track_benchmark.cpp) +add_executable(FaceTrackBenchmarkSample api/sample_face_track_benchmark.c) target_link_libraries(FaceTrackBenchmarkSample InspireFace ${ext}) set_target_properties(FaceTrackBenchmarkSample PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/api/" ) # Examples of face recognition -add_executable(FaceComparisonSample api/sample_face_comparison.cpp) +add_executable(FaceComparisonSample api/sample_face_comparison.c) target_link_libraries(FaceComparisonSample InspireFace ${ext}) set_target_properties(FaceComparisonSample PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/api/" ) -add_executable(FaceFeatureHubSample api/sample_feature_hub.cpp) +add_executable(FaceFeatureHubSample api/sample_feature_hub.c) target_link_libraries(FaceFeatureHubSample InspireFace ${ext}) set_target_properties(FaceFeatureHubSample PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/api/" ) -add_executable(FaceLoadReloadSample api/sample_load_reload.cpp) +add_executable(FaceLoadReloadSample api/sample_load_reload.c) target_link_libraries(FaceLoadReloadSample InspireFace ${ext}) set_target_properties(FaceLoadReloadSample PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/api/" ) -add_executable(FaceTrackerSample source/tracker_sample.cpp) -target_link_libraries(FaceTrackerSample InspireFace ${ext}) -set_target_properties(FaceTrackerSample PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/" +add_executable(CppResourcePoolSample api/sample_cpp_resource_pool.cpp) +target_link_libraries(CppResourcePoolSample InspireFace ${ext}) +set_target_properties(CppResourcePoolSample PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/api/" ) -add_executable(ExpansionLoadSample source/expansion_load.cpp) -target_link_libraries(ExpansionLoadSample InspireFace ${ext}) -set_target_properties(ExpansionLoadSample PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/" +add_executable(FaceCrudSample api/sample_face_crud.c) +target_link_libraries(FaceCrudSample InspireFace ${ext}) +set_target_properties(FaceCrudSample PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/api/" ) -add_executable(FaceTrackPipelineSample source/tracker_pipeline.cpp) -target_link_libraries(FaceTrackPipelineSample InspireFace ${ext}) -set_target_properties(FaceTrackPipelineSample PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/" +add_executable(FeatureHubPersistenceSample api/sample_feature_hub_persistence.c) +target_link_libraries(FeatureHubPersistenceSample InspireFace ${ext}) +set_target_properties(FeatureHubPersistenceSample PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/api/" ) -add_executable(FeatureHubSample source/feature_hub_sample.cpp) -target_link_libraries(FeatureHubSample InspireFace ${ext}) -set_target_properties(FeatureHubSample PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/" + + +# --- C++ API --- + +add_executable(CppSessionSample cpp_api/cpp_sample_face_track.cpp) +target_link_libraries(CppSessionSample InspireFace ${ext}) +set_target_properties(CppSessionSample PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cpp_api/" ) -add_executable(LandmarkSample source/landmark_sample.cpp) -target_link_libraries(LandmarkSample InspireFace ${ext}) -set_target_properties(LandmarkSample PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/" +add_executable(CppFaceComparisonSample cpp_api/cpp_sample_face_comparison.cpp) +target_link_libraries(CppFaceComparisonSample InspireFace ${ext}) +set_target_properties(CppFaceComparisonSample PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cpp_api/" ) +add_executable(CppFaceCrudSample cpp_api/cpp_sample_face_crud.cpp) +target_link_libraries(CppFaceCrudSample InspireFace ${ext}) +set_target_properties(CppFaceCrudSample PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cpp_api/" +) + +add_executable(CppSampleAffine cpp_api/cpp_sample_affine.cpp) +target_link_libraries(CppSampleAffine InspireFace ${ext}) +set_target_properties(CppSampleAffine PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cpp_api/" +) + +add_executable(CppSampleInspireCV cpp_api/cpp_sample_inspirecv.cpp) +target_link_libraries(CppSampleInspireCV InspireFace ${ext}) +set_target_properties(CppSampleInspireCV PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cpp_api/" +) + +if(ISF_BUILD_SAMPLE_INTERNAL) + add_executable(FaceTrackerSample source/tracker_sample.cpp) + target_link_libraries(FaceTrackerSample InspireFace ${ext}) + set_target_properties(FaceTrackerSample PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/" + ) + + add_executable(ExpansionLoadSample source/expansion_load.cpp) + target_link_libraries(ExpansionLoadSample InspireFace ${ext}) + set_target_properties(ExpansionLoadSample PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/" + ) + + add_executable(FaceTrackPipelineSample source/tracker_pipeline.cpp) + target_link_libraries(FaceTrackPipelineSample InspireFace ${ext}) + set_target_properties(FaceTrackPipelineSample PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/" + ) + + add_executable(FeatureHubSample source/feature_hub_sample.cpp) + target_link_libraries(FeatureHubSample InspireFace ${ext}) + set_target_properties(FeatureHubSample PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/" + ) + + add_executable(LandmarkSample source/landmark_sample.cpp) + target_link_libraries(LandmarkSample InspireFace ${ext}) + set_target_properties(LandmarkSample PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/" + ) + +endif() # Platform watershed if (ISF_BUILD_LINUX_ARM7 OR ISF_BUILD_LINUX_AARCH64) @@ -108,194 +164,194 @@ if(ISF_RK_DEVICE_TYPE STREQUAL "RV1106") add_executable(FaceTrackSampleRV1106 rv1106/face_detect.cpp) target_link_libraries(FaceTrackSampleRV1106 InspireFace ${ext}) set_target_properties(FaceTrackSampleRV1106 PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/internal/" ) add_executable(FaceAttributeSampleRV1106 rv1106/face_attribute.cpp) target_link_libraries(FaceAttributeSampleRV1106 InspireFace ${ext}) set_target_properties(FaceAttributeSampleRV1106 PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/internal/" ) -endif() add_executable(NexusImageSample rv1106/rga_image.cpp) target_link_libraries(NexusImageSample InspireFace ${ext}) -set_target_properties(NexusImageSample PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/" - ) + set_target_properties(NexusImageSample PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/internal/" + ) + + # These sample programs are debugging and testing code left behind by developers during the development process. + # They are cluttered and have not been organized, or similar functionalities have already been organized in the standard samples. + # You can ignore them. + if (ISF_BUILD_SAMPLE_CLUTTERED) + if (NOT ISF_BUILD_LINUX_ARM7 AND NOT ISF_BUILD_LINUX_AARCH64) + + # =======================InspireFace Sample=========================== + add_executable(TrackerSample cluttered/standard/tracker_sample.cpp) + target_link_libraries(TrackerSample InspireFace) + + set_target_properties(TrackerSample PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" + ) + add_executable(ContextSample cluttered/standard/context_sample.cpp) + target_link_libraries(ContextSample InspireFace) -# These sample programs are debugging and testing code left behind by developers during the development process. -# They are cluttered and have not been organized, or similar functionalities have already been organized in the standard samples. -# You can ignore them. -if (ISF_BUILD_SAMPLE_CLUTTERED) - if (NOT ISF_BUILD_LINUX_ARM7 AND NOT ISF_BUILD_LINUX_AARCH64) - - # =======================InspireFace Sample=========================== - add_executable(TrackerSample cluttered/standard/tracker_sample.cpp) - target_link_libraries(TrackerSample InspireFace) - - set_target_properties(TrackerSample PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" - ) + set_target_properties(ContextSample PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" + ) - add_executable(ContextSample cluttered/standard/context_sample.cpp) - target_link_libraries(ContextSample InspireFace) + add_executable(TestSample cluttered/standard/test_sample.cpp) + target_link_libraries(TestSample InspireFace) - set_target_properties(ContextSample PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" - ) + set_target_properties(TestSample PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" + ) + + add_executable(NetSample cluttered/standard/net_sample.cpp) + target_link_libraries(NetSample InspireFace) + + set_target_properties(NetSample PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" + ) - add_executable(TestSample cluttered/standard/test_sample.cpp) - target_link_libraries(TestSample InspireFace) + add_executable(RecSample cluttered/standard/rec_sample.cpp) + target_link_libraries(RecSample InspireFace) - set_target_properties(TestSample PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" - ) - - add_executable(NetSample cluttered/standard/net_sample.cpp) - target_link_libraries(NetSample InspireFace) - - set_target_properties(NetSample PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" - ) + set_target_properties(RecSample PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" + ) - add_executable(RecSample cluttered/standard/rec_sample.cpp) - target_link_libraries(RecSample InspireFace) + add_executable(BMSample cluttered/standard/bm_sample.cpp) + target_link_libraries(BMSample InspireFace) - set_target_properties(RecSample PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" - ) + set_target_properties(BMSample PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" + ) - add_executable(BMSample cluttered/standard/bm_sample.cpp) - target_link_libraries(BMSample InspireFace) + else() - set_target_properties(BMSample PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" - ) + # =======================RK Temporary test category=========================== + + if (ISF_ENABLE_RKNN) + set(ISF_RKNN_API_LIB ${ISF_THIRD_PARTY_DIR}/${ISF_RKNPU_MAJOR}/runtime/${ISF_RK_DEVICE_TYPE}/Linux/librknn_api/${CPU_ARCH}/) + message("Enable RKNN Inference") + link_directories(${ISF_RKNN_API_LIB}) + + # Face detection + add_executable(RKFaceDetSample cluttered/rk_sample/rk_face_det_sample.cpp) + target_link_libraries(RKFaceDetSample InspireFace rknn_api dl) + + set_target_properties(RKFaceDetSample PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" + ) + + # Simple network test + add_executable(RKSimpleNetSample cluttered/rk_sample/rk_simple_net_sample.cpp) + target_link_libraries(RKSimpleNetSample InspireFace rknn_api dl) + + set_target_properties(RKSimpleNetSample PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" + ) + + # Face recognize + add_executable(RKFaceRecSample cluttered/rk_sample/rk_face_recognize_sample.cpp) + target_link_libraries(RKFaceRecSample InspireFace rknn_api dl) + + set_target_properties(RKFaceRecSample PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" + ) + + # Tracking module + add_executable(RKTrackerSample cluttered/rk_sample/rk_tracker_sample.cpp) + target_link_libraries(RKTrackerSample InspireFace rknn_api dl) + + set_target_properties(RKTrackerSample PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" + ) + + # Debug + add_executable(DebugRKRec cluttered/rk_sample/debug_rk_rec.cpp) + target_link_libraries(DebugRKRec InspireFace rknn_api dl) + + set_target_properties(DebugRKRec PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" + ) - else() + add_executable(ArchTest cluttered/standard/archive_test.cpp) + target_link_libraries(ArchTest InspireFace) - # =======================RK Temporary test category=========================== + set_target_properties(ArchTest PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" + ) - if (ISF_ENABLE_RKNN) - set(ISF_RKNN_API_LIB ${ISF_THIRD_PARTY_DIR}/${ISF_RKNPU_MAJOR}/runtime/${ISF_RK_DEVICE_TYPE}/Linux/librknn_api/${CPU_ARCH}/) - message("Enable RKNN Inference") - link_directories(${ISF_RKNN_API_LIB}) - - # Face detection - add_executable(RKFaceDetSample cluttered/rk_sample/rk_face_det_sample.cpp) - target_link_libraries(RKFaceDetSample InspireFace rknn_api dl) - - set_target_properties(RKFaceDetSample PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" - ) - - # Simple network test - add_executable(RKSimpleNetSample cluttered/rk_sample/rk_simple_net_sample.cpp) - target_link_libraries(RKSimpleNetSample InspireFace rknn_api dl) - - set_target_properties(RKSimpleNetSample PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" - ) - - # Face recognize - add_executable(RKFaceRecSample cluttered/rk_sample/rk_face_recognize_sample.cpp) - target_link_libraries(RKFaceRecSample InspireFace rknn_api dl) - - set_target_properties(RKFaceRecSample PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" - ) - - # Tracking module - add_executable(RKTrackerSample cluttered/rk_sample/rk_tracker_sample.cpp) - target_link_libraries(RKTrackerSample InspireFace rknn_api dl) - - set_target_properties(RKTrackerSample PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" - ) - - # Debug - add_executable(DebugRKRec cluttered/rk_sample/debug_rk_rec.cpp) - target_link_libraries(DebugRKRec InspireFace rknn_api dl) - - set_target_properties(DebugRKRec PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" - ) - - - add_executable(ArchTest cluttered/standard/archive_test.cpp) - target_link_libraries(ArchTest InspireFace) - - set_target_properties(ArchTest PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" - ) + endif() endif() - endif() + # Tracking module + add_executable(SQLiteTest cluttered/standard/test_sqlite_sample.cpp) + target_link_libraries(SQLiteTest InspireFace) - # Tracking module - add_executable(SQLiteTest cluttered/standard/test_sqlite_sample.cpp) - target_link_libraries(SQLiteTest InspireFace) + set_target_properties(SQLiteTest PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" + ) - set_target_properties(SQLiteTest PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" - ) + if (ISF_ENABLE_RKNN) + set(DEPEND rknn_api dl) + endif () - if (ISF_ENABLE_RKNN) - set(DEPEND rknn_api dl) - endif () + # C_API Demo + add_executable(CAPISample cluttered/standard/c_api_sample.cpp) + target_link_libraries(CAPISample InspireFace ${DEPEND}) - # C_API Demo - add_executable(CAPISample cluttered/standard/c_api_sample.cpp) - target_link_libraries(CAPISample InspireFace ${DEPEND}) + set_target_properties(CAPISample PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" + ) - set_target_properties(CAPISample PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" - ) + # C_API Demo + add_executable(LoopTracker cluttered/standard/loop_tracker.cpp) + target_link_libraries(LoopTracker InspireFace ${DEPEND}) - # C_API Demo - add_executable(LoopTracker cluttered/standard/loop_tracker.cpp) - target_link_libraries(LoopTracker InspireFace ${DEPEND}) + set_target_properties(LoopTracker PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" + ) - set_target_properties(LoopTracker PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" - ) + add_executable(ArchTracker cluttered/standard/archive_tracker.cpp) + target_link_libraries(ArchTracker InspireFace) - add_executable(ArchTracker cluttered/standard/archive_tracker.cpp) - target_link_libraries(ArchTracker InspireFace) + set_target_properties(ArchTracker PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" + ) - set_target_properties(ArchTracker PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" - ) + add_executable(ErrorTest cluttered/standard/error_test.cpp) + target_link_libraries(ErrorTest InspireFace) - add_executable(ErrorTest cluttered/standard/error_test.cpp) - target_link_libraries(ErrorTest InspireFace) + set_target_properties(ErrorTest PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" + ) + endif () - set_target_properties(ErrorTest PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/sample/cluttered/" - ) -endif () +endif() # Print Message message(STATUS ">>>>>>>>>>>>>") message(STATUS "InspireFace Sample:") message(STATUS "\t ISF_BUILD_SAMPLE_CLUTTERED: ${ISF_BUILD_SAMPLE_CLUTTERED}") -# Install bin +# Install c bin install(TARGETS Leak RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/sample) install(TARGETS FaceTrackSample RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/sample) -# install(TARGETS FaceTrackSampleCost RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/sample) -# install(TARGETS MTFaceTrackSample RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/sample) -# install(TARGETS FaceRecognitionSample RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/sample) -# install(TARGETS FaceSearchSample RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/sample) install(TARGETS FaceComparisonSample RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/sample) +install(TARGETS FaceCrudSample RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/sample) +# Install cpp bin +install(TARGETS CppSessionSample RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/sample) +install(TARGETS CppFaceComparisonSample RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/sample) +install(TARGETS CppFaceCrudSample RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/sample) diff --git a/cpp-package/inspireface/cpp/sample/api/leak.cpp b/cpp-package/inspireface/cpp/sample/api/leak.c similarity index 63% rename from cpp-package/inspireface/cpp/sample/api/leak.cpp rename to cpp-package/inspireface/cpp/sample/api/leak.c index 0a478b4..efc01d1 100644 --- a/cpp-package/inspireface/cpp/sample/api/leak.cpp +++ b/cpp-package/inspireface/cpp/sample/api/leak.c @@ -2,9 +2,10 @@ * Created by Jingyu Yan * @date 2024-10-01 */ +#include int main() { - char *n = new char[1024]; + char *n = malloc(1024); return 0; } \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/sample/api/sample_cpp_resource_pool.cpp b/cpp-package/inspireface/cpp/sample/api/sample_cpp_resource_pool.cpp new file mode 100644 index 0000000..5041cc2 --- /dev/null +++ b/cpp-package/inspireface/cpp/sample/api/sample_cpp_resource_pool.cpp @@ -0,0 +1,100 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) { + if (argc != 5) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return -1; + } + + std::string model_path = argv[1]; + std::string image_path = argv[2]; + int loop = std::stoi(argv[3]); + int thread_num = std::stoi(argv[4]); + + if (thread_num > 10) { + std::cerr << "Error: thread_num cannot be greater than 10" << std::endl; + return -1; + } + if (loop < 1000) { + std::cerr << "Error: loop count must be at least 1000" << std::endl; + return -1; + } + + INSPIREFACE_CONTEXT->Load(model_path); + inspirecv::Image image = inspirecv::Image::Create(image_path); + inspirecv::FrameProcess process = + inspirecv::FrameProcess::Create(image.Data(), image.Height(), image.Width(), inspirecv::BGR, inspirecv::ROTATION_0); + + inspire::parallel::ResourcePool sessionPool(thread_num, [](inspire::Session& session) { + + }); + + for (int i = 0; i < thread_num; ++i) { + inspire::CustomPipelineParameter param; + param.enable_recognition = true; + param.enable_liveness = true; + param.enable_mask_detect = true; + param.enable_face_attribute = true; + param.enable_face_quality = true; + inspire::Session session = inspire::Session::Create(inspire::DetectModuleMode::DETECT_MODE_ALWAYS_DETECT, 1, param); + sessionPool.AddResource(std::move(session)); + } + + std::vector threads; + int tasksPerThread = loop / thread_num; + int remainingTasks = loop % thread_num; + + // Run the task in parallel + for (int i = 0; i < thread_num; ++i) { + int taskCount = tasksPerThread + (i < remainingTasks ? 1 : 0); + threads.emplace_back([&, taskCount]() { + for (int j = 0; j < taskCount; ++j) { + auto sessionGuard = sessionPool.AcquireResource(); + std::vector results; + int32_t ret; + ret = sessionGuard->FaceDetectAndTrack(process, results); + if (ret != 0) { + std::cerr << "FaceDetectAndTrack failed" << std::endl; + break; + } + if (results.size() == 0) { + std::cerr << "Not found face" << std::endl; + break; + } + } + }); + } + + // Print basic information before starting + std::cout << "\n=== Configuration Information ===" << std::endl; + std::cout << "Model Path: " << model_path << std::endl; + std::cout << "Image Path: " << image_path << std::endl; + std::cout << "Total Loop Count: " << loop << std::endl; + std::cout << "Number of Threads: " << thread_num << std::endl; + std::cout << "Tasks per Thread: " << tasksPerThread << std::endl; + std::cout << "Remaining Tasks: " << remainingTasks << std::endl; + std::cout << "==============================\n" << std::endl; + + inspire::SpendTimer timer("Number of threads: " + std::to_string(thread_num) + ", Number of tasks: " + std::to_string(loop)); + timer.Start(); + for (auto& thread : threads) { + thread.join(); + } + timer.Stop(); + std::cout << timer << std::endl; + + // Convert microseconds to milliseconds and print + double milliseconds = timer.Total() / 1000.0; + std::cout << "Total execution time: " << milliseconds << " ms" << std::endl; + + return 0; +} diff --git a/cpp-package/inspireface/cpp/sample/api/sample_face_comparison.cpp b/cpp-package/inspireface/cpp/sample/api/sample_face_comparison.c similarity index 53% rename from cpp-package/inspireface/cpp/sample/api/sample_face_comparison.cpp rename to cpp-package/inspireface/cpp/sample/api/sample_face_comparison.c index ce83519..2c79dad 100644 --- a/cpp-package/inspireface/cpp/sample/api/sample_face_comparison.cpp +++ b/cpp-package/inspireface/cpp/sample/api/sample_face_comparison.c @@ -2,113 +2,134 @@ * Created by Jingyu Yan * @date 2024-10-01 */ -#include -#include +#include +#include +#include #include +#define NUM_IMAGES 2 + int main(int argc, char* argv[]) { - // Check whether the number of parameters is correct + HResult ret; + const char* packPath; + const char* imgPath1; + const char* imgPath2; + HOption option; + HFSession session; + HFFaceFeature features[NUM_IMAGES]; + const char* imgPaths[NUM_IMAGES]; + int i; + HFloat similarity; + HFloat recommended_cosine_threshold; + HFloat percentage; + + /* Check whether the number of parameters is correct */ if (argc != 4) { HFLogPrint(HF_LOG_ERROR, "Usage: %s ", argv[0]); return 1; } - auto packPath = argv[1]; - auto imgPath1 = argv[2]; - auto imgPath2 = argv[3]; + packPath = argv[1]; + imgPath1 = argv[2]; + imgPath2 = argv[3]; + + /* Initialize features array to NULL */ + memset(features, 0, sizeof(features)); + + /* Allocate memory for feature vectors */ + for (i = 0; i < NUM_IMAGES; i++) { + ret = HFCreateFaceFeature(&features[i]); + if (ret != HSUCCEED) { + HFLogPrint(HF_LOG_ERROR, "Create face feature error: %d", ret); + goto cleanup; + } + } + + /* Set the image path array */ + imgPaths[0] = imgPath1; + imgPaths[1] = imgPath2; HFLogPrint(HF_LOG_INFO, "Pack file Path: %s", packPath); HFLogPrint(HF_LOG_INFO, "Source file Path 1: %s", imgPath1); HFLogPrint(HF_LOG_INFO, "Source file Path 2: %s", imgPath2); - HResult ret; - // The resource file must be loaded before it can be used + /* The resource file must be loaded before it can be used */ ret = HFLaunchInspireFace(packPath); if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "Load Resource error: %d", ret); - return ret; + goto cleanup; } - // Create a session for face recognition - HOption option = HF_ENABLE_FACE_RECOGNITION; - HFSession session; + /* Create a session for face recognition */ + option = HF_ENABLE_FACE_RECOGNITION; ret = HFCreateInspireFaceSessionOptional(option, HF_DETECT_MODE_ALWAYS_DETECT, 1, -1, -1, &session); if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "Create session error: %d", ret); - return ret; + goto cleanup; } - std::vector twoImg = {imgPath1, imgPath2}; - std::vector> vec(2, std::vector(512)); - for (int i = 0; i < twoImg.size(); ++i) { + /* Process two images */ + for (i = 0; i < NUM_IMAGES; i++) { HFImageBitmap imageBitmap = {0}; - ret = HFCreateImageBitmapFromFilePath(twoImg[i], 3, &imageBitmap); - if (ret != HSUCCEED) { - HFLogPrint(HF_LOG_ERROR, "Create image bitmap error: %d", ret); - return ret; - } - // Prepare image data for processing - HFImageStream stream; - ret = HFCreateImageStreamFromImageBitmap(imageBitmap, HF_CAMERA_ROTATION_0, &stream); // Create an image stream for processing - if (ret != HSUCCEED) { - HFLogPrint(HF_LOG_ERROR, "Create stream error: %d", ret); - return ret; - } - - // Execute face tracking on the image HFMultipleFaceData multipleFaceData = {0}; - ret = HFExecuteFaceTrack(session, stream, &multipleFaceData); // Track faces in the image + + ret = HFCreateImageBitmapFromFilePath(imgPaths[i], 3, &imageBitmap); if (ret != HSUCCEED) { + HFReleaseImageBitmap(imageBitmap); + HFLogPrint(HF_LOG_ERROR, "Create image bitmap error: %d", ret); + goto cleanup; + } + + ret = HFCreateImageStreamFromImageBitmap(imageBitmap, HF_CAMERA_ROTATION_0, &stream); + if (ret != HSUCCEED) { + HFReleaseImageStream(stream); + HFReleaseImageBitmap(imageBitmap); + HFLogPrint(HF_LOG_ERROR, "Create stream error: %d", ret); + goto cleanup; + } + + ret = HFExecuteFaceTrack(session, stream, &multipleFaceData); + if (ret != HSUCCEED) { + HFReleaseImageStream(stream); + HFReleaseImageBitmap(imageBitmap); HFLogPrint(HF_LOG_ERROR, "Run face track error: %d", ret); - return ret; - } - if (multipleFaceData.detectedNum == 0) { // Check if any faces were detected - HFLogPrint(HF_LOG_ERROR, "No face was detected: %s", twoImg[i]); - return ret; + goto cleanup; } - // Extract facial features from the first detected face, an interface that uses copy features in a comparison scenario - ret = HFFaceFeatureExtractCpy(session, stream, multipleFaceData.tokens[0], vec[i].data()); // Extract features + if (multipleFaceData.detectedNum == 0) { + HFReleaseImageStream(stream); + HFReleaseImageBitmap(imageBitmap); + HFLogPrint(HF_LOG_ERROR, "No face was detected: %s", imgPaths[i]); + goto cleanup; + } + + ret = HFFaceFeatureExtractTo(session, stream, multipleFaceData.tokens[0], features[i]); if (ret != HSUCCEED) { + HFReleaseImageStream(stream); + HFReleaseImageBitmap(imageBitmap); HFLogPrint(HF_LOG_ERROR, "Extract feature error: %d", ret); - return ret; + goto cleanup; } - ret = HFReleaseImageStream(stream); - if (ret != HSUCCEED) { - HFLogPrint(HF_LOG_ERROR, "Release image stream error: %d", ret); - } - ret = HFReleaseImageBitmap(imageBitmap); - if (ret != HSUCCEED) { - HFLogPrint(HF_LOG_ERROR, "Release image bitmap error: %d", ret); - return ret; - } + HFReleaseImageStream(stream); + HFReleaseImageBitmap(imageBitmap); } - // Make feature1 - HFFaceFeature feature1 = {0}; - feature1.data = vec[0].data(); - feature1.size = vec[0].size(); + HFFaceFeature feature1 = features[0]; + HFFaceFeature feature2 = features[1]; - // Make feature2 - HFFaceFeature feature2 = {0}; - feature2.data = vec[1].data(); - feature2.size = vec[1].size(); - - // Run comparison - HFloat similarity; + /* Run comparison */ ret = HFFaceComparison(feature1, feature2, &similarity); if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "Feature comparison error: %d", ret); - return ret; + goto cleanup; } - HFloat recommended_cosine_threshold; ret = HFGetRecommendedCosineThreshold(&recommended_cosine_threshold); if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "Get recommended cosine threshold error: %d", ret); - return ret; + goto cleanup; } if (similarity > recommended_cosine_threshold) { @@ -118,20 +139,28 @@ int main(int argc, char* argv[]) { } HFLogPrint(HF_LOG_INFO, "Similarity score: %.3f", similarity); - // Convert cosine similarity to percentage similarity. - // Note: conversion parameters are not optimal and should be adjusted based on your specific use case. - HFloat percentage; ret = HFCosineSimilarityConvertToPercentage(similarity, &percentage); if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "Convert similarity to percentage error: %d", ret); - return ret; + goto cleanup; } HFLogPrint(HF_LOG_INFO, "Percentage similarity: %f", percentage); - // The memory must be freed at the end of the program + /* Clean up resources */ ret = HFReleaseInspireFaceSession(session); if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "Release session error: %d", ret); - return ret; } + +cleanup: + /* Release the feature vector memory */ + for (i = 0; i < NUM_IMAGES; i++) { + if (features[i].data != NULL) { // Only release features that were successfully created + HFReleaseFaceFeature(&features[i]); + } + } + + HFDeBugShowResourceStatistics(); + + return ret; } \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/sample/api/sample_face_crud.c b/cpp-package/inspireface/cpp/sample/api/sample_face_crud.c new file mode 100644 index 0000000..4046018 --- /dev/null +++ b/cpp-package/inspireface/cpp/sample/api/sample_face_crud.c @@ -0,0 +1,178 @@ +#include +#include + +int main() { + HResult ret; + // The resource file must be loaded before it can be used + ret = HFLaunchInspireFace("test_res/pack/Pikachu"); + if (ret != HSUCCEED) { + HFLogPrint(HF_LOG_ERROR, "Load Resource error: %d", ret); + return ret; + } + + char *db_path = "case_crud.db"; + if (remove(db_path) != 0) { + HFLogPrint(HF_LOG_ERROR, "Remove database file error: %d", ret); + return ret; + } + + HFFeatureHubConfiguration configuration; + configuration.primaryKeyMode = HF_PK_AUTO_INCREMENT; + configuration.enablePersistence = 1; + configuration.persistenceDbPath = db_path; + configuration.searchMode = HF_SEARCH_MODE_EXHAUSTIVE; + configuration.searchThreshold = 0.48f; + ret = HFFeatureHubDataEnable(configuration); + if (ret != HSUCCEED) { + HFLogPrint(HF_LOG_ERROR, "Enable feature hub error: %d", ret); + return ret; + } + + // Create a session + HFSession session; + ret = HFCreateInspireFaceSessionOptional(HF_ENABLE_FACE_RECOGNITION, HF_DETECT_MODE_ALWAYS_DETECT, 1, 320, -1, &session); + if (ret != HSUCCEED) { + HFLogPrint(HF_LOG_ERROR, "Create session error: %d", ret); + return ret; + } + + // Prepare an image for insertion into the hub + HFImageBitmap image; + ret = HFCreateImageBitmapFromFilePath("test_res/data/bulk/kun.jpg", 3, &image); + if (ret != HSUCCEED) { + HFLogPrint(HF_LOG_ERROR, "Create image bitmap error: %d", ret); + return ret; + } + + // Create an image stream + HFImageStream imageHandle; + ret = HFCreateImageStreamFromImageBitmap(image, HF_CAMERA_ROTATION_0, &imageHandle); + if (ret != HSUCCEED) { + HFLogPrint(HF_LOG_ERROR, "Create image stream error: %d", ret); + return ret; + } + + // Detect and track + HFMultipleFaceData multipleFaceData; + ret = HFExecuteFaceTrack(session, imageHandle, &multipleFaceData); + if (ret != HSUCCEED) { + HFLogPrint(HF_LOG_ERROR, "Execute face track error: %d", ret); + return ret; + } + + if (multipleFaceData.detectedNum > 0) { + HFLogPrint(HF_LOG_INFO, "Face detected: %d", multipleFaceData.detectedNum); + } + + HFFaceFeature feature; + ret = HFCreateFaceFeature(&feature); + if (ret != HSUCCEED) { + HFLogPrint(HF_LOG_ERROR, "Create face feature error: %d", ret); + return ret; + } + + ret = HFFaceFeatureExtractCpy(session, imageHandle, multipleFaceData.tokens[0], feature.data); + if (ret != HSUCCEED) { + HFLogPrint(HF_LOG_ERROR, "Extract feature error: %d", ret); + return ret; + } + + // Insert face feature into the hub + HFFaceFeatureIdentity featureIdentity; + featureIdentity.feature = &feature; + featureIdentity.id = -1; + HFaceId result_id; + ret = HFFeatureHubInsertFeature(featureIdentity, &result_id); + if (ret != HSUCCEED) { + HFLogPrint(HF_LOG_ERROR, "Insert feature error: %d", ret); + return ret; + } + + // Prepare a photo of the same person for the query + HFImageBitmap query_image; + ret = HFCreateImageBitmapFromFilePath("test_res/data/bulk/jntm.jpg", 3, &query_image); + if (ret != HSUCCEED) { + HFLogPrint(HF_LOG_ERROR, "Create image bitmap error: %d", ret); + return ret; + } + + // Create an image stream + HFImageStream query_imageHandle; + ret = HFCreateImageStreamFromImageBitmap(query_image, HF_CAMERA_ROTATION_0, &query_imageHandle); + if (ret != HSUCCEED) { + HFLogPrint(HF_LOG_ERROR, "Create image stream error: %d", ret); + return ret; + } + + // Detect and track + ret = HFExecuteFaceTrack(session, query_imageHandle, &multipleFaceData); + if (ret != HSUCCEED) { + HFLogPrint(HF_LOG_ERROR, "Execute face track error: %d", ret); + return ret; + } + + if (multipleFaceData.detectedNum > 0) { + HFLogPrint(HF_LOG_INFO, "Face detected: %d", multipleFaceData.detectedNum); + } + + HFFaceFeature query_feature; + ret = HFCreateFaceFeature(&query_feature); + if (ret != HSUCCEED) { + HFLogPrint(HF_LOG_ERROR, "Create face feature error: %d", ret); + return ret; + } + + // Extract face feature + ret = HFFaceFeatureExtractTo(session, query_imageHandle, multipleFaceData.tokens[0], query_feature); + if (ret != HSUCCEED) { + HFLogPrint(HF_LOG_ERROR, "Extract feature error: %d", ret); + return ret; + } + + // Search face feature + HFFaceFeatureIdentity query_featureIdentity; + query_featureIdentity.feature = &query_feature; + query_featureIdentity.id = -1; + HFloat confidence; + ret = HFFeatureHubFaceSearch(query_feature, &confidence, &query_featureIdentity); + + if (ret != HSUCCEED) { + HFLogPrint(HF_LOG_ERROR, "Search feature error: %d", ret); + return ret; + } + + HFLogPrint(HF_LOG_INFO, "Search feature result: %d", query_featureIdentity.id); + HFLogPrint(HF_LOG_INFO, "Search feature confidence: %f", confidence); + + // Remove face feature + ret = HFFeatureHubFaceRemove(result_id); + if (ret != HSUCCEED) { + HFLogPrint(HF_LOG_ERROR, "Remove feature error: %d", ret); + return ret; + } + HFLogPrint(HF_LOG_INFO, "Remove feature result: %d", result_id); + + // Query again + ret = HFFeatureHubFaceSearch(query_feature, &confidence, &query_featureIdentity); + if (ret != HSUCCEED) { + HFLogPrint(HF_LOG_ERROR, "Search feature error: %d", ret); + return ret; + } + HFLogPrint(HF_LOG_INFO, "Query again, search feature result: %d", query_featureIdentity.id); + if (query_featureIdentity.id != -1) { + HFLogPrint(HF_LOG_INFO, "Remove feature failed"); + } + + // Release resources + HFReleaseFaceFeature(&feature); + HFReleaseFaceFeature(&query_feature); + HFReleaseImageStream(imageHandle); + HFReleaseImageStream(query_imageHandle); + HFReleaseImageBitmap(image); + HFReleaseImageBitmap(query_image); + HFReleaseInspireFaceSession(session); + + HFDeBugShowResourceStatistics(); + + return 0; +} \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/sample/api/sample_face_track.cpp b/cpp-package/inspireface/cpp/sample/api/sample_face_track.c similarity index 59% rename from cpp-package/inspireface/cpp/sample/api/sample_face_track.cpp rename to cpp-package/inspireface/cpp/sample/api/sample_face_track.c index 4b7be27..730bb26 100644 --- a/cpp-package/inspireface/cpp/sample/api/sample_face_track.cpp +++ b/cpp-package/inspireface/cpp/sample/api/sample_face_track.c @@ -2,30 +2,53 @@ * Created by Jingyu Yan * @date 2024-10-01 */ -#include +#include +#include #include int main(int argc, char* argv[]) { - // Check whether the number of parameters is correct + HResult ret; + const char* packPath; + const char* sourcePath; + int rotation; + HFRotation rotation_enum; + HOption option; + HFDetectMode detMode; + HInt32 maxDetectNum; + HInt32 detectPixelLevel; + HFSession session; + HFImageBitmap image; + HFImageStream imageHandle; + HFMultipleFaceData multipleFaceData; + int faceNum; + HFImageBitmap drawImage; + HFImageBitmapData data; + int index; + HFFaceMaskConfidence maskConfidence; + HFFaceQualityConfidence qualityConfidence; + HOption pipelineOption; + HFFaceDetectPixelList pixelLevels; + + /* Check whether the number of parameters is correct */ if (argc < 3 || argc > 4) { HFLogPrint(HF_LOG_ERROR, "Usage: %s [rotation]", argv[0]); return 1; } - auto packPath = argv[1]; - auto sourcePath = argv[2]; - int rotation = 0; + packPath = argv[1]; + sourcePath = argv[2]; + rotation = 0; - // If rotation is provided, check and set the value + /* If rotation is provided, check and set the value */ if (argc == 4) { - rotation = std::atoi(argv[3]); + rotation = atoi(argv[3]); if (rotation != 0 && rotation != 90 && rotation != 180 && rotation != 270) { HFLogPrint(HF_LOG_ERROR, "Invalid rotation value. Allowed values are 0, 90, 180, 270."); return 1; } } - HFRotation rotation_enum; - // Set rotation based on input parameter + + /* Set rotation based on input parameter */ switch (rotation) { case 90: rotation_enum = HF_CAMERA_ROTATION_90; @@ -46,28 +69,37 @@ int main(int argc, char* argv[]) { HFLogPrint(HF_LOG_INFO, "Source file Path: %s", sourcePath); HFLogPrint(HF_LOG_INFO, "Rotation: %d", rotation); - HFSetLogLevel(HF_LOG_INFO); + HFSetLogLevel(HF_LOG_DEBUG); - HResult ret; - // The resource file must be loaded before it can be used + /* The resource file must be loaded before it can be used */ ret = HFLaunchInspireFace(packPath); if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "Load Resource error: %d", ret); return ret; } - // Enable the functions in the pipeline: mask detection, live detection, and face quality - // detection - HOption option = HF_ENABLE_QUALITY | HF_ENABLE_MASK_DETECT | HF_ENABLE_LIVENESS | HF_ENABLE_DETECT_MODE_LANDMARK; - // Non-video or frame sequence mode uses IMAGE-MODE, which is always face detection without - // tracking - HFDetectMode detMode = HF_DETECT_MODE_ALWAYS_DETECT; - // Maximum number of faces detected - HInt32 maxDetectNum = 20; - // Face detection image input level - HInt32 detectPixelLevel = 160; - // Handle of the current face SDK algorithm context - HFSession session = {0}; + ret = HFQuerySupportedPixelLevelsForFaceDetection(&pixelLevels); + if (ret != HSUCCEED) { + HFLogPrint(HF_LOG_ERROR, "HFQuerySupportedPixelLevelsForFaceDetection error: %d", ret); + return ret; + } + HFLogPrint(HF_LOG_INFO, "Supported pixel levels for face detection: %d", pixelLevels.size); + for (int i = 0; i < pixelLevels.size; i++) { + HFLogPrint(HF_LOG_INFO, "Supported pixel level %d: %d", i + 1, pixelLevels.pixel_level[i]); + } + + /* Enable the functions in the pipeline: mask detection, live detection, and face quality + * detection */ + option = HF_ENABLE_QUALITY | HF_ENABLE_MASK_DETECT | HF_ENABLE_LIVENESS; + /* Non-video or frame sequence mode uses IMAGE-MODE, which is always face detection without + * tracking */ + detMode = HF_DETECT_MODE_LIGHT_TRACK; + /* Maximum number of faces detected */ + maxDetectNum = 20; + /* Face detection image input level */ + detectPixelLevel = 160; + /* Handle of the current face SDK algorithm context */ + session = NULL; ret = HFCreateInspireFaceSessionOptional(option, detMode, maxDetectNum, detectPixelLevel, -1, &session); if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "Create FaceContext error: %d", ret); @@ -77,123 +109,128 @@ int main(int argc, char* argv[]) { HFSessionSetTrackPreviewSize(session, detectPixelLevel); HFSessionSetFilterMinimumFacePixelSize(session, 4); - // Load a image - HFImageBitmap image; + /* Load a image */ ret = HFCreateImageBitmapFromFilePath(sourcePath, 3, &image); if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "The source entered is not a picture or read error."); return ret; } - // Prepare an image parameter structure for configuration - HFImageStream imageHandle = {0}; + /* Prepare an image parameter structure for configuration */ ret = HFCreateImageStreamFromImageBitmap(image, rotation_enum, &imageHandle); if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "Create ImageStream error: %d", ret); return ret; } - // Execute HF_FaceContextRunFaceTrack captures face information in an image - HFMultipleFaceData multipleFaceData = {0}; + /* Execute HF_FaceContextRunFaceTrack captures face information in an image */ ret = HFExecuteFaceTrack(session, imageHandle, &multipleFaceData); if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "Execute HFExecuteFaceTrack error: %d", ret); return ret; } - // Print the number of faces detected - auto faceNum = multipleFaceData.detectedNum; + /* Print the number of faces detected */ + faceNum = multipleFaceData.detectedNum; HFLogPrint(HF_LOG_INFO, "Num of face: %d", faceNum); - // Copy a new image to draw - HFImageBitmap drawImage = {0}; + /* Copy a new image to draw */ ret = HFImageBitmapCopy(image, &drawImage); if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "Copy ImageBitmap error: %d", ret); return ret; } - HFImageBitmapData data; ret = HFImageBitmapGetData(drawImage, &data); if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "Get ImageBitmap data error: %d", ret); return ret; } - for (int index = 0; index < faceNum; ++index) { + for (index = 0; index < faceNum; ++index) { + HInt32 numOfLmk; + HPoint2f* denseLandmarkPoints; + HPoint2f fiveKeyPoints[5]; + float area; + size_t i; + HFLogPrint(HF_LOG_INFO, "========================================"); HFLogPrint(HF_LOG_INFO, "Token size: %d", multipleFaceData.tokens[index].size); HFLogPrint(HF_LOG_INFO, "Process face index: %d", index); HFLogPrint(HF_LOG_INFO, "DetConfidence: %f", multipleFaceData.detConfidence[index]); - HFImageBitmapDrawRect(drawImage, multipleFaceData.rects[index], {0, 100, 255}, 4); + + HFImageBitmapDrawRect(drawImage, multipleFaceData.rects[index], (HColor){0, 100, 255}, 4); - // Print FaceID, In IMAGE-MODE it is changing, in VIDEO-MODE it is fixed, but it may be lost - HFLogPrint(HF_LOG_INFO, "FaceID: %d", multipleFaceData.trackIds[index]); - - // Print Head euler angle, It can often be used to judge the quality of a face by the Angle - // of the head - HFLogPrint(HF_LOG_INFO, "Roll: %f, Yaw: %f, Pitch: %f", multipleFaceData.angles.roll[index], multipleFaceData.angles.yaw[index], - multipleFaceData.angles.pitch[index]); - - HInt32 numOfLmk; + /* Get the number of dense landmark points */ HFGetNumOfFaceDenseLandmark(&numOfLmk); - HPoint2f denseLandmarkPoints[numOfLmk]; + denseLandmarkPoints = (HPoint2f*)malloc(sizeof(HPoint2f) * numOfLmk); + if (denseLandmarkPoints == NULL) { + HFLogPrint(HF_LOG_ERROR, "Memory allocation failed!"); + return -1; + } + ret = HFGetFaceDenseLandmarkFromFaceToken(multipleFaceData.tokens[index], denseLandmarkPoints, numOfLmk); if (ret != HSUCCEED) { + free(denseLandmarkPoints); HFLogPrint(HF_LOG_ERROR, "HFGetFaceDenseLandmarkFromFaceToken error!!"); return -1; } - for (size_t i = 0; i < numOfLmk; i++) { - HFImageBitmapDrawCircleF(drawImage, {denseLandmarkPoints[i].x, denseLandmarkPoints[i].y}, 0, {100, 100, 0}, 2); + + /* Draw dense landmark points */ + for (i = 0; i < numOfLmk; i++) { + HFImageBitmapDrawCircleF(drawImage, + (HPoint2f){denseLandmarkPoints[i].x, denseLandmarkPoints[i].y}, + 0, + (HColor){100, 100, 0}, + 2); } - auto& rt = multipleFaceData.rects[index]; - float area = ((float)(rt.height * rt.width)) / (data.width * data.height); + free(denseLandmarkPoints); + + HFaceRect rt = multipleFaceData.rects[index]; + area = ((float)(rt.height * rt.width)) / (data.width * data.height); HFLogPrint(HF_LOG_INFO, "area: %f", area); - HPoint2f fiveKeyPoints[5]; ret = HFGetFaceFiveKeyPointsFromFaceToken(multipleFaceData.tokens[index], fiveKeyPoints, 5); if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "HFGetFaceFiveKeyPointsFromFaceToken error!!"); return -1; } - for (size_t i = 0; i < 5; i++) { - HFImageBitmapDrawCircleF(drawImage, {fiveKeyPoints[i].x, fiveKeyPoints[i].y}, 0, {0, 0, 232}, 2); + for (i = 0; i < 5; i++) { + HFImageBitmapDrawCircleF(drawImage, (HPoint2f){fiveKeyPoints[i].x, fiveKeyPoints[i].y}, 0, (HColor){0, 0, 232}, 2); } } HFImageBitmapWriteToFile(drawImage, "draw_detected.jpg"); HFLogPrint(HF_LOG_WARN, "Write to file success: %s", "draw_detected.jpg"); - // Run pipeline function - // Select the pipeline function that you want to execute, provided that it is already enabled - // when FaceContext is created! - auto pipelineOption = HF_ENABLE_QUALITY | HF_ENABLE_MASK_DETECT | HF_ENABLE_LIVENESS; - // In this loop, all faces are processed + /* Run pipeline function */ + /* Select the pipeline function that you want to execute, provided that it is already enabled + * when FaceContext is created! */ + pipelineOption = HF_ENABLE_QUALITY | HF_ENABLE_MASK_DETECT | HF_ENABLE_LIVENESS; + /* In this loop, all faces are processed */ ret = HFMultipleFacePipelineProcessOptional(session, imageHandle, &multipleFaceData, pipelineOption); if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "Execute Pipeline error: %d", ret); return ret; } - // Get mask detection results from the pipeline cache - HFFaceMaskConfidence maskConfidence = {0}; + /* Get mask detection results from the pipeline cache */ ret = HFGetFaceMaskConfidence(session, &maskConfidence); if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "Get mask detect result error: %d", ret); return -1; } - // Get face quality results from the pipeline cache - HFFaceQualityConfidence qualityConfidence = {0}; + /* Get face quality results from the pipeline cache */ ret = HFGetFaceQualityConfidence(session, &qualityConfidence); if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "Get face quality result error: %d", ret); return -1; } - for (int index = 0; index < faceNum; ++index) { + for (index = 0; index < faceNum; ++index) { HFLogPrint(HF_LOG_INFO, "========================================"); HFLogPrint(HF_LOG_INFO, "Process face index from pipeline: %d", index); HFLogPrint(HF_LOG_INFO, "Mask detect result: %f", maskConfidence.confidence[index]); HFLogPrint(HF_LOG_INFO, "Quality predict result: %f", qualityConfidence.confidence[index]); - // We set the threshold of wearing a mask as 0.85. If it exceeds the threshold, it will be - // judged as wearing a mask. The threshold can be adjusted according to the scene + /* We set the threshold of wearing a mask as 0.85. If it exceeds the threshold, it will be + * judged as wearing a mask. The threshold can be adjusted according to the scene */ if (maskConfidence.confidence[index] > 0.85) { HFLogPrint(HF_LOG_INFO, "Mask"); } else { @@ -205,7 +242,7 @@ int main(int argc, char* argv[]) { if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "Release image stream error: %d", ret); } - // The memory must be freed at the end of the program + /* The memory must be freed at the end of the program */ ret = HFReleaseInspireFaceSession(session); if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "Release session error: %d", ret); @@ -224,5 +261,8 @@ int main(int argc, char* argv[]) { return ret; } + HFLogPrint(HF_LOG_INFO, ""); + HFDeBugShowResourceStatistics(); + return 0; } diff --git a/cpp-package/inspireface/cpp/sample/api/sample_face_track_benchmark.cpp b/cpp-package/inspireface/cpp/sample/api/sample_face_track_benchmark.c similarity index 76% rename from cpp-package/inspireface/cpp/sample/api/sample_face_track_benchmark.cpp rename to cpp-package/inspireface/cpp/sample/api/sample_face_track_benchmark.c index 389020b..5ee19a2 100644 --- a/cpp-package/inspireface/cpp/sample/api/sample_face_track_benchmark.cpp +++ b/cpp-package/inspireface/cpp/sample/api/sample_face_track_benchmark.c @@ -1,31 +1,33 @@ -/** +/* * Created by Jingyu Yan * @date 2024-10-01 */ -#include +#include +#include #include int main(int argc, char* argv[]) { - // Check whether the number of parameters is correct + /* Check whether the number of parameters is correct */ if (argc < 3 || argc > 4) { HFLogPrint(HF_LOG_ERROR, "Usage: %s [rotation]", argv[0]); return 1; } - auto packPath = argv[1]; - auto sourcePath = argv[2]; + const char* packPath = argv[1]; + const char* sourcePath = argv[2]; int rotation = 0; - // If rotation is provided, check and set the value + /* If rotation is provided, check and set the value */ if (argc == 4) { - rotation = std::atoi(argv[3]); + rotation = atoi(argv[3]); if (rotation != 0 && rotation != 90 && rotation != 180 && rotation != 270) { HFLogPrint(HF_LOG_ERROR, "Invalid rotation value. Allowed values are 0, 90, 180, 270."); return 1; } } + HFRotation rotation_enum; - // Set rotation based on input parameter + /* Set rotation based on input parameter */ switch (rotation) { case 90: rotation_enum = HF_CAMERA_ROTATION_90; @@ -49,24 +51,24 @@ int main(int argc, char* argv[]) { HFSetLogLevel(HF_LOG_INFO); HResult ret; - // The resource file must be loaded before it can be used + /* The resource file must be loaded before it can be used */ ret = HFLaunchInspireFace(packPath); if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "Load Resource error: %d", ret); return ret; } - // Enable the functions in the pipeline: mask detection, live detection, and face quality - // detection - HOption option = HF_ENABLE_QUALITY | HF_ENABLE_MASK_DETECT | HF_ENABLE_LIVENESS | HF_ENABLE_DETECT_MODE_LANDMARK; - // Non-video or frame sequence mode uses IMAGE-MODE, which is always face detection without - // tracking + /* Enable the functions in the pipeline: mask detection, live detection, and face quality + * detection */ + HOption option = HF_ENABLE_QUALITY | HF_ENABLE_MASK_DETECT | HF_ENABLE_LIVENESS; + /* Non-video or frame sequence mode uses IMAGE-MODE, which is always face detection without + * tracking */ HFDetectMode detMode = HF_DETECT_MODE_ALWAYS_DETECT; - // Maximum number of faces detected + /* Maximum number of faces detected */ HInt32 maxDetectNum = 20; - // Face detection image input level + /* Face detection image input level */ HInt32 detectPixelLevel = 160; - // Handle of the current face SDK algorithm context + /* Handle of the current face SDK algorithm context */ HFSession session = {0}; ret = HFCreateInspireFaceSessionOptional(option, detMode, maxDetectNum, detectPixelLevel, -1, &session); if (ret != HSUCCEED) { @@ -77,14 +79,14 @@ int main(int argc, char* argv[]) { HFSessionSetTrackPreviewSize(session, detectPixelLevel); HFSessionSetFilterMinimumFacePixelSize(session, 4); - // Load a image + /* Load a image */ HFImageBitmap image; ret = HFCreateImageBitmapFromFilePath(sourcePath, 3, &image); if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "The source entered is not a picture or read error."); return ret; } - // Prepare an image parameter structure for configuration + /* Prepare an image parameter structure for configuration */ HFImageStream imageHandle = {0}; ret = HFCreateImageStreamFromImageBitmap(image, rotation_enum, &imageHandle); if (ret != HSUCCEED) { @@ -94,12 +96,13 @@ int main(int argc, char* argv[]) { int loop = 100; - // Enable the cost spend + /* Enable the cost spend */ HFSessionSetEnableTrackCostSpend(session, 1); - // Execute HF_FaceContextRunFaceTrack captures face information in an image + int i; + /* Execute HF_FaceContextRunFaceTrack captures face information in an image */ HFMultipleFaceData multipleFaceData = {0}; - for (int i = 0; i < loop; i++) { + for (i = 0; i < loop; i++) { ret = HFExecuteFaceTrack(session, imageHandle, &multipleFaceData); if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "Execute HFExecuteFaceTrack error: %d", ret); @@ -113,7 +116,7 @@ int main(int argc, char* argv[]) { if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "Release image stream error: %d", ret); } - // The memory must be freed at the end of the program + /* The memory must be freed at the end of the program */ ret = HFReleaseInspireFaceSession(session); if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "Release session error: %d", ret); diff --git a/cpp-package/inspireface/cpp/sample/api/sample_feature_hub.cpp b/cpp-package/inspireface/cpp/sample/api/sample_feature_hub.c similarity index 96% rename from cpp-package/inspireface/cpp/sample/api/sample_feature_hub.cpp rename to cpp-package/inspireface/cpp/sample/api/sample_feature_hub.c index 72f8f9e..76aa718 100644 --- a/cpp-package/inspireface/cpp/sample/api/sample_feature_hub.cpp +++ b/cpp-package/inspireface/cpp/sample/api/sample_feature_hub.c @@ -1,8 +1,6 @@ -#include #include -#include -static std::vector FT = { +static float FT[] = { 0.0706566, 0.00640248, 0.0418103, -0.00597861, 0.0269879, 0.0187478, 0.0486305, 0.0349162, -0.0080779, -0.0550556, 0.0229963, -0.00683422, -0.0338589, 0.0533989, -0.0371725, 0.000972469, 0.0612415, 0.0389846, -0.00126743, -0.0128782, 0.0935529, 0.0588179, 0.0164787, -0.00732871, -0.0458209, -0.0100137, -0.0372892, 0.000871123, 0.0245121, -0.0811471, -0.00481095, 0.0266868, 0.0712961, @@ -73,12 +71,10 @@ int main() { return ret; } - // std::vector feature(512, 0.0f); - int64_t result_id = 0; HFFaceFeature feature = {0}; - feature.data = FT.data(); - feature.size = FT.size(); + feature.data = FT; + feature.size = sizeof(FT) / sizeof(FT[0]); HFFaceFeatureIdentity identity = {0}; identity.feature = &feature; ret = HFFeatureHubInsertFeature(identity, &result_id); @@ -87,10 +83,9 @@ int main() { return ret; } - // std::vector query_feature(512, 20.0f); HFFaceFeature query_feature = {0}; - query_feature.data = FT.data(); - query_feature.size = FT.size(); + query_feature.data = FT; + query_feature.size = sizeof(FT) / sizeof(FT[0]); HFloat confidence; HFFaceFeatureIdentity search_result = {0}; ret = HFFeatureHubFaceSearch(query_feature, &confidence, &search_result); diff --git a/cpp-package/inspireface/cpp/sample/api/sample_feature_hub_persistence.c b/cpp-package/inspireface/cpp/sample/api/sample_feature_hub_persistence.c new file mode 100644 index 0000000..ce3361c --- /dev/null +++ b/cpp-package/inspireface/cpp/sample/api/sample_feature_hub_persistence.c @@ -0,0 +1,52 @@ +#include +#include +#include + +int main(int argc, char* argv[]) { + if (argc != 2) { + HFLogPrint(HF_LOG_ERROR, "Usage: %s ", argv[0]); + return -1; + } + + const char* packPath = argv[1]; + HResult ret; + ret = HFLaunchInspireFace(packPath); + if (ret != HSUCCEED) { + HFLogPrint(HF_LOG_ERROR, "Load Resource error: %d", ret); + return ret; + } + const char* DBFilePath = "feature.db"; + + // remove old db file + if (access(DBFilePath, F_OK) == 0) { + if (remove(DBFilePath) != 0) { + HFLogPrint(HF_LOG_ERROR, "Failed to remove old db file: %s", DBFilePath); + return -1; + } + HFLogPrint(HF_LOG_INFO, "Remove old db file: %s", DBFilePath); + } + + HFFeatureHubConfiguration featureHubConfiguration; + featureHubConfiguration.primaryKeyMode = HF_PK_AUTO_INCREMENT; + featureHubConfiguration.enablePersistence = 1; + featureHubConfiguration.persistenceDbPath = DBFilePath; + featureHubConfiguration.searchMode = HF_SEARCH_MODE_EAGER; + featureHubConfiguration.searchThreshold = 0.48f; + + ret = HFFeatureHubDataEnable(featureHubConfiguration); + if (ret != HSUCCEED) + { + HFLogPrint(HF_LOG_ERROR, "Enable FeatureHub failed: %d\n", ret); + return ret; + } + if (access(DBFilePath, F_OK) != 0) { + HFLogPrint(HF_LOG_ERROR, "DB file not found: %s", DBFilePath); + return -1; + } + HFLogPrint(HF_LOG_INFO, "DB file found: %s", DBFilePath); + + // .... + + HFTerminateInspireFace(); + return 0; +} \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/sample/api/sample_load_reload.cpp b/cpp-package/inspireface/cpp/sample/api/sample_load_reload.c similarity index 74% rename from cpp-package/inspireface/cpp/sample/api/sample_load_reload.cpp rename to cpp-package/inspireface/cpp/sample/api/sample_load_reload.c index c62a174..607170f 100644 --- a/cpp-package/inspireface/cpp/sample/api/sample_load_reload.cpp +++ b/cpp-package/inspireface/cpp/sample/api/sample_load_reload.c @@ -1,9 +1,8 @@ -#include #include int main() { - std::string resourcePath = "test_res/pack/Pikachu"; - HResult ret = HFReloadInspireFace(resourcePath.c_str()); + const char* resourcePath = "test_res/pack/Pikachu"; + HResult ret = HFReloadInspireFace(resourcePath); if (ret != HSUCCEED) { HFLogPrint(HF_LOG_ERROR, "Failed to launch InspireFace: %d", ret); return 1; diff --git a/cpp-package/inspireface/cpp/sample/cluttered/rk_sample/debug_rk_rec.cpp b/cpp-package/inspireface/cpp/sample/cluttered/rk_sample/debug_rk_rec.cpp index 7a19301..ff761f6 100644 --- a/cpp-package/inspireface/cpp/sample/cluttered/rk_sample/debug_rk_rec.cpp +++ b/cpp-package/inspireface/cpp/sample/cluttered/rk_sample/debug_rk_rec.cpp @@ -91,7 +91,7 @@ int main() { m_extract_ = std::make_shared(); InspireModel model; loader.LoadModel("feature", model); - m_extract_->loadData(model, InferenceWrapper::INFER_RKNN); + m_extract_->LoadData(model, InferenceWrapper::INFER_RKNN); cv::Mat image = cv::imread(names[0]); // cv::Mat rgb; diff --git a/cpp-package/inspireface/cpp/sample/cluttered/rk_sample/rk_face_det_sample.cpp b/cpp-package/inspireface/cpp/sample/cluttered/rk_sample/rk_face_det_sample.cpp index 96fdf1d..c06decf 100644 --- a/cpp-package/inspireface/cpp/sample/cluttered/rk_sample/rk_face_det_sample.cpp +++ b/cpp-package/inspireface/cpp/sample/cluttered/rk_sample/rk_face_det_sample.cpp @@ -31,7 +31,7 @@ int main() { std::shared_ptr m_face_detector_; m_face_detector_ = std::make_shared(320); - m_face_detector_->loadData(model, InferenceWrapper::INFER_RKNN); + m_face_detector_->LoadData(model, InferenceWrapper::INFER_RKNN); // Load a image cv::Mat image = cv::imread("test_res/images/face_sample.png"); diff --git a/cpp-package/inspireface/cpp/sample/cluttered/rk_sample/rk_face_recognize_sample.cpp b/cpp-package/inspireface/cpp/sample/cluttered/rk_sample/rk_face_recognize_sample.cpp index deb9e3f..8276f84 100644 --- a/cpp-package/inspireface/cpp/sample/cluttered/rk_sample/rk_face_recognize_sample.cpp +++ b/cpp-package/inspireface/cpp/sample/cluttered/rk_sample/rk_face_recognize_sample.cpp @@ -35,7 +35,7 @@ void rec_function() { m_extract_ = std::make_shared(); InspireModel model; loader->LoadModel("feature", model); - m_extract_->loadData(model, InferenceWrapper::INFER_RKNN); + m_extract_->LoadData(model, InferenceWrapper::INFER_RKNN); loader.reset(); diff --git a/cpp-package/inspireface/cpp/sample/cluttered/rk_sample/rk_simple_net_sample.cpp b/cpp-package/inspireface/cpp/sample/cluttered/rk_sample/rk_simple_net_sample.cpp index a78de10..fae87aa 100644 --- a/cpp-package/inspireface/cpp/sample/cluttered/rk_sample/rk_simple_net_sample.cpp +++ b/cpp-package/inspireface/cpp/sample/cluttered/rk_sample/rk_simple_net_sample.cpp @@ -35,7 +35,7 @@ void test_rnet() { InspireModel model; loader.LoadModel("refine_net", model); m_rnet_ = std::make_shared(); - m_rnet_->loadData(model, InferenceWrapper::INFER_RKNN); + m_rnet_->LoadData(model, InferenceWrapper::INFER_RKNN); { // Load a image @@ -78,7 +78,7 @@ void test_mask() { m_mask_predict_ = std::make_shared(); InspireModel model; loader.LoadModel("mask_detect", model); - m_mask_predict_->loadData(model, InferenceWrapper::INFER_RKNN); + m_mask_predict_->LoadData(model, InferenceWrapper::INFER_RKNN); { // Load a image @@ -120,7 +120,7 @@ void test_quality() { m_face_quality_ = std::make_shared(); InspireModel model; loader.LoadModel("pose_quality", model); - m_face_quality_->loadData(model, InferenceWrapper::INFER_RKNN); + m_face_quality_->LoadData(model, InferenceWrapper::INFER_RKNN); { std::vector names = { @@ -166,7 +166,7 @@ void test_landmark_mnn() { m_landmark_predictor_ = std::make_shared(112); InspireModel model; loader.LoadModel("landmark", model); - m_landmark_predictor_->loadData(model); + m_landmark_predictor_->LoadData(model); cv::Mat image = cv::imread("test_res/images/test_data/crop.png"); cv::resize(image, image, cv::Size(112, 112)); @@ -206,7 +206,7 @@ void test_landmark() { m_landmark_predictor_ = std::make_shared(112); InspireModel model; loader.LoadModel("landmark", model); - m_landmark_predictor_->loadData(model, InferenceWrapper::INFER_RKNN); + m_landmark_predictor_->LoadData(model, InferenceWrapper::INFER_RKNN); cv::Mat image = cv::imread("test_res/images/test_data/0.jpg"); cv::resize(image, image, cv::Size(112, 112)); @@ -248,7 +248,7 @@ void test_liveness() { InspireModel model; loader.LoadModel("rgb_anti_spoofing", model); m_rgb_anti_spoofing_ = std::make_shared(80, true); - m_rgb_anti_spoofing_->loadData(model, InferenceWrapper::INFER_RKNN); + m_rgb_anti_spoofing_->LoadData(model, InferenceWrapper::INFER_RKNN); std::vector names = { "test_res/images/test_data/real.jpg", "test_res/images/test_data/fake.jpg", "test_res/images/test_data/live.jpg", diff --git a/cpp-package/inspireface/cpp/sample/cluttered/standard/net_sample.cpp b/cpp-package/inspireface/cpp/sample/cluttered/standard/net_sample.cpp index e4a45f0..a7f82e2 100644 --- a/cpp-package/inspireface/cpp/sample/cluttered/standard/net_sample.cpp +++ b/cpp-package/inspireface/cpp/sample/cluttered/standard/net_sample.cpp @@ -27,7 +27,7 @@ int main(int argc, char** argv) { auto m_pose_net_ = std::make_shared(); InspireModel model; loader.LoadModel("", model); - m_pose_net_->loadData(model); + m_pose_net_->LoadData(model); auto image = cv::imread("resource/images/crop.png"); diff --git a/cpp-package/inspireface/cpp/sample/cluttered/standard/test_sample.cpp b/cpp-package/inspireface/cpp/sample/cluttered/standard/test_sample.cpp index 7c6131d..6b08b9b 100644 --- a/cpp-package/inspireface/cpp/sample/cluttered/standard/test_sample.cpp +++ b/cpp-package/inspireface/cpp/sample/cluttered/standard/test_sample.cpp @@ -30,10 +30,10 @@ int main(int argc, char** argv) { stream.SetDataBuffer(rot90.data, rot90.rows, rot90.cols); ctx.FaceDetectAndTrack(stream); - std::vector faces; + std::vector faces; for (int i = 0; i < ctx.GetNumberOfFacesCurrentlyDetected(); ++i) { // const ByteArray &byteArray = ctx.GetDetectCache()[i]; - HyperFaceData face = {0}; + FaceTrackWrap face = {0}; // ret = DeserializeHyperFaceData(byteArray, face); const FaceBasicData& faceBasic = ctx.GetFaceBasicDataCache()[i]; diff --git a/cpp-package/inspireface/cpp/sample/cpp_api/cpp_sample_affine.cpp b/cpp-package/inspireface/cpp/sample/cpp_api/cpp_sample_affine.cpp new file mode 100644 index 0000000..4bdcf6b --- /dev/null +++ b/cpp-package/inspireface/cpp/sample/cpp_api/cpp_sample_affine.cpp @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include +#include +#include "inspireface/track_module/landmark/order_of_hyper_landmark.h" + +int main(int argc, char** argv) { + if (argc != 3) { + std::cout << "Usage: " << argv[0] << " " << std::endl; + return -1; + } + + std::string model_path = argv[1]; + std::string image_path = argv[2]; + + // Global init(only once) + INSPIREFACE_CONTEXT->Reload(model_path); + + // Create image and frame process + inspirecv::Image image = inspirecv::Image::Create(image_path); + inspirecv::FrameProcess process = + inspirecv::FrameProcess::Create(image.Data(), image.Height(), image.Width(), inspirecv::BGR, inspirecv::ROTATION_0); + + // Create session + inspire::CustomPipelineParameter param; + param.enable_recognition = true; + param.enable_liveness = true; + param.enable_mask_detect = true; + param.enable_face_attribute = true; + param.enable_face_quality = true; + param.enable_interaction_liveness = true; + std::shared_ptr session(inspire::Session::CreatePtr(inspire::DETECT_MODE_ALWAYS_DETECT, 1, param, 320)); + + INSPIREFACE_CHECK_MSG(session != nullptr, "Session is not valid"); + + // Detect and track + std::vector results; + int32_t ret; + ret = session->FaceDetectAndTrack(process, results); + INSPIREFACE_CHECK_MSG(ret == 0, "FaceDetectAndTrack failed"); + + auto first = results[0]; + auto lmk = session->GetFaceDenseLandmark(first); + std::cout << "lmk: " << lmk.size() << std::endl; + for (size_t i = 0; i < lmk.size(); i++) { + image.DrawCircle(lmk[i].As(), 5, inspirecv::Color::Red); + } + + inspirecv::TransformMatrix rotation_mode_affine = process.GetAffineMatrix(); + + std::vector stand_lmk = ApplyTransformToPoints(lmk, rotation_mode_affine.GetInverse()); + + // Use total lmk + auto rect = inspirecv::MinBoundingRect(stand_lmk); + auto rect_pts = rect.As().ToFourVertices(); + std::vector dst_pts = {{0, 0}, {112, 0}, {112, 112}, {0, 112}}; + std::vector camera_pts = ApplyTransformToPoints(rect_pts, rotation_mode_affine); + + auto affine = inspirecv::SimilarityTransformEstimate(camera_pts, dst_pts); + auto image_affine = process.ExecuteImageAffineProcessing(affine, 112, 112); + image_affine.Write("affine.jpg"); + + // image.DrawRect(rect.As(), inspirecv::Color::Red); + // image.Write("lmk.jpg"); + + std::vector points; + for (const auto& idx : inspire::HLMK_LEFT_EYE_POINTS_INDEX) { + points.emplace_back(stand_lmk[idx].GetX(), stand_lmk[idx].GetY()); + } + std::cout << "points: " << points.size() << std::endl; + auto rect_eye = inspirecv::MinBoundingRect(points).Square(1.4f); + // draw debug + image.DrawRect(rect_eye.As(), inspirecv::Color::Red); + auto rect_pts_eye = rect_eye.As().ToFourVertices(); + std::vector dst_pts_eye = {{0, 0}, {64, 0}, {64, 64}, {0, 64}}; + std::vector camera_pts_eye = ApplyTransformToPoints(rect_pts_eye, rotation_mode_affine); + + auto affine_eye = inspirecv::SimilarityTransformEstimate(camera_pts_eye, dst_pts_eye); + auto eye_affine = process.ExecuteImageAffineProcessing(affine_eye, 64, 64); + eye_affine.Write("eye.jpg"); + return 0; +} diff --git a/cpp-package/inspireface/cpp/sample/cpp_api/cpp_sample_face_comparison.cpp b/cpp-package/inspireface/cpp/sample/cpp_api/cpp_sample_face_comparison.cpp new file mode 100644 index 0000000..e629fe4 --- /dev/null +++ b/cpp-package/inspireface/cpp/sample/cpp_api/cpp_sample_face_comparison.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) { + if (argc != 4) { + std::cout << "Usage: " << argv[0] << " " << std::endl; + return -1; + } + + std::string model_path = argv[1]; + std::string image_path1 = argv[2]; + std::string image_path2 = argv[3]; + + // Global init(only once) + INSPIREFACE_CONTEXT->Reload(model_path); + + // Create image and frame process + inspirecv::Image image1 = inspirecv::Image::Create(image_path1); + inspirecv::Image image2 = inspirecv::Image::Create(image_path2); + inspirecv::FrameProcess process1 = inspirecv::FrameProcess::Create(image1.Data(), image1.Height(), image1.Width(), inspirecv::BGR, inspirecv::ROTATION_0); + inspirecv::FrameProcess process2 = inspirecv::FrameProcess::Create(image2.Data(), image2.Height(), image2.Width(), inspirecv::BGR, inspirecv::ROTATION_0); + + // Create session + inspire::CustomPipelineParameter param; + param.enable_recognition = true; + + // Create session + std::shared_ptr session( + inspire::Session::CreatePtr(inspire::DETECT_MODE_ALWAYS_DETECT, 1, param, 320)); + + INSPIREFACE_CHECK_MSG(session != nullptr, "Session is not valid"); + + // Detect and track + std::vector results1; + std::vector results2; + + // Detect and track + session->FaceDetectAndTrack(process1, results1); + session->FaceDetectAndTrack(process2, results2); + + INSPIREFACE_CHECK_MSG(!results1.empty() && !results2.empty(), "No face detected"); + + // Get feature + inspire::FaceEmbedding feature1; + inspire::FaceEmbedding feature2; + session->FaceFeatureExtract(process1, results1[0], feature1); + session->FaceFeatureExtract(process2, results2[0], feature2); + + // Compare + float similarity; + INSPIREFACE_FEATURE_HUB->CosineSimilarity(feature1.embedding, feature2.embedding, similarity); + std::cout << "cosine of similarity: " << similarity << std::endl; + std::cout << "percentage of similarity: " << SIMILARITY_CONVERTER_RUN(similarity) << std::endl; + + std::cout << "== using alignment image ==" << std::endl; + + // Get face alignment image + inspirecv::Image wrapped1; + inspirecv::Image wrapped2; + session->GetFaceAlignmentImage(process1, results1[0], wrapped1); + session->GetFaceAlignmentImage(process2, results2[0], wrapped2); + wrapped1.Write("wrapped1.jpg"); + wrapped2.Write("wrapped2.jpg"); + + inspire::FaceEmbedding feature1_alignment; + inspire::FaceEmbedding feature2_alignment; + session->FaceFeatureExtractWithAlignmentImage(wrapped1, feature1_alignment); + session->FaceFeatureExtractWithAlignmentImage(wrapped2, feature2_alignment); + + INSPIREFACE_FEATURE_HUB->CosineSimilarity(feature1_alignment.embedding, feature2_alignment.embedding, similarity); + std::cout << "cosine of similarity: " << similarity << std::endl; + std::cout << "percentage of similarity: " << SIMILARITY_CONVERTER_RUN(similarity) << std::endl; + return 0; +} \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/sample/cpp_api/cpp_sample_face_crud.cpp b/cpp-package/inspireface/cpp/sample/cpp_api/cpp_sample_face_crud.cpp new file mode 100644 index 0000000..3f92da2 --- /dev/null +++ b/cpp-package/inspireface/cpp/sample/cpp_api/cpp_sample_face_crud.cpp @@ -0,0 +1,80 @@ +#include +#include + +int main() { + // Launch InspireFace + std::string model_path = "test_res/pack/Pikachu"; + INSPIREFACE_CONTEXT->Reload(model_path); + INSPIREFACE_CHECK_MSG(INSPIREFACE_CONTEXT->isMLoad(), "InspireFace is not loaded"); + + // Enable feature hub + std::string db_path = "case_crud.db"; + // Remove the database file if it exists + if (std::remove(db_path.c_str()) != 0) { + std::cerr << "Error removing database file: " << db_path << std::endl; + } + inspire::DatabaseConfiguration db_config; + db_config.enable_persistence = true; + db_config.persistence_db_path = db_path; + db_config.search_mode = inspire::SEARCH_MODE_EXHAUSTIVE; + db_config.recognition_threshold = 0.48f; + db_config.primary_key_mode = inspire::AUTO_INCREMENT; + INSPIREFACE_FEATURE_HUB->EnableHub(db_config); + + // Create a session + auto param = inspire::CustomPipelineParameter(); + param.enable_recognition = true; + auto session = inspire::Session::CreatePtr(inspire::DETECT_MODE_ALWAYS_DETECT, 1, param, 320); + INSPIREFACE_CHECK_MSG(session != nullptr, "Session is not created"); + + // Prepare an image for insertion into the hub + auto image = inspirecv::Image::Create("test_res/data/bulk/kun.jpg"); + auto image_process = inspirecv::FrameProcess::Create(image.Data(), image.Height(), image.Width(), inspirecv::BGR, inspirecv::ROTATION_0); + + // Detect and track + std::vector results; + session->FaceDetectAndTrack(image_process, results); + INSPIREFACE_CHECK_MSG(results.size() > 0, "No face detected"); + + // Extract face feature + inspire::FaceEmbedding feature; + session->FaceFeatureExtract(image_process, results[0], feature); + + // Insert face feature into the hub, because the id is INSPIRE_INVALID_ID, so input id is ignored + int64_t result_id; + INSPIREFACE_FEATURE_HUB->FaceFeatureInsert(feature.embedding, INSPIRE_INVALID_ID, result_id); + + // Prepare a photo of the same person for the query + auto query_image = inspirecv::Image::Create("test_res/data/bulk/jntm.jpg"); + auto query_image_process = inspirecv::FrameProcess::Create(query_image.Data(), query_image.Height(), query_image.Width(), inspirecv::BGR, inspirecv::ROTATION_0); + + // Detect and track + std::vector query_results; + session->FaceDetectAndTrack(query_image_process, query_results); + INSPIREFACE_CHECK_MSG(query_results.size() > 0, "No face detected"); + + // Extract face feature + inspire::FaceEmbedding query_feature; + session->FaceFeatureExtract(query_image_process, query_results[0], query_feature); + + // Search face feature + inspire::FaceSearchResult search_result; + INSPIREFACE_FEATURE_HUB->SearchFaceFeature(query_feature.embedding, search_result, true); + std::cout << "Search face feature result: " << search_result.id << std::endl; + std::cout << "Search face feature similarity: " << search_result.similarity << std::endl; + + INSPIREFACE_CHECK_MSG(search_result.id == result_id, "Search face feature result id is not equal to the inserted id"); + + // Remove the face feature + INSPIREFACE_FEATURE_HUB->FaceFeatureRemove(result_id); + INSPIREFACE_CHECK_MSG(INSPIREFACE_FEATURE_HUB->GetFaceFeatureCount() == 0, "Face feature is not removed"); + + std::cout << "Remove face feature successfully" << std::endl; + + // Query again + INSPIREFACE_FEATURE_HUB->SearchFaceFeature(query_feature.embedding, search_result, true); + INSPIREFACE_CHECK_MSG(search_result.id == INSPIRE_INVALID_ID, "Search face feature result id is not equal to the inserted id"); + std::cout << "Query again, search face feature result: " << search_result.id << std::endl; + + return 0; +} \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/sample/cpp_api/cpp_sample_face_track.cpp b/cpp-package/inspireface/cpp/sample/cpp_api/cpp_sample_face_track.cpp new file mode 100644 index 0000000..bd7b707 --- /dev/null +++ b/cpp-package/inspireface/cpp/sample/cpp_api/cpp_sample_face_track.cpp @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) { + if (argc != 3) { + std::cout << "Usage: " << argv[0] << " " << std::endl; + return -1; + } + + std::string model_path = argv[1]; + std::string image_path = argv[2]; + + // Global init(only once) + INSPIREFACE_CONTEXT->Reload(model_path); + + // Create image and frame process + inspirecv::Image image = inspirecv::Image::Create(image_path); + inspirecv::FrameProcess process = + inspirecv::FrameProcess::Create(image.Data(), image.Height(), image.Width(), inspirecv::BGR, inspirecv::ROTATION_90); + + // Create session + inspire::CustomPipelineParameter param; + param.enable_recognition = true; + param.enable_liveness = true; + param.enable_mask_detect = true; + param.enable_face_attribute = true; + param.enable_face_quality = true; + std::shared_ptr session(inspire::Session::CreatePtr(inspire::DETECT_MODE_ALWAYS_DETECT, 100, param, 640)); + session->SetTrackPreviewSize(640); + + INSPIREFACE_CHECK_MSG(session != nullptr, "Session is not valid"); + + // Detect and track + std::vector results; + int32_t ret; + ret = session->FaceDetectAndTrack(process, results); + INSPIREFACE_CHECK_MSG(ret == 0, "FaceDetectAndTrack failed"); + + for (auto& result : results) { + std::cout << "result: " << result.trackId << std::endl; + std::cout << "quality: " << result.quality[0] << ", " << result.quality[1] << ", " << result.quality[2] << ", " << result.quality[3] << ", " + << result.quality[4] << std::endl; + inspirecv::Rect2i rect = inspirecv::Rect2i::Create(result.rect.x, result.rect.y, result.rect.width, result.rect.height); + std::cout << rect << std::endl; + image.DrawRect(rect, inspirecv::Color::Red); + inspirecv::TransformMatrix trans = inspirecv::TransformMatrix::Create(result.trans.m00, result.trans.m01, result.trans.tx, result.trans.m10, result.trans.m11, result.trans.ty); + std::cout << "trans: " << trans.GetInverse() << std::endl; + } + image.Write("result.jpg"); + + return 0; +} diff --git a/cpp-package/inspireface/cpp/sample/cpp_api/cpp_sample_inspirecv.cpp b/cpp-package/inspireface/cpp/sample/cpp_api/cpp_sample_inspirecv.cpp new file mode 100644 index 0000000..32927bc --- /dev/null +++ b/cpp-package/inspireface/cpp/sample/cpp_api/cpp_sample_inspirecv.cpp @@ -0,0 +1,200 @@ +#include +#include +#include +#ifdef _WIN32 +#include +#define CREATE_DIR(dir) _mkdir(dir) +#else +#include +#define CREATE_DIR(dir) mkdir(dir, 0777) +#endif + +int main() { + // Make directory + if (CREATE_DIR("cv") == -1) { + // If the directory already exists, it is not an error + if (errno != EEXIST) { + std::cerr << "Error creating directory" << std::endl; + return 1; + } + } + + /* Image I/O */ + + // Load image from file + // Load with 3 channels (BGR, like opencv) + inspirecv::Image img = inspirecv::Image::Create("test_res/data/bulk/kun_cartoon_crop.jpg", 3); + + // Load image from buffer + // uint8_t* buffer = ...; // buffer is a pointer to the image data + // bool is_alloc_mem = false; // if true, will allocate memory for the image data, + // // false is recommended to point to the original data to avoid copying + // inspirecv::Image img_buffer = inspirecv::Image::Create(width, height, channel, buffer, is_alloc_mem); + + // Save image to file + img.Write("cv/output.jpg"); + + // Show image, warning: it must depend on opencv + // img.Show("input"); + + // Get pointer to image data + const uint8_t* ptr = img.Data(); + + /* Image Processing */ + // Convert to grayscale + inspirecv::Image gray = img.ToGray(); + gray.Write("cv/gray.jpg"); + + // Apply Gaussian blur + inspirecv::Image blurred = img.GaussianBlur(3, 1.0); + blurred.Write("cv/blurred.jpg"); + + // Geometric transformations + auto scale = 0.35; + bool use_bilinear = true; + inspirecv::Image resized = img.Resize(img.Width() * scale, img.Height() * scale, use_bilinear); // Resize image + resized.Write("cv/resized.jpg"); + + // Rotate 90 degrees clockwise + inspirecv::Image rotated = img.Rotate90(); + rotated.Write("cv/rotated.jpg"); + + // Flip vertically + inspirecv::Image flipped_vertical = img.FlipVertical(); + flipped_vertical.Write("cv/flipped_vertical.jpg"); + + // Flip horizontally + inspirecv::Image flipped_horizontal = img.FlipHorizontal(); + flipped_horizontal.Write("cv/flipped_horizontal.jpg"); + + // Crop for rectangle + inspirecv::Rect rect = inspirecv::Rect::Create(78, 41, 171, 171); + inspirecv::Image cropped = img.Crop(rect); + cropped.Write("cv/cropped.jpg"); + + // Image padding + int top = 50, bottom = 50, left = 50, right = 50; + inspirecv::Image padded = img.Pad(top, bottom, left, right, inspirecv::Color::Black); + padded.Write("cv/padded.jpg"); + + // Swap red and blue channels + inspirecv::Image swapped = img.SwapRB(); + swapped.Write("cv/swapped.jpg"); + + // Multiply image by scale factor + double scale_factor = 0.5; + inspirecv::Image scaled = img.Mul(scale_factor); + scaled.Write("cv/scaled.jpg"); + + // Add value to image + double value = -175; + inspirecv::Image added = img.Add(value); + added.Write("cv/added.jpg"); + + // Rotate 90 degrees clockwise(also support 270 and 180) + inspirecv::Image rotated_90 = img.Rotate90(); + rotated_90.Write("cv/rotated_90.jpg"); + + // Affine transform + /** + * Create a transform matrix from the following matrix + * [[a11, a12, tx], + * [a21, a22, ty]] + * + * Face crop transform matrix + * [[0.0, -1.37626, 261.127], + * [1.37626, 0.0, 85.1831]] + */ + float a11 = 0.0f; + float a12 = -1.37626f; + float a21 = 1.37626f; + float a22 = 0.0f; + float b1 = 261.127f; + float b2 = 85.1831f; + + inspirecv::TransformMatrix trans = inspirecv::TransformMatrix::Create(a11, a12, b1, a21, a22, b2); + int dst_width = 112; + int dst_height = 112; + inspirecv::Image affine = rotated_90.WarpAffine(trans, dst_width, dst_height); + affine.Write("cv/affine.jpg"); + + /* Image Draw */ + inspirecv::Image draw_img = img.Clone(); + + // Draw a rectangle + inspirecv::Rect new_rect = rect.Square(1.1f); // Square and expand the rect + int thickness = 3; + draw_img.DrawRect(new_rect, inspirecv::Color::Green, thickness); + draw_img.Write("cv/draw_rect.jpg"); + + // Draw a circle + draw_img = img.Clone(); + std::vector> points = new_rect.As().ToFourVertices(); + for (auto& point : points) { + draw_img.DrawCircle(point, 1, inspirecv::Color::Red, 5); + } + draw_img.Write("cv/draw_circle.jpg"); + + // Draw a line + draw_img = img.Clone(); + draw_img.DrawLine(points[0], points[1], inspirecv::Color::Cyan, 2); + draw_img.DrawLine(points[1], points[2], inspirecv::Color::Magenta, 2); + draw_img.DrawLine(points[2], points[3], inspirecv::Color::Pink, 2); + draw_img.DrawLine(points[3], points[0], inspirecv::Color::Yellow, 2); + draw_img.Write("cv/draw_line.jpg"); + + // Fill a rectangle + draw_img = img.Clone(); + draw_img.Fill(new_rect, inspirecv::Color::Purple); + draw_img.Write("cv/fill_rect.jpg"); + + // Reset + std::vector gray_color(img.Width() * img.Height() * 3, 128); + img.Reset(img.Width(), img.Height(), 3, gray_color.data()); + img.Write("cv/reset.jpg"); + + /** FrameProcess */ + + // BGR888 as raw data + inspirecv::Image raw = inspirecv::Image::Create("test_res/data/bulk/kun_cartoon_crop_r90.jpg", 3); + const uint8_t* buffer = raw.Data(); + + // You can also use other image format, like NV21, NV12, RGBA, RGB, BGR, BGRA + // const uint8_t* buffer = ...; + + // Create frame process + auto width = raw.Width(); + auto height = raw.Height(); + auto rotation_mode = inspirecv::ROTATION_90; + auto data_format = inspirecv::BGR; + inspirecv::FrameProcess frame_process = inspirecv::FrameProcess::Create(buffer, height, width, data_format, rotation_mode); + + // Set preview size + frame_process.SetPreviewSize(160); + + // Set preview scale + // frame_process.SetPreviewScale(0.5f); + + // Get transform image + inspirecv::Image transform_img = frame_process.ExecutePreviewImageProcessing(true); + transform_img.Write("cv/transform_img.jpg"); + + // ExecuteImageAffineProcessing + + // Face crop transform matrix + // [[0.0, 0.726607, -61.8946], + // [-0.726607, 0.0, 189.737]] + a11 = 0.0f; + a12 = 0.726607f; + a21 = -0.726607; + a22 = 0.0f; + b1 = -61.8946f; + b2 = 189.737f; + inspirecv::TransformMatrix affine_matrix = inspirecv::TransformMatrix::Create(a11, a12, b1, a21, a22, b2); + dst_width = 112; + dst_height = 112; + inspirecv::Image affine_img = frame_process.ExecuteImageAffineProcessing(affine_matrix, dst_width, dst_height); + affine_img.Write("cv/affine_img.jpg"); + + return 0; +} diff --git a/cpp-package/inspireface/cpp/sample/rv1106/face_attribute.cpp b/cpp-package/inspireface/cpp/sample/rv1106/face_attribute.cpp index 39df3e1..8929b46 100644 --- a/cpp-package/inspireface/cpp/sample/rv1106/face_attribute.cpp +++ b/cpp-package/inspireface/cpp/sample/rv1106/face_attribute.cpp @@ -1,8 +1,9 @@ #include #include -#include "inspireface/initialization_module/launch.h" -#include -#include +#include +#include +#include +#include #include using namespace inspire; @@ -10,8 +11,8 @@ using namespace inspire; int main() { INSPIRE_SET_LOG_LEVEL(ISF_LOG_DEBUG); std::string expansion_path = ""; - INSPIRE_LAUNCH->Load("test_res/pack/Gundam_RV1106"); - auto archive = INSPIRE_LAUNCH->getMArchive(); + INSPIREFACE_CONTEXT->Load("test_res/pack/Gundam_RV1106"); + auto archive = INSPIREFACE_CONTEXT->getMArchive(); InspireModel detModel; auto ret = archive.LoadModel("face_attribute", detModel); if (ret != SARC_SUCCESS) { @@ -20,7 +21,7 @@ int main() { } FaceAttributePredictAdapt face_attribute; - face_attribute.loadData(detModel, detModel.modelType, false); + face_attribute.LoadData(detModel, detModel.modelType, false); auto img = inspirecv::Image::Create("test_res/data/crop/crop.png"); auto result = face_attribute(img); diff --git a/cpp-package/inspireface/cpp/sample/rv1106/face_detect.cpp b/cpp-package/inspireface/cpp/sample/rv1106/face_detect.cpp index 17778a5..778f9b4 100644 --- a/cpp-package/inspireface/cpp/sample/rv1106/face_detect.cpp +++ b/cpp-package/inspireface/cpp/sample/rv1106/face_detect.cpp @@ -1,16 +1,17 @@ #include #include -#include "inspireface/initialization_module/launch.h" -#include -#include +#include +#include +#include +#include using namespace inspire; int main() { INSPIRE_SET_LOG_LEVEL(ISF_LOG_DEBUG); std::string expansion_path = ""; - INSPIRE_LAUNCH->Load("test_res/pack/Gundam_RV1106"); - auto archive = INSPIRE_LAUNCH->getMArchive(); + INSPIREFACE_CONTEXT->Load("test_res/pack/Gundam_RV1106"); + auto archive = INSPIREFACE_CONTEXT->getMArchive(); InspireModel detModel; auto ret = archive.LoadModel("face_detect_160", detModel); if (ret != SARC_SUCCESS) { @@ -21,7 +22,7 @@ int main() { std::vector input_size; input_size = detModel.Config().get>("input_size"); - ret = face_detect.loadData(detModel, detModel.modelType, false); + ret = face_detect.LoadData(detModel, detModel.modelType, false); if (ret != 0) { INSPIRE_LOGE("Load %s error: %d", "face_detect_160", ret); return HERR_ARCHIVE_LOAD_MODEL_FAILURE; @@ -31,7 +32,7 @@ int main() { auto img = inspirecv::Image::Create("data/bulk/kun.jpg"); - inspirecv::TimeSpend time_spend("Detect"); + inspire::SpendTimer time_spend("Detect"); FaceLocList results; for (int i = 0; i < 10; i++) { time_spend.Start(); diff --git a/cpp-package/inspireface/cpp/sample/rv1106/rga_image.cpp b/cpp-package/inspireface/cpp/sample/rv1106/rga_image.cpp index 204a63b..e4f265a 100644 --- a/cpp-package/inspireface/cpp/sample/rv1106/rga_image.cpp +++ b/cpp-package/inspireface/cpp/sample/rv1106/rga_image.cpp @@ -1,7 +1,8 @@ #include -#include +#include #include "log.h" -#include +#include +#include using namespace inspire; @@ -18,7 +19,7 @@ int main() { uint8_t* resized_data = nullptr; int resized_width = 100; int resized_height = 100; - inspirecv::TimeSpend time_spend("RGA resize"); + inspire::SpendTimer time_spend("RGA resize"); for (int i = 0; i < 10; i++) { time_spend.Start(); auto ret = processor->Resize(img.Data(), img.Width(), img.Height(), img.Channels(), &resized_data, resized_width, resized_height); @@ -37,7 +38,7 @@ int main() { processor->MarkDone(); uint8_t* swapped_data = nullptr; - inspirecv::TimeSpend swap_time_spend("RGA swap color"); + inspire::SpendTimer swap_time_spend("RGA swap color"); for (int i = 0; i < 10; i++) { swap_time_spend.Start(); auto ret = processor->SwapColor(resized_img.Data(), resized_img.Width(), resized_img.Height(), resized_img.Channels(), &swapped_data); @@ -57,7 +58,7 @@ int main() { int bottom = 10; int left = 10; int right = 10; - inspirecv::TimeSpend padding_time_spend("RGA padding"); + inspire::SpendTimer padding_time_spend("RGA padding"); int padded_width = 0; int padded_height = 0; for (int i = 0; i < 10; i++) { @@ -76,7 +77,7 @@ int main() { // inspirecv crop inspirecv::Rect2i rect(30, 30, 70, 70); - inspirecv::TimeSpend inspirecv_crop_time_spend("InspireCV crop"); + inspire::SpendTimer inspirecv_crop_time_spend("InspireCV crop"); inspirecv::Image inspirecv_cropped_img; for (int i = 0; i < 10; i++) { inspirecv_crop_time_spend.Start(); @@ -91,7 +92,7 @@ int main() { int dst_width = 320; int dst_height = 320; float scale = 0.0f; - inspirecv::TimeSpend padded_crop_time_spend("RGA padded and cropped"); + inspire::SpendTimer padded_crop_time_spend("RGA padded and cropped"); for (int i = 0; i < 10; i++) { padded_crop_time_spend.Start(); auto ret = processor->ResizeAndPadding(image.Data(), image.Width(), image.Height(), image.Channels(), dst_width, dst_height, @@ -110,7 +111,7 @@ int main() { uint8_t* resized_data_2 = nullptr; int resized_width_2 = 512; int resized_height_2 = 512; - inspirecv::TimeSpend time_spend_2("RGA resize 2"); + inspire::SpendTimer time_spend_2("RGA resize 2"); for (int i = 0; i < 10; i++) { time_spend_2.Start(); auto ret = processor->Resize(padded_cropped_img.Data(), padded_cropped_img.Width(), padded_cropped_img.Height(), diff --git a/cpp-package/inspireface/cpp/sample/source/expansion_load.cpp b/cpp-package/inspireface/cpp/sample/source/expansion_load.cpp index 0fd95b1..84b937b 100644 --- a/cpp-package/inspireface/cpp/sample/source/expansion_load.cpp +++ b/cpp-package/inspireface/cpp/sample/source/expansion_load.cpp @@ -1,26 +1,28 @@ #include -#include "inspireface/initialization_module/launch.h" +#include #include "inspireface/middleware/model_archive/inspire_archive.h" #include "inspireface/track_module/face_detect/face_detect_adapt.h" #include "inspireface/track_module/landmark/face_landmark_adapt.h" #include "inspireface/track_module/quality/face_pose_quality_adapt.h" #include "inspireface/recognition_module/extract/extract_adapt.h" +#include "inspireface/include/inspireface/spend_timer.h" void test_face_detect() { inspire::InspireModel model; - INSPIRE_LAUNCH->getMArchive().LoadModel("face_detect_160", model); + INSPIREFACE_CONTEXT->getMArchive().LoadModel("face_detect_160", model); auto input_size = 160; inspire::FaceDetectAdapt faceDetectAdapt(input_size); - faceDetectAdapt.loadData(model, model.modelType); + faceDetectAdapt.LoadData(model, model.modelType); inspirecv::Image image = inspirecv::Image::Create("test_res/data/bulk/kun.jpg"); inspire::FaceLocList faces; - inspirecv::TimeSpend timeSpend("Face Detect@" + std::to_string(input_size)); + inspire::SpendTimer timeSpend("Face Detect@" + std::to_string(input_size)); for (int i = 0; i < 1000; i++) { timeSpend.Start(); faces = faceDetectAdapt(image); timeSpend.Stop(); } std::cout << timeSpend << std::endl; + ; std::cout << "faces size: " << faces.size() << std::endl; for (auto &face : faces) { inspirecv::Rect2i rect = inspirecv::Rect2i::Create(face.x1, face.y1, face.x2 - face.x1, face.y2 - face.y1); @@ -31,20 +33,21 @@ void test_face_detect() { void test_landmark() { inspire::InspireModel model; - INSPIRE_LAUNCH->getMArchive().LoadModel("landmark", model); + INSPIREFACE_CONTEXT->getMArchive().LoadModel("landmark", model); auto input_size = 112; inspire::FaceLandmarkAdapt landmarkAdapt(input_size); - landmarkAdapt.loadData(model, model.modelType); + landmarkAdapt.LoadData(model, model.modelType); inspirecv::Image image = inspirecv::Image::Create("test_res/data/crop/crop.png"); image = image.Resize(input_size, input_size); std::vector lmk; - inspirecv::TimeSpend timeSpend("Landmark@" + std::to_string(input_size)); + inspire::SpendTimer timeSpend("Landmark@" + std::to_string(input_size)); timeSpend.Start(); for (int i = 0; i < 10; i++) { lmk = landmarkAdapt(image); } timeSpend.Stop(); std::cout << timeSpend << std::endl; + ; for (int i = 0; i < inspire::FaceLandmarkAdapt::NUM_OF_LANDMARK; i++) { auto p = inspirecv::Point2i::Create(lmk[i * 2] * input_size, lmk[i * 2 + 1] * input_size); image.DrawCircle(p, 5, {0, 0, 255}); @@ -54,20 +57,21 @@ void test_landmark() { void test_quality() { inspire::InspireModel model; - INSPIRE_LAUNCH->getMArchive().LoadModel("pose_quality", model); + INSPIREFACE_CONTEXT->getMArchive().LoadModel("pose_quality", model); auto input_size = 96; inspire::FacePoseQualityAdapt poseQualityAdapt; - poseQualityAdapt.loadData(model, model.modelType); + poseQualityAdapt.LoadData(model, model.modelType); inspirecv::Image image = inspirecv::Image::Create("test_res/data/crop/crop.png"); image = image.Resize(input_size, input_size); inspire::FacePoseQualityAdaptResult quality; - inspirecv::TimeSpend timeSpend("Pose Quality@" + std::to_string(input_size)); + inspire::SpendTimer timeSpend("Pose Quality@" + std::to_string(input_size)); timeSpend.Start(); for (int i = 0; i < 10; i++) { quality = poseQualityAdapt(image); } timeSpend.Stop(); std::cout << timeSpend << std::endl; + ; std::cout << "quality: " << quality.pitch << ", " << quality.yaw << ", " << quality.roll << std::endl; for (int i = 0; i < quality.lmk.size(); i++) { std::cout << "lmk: " << quality.lmk[i].GetX() << ", " << quality.lmk[i].GetY() << std::endl; @@ -79,15 +83,15 @@ void test_quality() { void test_feature() { inspire::InspireModel model; - INSPIRE_LAUNCH->getMArchive().LoadModel("feature", model); + INSPIREFACE_CONTEXT->getMArchive().LoadModel("feature", model); auto input_size = 112; inspire::ExtractAdapt extractAdapt; - extractAdapt.loadData(model, model.modelType); + extractAdapt.LoadData(model, model.modelType); inspirecv::Image image = inspirecv::Image::Create("test_res/data/crop/crop.png"); image = image.Resize(input_size, input_size); float norm; bool normalize = true; - inspirecv::TimeSpend timeSpend("Extract@" + std::to_string(input_size)); + inspire::SpendTimer timeSpend("Extract@" + std::to_string(input_size)); timeSpend.Start(); inspire::Embedded feature; for (int i = 0; i < 10; i++) { @@ -95,12 +99,13 @@ void test_feature() { } timeSpend.Stop(); std::cout << timeSpend << std::endl; + ; std::cout << "feature: " << feature.size() << std::endl; } int main() { std::string archivePath = "test_res/pack/Pikachu_Apple"; - INSPIRE_LAUNCH->Load(archivePath); + INSPIREFACE_CONTEXT->Load(archivePath); // Test face detect test_face_detect(); diff --git a/cpp-package/inspireface/cpp/sample/source/feature_hub_sample.cpp b/cpp-package/inspireface/cpp/sample/source/feature_hub_sample.cpp index 1e0b5c6..029c68b 100644 --- a/cpp-package/inspireface/cpp/sample/source/feature_hub_sample.cpp +++ b/cpp-package/inspireface/cpp/sample/source/feature_hub_sample.cpp @@ -1,10 +1,10 @@ #include #include -#include "inspireface/initialization_module/launch.h" -#include +#include +#include #include #include -#include +#include using namespace inspire; @@ -60,19 +60,19 @@ static std::vector FT = { int main() { std::string expansion_path = ""; - INSPIRE_LAUNCH->Load("test_res/pack/Pikachu"); + INSPIREFACE_CONTEXT->Load("test_res/pack/Pikachu"); DatabaseConfiguration configuration; configuration.primary_key_mode = PrimaryKeyMode::MANUAL_INPUT; configuration.enable_persistence = false; configuration.recognition_threshold = 0.48f; - FEATURE_HUB_DB->EnableHub(configuration); + INSPIREFACE_FEATURE_HUB->EnableHub(configuration); // std::vector feature(512, 0.0f); int64_t result_id = 0; - auto ret = FEATURE_HUB_DB->FaceFeatureInsert(FT, 10086, result_id); + auto ret = INSPIREFACE_FEATURE_HUB->FaceFeatureInsert(FT, 10086, result_id); if (ret != HSUCCEED) { INSPIRE_LOGE("Failed to insert face feature"); INSPIRE_LOGI("result id: %lld", result_id); @@ -82,7 +82,7 @@ int main() { // std::vector query_feature(512, 20.0f); FaceSearchResult search_result; - ret = FEATURE_HUB_DB->SearchFaceFeature(FT, search_result, true); + ret = INSPIREFACE_FEATURE_HUB->SearchFaceFeature(FT, search_result, true); if (ret != HSUCCEED) { INSPIRE_LOGE("Failed to search face feature"); } else { diff --git a/cpp-package/inspireface/cpp/sample/source/landmark_sample.cpp b/cpp-package/inspireface/cpp/sample/source/landmark_sample.cpp index 34bf093..e04aa37 100644 --- a/cpp-package/inspireface/cpp/sample/source/landmark_sample.cpp +++ b/cpp-package/inspireface/cpp/sample/source/landmark_sample.cpp @@ -1,12 +1,12 @@ #include #include -#include "inspireface/initialization_module/launch.h" -#include +#include +#include #include "inspireface/track_module/landmark/face_landmark_adapt.h" int main() { std::string expansion_path = ""; - INSPIRE_LAUNCH->Load("test_res/pack/Pikachu-t4"); - auto archive = INSPIRE_LAUNCH->getMArchive(); + INSPIREFACE_CONTEXT->Load("test_res/pack/Pikachu-t4"); + auto archive = INSPIREFACE_CONTEXT->getMArchive(); inspire::InspireModel lmkModel; auto ret = archive.LoadModel("landmark", lmkModel); @@ -16,7 +16,7 @@ int main() { } inspire::FaceLandmarkAdapt lmk; - lmk.loadData(lmkModel, lmkModel.modelType); + lmk.LoadData(lmkModel, lmkModel.modelType); auto image = inspirecv::Image::Create("test_res/data/crop/crop.png"); auto data = image.Resize(112, 112); diff --git a/cpp-package/inspireface/cpp/sample/source/tracker_pipeline.cpp b/cpp-package/inspireface/cpp/sample/source/tracker_pipeline.cpp index d36e6c2..07ee09b 100644 --- a/cpp-package/inspireface/cpp/sample/source/tracker_pipeline.cpp +++ b/cpp-package/inspireface/cpp/sample/source/tracker_pipeline.cpp @@ -1,7 +1,7 @@ #include #include -#include "inspireface/initialization_module/launch.h" -#include +#include +#include #include #include @@ -9,8 +9,8 @@ using namespace inspire; int main() { std::string expansion_path = ""; - INSPIRE_LAUNCH->Load("test_res/pack/Pikachu"); - auto archive = INSPIRE_LAUNCH->getMArchive(); + INSPIREFACE_CONTEXT->Load("test_res/pack/Pikachu"); + auto archive = INSPIREFACE_CONTEXT->getMArchive(); auto mode = inspire::DetectModuleMode::DETECT_MODE_LIGHT_TRACK; FaceTrackModule tracker(mode, 10, 20, 320, -1); tracker.Configuration(archive, expansion_path); @@ -18,7 +18,7 @@ int main() { FacePipelineModule pipe(archive, true, true, true, true); auto image = inspirecv::Image::Create("test_res/data/bulk/r90.jpg"); - inspirecv::InspireImageProcess processor; + inspirecv::FrameProcess processor; processor.SetDataBuffer(image.Data(), image.Height(), image.Width()); processor.SetDataFormat(inspirecv::DATA_FORMAT::BGR); processor.SetRotationMode(inspirecv::ROTATION_MODE::ROTATION_90); diff --git a/cpp-package/inspireface/cpp/sample/source/tracker_sample.cpp b/cpp-package/inspireface/cpp/sample/source/tracker_sample.cpp index f6d3f75..78ed291 100644 --- a/cpp-package/inspireface/cpp/sample/source/tracker_sample.cpp +++ b/cpp-package/inspireface/cpp/sample/source/tracker_sample.cpp @@ -1,20 +1,20 @@ #include #include -#include "inspireface/initialization_module/launch.h" -#include +#include +#include using namespace inspire; int main() { std::string expansion_path = ""; - INSPIRE_LAUNCH->Load("test_res/pack/Pikachu"); - auto archive = INSPIRE_LAUNCH->getMArchive(); + INSPIREFACE_CONTEXT->Load("test_res/pack/Pikachu"); + auto archive = INSPIREFACE_CONTEXT->getMArchive(); auto mode = inspire::DetectModuleMode::DETECT_MODE_ALWAYS_DETECT; FaceTrackModule tracker(mode, 10, 20, 320, -1); tracker.Configuration(archive, expansion_path); auto image = inspirecv::Image::Create("test_res/data/bulk/r0.jpg"); - inspirecv::InspireImageProcess processor; + inspirecv::FrameProcess processor; processor.SetDataBuffer(image.Data(), image.Height(), image.Width()); processor.SetDataFormat(inspirecv::DATA_FORMAT::BGR); processor.SetRotationMode(inspirecv::ROTATION_MODE::ROTATION_0); diff --git a/cpp-package/inspireface/cpp/test/CMakeLists.txt b/cpp-package/inspireface/cpp/test/CMakeLists.txt index 832e611..a6391d3 100644 --- a/cpp-package/inspireface/cpp/test/CMakeLists.txt +++ b/cpp-package/inspireface/cpp/test/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.20) project(InspireFaceTest) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3") @@ -8,6 +8,8 @@ option(ISF_ENABLE_BENCHMARK "Enable the benchmark test cases." ON) option(ISF_ENABLE_USE_LFW_DATA "Enable test cases for LFW data sets." OFF) # If you want to test the evaluation function, you need to set this to ON, need LFW data set. option(ISF_ENABLE_TEST_EVALUATION "Enable evaluation function test cases." OFF) +# If you want to test the base test cases, you need to set this to ON, if you want to hide the symbols, you need to set ISF_ENABLE_SYMBOL_HIDING to ON +option(ISF_ENABLE_TEST_INTERNAL "Enable internal test cases." OFF) if (ISF_ENABLE_BENCHMARK) add_definitions("-DISF_ENABLE_BENCHMARK") @@ -51,27 +53,26 @@ set(TEST_COMMON_FILES ${CMAKE_CURRENT_SOURCE_DIR}/settings/test_settings.cpp) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/settings) # =======================Internal Base Import Tests=========================== -file(GLOB_RECURSE INTERNAL_TEST_INTERNAL_FILES unit/base/*.cpp) -add_executable(TestBase ${CMAKE_CURRENT_SOURCE_DIR}/test_base.cpp ${INTERNAL_TEST_INTERNAL_FILES} ${TEST_COMMON_FILES}) -target_link_libraries(TestBase InspireFace ${DEPEND}) -target_include_directories(TestBase PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/ - ${ISF_THIRD_PARTY_DIR}/spdlog/include - ${ISF_THIRD_PARTY_DIR}/Catch2/single_include/ - ${ISF_THIRD_PARTY_DIR}/indicators/include/ - ${SRC_DIR} - ${INSPIRECV_INCLUDE_PATH} -) - -set_target_properties(TestBase PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/test/") - +if (ISF_ENABLE_TEST_INTERNAL) + file(GLOB_RECURSE INTERNAL_TEST_INTERNAL_FILES unit/base/*.cpp) + add_executable(TestInternal ${CMAKE_CURRENT_SOURCE_DIR}/test_base.cpp ${INTERNAL_TEST_INTERNAL_FILES} ${TEST_COMMON_FILES}) + target_link_libraries(TestInternal InspireFace ${DEPEND}) + target_include_directories(TestInternal PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/ + ${ISF_THIRD_PARTY_DIR}/spdlog/include + ${ISF_THIRD_PARTY_DIR}/Catch2/single_include/ + ${ISF_THIRD_PARTY_DIR}/indicators/include/ + ${SRC_DIR} + ${INSPIRECV_INCLUDE_PATH} + ) + set_target_properties(TestInternal PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/test/") +endif() # =======================External API Testing=========================== file(GLOB_RECURSE TEST_INTERNAL_FILES unit/api/*.cpp) -add_executable(Test ${CMAKE_CURRENT_SOURCE_DIR}/test.cpp ${TEST_INTERNAL_FILES} ${TEST_COMMON_FILES} - unit/api/test_evaluation.cpp) +add_executable(Test ${CMAKE_CURRENT_SOURCE_DIR}/test.cpp ${TEST_INTERNAL_FILES} ${TEST_COMMON_FILES}) target_link_libraries(Test InspireFace ${DEPEND}) target_include_directories(Test PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/ @@ -84,6 +85,20 @@ target_include_directories(Test PUBLIC set_target_properties(Test PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/test/") +# =======================CPP API Testing=========================== +file(GLOB_RECURSE TEST_CPP_FILES unit/cpp_api/*.cpp) +add_executable(TestCPP ${CMAKE_CURRENT_SOURCE_DIR}/test_cpp.cpp ${TEST_CPP_FILES} ${TEST_COMMON_FILES}) +target_link_libraries(TestCPP InspireFace ${DEPEND}) +target_include_directories(TestCPP PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/ + ${ISF_THIRD_PARTY_DIR}/spdlog/include + ${ISF_THIRD_PARTY_DIR}/Catch2/single_include/ + ${ISF_THIRD_PARTY_DIR}/indicators/include/ + ${SRC_DIR} + ) + +set_target_properties(TestCPP PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/test/") # Print Message message(STATUS ">>>>>>>>>>>>>") diff --git a/cpp-package/inspireface/cpp/test/settings/test_settings.h b/cpp-package/inspireface/cpp/test/settings/test_settings.h index 565c45b..28548b2 100644 --- a/cpp-package/inspireface/cpp/test/settings/test_settings.h +++ b/cpp-package/inspireface/cpp/test/settings/test_settings.h @@ -11,6 +11,7 @@ #include "enviro.h" #include "check.h" #include "inspireface/middleware/system.h" +#include "inspireface/include/inspireface/spend_timer.h" // Define the test model file #define TEST_MODEL_FILE Enviro::getInstance().getPackName() diff --git a/cpp-package/inspireface/cpp/test/test.cpp b/cpp-package/inspireface/cpp/test/test.cpp index 6c92dcf..2f0c14b 100644 --- a/cpp-package/inspireface/cpp/test/test.cpp +++ b/cpp-package/inspireface/cpp/test/test.cpp @@ -118,7 +118,7 @@ int main(int argc, char* argv[]) { TEST_ERROR_PRINT("An error occurred while starting InspireFace: {}", ret); return ret; } - + // Set log level HFSetLogLevel(HF_LOG_INFO); diff --git a/cpp-package/inspireface/cpp/test/test_base.cpp b/cpp-package/inspireface/cpp/test/test_base.cpp index 8768cd6..2fc8ccc 100644 --- a/cpp-package/inspireface/cpp/test/test_base.cpp +++ b/cpp-package/inspireface/cpp/test/test_base.cpp @@ -8,7 +8,7 @@ #include "settings/test_settings.h" #include #include "spdlog/spdlog.h" -#include "initialization_module/launch.h" +#include #define ENABLE_DRAW_SPLIT_LINE 1 // Whether dividers are printed during the test #define ENABLE_TEST_MSG 1 // TEST PRINT output @@ -30,7 +30,7 @@ int init_test_logger() { int main(int argc, char* argv[]) { init_test_logger(); - auto ret = INSPIRE_LAUNCH->Load("test_res/pack/Pikachu"); + auto ret = INSPIREFACE_CONTEXT->Load("test_res/pack/Pikachu"); if (ret != 0) { std::cerr << "Load error" << std::endl; return -1; diff --git a/cpp-package/inspireface/cpp/test/test_cpp.cpp b/cpp-package/inspireface/cpp/test/test_cpp.cpp new file mode 100644 index 0000000..ed0ce57 --- /dev/null +++ b/cpp-package/inspireface/cpp/test/test_cpp.cpp @@ -0,0 +1,138 @@ +/** + * Created by Jingyu Yan + * @date 2024-10-01 + */ +#include +#define CATCH_CONFIG_RUNNER + +#include +#include "settings/test_settings.h" +#include +#include "spdlog/spdlog.h" +#include +#include "unit/test_helper/simple_csv_writer.h" + +int init_test_logger() { + std::string name("TEST"); + auto stdout_sink = std::make_shared(); + auto logger = std::make_shared(name, stdout_sink); +#if ENABLE_TEST_MSG + logger->set_level(spdlog::level::trace); +#else + logger->set_level(spdlog::level::off); +#endif + logger->set_pattern("%Y-%m-%d %H:%M:%S.%e [Test Message] ===> %v"); + spdlog::register_logger(logger); + return 0; +} + +int init_test_benchmark_record() { +#ifdef ISF_ENABLE_BENCHMARK + if (std::remove(getBenchmarkRecordFile().c_str()) != 0) { + spdlog::trace("Error deleting file"); + } + BenchmarkRecord record(getBenchmarkRecordFile(), TEST_MODEL_FILE); +#endif + return 0; +} + +int init_test_evaluation_record() { +#ifdef ISF_ENABLE_TEST_EVALUATION + if (std::remove(getEvaluationRecordFile().c_str()) != 0) { + spdlog::trace("Error deleting file"); + } + EvaluationRecord record(getEvaluationRecordFile()); +#endif + return 0; +} + +int main(int argc, char* argv[]) { + init_test_logger(); + init_test_benchmark_record(); + init_test_evaluation_record(); + TEST_PRINT_OUTPUT(true); + + TEST_PRINT("InspireFace Version: v{}.{}.{}", + INSPIRE_FACE_VERSION_MAJOR_STR, + INSPIRE_FACE_VERSION_MINOR_STR, + INSPIRE_FACE_VERSION_PATCH_STR); + TEST_PRINT("Extended Information: {}", INSPIRE_FACE_EXTENDED_INFORMATION); + + Catch::Session session; + // Pack file name and test directory + std::string pack; + std::string testDir; + std::string packPath; + + int32_t ret; + + // Add command line options + auto cli = session.cli() | Catch::clara::Opt(pack, "value")["--pack"]("Resource pack filename") | + Catch::clara::Opt(testDir, "value")["--test_dir"]("Test dir resource") | + Catch::clara::Opt(packPath, "value")["--pack_path"]("The specified path to the pack file"); + + // Set combined CLI to the session + session.cli(cli); + + // Parse command line arguments + int returnCode = session.applyCommandLine(argc, argv); + if (returnCode != 0) // Indicate an error + return returnCode; + + if (!testDir.empty()) { + SET_TEST_DIR(testDir); + TEST_PRINT("Updated test dir to: {}", getTestDataDir()); + } else { + TEST_PRINT("Using default test dir: {}", getTestDataDir()); + } + +#if defined(ISF_ENABLE_TENSORRT) + int32_t cuda_device_count; + ret = inspire::GetCudaDeviceCount(&cuda_device_count); + if (ret != InspireFace::HSUCCEED) { + TEST_ERROR_PRINT("An error occurred while checking CUDA device support: {}", ret); + return ret; + } + if (cuda_device_count == 0) { + TEST_ERROR_PRINT("CUDA device support is not available"); + return HERR_DEVICE_CUDA_NOT_SUPPORT; + } + + inspire::PrintCudaDeviceInfo(); +#endif + + std::string fullPath; + // Check whether custom parameters are set + if (!pack.empty()) { + SET_PACK_NAME(pack); + fullPath = GET_MODEL_FILE(); + TEST_PRINT("Updated global Pack to: {}", TEST_MODEL_FILE); + SET_RUNTIME_FULLPATH_NAME(fullPath); + } else if (!packPath.empty()) { + fullPath = packPath; + TEST_PRINT("Updated global Pack File to: {}", packPath); + SET_RUNTIME_FULLPATH_NAME(packPath); + } else { + fullPath = GET_MODEL_FILE(); + TEST_PRINT("Using default global Pack: {}", TEST_MODEL_FILE); + SET_RUNTIME_FULLPATH_NAME(fullPath); + } + + TEST_PRINT("Launching InspireFace with path: {}", fullPath); + ret = INSPIREFACE_CONTEXT->Load(fullPath.c_str()); + if (ret != HSUCCEED) { + TEST_ERROR_PRINT("An error occurred while starting InspireFace: {}", ret); + return ret; + } + + // Set log level + INSPIRE_SET_LOG_LEVEL(inspire::LogLevel::ISF_LOG_INFO); + + // Run the test + ret = session.run(); + + // Terminate the InspireFace instance + INSPIREFACE_CONTEXT->Unload(); + + return ret; +} diff --git a/cpp-package/inspireface/cpp/test/unit/api/test_benchmark.cpp b/cpp-package/inspireface/cpp/test/unit/api/test_benchmark.cpp index 470c906..83d0631 100644 --- a/cpp-package/inspireface/cpp/test/unit/api/test_benchmark.cpp +++ b/cpp-package/inspireface/cpp/test/unit/api/test_benchmark.cpp @@ -34,7 +34,7 @@ TEST_CASE("test_BenchmarkFaceDetect", "[benchmark]") { ret = CVImageToImageStream(image, imgHandle); REQUIRE(ret == HSUCCEED); - inspirecv::TimeSpend timeSpend("Face Detect@160"); + inspire::SpendTimer timeSpend("Face Detect@160"); for (size_t i = 0; i < loop; i++) { timeSpend.Start(); // Extract basic face information from photos @@ -68,7 +68,7 @@ TEST_CASE("test_BenchmarkFaceDetect", "[benchmark]") { ret = CVImageToImageStream(image, imgHandle); REQUIRE(ret == HSUCCEED); - inspirecv::TimeSpend timeSpend("Face Detect@320"); + inspire::SpendTimer timeSpend("Face Detect@320"); for (size_t i = 0; i < loop; i++) { timeSpend.Start(); // Extract basic face information from photos @@ -102,7 +102,7 @@ TEST_CASE("test_BenchmarkFaceDetect", "[benchmark]") { ret = CVImageToImageStream(image, imgHandle); REQUIRE(ret == HSUCCEED); - inspirecv::TimeSpend timeSpend("Face Detect@640"); + inspire::SpendTimer timeSpend("Face Detect@640"); for (size_t i = 0; i < loop; i++) { timeSpend.Start(); // Extract basic face information from photos @@ -141,7 +141,7 @@ TEST_CASE("test_BenchmarkFaceTrack", "[benchmark]") { ret = CVImageToImageStream(image, imgHandle); REQUIRE(ret == HSUCCEED); - inspirecv::TimeSpend timeSpend("Face Track"); + inspire::SpendTimer timeSpend("Face Track"); for (size_t i = 0; i < loop; i++) { timeSpend.Start(); // Extract basic face information from photos @@ -186,7 +186,7 @@ TEST_CASE("test_BenchmarkFaceExtractWithAlign", "[benchmark]") { ret = HFExecuteFaceTrack(session, imgHandle, &multipleFaceData); REQUIRE(ret == HSUCCEED); - inspirecv::TimeSpend timeSpend("Face Extract With Align"); + inspire::SpendTimer timeSpend("Face Extract With Align"); for (size_t i = 0; i < loop; i++) { timeSpend.Start(); // Extract basic face information from photos @@ -257,7 +257,7 @@ TEST_CASE("test_BenchmarkFaceComparison", "[benchmark]") { featureZyQuery.size = featureNum; REQUIRE(ret == HSUCCEED); - inspirecv::TimeSpend timeSpend("Face Comparison"); + inspire::SpendTimer timeSpend("Face Comparison"); for (int i = 0; i < loop; ++i) { timeSpend.Start(); HFloat compRes; @@ -333,7 +333,7 @@ TEST_CASE("test_BenchmarkFaceHubSearchPersistence", "[benchmark]") { searchFeature.data = searchFeat.data(); HFloat confidence = 0.0f; HFFaceFeatureIdentity mostSimilar = {0}; - inspirecv::TimeSpend timeSpend("Face Search 1k@Persistence"); + inspire::SpendTimer timeSpend("Face Search 1k@Persistence"); for (size_t i = 0; i < loop; i++) { timeSpend.Start(); ret = HFFeatureHubFaceSearch(searchFeature, &confidence, &mostSimilar); @@ -401,7 +401,7 @@ TEST_CASE("test_BenchmarkFaceHubSearchPersistence", "[benchmark]") { searchFeature.data = searchFeat.data(); HFloat confidence = 0.0f; HFFaceFeatureIdentity mostSimilar = {0}; - inspirecv::TimeSpend timeSpend("Face Search 5k@Persistence"); + inspire::SpendTimer timeSpend("Face Search 5k@Persistence"); for (size_t i = 0; i < loop; i++) { timeSpend.Start(); ret = HFFeatureHubFaceSearch(searchFeature, &confidence, &mostSimilar); @@ -469,7 +469,7 @@ TEST_CASE("test_BenchmarkFaceHubSearchPersistence", "[benchmark]") { searchFeature.data = searchFeat.data(); HFloat confidence = 0.0f; HFFaceFeatureIdentity mostSimilar = {0}; - inspirecv::TimeSpend timeSpend("Face Search 10k@Persistence"); + inspire::SpendTimer timeSpend("Face Search 10k@Persistence"); for (size_t i = 0; i < loop; i++) { timeSpend.Start(); ret = HFFeatureHubFaceSearch(searchFeature, &confidence, &mostSimilar); @@ -534,7 +534,7 @@ TEST_CASE("test_BenchmarkFaceHubSearchMemory", "[benchmark]") { searchFeature.data = searchFeat.data(); HFloat confidence = 0.0f; HFFaceFeatureIdentity mostSimilar = {0}; - inspirecv::TimeSpend timeSpend("Face Search 1k@Memory"); + inspire::SpendTimer timeSpend("Face Search 1k@Memory"); for (size_t i = 0; i < loop; i++) { timeSpend.Start(); ret = HFFeatureHubFaceSearch(searchFeature, &confidence, &mostSimilar); @@ -592,7 +592,7 @@ TEST_CASE("test_BenchmarkFaceHubSearchMemory", "[benchmark]") { searchFeature.data = searchFeat.data(); HFloat confidence = 0.0f; HFFaceFeatureIdentity mostSimilar = {0}; - inspirecv::TimeSpend timeSpend("Face Search 5k@Memory"); + inspire::SpendTimer timeSpend("Face Search 5k@Memory"); for (size_t i = 0; i < loop; i++) { timeSpend.Start(); ret = HFFeatureHubFaceSearch(searchFeature, &confidence, &mostSimilar); @@ -651,7 +651,7 @@ TEST_CASE("test_BenchmarkFaceHubSearchMemory", "[benchmark]") { searchFeature.data = searchFeat.data(); HFloat confidence = 0.0f; HFFaceFeatureIdentity mostSimilar = {0}; - inspirecv::TimeSpend timeSpend("Face Search 10k@Memory"); + inspire::SpendTimer timeSpend("Face Search 10k@Memory"); for (size_t i = 0; i < loop; i++) { timeSpend.Start(); ret = HFFeatureHubFaceSearch(searchFeature, &confidence, &mostSimilar); diff --git a/cpp-package/inspireface/cpp/test/unit/api/test_face_pipeline.cpp b/cpp-package/inspireface/cpp/test/unit/api/test_face_pipeline.cpp index 1a397d3..d0dc367 100644 --- a/cpp-package/inspireface/cpp/test/unit/api/test_face_pipeline.cpp +++ b/cpp-package/inspireface/cpp/test/unit/api/test_face_pipeline.cpp @@ -147,7 +147,7 @@ TEST_CASE("test_FacePipeline", "[face_pipeline]") { TEST_PRINT("{}", confidence.confidence[0]); REQUIRE(ret == HSUCCEED); CHECK(confidence.num > 0); - CHECK(confidence.confidence[0] > 0.8); + CHECK(confidence.confidence[0] > 0.70f); ret = HFReleaseImageStream(img1Handle); REQUIRE(ret == HSUCCEED); @@ -415,7 +415,7 @@ TEST_CASE("test_TrackModeFaceAction", "[face_action]") { ret = HFCreateInspireFaceSession(parameter, detMode, 3, -1, -1, &session); REQUIRE(ret == HSUCCEED); -#if 0 +#if 0 // Temporarily shut down the processing of frame related cases to prevent excessive data from occupying the github memory SECTION("Action Blink") { auto start = 130, end = 150; std::vector filenames = generateFilenames("frame-%04d.jpg", start, end); @@ -449,7 +449,7 @@ TEST_CASE("test_TrackModeFaceAction", "[face_action]") { } #endif -#if 0 +#if 0 // Temporarily shut down the processing of frame related cases to prevent excessive data from occupying the github memory SECTION("Action Jaw Open") { auto start = 110, end = 150; std::vector filenames = generateFilenames("frame-%04d.jpg", start, end); diff --git a/cpp-package/inspireface/cpp/test/unit/api/test_face_track.cpp b/cpp-package/inspireface/cpp/test/unit/api/test_face_track.cpp index 88666b5..9b2fd33 100644 --- a/cpp-package/inspireface/cpp/test/unit/api/test_face_track.cpp +++ b/cpp-package/inspireface/cpp/test/unit/api/test_face_track.cpp @@ -48,8 +48,8 @@ TEST_CASE("test_FaceTrack", "[face_track]") { auto cvRect = inspirecv::Rect::Create(rect.x, rect.y, rect.width, rect.height); image.DrawRect(cvRect, {0, 0, 255}, 2); image.Write("ww.jpg"); - // The iou is allowed to have an error of 25% - CHECK(iou == Approx(1.0f).epsilon(0.25)); + // The iou is allowed to have an error of 50% + CHECK(iou == Approx(1.0f).epsilon(0.5)); ret = HFReleaseImageStream(imgHandle); REQUIRE(ret == HSUCCEED); @@ -70,6 +70,7 @@ TEST_CASE("test_FaceTrack", "[face_track]") { REQUIRE(ret == HSUCCEED); } +// Temporarily shut down the processing of frame related cases to prevent excessive data from occupying the github memory #if 0 SECTION("Face tracking stability from frames") { HResult ret; @@ -124,6 +125,7 @@ TEST_CASE("test_FaceTrack", "[face_track]") { SECTION("Head pose estimation") { HResult ret; HFSessionCustomParameter parameter = {0}; + parameter.enable_face_pose = true; HFDetectMode detMode = HF_DETECT_MODE_ALWAYS_DETECT; HFSession session; ret = HFCreateInspireFaceSession(parameter, detMode, 3, -1, -1, &session); @@ -318,6 +320,12 @@ TEST_CASE("test_MultipleLevelFaceDetect", "[face_detect]") { REQUIRE(ret == HSUCCEED); HFSessionSetTrackPreviewSize(session, detectPixelLevel); HFSessionSetFilterMinimumFacePixelSize(session, 0); + + // Check the preview size + HInt32 previewSize; + ret = HFSessionGetTrackPreviewSize(session, &previewSize); + REQUIRE(ret == HSUCCEED); + CHECK(previewSize == detectPixelLevel); // Get a face picture HFImageStream imgHandle; @@ -351,6 +359,12 @@ TEST_CASE("test_MultipleLevelFaceDetect", "[face_detect]") { HFSessionSetTrackPreviewSize(session, detectPixelLevel); HFSessionSetFilterMinimumFacePixelSize(session, 0); + // Check the preview size + HInt32 previewSize; + ret = HFSessionGetTrackPreviewSize(session, &previewSize); + REQUIRE(ret == HSUCCEED); + CHECK(previewSize == detectPixelLevel); + // Get a face picture HFImageStream imgHandle; auto image = inspirecv::Image::Create(GET_DATA("data/bulk/pedestrian.png")); @@ -383,6 +397,12 @@ TEST_CASE("test_MultipleLevelFaceDetect", "[face_detect]") { HFSessionSetTrackPreviewSize(session, detectPixelLevel); HFSessionSetFilterMinimumFacePixelSize(session, 0); + // Check the preview size + HInt32 previewSize; + ret = HFSessionGetTrackPreviewSize(session, &previewSize); + REQUIRE(ret == HSUCCEED); + CHECK(previewSize == detectPixelLevel); + // Get a face picture HFImageStream imgHandle; auto image = inspirecv::Image::Create(GET_DATA("data/bulk/pedestrian.png")); @@ -403,4 +423,133 @@ TEST_CASE("test_MultipleLevelFaceDetect", "[face_detect]") { ret = HFReleaseInspireFaceSession(session); REQUIRE(ret == HSUCCEED); } +} + +TEST_CASE("test_FaceTrackPreviewSizeSetting", "[face_track]") { + DRAW_SPLIT_LINE + TEST_PRINT_OUTPUT(true); + + SECTION("Default preview size and detection level size") { + HResult ret; + HFSessionCustomParameter parameter = {0}; + HFDetectMode detMode = HF_DETECT_MODE_ALWAYS_DETECT; + HFSession session; + HInt32 levelSize = -1; + ret = HFCreateInspireFaceSession(parameter, detMode, 20, levelSize, -1, &session); + REQUIRE(ret == HSUCCEED); + + // Check the preview size + HInt32 previewSize; + ret = HFSessionGetTrackPreviewSize(session, &previewSize); + REQUIRE(ret == HSUCCEED); + + CHECK(previewSize == 320); + + // Get a face picture + HFImageStream imgHandle; + auto image = inspirecv::Image::Create(GET_DATA("data/bulk/pedestrian.png")); + ret = CVImageToImageStream(image, imgHandle); + REQUIRE(ret == HSUCCEED); + + // Extract basic face information from photos + HFMultipleFaceData multipleFaceData = {0}; + ret = HFExecuteFaceTrack(session, imgHandle, &multipleFaceData); + REQUIRE(ret == HSUCCEED); + + CHECK(multipleFaceData.detectedNum > 0); + // Check the preview size + HInt32 debugPreviewSize; + ret = HFSessionLastFaceDetectionGetDebugPreviewImageSize(session, &debugPreviewSize); + REQUIRE(ret == HSUCCEED); + CHECK(debugPreviewSize == 320); + + ret = HFReleaseImageStream(imgHandle); + REQUIRE(ret == HSUCCEED); + + ret = HFReleaseInspireFaceSession(session); + REQUIRE(ret == HSUCCEED); + } + + SECTION("Set preview size to 320px") { + HResult ret; + HFSessionCustomParameter parameter = {0}; + HFDetectMode detMode = HF_DETECT_MODE_ALWAYS_DETECT; + HFSession session; + HInt32 levelSize = 320; + ret = HFCreateInspireFaceSession(parameter, detMode, 20, levelSize, -1, &session); + REQUIRE(ret == HSUCCEED); + + // Check the preview size + HInt32 previewSize; + ret = HFSessionGetTrackPreviewSize(session, &previewSize); + REQUIRE(ret == HSUCCEED); + CHECK(previewSize == levelSize); + + ret = HFReleaseInspireFaceSession(session); + REQUIRE(ret == HSUCCEED); + } + + SECTION("Set the detect level to an invalid value") { + HResult ret; + HFSessionCustomParameter parameter = {0}; + HFDetectMode detMode = HF_DETECT_MODE_ALWAYS_DETECT; + HFSession session; + HInt32 levelSize = 1000; + ret = HFCreateInspireFaceSession(parameter, detMode, 20, levelSize, -1, &session); + REQUIRE(ret == HSUCCEED); + + // Check the preview size + HInt32 previewSize; + ret = HFSessionGetTrackPreviewSize(session, &previewSize); + REQUIRE(ret == HSUCCEED); + // If the detect level value is invalid, the value will be automatically adjusted to the nearest legal size. + // If the default value of preview_size is -1, the value will also be adjusted + CHECK(previewSize == 640); + + // Get a face picture + HFImageStream imgHandle; + auto image = inspirecv::Image::Create(GET_DATA("data/bulk/pedestrian.png")); + ret = CVImageToImageStream(image, imgHandle); + REQUIRE(ret == HSUCCEED); + + // Extract basic face information from photos + HFMultipleFaceData multipleFaceData = {0}; + ret = HFExecuteFaceTrack(session, imgHandle, &multipleFaceData); + REQUIRE(ret == HSUCCEED); + + CHECK(multipleFaceData.detectedNum > 0); + + // Check the preview size + HInt32 debugPreviewSize; + ret = HFSessionLastFaceDetectionGetDebugPreviewImageSize(session, &debugPreviewSize); + REQUIRE(ret == HSUCCEED); + CHECK(debugPreviewSize == 640); + + // Set a value manually + ret = HFSessionSetTrackPreviewSize(session, 192); + REQUIRE(ret == HSUCCEED); + + // Check the preview size + ret = HFSessionGetTrackPreviewSize(session, &previewSize); + REQUIRE(ret == HSUCCEED); + CHECK(previewSize == 192); + + ret = HFExecuteFaceTrack(session, imgHandle, &multipleFaceData); + REQUIRE(ret == HSUCCEED); + + CHECK(multipleFaceData.detectedNum > 0); + + // Check the preview size + ret = HFSessionLastFaceDetectionGetDebugPreviewImageSize(session, &debugPreviewSize); + REQUIRE(ret == HSUCCEED); + CHECK(debugPreviewSize == 192); + + + ret = HFReleaseImageStream(imgHandle); + REQUIRE(ret == HSUCCEED); + + ret = HFReleaseInspireFaceSession(session); + REQUIRE(ret == HSUCCEED); + } + } \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/test/unit/api/test_session_parallel.cpp b/cpp-package/inspireface/cpp/test/unit/api/test_session_parallel.cpp index 27dc907..4f60e6c 100644 --- a/cpp-package/inspireface/cpp/test/unit/api/test_session_parallel.cpp +++ b/cpp-package/inspireface/cpp/test/unit/api/test_session_parallel.cpp @@ -43,7 +43,7 @@ TEST_CASE("test_SessionParallel", "[Session][Parallel]") { REQUIRE(ret == HSUCCEED); float similarity = 0.0f; - inspirecv::TimeSpend timeSpend("Serial loop: " + std::to_string(loop)); + inspire::SpendTimer timeSpend("Serial loop: " + std::to_string(loop)); timeSpend.Start(); for (int i = 0; i < loop; ++i) { ret = CompareTwoFaces(session, image1, image2, similarity); @@ -52,6 +52,7 @@ TEST_CASE("test_SessionParallel", "[Session][Parallel]") { } timeSpend.Stop(); std::cout << timeSpend << std::endl; + ; ret = HFReleaseInspireFaceSession(session); REQUIRE(ret == HSUCCEED); @@ -85,7 +86,7 @@ TEST_CASE("test_SessionParallel", "[Session][Parallel]") { float similaritySum = 0.0f; std::mutex similarityMutex; - inspirecv::TimeSpend timeSpend("Parallel loop: " + std::to_string(loop) + ", thread: " + std::to_string(N)); + inspire::SpendTimer timeSpend("Parallel loop: " + std::to_string(loop) + ", thread: " + std::to_string(N)); timeSpend.Start(); // Start worker thread @@ -119,6 +120,7 @@ TEST_CASE("test_SessionParallel", "[Session][Parallel]") { timeSpend.Stop(); std::cout << timeSpend << std::endl; + ; // Optional: Output average similarity(stability) TEST_PRINT("Average similarity: {}", (similaritySum / loop)); diff --git a/cpp-package/inspireface/cpp/test/unit/api/test_similarity_converter.cpp b/cpp-package/inspireface/cpp/test/unit/api/test_similarity_converter.cpp index f83d01a..5fa30f5 100644 --- a/cpp-package/inspireface/cpp/test/unit/api/test_similarity_converter.cpp +++ b/cpp-package/inspireface/cpp/test/unit/api/test_similarity_converter.cpp @@ -5,7 +5,7 @@ #include #include "settings/test_settings.h" #include "unit/test_helper/test_help.h" -#include "inspireface/recognition_module/similarity_converter.h" +#include "inspireface/include/inspireface/similarity_converter.h" TEST_CASE("test_similarity_converter", "[similarity_converter]") { DRAW_SPLIT_LINE diff --git a/cpp-package/inspireface/cpp/test/unit/api/test_system.cpp b/cpp-package/inspireface/cpp/test/unit/api/test_system.cpp index dd95766..229ebcf 100644 --- a/cpp-package/inspireface/cpp/test/unit/api/test_system.cpp +++ b/cpp-package/inspireface/cpp/test/unit/api/test_system.cpp @@ -1,9 +1,7 @@ -#if 0 #include #include "settings/test_settings.h" #include "inspireface/c_api/inspireface.h" -#include "inspireface/herror.h" -#include "opencv2/opencv.hpp" +#include "inspireface/include/inspireface/herror.h" #include "unit/test_helper/test_tools.h" #include @@ -11,12 +9,17 @@ TEST_CASE("test_System", "[system]") { DRAW_SPLIT_LINE TEST_PRINT_OUTPUT(true); - // The global TEST environment has been started, so this side needs to be temporarily closed - // before testing - HFTerminateInspireFace(); + HResult ret; + HInt32 status; + ret = HFQueryInspireFaceLaunchStatus(&status); + REQUIRE(ret == HSUCCEED); + if (status == HF_STATUS_ENABLE) { + // The global TEST environment has been started, so this side needs to be temporarily closed + // before testing + HFTerminateInspireFace(); + } SECTION("Create a session test when it is not loaded") { - HResult ret; HFSessionCustomParameter parameter = {0}; HFDetectMode detMode = HF_DETECT_MODE_ALWAYS_DETECT; HFSession session; @@ -27,7 +30,7 @@ TEST_CASE("test_System", "[system]") { } // Restart and start InspireFace - auto ret = HFLaunchInspireFace(GET_RUNTIME_FULLPATH_NAME.c_str()); + ret = HFLaunchInspireFace(GET_RUNTIME_FULLPATH_NAME.c_str()); REQUIRE(ret == HSUCCEED); SECTION("Create a session test when it is reloaded") { @@ -178,7 +181,7 @@ TEST_CASE("test_SystemStreamReleaseCase", "[system]") { for (int i = 0; i < createCount; ++i) { HFImageStream imgHandle; - auto image = cv::imread(GET_DATA("data/bulk/pedestrian.png")); + auto image = inspirecv::Image::Create(GET_DATA("data/bulk/pedestrian.png")); ret = CVImageToImageStream(image, imgHandle); REQUIRE(ret == HSUCCEED); streams[i] = imgHandle; @@ -267,5 +270,3 @@ TEST_CASE("test_SystemStreamReleaseCase", "[system]") { REQUIRE(count == 0); } } - -#endif \ No newline at end of file diff --git a/cpp-package/inspireface/cpp/test/unit/base/test_face_session.cpp b/cpp-package/inspireface/cpp/test/unit/base/test_face_session.cpp index 30d061e..4297719 100644 --- a/cpp-package/inspireface/cpp/test/unit/base/test_face_session.cpp +++ b/cpp-package/inspireface/cpp/test/unit/base/test_face_session.cpp @@ -2,12 +2,11 @@ #include #include "settings/test_settings.h" #include "unit/test_helper/help.h" -#include "feature_hub/feature_hub_db.h" #include "middleware/costman.h" -#include "inspireface/initialization_module/launch.h" -#include "middleware/inspirecv_image_process.h" -#include "inspireface/face_session.h" -#include "inspireface/feature_hub/feature_hub_db.h" +#include +#include "frame_process.h" +#include "inspireface/engine/face_session.h" +#include using namespace inspire; @@ -29,12 +28,10 @@ TEST_CASE("test_FaceSession", "[face_session") { inspirecv::Image kun1 = inspirecv::Image::Create(GET_DATA("data/bulk/kun.jpg")); inspirecv::Image kun2 = inspirecv::Image::Create(GET_DATA("data/bulk/jntm.jpg")); - inspirecv::InspireImageProcess proc1 = - inspirecv::InspireImageProcess::Create(kun1.Data(), kun1.Height(), kun1.Width(), inspirecv::BGR, inspirecv::ROTATION_0); - inspirecv::InspireImageProcess proc2 = - inspirecv::InspireImageProcess::Create(kun2.Data(), kun2.Height(), kun2.Width(), inspirecv::BGR, inspirecv::ROTATION_0); + inspirecv::FrameProcess proc1 = inspirecv::FrameProcess::Create(kun1.Data(), kun1.Height(), kun1.Width(), inspirecv::BGR, inspirecv::ROTATION_0); + inspirecv::FrameProcess proc2 = inspirecv::FrameProcess::Create(kun2.Data(), kun2.Height(), kun2.Width(), inspirecv::BGR, inspirecv::ROTATION_0); std::vector> features; - std::vector processes = {proc1, proc2}; + std::vector processes = {proc1, proc2}; for (auto &process : processes) { ret = session.FaceDetectAndTrack(process); REQUIRE(ret == HSUCCEED); @@ -45,7 +42,7 @@ TEST_CASE("test_FaceSession", "[face_session") { const auto &faces = session.GetTrackingFaceList(); REQUIRE(faces.size() > 0); Embedded feature; - HyperFaceData hyper_face_data = FaceObjectInternalToHyperFaceData(faces[0]); + FaceTrackWrap hyper_face_data = FaceObjectInternalToHyperFaceData(faces[0]); float norm; ret = session.FaceRecognitionModule()->FaceExtract(process, hyper_face_data, feature, norm); REQUIRE(ret == HSUCCEED); @@ -59,8 +56,8 @@ TEST_CASE("test_FaceSession", "[face_session") { REQUIRE(res > 0.5f); inspirecv::Image other = inspirecv::Image::Create(GET_DATA("data/bulk/woman.png")); - inspirecv::InspireImageProcess proc3 = - inspirecv::InspireImageProcess::Create(other.Data(), other.Height(), other.Width(), inspirecv::BGR, inspirecv::ROTATION_0); + inspirecv::FrameProcess proc3 = + inspirecv::FrameProcess::Create(other.Data(), other.Height(), other.Width(), inspirecv::BGR, inspirecv::ROTATION_0); ret = session.FaceDetectAndTrack(proc3); REQUIRE(ret == HSUCCEED); if (session.GetDetectCache().size() > 0) { @@ -69,7 +66,7 @@ TEST_CASE("test_FaceSession", "[face_session") { auto faces = session.GetTrackingFaceList(); REQUIRE(ret == HSUCCEED); Embedded feature; - HyperFaceData hyper_face_data = FaceObjectInternalToHyperFaceData(faces[0]); + FaceTrackWrap hyper_face_data = FaceObjectInternalToHyperFaceData(faces[0]); float norm; ret = session.FaceRecognitionModule()->FaceExtract(proc3, hyper_face_data, feature, norm); REQUIRE(ret == HSUCCEED); diff --git a/cpp-package/inspireface/cpp/test/unit/base/test_module_feature_hub.cpp b/cpp-package/inspireface/cpp/test/unit/base/test_module_feature_hub.cpp index f7fee6c..d5cb825 100644 --- a/cpp-package/inspireface/cpp/test/unit/base/test_module_feature_hub.cpp +++ b/cpp-package/inspireface/cpp/test/unit/base/test_module_feature_hub.cpp @@ -2,7 +2,7 @@ #include "settings/test_settings.h" #include "inspireface/c_api/inspireface.h" #include "unit/test_helper/help.h" -#include "feature_hub/feature_hub_db.h" +#include #include "middleware/costman.h" using namespace inspire; @@ -16,11 +16,11 @@ TEST_CASE("test_FeatureHubBasic", "[feature_hub") { config.primary_key_mode = PrimaryKeyMode::AUTO_INCREMENT; config.enable_persistence = false; // memory mode int32_t ret; - ret = FEATURE_HUB_DB->EnableHub(config); + ret = INSPIREFACE_FEATURE_HUB->EnableHub(config); REQUIRE(ret == HSUCCEED); // Check if feature hub is enabled - ret = FEATURE_HUB_DB->EnableHub(config); + ret = INSPIREFACE_FEATURE_HUB->EnableHub(config); REQUIRE(ret == HSUCCEED); // Check number of features @@ -30,69 +30,69 @@ TEST_CASE("test_FeatureHubBasic", "[feature_hub") { for (int32_t i = 0; i < count; i++) { auto vec = GenerateRandomFeature(512, false); int64_t alloc_id; - ret = FEATURE_HUB_DB->FaceFeatureInsert(vec, -1, alloc_id); + ret = INSPIREFACE_FEATURE_HUB->FaceFeatureInsert(vec, -1, alloc_id); REQUIRE(ret == HSUCCEED); ids.push_back(alloc_id); expected_ids.push_back(i + 1); } - REQUIRE(FEATURE_HUB_DB->GetFaceFeatureCount() == ids.size()); + REQUIRE(INSPIREFACE_FEATURE_HUB->GetFaceFeatureCount() == ids.size()); REQUIRE(ids == expected_ids); // Delete data std::vector delete_ids = {5, 20, 100}; for (auto id : delete_ids) { - FEATURE_HUB_DB->FaceFeatureRemove(id); + INSPIREFACE_FEATURE_HUB->FaceFeatureRemove(id); } - REQUIRE(FEATURE_HUB_DB->GetFaceFeatureCount() == ids.size() - delete_ids.size()); + REQUIRE(INSPIREFACE_FEATURE_HUB->GetFaceFeatureCount() == ids.size() - delete_ids.size()); // Check if the deleted data can be found std::vector feature; - ret = FEATURE_HUB_DB->GetFaceFeature(5, feature); + ret = INSPIREFACE_FEATURE_HUB->GetFaceFeature(5, feature); REQUIRE(ret == HERR_FT_HUB_NOT_FOUND_FEATURE); // Check if the data can be found - ret = FEATURE_HUB_DB->GetFaceFeature(1, feature); + ret = INSPIREFACE_FEATURE_HUB->GetFaceFeature(1, feature); REQUIRE(ret == HSUCCEED); REQUIRE(feature.size() == 512); // Check if the cached data is correct - ret = FEATURE_HUB_DB->GetFaceFeature(1); + ret = INSPIREFACE_FEATURE_HUB->GetFaceFeature(1); REQUIRE(ret == HSUCCEED); - auto cached_feature = FEATURE_HUB_DB->GetFaceFeaturePtrCache(); + auto cached_feature = INSPIREFACE_FEATURE_HUB->GetFaceFeaturePtrCache(); for (size_t i = 0; i < cached_feature->dataSize; i++) { REQUIRE(feature[i] == cached_feature->data[i]); } // Update data auto update_feature = GenerateRandomFeature(512, false); - ret = FEATURE_HUB_DB->FaceFeatureUpdate(update_feature, 1); + ret = INSPIREFACE_FEATURE_HUB->FaceFeatureUpdate(update_feature, 1); REQUIRE(ret == HSUCCEED); // Check if the updated data is correct - ret = FEATURE_HUB_DB->GetFaceFeature(1, feature); + ret = INSPIREFACE_FEATURE_HUB->GetFaceFeature(1, feature); REQUIRE(ret == HSUCCEED); for (size_t i = 0; i < feature.size(); i++) { REQUIRE(feature[i] == Approx(update_feature[i]).epsilon(0.0001)); } // Update removed data - ret = FEATURE_HUB_DB->FaceFeatureUpdate(update_feature, 5); + ret = INSPIREFACE_FEATURE_HUB->FaceFeatureUpdate(update_feature, 5); REQUIRE(ret == HERR_FT_HUB_NOT_FOUND_FEATURE); // Disable feature hub - FEATURE_HUB_DB->DisableHub(); - REQUIRE(FEATURE_HUB_DB->GetFaceFeatureCount() == 0); + INSPIREFACE_FEATURE_HUB->DisableHub(); + REQUIRE(INSPIREFACE_FEATURE_HUB->GetFaceFeatureCount() == 0); // Check if the data can be found - ret = FEATURE_HUB_DB->GetFaceFeature(1, feature); + ret = INSPIREFACE_FEATURE_HUB->GetFaceFeature(1, feature); REQUIRE(ret == HERR_FT_HUB_DISABLE); - ret = FEATURE_HUB_DB->EnableHub(config); + ret = INSPIREFACE_FEATURE_HUB->EnableHub(config); REQUIRE(ret == HSUCCEED); // Because the memory mode is turned on, once the data is turned off, it goes back to empty - REQUIRE(FEATURE_HUB_DB->GetFaceFeatureCount() == 0); + REQUIRE(INSPIREFACE_FEATURE_HUB->GetFaceFeatureCount() == 0); - ret = FEATURE_HUB_DB->DisableHub(); + ret = INSPIREFACE_FEATURE_HUB->DisableHub(); REQUIRE(ret == HSUCCEED); } @@ -104,7 +104,7 @@ TEST_CASE("test_PerformanceMemoryMode", "[feature_hub") { config.primary_key_mode = PrimaryKeyMode::AUTO_INCREMENT; config.enable_persistence = false; // memory mode int32_t ret; - ret = FEATURE_HUB_DB->EnableHub(config); + ret = INSPIREFACE_FEATURE_HUB->EnableHub(config); REQUIRE(ret == HSUCCEED); Timer t1; @@ -112,32 +112,32 @@ TEST_CASE("test_PerformanceMemoryMode", "[feature_hub") { for (int i = 0; i < num; i++) { auto vec = GenerateRandomFeature(512, false); int64_t alloc_id; - ret = FEATURE_HUB_DB->FaceFeatureInsert(vec, -1, alloc_id); + ret = INSPIREFACE_FEATURE_HUB->FaceFeatureInsert(vec, -1, alloc_id); REQUIRE(ret == HSUCCEED); } TEST_PRINT("[Memory Mode]Insert 10000 features cost: {:.2f} ms", t1.GetCostTime()); Timer t2; std::vector feature; - ret = FEATURE_HUB_DB->GetFaceFeature(1, feature); + ret = INSPIREFACE_FEATURE_HUB->GetFaceFeature(1, feature); TEST_PRINT("[Memory Mode]Get feature from id cost: {:.2f} ms", t2.GetCostTime()); REQUIRE(ret == HSUCCEED); Timer t3; - ret = FEATURE_HUB_DB->GetFaceFeature(9998, feature); + ret = INSPIREFACE_FEATURE_HUB->GetFaceFeature(9998, feature); TEST_PRINT("[Memory Mode]Get feature from id cost: {:.2f} ms", t3.GetCostTime()); REQUIRE(ret == HSUCCEED); auto sim_vec = SimulateSimilarVector(feature, false); FaceSearchResult search_result; Timer t4; - FEATURE_HUB_DB->SearchFaceFeature(sim_vec, search_result, true); + INSPIREFACE_FEATURE_HUB->SearchFaceFeature(sim_vec, search_result, true); TEST_PRINT("[Memory Mode]Search feature cost: {:.2f} ms", t4.GetCostTime()); REQUIRE(search_result.id == 9998); - ret = FEATURE_HUB_DB->FaceFeatureRemove(9998); + ret = INSPIREFACE_FEATURE_HUB->FaceFeatureRemove(9998); REQUIRE(ret == HSUCCEED); - FEATURE_HUB_DB->DisableHub(); + INSPIREFACE_FEATURE_HUB->DisableHub(); } TEST_CASE("test_PerformancePersistentMode", "[feature_hub") { @@ -153,7 +153,7 @@ TEST_CASE("test_PerformancePersistentMode", "[feature_hub") { config.persistence_db_path = db_path; int32_t ret; - ret = FEATURE_HUB_DB->EnableHub(config); + ret = INSPIREFACE_FEATURE_HUB->EnableHub(config); REQUIRE(ret == HSUCCEED); Timer t1; @@ -161,38 +161,38 @@ TEST_CASE("test_PerformancePersistentMode", "[feature_hub") { for (int i = 0; i < num; i++) { auto vec = GenerateRandomFeature(512, false); int64_t alloc_id; - ret = FEATURE_HUB_DB->FaceFeatureInsert(vec, -1, alloc_id); + ret = INSPIREFACE_FEATURE_HUB->FaceFeatureInsert(vec, -1, alloc_id); REQUIRE(ret == HSUCCEED); } TEST_PRINT("[Persistent Mode]Insert 10000 features cost: {:.2f} ms", t1.GetCostTime()); Timer t2; std::vector feature; - ret = FEATURE_HUB_DB->GetFaceFeature(1, feature); + ret = INSPIREFACE_FEATURE_HUB->GetFaceFeature(1, feature); TEST_PRINT("[Persistent Mode]Get feature from id cost: {:.2f} ms", t2.GetCostTime()); REQUIRE(ret == HSUCCEED); Timer t3; - ret = FEATURE_HUB_DB->GetFaceFeature(9998, feature); + ret = INSPIREFACE_FEATURE_HUB->GetFaceFeature(9998, feature); TEST_PRINT("[Persistent Mode]Get feature from id cost: {:.2f} ms", t3.GetCostTime()); REQUIRE(ret == HSUCCEED); auto sim_vec = SimulateSimilarVector(feature, false); FaceSearchResult search_result; Timer t4; - FEATURE_HUB_DB->SearchFaceFeature(sim_vec, search_result, true); + INSPIREFACE_FEATURE_HUB->SearchFaceFeature(sim_vec, search_result, true); TEST_PRINT("[Persistent Mode]Search feature cost: {:.2f} ms", t4.GetCostTime()); REQUIRE(search_result.id == 9998); - ret = FEATURE_HUB_DB->FaceFeatureRemove(9998); + ret = INSPIREFACE_FEATURE_HUB->FaceFeatureRemove(9998); REQUIRE(ret == HSUCCEED); - auto remark_num = FEATURE_HUB_DB->GetFaceFeatureCount(); + auto remark_num = INSPIREFACE_FEATURE_HUB->GetFaceFeatureCount(); REQUIRE(remark_num == num - 1); // Verify important of persistence test - ret = FEATURE_HUB_DB->EnableHub(config); + ret = INSPIREFACE_FEATURE_HUB->EnableHub(config); REQUIRE(ret == HSUCCEED); - REQUIRE(FEATURE_HUB_DB->GetFaceFeatureCount() == remark_num); + REQUIRE(INSPIREFACE_FEATURE_HUB->GetFaceFeatureCount() == remark_num); - FEATURE_HUB_DB->DisableHub(); + INSPIREFACE_FEATURE_HUB->DisableHub(); } diff --git a/cpp-package/inspireface/cpp/test/unit/base/test_module_track.cpp b/cpp-package/inspireface/cpp/test/unit/base/test_module_track.cpp index 60150c1..db4bb8c 100644 --- a/cpp-package/inspireface/cpp/test/unit/base/test_module_track.cpp +++ b/cpp-package/inspireface/cpp/test/unit/base/test_module_track.cpp @@ -2,19 +2,18 @@ #include #include "settings/test_settings.h" #include "unit/test_helper/help.h" -#include "feature_hub/feature_hub_db.h" +#include #include "middleware/costman.h" #include "track_module/face_detect/all.h" -#include "inspireface/initialization_module/launch.h" #include "track_module/face_track_module.h" -#include "middleware/inspirecv_image_process.h" +#include using namespace inspire; TEST_CASE("test_FaceDetect", "[track_module") { DRAW_SPLIT_LINE TEST_PRINT_OUTPUT(true); - auto archive = INSPIRE_LAUNCH->getMArchive(); + auto archive = INSPIREFACE_CONTEXT->getMArchive(); const std::vector supported_sizes = {160, 320, 640}; const std::vector scheme_names = {"face_detect_160", "face_detect_320", "face_detect_640"}; for (size_t i = 0; i < scheme_names.size(); i++) { @@ -22,7 +21,7 @@ TEST_CASE("test_FaceDetect", "[track_module") { auto ret = archive.LoadModel(scheme_names[i], model); REQUIRE(ret == 0); FaceDetectAdapt face_detector(supported_sizes[i]); - face_detector.loadData(model, model.modelType, false); + face_detector.LoadData(model, model.modelType, false); inspirecv::Image img = inspirecv::Image::Create(GET_DATA("data/bulk/kun.jpg")); auto result = face_detector(img); @@ -33,12 +32,12 @@ TEST_CASE("test_FaceDetect", "[track_module") { TEST_CASE("test_RefineNet", "[track_module") { DRAW_SPLIT_LINE TEST_PRINT_OUTPUT(true); - auto archive = INSPIRE_LAUNCH->getMArchive(); + auto archive = INSPIREFACE_CONTEXT->getMArchive(); InspireModel model; auto ret = archive.LoadModel("refine_net", model); REQUIRE(ret == 0); RNetAdapt rnet; - rnet.loadData(model, model.modelType, false); + rnet.LoadData(model, model.modelType, false); inspirecv::Image face = inspirecv::Image::Create(GET_DATA("data/crop/crop.png")); auto result1 = rnet(face); @@ -52,13 +51,13 @@ TEST_CASE("test_RefineNet", "[track_module") { TEST_CASE("test_Landmark", "[track_module") { DRAW_SPLIT_LINE TEST_PRINT_OUTPUT(true); - auto archive = INSPIRE_LAUNCH->getMArchive(); + auto archive = INSPIREFACE_CONTEXT->getMArchive(); InspireModel model; auto ret = archive.LoadModel("landmark", model); REQUIRE(ret == 0); FaceLandmarkAdapt face_landmark(112); - face_landmark.loadData(model, model.modelType); + face_landmark.LoadData(model, model.modelType); inspirecv::Image img = inspirecv::Image::Create(GET_DATA("data/crop/crop.png")); auto result = face_landmark(img); @@ -68,12 +67,12 @@ TEST_CASE("test_Landmark", "[track_module") { TEST_CASE("test_Quality", "[track_module") { DRAW_SPLIT_LINE TEST_PRINT_OUTPUT(true); - auto archive = INSPIRE_LAUNCH->getMArchive(); + auto archive = INSPIREFACE_CONTEXT->getMArchive(); InspireModel model; auto ret = archive.LoadModel("pose_quality", model); REQUIRE(ret == 0); FacePoseQualityAdapt quality; - ret = quality.loadData(model, model.modelType); + ret = quality.LoadData(model, model.modelType); REQUIRE(ret == 0); inspirecv::Image img = inspirecv::Image::Create(GET_DATA("data/crop/crop.png")); @@ -85,7 +84,7 @@ TEST_CASE("test_Quality", "[track_module") { TEST_CASE("test_FaceTrackModule", "[track_module") { DRAW_SPLIT_LINE TEST_PRINT_OUTPUT(true); - auto archive = INSPIRE_LAUNCH->getMArchive(); + auto archive = INSPIREFACE_CONTEXT->getMArchive(); SECTION("Test face detect rotate 0") { auto mode = DetectModuleMode::DETECT_MODE_ALWAYS_DETECT; @@ -93,7 +92,7 @@ TEST_CASE("test_FaceTrackModule", "[track_module") { FaceTrackModule face_track(mode, max_detected_faces); face_track.Configuration(archive); inspirecv::Image img = inspirecv::Image::Create(GET_DATA("data/bulk/kun.jpg")); - inspirecv::InspireImageProcess image = inspirecv::InspireImageProcess::Create(img.Data(), img.Height(), img.Width(), inspirecv::BGR); + inspirecv::FrameProcess image = inspirecv::FrameProcess::Create(img.Data(), img.Height(), img.Width(), inspirecv::BGR); face_track.UpdateStream(image); REQUIRE(face_track.trackingFace.size() == 1); } @@ -104,8 +103,8 @@ TEST_CASE("test_FaceTrackModule", "[track_module") { FaceTrackModule face_track(mode, max_detected_faces); face_track.Configuration(archive); inspirecv::Image img = inspirecv::Image::Create(GET_DATA("data/bulk/r90.jpg")); - inspirecv::InspireImageProcess image = - inspirecv::InspireImageProcess::Create(img.Data(), img.Height(), img.Width(), inspirecv::BGR, inspirecv::ROTATION_90); + inspirecv::FrameProcess image = + inspirecv::FrameProcess::Create(img.Data(), img.Height(), img.Width(), inspirecv::BGR, inspirecv::ROTATION_90); face_track.UpdateStream(image); REQUIRE(face_track.trackingFace.size() == 1); } diff --git a/cpp-package/inspireface/cpp/test/unit/cpp_api/test_session_face_track.cpp b/cpp-package/inspireface/cpp/test/unit/cpp_api/test_session_face_track.cpp new file mode 100644 index 0000000..496fbc4 --- /dev/null +++ b/cpp-package/inspireface/cpp/test/unit/cpp_api/test_session_face_track.cpp @@ -0,0 +1,118 @@ +/** + * Created by Jingyu Yan + * @date 2025-03-29 + */ + +#include +#include "settings/test_settings.h" +#include "unit/test_helper/simple_csv_writer.h" +#include "unit/test_helper/test_help.h" +#include "unit/test_helper/test_tools.h" +#include + +using namespace inspire; + +TEST_CASE("test_SessionFaceTrack", "[session_face_track]") { + DRAW_SPLIT_LINE + TEST_PRINT_OUTPUT(true); + + SECTION("Face detection from image") { + CustomPipelineParameter param; + int32_t ret; + + std::shared_ptr session = std::shared_ptr(Session::CreatePtr(DetectModuleMode::DETECT_MODE_ALWAYS_DETECT, 3, param)); + REQUIRE(session != nullptr); + + auto image = inspirecv::Image::Create(GET_DATA("data/bulk/kun.jpg")); + auto process = inspirecv::FrameProcess::Create(image, inspirecv::DATA_FORMAT::BGR, inspirecv::ROTATION_MODE::ROTATION_0); + + std::vector results; + ret = session->FaceDetectAndTrack(process, results); + REQUIRE(ret == HSUCCEED); + REQUIRE(results.size() == 1); + } + + SECTION("Head pose estimation") { + CustomPipelineParameter param; + int32_t ret; + + std::shared_ptr session = std::shared_ptr(Session::CreatePtr(DetectModuleMode::DETECT_MODE_ALWAYS_DETECT, 3, param)); + REQUIRE(session != nullptr); + + HFMultipleFaceData multipleFaceData = {0}; + std::vector results; + + // Left side face + HFImageStream leftHandle; + auto left = inspirecv::Image::Create(GET_DATA("data/pose/left_face.jpeg")); + auto leftProcess = inspirecv::FrameProcess::Create(left, inspirecv::DATA_FORMAT::BGR, inspirecv::ROTATION_MODE::ROTATION_0); + + ret = session->FaceDetectAndTrack(leftProcess, results); + REQUIRE(ret == HSUCCEED); + REQUIRE(results.size() == 1); + + HFloat yaw, pitch, roll; + bool checked; + + // Left-handed rotation + yaw = results[0].face3DAngle.yaw; + checked = (yaw > -90 && yaw < -10); + CHECK(checked); + + // Right-handed rotation + auto right = inspirecv::Image::Create(GET_DATA("data/pose/right_face.png")); + auto rightProcess = inspirecv::FrameProcess::Create(right, inspirecv::DATA_FORMAT::BGR, inspirecv::ROTATION_MODE::ROTATION_0); + + ret = session->FaceDetectAndTrack(rightProcess, results); + REQUIRE(ret == HSUCCEED); + REQUIRE(results.size() == 1); + + yaw = results[0].face3DAngle.yaw; + checked = (yaw > 10 && yaw < 90); + CHECK(checked); + + // Rise head + auto rise = inspirecv::Image::Create(GET_DATA("data/pose/rise_face.jpeg")); + auto riseProcess = inspirecv::FrameProcess::Create(rise, inspirecv::DATA_FORMAT::BGR, inspirecv::ROTATION_MODE::ROTATION_0); + + ret = session->FaceDetectAndTrack(riseProcess, results); + REQUIRE(ret == HSUCCEED); + REQUIRE(results.size() == 1); + + pitch = results[0].face3DAngle.pitch; + CHECK(pitch > 3); + + // Lower head + auto lower = inspirecv::Image::Create(GET_DATA("data/pose/lower_face.jpeg")); + auto lowerProcess = inspirecv::FrameProcess::Create(lower, inspirecv::DATA_FORMAT::BGR, inspirecv::ROTATION_MODE::ROTATION_0); + + ret = session->FaceDetectAndTrack(lowerProcess, results); + REQUIRE(ret == HSUCCEED); + REQUIRE(results.size() == 1); + + pitch = results[0].face3DAngle.pitch; + CHECK(pitch < -10); + + // Roll head + auto leftWryneck = inspirecv::Image::Create(GET_DATA("data/pose/left_wryneck.png")); + auto leftWryneckProcess = inspirecv::FrameProcess::Create(leftWryneck, inspirecv::DATA_FORMAT::BGR, inspirecv::ROTATION_MODE::ROTATION_0); + + ret = session->FaceDetectAndTrack(leftWryneckProcess, results); + REQUIRE(ret == HSUCCEED); + REQUIRE(results.size() == 1); + + roll = results[0].face3DAngle.roll; + CHECK(roll < -30); + + // Roll head + auto rightWryneck = inspirecv::Image::Create(GET_DATA("data/pose/right_wryneck.png")); + auto rightWryneckProcess = inspirecv::FrameProcess::Create(rightWryneck, inspirecv::DATA_FORMAT::BGR, inspirecv::ROTATION_MODE::ROTATION_0); + + ret = session->FaceDetectAndTrack(rightWryneckProcess, results); + REQUIRE(ret == HSUCCEED); + REQUIRE(results.size() == 1); + + roll = results[0].face3DAngle.roll; + CHECK(roll > 25); + } +} \ No newline at end of file diff --git a/cpp-package/inspireface/doc/CMake-Option.md b/cpp-package/inspireface/doc/CMake-Option.md index e52c50c..e96e4da 100644 --- a/cpp-package/inspireface/doc/CMake-Option.md +++ b/cpp-package/inspireface/doc/CMake-Option.md @@ -7,6 +7,8 @@ Here are the translation details for the compilation parameters as per your requ | ISF_THIRD_PARTY_DIR | 3rdparty | Path for required third-party libraries | | ISF_SANITIZE_ADDRESS | OFF | Enable AddressSanitizer for memory error detection | | ISF_SANITIZE_LEAK | OFF | Enable LeakSanitizer to detect memory leaks | +| ISF_ENABLE_SYMBOL_HIDING | ON | Enable symbol hiding by default for better security and performance. Only symbols explicitly marked for export will be visible. | +| ISF_INSTALL_CPP_HEADER | OFF | Whether to install the headers file for **CPP-API** (the default **C-API** with wider compatibility is recommended) | | ISF_ENABLE_RKNN | OFF | Enable RKNN for Rockchip embedded devices | | ISF_RK_DEVICE_TYPE | RV1109RV1126 | Target device model for Rockchip(Supports RV1109RV1126, RV1106, RV356X) | | ISF_RK_COMPILER_TYPE | armhf | The **armhf**, **armhf-uclibc** and aarch64 compilers are supported. Select one based on the actual situation | @@ -19,6 +21,8 @@ Here are the translation details for the compilation parameters as per your requ | ISF_ENABLE_BENCHMARK | OFF | Enable Benchmark tests for test cases | | ISF_ENABLE_USE_LFW_DATA | OFF | Enable using LFW data for test cases | | ISF_ENABLE_TEST_EVALUATION | OFF | Enable evaluation functionality for test cases, must be used together with ISF_ENABLE_USE_LFW_DATA | +| ISF_ENABLE_TEST_INTERNAL | OFF | Enable test cases for some internal functions, requires disabling **ISF_ENABLE_SYMBOL_HIDING** for compilation. | +| ISF_BUILD_SAMPLE_INTERNAL | OFF | Enable executable examples for some internal functions, requires disabling **ISF_ENABLE_SYMBOL_HIDING** for compilation. | | ISF_ENABLE_TENSORRT | OFF | Enable the backend of inference using TensorRT, Linux must be on **NVIDIA devices**, and **CUDA**, **TensorRT-10** must be installed | | TENSORRT_ROOT | /usr/local/TensorRT | **TensorRT-10** installation path | | ISF_GLOBAL_INFERENCE_BACKEND_USE_MNN_CUDA | OFF | Enable global MNN_CUDA inference mode, requires device support for CUDA | diff --git a/cpp-package/inspireface/docker/Dockerfile.arm-linux-aarch64 b/cpp-package/inspireface/docker/Dockerfile.arm-linux-aarch64 index 1eb6427..fd11604 100644 --- a/cpp-package/inspireface/docker/Dockerfile.arm-linux-aarch64 +++ b/cpp-package/inspireface/docker/Dockerfile.arm-linux-aarch64 @@ -11,7 +11,14 @@ RUN apt-get update && \ git # Install CMake -RUN apt-get install -y --no-install-recommends cmake +RUN apt-get install -y --no-install-recommends + +# Install CMake 3.20.6 +RUN cd /opt && \ + wget https://github.com/Kitware/CMake/releases/download/v3.20.6/cmake-3.20.6-linux-x86_64.tar.gz && \ + tar -zxvf cmake-3.20.6-linux-x86_64.tar.gz && \ + ln -s /opt/cmake-3.20.6-linux-x86_64/bin/* /usr/local/bin/ && \ + rm cmake-3.20.6-linux-x86_64.tar.gz # Set the URL and installation path for the Linaro toolchain ARG LINARO_URL="https://releases.linaro.org/components/toolchain/binaries/6.3-2017.05/aarch64-linux-gnu/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu.tar.xz" diff --git a/cpp-package/inspireface/docker/Dockerfile.arm-linux-gnueabihf b/cpp-package/inspireface/docker/Dockerfile.arm-linux-gnueabihf index 3b2a363..26ba3a8 100644 --- a/cpp-package/inspireface/docker/Dockerfile.arm-linux-gnueabihf +++ b/cpp-package/inspireface/docker/Dockerfile.arm-linux-gnueabihf @@ -12,7 +12,14 @@ RUN apt-get update && \ vim # Install CMake -RUN apt-get install -y --no-install-recommends cmake +RUN apt-get install -y --no-install-recommends + +# Install CMake 3.20.6 +RUN cd /opt && \ + wget https://github.com/Kitware/CMake/releases/download/v3.20.6/cmake-3.20.6-linux-x86_64.tar.gz && \ + tar -zxvf cmake-3.20.6-linux-x86_64.tar.gz && \ + ln -s /opt/cmake-3.20.6-linux-x86_64/bin/* /usr/local/bin/ && \ + rm cmake-3.20.6-linux-x86_64.tar.gz # Set the URL and installation path for the Linaro toolchain ARG LINARO_URL="https://releases.linaro.org/components/toolchain/binaries/6.3-2017.05/arm-linux-gnueabihf/gcc-linaro-6.3.1-2017.05-x86_64_arm-linux-gnueabihf.tar.xz" diff --git a/cpp-package/inspireface/docker/Dockerfile.arm-linux-rockchip830-armhf-uclibc b/cpp-package/inspireface/docker/Dockerfile.arm-linux-rockchip830-armhf-uclibc index 1457ee9..8202bd7 100644 --- a/cpp-package/inspireface/docker/Dockerfile.arm-linux-rockchip830-armhf-uclibc +++ b/cpp-package/inspireface/docker/Dockerfile.arm-linux-rockchip830-armhf-uclibc @@ -13,7 +13,14 @@ RUN apt-get update && \ unzip # Install CMake -RUN apt-get install -y --no-install-recommends cmake +RUN apt-get install -y --no-install-recommends + +# Install CMake 3.20.6 +RUN cd /opt && \ + wget https://github.com/Kitware/CMake/releases/download/v3.20.6/cmake-3.20.6-linux-x86_64.tar.gz && \ + tar -zxvf cmake-3.20.6-linux-x86_64.tar.gz && \ + ln -s /opt/cmake-3.20.6-linux-x86_64/bin/* /usr/local/bin/ && \ + rm cmake-3.20.6-linux-x86_64.tar.gz # Clone RV1106 toolchain repository RUN git clone https://github.com/tunmx/arm-rockchip830-linux-uclibcgnueabihf.git /opt/toolchain diff --git a/cpp-package/inspireface/docker/Dockerfile.cuda.ubuntu18 b/cpp-package/inspireface/docker/Dockerfile.cuda.ubuntu18 index c614d06..597e707 100644 --- a/cpp-package/inspireface/docker/Dockerfile.cuda.ubuntu18 +++ b/cpp-package/inspireface/docker/Dockerfile.cuda.ubuntu18 @@ -7,13 +7,6 @@ ARG all_proxy RUN apt-get update -# Install OpenCV dependency -RUN apt-get install -y --no-install-recommends libgtk-3-dev -RUN apt-get install -y --no-install-recommends libavcodec-dev -RUN apt-get install -y --no-install-recommends libavformat-dev -RUN apt-get install -y --no-install-recommends libswscale-dev -RUN apt-get install -y --no-install-recommends libjpeg-dev -RUN apt-get install -y --no-install-recommends libpng-dev # Update the package list and install basic development tools RUN apt-get install -y --no-install-recommends build-essential @@ -24,8 +17,14 @@ RUN apt-get install -y --no-install-recommends git RUN apt-get install -y --no-install-recommends vim # Install CMake -RUN apt-get install -y --no-install-recommends cmake +RUN apt-get install -y --no-install-recommends +# Install CMake 3.20.6 +RUN cd /opt && \ + wget https://github.com/Kitware/CMake/releases/download/v3.20.6/cmake-3.20.6-linux-x86_64.tar.gz && \ + tar -zxvf cmake-3.20.6-linux-x86_64.tar.gz && \ + ln -s /opt/cmake-3.20.6-linux-x86_64/bin/* /usr/local/bin/ && \ + rm cmake-3.20.6-linux-x86_64.tar.gz # Clean temporary files to reduce image size RUN apt-get clean && \ diff --git a/cpp-package/inspireface/docker/Dockerfile.cuda.ubuntu20 b/cpp-package/inspireface/docker/Dockerfile.cuda.ubuntu20 index 3b7468d..247a5fa 100644 --- a/cpp-package/inspireface/docker/Dockerfile.cuda.ubuntu20 +++ b/cpp-package/inspireface/docker/Dockerfile.cuda.ubuntu20 @@ -7,14 +7,6 @@ ARG all_proxy RUN apt-get update -# Install OpenCV dependency -# RUN apt-get install -y --no-install-recommends libgtk-3-dev -# RUN apt-get install -y --no-install-recommends libavcodec-dev -# RUN apt-get install -y --no-install-recommends libavformat-dev -# RUN apt-get install -y --no-install-recommends libswscale-dev -# RUN apt-get install -y --no-install-recommends libjpeg-dev -# RUN apt-get install -y --no-install-recommends libpng-dev - # Update the package list and install basic development tools RUN apt-get install -y --no-install-recommends build-essential RUN apt-get install -y --no-install-recommends software-properties-common @@ -24,8 +16,14 @@ RUN apt-get install -y --no-install-recommends git RUN apt-get install -y --no-install-recommends vim # Install CMake -RUN apt-get install -y --no-install-recommends cmake +RUN apt-get install -y --no-install-recommends +# Install CMake 3.20.6 +RUN cd /opt && \ + wget https://github.com/Kitware/CMake/releases/download/v3.20.6/cmake-3.20.6-linux-x86_64.tar.gz && \ + tar -zxvf cmake-3.20.6-linux-x86_64.tar.gz && \ + ln -s /opt/cmake-3.20.6-linux-x86_64/bin/* /usr/local/bin/ && \ + rm cmake-3.20.6-linux-x86_64.tar.gz # Clean temporary files to reduce image size RUN apt-get clean && \ diff --git a/cpp-package/inspireface/docker/Dockerfile.cuda12_ubuntu22 b/cpp-package/inspireface/docker/Dockerfile.cuda12_ubuntu22 index 0db03b7..9270b52 100644 --- a/cpp-package/inspireface/docker/Dockerfile.cuda12_ubuntu22 +++ b/cpp-package/inspireface/docker/Dockerfile.cuda12_ubuntu22 @@ -11,9 +11,17 @@ RUN apt-get update RUN apt-get install -y --no-install-recommends build-essential RUN apt-get install -y --no-install-recommends software-properties-common RUN apt-get install -y --no-install-recommends curl +RUN apt-get install -y --no-install-recommends wget # Install CMake -RUN apt-get install -y --no-install-recommends cmake +RUN apt-get install -y --no-install-recommends + +# Install CMake 3.20.6 +RUN cd /opt && \ + wget https://github.com/Kitware/CMake/releases/download/v3.20.6/cmake-3.20.6-linux-x86_64.tar.gz && \ + tar -zxvf cmake-3.20.6-linux-x86_64.tar.gz && \ + ln -s /opt/cmake-3.20.6-linux-x86_64/bin/* /usr/local/bin/ && \ + rm cmake-3.20.6-linux-x86_64.tar.gz # Clean temporary files to reduce image size RUN apt-get clean && \ diff --git a/cpp-package/inspireface/docker/Dockerfile.ubuntu18 b/cpp-package/inspireface/docker/Dockerfile.ubuntu18 index 32c81c7..120e1eb 100644 --- a/cpp-package/inspireface/docker/Dockerfile.ubuntu18 +++ b/cpp-package/inspireface/docker/Dockerfile.ubuntu18 @@ -1,37 +1,32 @@ # Use Ubuntu 18.04 as the base image FROM ubuntu:18.04 -# FROM skybro/ubuntu-cn:18 +# Proxy settings ARG https_proxy ARG http_proxy ARG all_proxy -RUN apt-get update - -# Install OpenCV dependency -RUN apt-get install -y --no-install-recommends libgtk-3-dev -RUN apt-get install -y --no-install-recommends libavcodec-dev -RUN apt-get install -y --no-install-recommends libavformat-dev -RUN apt-get install -y --no-install-recommends libswscale-dev -RUN apt-get install -y --no-install-recommends libjpeg-dev -RUN apt-get install -y --no-install-recommends libpng-dev - -# Update the package list and install basic development tools -RUN apt-get install -y --no-install-recommends build-essential -RUN apt-get install -y --no-install-recommends software-properties-common -RUN apt-get install -y --no-install-recommends wget -RUN apt-get install -y --no-install-recommends curl -RUN apt-get install -y --no-install-recommends git -RUN apt-get install -y --no-install-recommends vim - -# Install CMake -RUN apt-get install -y --no-install-recommends cmake - - -# Clean temporary files to reduce image size -RUN apt-get clean && \ +# Base packages +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + build-essential \ + software-properties-common \ + wget \ + curl \ + git \ + vim \ + ca-certificates \ + openssl && \ + apt-get clean && \ rm -rf /var/lib/apt/lists/* +# Install CMake 3.20.6 +RUN cd /opt && \ + wget https://github.com/Kitware/CMake/releases/download/v3.20.6/cmake-3.20.6-linux-x86_64.tar.gz && \ + tar -zxvf cmake-3.20.6-linux-x86_64.tar.gz && \ + ln -s /opt/cmake-3.20.6-linux-x86_64/bin/* /usr/local/bin/ && \ + rm cmake-3.20.6-linux-x86_64.tar.gz + # Set the working directory WORKDIR /workspace diff --git a/cpp-package/inspireface/python/inspireface/modules/__init__.py b/cpp-package/inspireface/python/inspireface/modules/__init__.py index 68971a2..983af57 100644 --- a/cpp-package/inspireface/python/inspireface/modules/__init__.py +++ b/cpp-package/inspireface/python/inspireface/modules/__init__.py @@ -2,7 +2,7 @@ from .inspireface import ImageStream, FaceExtended, FaceInformation, SessionCust launch, FeatureHubConfiguration, feature_hub_enable, feature_hub_disable, feature_comparison, \ FaceIdentity, feature_hub_set_search_threshold, feature_hub_face_insert, SearchResult, \ feature_hub_face_search, feature_hub_face_search_top_k, feature_hub_face_update, feature_hub_face_remove, \ - feature_hub_get_face_identity, feature_hub_get_face_count, view_table_in_terminal, version, query_launch_status, reload, set_expansive_pack_path, \ + feature_hub_get_face_identity, feature_hub_get_face_count, view_table_in_terminal, version, query_launch_status, reload, \ set_logging_level, disable_logging, show_system_resource_statistics, get_recommended_cosine_threshold, cosine_similarity_convert_to_percentage, \ - get_similarity_converter_config, set_similarity_converter_config, pull_latest_model, \ - HF_PK_AUTO_INCREMENT, HF_PK_MANUAL_INPUT, HF_SEARCH_MODE_EAGER, HF_SEARCH_MODE_EXHAUSTIVE \ No newline at end of file + get_similarity_converter_config, set_similarity_converter_config, pull_latest_model, switch_landmark_engine, \ + HF_PK_AUTO_INCREMENT, HF_PK_MANUAL_INPUT, HF_SEARCH_MODE_EAGER, HF_SEARCH_MODE_EXHAUSTIVE \ No newline at end of file diff --git a/cpp-package/inspireface/python/inspireface/modules/core/native.py b/cpp-package/inspireface/python/inspireface/modules/core/native.py index 75d2774..2171848 100644 --- a/cpp-package/inspireface/python/inspireface/modules/core/native.py +++ b/cpp-package/inspireface/python/inspireface/modules/core/native.py @@ -1050,35 +1050,35 @@ struct_HColor._fields_ = [ HColor = struct_HColor# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/intypedef.h: 58 -enum_HFImageFormat = c_int# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 49 +enum_HFImageFormat = c_int# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 52 -HF_STREAM_RGB = 0# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 49 +HF_STREAM_RGB = 0# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 52 -HF_STREAM_BGR = 1# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 49 +HF_STREAM_BGR = 1# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 52 -HF_STREAM_RGBA = 2# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 49 +HF_STREAM_RGBA = 2# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 52 -HF_STREAM_BGRA = 3# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 49 +HF_STREAM_BGRA = 3# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 52 -HF_STREAM_YUV_NV12 = 4# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 49 +HF_STREAM_YUV_NV12 = 4# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 52 -HF_STREAM_YUV_NV21 = 5# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 49 +HF_STREAM_YUV_NV21 = 5# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 52 -HFImageFormat = enum_HFImageFormat# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 49 +HFImageFormat = enum_HFImageFormat# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 52 -enum_HFRotation = c_int# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 60 +enum_HFRotation = c_int# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 63 -HF_CAMERA_ROTATION_0 = 0# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 60 +HF_CAMERA_ROTATION_0 = 0# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 63 -HF_CAMERA_ROTATION_90 = 1# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 60 +HF_CAMERA_ROTATION_90 = 1# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 63 -HF_CAMERA_ROTATION_180 = 2# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 60 +HF_CAMERA_ROTATION_180 = 2# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 63 -HF_CAMERA_ROTATION_270 = 3# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 60 +HF_CAMERA_ROTATION_270 = 3# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 63 -HFRotation = enum_HFRotation# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 60 +HFRotation = enum_HFRotation# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 63 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 72 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 75 class struct_HFImageData(Structure): pass @@ -1097,47 +1097,47 @@ struct_HFImageData._fields_ = [ ('rotation', HFRotation), ] -HFImageData = struct_HFImageData# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 72 +HFImageData = struct_HFImageData# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 75 -PHFImageData = POINTER(struct_HFImageData)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 72 +PHFImageData = POINTER(struct_HFImageData)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 75 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 83 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 86 if _libs[_LIBRARY_FILENAME].has("HFCreateImageStream", "cdecl"): HFCreateImageStream = _libs[_LIBRARY_FILENAME].get("HFCreateImageStream", "cdecl") HFCreateImageStream.argtypes = [PHFImageData, POINTER(HFImageStream)] HFCreateImageStream.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 93 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 96 if _libs[_LIBRARY_FILENAME].has("HFCreateImageStreamEmpty", "cdecl"): HFCreateImageStreamEmpty = _libs[_LIBRARY_FILENAME].get("HFCreateImageStreamEmpty", "cdecl") HFCreateImageStreamEmpty.argtypes = [POINTER(HFImageStream)] HFCreateImageStreamEmpty.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 104 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 107 if _libs[_LIBRARY_FILENAME].has("HFImageStreamSetBuffer", "cdecl"): HFImageStreamSetBuffer = _libs[_LIBRARY_FILENAME].get("HFImageStreamSetBuffer", "cdecl") HFImageStreamSetBuffer.argtypes = [HFImageStream, HPUInt8, HInt32, HInt32] HFImageStreamSetBuffer.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 113 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 116 if _libs[_LIBRARY_FILENAME].has("HFImageStreamSetRotation", "cdecl"): HFImageStreamSetRotation = _libs[_LIBRARY_FILENAME].get("HFImageStreamSetRotation", "cdecl") HFImageStreamSetRotation.argtypes = [HFImageStream, HFRotation] HFImageStreamSetRotation.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 122 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 125 if _libs[_LIBRARY_FILENAME].has("HFImageStreamSetFormat", "cdecl"): HFImageStreamSetFormat = _libs[_LIBRARY_FILENAME].get("HFImageStreamSetFormat", "cdecl") HFImageStreamSetFormat.argtypes = [HFImageStream, HFImageFormat] HFImageStreamSetFormat.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 132 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 135 if _libs[_LIBRARY_FILENAME].has("HFReleaseImageStream", "cdecl"): HFReleaseImageStream = _libs[_LIBRARY_FILENAME].get("HFReleaseImageStream", "cdecl") HFReleaseImageStream.argtypes = [HFImageStream] HFReleaseImageStream.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 142 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 145 class struct_HFImageBitmapData(Structure): pass @@ -1154,135 +1154,165 @@ struct_HFImageBitmapData._fields_ = [ ('channels', HInt32), ] -HFImageBitmapData = struct_HFImageBitmapData# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 142 +HFImageBitmapData = struct_HFImageBitmapData# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 145 -PHFImageBitmapData = POINTER(struct_HFImageBitmapData)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 142 +PHFImageBitmapData = POINTER(struct_HFImageBitmapData)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 145 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 151 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 154 if _libs[_LIBRARY_FILENAME].has("HFCreateImageBitmap", "cdecl"): HFCreateImageBitmap = _libs[_LIBRARY_FILENAME].get("HFCreateImageBitmap", "cdecl") HFCreateImageBitmap.argtypes = [PHFImageBitmapData, POINTER(HFImageBitmap)] HFCreateImageBitmap.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 161 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 164 if _libs[_LIBRARY_FILENAME].has("HFCreateImageBitmapFromFilePath", "cdecl"): HFCreateImageBitmapFromFilePath = _libs[_LIBRARY_FILENAME].get("HFCreateImageBitmapFromFilePath", "cdecl") HFCreateImageBitmapFromFilePath.argtypes = [HPath, HInt32, POINTER(HFImageBitmap)] HFCreateImageBitmapFromFilePath.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 170 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 173 if _libs[_LIBRARY_FILENAME].has("HFImageBitmapCopy", "cdecl"): HFImageBitmapCopy = _libs[_LIBRARY_FILENAME].get("HFImageBitmapCopy", "cdecl") HFImageBitmapCopy.argtypes = [HFImageBitmap, POINTER(HFImageBitmap)] HFImageBitmapCopy.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 178 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 181 if _libs[_LIBRARY_FILENAME].has("HFReleaseImageBitmap", "cdecl"): HFReleaseImageBitmap = _libs[_LIBRARY_FILENAME].get("HFReleaseImageBitmap", "cdecl") HFReleaseImageBitmap.argtypes = [HFImageBitmap] HFReleaseImageBitmap.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 188 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 191 if _libs[_LIBRARY_FILENAME].has("HFCreateImageStreamFromImageBitmap", "cdecl"): HFCreateImageStreamFromImageBitmap = _libs[_LIBRARY_FILENAME].get("HFCreateImageStreamFromImageBitmap", "cdecl") HFCreateImageStreamFromImageBitmap.argtypes = [HFImageBitmap, HFRotation, POINTER(HFImageStream)] HFCreateImageStreamFromImageBitmap.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 199 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 202 if _libs[_LIBRARY_FILENAME].has("HFCreateImageBitmapFromImageStreamProcess", "cdecl"): HFCreateImageBitmapFromImageStreamProcess = _libs[_LIBRARY_FILENAME].get("HFCreateImageBitmapFromImageStreamProcess", "cdecl") HFCreateImageBitmapFromImageStreamProcess.argtypes = [HFImageStream, POINTER(HFImageBitmap), c_int, c_float] HFCreateImageBitmapFromImageStreamProcess.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 209 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 212 if _libs[_LIBRARY_FILENAME].has("HFImageBitmapWriteToFile", "cdecl"): HFImageBitmapWriteToFile = _libs[_LIBRARY_FILENAME].get("HFImageBitmapWriteToFile", "cdecl") HFImageBitmapWriteToFile.argtypes = [HFImageBitmap, HPath] HFImageBitmapWriteToFile.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 220 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 223 if _libs[_LIBRARY_FILENAME].has("HFImageBitmapDrawRect", "cdecl"): HFImageBitmapDrawRect = _libs[_LIBRARY_FILENAME].get("HFImageBitmapDrawRect", "cdecl") HFImageBitmapDrawRect.argtypes = [HFImageBitmap, HFaceRect, HColor, HInt32] HFImageBitmapDrawRect.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 232 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 235 if _libs[_LIBRARY_FILENAME].has("HFImageBitmapDrawCircleF", "cdecl"): HFImageBitmapDrawCircleF = _libs[_LIBRARY_FILENAME].get("HFImageBitmapDrawCircleF", "cdecl") HFImageBitmapDrawCircleF.argtypes = [HFImageBitmap, HPoint2f, HInt32, HColor, HInt32] HFImageBitmapDrawCircleF.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 233 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 236 if _libs[_LIBRARY_FILENAME].has("HFImageBitmapDrawCircle", "cdecl"): HFImageBitmapDrawCircle = _libs[_LIBRARY_FILENAME].get("HFImageBitmapDrawCircle", "cdecl") HFImageBitmapDrawCircle.argtypes = [HFImageBitmap, HPoint2i, HInt32, HColor, HInt32] HFImageBitmapDrawCircle.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 242 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 245 if _libs[_LIBRARY_FILENAME].has("HFImageBitmapGetData", "cdecl"): HFImageBitmapGetData = _libs[_LIBRARY_FILENAME].get("HFImageBitmapGetData", "cdecl") HFImageBitmapGetData.argtypes = [HFImageBitmap, PHFImageBitmapData] HFImageBitmapGetData.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 252 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 255 if _libs[_LIBRARY_FILENAME].has("HFImageBitmapShow", "cdecl"): HFImageBitmapShow = _libs[_LIBRARY_FILENAME].get("HFImageBitmapShow", "cdecl") HFImageBitmapShow.argtypes = [HFImageBitmap, HString, HInt32] HFImageBitmapShow.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 266 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 269 if _libs[_LIBRARY_FILENAME].has("HFLaunchInspireFace", "cdecl"): HFLaunchInspireFace = _libs[_LIBRARY_FILENAME].get("HFLaunchInspireFace", "cdecl") HFLaunchInspireFace.argtypes = [HPath] HFLaunchInspireFace.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 274 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 277 if _libs[_LIBRARY_FILENAME].has("HFReloadInspireFace", "cdecl"): HFReloadInspireFace = _libs[_LIBRARY_FILENAME].get("HFReloadInspireFace", "cdecl") HFReloadInspireFace.argtypes = [HPath] HFReloadInspireFace.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 282 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 285 if _libs[_LIBRARY_FILENAME].has("HFTerminateInspireFace", "cdecl"): HFTerminateInspireFace = _libs[_LIBRARY_FILENAME].get("HFTerminateInspireFace", "cdecl") HFTerminateInspireFace.argtypes = [] HFTerminateInspireFace.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 290 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 293 if _libs[_LIBRARY_FILENAME].has("HFQueryInspireFaceLaunchStatus", "cdecl"): HFQueryInspireFaceLaunchStatus = _libs[_LIBRARY_FILENAME].get("HFQueryInspireFaceLaunchStatus", "cdecl") HFQueryInspireFaceLaunchStatus.argtypes = [POINTER(HInt32)] HFQueryInspireFaceLaunchStatus.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 308 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 311 if _libs[_LIBRARY_FILENAME].has("HFSetExpansiveHardwareRockchipDmaHeapPath", "cdecl"): HFSetExpansiveHardwareRockchipDmaHeapPath = _libs[_LIBRARY_FILENAME].get("HFSetExpansiveHardwareRockchipDmaHeapPath", "cdecl") HFSetExpansiveHardwareRockchipDmaHeapPath.argtypes = [HPath] HFSetExpansiveHardwareRockchipDmaHeapPath.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 316 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 319 if _libs[_LIBRARY_FILENAME].has("HFQueryExpansiveHardwareRockchipDmaHeapPath", "cdecl"): HFQueryExpansiveHardwareRockchipDmaHeapPath = _libs[_LIBRARY_FILENAME].get("HFQueryExpansiveHardwareRockchipDmaHeapPath", "cdecl") HFQueryExpansiveHardwareRockchipDmaHeapPath.argtypes = [HString] HFQueryExpansiveHardwareRockchipDmaHeapPath.restype = HResult -enum_HFAppleCoreMLInferenceMode = c_int# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 325 +enum_HFAppleCoreMLInferenceMode = c_int# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 328 -HF_APPLE_COREML_INFERENCE_MODE_CPU = 0# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 325 +HF_APPLE_COREML_INFERENCE_MODE_CPU = 0# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 328 -HF_APPLE_COREML_INFERENCE_MODE_GPU = 1# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 325 +HF_APPLE_COREML_INFERENCE_MODE_GPU = 1# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 328 -HF_APPLE_COREML_INFERENCE_MODE_ANE = 2# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 325 +HF_APPLE_COREML_INFERENCE_MODE_ANE = 2# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 328 -HFAppleCoreMLInferenceMode = enum_HFAppleCoreMLInferenceMode# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 325 +HFAppleCoreMLInferenceMode = enum_HFAppleCoreMLInferenceMode# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 328 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 332 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 335 if _libs[_LIBRARY_FILENAME].has("HFSetAppleCoreMLInferenceMode", "cdecl"): HFSetAppleCoreMLInferenceMode = _libs[_LIBRARY_FILENAME].get("HFSetAppleCoreMLInferenceMode", "cdecl") HFSetAppleCoreMLInferenceMode.argtypes = [HFAppleCoreMLInferenceMode] HFSetAppleCoreMLInferenceMode.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 354 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 342 +if _libs[_LIBRARY_FILENAME].has("HFSetCudaDeviceId", "cdecl"): + HFSetCudaDeviceId = _libs[_LIBRARY_FILENAME].get("HFSetCudaDeviceId", "cdecl") + HFSetCudaDeviceId.argtypes = [c_int32] + HFSetCudaDeviceId.restype = HResult + +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 349 +if _libs[_LIBRARY_FILENAME].has("HFGetCudaDeviceId", "cdecl"): + HFGetCudaDeviceId = _libs[_LIBRARY_FILENAME].get("HFGetCudaDeviceId", "cdecl") + HFGetCudaDeviceId.argtypes = [POINTER(c_int32)] + HFGetCudaDeviceId.restype = HResult + +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 355 +if _libs[_LIBRARY_FILENAME].has("HFPrintCudaDeviceInfo", "cdecl"): + HFPrintCudaDeviceInfo = _libs[_LIBRARY_FILENAME].get("HFPrintCudaDeviceInfo", "cdecl") + HFPrintCudaDeviceInfo.argtypes = [] + HFPrintCudaDeviceInfo.restype = HResult + +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 362 +if _libs[_LIBRARY_FILENAME].has("HFGetNumCudaDevices", "cdecl"): + HFGetNumCudaDevices = _libs[_LIBRARY_FILENAME].get("HFGetNumCudaDevices", "cdecl") + HFGetNumCudaDevices.argtypes = [POINTER(c_int32)] + HFGetNumCudaDevices.restype = HResult + +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 369 +if _libs[_LIBRARY_FILENAME].has("HFCheckCudaDeviceSupport", "cdecl"): + HFCheckCudaDeviceSupport = _libs[_LIBRARY_FILENAME].get("HFCheckCudaDeviceSupport", "cdecl") + HFCheckCudaDeviceSupport.argtypes = [POINTER(c_int32)] + HFCheckCudaDeviceSupport.restype = HResult + +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 392 class struct_HFSessionCustomParameter(Structure): pass @@ -1295,6 +1325,7 @@ struct_HFSessionCustomParameter.__slots__ = [ 'enable_face_attribute', 'enable_interaction_liveness', 'enable_detect_mode_landmark', + 'enable_face_pose', ] struct_HFSessionCustomParameter._fields_ = [ ('enable_recognition', HInt32), @@ -1305,41 +1336,58 @@ struct_HFSessionCustomParameter._fields_ = [ ('enable_face_attribute', HInt32), ('enable_interaction_liveness', HInt32), ('enable_detect_mode_landmark', HInt32), + ('enable_face_pose', HInt32), ] -HFSessionCustomParameter = struct_HFSessionCustomParameter# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 354 +HFSessionCustomParameter = struct_HFSessionCustomParameter# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 392 -PHFSessionCustomParameter = POINTER(struct_HFSessionCustomParameter)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 354 +PHFSessionCustomParameter = POINTER(struct_HFSessionCustomParameter)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 392 -enum_HFDetectMode = c_int# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 367 +enum_HFDetectMode = c_int# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 405 -HF_DETECT_MODE_ALWAYS_DETECT = 0# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 367 +HF_DETECT_MODE_ALWAYS_DETECT = 0# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 405 -HF_DETECT_MODE_LIGHT_TRACK = (HF_DETECT_MODE_ALWAYS_DETECT + 1)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 367 +HF_DETECT_MODE_LIGHT_TRACK = (HF_DETECT_MODE_ALWAYS_DETECT + 1)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 405 -HF_DETECT_MODE_TRACK_BY_DETECTION = (HF_DETECT_MODE_LIGHT_TRACK + 1)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 367 +HF_DETECT_MODE_TRACK_BY_DETECTION = (HF_DETECT_MODE_LIGHT_TRACK + 1)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 405 -HFDetectMode = enum_HFDetectMode# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 367 +HFDetectMode = enum_HFDetectMode# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 405 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 383 +enum_HFSessionLandmarkEngine = c_int# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 415 + +HF_LANDMARK_HYPLMV2_0_25 = 0# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 415 + +HF_LANDMARK_HYPLMV2_0_50 = 1# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 415 + +HF_LANDMARK_INSIGHTFACE_2D106_TRACK = 2# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 415 + +HFSessionLandmarkEngine = enum_HFSessionLandmarkEngine# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 415 + +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 423 +if _libs[_LIBRARY_FILENAME].has("HFSwitchLandmarkEngine", "cdecl"): + HFSwitchLandmarkEngine = _libs[_LIBRARY_FILENAME].get("HFSwitchLandmarkEngine", "cdecl") + HFSwitchLandmarkEngine.argtypes = [HFSessionLandmarkEngine] + HFSwitchLandmarkEngine.restype = HResult + +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 439 if _libs[_LIBRARY_FILENAME].has("HFCreateInspireFaceSession", "cdecl"): HFCreateInspireFaceSession = _libs[_LIBRARY_FILENAME].get("HFCreateInspireFaceSession", "cdecl") HFCreateInspireFaceSession.argtypes = [HFSessionCustomParameter, HFDetectMode, HInt32, HInt32, HInt32, POINTER(HFSession)] HFCreateInspireFaceSession.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 400 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 456 if _libs[_LIBRARY_FILENAME].has("HFCreateInspireFaceSessionOptional", "cdecl"): HFCreateInspireFaceSessionOptional = _libs[_LIBRARY_FILENAME].get("HFCreateInspireFaceSessionOptional", "cdecl") HFCreateInspireFaceSessionOptional.argtypes = [HOption, HFDetectMode, HInt32, HInt32, HInt32, POINTER(HFSession)] HFCreateInspireFaceSessionOptional.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 409 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 465 if _libs[_LIBRARY_FILENAME].has("HFReleaseInspireFaceSession", "cdecl"): HFReleaseInspireFaceSession = _libs[_LIBRARY_FILENAME].get("HFReleaseInspireFaceSession", "cdecl") HFReleaseInspireFaceSession.argtypes = [HFSession] HFReleaseInspireFaceSession.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 419 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 475 class struct_HFFaceBasicToken(Structure): pass @@ -1352,11 +1400,11 @@ struct_HFFaceBasicToken._fields_ = [ ('data', HPVoid), ] -HFFaceBasicToken = struct_HFFaceBasicToken# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 419 +HFFaceBasicToken = struct_HFFaceBasicToken# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 475 -PHFFaceBasicToken = POINTER(struct_HFFaceBasicToken)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 419 +PHFFaceBasicToken = POINTER(struct_HFFaceBasicToken)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 475 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 430 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 486 class struct_HFFaceEulerAngle(Structure): pass @@ -1371,9 +1419,9 @@ struct_HFFaceEulerAngle._fields_ = [ ('pitch', POINTER(HFloat)), ] -HFFaceEulerAngle = struct_HFFaceEulerAngle# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 430 +HFFaceEulerAngle = struct_HFFaceEulerAngle# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 486 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 445 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 501 class struct_HFMultipleFaceData(Structure): pass @@ -1394,95 +1442,96 @@ struct_HFMultipleFaceData._fields_ = [ ('tokens', PHFFaceBasicToken), ] -HFMultipleFaceData = struct_HFMultipleFaceData# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 445 +HFMultipleFaceData = struct_HFMultipleFaceData# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 501 -PHFMultipleFaceData = POINTER(struct_HFMultipleFaceData)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 445 +PHFMultipleFaceData = POINTER(struct_HFMultipleFaceData)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 501 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 455 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 511 if _libs[_LIBRARY_FILENAME].has("HFSessionSetTrackPreviewSize", "cdecl"): HFSessionSetTrackPreviewSize = _libs[_LIBRARY_FILENAME].get("HFSessionSetTrackPreviewSize", "cdecl") HFSessionSetTrackPreviewSize.argtypes = [HFSession, HInt32] HFSessionSetTrackPreviewSize.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 465 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 521 if _libs[_LIBRARY_FILENAME].has("HFSessionSetFilterMinimumFacePixelSize", "cdecl"): HFSessionSetFilterMinimumFacePixelSize = _libs[_LIBRARY_FILENAME].get("HFSessionSetFilterMinimumFacePixelSize", "cdecl") HFSessionSetFilterMinimumFacePixelSize.argtypes = [HFSession, HInt32] HFSessionSetFilterMinimumFacePixelSize.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 474 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 530 if _libs[_LIBRARY_FILENAME].has("HFSessionSetFaceDetectThreshold", "cdecl"): HFSessionSetFaceDetectThreshold = _libs[_LIBRARY_FILENAME].get("HFSessionSetFaceDetectThreshold", "cdecl") HFSessionSetFaceDetectThreshold.argtypes = [HFSession, HFloat] HFSessionSetFaceDetectThreshold.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 483 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 539 if _libs[_LIBRARY_FILENAME].has("HFSessionSetTrackModeSmoothRatio", "cdecl"): HFSessionSetTrackModeSmoothRatio = _libs[_LIBRARY_FILENAME].get("HFSessionSetTrackModeSmoothRatio", "cdecl") HFSessionSetTrackModeSmoothRatio.argtypes = [HFSession, HFloat] HFSessionSetTrackModeSmoothRatio.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 492 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 548 if _libs[_LIBRARY_FILENAME].has("HFSessionSetTrackModeNumSmoothCacheFrame", "cdecl"): HFSessionSetTrackModeNumSmoothCacheFrame = _libs[_LIBRARY_FILENAME].get("HFSessionSetTrackModeNumSmoothCacheFrame", "cdecl") HFSessionSetTrackModeNumSmoothCacheFrame.argtypes = [HFSession, HInt32] HFSessionSetTrackModeNumSmoothCacheFrame.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 501 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 557 if _libs[_LIBRARY_FILENAME].has("HFSessionSetTrackModeDetectInterval", "cdecl"): HFSessionSetTrackModeDetectInterval = _libs[_LIBRARY_FILENAME].get("HFSessionSetTrackModeDetectInterval", "cdecl") HFSessionSetTrackModeDetectInterval.argtypes = [HFSession, HInt32] HFSessionSetTrackModeDetectInterval.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 511 + +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 576 if _libs[_LIBRARY_FILENAME].has("HFExecuteFaceTrack", "cdecl"): HFExecuteFaceTrack = _libs[_LIBRARY_FILENAME].get("HFExecuteFaceTrack", "cdecl") HFExecuteFaceTrack.argtypes = [HFSession, HFImageStream, PHFMultipleFaceData] HFExecuteFaceTrack.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 528 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 593 if _libs[_LIBRARY_FILENAME].has("HFCopyFaceBasicToken", "cdecl"): HFCopyFaceBasicToken = _libs[_LIBRARY_FILENAME].get("HFCopyFaceBasicToken", "cdecl") HFCopyFaceBasicToken.argtypes = [HFFaceBasicToken, HPBuffer, HInt32] HFCopyFaceBasicToken.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 542 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 607 if _libs[_LIBRARY_FILENAME].has("HFGetFaceBasicTokenSize", "cdecl"): HFGetFaceBasicTokenSize = _libs[_LIBRARY_FILENAME].get("HFGetFaceBasicTokenSize", "cdecl") HFGetFaceBasicTokenSize.argtypes = [HPInt32] HFGetFaceBasicTokenSize.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 549 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 614 if _libs[_LIBRARY_FILENAME].has("HFGetNumOfFaceDenseLandmark", "cdecl"): HFGetNumOfFaceDenseLandmark = _libs[_LIBRARY_FILENAME].get("HFGetNumOfFaceDenseLandmark", "cdecl") HFGetNumOfFaceDenseLandmark.argtypes = [HPInt32] HFGetNumOfFaceDenseLandmark.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 559 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 624 if _libs[_LIBRARY_FILENAME].has("HFGetFaceDenseLandmarkFromFaceToken", "cdecl"): HFGetFaceDenseLandmarkFromFaceToken = _libs[_LIBRARY_FILENAME].get("HFGetFaceDenseLandmarkFromFaceToken", "cdecl") HFGetFaceDenseLandmarkFromFaceToken.argtypes = [HFFaceBasicToken, POINTER(HPoint2f), HInt32] HFGetFaceDenseLandmarkFromFaceToken.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 568 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 633 if _libs[_LIBRARY_FILENAME].has("HFGetFaceFiveKeyPointsFromFaceToken", "cdecl"): HFGetFaceFiveKeyPointsFromFaceToken = _libs[_LIBRARY_FILENAME].get("HFGetFaceFiveKeyPointsFromFaceToken", "cdecl") HFGetFaceFiveKeyPointsFromFaceToken.argtypes = [HFFaceBasicToken, POINTER(HPoint2f), HInt32] HFGetFaceFiveKeyPointsFromFaceToken.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 575 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 640 if _libs[_LIBRARY_FILENAME].has("HFSessionSetEnableTrackCostSpend", "cdecl"): HFSessionSetEnableTrackCostSpend = _libs[_LIBRARY_FILENAME].get("HFSessionSetEnableTrackCostSpend", "cdecl") HFSessionSetEnableTrackCostSpend.argtypes = [HFSession, c_int] HFSessionSetEnableTrackCostSpend.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 582 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 647 if _libs[_LIBRARY_FILENAME].has("HFSessionPrintTrackCostSpend", "cdecl"): HFSessionPrintTrackCostSpend = _libs[_LIBRARY_FILENAME].get("HFSessionPrintTrackCostSpend", "cdecl") HFSessionPrintTrackCostSpend.argtypes = [HFSession] HFSessionPrintTrackCostSpend.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 596 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 661 class struct_HFFaceFeature(Structure): pass @@ -1495,45 +1544,69 @@ struct_HFFaceFeature._fields_ = [ ('data', HPFloat), ] -HFFaceFeature = struct_HFFaceFeature# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 596 +HFFaceFeature = struct_HFFaceFeature# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 661 -PHFFaceFeature = POINTER(struct_HFFaceFeature)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 596 +PHFFaceFeature = POINTER(struct_HFFaceFeature)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 661 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 607 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 672 if _libs[_LIBRARY_FILENAME].has("HFFaceFeatureExtract", "cdecl"): HFFaceFeatureExtract = _libs[_LIBRARY_FILENAME].get("HFFaceFeatureExtract", "cdecl") HFFaceFeatureExtract.argtypes = [HFSession, HFImageStream, HFFaceBasicToken, PHFFaceFeature] HFFaceFeatureExtract.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 619 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 683 +if _libs[_LIBRARY_FILENAME].has("HFFaceFeatureExtractTo", "cdecl"): + HFFaceFeatureExtractTo = _libs[_LIBRARY_FILENAME].get("HFFaceFeatureExtractTo", "cdecl") + HFFaceFeatureExtractTo.argtypes = [HFSession, HFImageStream, HFFaceBasicToken, HFFaceFeature] + HFFaceFeatureExtractTo.restype = HResult + +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 695 if _libs[_LIBRARY_FILENAME].has("HFFaceFeatureExtractCpy", "cdecl"): HFFaceFeatureExtractCpy = _libs[_LIBRARY_FILENAME].get("HFFaceFeatureExtractCpy", "cdecl") HFFaceFeatureExtractCpy.argtypes = [HFSession, HFImageStream, HFFaceBasicToken, HPFloat] HFFaceFeatureExtractCpy.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 629 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 702 +if _libs[_LIBRARY_FILENAME].has("HFCreateFaceFeature", "cdecl"): + HFCreateFaceFeature = _libs[_LIBRARY_FILENAME].get("HFCreateFaceFeature", "cdecl") + HFCreateFaceFeature.argtypes = [PHFFaceFeature] + HFCreateFaceFeature.restype = HResult + +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 709 +if _libs[_LIBRARY_FILENAME].has("HFReleaseFaceFeature", "cdecl"): + HFReleaseFaceFeature = _libs[_LIBRARY_FILENAME].get("HFReleaseFaceFeature", "cdecl") + HFReleaseFaceFeature.argtypes = [PHFFaceFeature] + HFReleaseFaceFeature.restype = HResult + +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 719 if _libs[_LIBRARY_FILENAME].has("HFFaceGetFaceAlignmentImage", "cdecl"): HFFaceGetFaceAlignmentImage = _libs[_LIBRARY_FILENAME].get("HFFaceGetFaceAlignmentImage", "cdecl") HFFaceGetFaceAlignmentImage.argtypes = [HFSession, HFImageStream, HFFaceBasicToken, POINTER(HFImageBitmap)] HFFaceGetFaceAlignmentImage.restype = HResult -enum_HFSearchMode = c_int# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 643 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 729 +if _libs[_LIBRARY_FILENAME].has("HFFaceFeatureExtractWithAlignmentImage", "cdecl"): + HFFaceFeatureExtractWithAlignmentImage = _libs[_LIBRARY_FILENAME].get("HFFaceFeatureExtractWithAlignmentImage", "cdecl") + HFFaceFeatureExtractWithAlignmentImage.argtypes = [HFSession, HFImageStream, HFFaceFeature] + HFFaceFeatureExtractWithAlignmentImage.restype = HResult -HF_SEARCH_MODE_EAGER = 0# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 643 +enum_HFSearchMode = c_int# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 742 -HF_SEARCH_MODE_EXHAUSTIVE = (HF_SEARCH_MODE_EAGER + 1)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 643 +HF_SEARCH_MODE_EAGER = 0# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 742 -HFSearchMode = enum_HFSearchMode# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 643 +HF_SEARCH_MODE_EXHAUSTIVE = (HF_SEARCH_MODE_EAGER + 1)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 742 -enum_HFPKMode = c_int# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 651 +HFSearchMode = enum_HFSearchMode# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 742 -HF_PK_AUTO_INCREMENT = 0# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 651 +enum_HFPKMode = c_int# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 750 -HF_PK_MANUAL_INPUT = (HF_PK_AUTO_INCREMENT + 1)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 651 +HF_PK_AUTO_INCREMENT = 0# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 750 -HFPKMode = enum_HFPKMode# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 651 +HF_PK_MANUAL_INPUT = (HF_PK_AUTO_INCREMENT + 1)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 750 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 664 +HFPKMode = enum_HFPKMode# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 750 + +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 763 class struct_HFFeatureHubConfiguration(Structure): pass @@ -1552,21 +1625,21 @@ struct_HFFeatureHubConfiguration._fields_ = [ ('searchMode', HFSearchMode), ] -HFFeatureHubConfiguration = struct_HFFeatureHubConfiguration# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 664 +HFFeatureHubConfiguration = struct_HFFeatureHubConfiguration# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 763 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 676 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 775 if _libs[_LIBRARY_FILENAME].has("HFFeatureHubDataEnable", "cdecl"): HFFeatureHubDataEnable = _libs[_LIBRARY_FILENAME].get("HFFeatureHubDataEnable", "cdecl") HFFeatureHubDataEnable.argtypes = [HFFeatureHubConfiguration] HFFeatureHubDataEnable.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 682 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 781 if _libs[_LIBRARY_FILENAME].has("HFFeatureHubDataDisable", "cdecl"): HFFeatureHubDataDisable = _libs[_LIBRARY_FILENAME].get("HFFeatureHubDataDisable", "cdecl") HFFeatureHubDataDisable.argtypes = [] HFFeatureHubDataDisable.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 693 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 792 class struct_HFFaceFeatureIdentity(Structure): pass @@ -1579,11 +1652,11 @@ struct_HFFaceFeatureIdentity._fields_ = [ ('feature', PHFFaceFeature), ] -HFFaceFeatureIdentity = struct_HFFaceFeatureIdentity# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 693 +HFFaceFeatureIdentity = struct_HFFaceFeatureIdentity# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 792 -PHFFaceFeatureIdentity = POINTER(struct_HFFaceFeatureIdentity)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 693 +PHFFaceFeatureIdentity = POINTER(struct_HFFaceFeatureIdentity)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 792 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 702 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 801 class struct_HFSearchTopKResults(Structure): pass @@ -1598,35 +1671,35 @@ struct_HFSearchTopKResults._fields_ = [ ('ids', HPFaceId), ] -HFSearchTopKResults = struct_HFSearchTopKResults# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 702 +HFSearchTopKResults = struct_HFSearchTopKResults# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 801 -PHFSearchTopKResults = POINTER(struct_HFSearchTopKResults)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 702 +PHFSearchTopKResults = POINTER(struct_HFSearchTopKResults)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 801 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 714 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 813 if _libs[_LIBRARY_FILENAME].has("HFFeatureHubFaceSearchThresholdSetting", "cdecl"): HFFeatureHubFaceSearchThresholdSetting = _libs[_LIBRARY_FILENAME].get("HFFeatureHubFaceSearchThresholdSetting", "cdecl") HFFeatureHubFaceSearchThresholdSetting.argtypes = [c_float] HFFeatureHubFaceSearchThresholdSetting.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 729 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 828 if _libs[_LIBRARY_FILENAME].has("HFFaceComparison", "cdecl"): HFFaceComparison = _libs[_LIBRARY_FILENAME].get("HFFaceComparison", "cdecl") HFFaceComparison.argtypes = [HFFaceFeature, HFFaceFeature, HPFloat] HFFaceComparison.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 736 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 835 if _libs[_LIBRARY_FILENAME].has("HFGetRecommendedCosineThreshold", "cdecl"): HFGetRecommendedCosineThreshold = _libs[_LIBRARY_FILENAME].get("HFGetRecommendedCosineThreshold", "cdecl") HFGetRecommendedCosineThreshold.argtypes = [HPFloat] HFGetRecommendedCosineThreshold.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 749 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 848 if _libs[_LIBRARY_FILENAME].has("HFCosineSimilarityConvertToPercentage", "cdecl"): HFCosineSimilarityConvertToPercentage = _libs[_LIBRARY_FILENAME].get("HFCosineSimilarityConvertToPercentage", "cdecl") HFCosineSimilarityConvertToPercentage.argtypes = [HFloat, HPFloat] HFCosineSimilarityConvertToPercentage.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 762 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 861 class struct_HFSimilarityConverterConfig(Structure): pass @@ -1645,77 +1718,77 @@ struct_HFSimilarityConverterConfig._fields_ = [ ('outputMax', HFloat), ] -HFSimilarityConverterConfig = struct_HFSimilarityConverterConfig# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 762 +HFSimilarityConverterConfig = struct_HFSimilarityConverterConfig# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 861 -PHFSimilarityConverterConfig = POINTER(struct_HFSimilarityConverterConfig)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 762 +PHFSimilarityConverterConfig = POINTER(struct_HFSimilarityConverterConfig)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 861 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 771 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 870 if _libs[_LIBRARY_FILENAME].has("HFUpdateCosineSimilarityConverter", "cdecl"): HFUpdateCosineSimilarityConverter = _libs[_LIBRARY_FILENAME].get("HFUpdateCosineSimilarityConverter", "cdecl") HFUpdateCosineSimilarityConverter.argtypes = [HFSimilarityConverterConfig] HFUpdateCosineSimilarityConverter.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 778 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 877 if _libs[_LIBRARY_FILENAME].has("HFGetCosineSimilarityConverter", "cdecl"): HFGetCosineSimilarityConverter = _libs[_LIBRARY_FILENAME].get("HFGetCosineSimilarityConverter", "cdecl") HFGetCosineSimilarityConverter.argtypes = [PHFSimilarityConverterConfig] HFGetCosineSimilarityConverter.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 786 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 885 if _libs[_LIBRARY_FILENAME].has("HFGetFeatureLength", "cdecl"): HFGetFeatureLength = _libs[_LIBRARY_FILENAME].get("HFGetFeatureLength", "cdecl") HFGetFeatureLength.argtypes = [HPInt32] HFGetFeatureLength.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 794 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 893 if _libs[_LIBRARY_FILENAME].has("HFFeatureHubInsertFeature", "cdecl"): HFFeatureHubInsertFeature = _libs[_LIBRARY_FILENAME].get("HFFeatureHubInsertFeature", "cdecl") HFFeatureHubInsertFeature.argtypes = [HFFaceFeatureIdentity, HPFaceId] HFFeatureHubInsertFeature.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 805 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 904 if _libs[_LIBRARY_FILENAME].has("HFFeatureHubFaceSearch", "cdecl"): HFFeatureHubFaceSearch = _libs[_LIBRARY_FILENAME].get("HFFeatureHubFaceSearch", "cdecl") HFFeatureHubFaceSearch.argtypes = [HFFaceFeature, HPFloat, PHFFaceFeatureIdentity] HFFeatureHubFaceSearch.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 815 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 914 if _libs[_LIBRARY_FILENAME].has("HFFeatureHubFaceSearchTopK", "cdecl"): HFFeatureHubFaceSearchTopK = _libs[_LIBRARY_FILENAME].get("HFFeatureHubFaceSearchTopK", "cdecl") HFFeatureHubFaceSearchTopK.argtypes = [HFFaceFeature, HInt32, PHFSearchTopKResults] HFFeatureHubFaceSearchTopK.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 823 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 922 if _libs[_LIBRARY_FILENAME].has("HFFeatureHubFaceRemove", "cdecl"): HFFeatureHubFaceRemove = _libs[_LIBRARY_FILENAME].get("HFFeatureHubFaceRemove", "cdecl") HFFeatureHubFaceRemove.argtypes = [HFaceId] HFFeatureHubFaceRemove.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 831 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 930 if _libs[_LIBRARY_FILENAME].has("HFFeatureHubFaceUpdate", "cdecl"): HFFeatureHubFaceUpdate = _libs[_LIBRARY_FILENAME].get("HFFeatureHubFaceUpdate", "cdecl") HFFeatureHubFaceUpdate.argtypes = [HFFaceFeatureIdentity] HFFeatureHubFaceUpdate.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 840 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 939 if _libs[_LIBRARY_FILENAME].has("HFFeatureHubGetFaceIdentity", "cdecl"): HFFeatureHubGetFaceIdentity = _libs[_LIBRARY_FILENAME].get("HFFeatureHubGetFaceIdentity", "cdecl") HFFeatureHubGetFaceIdentity.argtypes = [HFaceId, PHFFaceFeatureIdentity] HFFeatureHubGetFaceIdentity.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 848 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 947 if _libs[_LIBRARY_FILENAME].has("HFFeatureHubGetFaceCount", "cdecl"): HFFeatureHubGetFaceCount = _libs[_LIBRARY_FILENAME].get("HFFeatureHubGetFaceCount", "cdecl") HFFeatureHubGetFaceCount.argtypes = [POINTER(HInt32)] HFFeatureHubGetFaceCount.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 855 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 954 if _libs[_LIBRARY_FILENAME].has("HFFeatureHubViewDBTable", "cdecl"): HFFeatureHubViewDBTable = _libs[_LIBRARY_FILENAME].get("HFFeatureHubViewDBTable", "cdecl") HFFeatureHubViewDBTable.argtypes = [] HFFeatureHubViewDBTable.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 863 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 962 class struct_HFFeatureHubExistingIds(Structure): pass @@ -1728,29 +1801,29 @@ struct_HFFeatureHubExistingIds._fields_ = [ ('ids', HPFaceId), ] -HFFeatureHubExistingIds = struct_HFFeatureHubExistingIds# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 863 +HFFeatureHubExistingIds = struct_HFFeatureHubExistingIds# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 962 -PHFFeatureHubExistingIds = POINTER(struct_HFFeatureHubExistingIds)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 863 +PHFFeatureHubExistingIds = POINTER(struct_HFFeatureHubExistingIds)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 962 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 870 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 969 if _libs[_LIBRARY_FILENAME].has("HFFeatureHubGetExistingIds", "cdecl"): HFFeatureHubGetExistingIds = _libs[_LIBRARY_FILENAME].get("HFFeatureHubGetExistingIds", "cdecl") HFFeatureHubGetExistingIds.argtypes = [PHFFeatureHubExistingIds] HFFeatureHubGetExistingIds.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 888 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 987 if _libs[_LIBRARY_FILENAME].has("HFMultipleFacePipelineProcess", "cdecl"): HFMultipleFacePipelineProcess = _libs[_LIBRARY_FILENAME].get("HFMultipleFacePipelineProcess", "cdecl") HFMultipleFacePipelineProcess.argtypes = [HFSession, HFImageStream, PHFMultipleFaceData, HFSessionCustomParameter] HFMultipleFacePipelineProcess.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 903 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1002 if _libs[_LIBRARY_FILENAME].has("HFMultipleFacePipelineProcessOptional", "cdecl"): HFMultipleFacePipelineProcessOptional = _libs[_LIBRARY_FILENAME].get("HFMultipleFacePipelineProcessOptional", "cdecl") HFMultipleFacePipelineProcessOptional.argtypes = [HFSession, HFImageStream, PHFMultipleFaceData, HInt32] HFMultipleFacePipelineProcessOptional.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 915 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1014 class struct_HFRGBLivenessConfidence(Structure): pass @@ -1763,17 +1836,17 @@ struct_HFRGBLivenessConfidence._fields_ = [ ('confidence', HPFloat), ] -HFRGBLivenessConfidence = struct_HFRGBLivenessConfidence# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 915 +HFRGBLivenessConfidence = struct_HFRGBLivenessConfidence# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1014 -PHFRGBLivenessConfidence = POINTER(struct_HFRGBLivenessConfidence)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 915 +PHFRGBLivenessConfidence = POINTER(struct_HFRGBLivenessConfidence)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1014 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 927 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1026 if _libs[_LIBRARY_FILENAME].has("HFGetRGBLivenessConfidence", "cdecl"): HFGetRGBLivenessConfidence = _libs[_LIBRARY_FILENAME].get("HFGetRGBLivenessConfidence", "cdecl") HFGetRGBLivenessConfidence.argtypes = [HFSession, PHFRGBLivenessConfidence] HFGetRGBLivenessConfidence.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 938 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1037 class struct_HFFaceMaskConfidence(Structure): pass @@ -1786,17 +1859,17 @@ struct_HFFaceMaskConfidence._fields_ = [ ('confidence', HPFloat), ] -HFFaceMaskConfidence = struct_HFFaceMaskConfidence# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 938 +HFFaceMaskConfidence = struct_HFFaceMaskConfidence# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1037 -PHFFaceMaskConfidence = POINTER(struct_HFFaceMaskConfidence)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 938 +PHFFaceMaskConfidence = POINTER(struct_HFFaceMaskConfidence)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1037 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 950 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1049 if _libs[_LIBRARY_FILENAME].has("HFGetFaceMaskConfidence", "cdecl"): HFGetFaceMaskConfidence = _libs[_LIBRARY_FILENAME].get("HFGetFaceMaskConfidence", "cdecl") HFGetFaceMaskConfidence.argtypes = [HFSession, PHFFaceMaskConfidence] HFGetFaceMaskConfidence.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 961 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1060 class struct_HFFaceQualityConfidence(Structure): pass @@ -1809,23 +1882,23 @@ struct_HFFaceQualityConfidence._fields_ = [ ('confidence', HPFloat), ] -HFFaceQualityConfidence = struct_HFFaceQualityConfidence# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 961 +HFFaceQualityConfidence = struct_HFFaceQualityConfidence# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1060 -PHFFaceQualityConfidence = POINTER(struct_HFFaceQualityConfidence)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 961 +PHFFaceQualityConfidence = POINTER(struct_HFFaceQualityConfidence)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1060 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 973 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1072 if _libs[_LIBRARY_FILENAME].has("HFGetFaceQualityConfidence", "cdecl"): HFGetFaceQualityConfidence = _libs[_LIBRARY_FILENAME].get("HFGetFaceQualityConfidence", "cdecl") HFGetFaceQualityConfidence.argtypes = [HFSession, PHFFaceQualityConfidence] HFGetFaceQualityConfidence.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 985 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1084 if _libs[_LIBRARY_FILENAME].has("HFFaceQualityDetect", "cdecl"): HFFaceQualityDetect = _libs[_LIBRARY_FILENAME].get("HFFaceQualityDetect", "cdecl") HFFaceQualityDetect.argtypes = [HFSession, HFFaceBasicToken, POINTER(HFloat)] HFFaceQualityDetect.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 996 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1095 class struct_HFFaceInteractionState(Structure): pass @@ -1840,17 +1913,17 @@ struct_HFFaceInteractionState._fields_ = [ ('rightEyeStatusConfidence', HPFloat), ] -HFFaceInteractionState = struct_HFFaceInteractionState# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 996 +HFFaceInteractionState = struct_HFFaceInteractionState# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1095 -PHFFaceInteractionState = POINTER(struct_HFFaceInteractionState)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 996 +PHFFaceInteractionState = POINTER(struct_HFFaceInteractionState)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1095 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1003 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1102 if _libs[_LIBRARY_FILENAME].has("HFGetFaceInteractionStateResult", "cdecl"): HFGetFaceInteractionStateResult = _libs[_LIBRARY_FILENAME].get("HFGetFaceInteractionStateResult", "cdecl") HFGetFaceInteractionStateResult.argtypes = [HFSession, PHFFaceInteractionState] HFGetFaceInteractionStateResult.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1015 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1114 class struct_HFFaceInteractionsActions(Structure): pass @@ -1871,17 +1944,17 @@ struct_HFFaceInteractionsActions._fields_ = [ ('blink', HPInt32), ] -HFFaceInteractionsActions = struct_HFFaceInteractionsActions# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1015 +HFFaceInteractionsActions = struct_HFFaceInteractionsActions# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1114 -PHFFaceInteractionsActions = POINTER(struct_HFFaceInteractionsActions)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1015 +PHFFaceInteractionsActions = POINTER(struct_HFFaceInteractionsActions)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1114 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1023 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1122 if _libs[_LIBRARY_FILENAME].has("HFGetFaceInteractionActionsResult", "cdecl"): HFGetFaceInteractionActionsResult = _libs[_LIBRARY_FILENAME].get("HFGetFaceInteractionActionsResult", "cdecl") HFGetFaceInteractionActionsResult.argtypes = [HFSession, PHFFaceInteractionsActions] HFGetFaceInteractionActionsResult.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1050 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1149 class struct_HFFaceAttributeResult(Structure): pass @@ -1898,17 +1971,17 @@ struct_HFFaceAttributeResult._fields_ = [ ('ageBracket', HPInt32), ] -HFFaceAttributeResult = struct_HFFaceAttributeResult# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1050 +HFFaceAttributeResult = struct_HFFaceAttributeResult# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1149 -PHFFaceAttributeResult = POINTER(struct_HFFaceAttributeResult)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1050 +PHFFaceAttributeResult = POINTER(struct_HFFaceAttributeResult)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1149 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1062 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1161 if _libs[_LIBRARY_FILENAME].has("HFGetFaceAttributeResult", "cdecl"): HFGetFaceAttributeResult = _libs[_LIBRARY_FILENAME].get("HFGetFaceAttributeResult", "cdecl") HFGetFaceAttributeResult.argtypes = [HFSession, PHFFaceAttributeResult] HFGetFaceAttributeResult.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1075 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1174 class struct_HFInspireFaceVersion(Structure): pass @@ -1923,17 +1996,17 @@ struct_HFInspireFaceVersion._fields_ = [ ('patch', c_int), ] -HFInspireFaceVersion = struct_HFInspireFaceVersion# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1075 +HFInspireFaceVersion = struct_HFInspireFaceVersion# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1174 -PHFInspireFaceVersion = POINTER(struct_HFInspireFaceVersion)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1075 +PHFInspireFaceVersion = POINTER(struct_HFInspireFaceVersion)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1174 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1085 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1184 if _libs[_LIBRARY_FILENAME].has("HFQueryInspireFaceVersion", "cdecl"): HFQueryInspireFaceVersion = _libs[_LIBRARY_FILENAME].get("HFQueryInspireFaceVersion", "cdecl") HFQueryInspireFaceVersion.argtypes = [PHFInspireFaceVersion] HFQueryInspireFaceVersion.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1093 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1192 class struct_HFInspireFaceExtendedInformation(Structure): pass @@ -1944,45 +2017,45 @@ struct_HFInspireFaceExtendedInformation._fields_ = [ ('information', HChar * int(256)), ] -HFInspireFaceExtendedInformation = struct_HFInspireFaceExtendedInformation# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1093 +HFInspireFaceExtendedInformation = struct_HFInspireFaceExtendedInformation# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1192 -PHFInspireFaceExtendedInformation = POINTER(struct_HFInspireFaceExtendedInformation)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1093 +PHFInspireFaceExtendedInformation = POINTER(struct_HFInspireFaceExtendedInformation)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1192 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1100 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1199 if _libs[_LIBRARY_FILENAME].has("HFQueryInspireFaceExtendedInformation", "cdecl"): HFQueryInspireFaceExtendedInformation = _libs[_LIBRARY_FILENAME].get("HFQueryInspireFaceExtendedInformation", "cdecl") HFQueryInspireFaceExtendedInformation.argtypes = [PHFInspireFaceExtendedInformation] HFQueryInspireFaceExtendedInformation.restype = HResult -enum_HFLogLevel = c_int# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1112 +enum_HFLogLevel = c_int# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1211 -HF_LOG_NONE = 0# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1112 +HF_LOG_NONE = 0# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1211 -HF_LOG_DEBUG = (HF_LOG_NONE + 1)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1112 +HF_LOG_DEBUG = (HF_LOG_NONE + 1)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1211 -HF_LOG_INFO = (HF_LOG_DEBUG + 1)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1112 +HF_LOG_INFO = (HF_LOG_DEBUG + 1)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1211 -HF_LOG_WARN = (HF_LOG_INFO + 1)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1112 +HF_LOG_WARN = (HF_LOG_INFO + 1)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1211 -HF_LOG_ERROR = (HF_LOG_WARN + 1)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1112 +HF_LOG_ERROR = (HF_LOG_WARN + 1)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1211 -HF_LOG_FATAL = (HF_LOG_ERROR + 1)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1112 +HF_LOG_FATAL = (HF_LOG_ERROR + 1)# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1211 -HFLogLevel = enum_HFLogLevel# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1112 +HFLogLevel = enum_HFLogLevel# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1211 -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1117 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1216 if _libs[_LIBRARY_FILENAME].has("HFSetLogLevel", "cdecl"): HFSetLogLevel = _libs[_LIBRARY_FILENAME].get("HFSetLogLevel", "cdecl") HFSetLogLevel.argtypes = [HFLogLevel] HFSetLogLevel.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1122 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1221 if _libs[_LIBRARY_FILENAME].has("HFLogDisable", "cdecl"): HFLogDisable = _libs[_LIBRARY_FILENAME].get("HFLogDisable", "cdecl") HFLogDisable.argtypes = [] HFLogDisable.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1132 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1231 if _libs[_LIBRARY_FILENAME].has("HFLogPrint", "cdecl"): _func = _libs[_LIBRARY_FILENAME].get("HFLogPrint", "cdecl") _restype = HResult @@ -1990,43 +2063,43 @@ if _libs[_LIBRARY_FILENAME].has("HFLogPrint", "cdecl"): _argtypes = [HFLogLevel, HFormat] HFLogPrint = _variadic_function(_func,_restype,_argtypes,_errcheck) -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1145 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1244 if _libs[_LIBRARY_FILENAME].has("HFDeBugImageStreamImShow", "cdecl"): HFDeBugImageStreamImShow = _libs[_LIBRARY_FILENAME].get("HFDeBugImageStreamImShow", "cdecl") HFDeBugImageStreamImShow.argtypes = [HFImageStream] HFDeBugImageStreamImShow.restype = None -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1157 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1256 if _libs[_LIBRARY_FILENAME].has("HFDeBugImageStreamDecodeSave", "cdecl"): HFDeBugImageStreamDecodeSave = _libs[_LIBRARY_FILENAME].get("HFDeBugImageStreamDecodeSave", "cdecl") HFDeBugImageStreamDecodeSave.argtypes = [HFImageStream, HPath] HFDeBugImageStreamDecodeSave.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1172 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1271 if _libs[_LIBRARY_FILENAME].has("HFDeBugShowResourceStatistics", "cdecl"): HFDeBugShowResourceStatistics = _libs[_LIBRARY_FILENAME].get("HFDeBugShowResourceStatistics", "cdecl") HFDeBugShowResourceStatistics.argtypes = [] HFDeBugShowResourceStatistics.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1182 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1281 if _libs[_LIBRARY_FILENAME].has("HFDeBugGetUnreleasedSessionsCount", "cdecl"): HFDeBugGetUnreleasedSessionsCount = _libs[_LIBRARY_FILENAME].get("HFDeBugGetUnreleasedSessionsCount", "cdecl") HFDeBugGetUnreleasedSessionsCount.argtypes = [POINTER(HInt32)] HFDeBugGetUnreleasedSessionsCount.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1193 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1292 if _libs[_LIBRARY_FILENAME].has("HFDeBugGetUnreleasedSessions", "cdecl"): HFDeBugGetUnreleasedSessions = _libs[_LIBRARY_FILENAME].get("HFDeBugGetUnreleasedSessions", "cdecl") HFDeBugGetUnreleasedSessions.argtypes = [POINTER(HFSession), HInt32] HFDeBugGetUnreleasedSessions.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1203 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1302 if _libs[_LIBRARY_FILENAME].has("HFDeBugGetUnreleasedStreamsCount", "cdecl"): HFDeBugGetUnreleasedStreamsCount = _libs[_LIBRARY_FILENAME].get("HFDeBugGetUnreleasedStreamsCount", "cdecl") HFDeBugGetUnreleasedStreamsCount.argtypes = [POINTER(HInt32)] HFDeBugGetUnreleasedStreamsCount.restype = HResult -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1214 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1313 if _libs[_LIBRARY_FILENAME].has("HFDeBugGetUnreleasedStreams", "cdecl"): HFDeBugGetUnreleasedStreams = _libs[_LIBRARY_FILENAME].get("HFDeBugGetUnreleasedStreams", "cdecl") HFDeBugGetUnreleasedStreams.argtypes = [POINTER(HFImageStream), HInt32] @@ -2034,103 +2107,115 @@ if _libs[_LIBRARY_FILENAME].has("HFDeBugGetUnreleasedStreams", "cdecl"): # /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 27 try: - HF_ENABLE_NONE = 0x00000000 + HF_STATUS_ENABLE = 1 except: pass # /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 28 try: - HF_ENABLE_FACE_RECOGNITION = 0x00000002 -except: - pass - -# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 29 -try: - HF_ENABLE_LIVENESS = 0x00000004 + HF_STATUS_DISABLE = 0 except: pass # /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 30 try: - HF_ENABLE_IR_LIVENESS = 0x00000008 + HF_ENABLE_NONE = 0x00000000 except: pass # /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 31 try: - HF_ENABLE_MASK_DETECT = 0x00000010 + HF_ENABLE_FACE_RECOGNITION = 0x00000002 except: pass # /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 32 try: - HF_ENABLE_FACE_ATTRIBUTE = 0x00000020 + HF_ENABLE_LIVENESS = 0x00000004 except: pass # /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 33 try: - HF_ENABLE_PLACEHOLDER_ = 0x00000040 + HF_ENABLE_IR_LIVENESS = 0x00000008 except: pass # /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 34 try: - HF_ENABLE_QUALITY = 0x00000080 + HF_ENABLE_MASK_DETECT = 0x00000010 except: pass # /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 35 try: - HF_ENABLE_INTERACTION = 0x00000100 + HF_ENABLE_FACE_ATTRIBUTE = 0x00000020 except: pass # /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 36 try: - HF_ENABLE_DETECT_MODE_LANDMARK = 0x00000200 + HF_ENABLE_PLACEHOLDER_ = 0x00000040 except: pass -HFImageData = struct_HFImageData# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 72 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 37 +try: + HF_ENABLE_QUALITY = 0x00000080 +except: + pass -HFImageBitmapData = struct_HFImageBitmapData# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 142 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 38 +try: + HF_ENABLE_INTERACTION = 0x00000100 +except: + pass -HFSessionCustomParameter = struct_HFSessionCustomParameter# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 354 +# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 39 +try: + HF_ENABLE_FACE_POSE = 0x00000200 +except: + pass -HFFaceBasicToken = struct_HFFaceBasicToken# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 419 +HFImageData = struct_HFImageData# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 75 -HFFaceEulerAngle = struct_HFFaceEulerAngle# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 430 +HFImageBitmapData = struct_HFImageBitmapData# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 145 -HFMultipleFaceData = struct_HFMultipleFaceData# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 445 +HFSessionCustomParameter = struct_HFSessionCustomParameter# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 392 -HFFaceFeature = struct_HFFaceFeature# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 596 +HFFaceBasicToken = struct_HFFaceBasicToken# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 475 -HFFeatureHubConfiguration = struct_HFFeatureHubConfiguration# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 664 +HFFaceEulerAngle = struct_HFFaceEulerAngle# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 486 -HFFaceFeatureIdentity = struct_HFFaceFeatureIdentity# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 693 +HFMultipleFaceData = struct_HFMultipleFaceData# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 501 -HFSearchTopKResults = struct_HFSearchTopKResults# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 702 +HFFaceFeature = struct_HFFaceFeature# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 661 -HFSimilarityConverterConfig = struct_HFSimilarityConverterConfig# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 762 +HFFeatureHubConfiguration = struct_HFFeatureHubConfiguration# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 763 -HFFeatureHubExistingIds = struct_HFFeatureHubExistingIds# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 863 +HFFaceFeatureIdentity = struct_HFFaceFeatureIdentity# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 792 -HFRGBLivenessConfidence = struct_HFRGBLivenessConfidence# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 915 +HFSearchTopKResults = struct_HFSearchTopKResults# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 801 -HFFaceMaskConfidence = struct_HFFaceMaskConfidence# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 938 +HFSimilarityConverterConfig = struct_HFSimilarityConverterConfig# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 861 -HFFaceQualityConfidence = struct_HFFaceQualityConfidence# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 961 +HFFeatureHubExistingIds = struct_HFFeatureHubExistingIds# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 962 -HFFaceInteractionState = struct_HFFaceInteractionState# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 996 +HFRGBLivenessConfidence = struct_HFRGBLivenessConfidence# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1014 -HFFaceInteractionsActions = struct_HFFaceInteractionsActions# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1015 +HFFaceMaskConfidence = struct_HFFaceMaskConfidence# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1037 -HFFaceAttributeResult = struct_HFFaceAttributeResult# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1050 +HFFaceQualityConfidence = struct_HFFaceQualityConfidence# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1060 -HFInspireFaceVersion = struct_HFInspireFaceVersion# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1075 +HFFaceInteractionState = struct_HFFaceInteractionState# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1095 -HFInspireFaceExtendedInformation = struct_HFInspireFaceExtendedInformation# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1093 +HFFaceInteractionsActions = struct_HFFaceInteractionsActions# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1114 + +HFFaceAttributeResult = struct_HFFaceAttributeResult# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1149 + +HFInspireFaceVersion = struct_HFInspireFaceVersion# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1174 + +HFInspireFaceExtendedInformation = struct_HFInspireFaceExtendedInformation# /Users/tunm/work/InspireFace/cpp/inspireface/c_api/inspireface.h: 1192 # No inserted files diff --git a/cpp-package/inspireface/python/inspireface/modules/inspireface.py b/cpp-package/inspireface/python/inspireface/modules/inspireface.py index 6bf50fa..f5d2d2e 100644 --- a/cpp-package/inspireface/python/inspireface/modules/inspireface.py +++ b/cpp-package/inspireface/python/inspireface/modules/inspireface.py @@ -99,6 +99,14 @@ class ImageStream(object): if ret != 0: raise Exception("Error in creating ImageStream") + def write_to_file(self, file_path: str): + """ + Write the image stream to a file. Like PATH/image.jpg + """ + ret = HFDeBugImageStreamDecodeSave(self._handle, file_path) + if ret != 0: + logger.error(f"Write ImageStream to file error: {ret}") + def release(self): """ Release the resources associated with the ImageStream. @@ -365,6 +373,16 @@ class InspireFaceSession(object): return np.asarray(landmark).reshape(-1, 2) + def print_track_cost_spend(self): + ret = HFSessionPrintTrackCostSpend(self._sess) + if ret != 0: + logger.error(f"Print track cost spend error: {ret}") + + def set_enable_track_cost_spend(self, enable: bool): + ret = HFSessionSetEnableTrackCostSpend(self._sess, enable) + if ret != 0: + logger.error(f"Set enable track cost spend error: {ret}") + def set_detection_confidence_threshold(self, threshold: float): """ Sets the detection confidence threshold for the face detection session. @@ -633,13 +651,6 @@ def launch(model_name: str = "Pikachu", resource_path: str = None) -> bool: return False return True -def set_expansive_pack_path(path: str): - path_c = String(bytes(path, encoding="utf8")) - ret = HFSetExpansiveHardwareAppleCoreMLModelPath(path_c) - if ret != 0: - logger.error(f"Set expansive pack path error: {ret}") - return False - return True def pull_latest_model(model_name: str = "Pikachu") -> str: sm = ResourceManager() @@ -676,6 +687,13 @@ def query_launch_status() -> bool: return False return status.value == 1 +def switch_landmark_engine(engine: int): + ret = HFSwitchLandmarkEngine(engine) + if ret != 0: + logger.error(f"Switch landmark engine error: {ret}") + return False + return True + @dataclass class FeatureHubConfiguration: """ @@ -840,7 +858,7 @@ class FaceIdentity(object): feature.size = HInt32(self.feature.size) feature.data = data_ptr return HFFaceFeatureIdentity( - customId=HFaceId(self.id), + id=HFaceId(self.id), feature=PHFFaceFeature(feature) ) @@ -1109,7 +1127,6 @@ def set_logging_level(level: int) -> None: """ HFSetLogLevel(level) - def disable_logging() -> None: """ Disables all logging from the InspireFace library. @@ -1122,3 +1139,34 @@ def show_system_resource_statistics(): """ HFDeBugShowResourceStatistics() +def switch_apple_coreml_inference_mode(mode: int): + """ + Switches the Apple CoreML inference mode. + """ + ret = HFSetAppleCoreMLInferenceMode(mode) + if ret != 0: + logger.error(f"Failed to set Apple CoreML inference mode: {ret}") + return False + return True + +def set_expansive_hardware_rockchip_dma_heap_path(path: str): + """ + Sets the path to the expansive hardware Rockchip DMA heap. + """ + ret = HFSetExpansiveHardwareRockchipDmaHeapPath(path) + if ret != 0: + logger.error(f"Failed to set expansive hardware Rockchip DMA heap path: {ret}") + return False + return True + +def query_expansive_hardware_rockchip_dma_heap_path() -> str: + """ + Queries the path to the expansive hardware Rockchip DMA heap. + """ + path = HString() + ret = HFQueryExpansiveHardwareRockchipDmaHeapPath(path) + if ret != 0: + logger.error(f"Failed to query expansive hardware Rockchip DMA heap path: {ret}") + return None + return str(path.value) + diff --git a/cpp-package/inspireface/python/inspireface/modules/utils/resource.py b/cpp-package/inspireface/python/inspireface/modules/utils/resource.py index f12bb5c..88364d5 100644 --- a/cpp-package/inspireface/python/inspireface/modules/utils/resource.py +++ b/cpp-package/inspireface/python/inspireface/modules/utils/resource.py @@ -34,7 +34,22 @@ class ResourceManager: "url": "https://github.com/HyperInspire/InspireFace/releases/download/v1.x/Megatron", "filename": "Megatron", "md5": "28f2284c5e7cf53b0e152ff524a416c966ab21e724002643b1304aedc4af6b06" - } + }, + "Megatron_TRT": { + "url": "https://github.com/HyperInspire/InspireFace/releases/download/v1.x/Megatron_TRT", + "filename": "Megatron_TRT", + "md5": "25fb4a585b73b0114ff0d64c2bc4071bd005a32a77149b66c474985077dc8f8a" + }, + "Gundam_RK356X": { + "url": "https://github.com/HyperInspire/InspireFace/releases/download/v1.x/Gundam_RK356X", + "filename": "Gundam_RK356X", + "md5": "69ea23b89851a38c729b32bb0ed33cf62ebd3c891ea5d596afeadeb1f1c79c69" + }, + "Gundam_RK3588": { + "url": "https://github.com/HyperInspire/InspireFace/releases/download/v1.x/Gundam_RK3588", + "filename": "Gundam_RK3588", + "md5": "030965798c5257aef11640657f85b89d82e9d170c3798d0b4f2b62ee6aa245ea" + }, } def get_model(self, name: str, re_download: bool = False) -> str: diff --git a/cpp-package/inspireface/python/inspireface/param.py b/cpp-package/inspireface/python/inspireface/param.py index 010e1e4..0337fd1 100644 --- a/cpp-package/inspireface/python/inspireface/param.py +++ b/cpp-package/inspireface/python/inspireface/param.py @@ -2,7 +2,8 @@ # Session option from inspireface.modules.core.native import HF_ENABLE_NONE, HF_ENABLE_FACE_RECOGNITION, HF_ENABLE_LIVENESS, HF_ENABLE_IR_LIVENESS, \ - HF_ENABLE_MASK_DETECT, HF_ENABLE_FACE_ATTRIBUTE, HF_ENABLE_QUALITY, HF_ENABLE_INTERACTION, HF_PK_AUTO_INCREMENT, HF_PK_MANUAL_INPUT + HF_ENABLE_MASK_DETECT, HF_ENABLE_FACE_ATTRIBUTE, HF_ENABLE_QUALITY, HF_ENABLE_INTERACTION, HF_ENABLE_FACE_POSE, HF_PK_AUTO_INCREMENT, HF_PK_MANUAL_INPUT, \ + HF_LANDMARK_HYPLMV2_0_25, HF_LANDMARK_HYPLMV2_0_50, HF_LANDMARK_INSIGHTFACE_2D106_TRACK # Face track mode from inspireface.modules.core.native import HF_DETECT_MODE_ALWAYS_DETECT, HF_DETECT_MODE_LIGHT_TRACK, HF_DETECT_MODE_TRACK_BY_DETECTION diff --git a/cpp-package/inspireface/python/read_nv21.py b/cpp-package/inspireface/python/read_nv21.py new file mode 100644 index 0000000..cc3da64 --- /dev/null +++ b/cpp-package/inspireface/python/read_nv21.py @@ -0,0 +1,71 @@ +import cv2 +import numpy as np +from inspireface import ImageStream +import inspireface as isf + + +def read_nv21(file_path, width, height, rotate=0): + with open(file_path, 'rb') as f: + nv21_data = f.read() + + yuv = np.frombuffer(nv21_data, dtype=np.uint8) + + expected_size = width * height * 3 // 2 + if len(yuv) < expected_size: + raise ValueError(f"NV21 data size is not enough: expected {expected_size} bytes, actual {len(yuv)} bytes") + + yuv_mat = np.zeros((height * 3 // 2, width), dtype=np.uint8) + yuv_mat[:] = yuv[:height * width * 3 // 2].reshape(height * 3 // 2, width) + + bgr_mat = cv2.cvtColor(yuv_mat, cv2.COLOR_YUV2BGR_NV21) + + # add reverse rotate + if rotate != 0: + # calculate reverse rotate angle + reverse_angle = (360 - rotate) % 360 + + # select rotate method by angle + if reverse_angle == 90: + bgr_mat = cv2.rotate(bgr_mat, cv2.ROTATE_90_CLOCKWISE) + elif reverse_angle == 180: + bgr_mat = cv2.rotate(bgr_mat, cv2.ROTATE_180) + elif reverse_angle == 270: + bgr_mat = cv2.rotate(bgr_mat, cv2.ROTATE_90_COUNTERCLOCKWISE) + else: + # for non 90 degree angle, use affine transform + center = (bgr_mat.shape[1] // 2, bgr_mat.shape[0] // 2) + rotation_matrix = cv2.getRotationMatrix2D(center, reverse_angle, 1.0) + bgr_mat = cv2.warpAffine(bgr_mat, rotation_matrix, (bgr_mat.shape[1], bgr_mat.shape[0])) + + return bgr_mat + +# example usage +if __name__ == "__main__": + image_width = 640 + image_height = 480 + nv21_file = "" + pre_rotate = 90 + try: + # read and reverse rotate image + bgr_image = read_nv21(nv21_file, image_width, image_height, pre_rotate) + + cv2.imshow("origin", bgr_image) + + # show original image for comparison + bgr_image_original = read_nv21(nv21_file, image_width, image_height) + cv2.imshow("rotate ", bgr_image_original) + + cv2.waitKey(0) + cv2.destroyAllWindows() + + except Exception as e: + print(f"error: {e}") + + with open(nv21_file, 'rb') as f: + nv21_data = f.read() + + stream = ImageStream.load_from_buffer(nv21_data, image_width, image_height, isf.HF_STREAM_YUV_NV21, isf.HF_CAMERA_ROTATION_90) + + stream.debug_show() + + \ No newline at end of file diff --git a/cpp-package/inspireface/python/sample_face_detection.py b/cpp-package/inspireface/python/sample_face_detection.py index fd77660..418ab09 100644 --- a/cpp-package/inspireface/python/sample_face_detection.py +++ b/cpp-package/inspireface/python/sample_face_detection.py @@ -5,85 +5,91 @@ import click import numpy as np race_tags = ["Black", "Asian", "Latino/Hispanic", "Middle Eastern", "White"] -gender_tags = ["Female", "Male", ] -age_bracket_tags = ["0-2 years old", "3-9 years old", "10-19 years old", "20-29 years old", "30-39 years old", - "40-49 years old", "50-59 years old", "60-69 years old", "more than 70 years old"] +gender_tags = ["Female", "Male"] +age_bracket_tags = [ + "0-2 years old", "3-9 years old", "10-19 years old", "20-29 years old", "30-39 years old", + "40-49 years old", "50-59 years old", "60-69 years old", "more than 70 years old" +] @click.command() @click.argument('image_path') -def case_face_detection_image(image_path): +@click.option('--show', is_flag=True, help='Display the image with detected faces.') +def case_face_detection_image(image_path, show): """ This is a sample application for face detection and tracking using an image. It also includes pipeline extensions such as RGB liveness, mask detection, and face quality evaluation. """ - # Optional features, loaded during session creation based on the modules specified. - opt = isf.HF_ENABLE_FACE_RECOGNITION | isf.HF_ENABLE_QUALITY | isf.HF_ENABLE_MASK_DETECT | isf.HF_ENABLE_LIVENESS | isf.HF_ENABLE_INTERACTION | isf.HF_ENABLE_FACE_ATTRIBUTE + opt = isf.HF_ENABLE_FACE_RECOGNITION | isf.HF_ENABLE_QUALITY | isf.HF_ENABLE_MASK_DETECT | \ + isf.HF_ENABLE_LIVENESS | isf.HF_ENABLE_INTERACTION | isf.HF_ENABLE_FACE_ATTRIBUTE session = isf.InspireFaceSession(opt, isf.HF_DETECT_MODE_ALWAYS_DETECT) - - # Set detection confidence threshold session.set_detection_confidence_threshold(0.5) - # Load the image using OpenCV. + # Load image image = cv2.imread(image_path) assert image is not None, "Please check that the image path is correct." - # Perform face detection on the image. + # Dynamic drawing parameters (adjusted to image size) + h, w = image.shape[:2] + scale = max(w, h) / 1000.0 + line_thickness = max(1, int(2 * scale)) + circle_radius = max(1, int(1.5 * scale)) + font_scale = 0.5 * scale + + # Detect faces faces = session.face_detection(image) print(f"face detection: {len(faces)} found") - - # Copy the image for drawing the bounding boxes. + draw = image.copy() for idx, face in enumerate(faces): print(f"{'==' * 20}") print(f"idx: {idx}") print(f"detection confidence: {face.detection_confidence}") - # Print Euler angles of the face. print(f"roll: {face.roll}, yaw: {face.yaw}, pitch: {face.pitch}") - # Get face bounding box x1, y1, x2, y2 = face.location - - # Calculate center, size, and angle center = ((x1 + x2) / 2, (y1 + y2) / 2) size = (x2 - x1, y2 - y1) angle = face.roll - # Apply rotation to the bounding box corners rect = ((center[0], center[1]), (size[0], size[1]), angle) - box = cv2.boxPoints(rect) - box = box.astype(int) + box = cv2.boxPoints(rect).astype(int) + cv2.drawContours(draw, [box], 0, (100, 180, 29), line_thickness) - # Draw the rotated bounding box - cv2.drawContours(draw, [box], 0, (100, 180, 29), 2) - - # Draw landmarks + # Draw landmark lmk = session.get_face_dense_landmark(face) for x, y in lmk.astype(int): - cv2.circle(draw, (x, y), 0, (220, 100, 0), 2) + cv2.circle(draw, (x, y), circle_radius, (220, 100, 0), -1) - # Features must be enabled during session creation to use them here. - select_exec_func = isf.HF_ENABLE_QUALITY | isf.HF_ENABLE_MASK_DETECT | isf.HF_ENABLE_LIVENESS | isf.HF_ENABLE_INTERACTION | isf.HF_ENABLE_FACE_ATTRIBUTE - # Execute the pipeline to obtain richer face information. + # Optional: Add detection confidence (text) on the face box + # label = f"{face.detection_confidence:.2f}" + # cv2.putText(draw, label, (x1, max(y1 - 10, 0)), cv2.FONT_HERSHEY_SIMPLEX, + # font_scale, (255, 255, 255), line_thickness) + + # Execute extended functions (optional modules) + select_exec_func = isf.HF_ENABLE_QUALITY | isf.HF_ENABLE_MASK_DETECT | \ + isf.HF_ENABLE_LIVENESS | isf.HF_ENABLE_INTERACTION | isf.HF_ENABLE_FACE_ATTRIBUTE extends = session.face_pipeline(image, faces, select_exec_func) + for idx, ext in enumerate(extends): print(f"{'==' * 20}") print(f"idx: {idx}") - # For these pipeline results, you can set thresholds based on the specific scenario to make judgments. print(f"quality: {ext.quality_confidence}") print(f"rgb liveness: {ext.rgb_liveness_confidence}") print(f"face mask: {ext.mask_confidence}") - print( - f"face eyes status: left eye: {ext.left_eye_status_confidence} right eye: {ext.right_eye_status_confidence}") + print(f"face eyes status: left eye: {ext.left_eye_status_confidence} right eye: {ext.right_eye_status_confidence}") print(f"gender: {gender_tags[ext.gender]}") print(f"race: {race_tags[ext.race]}") print(f"age: {age_bracket_tags[ext.age_bracket]}") - # Save the annotated image to the 'tmp/' directory. - save_path = os.path.join("tmp/", "det.jpg") + # Save the annotated image + save_path = os.path.join("tmp", "det.jpg") + os.makedirs("tmp", exist_ok=True) cv2.imwrite(save_path, draw) + if show: + cv2.imshow("Face Detection", draw) + cv2.waitKey(0) + cv2.destroyAllWindows() print(f"\nSave annotated image to {save_path}") - if __name__ == '__main__': - os.makedirs("tmp", exist_ok=True) case_face_detection_image() diff --git a/cpp-package/inspireface/python/sample_face_track_from_video.py b/cpp-package/inspireface/python/sample_face_track_from_video.py index fa9cc7d..4bb78d4 100644 --- a/cpp-package/inspireface/python/sample_face_track_from_video.py +++ b/cpp-package/inspireface/python/sample_face_track_from_video.py @@ -4,7 +4,7 @@ import click import cv2 import inspireface as isf import numpy as np - +import time def generate_color(id): """ @@ -50,8 +50,13 @@ def case_face_tracker_from_video(source, show, out): """ # Optional features, loaded during session creation based on the modules specified. opt = isf.HF_ENABLE_NONE | isf.HF_ENABLE_INTERACTION - session = isf.InspireFaceSession(opt, isf.HF_DETECT_MODE_LIGHT_TRACK, max_detect_num=25, detect_pixel_level=320) # Use video mode + session = isf.InspireFaceSession(opt, isf.HF_DETECT_MODE_LIGHT_TRACK, max_detect_num=25, detect_pixel_level=160) # Use video mode + session.set_track_mode_smooth_ratio(0.06) + session.set_track_mode_num_smooth_cache_frame(15) session.set_filter_minimum_face_pixel_size(0) + session.set_track_model_detect_interval(0) + session.set_landmark_augmentation_num(1) + session.set_enable_track_cost_spend(True) # Determine if the source is a digital webcam index or a video file path. try: source_index = int(source) # Try to convert source to an integer. @@ -82,8 +87,11 @@ def case_face_tracker_from_video(source, show, out): break # Exit loop if no more frames or error occurs. # Process frame here (e.g., face detection/tracking). + t1 = time.time() faces = session.face_detection(frame) - + t2 = time.time() + # print(f"Face detection time: {t2 - t1} seconds") + session.print_track_cost_spend() exts = session.face_pipeline(frame, faces, isf.HF_ENABLE_INTERACTION) for idx, face in enumerate(faces): diff --git a/cpp-package/inspireface/python/sample_feature_hub.py b/cpp-package/inspireface/python/sample_feature_hub.py index a8b7cff..2ffe502 100644 --- a/cpp-package/inspireface/python/sample_feature_hub.py +++ b/cpp-package/inspireface/python/sample_feature_hub.py @@ -4,59 +4,14 @@ import inspireface as isf import numpy as np import os -FEATURE = np.asarray([ 0.0706566, 0.00640248, 0.0418103, -0.00597861, 0.0269879, 0.0187478, 0.0486305, 0.0349162, -0.0080779, -0.0550556, 0.0229963, - -0.00683422, -0.0338589, 0.0533989, -0.0371725, 0.000972469, 0.0612415, 0.0389846, -0.00126743, -0.0128782, 0.0935529, 0.0588179, - 0.0164787, -0.00732871, -0.0458209, -0.0100137, -0.0372892, 0.000871123, 0.0245121, -0.0811471, -0.00481095, 0.0266868, 0.0712961, - -0.0675362, -0.0117453, 0.0658745, -0.0694139, -0.00704822, -0.0237313, 0.0209365, 0.0131902, 0.00192449, -0.0593105, 0.0191942, - -0.00625798, 0.00748682, 0.0533557, 0.0314002, -0.0627113, 0.0827862, 0.00336722, -0.0191575, -0.0180252, 0.0150318, -0.0686462, - 0.0465634, 0.0627244, 0.0449248, -0.037054, -0.0486668, 0.040752, 0.0143315, -0.0763842, -0.0161973, 0.0319588, 0.0112792, - -0.102007, 0.0649219, 0.0630833, 0.0421069, 0.0519043, -0.084082, 0.0249516, 0.023046, 0.071994, -0.0272229, 0.0167103, - -0.00694243, 0.0366775, 0.0672882, 0.0122419, -0.0233413, -0.0144258, -0.012853, -0.0202025, 0.000983093, -0.00776073, -0.0268638, - 0.00682446, 0.0262906, -0.0407654, -0.0144264, -0.0310807, 0.0596711, 0.0238081, -0.0138019, 0.000502882, 0.0496892, 0.0126823, - 0.0511028, -0.0310699, -0.0322141, 0.00996936, 0.0675392, -0.0164277, 0.0930009, -0.037467, 0.0419618, -0.00358901, -0.0309569, - -0.0225608, -0.0332198, 0.00102291, 0.108814, -0.0831313, 0.048208, -0.0277542, -0.061584, 0.0721224, -0.0795082, 0.0340047, - 0.056139, -0.0166783, -0.0803042, -0.014245, -0.0476374, 0.048495, 0.0378856, 0.0706566, 0.00640248, 0.0418103, -0.00597861, - 0.0269879, 0.0187478, 0.0486305, 0.0349162, -0.0080779, -0.0550556, 0.0229963, -0.00683422, -0.0338589, 0.0533989, -0.0371725, - 0.000972469, 0.0612415, 0.0389846, -0.00126743, -0.0128782, 0.0935529, 0.0588179, 0.0164787, -0.00732871, -0.0458209, -0.0100137, - -0.0372892, 0.000871123, 0.0245121, -0.0811471, -0.00481095, 0.0266868, 0.0712961, -0.0675362, -0.0117453, 0.0658745, -0.0694139, - -0.00704822, -0.0237313, 0.0209365, 0.0131902, 0.00192449, -0.0593105, 0.0191942, -0.00625798, 0.00748682, 0.0533557, 0.0314002, - -0.0627113, 0.0827862, 0.00336722, -0.0191575, -0.0180252, 0.0150318, -0.0686462, 0.0465634, 0.0627244, 0.0449248, -0.037054, - -0.0486668, 0.040752, 0.0143315, -0.0763842, -0.0161973, 0.0319588, 0.0112792, -0.102007, 0.0649219, 0.0630833, 0.0421069, - 0.0519043, -0.084082, 0.0249516, 0.023046, 0.071994, -0.0272229, 0.0167103, -0.00694243, 0.0366775, 0.0672882, 0.0122419, - -0.0233413, -0.0144258, -0.012853, -0.0202025, 0.000983093, -0.00776073, -0.0268638, 0.00682446, 0.0262906, -0.0407654, -0.0144264, - -0.0310807, 0.0596711, 0.0238081, -0.0138019, 0.000502882, 0.0496892, 0.0126823, 0.0511028, -0.0310699, -0.0322141, 0.00996936, - 0.0675392, -0.0164277, 0.0930009, -0.037467, 0.0419618, -0.00358901, -0.0309569, -0.0225608, -0.0332198, 0.00102291, 0.108814, - -0.0831313, 0.048208, -0.0277542, -0.061584, 0.0721224, -0.0795082, 0.0340047, 0.056139, -0.0166783, -0.0803042, -0.014245, - -0.0476374, 0.048495, 0.0378856, 0.0706566, 0.00640248, 0.0418103, -0.00597861, 0.0269879, 0.0187478, 0.0486305, 0.0349162, - -0.0080779, -0.0550556, 0.0229963, -0.00683422, -0.0338589, 0.0533989, -0.0371725, 0.000972469, 0.0612415, 0.0389846, -0.00126743, - -0.0128782, 0.0935529, 0.0588179, 0.0164787, -0.00732871, -0.0458209, -0.0100137, -0.0372892, 0.000871123, 0.0245121, -0.0811471, - -0.00481095, 0.0266868, 0.0712961, -0.0675362, -0.0117453, 0.0658745, -0.0694139, -0.00704822, -0.0237313, 0.0209365, 0.0131902, - 0.00192449, -0.0593105, 0.0191942, -0.00625798, 0.00748682, 0.0533557, 0.0314002, -0.0627113, 0.0827862, 0.00336722, -0.0191575, - -0.0180252, 0.0150318, -0.0686462, 0.0465634, 0.0627244, 0.0449248, -0.037054, -0.0486668, 0.040752, 0.0143315, -0.0763842, - -0.0161973, 0.0319588, 0.0112792, -0.102007, 0.0649219, 0.0630833, 0.0421069, 0.0519043, -0.084082, 0.0249516, 0.023046, - 0.071994, -0.0272229, 0.0167103, -0.00694243, 0.0366775, 0.0672882, 0.0122419, -0.0233413, -0.0144258, -0.012853, -0.0202025, - 0.000983093, -0.00776073, -0.0268638, 0.00682446, 0.0262906, -0.0407654, -0.0144264, -0.0310807, 0.0596711, 0.0238081, -0.0138019, - 0.000502882, 0.0496892, 0.0126823, 0.0511028, -0.0310699, -0.0322141, 0.00996936, 0.0675392, -0.0164277, 0.0930009, -0.037467, - 0.0419618, -0.00358901, -0.0309569, -0.0225608, -0.0332198, 0.00102291, 0.108814, -0.0831313, 0.048208, -0.0277542, -0.061584, - 0.0721224, -0.0795082, 0.0340047, 0.056139, -0.0166783, -0.0803042, -0.014245, -0.0476374, 0.048495, 0.0378856, 0.0706566, - 0.00640248, 0.0418103, -0.00597861, 0.0269879, 0.0187478, 0.0486305, 0.0349162, -0.0080779, -0.0550556, 0.0229963, -0.00683422, - -0.0338589, 0.0533989, -0.0371725, 0.000972469, 0.0612415, 0.0389846, -0.00126743, -0.0128782, 0.0935529, 0.0588179, 0.0164787, - -0.00732871, -0.0458209, -0.0100137, -0.0372892, 0.000871123, 0.0245121, -0.0811471, -0.00481095, 0.0266868, 0.0712961, -0.0675362, - -0.0117453, 0.0658745, -0.0694139, -0.00704822, -0.0237313, 0.0209365, 0.0131902, 0.00192449, -0.0593105, 0.0191942, -0.00625798, - 0.00748682, 0.0533557, 0.0314002, -0.0627113, 0.0827862, 0.00336722, -0.0191575, -0.0180252, 0.0150318, -0.0686462, 0.0465634, - 0.0627244, 0.0449248, -0.037054, -0.0486668, 0.040752, 0.0143315, -0.0763842, -0.0161973, 0.0319588, 0.0112792, -0.102007, - 0.0649219, 0.0630833, 0.0421069, 0.0519043, -0.084082, 0.0249516, 0.023046, 0.071994, -0.0272229, 0.0167103, -0.00694243, - 0.0366775, 0.0672882, 0.0122419, -0.0233413, -0.0144258, -0.012853, -0.0202025, 0.000983093, -0.00776073, -0.0268638, 0.00682446, - 0.0262906, -0.0407654, -0.0144264, -0.0310807, 0.0596711, 0.0238081, -0.0138019, 0.000502882, 0.0496892, 0.0126823, 0.0511028, - -0.0310699, -0.0322141, 0.00996936, 0.0675392, -0.0164277, 0.0930009, -0.037467, 0.0419618, -0.00358901, -0.0309569, -0.0225608, - -0.0332198, 0.00102291, 0.108814, -0.0831313, 0.048208, -0.0277542, -0.061584, 0.0721224, -0.0795082, 0.0340047, 0.056139, - -0.0166783, -0.0803042, -0.014245, -0.0476374, 0.048495, 0.0378856,], dtype=np.float32) - def case_feature_hub(): + # Gen a random feature + gen = np.random.rand(512).astype(np.float32) + # Set db path db_path = "test.db" # Configure the feature management system. feature_hub_config = isf.FeatureHubConfiguration( - primary_key_mode=isf.HF_PK_AUTO_INCREMENT, + primary_key_mode=isf.HF_PK_MANUAL_INPUT, enable_persistence=True, persistence_db_path=db_path, search_threshold=0.48, @@ -64,16 +19,17 @@ def case_feature_hub(): ) ret = isf.feature_hub_enable(feature_hub_config) assert ret, "Failed to enable FeatureHub." - print(isf.feature_hub_get_face_count()) + print('T1, face count:', isf.feature_hub_get_face_count()) for i in range(10): v = np.random.rand(512).astype(np.float32) - feature = isf.FaceIdentity(v, -1) - isf.feature_hub_face_insert(feature) - feature = isf.FaceIdentity(FEATURE, -1) + feature = isf.FaceIdentity(v, i) + ret, new_id = isf.feature_hub_face_insert(feature) + assert ret, "Failed to insert face feature data into FeatureHub." + assert new_id == i, "Failed to get the correct new id." + feature = isf.FaceIdentity(gen, -1) isf.feature_hub_face_insert(feature) - print(isf.feature_hub_get_face_count()) - result = isf.feature_hub_face_search(FEATURE) - print(result.confidence, result.similar_identity.id) + result = isf.feature_hub_face_search(gen) + print(f"result: {result}") assert os.path.exists(db_path), "FeatureHub database file not found." diff --git a/cpp-package/inspireface/python/setup.py b/cpp-package/inspireface/python/setup.py index faddcf2..20197be 100644 --- a/cpp-package/inspireface/python/setup.py +++ b/cpp-package/inspireface/python/setup.py @@ -7,14 +7,17 @@ import os def get_version(): """Get version number""" version_path = os.path.join(os.path.dirname(__file__), 'version.txt') - with open(version_path, 'r') as f: - return f.read().strip() + try: + with open(version_path, 'r') as f: + return f.read().strip() + except FileNotFoundError: + return "0.0.0" def get_wheel_platform_tag(): """Get wheel package platform tag""" system = platform.system().lower() machine = platform.machine().lower() - + arch_mapping = { 'x86_64': { 'windows': 'win_amd64', @@ -37,7 +40,10 @@ def get_wheel_platform_tag(): 'darwin': 'macosx_11_0_arm64' } } - platform_arch = arch_mapping.get(machine, {}).get(system) + if os.getenv('INSPIRE_FACE_TARGET_AARCH_MAPPING'): + platform_arch = os.getenv('INSPIRE_FACE_TARGET_AARCH_MAPPING') + else: + platform_arch = arch_mapping.get(machine, {}).get(system) if not platform_arch: print("Unsupported platform: {} {}".format(system, machine)) raise RuntimeError("Unsupported platform: {} {}".format(system, machine)) @@ -77,8 +83,22 @@ class BinaryDistWheel(bdist_wheel): self.plat_name = get_wheel_platform_tag() self.universal = False + +def get_target_platform_for_envs(): + """Get target platform for environments""" + system = os.environ.get('INSPIRE_FACE_TARGET_PLATFORM') + if system is None: + system = get_lib_path_info()[0] + + machine = os.environ.get('INSPIRE_FACE_TARGET_ARCH') + if machine is None: + machine = get_lib_path_info()[1] + + return system, machine + # Get current platform information -system, arch = get_lib_path_info() +system, arch = get_target_platform_for_envs() +print(f"Building for system: {system}, arch: {arch}") # Build library file path relative to package lib_path = os.path.join('modules', 'core', 'libs', system, arch, '*') diff --git a/cpp-package/inspireface/test_res/data/bulk/kun_cartoon_crop.jpg b/cpp-package/inspireface/test_res/data/bulk/kun_cartoon_crop.jpg new file mode 100644 index 0000000000000000000000000000000000000000..058b91c82cf691a2047d05008547fce517ed9597 GIT binary patch literal 36746 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�sc>e%^)ZqUBii^g__h*B${_;lu z0BrR7(>DRmFeynSxa;#De*XZ41&QIlpQU>iJhX%xuoz;Tk@6IAoN-L#V?UKo1+@1Q zC9SF%WRQeJP+6Ie000LkwPNFBG>@xv-wR&oJ|6!7g?)Q*_RG3#bqPEkt_(! zhnIU`I_y~0lsXnUtgjmC>31HpuUfDo>RYRb$lz_6*_Wn2yeqlWE;X$>{{ThQd{C<% zvuqu%(O5bE0Eu)TXo^`j{HZaHBOHmH-zu{OB-e>}-~J`O5&Il`58cGSKtI82#-$t2 zQ`Ti3+ic&Wmew2W@bs;u3uz$5HpZs-m^x~uto?~epa0VS8i*Y3 zsP>F{an34P56pNNsDV9&OVdy-ToCM%FWz{@N4osK$W>ANG@;JVDDV{Iej^9@QXD9z zkQJ55mv2lARgB;or3{&si+3NTT~p5=tuPLUb}&7u*f`BMoR82`zuD=U06tOoMk>vX z^tx^QchGWTIV03}AJVGl^rMC298z&suq_X$JU6Oo@lO`1Kh0$_7^eV#%eK)c!AA#W#uIhDbzLklDfZ!+@o(9emc^fHKG& zM&y7=LQh=RmIYJDuVRev6nV3SorMGYoK>AV>Rmffg7!WdCI|dKkgBVYx%y-9q>+e^ zccu+%%85SEwYW>wj^Zwos3WF185KJDaRR4~)x&&0)Dr&y!;#0kEe2HmI3x1uUB1%1 zFRgf0W6Ih+s&y|E$v#G1xX1qhsvP^Ecnkc{ECBb3%C^fnV%t-e?%9?suhpR>;J6m75+B#dnzxecr z{5w|YEC%356_scI03jR0oI31W{TCzo)~c}t3}Es8af&DYMbg+A0}ei&de@5lN||QA zg4s{{sKMjkkMOT&UE>j+FgOE>`MbooqWfOCnl4v6SLnP~USA8&=N4g7(6Q&8By4fD zi2Z3>0B#=Cz~4KT?#7%CAYELAbN3J@#K0RL0--9PS-`$^^5x%Z6mnV{>)_4M8kR+BO&BPjQp)3j2|dCDY#&P zk&G)!(b%d=@<|@Yb75m)Z*d)8FlqFASQ2S4q37#v^#00O>e_=j+2 z@e)El>@gtzZTzq+%D}_kx#(i$XxQWNg~9Zs0|y_SA;AMHf&PD$I|tIe!_5@2Cu!?O z21Yt@O(FX9AK_2ID~{*2B*0R`55RQ) z063?G?hP^#wjfV&QebsFWAvtw34ljm%8jGc)5`7xKjBUCm)fJ90sqtf8i)agDTCXL zRE_tAM!o7~un-0;>VF^4x9L(yHpS-tl?ruV_eD=P{CZUZmkSXf{{VMC{c6=5h5rC+ z^sI=#)-U^&uDv6m1n8>6zv)6YTyaPIvr1%td{6_4$Oqd${c2Swm8OrnKi#2z@t_O( zUF5oUv2S;8!y_pC7;--@E9(ymSVgMq_WA_eL9BnozHaBsn6A_HjrK^rK4cxMzxr_vqWdLI6T#w_gm6dT{R(LVB2#oX4^JD7u&JuV{Ybz!;BpAP>tm?t5?p4?$iB@T2}h-}dYO0J9}~ z@%w}Q(Oy0~_>Awtd0Qmyk{{Yv!{{ZMU^C4OzGT8tXC;{{X4q_~O0C;U9)H)2Cj1N7PJ`wuNk@<_Z-{{Ub5SN?`;hO_Hh$-)8qaM^$rqy3v+*Y1*mPF zW;w=j)9GG~W8ga?Khmdp2cY>!Ya<+$RYkD&Tg V>G!Kb2LKzIU4n$^Po-oB|Ji~;`I-O# literal 0 HcmV?d00001 diff --git a/cpp-package/inspireface/test_res/data/bulk/kun_cartoon_crop_r90.jpg b/cpp-package/inspireface/test_res/data/bulk/kun_cartoon_crop_r90.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9c4157e15f776cb2a3ea1e35a6ab34077578fdac GIT binary patch literal 38091 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

7FNui% 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 literal 0 HcmV?d00001 diff --git a/cpp-package/inspireface/tools/generate_release_models_info.py b/cpp-package/inspireface/tools/generate_release_models_info.py new file mode 100644 index 0000000..805556f --- /dev/null +++ b/cpp-package/inspireface/tools/generate_release_models_info.py @@ -0,0 +1,46 @@ +import hashlib +import os +import json +import click + +need_models = [ + "Pikachu", + "Megatron", +] + +def get_file_hash_sha256(file_path): + sha256 = hashlib.sha256() + with open(file_path, 'rb') as f: + for chunk in iter(lambda: f.read(4096), b''): + sha256.update(chunk) + return sha256.hexdigest() + +@click.command() +@click.argument('model_dir', default="test_res/pack/") +@click.option('--models', '-m', multiple=True, help='Specify the model name to process') +def main(model_dir, models): + model_info = {} + + # If no model is specified, process all predefined models + models_to_process = models if models else need_models + + for file in models_to_process: + file_path = os.path.join(model_dir, file) + if os.path.exists(file_path): + md5 = get_file_hash_sha256(file_path) + model_info[file] = { + "url": f"https://github.com/HyperInspire/InspireFace/releases/download/v1.x/{file}", + "filename": file, + "md5": md5 + } + else: + print(f"Warning: File {file_path} does not exist") + + print("\033[33mNeed to modify the python/inspireface/modules/utils/resource.py, changes to the information release\033[0m") + # Output the result + print(json.dumps(model_info, indent=4, ensure_ascii=False)) + +if __name__ == "__main__": + main() + + diff --git a/cpp-package/inspireface/tools/get_model_md5.py b/cpp-package/inspireface/tools/get_model_md5.py index 7a81002..57045df 100644 --- a/cpp-package/inspireface/tools/get_model_md5.py +++ b/cpp-package/inspireface/tools/get_model_md5.py @@ -5,6 +5,9 @@ import click file_list = [ "Pikachu", "Megatron", + "Megatron_TRT", + "Gundam_RK356X", + "Gundam_RK3588", ] def get_file_hash_sha256(file_path):