Support for raw chinese_whispers

This commit introduces chinese_whispers method without any "helpers" on
PHP side. Users needs to take care of everything (building edges from
128D face chip vector, for example), but this is exposed for people
that need low-level call and want to calculate distances and build edges
in PHP directly.
This commit is contained in:
Branko Kokanovic 2018-07-19 19:12:18 +02:00
parent b17cd7c945
commit 3f60326661
11 changed files with 254 additions and 0 deletions

View File

@ -71,9 +71,28 @@ var_dump($landmarks);
```
#### chinese whisers
Provides raw access to dlib's `chinese_whispers` function.
Client need to build and provide edges. Edges are provided
as numeric array. Each element of this array should also be
numeric array with 2 elements of long type.
Returned value is also numeric array, containing obtained labels.
```php
<?php
// This example will cluster nodes 0 and 1, but would leave 2 out.
// $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] 4.Deep Learning Face Detection
- [x] 5. Raw chinese_whispers

View File

@ -25,6 +25,7 @@ if test "$PHP_PDLIB" != "no"; then
PHP_SUBST(PDLIB_SHARED_LIBADD)
pdlib_src_files="pdlib.cc \
src/chinese_whispers.cc \
src/face_detection.cc \
src/face_landmark_detection.cc \
src/cnn_face_detection.cc"

View File

@ -28,6 +28,7 @@ extern "C" {
#include "ext/standard/info.h"
}
#include "php_pdlib.h"
#include "src/chinese_whispers.h"
#include "src/face_detection.h"
#include "src/cnn_face_detection.h"
#include "src/face_landmark_detection.h"
@ -187,6 +188,7 @@ PHP_MINFO_FUNCTION(pdlib)
*/
const zend_function_entry pdlib_functions[] = {
PHP_FE(confirm_pdlib_compiled, NULL)
PHP_FE(dlib_chinese_whispers, dlib_chinese_whispers_arginfo)
PHP_FE(dlib_face_detection, dlib_face_detection_arginfo)
PHP_FE(dlib_face_landmark_detection, dlib_face_landmark_detection_arginfo)
PHP_FE_END /* Must be the last line in pdlib_functions[] */

104
src/chinese_whispers.cc Normal file
View File

@ -0,0 +1,104 @@
#include "../php_pdlib.h"
#include "chinese_whispers.h"
#include <zend_exceptions.h>
#include <dlib/clustering.h>
#include <iostream>
using namespace dlib;
using namespace std;
PHP_FUNCTION(dlib_chinese_whispers)
{
zval *edges_arg;
HashTable *edges_arg_hash;
std::vector<sample_pair> edges;
std::vector<unsigned long> labels;
if(zend_parse_parameters(ZEND_NUM_ARGS(), "a", &edges_arg) == FAILURE){
zend_throw_exception_ex(
zend_ce_exception,
0 TSRMLS_CC,
"Unable to parse edges in dlib_chinese_whispers");
return;
}
edges_arg_hash = Z_ARRVAL_P(edges_arg);
try {
HashPosition pos;
zval *edge;
HashTable *edge_hash;
// Iterate for all given edges, check if they are valid and put them to edges
//
for (
zend_hash_internal_pointer_reset_ex(edges_arg_hash, &pos);
(edge = zend_hash_get_current_data_ex(edges_arg_hash, &pos)) != nullptr;
zend_hash_move_forward_ex(edges_arg_hash, &pos)) {
// Check that each given edge is actually array
//
if (Z_TYPE_P(edge) != IS_ARRAY) {
zend_throw_exception_ex(
zend_ce_exception,
0 TSRMLS_CC,
"Each edge provided in array needs to be numeric array of 2 elements");
return;
}
edge_hash = Z_ARRVAL_P(edge);
// Check that there are two elements in this edge
//
if (zend_hash_num_elements(edge_hash) != 2) {
zend_throw_exception_ex(
zend_ce_exception,
0 TSRMLS_CC,
"Edges need to contain exactly two elements");
return;
}
// Check that this is regular array with integer keys
//
if (!zend_hash_index_exists(edge_hash, 0) ||
!zend_hash_index_exists(edge_hash, 1)) {
zend_throw_exception_ex(
zend_ce_exception,
0 TSRMLS_CC,
"Edge should be numeric array with integer keys");
return;
}
zval *elem_i = zend_hash_index_find(edge_hash, 0);
zval *elem_j = zend_hash_index_find(edge_hash, 1);
// Check that both elements in array are longs
if ((Z_TYPE_P(elem_i) != IS_LONG) || (Z_TYPE_P(elem_j) != IS_LONG)) {
zend_throw_exception_ex(
zend_ce_exception,
0 TSRMLS_CC,
"Both elements in each edge must be of long type");
return;
}
// Finally, put extracted elements to edges
edges.push_back(sample_pair(Z_LVAL_P(elem_i), Z_LVAL_P(elem_j)));
}
chinese_whispers(edges, labels);
// Preparing and generating response array containing labels
//
array_init(return_value);
for (auto label = labels.begin(); label != labels.end(); label++) {
add_next_index_long(return_value, *label);
}
} catch (exception& e)
{
zend_throw_exception_ex(zend_ce_exception, 0 TSRMLS_CC, e.what());
return;
}
}

13
src/chinese_whispers.h Normal file
View File

@ -0,0 +1,13 @@
//
// Created by branko at kokanovic dot org on 2018/7/19.
//
#ifndef PHP_DLIB_CHINESE_WHISPERS_H
#define PHP_DLIB_CHINESE_WHISPERS_H
ZEND_BEGIN_ARG_INFO_EX(dlib_chinese_whispers_arginfo, 0, 0, 1)
ZEND_ARG_INFO(0, edges)
ZEND_END_ARG_INFO()
PHP_FUNCTION(dlib_chinese_whispers);
#endif //PHP_DLIB_CHINESE_WHISPERS_H

View File

@ -0,0 +1,27 @@
--TEST--
Basic tests for chinese_whispers
--SKIPIF--
<?php if (!extension_loaded("pdlib")) print "skip"; ?>
--FILE--
<?php
var_dump(dlib_chinese_whispers([[0,0]])); // Case with only one node
var_dump(dlib_chinese_whispers([[0,0], [1,1]])); // Case with two separate nodes
var_dump(dlib_chinese_whispers([[0,0], [0,1], [1,0], [1,1]])); // Case with two connected nodes
?>
--EXPECT--
array(1) {
[0]=>
int(0)
}
array(2) {
[0]=>
int(0)
[1]=>
int(1)
}
array(2) {
[0]=>
int(0)
[1]=>
int(0)
}

View File

@ -0,0 +1,13 @@
--TEST--
Edge given in edges array for chinese_whispers functions is associative array
--SKIPIF--
<?php if (!extension_loaded("pdlib")) print "skip"; ?>
--FILE--
<?php
try {
dlib_chinese_whispers([[0,0], ["foo"=>0, "bar"=>1]]);
} catch (Exception $e) {
var_dump($e->getMessage());
}
--EXPECT--
string(46) "Edge should be numeric array with integer keys"

View File

@ -0,0 +1,19 @@
--TEST--
Edge elements given in edges array for chinese_whispers functions are not of long type
--SKIPIF--
<?php if (!extension_loaded("pdlib")) print "skip"; ?>
--FILE--
<?php
try {
dlib_chinese_whispers([[0,0], [1, "foo"]]);
} catch (Exception $e) {
var_dump($e->getMessage());
}
try {
dlib_chinese_whispers([[0,0], [1, 1.1]]);
} catch (Exception $e) {
var_dump($e->getMessage());
}
--EXPECT--
string(47) "Both elements in each edge must be of long type"
string(47) "Both elements in each edge must be of long type"

View File

@ -0,0 +1,20 @@
--TEST--
Edge given in edges array for chinese_whispers functions is not having all values to be arrays with 2 elements
--SKIPIF--
<?php if (!extension_loaded("pdlib")) print "skip"; ?>
--FILE--
<?php
try {
dlib_chinese_whispers([[0,0], [1]]);
} catch (Exception $e) {
var_dump($e->getMessage());
}
try {
dlib_chinese_whispers([[0,0], [1,1,1]]);
} catch (Exception $e) {
var_dump($e->getMessage());
}
?>
--EXPECT--
string(42) "Edges need to contain exactly two elements"
string(42) "Edges need to contain exactly two elements"

View File

@ -0,0 +1,21 @@
--TEST--
Edge given in edges array is not array for chinese_whispers functions
--SKIPIF--
<?php if (!extension_loaded("pdlib")) print "skip"; ?>
--FILE--
<?php
try {
dlib_chinese_whispers([[0,0], "foo"]);
} catch (Exception $e) {
var_dump($e->getMessage());
}
try {
dlib_chinese_whispers([[0,0], 1]);
} catch (Exception $e) {
var_dump($e->getMessage());
}
?>
--EXPECT--
string(67) "Each edge provided in array needs to be numeric array of 2 elements"
string(67) "Each edge provided in array needs to be numeric array of 2 elements"

View File

@ -0,0 +1,15 @@
--TEST--
Args given to chinese_whispers functions is not correct
--SKIPIF--
<?php if (!extension_loaded("pdlib")) print "skip"; ?>
--FILE--
<?php
try {
dlib_chinese_whispers("foo");
} catch (Exception $e) {
var_dump($e->getMessage());
}
?>
--EXPECT--
Warning: dlib_chinese_whispers() expects parameter 1 to be array, string given in /home/branko/pdlib/tests/chinese_whispers_wrong_arg_type_error.php on line 3
string(46) "Unable to parse edges in dlib_chinese_whispers"