diff --git a/.gitignore b/.gitignore index a1a9241..54983d3 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ config.status config.sub configure configure.ac +configure.in include install-sh libtool @@ -35,4 +36,4 @@ tests/*/*.log tests/*/*.sh .idea -cmake-build-debug \ No newline at end of file +cmake-build-debug diff --git a/README.md b/README.md index 1cbeccb..ad3114f 100644 --- a/README.md +++ b/README.md @@ -75,5 +75,5 @@ var_dump($landmarks); - [x] 1.Face Detection - [x] 2.Face Landmark Detection - [ ] 3.Deep Face Recognition -- [ ] 4.Deep Learning Face Detection +- [x] 4.Deep Learning Face Detection diff --git a/config.m4 b/config.m4 index a4e692e..1ad4c39 100644 --- a/config.m4 +++ b/config.m4 @@ -26,7 +26,8 @@ if test "$PHP_PDLIB" != "no"; then pdlib_src_files="pdlib.cc \ src/face_detection.cc \ - src/face_landmark_detection.cc" + src/face_landmark_detection.cc \ + src/cnn_face_detection.cc" AC_MSG_CHECKING(for pkg-config) if test ! -f "$PKG_CONFIG"; then @@ -49,4 +50,4 @@ if test "$PHP_PDLIB" != "no"; then PHP_EVAL_INCLINE($LIBDLIB_CFLAGS) PHP_NEW_EXTENSION(pdlib, $pdlib_src_files, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1) -fi \ No newline at end of file +fi diff --git a/pdlib.cc b/pdlib.cc index b923421..7aa8d44 100644 --- a/pdlib.cc +++ b/pdlib.cc @@ -29,6 +29,7 @@ extern "C" { } #include "php_pdlib.h" #include "src/face_detection.h" +#include "src/cnn_face_detection.h" #include "src/face_landmark_detection.h" /* If you declare any globals in php_pdlib.h uncomment this: @@ -38,6 +39,9 @@ ZEND_DECLARE_MODULE_GLOBALS(pdlib) /* True global resources - no need for thread safety here */ static int le_pdlib; +static zend_class_entry *cnn_face_detection_ce = nullptr; +static zend_object_handlers cnn_face_detection_obj_handlers; + /* {{{ PHP_INI */ /* Remove comments and fill if you need to have entries in php.ini @@ -88,15 +92,47 @@ static void php_pdlib_init_globals(zend_pdlib_globals *pdlib_globals) */ /* }}} */ +const zend_function_entry cnn_face_detection_class_methods[] = { + PHP_ME(CnnFaceDetection, __construct, cnn_face_detection_ctor_arginfo, ZEND_ACC_PUBLIC) + PHP_ME(CnnFaceDetection, detect, cnn_face_detection_detect_arginfo, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +zend_object* php_cnn_face_detection_new(zend_class_entry *class_type TSRMLS_DC) +{ + cnn_face_detection *cfd = (cnn_face_detection*)ecalloc(1, sizeof(cnn_face_detection)); + zend_object_std_init(&cfd->std, class_type TSRMLS_CC); + object_properties_init(&cfd->std, class_type); + cfd->std.handlers = &cnn_face_detection_obj_handlers; //zend_get_std_object_handlers(); + + return &cfd->std; +} + +static void php_cnn_face_detection_free(zend_object *object) +{ + cnn_face_detection *cfd = (cnn_face_detection*)((char*)object - XtOffsetOf(cnn_face_detection, std)); + delete cfd->net; + zend_object_std_dtor(object); +} + /* {{{ PHP_MINIT_FUNCTION */ PHP_MINIT_FUNCTION(pdlib) { + zend_class_entry ce; + INIT_CLASS_ENTRY(ce, "CnnFaceDetection", cnn_face_detection_class_methods); + cnn_face_detection_ce = zend_register_internal_class(&ce TSRMLS_CC); + cnn_face_detection_ce->create_object = php_cnn_face_detection_new; + memcpy(&cnn_face_detection_obj_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + cnn_face_detection_obj_handlers.offset = XtOffsetOf(cnn_face_detection, std); + cnn_face_detection_obj_handlers.free_obj = php_cnn_face_detection_free; + /* If you have INI entries, uncomment these lines REGISTER_INI_ENTRIES(); */ return SUCCESS; } + /* }}} */ /* {{{ PHP_MSHUTDOWN_FUNCTION diff --git a/src/cnn_face_detection.cc b/src/cnn_face_detection.cc new file mode 100644 index 0000000..c4fad7a --- /dev/null +++ b/src/cnn_face_detection.cc @@ -0,0 +1,108 @@ +#include "../php_pdlib.h" +#include "cnn_face_detection.h" + +#include +#include +#include +#include +#include +#include + +using namespace dlib; +using namespace std; + +static inline cnn_face_detection *php_cnn_face_detection_from_obj(zend_object *obj) { + return (cnn_face_detection*)((char*)(obj) - XtOffsetOf(cnn_face_detection, std)); +} + +#define Z_CNN_FACE_DETECTION_P(zv) php_cnn_face_detection_from_obj(Z_OBJ_P((zv))) + +PHP_METHOD(CnnFaceDetection, __construct) +{ + char *sz_cnn_face_detection_model_path; + size_t cnn_face_detection_model_path_len; + + cnn_face_detection *cfd = Z_CNN_FACE_DETECTION_P(getThis()); + + if (NULL == cfd) { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "Unable to find obj in CnnFaceDetection::__construct()"); + return; + } + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", + &sz_cnn_face_detection_model_path, &cnn_face_detection_model_path_len) == FAILURE){ + zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, "Unable to parse face_detection_model_path"); + return; + } + + try { + string cnn_face_detection_model_path( + sz_cnn_face_detection_model_path, cnn_face_detection_model_path_len); + net_type *pnet = new net_type; + deserialize(cnn_face_detection_model_path) >> *pnet; + cfd->net = pnet; + } catch (exception& e) { + zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, e.what()); + return; + } +} + +PHP_METHOD(CnnFaceDetection, detect) +{ + char *img_path; + size_t img_path_len; + long upsample_num = 1; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &img_path, &img_path_len, &upsample_num) == FAILURE){ + zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, "Unable to parse detect arguments"); + RETURN_FALSE; + } + + try { + cnn_face_detection *cfd = Z_CNN_FACE_DETECTION_P(getThis()); + + pyramid_down<2> pyr; + matrix img; + load_image(img, img_path); + + // Upsampling the image will allow us to detect smaller faces but will cause the + // program to use more RAM and run longer. + // + unsigned int levels = upsample_num; + while (levels > 0) + { + levels--; + pyramid_up(img, pyr); + } + + net_type *pnet = cfd->net; + auto dets = (*pnet)(img); + int rect_count = 0; + array_init(return_value); + + // Scale the detection locations back to the original image size + // if the image was upscaled. + // + for (auto&& d: dets) { + d.rect = pyr.rect_down(d.rect, upsample_num); + // Create new assoc array with dimensions of found rectt and confidence + // + zval rect_arr; + array_init(&rect_arr); + add_assoc_long(&rect_arr, "left", d.rect.left()); + add_assoc_long(&rect_arr, "top", d.rect.top()); + add_assoc_long(&rect_arr, "right", d.rect.right()); + add_assoc_long(&rect_arr, "bottom", d.rect.bottom()); + add_assoc_double(&rect_arr, "detection_confidence", d.detection_confidence); + // Add this assoc array to returned array + // + add_next_index_zval(return_value, &rect_arr); + } + } + catch (exception& e) + { + zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, e.what()); + return; + } +} + diff --git a/src/cnn_face_detection.h b/src/cnn_face_detection.h new file mode 100644 index 0000000..1a604a8 --- /dev/null +++ b/src/cnn_face_detection.h @@ -0,0 +1,36 @@ +// +// Created by branko at kokanovic dot org on 2018/7/16. +// + +#ifndef PHP_DLIB_CNN_FACE_DETECTION_H +#define PHP_DLIB_CNN_FACE_DETECTION_H + +#include + +using namespace dlib; + +template using con5d = con; +template using con5 = con; + +template using downsampler = relu>>>>>>>>; +template using rcon5 = relu>>; + +using net_type = loss_mmod>>>>>>>; + +typedef struct _cnn_face_detection { + net_type *net; + zend_object std; +} cnn_face_detection; + +ZEND_BEGIN_ARG_INFO_EX(cnn_face_detection_ctor_arginfo, 0, 0, 1) + ZEND_ARG_INFO(0, cnn_face_detection_model_path) +ZEND_END_ARG_INFO() +PHP_METHOD(CnnFaceDetection, __construct); + +ZEND_BEGIN_ARG_INFO_EX(cnn_face_detection_detect_arginfo, 0, 0, 2) + ZEND_ARG_INFO(0, img_path) + ZEND_ARG_INFO(0, upsample_num) +ZEND_END_ARG_INFO() +PHP_METHOD(CnnFaceDetection, detect); + +#endif //PHP_DLIB_CNN_FACE_DETECTION_H diff --git a/tests/cnn_face_detection_ctor_error.phpt b/tests/cnn_face_detection_ctor_error.phpt new file mode 100644 index 0000000..2da7238 --- /dev/null +++ b/tests/cnn_face_detection_ctor_error.phpt @@ -0,0 +1,15 @@ +--TEST-- +Testing CnnFaceDetection constructor without arguments +--SKIPIF-- + +--FILE-- +getMessage()); +} +?> +--EXPECT-- +Warning: CnnFaceDetection::__construct() expects exactly 1 parameter, 0 given in /home/branko/pdlib/tests/cnn_face_detection_ctor_error.php on line 3 +string(41) "Unable to parse face_detection_model_path" diff --git a/tests/cnn_face_detection_ctor_model_not_found_error.phpt b/tests/cnn_face_detection_ctor_model_not_found_error.phpt new file mode 100644 index 0000000..493f4de --- /dev/null +++ b/tests/cnn_face_detection_ctor_model_not_found_error.phpt @@ -0,0 +1,14 @@ +--TEST-- +Testing CnnFaceDetection constructor with model that do not exist +--SKIPIF-- + +--FILE-- +getMessage()); +} +?> +--EXPECT-- +string(31) "Unable to open foo for reading."