diff --git a/.gitignore b/.gitignore index 14d939274..52a744686 100644 --- a/.gitignore +++ b/.gitignore @@ -103,5 +103,14 @@ lib/3rdParty/CameraEnumerator/Debug/ lib/local/CppInerop/Debug/ *.user +# Python API folders +python_api/pybind* +python_api/build +python_api/dist +python_api/*.egg-info + # IDE-generated folders .idea + +# Useless macOS files +.DS_Store diff --git a/python_api/CMakeLists.txt b/python_api/CMakeLists.txt new file mode 100755 index 000000000..77fd8fcbc --- /dev/null +++ b/python_api/CMakeLists.txt @@ -0,0 +1,68 @@ +cmake_minimum_required(VERSION 3.8) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/../cmake/modules/") + +project(PyOpenFace VERSION 0.0.1) + +# NOTE: This is separate from the main CMake build script so that there's no dependency on +# Python to build, and so that building/installing is done using the normal method of pip or +# python setup.py install (makes building binary wheels for multiple Python versions easier) + + +# Find libraries the static OpenFace libraries need to work properly + +find_package(OpenCV 4.0 REQUIRED COMPONENTS core imgproc calib3d highgui objdetect) +find_package(OpenBLAS REQUIRED) +find_package(Boost 1.5.9 COMPONENTS filesystem system) +find_package(dlib 19.13) +if(${dlib_FOUND}) + if (NOT TARGET dlib) + add_library(dlib INTERFACE IMPORTED GLOBAL) + endif() +endif() + + +# Find the pre-compiled OpenFace libraries (expecting OpenFace already built) + +find_library( + LANDMARK_LIB + NAME LandmarkDetector + HINTS ../build/lib/local/LandmarkDetector/ + NO_DEFAULT_PATH +) + + +# Add required include directories + +include_directories( + ../lib/3rdParty/dlib/include/ + ../lib/3rdParty/OpenCV/include/ + ../lib/local/LandmarkDetector/include/ +) + + +# Actually create the pybind module for OpenFace + +add_subdirectory(pybind11) +pybind11_add_module( + _openface MODULE src/openface.cpp + ../lib/local/LandmarkDetector/include/LandmarkDetectorModel.h + ../lib/local/LandmarkDetector/include/LandmarkDetectorParameters.h + ../lib/local/LandmarkDetector/include/LandmarkDetectorFunc.h + ../lib/local/LandmarkDetector/include/LandmarkDetectorUtils.h +) + + +# Link required libraries to PyOpenFace binary + +target_link_libraries( + _openface PRIVATE + ${LANDMARK_LIB} + ${OpenCV_LIBS} + ${OpenBLAS_LIB} + dlib::dlib +) +if(${Boost_FOUND}) + target_link_libraries(_openface PRIVATE ${Boost_LIBRARIES}) +else() + target_link_libraries(_openface PRIVATE stdc++fs) +endif() diff --git a/python_api/pyopenface/__init__.py b/python_api/pyopenface/__init__.py new file mode 100644 index 000000000..70999e484 --- /dev/null +++ b/python_api/pyopenface/__init__.py @@ -0,0 +1,4 @@ +"""root of module""" +from .models import install_models, download_models +from _openface import * +from .openface import * diff --git a/python_api/pyopenface/models.py b/python_api/pyopenface/models.py new file mode 100644 index 000000000..5f10bfbc0 --- /dev/null +++ b/python_api/pyopenface/models.py @@ -0,0 +1,257 @@ +import os +import shutil + +try: + from urllib.request import urlopen # Python 3.x + from urllib.error import URLError, HTTPError +except ImportError: + from urllib2 import urlopen # Python 2 + from urllib2 import URLError, HTTPError + + +# Core OpenFace model files to download from GitHub + +repo_base = 'https://raw.githubusercontent.com/TadasBaltrusaitis/OpenFace/master/' +base_modpath = 'lib/local/LandmarkDetector/' +classifier_path = 'lib/3rdParty/OpenCV/classifiers/' +classifier_file = 'haarcascade_frontalface_alt.xml' + +model_files = { + 'detection_validation': { + 'validator_cnn.txt': None, + 'validator_cnn_68.txt': None, + 'validator_general_68.txt': None + }, + 'mtcnn_detector': { + 'MTCNN_detector.txt': None, + 'ONet.dat': None, + 'PNet.dat': None, + 'RNet.dat': None + }, + 'pdms': { + 'In-the-wild_aligned_PDM_68.txt': None, + 'Multi-PIE_aligned_PDM_68.txt': None, + 'pdm_68_aligned_menpo.txt': None + }, + 'patch_experts': { + 'ccnf_patches_0.25_general.txt': None, + 'ccnf_patches_0.25_wild.txt': None, + 'ccnf_patches_0.25_multi_pie.txt': None, + 'ccnf_patches_0.35_general.txt': None, + 'ccnf_patches_0.35_wild.txt': None, + 'ccnf_patches_0.35_multi_pie.txt': None, + 'ccnf_patches_0.5_general.txt': None, + 'ccnf_patches_0.5_wild.txt': None, + 'ccnf_patches_0.5_multi_pie.txt': None, + 'ccnf_patches_1_wild.txt': None, + 'svr_patches_0.25_general.txt': None, + 'svr_patches_0.25_wild.txt': None, + 'svr_patches_0.35_general.txt': None, + 'svr_patches_0.35_wild.txt': None, + 'svr_patches_0.5_general.txt': None, + 'svr_patches_0.5_wild.txt': None + }, + 'model_inner': { + 'clnf_inner.txt': None, + 'main_clnf_inner.txt': None, + 'pdms': {'pdm_51_inner.txt': None}, + 'patch_experts': {'ccnf_patches_1.00_inner.txt': None} + }, + 'model_eye': { + 'clnf_left_synth.txt': None, + 'clnf_right_synth.txt': None, + 'main_clnf_synth_left.txt': None, + 'main_clnf_synth_right.txt': None, + 'patch_experts': { + 'ccnf_patches_1.00_synth_lid_.txt': None, + 'ccnf_patches_1.50_synth_lid_.txt': None, + 'left_ccnf_patches_1.00_synth_lid_.txt': None, + 'left_ccnf_patches_1.50_synth_lid_.txt': None + }, + 'pdms': { + 'pdm_28_l_eye_3D_closed.txt': None, + 'pdm_28_eye_3D_closed.txt': None + } + }, + 'cen_general.txt': None, + 'clm_general.txt': None, + 'clm_wild.txt': None, + 'clnf_general.txt': None, + 'clnf_wild.txt': None, + 'clnf_multi_pie.txt': None, + 'main_ceclm_general.txt': None, + 'main_clm_general.txt': None, + 'main_clm_wild.txt': None, + 'main_clnf_demos.txt': None, + 'main_clnf_general.txt': None, + 'main_clnf_wild.txt': None, + 'main_clnf_multi_pie.txt': None, + 'early_term_cen_of.txt': None, + 'haarAlign.txt': None, + 'tris_68.txt': None, + 'tris_68_full.txt': None +} + + +# CEN general model patches to download from Dropbox/OneDrive + +onedrive_base = 'https://onedrive.live.com/download?cid=2E2ADA578BFF6E6E&' + +patch_seq = ['0.25', '0.35', '0.50', '1.00'] +cen_patches = { + '0.25': { + 'main': 'https://www.dropbox.com/s/7na5qsjzz8yfoer/cen_patches_0.25_of.dat?dl=1', + 'backup': onedrive_base + 'resid=2E2ADA578BFF6E6E%2153072&authkey=AKqoZtcN0PSIZH4' + }, + '0.35': { + 'main': 'https://www.dropbox.com/s/k7bj804cyiu474t/cen_patches_0.35_of.dat?dl=1', + 'backup': onedrive_base + 'resid=2E2ADA578BFF6E6E%2153079&authkey=ANpDR1n3ckL_0gs' + }, + '0.50': { + 'main': 'https://www.dropbox.com/s/ixt4vkbmxgab1iu/cen_patches_0.50_of.dat?dl=1', + 'backup': onedrive_base + 'resid=2E2ADA578BFF6E6E%2153074&authkey=AGi-e30AfRc_zvs' + }, + '1.00': { + 'main': 'https://www.dropbox.com/s/2t5t1sdpshzfhpj/cen_patches_1.00_of.dat?dl=1', + 'backup': onedrive_base + 'resid=2E2ADA578BFF6E6E%2153070&authkey=AD6KjtYipphwBPc' + } +} + + +# Utility functions for downloading files + +def _getinput(*args, **kwargs): + # Python-agnostic function for getting console input. + try: + return raw_input(*args, **kwargs) + except NameError: + return input(*args, **kwargs) + + +def _download_file(f, parent, url): + + print("* Downloading '{0}'...".format(f)) + filepath = os.path.join(parent, f) + try: + mod_http = urlopen(url) + except (URLError, HTTPError): + raise RuntimeError("Failed to download '{0}'".format(url)) + with open(filepath, 'wb') as out: + out.write(mod_http.read()) + + +def _download_dir(d, root, path = []): + + dirpath = os.path.join(root, *path) + if not os.path.isdir(dirpath): + os.mkdir(dirpath) + + for f in d.keys(): + # If file, download to current path + if not d[f]: + mod_url_suffix = "/".join(path) + "/" + f if len(path) else f + mod_url = repo_base + base_modpath + mod_url_suffix + _download_file(f, dirpath, mod_url) + else: + _download_dir(d[f], root, path = path + [f]) + + +# Main function for downloading all OpenFace models + +def download_models(modpath = None): + """Downloads all models required to run OpenFace to a given local + directory, including the large externally-hosted CEN patch files. + + If no path is provided, the current working directory will be used. + """ + if not modpath: + modpath = os.path.join(os.getcwd(), 'openface_models') + else: + parentdir = os.path.normpath(os.path.join(modpath, os.pardir)) + if not os.path.isdir(parentdir): + raise RuntimeError("Path '{0}' does not exist".format(parentdir)) + + print("\nAll OpenFace models will be downloaded to '{0}'\n".format(modpath)) + prompt = "This will take approximately 470 MB of disk space. Continue?" + resp = _getinput(prompt + " (y/n): ") + if not len(resp) or resp[0].lower() != 'y': + print('') + return + + # Create local model directory if it doesn't already exist + if os.path.isdir(modpath): + prompt = "\nDirectory '{0}' already exists. Overwrite? (y/n): " + resp = _getinput(prompt.format(modpath)) + if not len(resp) or resp[0].lower() != 'y': + print('') + return + else: + shutil.rmtree(modpath) + os.mkdir(modpath) + + print('\n=== Downloading OpenFace models ===\n') + print('Destination: {0}\n'.format(modpath)) + + # First, download smaller models from GitHub + _download_dir(model_files, modpath, path = ['model']) + + # Then, download OpenCV haarcascade classifier from GitHub + classifier_url = repo_base + classifier_path + classifier_file + classifier_dir = os.path.join(modpath, 'classifiers') + if not os.path.isdir(classifier_dir): + os.mkdir(classifier_dir) + _download_file(classifier_file, classifier_dir, classifier_url) + + # Finally, download CEN patches to the model directory + patch_dir = os.path.join(modpath, 'model', 'patch_experts') + for patch in patch_seq: + patch_name = 'cen_patches_{0}_of.dat'.format(patch) + try: + patch_url = cen_patches[patch]['main'] + _download_file(patch_name, patch_dir, patch_url) + except RuntimeError: + patch_url = cen_patches[patch]['backup'] + _download_file(patch_name, patch_dir, patch_url) + + print('\n=== Downloaded all OpenFace models sucessfully! ===\n') + + +def install_models(): + """Installs the full set of OpenFace models to a hidden folder in the user's + home directory, allowing use of the OpenFace Python bindings from any folder. + """ + homedir = os.path.expanduser("~") + modpath = os.path.join(homedir, '.openface') + download_models(modpath) + + +def local_mod_path(): + modpath = os.path.join(os.getcwd(), 'openface_models') + if os.path.isdir(modpath): + return modpath + return None + + +def installed_mod_path(): + modpath = os.path.join(os.path.expanduser("~"), '.openface') + if os.path.isdir(modpath): + return modpath + return None + + +def get_mod_path(): + modpath = local_mod_path() + if not modpath: + modpath = installed_mod_path() + if not modpath: + e = ("Could not find a useable model directory. Please run either " + "'download_models()' to install the OpenFace models in the current " + "folder, or 'install_models()' to install them globally for the " + "current user." + ) + raise RuntimeError(e) + return modpath + + +if __name__ == '__main__': + download_models() diff --git a/python_api/pyopenface/openface.py b/python_api/pyopenface/openface.py new file mode 100644 index 000000000..07c850e88 --- /dev/null +++ b/python_api/pyopenface/openface.py @@ -0,0 +1,124 @@ +import os +from collections import namedtuple + +from _openface import (FaceModelParams, CLNF, DetectLandmarksInImage, DetectLandmarksInImageBounds, + DetectLandmarksInVideo, DetectSingleFaceHAAR, DetectSingleFaceHOG, DetectSingleFaceMTCNN) +from .models import installed_mod_path, local_mod_path, get_mod_path + +CLM_DETECTOR = 0 +CLNF_DETECTOR = 1 +CECLM_DETECTOR = 2 + +CLM_GENERAL = 'clm_general' # fastest, but least accurate +CLM_WILD = 'clm_wild' # same as CLM_GENERAL, but only trained on 300W dataset +CLNF_GENERAL = 'clnf_general' # medium speed/accuracy +CLNF_WILD = 'clnf_wild' # same as CLN_GENERAL, but only trained on 300W dataset +CLNF_MULTI_PIE = 'clnf_multi_pie' # same as CLN_GENERAL, but only trained on Multi-PIE +CECLM_GENERAL = 'ceclm_general' # Slowest but most accurate + +landmark_detector_types = { + CLM_GENERAL: CLM_DETECTOR, + CLM_WILD: CLM_DETECTOR, + CLNF_GENERAL: CLNF_DETECTOR, + CLNF_WILD: CLNF_DETECTOR, + CLNF_MULTI_PIE: CLNF_DETECTOR, + CECLM_GENERAL: CECLM_DETECTOR +} + +HAAR_DETECTOR = 0 +HOG_SVM_DETECTOR = 1 +MTCNN_DETECTOR = 2 +face_detectors = [HAAR_DETECTOR, HOG_SVM_DETECTOR, MTCNN_DETECTOR] + +# TODO: do these need to be made windows-compatible? +haar_detector_path = 'classifiers/haarcascade_frontalface_alt.xml' +mtcnn_detector_path = 'model/mtcnn_detector/MTCNN_detector.txt' + + +Rect = namedtuple("Rect", ['x', 'y', 'w', 'h']) + + +class FaceParams(object): + + def __init__(self): + + modpath = get_mod_path() + + init_mod = 'main_ceclm_general.txt' # model file required to init w/o error + mod_loc = os.path.join(modpath, 'model', init_mod) + haar_path = os.path.join(modpath, haar_detector_path) + mtcnn_path = os.path.join(modpath, mtcnn_detector_path) + + self._params = FaceModelParams([' ', + '-mloc', mod_loc#, + #'-fdloc', haar_path + ]) + self._params.haar_path = os.path.join(modpath, haar_path) + self._params.mtcnn_path = os.path.join(modpath, mtcnn_path) + #self._params.curr_landmark_detector = CECLM_DETECTOR + #self._params.curr_face_detector = MTCNN_DETECTOR + + +class FaceModel(object): + + def __init__(self, landmark_det=CECLM_GENERAL): + + try: + self._landmark_det = landmark_det + self._landmark_det_type = landmark_detector_types[landmark_det] + except KeyError: + raise ValueError("Unknown landmark detector model '{0}'".format(landmark_det)) + + modpath = get_mod_path() + modname = 'main_{0}.txt'.format(landmark_det) + self._modloc = os.path.join(modpath, 'model', modname) + self._mod = CLNF(self._modloc) + + # Preload MTCNN and HAAR face detectors + haar_loc = os.path.join(modpath, haar_detector_path) + mtcnn_loc = os.path.join(modpath, mtcnn_detector_path) + self._mod.load_haar(haar_loc) + self._mod.load_mtcnn(mtcnn_loc) + + + @property + def model_path(self): + return self._modloc + + +def detect_face(image, model, face_det=MTCNN_DETECTOR): + """Returns confidence in face detection & rectangle defining detected face, + if a face is detected. Otherwise, returns (None, None). + """ + if face_det == HAAR_DETECTOR: + result = DetectSingleFaceHAAR(image, model._mod) + elif face_det == HOG_SVM_DETECTOR: + result = DetectSingleFaceHOG(image, model._mod) + elif face_det == MTCNN_DETECTOR: + result = DetectSingleFaceMTCNN(image, model._mod) + else: + raise RuntimeError("Unknown face detector {0}".format(str(face_det))) + + if len(result): + if face_det == HAAR_DETECTOR: + confidence = None + bbox = result + else: + # NOTE: HOG confidence has no upper bound, can be > 1.0 + confidence = result[0] + bbox = result[1:] + return (confidence, Rect(*bbox)) + else: + return (None, None) + + +def detect_landmarks(image, model, params, bbox = None): + # TODO: array typechecking + if bbox and len(bbox) == 4: + return DetectLandmarksInImageBounds(image, model._mod, params._params, bbox) + return DetectLandmarksInImage(image, model._mod, params._params) + + +def detect_landmarks_video(frame, model, params): + # TODO: array typechecking + return DetectLandmarksInVideo(frame, model._mod, params._params) diff --git a/python_api/setup.py b/python_api/setup.py new file mode 100644 index 000000000..de079e82f --- /dev/null +++ b/python_api/setup.py @@ -0,0 +1,97 @@ +import os +import re +import sys +import platform +import subprocess +from zipfile import ZipFile + +try: + from urllib.request import urlopen # Python 3.x + from urllib.error import URLError, HTTPError +except ImportError: + from urllib2 import urlopen # Python 2 + from urllib2 import URLError, HTTPError + +from setuptools import setup, Extension +from setuptools.command.build_ext import build_ext +from distutils.version import LooseVersion + + +# Prior to installation, make sure pybind11 folder exists. If not, download it. + +pybind_version = "2.4.3" +pybind_url = "https://github.com/pybind/pybind11/archive/v{0}.zip".format(pybind_version) +if not os.path.isdir("pybind11"): + print("\n=== Downloading pybind11 ===") + try: + pybind_http = urlopen(pybind_url) + except (URLError, HTTPError): + raise RuntimeError("Failed to download pybind.") + with open("pybind.zip", 'wb') as out: + out.write(pybind_http.read()) + with ZipFile("pybind.zip", "r") as zf: + zf.extractall() + os.rename("pybind11-{0}".format(pybind_version), "pybind11") + os.remove("pybind.zip") + print("=== pybind11 downloaded sucessfully ===\n") + + +class CMakeExtension(Extension): + def __init__(self, name, sourcedir=''): + Extension.__init__(self, name, sources=[]) + self.sourcedir = os.path.abspath(sourcedir) + + +class CMakeBuild(build_ext): + def run(self): + try: + out = subprocess.check_output(['cmake', '--version']) + except OSError: + raise RuntimeError("CMake must be installed to build the following extensions: " + + ", ".join(e.name for e in self.extensions)) + + if platform.system() == "Windows": + cmake_version = LooseVersion(re.search(r'version\s*([\d.]+)', out.decode()).group(1)) + if cmake_version < '3.1.0': + raise RuntimeError("CMake >= 3.1.0 is required on Windows") + + for ext in self.extensions: + self.build_extension(ext) + + def build_extension(self, ext): + extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) + cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, + '-DPYTHON_EXECUTABLE=' + sys.executable] + + cfg = 'Debug' if self.debug else 'Release' + build_args = ['--config', cfg] + + if platform.system() == "Windows": + cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)] + if sys.maxsize > 2**32: + cmake_args += ['-A', 'x64'] + build_args += ['--', '/m'] + else: + cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg] + build_args += ['--', '-j2'] + + env = os.environ.copy() + env['CXXFLAGS'] = '{} -DVERSION_INFO=\\"{}\\"'.format(env.get('CXXFLAGS', ''), + self.distribution.get_version()) + if not os.path.exists(self.build_temp): + os.makedirs(self.build_temp) + subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env) + subprocess.check_call(['cmake', '--build', '.'] + build_args, cwd=self.build_temp) + +setup( + name='pyopenface', + version='0.0.1', + author='Austin Hurst', + author_email='mynameisaustinhurst@gmail.com', + description='Experimental Python bindings for the OpenFace library.', + long_description='', + ext_modules=[CMakeExtension('_openface')], + cmdclass=dict(build_ext=CMakeBuild), + zip_safe=False, + packages=['pyopenface'] +) diff --git a/python_api/src/openface.cpp b/python_api/src/openface.cpp new file mode 100644 index 000000000..b9bd3c071 --- /dev/null +++ b/python_api/src/openface.cpp @@ -0,0 +1,202 @@ +#include +#include +#include +#include + +#include "LandmarkDetectorModel.h" +#include "LandmarkDetectorParameters.h" +#include "LandmarkDetectorFunc.h" +#include "LandmarkDetectorUtils.h" + +namespace py = pybind11; + + +cv::Mat numpy_uint8_to_mat(py::array_t& input, bool togray) { + + py::buffer_info buf = input.request(); + + // TODO extend this a bunch + int dtype = CV_8UC1; + if (input.ndim() == 3) + dtype = (buf.shape[2] == 3) ? CV_8UC3 : CV_8UC4; + + cv::Mat mat(buf.shape[0], buf.shape[1], dtype, (unsigned char*)buf.ptr); + + // if input not grayscale, convert to grayscale before returning + if (togray && dtype != CV_8UC1) { + cv::Mat gray; + int convtype = (dtype == CV_8UC3) ? cv::COLOR_BGR2GRAY : cv::COLOR_BGRA2GRAY; + cv::cvtColor(mat, gray, convtype); + return gray; + } + + return mat; +} + + +PYBIND11_MODULE(_openface, m) { + + py::class_(m, "FaceModelParams") + .def(py::init<>()) + .def(py::init &>()) + .def_readwrite("limit_pose", &LandmarkDetector::FaceModelParameters::limit_pose) + .def_readwrite("validate_detections", &LandmarkDetector::FaceModelParameters::validate_detections) + .def_readwrite("use_face_template", &LandmarkDetector::FaceModelParameters::use_face_template) + .def_readwrite("model_path", &LandmarkDetector::FaceModelParameters::model_location) + .def_readwrite("haar_path", &LandmarkDetector::FaceModelParameters::haar_face_detector_location) + .def_readwrite("mtcnn_path", &LandmarkDetector::FaceModelParameters::mtcnn_face_detector_location) + .def_readwrite("curr_landmark_detector", &LandmarkDetector::FaceModelParameters::curr_landmark_detector) + .def_readwrite("curr_face_detector", &LandmarkDetector::FaceModelParameters::curr_face_detector) + ; + + py::class_(m, "CLNF") + .def(py::init()) + .def_readonly("detection_success", &LandmarkDetector::CLNF::detection_success) + .def_readonly("tracking_initialised", &LandmarkDetector::CLNF::tracking_initialised) + .def_readonly("detection_certainty", &LandmarkDetector::CLNF::detection_certainty) + .def_readonly("eye_model", &LandmarkDetector::CLNF::eye_model) + .def_readonly("consecutive_failures", &LandmarkDetector::CLNF::failures_in_a_row) + .def_readonly("loaded_successfully", &LandmarkDetector::CLNF::loaded_successfully) + //.def_readonly("detected_landmarks", &LandmarkDetector::CLNF::detected_landmarks) + .def_readonly("model_likelihood", &LandmarkDetector::CLNF::model_likelihood) + .def_readonly("landmark_likelihoods", &LandmarkDetector::CLNF::landmark_likelihoods) + .def("load_mtcnn", [](LandmarkDetector::CLNF& o, std::string& path) { + o.face_detector_MTCNN.Read(path); + bool ret = !o.face_detector_MTCNN.empty(); + return ret; + }) + .def("load_haar", [](LandmarkDetector::CLNF& o, std::string& path) { + o.face_detector_HAAR.load(path); + bool ret = !o.face_detector_HAAR.empty(); + return ret; + }) + .def("reset", [](LandmarkDetector::CLNF& o) { + o.Reset(); + return py::cast(Py_None); + }) + ; + + m.def("DetectSingleFaceHAAR", + [](py::array_t& image, + LandmarkDetector::CLNF& model + ) { + // NOTE: min_width and roi not implemented yet + cv::Rect_ bbox; + cv::Mat img = numpy_uint8_to_mat(image, true); + cv::Point prefpoint = cv::Point(-1, -1); + + model.Reset(); + bool success = LandmarkDetector::DetectSingleFace( + bbox, img, model.face_detector_HAAR, prefpoint + ); + if (success) { + std::vector vec = {bbox.x, bbox.y, bbox.width, bbox.height}; + return vec; + } else { + std::vector vec = {}; + return vec; + } + } + ); + + m.def("DetectSingleFaceHOG", + [](py::array_t& image, + LandmarkDetector::CLNF& model + ) { + // NOTE: min_width and roi not implemented yet + cv::Rect_ bbox; + cv::Mat img = numpy_uint8_to_mat(image, true); + float confidence = 0.0; + cv::Point prefpoint = cv::Point(-1, -1); + + model.Reset(); + bool success = LandmarkDetector::DetectSingleFaceHOG( + bbox, img, model.face_detector_HOG, confidence, prefpoint + ); + if (success) { + std::vector vec = {confidence, bbox.x, bbox.y, bbox.width, bbox.height}; + return vec; + } else { + std::vector vec = {}; + return vec; + } + } + ); + + m.def("DetectSingleFaceMTCNN", + [](py::array_t& image, + LandmarkDetector::CLNF& model + ) { + // NOTE: min_width and roi not implemented yet + cv::Rect_ bbox; + cv::Mat img = numpy_uint8_to_mat(image, true); + float confidence = 0.0; + cv::Point prefpoint = cv::Point(-1, -1); + + model.Reset(); + bool success = LandmarkDetector::DetectSingleFaceMTCNN( + bbox, img, model.face_detector_MTCNN, confidence, prefpoint + ); + if (success) { + std::vector vec = {confidence, bbox.x, bbox.y, bbox.width, bbox.height}; + return vec; + } else { + std::vector vec = {}; + return vec; + } + } + ); + + m.def("DetectLandmarksInImage", + [](py::array_t& image, + LandmarkDetector::CLNF& model, + LandmarkDetector::FaceModelParameters& params + ) { + model.Reset(); + cv::Mat img = numpy_uint8_to_mat(image, true); + if (LandmarkDetector::DetectLandmarksInImage(img, model, params, img)) { + std::vector vec = model.detected_landmarks; + return vec; + } else { + std::vector vec = {}; + return vec; + } + } + ); + + m.def("DetectLandmarksInImageBounds", + [](py::array_t& image, + LandmarkDetector::CLNF& model, + LandmarkDetector::FaceModelParameters& params, + std::vector& bbox + ) { + model.Reset(); + cv::Mat img = numpy_uint8_to_mat(image, true); + cv::Rect_ bounds = cv::Rect_(bbox[0], bbox[1], bbox[2], bbox[3]); + if (LandmarkDetector::DetectLandmarksInImage(img, bounds, model, params, img)) { + std::vector vec = model.detected_landmarks; + return vec; + } else { + std::vector vec = {}; + return vec; + } + } + ); + + m.def("DetectLandmarksInVideo", + [](py::array_t& image, + LandmarkDetector::CLNF& model, + LandmarkDetector::FaceModelParameters& params + ) { + cv::Mat img = numpy_uint8_to_mat(image, true); + if (LandmarkDetector::DetectLandmarksInVideo(img, model, params, img)) { + std::vector vec = model.detected_landmarks; + return vec; + } else { + std::vector vec = {}; + return vec; + } + } + ); + +} \ No newline at end of file