Merge pull request #6 from stalker314314/face_recognition
Face recognition
This commit is contained in:
commit
ac65116a0c
81
README.md
81
README.md
@ -1,16 +1,15 @@
|
||||
# PDlib - A PHP extension for Dlib
|
||||
A PHP extension
|
||||
|
||||
## Requirements
|
||||
- Dlib 19.13+
|
||||
- PHP 7.0+
|
||||
- C++ 11
|
||||
- C++11
|
||||
|
||||
## Dependence
|
||||
## Dependencies
|
||||
|
||||
### Dlib
|
||||
|
||||
Install Dlib as share library
|
||||
Install Dlib as shared library
|
||||
|
||||
```bash
|
||||
git clone git@github.com:davisking/dlib.git
|
||||
@ -33,23 +32,62 @@ make
|
||||
sudo make install
|
||||
```
|
||||
|
||||
## Configure
|
||||
### Configure PHP installation
|
||||
|
||||
```
|
||||
```bash
|
||||
vim youpath/php.ini
|
||||
```
|
||||
|
||||
Write the below content into `php.ini`
|
||||
Append the content below into `php.ini`
|
||||
|
||||
```
|
||||
[pdlib]
|
||||
extension="pdlib.so"
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
For tests, you will need to have bz2 extension installed. On Ubuntu, it boils to:
|
||||
```bash
|
||||
sudo apt-get install php-bz2
|
||||
```
|
||||
|
||||
After you successfully compiled everything, just run:
|
||||
```bash
|
||||
make test
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### General Usage
|
||||
|
||||
Good starting point can be `tests/integration_face_recognition.phpt`. Check that first.
|
||||
|
||||
Basically, if you just quickly want to get from your image to 128D descriptor of faces in image,
|
||||
here is really minimal example how:
|
||||
|
||||
```php
|
||||
<?php
|
||||
$img_path = "image.jpg";
|
||||
$fd = new CnnFaceDetection("detection_cnn_model.dat");
|
||||
$detected_faces = $fd->detect($img_path);
|
||||
foreach($detected_faces as $detected_face) {
|
||||
$fld = new FaceLandmarkDetection("landmark_model.dat");
|
||||
$landmarks = $fld->detect($img_path, $detected_face);
|
||||
$fr = new FaceRecognition("recognition_model.dat");
|
||||
$descriptor = $fr->computeDescriptor($img_path, $landmarks);
|
||||
// Optionally use descriptor later in `dlib_chinese_whispers` function
|
||||
}
|
||||
```
|
||||
|
||||
Location from where to get these models can be found on DLib website, as well as in `tests/integration_face_recognition.phpt` test.
|
||||
|
||||
### Specific use cases
|
||||
|
||||
#### face detection
|
||||
|
||||
If you want to use HOG based approach:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
@ -57,9 +95,19 @@ extension="pdlib.so"
|
||||
$faceCount = dlib_face_detection("~/a.jpg");
|
||||
// how mary face in the picture.
|
||||
var_dump($faceCount);
|
||||
|
||||
```
|
||||
|
||||
If you want to use CNN approach (and CNN model):
|
||||
|
||||
```php
|
||||
<?php
|
||||
$fd = new CnnFaceDetection("detection_cnn_model.dat");
|
||||
$detected_faces = $fd->detect("image.jpg");
|
||||
// $detected_face is indexed array, where values are assoc arrays with "top", "bottom", "left" and "right" values
|
||||
```
|
||||
|
||||
CNN model can get you slightly better results, but is much, much more demanding (CPU and memory, GPU is also preferred).
|
||||
|
||||
#### face landmark detection
|
||||
|
||||
```php
|
||||
@ -68,7 +116,6 @@ var_dump($faceCount);
|
||||
// face landmark detection
|
||||
$landmarks = dlib_face_landmark_detection("~/a.jpg");
|
||||
var_dump($landmarks);
|
||||
|
||||
```
|
||||
|
||||
Additionally, you can also use class-based approach:
|
||||
@ -83,6 +130,19 @@ $parts = $fld->detect("path/to/image.jpg", $rect);
|
||||
|
||||
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).
|
||||
|
||||
#### face recognition (aka getting face descriptor)
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
$fr = new FaceRecognition($model_path);
|
||||
$landmarks = array(
|
||||
"rect" => $rect_of_faces_obtained_with_CnnFaceDetection,
|
||||
"parts" => $parts_obtained_with_FaceLandmarkDetection);
|
||||
$descriptor = $fr->computeDescriptor($img_path, $landmarks);
|
||||
// $descriptor is 128D array
|
||||
```
|
||||
|
||||
#### chinese whispers
|
||||
|
||||
Provides raw access to dlib's `chinese_whispers` function.
|
||||
@ -98,13 +158,12 @@ Returned value is also numeric array, containing obtained labels.
|
||||
// $labels will look like [0,0,1].
|
||||
$edges = [[0,0], [0,1], [1,1], [2,2]];
|
||||
$labels = dlib_chinese_whispers($edges);
|
||||
|
||||
```
|
||||
|
||||
## Features
|
||||
- [x] 1.Face Detection
|
||||
- [x] 2.Face Landmark Detection
|
||||
- [ ] 3.Deep Face Recognition
|
||||
- [x] 3.Deep Face Recognition
|
||||
- [x] 4.Deep Learning Face Detection
|
||||
- [x] 5. Raw chinese_whispers
|
||||
|
||||
|
@ -28,7 +28,8 @@ if test "$PHP_PDLIB" != "no"; then
|
||||
src/chinese_whispers.cc \
|
||||
src/face_detection.cc \
|
||||
src/face_landmark_detection.cc \
|
||||
src/cnn_face_detection.cc"
|
||||
src/face_recognition.cc \
|
||||
src/cnn_face_detection.cc "
|
||||
|
||||
AC_MSG_CHECKING(for pkg-config)
|
||||
if test ! -f "$PKG_CONFIG"; then
|
||||
|
36
pdlib.cc
36
pdlib.cc
@ -30,6 +30,7 @@ extern "C" {
|
||||
#include "php_pdlib.h"
|
||||
#include "src/chinese_whispers.h"
|
||||
#include "src/face_detection.h"
|
||||
#include "src/face_recognition.h"
|
||||
#include "src/cnn_face_detection.h"
|
||||
#include "src/face_landmark_detection.h"
|
||||
|
||||
@ -46,6 +47,9 @@ 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;
|
||||
|
||||
static zend_class_entry *face_recognition_ce = nullptr;
|
||||
static zend_object_handlers face_recognition_obj_handlers;
|
||||
|
||||
/* {{{ PHP_INI
|
||||
*/
|
||||
/* Remove comments and fill if you need to have entries in php.ini
|
||||
@ -142,6 +146,29 @@ static void php_face_landmark_detection_free(zend_object *object)
|
||||
zend_object_std_dtor(object);
|
||||
}
|
||||
|
||||
const zend_function_entry face_recognition_class_methods[] = {
|
||||
PHP_ME(FaceRecognition, __construct, face_recognition_ctor_arginfo, ZEND_ACC_PUBLIC)
|
||||
PHP_ME(FaceRecognition, computeDescriptor, face_recognition_compute_descriptor_arginfo, ZEND_ACC_PUBLIC)
|
||||
PHP_FE_END
|
||||
};
|
||||
|
||||
zend_object* php_face_recognition_new(zend_class_entry *class_type TSRMLS_DC)
|
||||
{
|
||||
face_recognition *fr = (face_recognition*)ecalloc(1, sizeof(face_recognition));
|
||||
zend_object_std_init(&fr->std, class_type TSRMLS_CC);
|
||||
object_properties_init(&fr->std, class_type);
|
||||
fr->std.handlers = &face_recognition_obj_handlers;
|
||||
|
||||
return &fr->std;
|
||||
}
|
||||
|
||||
static void php_face_recognition_free(zend_object *object)
|
||||
{
|
||||
face_recognition *fr = (face_recognition*)((char*)object - XtOffsetOf(face_recognition, std));
|
||||
delete fr->net;
|
||||
zend_object_std_dtor(object);
|
||||
}
|
||||
|
||||
/* {{{ PHP_MINIT_FUNCTION
|
||||
*/
|
||||
PHP_MINIT_FUNCTION(pdlib)
|
||||
@ -165,6 +192,15 @@ PHP_MINIT_FUNCTION(pdlib)
|
||||
face_landmark_detection_obj_handlers.offset = XtOffsetOf(face_landmark_detection, std);
|
||||
face_landmark_detection_obj_handlers.free_obj = php_face_landmark_detection_free;
|
||||
|
||||
// FaceRecognition class definition
|
||||
//
|
||||
INIT_CLASS_ENTRY(ce, "FaceRecognition", face_recognition_class_methods);
|
||||
face_recognition_ce = zend_register_internal_class(&ce TSRMLS_CC);
|
||||
face_recognition_ce->create_object = php_face_recognition_new;
|
||||
memcpy(&face_recognition_obj_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
|
||||
face_recognition_obj_handlers.offset = XtOffsetOf(face_recognition, std);
|
||||
face_recognition_obj_handlers.free_obj = php_face_recognition_free;
|
||||
|
||||
/* If you have INI entries, uncomment these lines
|
||||
REGISTER_INI_ENTRIES();
|
||||
*/
|
||||
|
16
php_pdlib.h
16
php_pdlib.h
@ -61,6 +61,22 @@ ZEND_END_MODULE_GLOBALS(pdlib)
|
||||
ZEND_TSRMLS_CACHE_EXTERN()
|
||||
#endif
|
||||
|
||||
#define PARSE_LONG_FROM_ARRAY(hashtable, key, error_key_missing, error_key_not_long) \
|
||||
zval* data##key; \
|
||||
/* Tries to find given key in array */ \
|
||||
data##key = zend_hash_str_find(hashtable, #key, sizeof(#key)-1); \
|
||||
if (data##key == nullptr) { \
|
||||
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, #error_key_missing); \
|
||||
return; \
|
||||
} \
|
||||
\
|
||||
/* We also need to check proper type of value in associative array */ \
|
||||
if (Z_TYPE_P(data##key) != IS_LONG) { \
|
||||
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, #error_key_not_long); \
|
||||
return; \
|
||||
} \
|
||||
zend_long key = Z_LVAL_P(data##key); \
|
||||
|
||||
#endif /* PHP_PDLIB_H */
|
||||
|
||||
|
||||
|
@ -79,13 +79,13 @@ PHP_METHOD(CnnFaceDetection, detect)
|
||||
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
|
||||
// Create new assoc array with dimensions of found rect and confidence
|
||||
//
|
||||
zval rect_arr;
|
||||
array_init(&rect_arr);
|
||||
|
@ -102,20 +102,8 @@ PHP_METHOD(FaceLandmarkDetection, __construct)
|
||||
|
||||
// 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); \
|
||||
PARSE_LONG_FROM_ARRAY(bounding_box_hash, side, \
|
||||
"Bounding box (second argument) is missing " #side "key", "Value of bounding box's (second argument) " #side " key is not long type")
|
||||
|
||||
PHP_METHOD(FaceLandmarkDetection, detect)
|
||||
{
|
||||
@ -134,8 +122,8 @@ PHP_METHOD(FaceLandmarkDetection, detect)
|
||||
// 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");
|
||||
if (bounding_box_num_elements < 4) {
|
||||
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, "Bounding box (second argument) needs to have at least 4 elements");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -158,16 +146,30 @@ PHP_METHOD(FaceLandmarkDetection, detect)
|
||||
// Each key is one part from shape. Value of each part is associative array of keys "x" and "y".
|
||||
//
|
||||
array_init(return_value);
|
||||
|
||||
zval rect_arr, parts_arr;
|
||||
array_init(&rect_arr);
|
||||
array_init(&parts_arr);
|
||||
|
||||
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);
|
||||
add_next_index_zval(&parts_arr, &part);
|
||||
}
|
||||
|
||||
const rectangle& r = shape.get_rect();
|
||||
add_assoc_long(&rect_arr, "left", r.left());
|
||||
add_assoc_long(&rect_arr, "top", r.top());
|
||||
add_assoc_long(&rect_arr, "right", r.right());
|
||||
add_assoc_long(&rect_arr, "bottom", r.bottom());
|
||||
|
||||
add_assoc_zval(return_value, "rect", &rect_arr);
|
||||
add_assoc_zval(return_value, "parts", &parts_arr);
|
||||
} catch (exception& e) {
|
||||
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, e.what());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
189
src/face_recognition.cc
Normal file
189
src/face_recognition.cc
Normal file
@ -0,0 +1,189 @@
|
||||
#include "../php_pdlib.h"
|
||||
#include "face_recognition.h"
|
||||
|
||||
#include <zend_exceptions.h>
|
||||
|
||||
#include <dlib/image_io.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace dlib;
|
||||
|
||||
static inline face_recognition *php_face_recognition_from_obj(zend_object *obj) {
|
||||
return (face_recognition*)((char*)(obj) - XtOffsetOf(face_recognition, std));
|
||||
}
|
||||
|
||||
#define Z_FACE_RECOGNITION_P(zv) php_face_recognition_from_obj(Z_OBJ_P((zv)))
|
||||
|
||||
PHP_METHOD(FaceRecognition, __construct)
|
||||
{
|
||||
char *sz_face_recognition_model_path;
|
||||
size_t face_recognition_model_path_len;
|
||||
|
||||
face_recognition *fr = Z_FACE_RECOGNITION_P(getThis());
|
||||
|
||||
if (NULL == fr) {
|
||||
php_error_docref(NULL TSRMLS_CC, E_ERROR, "Unable to find obj in FaceRecognition::__construct()");
|
||||
return;
|
||||
}
|
||||
|
||||
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s",
|
||||
&sz_face_recognition_model_path, &face_recognition_model_path_len) == FAILURE){
|
||||
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, "Unable to parse face_recognition_model_path");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
string face_recognition_model_path(sz_face_recognition_model_path, face_recognition_model_path_len);
|
||||
fr->net = new anet_type;
|
||||
deserialize(face_recognition_model_path) >> *(fr->net);
|
||||
} catch (exception& e) {
|
||||
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, e.what());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<matrix<rgb_pixel>> pdlib_jitter_image(
|
||||
const matrix<rgb_pixel>& img,
|
||||
const int num_jitters,
|
||||
dlib::rand& rnd) {
|
||||
std::vector<matrix<rgb_pixel>> crops;
|
||||
for (int i = 0; i < num_jitters; ++i)
|
||||
crops.push_back(dlib::jitter_image(img,rnd));
|
||||
return crops;
|
||||
}
|
||||
|
||||
|
||||
// Helper macro to automatically have parsing of "top"/"bottom"/"left"/"right"
|
||||
//
|
||||
#define PARSE_BOUNDING_BOX_EDGE(side) \
|
||||
PARSE_LONG_FROM_ARRAY(rect_hash, side, \
|
||||
"Shape's rect array is missing " #side "key", "Shape's rect array's " #side " key is not long type")
|
||||
|
||||
// Helper macro to parse "x"/"y"
|
||||
//
|
||||
#define PARSE_POINT(coord) \
|
||||
PARSE_LONG_FROM_ARRAY(part_hash, coord, \
|
||||
#coord " coordinate key is missing in parts array", #coord " coordinate key is not of long type")
|
||||
|
||||
|
||||
PHP_METHOD(FaceRecognition, computeDescriptor)
|
||||
{
|
||||
char *img_path;
|
||||
size_t img_path_len;
|
||||
zval *shape;
|
||||
long num_jitters = 1;
|
||||
|
||||
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa|l", &img_path, &img_path_len, &shape, &num_jitters) == FAILURE){
|
||||
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, "Unable to parse computeDescriptor arguments");
|
||||
return;
|
||||
}
|
||||
|
||||
HashTable *shape_hash = Z_ARRVAL_P(shape);
|
||||
uint32_t shape_hash_num_elements = zend_hash_num_elements(shape_hash);
|
||||
if (shape_hash_num_elements != 2) {
|
||||
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, "Shape (second argument) needs to have exactly 2 elements - keys \"rect\" and \"parts\"");
|
||||
return;
|
||||
}
|
||||
|
||||
zval *rect_zval = zend_hash_str_find(shape_hash, "rect", sizeof("rect")-1);
|
||||
if (rect_zval == nullptr) {
|
||||
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, "Shape (second argument) array needs to have \"rect\" key"); \
|
||||
return;
|
||||
}
|
||||
if (Z_TYPE_P(rect_zval) != IS_ARRAY) {
|
||||
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, "Value of shape's key \"rect\" must be array");
|
||||
return;
|
||||
}
|
||||
HashTable *rect_hash = Z_ARRVAL_P(rect_zval);
|
||||
PARSE_BOUNDING_BOX_EDGE(top)
|
||||
PARSE_BOUNDING_BOX_EDGE(bottom)
|
||||
PARSE_BOUNDING_BOX_EDGE(left)
|
||||
PARSE_BOUNDING_BOX_EDGE(right)
|
||||
rectangle rect(left, top, right, bottom);
|
||||
|
||||
|
||||
zval *parts_zval = zend_hash_str_find(shape_hash, "parts", sizeof("parts")-1);
|
||||
if (parts_zval == nullptr) {
|
||||
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, "Shape (second argument) array needs to have \"parts\" key"); \
|
||||
return;
|
||||
}
|
||||
if (Z_TYPE_P(parts_zval) != IS_ARRAY) {
|
||||
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, "Value of shape's key \"parts\" must be array");
|
||||
return;
|
||||
}
|
||||
HashTable *parts_hash = Z_ARRVAL_P(parts_zval);
|
||||
HashPosition parts_pos;
|
||||
uint32_t parts_count = zend_hash_num_elements(parts_hash);
|
||||
point parts_points[parts_count];
|
||||
|
||||
if ((parts_count != 5) && (parts_count != 68)) {
|
||||
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC,
|
||||
"The full_object_detection must use the iBUG 300W 68 point face landmark style or dlib's 5 point style");
|
||||
return;
|
||||
}
|
||||
|
||||
for (zend_hash_internal_pointer_reset_ex(parts_hash, &parts_pos);
|
||||
zend_hash_has_more_elements_ex(parts_hash, &parts_pos) == SUCCESS;
|
||||
zend_hash_move_forward_ex(parts_hash, &parts_pos)
|
||||
) {
|
||||
zend_string* str_index = {0};
|
||||
zend_ulong num_index;
|
||||
zval *part_zval = zend_hash_get_current_data_ex(parts_hash, &parts_pos);
|
||||
switch (zend_hash_get_current_key_ex(parts_hash, &str_index, &num_index, &parts_pos)) {
|
||||
case HASH_KEY_IS_LONG:
|
||||
if (Z_TYPE_P(part_zval) == IS_ARRAY)
|
||||
{
|
||||
HashTable *part_hash = Z_ARRVAL_P(part_zval);
|
||||
PARSE_POINT(x)
|
||||
PARSE_POINT(y)
|
||||
if (num_index > parts_count) {
|
||||
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, "Internal error, bad parsing of parts array");
|
||||
return;
|
||||
}
|
||||
parts_points[num_index] = point(x, y);
|
||||
} else {
|
||||
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, "Values from parts array must be arrays with \"x\" and \"y\" keys");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case HASH_KEY_IS_STRING:
|
||||
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, "Parts array must be indexed and it contains string keys");
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<point> parts;
|
||||
for (unsigned int i = 0; i < parts_count; i++) {
|
||||
parts.push_back(parts_points[i]);
|
||||
}
|
||||
|
||||
try {
|
||||
face_recognition *fr = Z_FACE_RECOGNITION_P(getThis());
|
||||
full_object_detection fod(rect, parts);
|
||||
matrix<rgb_pixel> img;
|
||||
load_image(img, img_path);
|
||||
|
||||
std::vector<chip_details> dets;
|
||||
dets.push_back(get_face_chip_details(fod, 150, 0.25));
|
||||
dlib::array<matrix<rgb_pixel>> face_chips;
|
||||
extract_image_chips(img, dets, face_chips);
|
||||
|
||||
array_init(return_value);
|
||||
matrix<float,0,1> face_descriptor;
|
||||
if (num_jitters <= 1) {
|
||||
std::vector<matrix<float,0,1>> face_descriptors = fr->net->operator()(face_chips, 16);
|
||||
face_descriptor = face_descriptors[0];
|
||||
} else {
|
||||
matrix<rgb_pixel>& face_chip = face_chips[0];
|
||||
face_descriptor = mean(mat(fr->net->operator()(pdlib_jitter_image(face_chip, num_jitters, fr->rnd), 16)));
|
||||
}
|
||||
|
||||
for (auto& d : face_descriptor) {
|
||||
add_next_index_double(return_value, d);
|
||||
}
|
||||
} catch (exception& e) {
|
||||
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, e.what());
|
||||
return;
|
||||
}
|
||||
}
|
58
src/face_recognition.h
Normal file
58
src/face_recognition.h
Normal file
@ -0,0 +1,58 @@
|
||||
//
|
||||
// Created by branko at kokanovic dot org on 2018/8/26.
|
||||
//
|
||||
|
||||
#ifndef PHP_DLIB_FACE_RECOGNITION_H
|
||||
#define PHP_DLIB_FACE_RECOGNITION_H
|
||||
|
||||
#include <dlib/dnn.h>
|
||||
|
||||
using namespace dlib;
|
||||
|
||||
template <template <int,template<typename>class,int,typename> class block, int N, template<typename>class BN, typename SUBNET>
|
||||
using residual = add_prev1<block<N,BN,1,tag1<SUBNET>>>;
|
||||
|
||||
template <template <int,template<typename>class,int,typename> class block, int N, template<typename>class BN, typename SUBNET>
|
||||
using residual_down = add_prev2<avg_pool<2,2,2,2,skip1<tag2<block<N,BN,2,tag1<SUBNET>>>>>>;
|
||||
|
||||
template <int N, template <typename> class BN, int stride, typename SUBNET>
|
||||
using block = BN<con<N,3,3,1,1,relu<BN<con<N,3,3,stride,stride,SUBNET>>>>>;
|
||||
|
||||
template <int N, typename SUBNET> using ares = relu<residual<block,N,affine,SUBNET>>;
|
||||
template <int N, typename SUBNET> using ares_down = relu<residual_down<block,N,affine,SUBNET>>;
|
||||
|
||||
template <typename SUBNET> using alevel0 = ares_down<256,SUBNET>;
|
||||
template <typename SUBNET> using alevel1 = ares<256,ares<256,ares_down<256,SUBNET>>>;
|
||||
template <typename SUBNET> using alevel2 = ares<128,ares<128,ares_down<128,SUBNET>>>;
|
||||
template <typename SUBNET> using alevel3 = ares<64,ares<64,ares<64,ares_down<64,SUBNET>>>>;
|
||||
template <typename SUBNET> using alevel4 = ares<32,ares<32,ares<32,SUBNET>>>;
|
||||
|
||||
using anet_type = loss_metric<fc_no_bias<128,avg_pool_everything<
|
||||
alevel0<
|
||||
alevel1<
|
||||
alevel2<
|
||||
alevel3<
|
||||
alevel4<
|
||||
max_pool<3,3,2,2,relu<affine<con<32,7,7,2,2,
|
||||
input_rgb_image_sized<150>
|
||||
>>>>>>>>>>>>;
|
||||
|
||||
typedef struct _face_recognition {
|
||||
anet_type *net;
|
||||
zend_object std;
|
||||
dlib::rand rnd;
|
||||
} face_recognition;
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(face_recognition_ctor_arginfo, 0, 0, 1)
|
||||
ZEND_ARG_INFO(0, face_recognition_model_path)
|
||||
ZEND_END_ARG_INFO()
|
||||
PHP_METHOD(FaceRecognition, __construct);
|
||||
|
||||
ZEND_BEGIN_ARG_INFO_EX(face_recognition_compute_descriptor_arginfo, 0, 0, 3)
|
||||
ZEND_ARG_INFO(0, img_path)
|
||||
ZEND_ARG_INFO(0, landmarks)
|
||||
ZEND_ARG_INFO(0, num_jitters)
|
||||
ZEND_END_ARG_INFO()
|
||||
PHP_METHOD(FaceRecognition, computeDescriptor);
|
||||
|
||||
#endif //PHP_DLIB_FACE_RECOGNITION_H
|
15
tests/face_recognition_ctor_error.phpt
Normal file
15
tests/face_recognition_ctor_error.phpt
Normal file
@ -0,0 +1,15 @@
|
||||
--TEST--
|
||||
Testing FaceRecognition constructor without arguments
|
||||
--SKIPIF--
|
||||
<?php if (!extension_loaded("pdlib")) print "skip"; ?>
|
||||
--FILE--
|
||||
<?php
|
||||
try {
|
||||
new FaceRecognition();
|
||||
} catch (Exception $e) {
|
||||
var_dump($e->getMessage());
|
||||
}
|
||||
?>
|
||||
--EXPECT--
|
||||
Warning: FaceRecognition::__construct() expects exactly 1 parameter, 0 given in /home/branko/pdlib/tests/face_recognition_ctor_error.php on line 3
|
||||
string(43) "Unable to parse face_recognition_model_path"
|
60
tests/integration_face_recognition.phpt
Normal file
60
tests/integration_face_recognition.phpt
Normal file
@ -0,0 +1,60 @@
|
||||
--TEST--
|
||||
Full test for face recognition - download models, detect faces, landmark detection and face recognition.
|
||||
--SKIPIF--
|
||||
<?php if (!extension_loaded("pdlib") || (function_exists("bzopen"))) print "skip"; ?>
|
||||
--FILE--
|
||||
<?php
|
||||
$models = array(
|
||||
"detection" => array("uri"=>"http://dlib.net/files/mmod_human_face_detector.dat.bz2"),
|
||||
"prediction" => array("uri"=>"http://dlib.net/files/shape_predictor_5_face_landmarks.dat.bz2"),
|
||||
"recognition" => array("uri"=>"http://dlib.net/files/dlib_face_recognition_resnet_model_v1.dat.bz2")
|
||||
);
|
||||
|
||||
// Check if there are models in local tmp. Download them if not (lazy caching).
|
||||
//
|
||||
foreach ($models as $modelName => $modelBag) {
|
||||
printf("Processing %s model\n", $modelName);
|
||||
$bz2_filename = array_values(array_slice(explode("/", $modelBag["uri"]), -1))[0];
|
||||
$temp_bz2_file = sys_get_temp_dir() . "/" . $bz2_filename;
|
||||
$dat_filename = array_values(array_slice(explode(".", $bz2_filename), 0))[0] . ".dat";
|
||||
$temp_dat_file = sys_get_temp_dir() . "/" . $dat_filename;
|
||||
$models[$modelName]["local_path"] = $temp_dat_file;
|
||||
|
||||
if (file_exists($temp_dat_file)) {
|
||||
continue;
|
||||
}
|
||||
file_put_contents($temp_bz2_file, fopen($modelBag["uri"], 'r'));
|
||||
$bz = bzopen($temp_bz2_file, "r");
|
||||
$decompressed_file = "";
|
||||
while (!feof($bz)) {
|
||||
$decompressed_file .= bzread($bz, 4096);
|
||||
}
|
||||
bzclose($bz);
|
||||
|
||||
file_put_contents($temp_dat_file, $decompressed_file);
|
||||
}
|
||||
|
||||
printf("Detection\n");
|
||||
$fd = new CnnFaceDetection($models["detection"]["local_path"]);
|
||||
$detected_faces = $fd->detect(__DIR__ . "/lenna.jpg");
|
||||
printf("Faces found = %d\n", count($detected_faces));
|
||||
foreach($detected_faces as $index => $detected_face) {
|
||||
printf("Face[%d] in bounding box (left=%d, top=%d, right=%d, bottom=%d)\n", $index,
|
||||
$detected_face["left"], $detected_face["top"], $detected_face["right"], $detected_face["bottom"]);
|
||||
$fld = new FaceLandmarkDetection($models["prediction"]["local_path"]);
|
||||
$landmarks = $fld->detect(__DIR__ . "/lenna.jpg", $detected_face);
|
||||
printf("Since we used model with 5 shape predictions, we found %d landmark parts\n", count($landmarks["parts"]));
|
||||
$fr = new FaceRecognition($models["recognition"]["local_path"]);
|
||||
$descriptor = $fr->computeDescriptor(__DIR__ . "/lenna.jpg", $landmarks);
|
||||
printf("Descriptor is vector of %d dimensions\n", count($descriptor));
|
||||
}
|
||||
?>
|
||||
--EXPECT--
|
||||
Processing detection model
|
||||
Processing prediction model
|
||||
Processing recognition model
|
||||
Detection
|
||||
Faces found = 1
|
||||
Face[0] in bounding box (left=187, top=186, right=357, bottom=355)
|
||||
Since we used model with 5 shape predictions, we found 5 landmark parts
|
||||
Descriptor is vector of 128 dimensions
|
BIN
tests/lenna.jpg
Executable file
BIN
tests/lenna.jpg
Executable file
Binary file not shown.
After Width: | Height: | Size: 460 KiB |
Loading…
Reference in New Issue
Block a user