Merge pull request #5 from stalker314314/landmark_detection

Landmark detection (custom model and class-based)
This commit is contained in:
goodspb 2018-08-27 22:53:32 +08:00 committed by GitHub
commit 661c37c9ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 205 additions and 5 deletions

View File

@ -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.
Client need to build and provide edges. Edges are provided

View File

@ -43,6 +43,9 @@ static int le_pdlib;
static zend_class_entry *cnn_face_detection_ce = nullptr;
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
*/
/* Remove comments and fill if you need to have entries in php.ini
@ -116,11 +119,36 @@ static void php_cnn_face_detection_free(zend_object *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(pdlib)
{
zend_class_entry ce;
// CnnFaceDetection class definition
//
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;
@ -128,6 +156,15 @@ PHP_MINIT_FUNCTION(pdlib)
cnn_face_detection_obj_handlers.offset = XtOffsetOf(cnn_face_detection, std);
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
REGISTER_INI_ENTRIES();
*/

View File

@ -2,6 +2,8 @@
#include "../php_pdlib.h"
#include "face_landmark_detection.h"
#include <zend_exceptions.h>
#include <dlib/image_processing/frontal_face_detector.h>
#include <dlib/image_processing/render_face_detections.h>
#include <dlib/image_processing.h>
@ -13,6 +15,12 @@
using namespace dlib;
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)
{
char *shape_predictor_file_path;
@ -61,3 +69,105 @@ PHP_FUNCTION(dlib_face_landmark_detection)
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;
}
}

View File

@ -5,10 +5,30 @@
#ifndef 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_ARG_INFO(0, shape_predictor_file_path)
ZEND_ARG_INFO(0, img_path)
ZEND_END_ARG_INFO()
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

View File

@ -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."