PHP face detection class

Face detection is a computer technology that determines the locations and sizes of human faces in arbitrary (digital) images.

It detects facial features and ignores anything else, such as buildings, trees and bodies.

(source: Wikipedia)

There is so much to say about face detection and all its algorithms… I am planning a step by step tutorial about every branch of object detection in digital images, but first I want to publish Maurice Svay‘s PHP class.

Maurice, in his blog svay.com, explains he was looking for a face detection script for PHP, but wasn’t able to find one working without OpenCV (Open Source Computer Vision), an opensource lib that was originally developed by Intel.

OpenCV seems to perform well but you need to be able to install it on your server.

So he coded his own pure PHP solution, that does not require any library to be installed on the server.

And, obviously, that can be easily ported into any language. His class itself has been translated from a javascript code that actually is no longer available online

So this is the class:

<?php
// 
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.     
// 
// @Author Karthik Tharavaad 
//         karthik_tharavaad@yahoo.com
// @Contributor Maurice Svay
//              maurice@svay.Com
 
class Face_Detector {
 
    protected $detection_data;
    protected $canvas;
    protected $face;
    private $reduced_canvas;
 
    public function __construct($detection_file = 'detection.dat') {
        if (is_file($detection_file)) {
            $this->detection_data = unserialize(file_get_contents($detection_file));
        } else {
            throw new Exception("Couldn't load detection data");
        }
        //$this->detection_data = json_decode(file_get_contents('data.js'));
    }
 
    public function face_detect($file) {
        if (!is_file($file)) {
            throw new Exception("Can not load $file");
        }
 
        $this->canvas = imagecreatefromjpeg($file);
        $im_width = imagesx($this->canvas);
        $im_height = imagesy($this->canvas);
 
        //Resample before detection?
        $ratio = 0;
        $diff_width = 320 - $im_width;
        $diff_height = 240 - $im_height;
        if ($diff_width > $diff_height) {
            $ratio = $im_width / 320;
        } else {
            $ratio = $im_height / 240;
        }
 
        if ($ratio != 0) {
            $this->reduced_canvas = imagecreatetruecolor($im_width / $ratio, $im_height / $ratio);
            imagecopyresampled($this->reduced_canvas, $this->canvas, 0, 0, 0, 0, $im_width / $ratio, $im_height / $ratio, $im_width, $im_height);
 
            $stats = $this->get_img_stats($this->reduced_canvas);
            $this->face = $this->do_detect_greedy_big_to_small($stats['ii'], $stats['ii2'], $stats['width'], $stats['height']);
            $this->face['x'] *= $ratio;
            $this->face['y'] *= $ratio;
            $this->face['w'] *= $ratio;
        } else {
            $stats = $this->get_img_stats($this->canvas);
            $this->face = $this->do_detect_greedy_big_to_small($stats['ii'], $stats['ii2'], $stats['width'], $stats['height']);
        }
        return ($this->face['w'] > 0);
    }
 
 
    public function toJpeg() {
        $color = imagecolorallocate($this->canvas, 255, 0, 0); //red
        imagerectangle($this->canvas, $this->face['x'], $this->face['y'], $this->face['x']+$this->face['w'], $this->face['y']+ $this->face['w'], $color);
        header('Content-type: image/jpeg');
        imagejpeg($this->canvas);
    }
 
    public function toJson() {
        return "{'x':" . $this->face['x'] . ", 'y':" . $this->face['y'] . ", 'w':" . $this->face['w'] . "}";
    }
 
    public function getFace() {
        return $this->face;
    }
 
    protected function get_img_stats($canvas){
        $image_width = imagesx($canvas);
        $image_height = imagesy($canvas);     
        $iis =  $this->compute_ii($canvas, $image_width, $image_height);
        return array(
            'width' => $image_width,
            'height' => $image_height,
            'ii' => $iis['ii'],
            'ii2' => $iis['ii2']
        );         
    }
 
    protected function compute_ii($canvas, $image_width, $image_height ){
        $ii_w = $image_width+1;
        $ii_h = $image_height+1;
        $ii = array();
        $ii2 = array();      
 
        for($i=0; $i<$ii_w; $i++ ){
            $ii[$i] = 0;
            $ii2[$i] = 0;
        }                        
 
        for($i=1; $i<$ii_w; $i++ ){  
            $ii[$i*$ii_w] = 0;       
            $ii2[$i*$ii_w] = 0; 
            $rowsum = 0;
            $rowsum2 = 0;
            for($j=1; $j<$ii_h; $j++ ){
                $rgb = ImageColorAt($canvas, $j, $i);
                $red = ($rgb >> 16) & 0xFF;
                $green = ($rgb >> 8) & 0xFF;
                $blue = $rgb & 0xFF;
                $grey = ( 0.2989*$red + 0.587*$green + 0.114*$blue )>>0;  // this is what matlab uses
                $rowsum += $grey;
                $rowsum2 += $grey*$grey;
 
                $ii_above = ($i-1)*$ii_w + $j;
                $ii_this = $i*$ii_w + $j;
 
                $ii[$ii_this] = $ii[$ii_above] + $rowsum;
                $ii2[$ii_this] = $ii2[$ii_above] + $rowsum2;
            }
        }
        return array('ii'=>$ii, 'ii2' => $ii2);
    }
 
    protected function do_detect_greedy_big_to_small( $ii, $ii2, $width, $height ){
        $s_w = $width/20.0;
        $s_h = $height/20.0;
        $start_scale = $s_h < $s_w ? $s_h : $s_w;
        $scale_update = 1 / 1.2;
        for($scale = $start_scale; $scale > 1; $scale *= $scale_update ){
            $w = (20*$scale) >> 0;
            $endx = $width - $w - 1;
            $endy = $height - $w - 1;
            $step = max( $scale, 2 ) >> 0;
            $inv_area = 1 / ($w*$w);
            for($y = 0; $y < $endy ; $y += $step ){
                for($x = 0; $x < $endx ; $x += $step ){
                    $passed = $this->detect_on_sub_image( $x, $y, $scale, $ii, $ii2, $w, $width+1, $inv_area);
                    if( $passed ) {
                        return array('x'=>$x, 'y'=>$y, 'w'=>$w);
                    }
                } // end x
            } // end y
        }  // end scale
        return null;
    }
 
    protected function detect_on_sub_image( $x, $y, $scale, $ii, $ii2, $w, $iiw, $inv_area){
        $mean = ( $ii[($y+$w)*$iiw + $x + $w] + $ii[$y*$iiw+$x] - $ii[($y+$w)*$iiw+$x] - $ii[$y*$iiw+$x+$w]  )*$inv_area;
        $vnorm =  ( $ii2[($y+$w)*$iiw + $x + $w] + $ii2[$y*$iiw+$x] - $ii2[($y+$w)*$iiw+$x] - $ii2[$y*$iiw+$x+$w]  )*$inv_area - ($mean*$mean);    
        $vnorm = $vnorm > 1 ? sqrt($vnorm) : 1;
 
        $passed = true;
        for($i_stage = 0; $i_stage < count($this->detection_data); $i_stage++ ){
            $stage = $this->detection_data[$i_stage];  
            $trees = $stage[0];  
 
            $stage_thresh = $stage[1];
            $stage_sum = 0;
 
            for($i_tree = 0; $i_tree < count($trees); $i_tree++ ){
                $tree = $trees[$i_tree];
                $current_node = $tree[0];    
                $tree_sum = 0;
                while( $current_node != null ){
                    $vals = $current_node[0];
                    $node_thresh = $vals[0];
                    $leftval = $vals[1];
                    $rightval = $vals[2];
                    $leftidx = $vals[3];
                    $rightidx = $vals[4];
                    $rects = $current_node[1];
 
                    $rect_sum = 0;
                    for( $i_rect = 0; $i_rect < count($rects); $i_rect++ ){
                        $s = $scale;
                        $rect = $rects[$i_rect];
                        $rx = ($rect[0]*$s+$x)>>0;
                        $ry = ($rect[1]*$s+$y)>>0;
                        $rw = ($rect[2]*$s)>>0;  
                        $rh = ($rect[3]*$s)>>0;
                        $wt = $rect[4];
 
                        $r_sum = ( $ii[($ry+$rh)*$iiw + $rx + $rw] + $ii[$ry*$iiw+$rx] - $ii[($ry+$rh)*$iiw+$rx] - $ii[$ry*$iiw+$rx+$rw] )*$wt;
                        $rect_sum += $r_sum;
                    } 
 
                    $rect_sum *= $inv_area;
 
                    $current_node = null;
                    if( $rect_sum >= $node_thresh*$vnorm ){
                        if( $rightidx == -1 ) 
                            $tree_sum = $rightval;
                        else
                            $current_node = $tree[$rightidx];
                    } else {
                        if( $leftidx == -1 )
                            $tree_sum = $leftval;
                        else
                            $current_node = $tree[$leftidx];
                    }
                } 
                $stage_sum += $tree_sum;
            } 
            if( $stage_sum < $stage_thresh ){
                return false;
            }
        } 
        return true;
    }
}

Using it is very easy, just write

$detector = new Face_Detector('detection.dat');
$detector->face_detect('your_file.jpg');
$detector->toJpeg();

once you unpacked and included detection.dat and the image in the same path as your class.

Do you want to see a result?

This was generated by the script, just changing the frame color from red to green, to make it more visible.

The class does not work that well on every photo, but it’s a good start for a journey into face detection

Rate this post: 1 Star2 Stars3 Stars4 Stars5 Stars (25 votes, average: 4.88 out of 5)
Loading ... Loading ...
Be my fan on Facebook and follow me on Twitter! Exclusive content for my Facebook fans and Twitter followers

This post has 28 comments

  1. Martti Laine

    on July 6, 2010 at 6:47 pm

    Holy sh*t. Never even thought about detecting faces in php. Thumbs up!

  2. Martti Laine

    on July 6, 2010 at 6:48 pm

    Sorry for my bad language.

    This article proves it, we’re on 21st century.

  3. Thomas

    on July 7, 2010 at 1:31 am

    So, what are the logics that make it work?

  4. mario

    on July 7, 2010 at 9:33 am

    face.com had release a API for face detection, you can even use the code to detect the location of eye, nose and mouth

  5. Mal kurz rundgeschaut… #24 | Braekling.de

    on July 11, 2010 at 9:41 am

    [...] PHP face detection class – gut erkannt mit Emanuele Feronato. [...]

  6. bossoi

    on July 13, 2010 at 7:56 pm

    I have a question… why are .dat file for?

  7. ammii*

    on August 2, 2010 at 4:44 pm

    Absolutely yes!! I’ve never ever thought about PHP for detecting faces!!!
    Thank you very much :))

  8. Marcius

    on August 9, 2010 at 12:23 pm

    Very good, congratuloations and tks for your contribe

  9. prince

    on August 16, 2010 at 9:39 am

    Amazing might use it in my next project :)

  10. Zeeshan Lalani

    on August 16, 2010 at 10:41 am

    very nice.. thanks..

  11. Face Detection Using PHP | blogfreakz.com

    on August 16, 2010 at 12:18 pm

    [...] Source: http://www.emanueleferonato.com/2010/07/06/php-face-detection-class/ [...]

  12. Shashi

    on August 16, 2010 at 1:22 pm

    Thanks a lot for this great post… i have been wondering whether it would be possible to do these kind of things with php… hats off php !

  13. datshay

    on August 16, 2010 at 2:24 pm

    Amazing….. No words to say ….
    Thumbs up !

  14. Karl Roos

    on August 16, 2010 at 10:10 pm

    This is amazing! Never thought you’d be able to do this with PHP!

  15. vcrack

    on August 18, 2010 at 2:45 pm

    how long your execution time to do this?

  16. Vincent

    on August 19, 2010 at 1:58 pm

    I’ve never tought that PHP could do this thing too. It’s awesome

  17. harika

    on August 22, 2010 at 4:32 pm

    thank you:D

  18. Proger

    on August 29, 2010 at 9:02 am

    Thanks, cool script.
    However, it is not always working correctly, here’s an example:
    http://programming.su/sites/programming.su/files/demo/face-detector/putin.jpg

  19. Detectar una cara en una foto usando PHP | Blog personal de Brian Urban

    on September 8, 2010 at 2:28 am

    [...] Via Emanuele Feronato [...]

  20. Face detection using PHP

    on November 20, 2010 at 12:00 pm

    [...] Without Open CV you can apply face detection using the PHP class mentioned at this blog [...]

  21. long.vu

    on January 13, 2011 at 6:23 pm

    I saw that happend error in function imagecolorat and can not work

  22. PHP and Jquery face detection scripts « Webania.net

    on January 21, 2011 at 11:52 pm

    [...] Download page [...]

  23. Dmitry

    on January 25, 2011 at 5:39 am

    Doesn`t detect multiple and small faces. How’d you tweak the code to do that?

  24. tumaji

    on May 6, 2011 at 2:09 pm

    Thank you, I looking for this script.

  25. tejas tank

    on December 2, 2011 at 7:44 am

    Here detection.dat is which kind of file. is a jpg or movie or other things…

  26. Mohd

    on December 28, 2011 at 3:13 pm

    Hay @Proger..

    At least it detected the face of Putin’s care :) I think PHP can’t recognize Putin.. same of the rest of us, this is logic.

  27. mrtaza

    on January 19, 2012 at 7:10 pm

    http://bit.ly/yZ7UxW

  28. mrtaza

    on January 19, 2012 at 7:12 pm

    i have used the above class in this sample
    http://bit.ly/yZ7UxW
    works in most cases.