Merge pull request #5 from stalker314314/landmark_detection
Landmark detection (custom model and class-based)
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
# PDlib - A PHP extension for Dlib
|
# PDlib - A PHP extension for Dlib
|
||||||
A PHP extension
|
A PHP extension
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
- Dlib 19.13+
|
- Dlib 19.13+
|
||||||
@@ -71,7 +71,19 @@ var_dump($landmarks);
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### chinese whisers
|
Additionally, you can also use class-based approach:
|
||||||
|
```php
|
||||||
|
$rect = array("left"=>value, "top"=>value, "right"=>value, "bottom"=>value);
|
||||||
|
// You can download a trained facial shape predictor from:
|
||||||
|
// http://dlib.net/files/shape_predictor_5_face_landmarks.dat.bz2
|
||||||
|
$fld = new FaceLandmarkDetection("path/to/shape/predictor/model");
|
||||||
|
$parts = $fld->detect("path/to/image.jpg", $rect);
|
||||||
|
// $parts is integer array where keys are associative values with "x" and "y" for keys
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that, if you use class-based approach, you need to feed bounding box rectangle with values obtained from `dlib_face_detection`. If you use `dlib_face_landmark_detection`, everything is already done for you (and you are using HOG face detection model).
|
||||||
|
|
||||||
|
#### chinese whispers
|
||||||
|
|
||||||
Provides raw access to dlib's `chinese_whispers` function.
|
Provides raw access to dlib's `chinese_whispers` function.
|
||||||
Client need to build and provide edges. Edges are provided
|
Client need to build and provide edges. Edges are provided
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ static int le_pdlib;
|
|||||||
static zend_class_entry *cnn_face_detection_ce = nullptr;
|
static zend_class_entry *cnn_face_detection_ce = nullptr;
|
||||||
static zend_object_handlers cnn_face_detection_obj_handlers;
|
static zend_object_handlers cnn_face_detection_obj_handlers;
|
||||||
|
|
||||||
|
static zend_class_entry *face_landmark_detection_ce = nullptr;
|
||||||
|
static zend_object_handlers face_landmark_detection_obj_handlers;
|
||||||
|
|
||||||
/* {{{ PHP_INI
|
/* {{{ PHP_INI
|
||||||
*/
|
*/
|
||||||
/* Remove comments and fill if you need to have entries in php.ini
|
/* Remove comments and fill if you need to have entries in php.ini
|
||||||
@@ -111,9 +114,32 @@ zend_object* php_cnn_face_detection_new(zend_class_entry *class_type TSRMLS_DC)
|
|||||||
|
|
||||||
static void php_cnn_face_detection_free(zend_object *object)
|
static void php_cnn_face_detection_free(zend_object *object)
|
||||||
{
|
{
|
||||||
cnn_face_detection *cfd = (cnn_face_detection*)((char*)object - XtOffsetOf(cnn_face_detection, std));
|
cnn_face_detection *cfd = (cnn_face_detection*)((char*)object - XtOffsetOf(cnn_face_detection, std));
|
||||||
delete cfd->net;
|
delete cfd->net;
|
||||||
zend_object_std_dtor(object);
|
zend_object_std_dtor(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
const zend_function_entry face_landmark_detection_class_methods[] = {
|
||||||
|
PHP_ME(FaceLandmarkDetection, __construct, face_landmark_detection_ctor_arginfo, ZEND_ACC_PUBLIC)
|
||||||
|
PHP_ME(FaceLandmarkDetection, detect, face_landmark_detection_detect_arginfo, ZEND_ACC_PUBLIC)
|
||||||
|
PHP_FE_END
|
||||||
|
};
|
||||||
|
|
||||||
|
zend_object* php_face_landmark_detection_new(zend_class_entry *class_type TSRMLS_DC)
|
||||||
|
{
|
||||||
|
face_landmark_detection *fld = (face_landmark_detection*)ecalloc(1, sizeof(face_landmark_detection));
|
||||||
|
zend_object_std_init(&fld->std, class_type TSRMLS_CC);
|
||||||
|
object_properties_init(&fld->std, class_type);
|
||||||
|
fld->std.handlers = &face_landmark_detection_obj_handlers;
|
||||||
|
|
||||||
|
return &fld->std;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void php_face_landmark_detection_free(zend_object *object)
|
||||||
|
{
|
||||||
|
face_landmark_detection *fld = (face_landmark_detection*)((char*)object - XtOffsetOf(face_landmark_detection, std));
|
||||||
|
delete fld->sp;
|
||||||
|
zend_object_std_dtor(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* {{{ PHP_MINIT_FUNCTION
|
/* {{{ PHP_MINIT_FUNCTION
|
||||||
@@ -121,6 +147,8 @@ static void php_cnn_face_detection_free(zend_object *object)
|
|||||||
PHP_MINIT_FUNCTION(pdlib)
|
PHP_MINIT_FUNCTION(pdlib)
|
||||||
{
|
{
|
||||||
zend_class_entry ce;
|
zend_class_entry ce;
|
||||||
|
// CnnFaceDetection class definition
|
||||||
|
//
|
||||||
INIT_CLASS_ENTRY(ce, "CnnFaceDetection", cnn_face_detection_class_methods);
|
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 = zend_register_internal_class(&ce TSRMLS_CC);
|
||||||
cnn_face_detection_ce->create_object = php_cnn_face_detection_new;
|
cnn_face_detection_ce->create_object = php_cnn_face_detection_new;
|
||||||
@@ -128,6 +156,15 @@ PHP_MINIT_FUNCTION(pdlib)
|
|||||||
cnn_face_detection_obj_handlers.offset = XtOffsetOf(cnn_face_detection, std);
|
cnn_face_detection_obj_handlers.offset = XtOffsetOf(cnn_face_detection, std);
|
||||||
cnn_face_detection_obj_handlers.free_obj = php_cnn_face_detection_free;
|
cnn_face_detection_obj_handlers.free_obj = php_cnn_face_detection_free;
|
||||||
|
|
||||||
|
// FaceLandmarkDetection class definition
|
||||||
|
//
|
||||||
|
INIT_CLASS_ENTRY(ce, "FaceLandmarkDetection", face_landmark_detection_class_methods);
|
||||||
|
face_landmark_detection_ce = zend_register_internal_class(&ce TSRMLS_CC);
|
||||||
|
face_landmark_detection_ce->create_object = php_face_landmark_detection_new;
|
||||||
|
memcpy(&face_landmark_detection_obj_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
|
||||||
|
face_landmark_detection_obj_handlers.offset = XtOffsetOf(face_landmark_detection, std);
|
||||||
|
face_landmark_detection_obj_handlers.free_obj = php_face_landmark_detection_free;
|
||||||
|
|
||||||
/* If you have INI entries, uncomment these lines
|
/* If you have INI entries, uncomment these lines
|
||||||
REGISTER_INI_ENTRIES();
|
REGISTER_INI_ENTRIES();
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
#include "../php_pdlib.h"
|
#include "../php_pdlib.h"
|
||||||
#include "face_landmark_detection.h"
|
#include "face_landmark_detection.h"
|
||||||
|
|
||||||
|
#include <zend_exceptions.h>
|
||||||
|
|
||||||
#include <dlib/image_processing/frontal_face_detector.h>
|
#include <dlib/image_processing/frontal_face_detector.h>
|
||||||
#include <dlib/image_processing/render_face_detections.h>
|
#include <dlib/image_processing/render_face_detections.h>
|
||||||
#include <dlib/image_processing.h>
|
#include <dlib/image_processing.h>
|
||||||
@@ -13,6 +15,12 @@
|
|||||||
using namespace dlib;
|
using namespace dlib;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
static inline face_landmark_detection *php_face_landmark_detection_from_obj(zend_object *obj) {
|
||||||
|
return (face_landmark_detection*)((char*)(obj) - XtOffsetOf(face_landmark_detection, std));
|
||||||
|
}
|
||||||
|
|
||||||
|
#define Z_FACE_LANDMARK_DETECTION_P(zv) php_face_landmark_detection_from_obj(Z_OBJ_P((zv)))
|
||||||
|
|
||||||
PHP_FUNCTION(dlib_face_landmark_detection)
|
PHP_FUNCTION(dlib_face_landmark_detection)
|
||||||
{
|
{
|
||||||
char *shape_predictor_file_path;
|
char *shape_predictor_file_path;
|
||||||
@@ -60,4 +68,106 @@ PHP_FUNCTION(dlib_face_landmark_detection)
|
|||||||
{
|
{
|
||||||
RETURN_FALSE;
|
RETURN_FALSE;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PHP_METHOD(FaceLandmarkDetection, __construct)
|
||||||
|
{
|
||||||
|
char *sz_shape_predictor_file_path;
|
||||||
|
size_t shape_predictor_file_path_len;
|
||||||
|
|
||||||
|
face_landmark_detection *fld = Z_FACE_LANDMARK_DETECTION_P(getThis());
|
||||||
|
|
||||||
|
if (nullptr == fld) {
|
||||||
|
php_error_docref(NULL TSRMLS_CC, E_ERROR, "Unable to find obj in FaceLandmarkDetection::__construct()");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse predictor model's path
|
||||||
|
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s",
|
||||||
|
&sz_shape_predictor_file_path, &shape_predictor_file_path_len) == FAILURE){
|
||||||
|
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, "Unable to parse shape_predictor_file_path");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load predictor model from given path
|
||||||
|
try {
|
||||||
|
string shape_predictor_file_path(sz_shape_predictor_file_path, shape_predictor_file_path_len);
|
||||||
|
fld->sp = new shape_predictor;
|
||||||
|
deserialize(shape_predictor_file_path) >> *(fld->sp);
|
||||||
|
} catch (exception& e) {
|
||||||
|
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, e.what());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper macro to automatically have parsing of "top"/"bottom"/"left"/"right"
|
||||||
|
#define PARSE_BOUNDING_BOX_EDGE(side) \
|
||||||
|
zval* data##side; \
|
||||||
|
/* Tries to find given key in array */ \
|
||||||
|
data##side = zend_hash_str_find(bounding_box_hash, #side, sizeof(#side)-1); \
|
||||||
|
if (data##side == nullptr) { \
|
||||||
|
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, "Bounding box (second argument) is missing " #side "key"); \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
/* We also need to check proper type of value in associative array */ \
|
||||||
|
if (Z_TYPE_P(data##side) != IS_LONG) { \
|
||||||
|
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, "Value of bounding box's (second argument) " #side " key is not long type"); \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
zend_long side = Z_LVAL_P(data##side); \
|
||||||
|
|
||||||
|
PHP_METHOD(FaceLandmarkDetection, detect)
|
||||||
|
{
|
||||||
|
char *img_path;
|
||||||
|
size_t img_path_len;
|
||||||
|
zval *bounding_box;
|
||||||
|
array2d<rgb_pixel> img;
|
||||||
|
|
||||||
|
// Parse path to image and bounding box. Bounding box is associative array of 4 elements - "top", "bottom", "left" and "right".
|
||||||
|
//
|
||||||
|
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa", &img_path, &img_path_len, &bounding_box) == FAILURE){
|
||||||
|
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, "Unable to parse detect arguments");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that bounding box have exactly 4 elements
|
||||||
|
HashTable *bounding_box_hash = Z_ARRVAL_P(bounding_box);
|
||||||
|
uint32_t bounding_box_num_elements = zend_hash_num_elements(bounding_box_hash);
|
||||||
|
if (bounding_box_num_elements != 4) {
|
||||||
|
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, "Bounding box (second argument) needs to have exactly 4 elements");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve all 4 edges of bounding box
|
||||||
|
//
|
||||||
|
PARSE_BOUNDING_BOX_EDGE(top)
|
||||||
|
PARSE_BOUNDING_BOX_EDGE(bottom)
|
||||||
|
PARSE_BOUNDING_BOX_EDGE(left)
|
||||||
|
PARSE_BOUNDING_BOX_EDGE(right)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Load image and execute shape predictor on it.
|
||||||
|
//
|
||||||
|
face_landmark_detection *fld = Z_FACE_LANDMARK_DETECTION_P(getThis());
|
||||||
|
load_image(img, img_path);
|
||||||
|
rectangle rct(left, top, right, bottom);
|
||||||
|
full_object_detection shape = fld->sp->operator()(img, rct);
|
||||||
|
|
||||||
|
// Return value is regular array with integer keys.
|
||||||
|
// Each key is one part from shape. Value of each part is associative array of keys "x" and "y".
|
||||||
|
//
|
||||||
|
array_init(return_value);
|
||||||
|
for (int i = 0; i < shape.num_parts(); i++) {
|
||||||
|
zval part;
|
||||||
|
array_init(&part);
|
||||||
|
dlib::point p = shape.part(i);
|
||||||
|
add_assoc_long(&part, "x", p.x());
|
||||||
|
add_assoc_long(&part, "y", p.y());
|
||||||
|
add_next_index_zval(return_value, &part);
|
||||||
|
}
|
||||||
|
} catch (exception& e) {
|
||||||
|
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, e.what());
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,10 +5,30 @@
|
|||||||
#ifndef PDLIB_FACE_LANDMARK_DETECTION_H
|
#ifndef PDLIB_FACE_LANDMARK_DETECTION_H
|
||||||
#define PDLIB_FACE_LANDMARK_DETECTION_H
|
#define PDLIB_FACE_LANDMARK_DETECTION_H
|
||||||
|
|
||||||
|
#include <dlib/dnn.h>
|
||||||
|
|
||||||
|
using namespace dlib;
|
||||||
|
|
||||||
ZEND_BEGIN_ARG_INFO_EX(dlib_face_landmark_detection_arginfo, 0, 0, 1)
|
ZEND_BEGIN_ARG_INFO_EX(dlib_face_landmark_detection_arginfo, 0, 0, 1)
|
||||||
ZEND_ARG_INFO(0, shape_predictor_file_path)
|
ZEND_ARG_INFO(0, shape_predictor_file_path)
|
||||||
ZEND_ARG_INFO(0, img_path)
|
ZEND_ARG_INFO(0, img_path)
|
||||||
ZEND_END_ARG_INFO()
|
ZEND_END_ARG_INFO()
|
||||||
PHP_FUNCTION(dlib_face_landmark_detection);
|
PHP_FUNCTION(dlib_face_landmark_detection);
|
||||||
|
|
||||||
|
typedef struct _face_landmark_detection {
|
||||||
|
shape_predictor *sp;
|
||||||
|
zend_object std;
|
||||||
|
} face_landmark_detection;
|
||||||
|
|
||||||
|
ZEND_BEGIN_ARG_INFO_EX(face_landmark_detection_ctor_arginfo, 0, 0, 1)
|
||||||
|
ZEND_ARG_INFO(0, shape_predictor_file_path)
|
||||||
|
ZEND_END_ARG_INFO()
|
||||||
|
PHP_METHOD(FaceLandmarkDetection, __construct);
|
||||||
|
|
||||||
|
ZEND_BEGIN_ARG_INFO_EX(face_landmark_detection_detect_arginfo, 0, 0, 2)
|
||||||
|
ZEND_ARG_INFO(0, img_path)
|
||||||
|
ZEND_ARG_INFO(0, bounding_box)
|
||||||
|
ZEND_END_ARG_INFO()
|
||||||
|
PHP_METHOD(FaceLandmarkDetection, detect);
|
||||||
|
|
||||||
#endif //PDLIB_FACE_LANDMARK_DETECTION_H
|
#endif //PDLIB_FACE_LANDMARK_DETECTION_H
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
--TEST--
|
||||||
|
Testing FaceLandmarkDetection constructor without arguments
|
||||||
|
--SKIPIF--
|
||||||
|
<?php if (!extension_loaded("pdlib")) print "skip"; ?>
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
try {
|
||||||
|
new FaceLandmarkDetection();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
var_dump($e->getMessage());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
new FaceLandmarkDetection("non-existent file");
|
||||||
|
} catch (Exception $e) {
|
||||||
|
var_dump($e->getMessage());
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
Warning: FaceLandmarkDetection::__construct() expects exactly 1 parameter, 0 given in /home/branko/pdlib/tests/face_landmark_detection_ctor_error.php on line 3
|
||||||
|
string(41) "Unable to parse shape_predictor_file_path"
|
||||||
|
string(45) "Unable to open non-existent file for reading."
|
||||||
Reference in New Issue
Block a user