{"id":2046,"date":"2009-06-19T12:17:00","date_gmt":"2009-06-19T12:17:00","guid":{"rendered":"http:\/\/svay.com\/blog\/?p=2046"},"modified":"2012-04-24T21:57:19","modified_gmt":"2012-04-24T19:57:19","slug":"face-detection-in-pure-php-without-opencv","status":"publish","type":"post","link":"https:\/\/svay.com\/blog\/face-detection-in-pure-php-without-opencv\/","title":{"rendered":"Face detection in pure PHP (without OpenCV)"},"content":{"rendered":"<p><em>Une r\u00e9sum\u00e9 en fran\u00e7ais est disponible en fin d&#8217;article.<\/em><\/p>\n<p>Lately, I&#8217;ve been looking for ways to detect faces in photos with PHP. Nowadays, face detection is built in many consumer products (camera obviously, but also Google and iPhoto), and seems to be a pretty common job. So I expected to find many solutions for doing it with PHP. Surprisingly, the only one I could find is <a href=\"http:\/\/opencv.willowgarage.com\/wiki\/\" hreflang=\"en\">OpenCV<\/a>, 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. In my case, I wanted to have a pure PHP solution, so it can work with most hosts.<\/p>\n<h3>Learning about face detection<\/h3>\n<p>So I started to think about implementing it myself. I read a articles, scientific papers, etc. The website <a href=\"http:\/\/www.facedetection.com\/\" hreflang=\"en\">http:\/\/www.facedetection.com\/<\/a> is a great resource by the way. From this short research, I learnt that one of the most popular solutions is to use Viola Jones training with a Haar classifier. Very informative, but tedious to implement. I&#8217;m <strong>lazy<\/strong>, you know.<\/p>\n<h3>Always look at what others are doing<\/h3>\n<p>Then I looked for existing implementations in other languages. Let&#8217;s say Flash and Javascript. With Canvas, Javascript developers will certainly look at what flash developers do. There seem to be a few libs for face detection in AS3. They even work in real time. Pretty cool. I kept searching and finally found a canvas+javascript implementation of face detection at <a href=\"http:\/\/blog.kpicturebooth.com\/?p=8\" hreflang=\"en\">http:\/\/blog.kpicturebooth.com\/?p=8<\/a>. The code looked fairly compact and simple. Shouldn&#8217;t be hard to port to PHP.<\/p>\n<h3>The code<\/h3>\n<p><strong>Update: the code has moved to\u00a0<a href=\"https:\/\/github.com\/mauricesvay\/php-facedetection\">https:\/\/github.com\/mauricesvay\/php-facedetection<\/a><\/strong><\/p>\n<p>Once the code converted to PHP, here&#8217;s the result:<\/p>\n<pre> &lt;?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-&gt;detection_data = unserialize(file_get_contents($detection_file));         } else {             throw new Exception(\"Couldn't load detection data\");         }         \/\/$this-&gt;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-&gt;canvas = imagecreatefromjpeg($file);         $im_width = imagesx($this-&gt;canvas);         $im_height = imagesy($this-&gt;canvas);         \/\/Resample before detection?         $ratio = 0;         $diff_width = 320 - $im_width;         $diff_height = 240 - $im_height;         if ($diff_width &gt; $diff_height) {             $ratio = $im_width \/ 320;         } else {             $ratio = $im_height \/ 240;         }         if ($ratio != 0) {             $this-&gt;reduced_canvas = imagecreatetruecolor($im_width \/ $ratio, $im_height \/ $ratio);             imagecopyresampled($this-&gt;reduced_canvas, $this-&gt;canvas, 0, 0, 0, 0, $im_width \/ $ratio, $im_height \/ $ratio, $im_width, $im_height);             $stats = $this-&gt;get_img_stats($this-&gt;reduced_canvas);             $this-&gt;face = $this-&gt;do_detect_greedy_big_to_small($stats['ii'], $stats['ii2'], $stats['width'], $stats['height']);             $this-&gt;face['x'] *= $ratio;             $this-&gt;face['y'] *= $ratio;             $this-&gt;face['w'] *= $ratio;         } else {             $stats = $this-&gt;get_img_stats($this-&gt;canvas);             $this-&gt;face = $this-&gt;do_detect_greedy_big_to_small($stats['ii'], $stats['ii2'], $stats['width'], $stats['height']);         }         return ($this-&gt;face['w'] &gt; 0);     }     public function toJpeg() {         $color = imagecolorallocate($this-&gt;canvas, 255, 0, 0); \/\/red         imagerectangle($this-&gt;canvas, $this-&gt;face['x'], $this-&gt;face['y'], $this-&gt;face['x']+$this-&gt;face['w'], $this-&gt;face['y']+ $this-&gt;face['w'], $color);         header('Content-type: image\/jpeg');         imagejpeg($this-&gt;canvas);     }     public function toJson() {         return \"{'x':\" . $this-&gt;face['x'] . \", 'y':\" . $this-&gt;face['y'] . \", 'w':\" . $this-&gt;face['w'] . \"}\";     }     public function getFace() {         return $this-&gt;face;     }     protected function get_img_stats($canvas){         $image_width = imagesx($canvas);         $image_height = imagesy($canvas);         $iis =  $this-&gt;compute_ii($canvas, $image_width, $image_height);         return array(             'width' =&gt; $image_width,             'height' =&gt; $image_height,             'ii' =&gt; $iis['ii'],             'ii2' =&gt; $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&lt;$ii_w; $i++ ){             $ii[$i] = 0;             $ii2[$i] = 0;         }         for($i=1; $i&lt;$ii_w; $i++ ){             $ii[$i*$ii_w] = 0;             $ii2[$i*$ii_w] = 0;             $rowsum = 0;             $rowsum2 = 0;             for($j=1; $j&lt;$ii_h; $j++ ){                 $rgb = ImageColorAt($canvas, $j, $i);                 $red = ($rgb &gt;&gt; 16) &amp; 0xFF;                 $green = ($rgb &gt;&gt; 8) &amp; 0xFF;                 $blue = $rgb &amp; 0xFF;                 $grey = ( 0.2989*$red + 0.587*$green + 0.114*$blue )&gt;&gt;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'=&gt;$ii, 'ii2' =&gt; $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 &lt; $s_w ? $s_h : $s_w;         $scale_update = 1 \/ 1.2;         for($scale = $start_scale; $scale &gt; 1; $scale *= $scale_update ){             $w = (20*$scale) &gt;&gt; 0;             $endx = $width - $w - 1;             $endy = $height - $w - 1;             $step = max( $scale, 2 ) &gt;&gt; 0;             $inv_area = 1 \/ ($w*$w);             for($y = 0; $y &lt; $endy ; $y += $step ){                 for($x = 0; $x &lt; $endx ; $x += $step ){                     $passed = $this-&gt;detect_on_sub_image( $x, $y, $scale, $ii, $ii2, $w, $width+1, $inv_area);                     if( $passed ) {                         return array('x'=&gt;$x, 'y'=&gt;$y, 'w'=&gt;$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 &gt; 1 ? sqrt($vnorm) : 1;         $passed = true;         for($i_stage = 0; $i_stage &lt; count($this-&gt;detection_data); $i_stage++ ){             $stage = $this-&gt;detection_data[$i_stage];             $trees = $stage[0];             $stage_thresh = $stage[1];             $stage_sum = 0;             for($i_tree = 0; $i_tree &lt; 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 &lt; count($rects); $i_rect++ ){                         $s = $scale;                         $rect = $rects[$i_rect];                         $rx = ($rect[0]*$s+$x)&gt;&gt;0;                         $ry = ($rect[1]*$s+$y)&gt;&gt;0;                         $rw = ($rect[2]*$s)&gt;&gt;0;                         $rh = ($rect[3]*$s)&gt;&gt;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 &gt;= $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 &lt; $stage_thresh ){                 return false;             }         }         return true;     } }<\/pre>\n<p>And you simply use the class this way:<\/p>\n<pre> $detector = new Face_Detector('detection.dat'); $detector-&gt;face_detect('maurice_svay_150.jpg'); $detector-&gt;toJpeg();<\/pre>\n<p>Which gives the following result:<\/p>\n<p><img decoding=\"async\" title=\"Face detection result, Jun 2009\" src=\"http:\/\/svay.com\/blog\/public\/images\/2009-06-19\/detection.jpg\" alt=\"Face detection result\" \/><\/p>\n<p>The code requires GD and is a bit slow but should work on most PHP servers. You&#8217;ll also need the data file: <a href=\"\/experiences\/face-detection\/detection.dat\">http:\/\/svay.com\/experiences\/face-detection\/detection.dat<\/a>. Let me know if you have ideas for improving the code \ud83d\ude42<\/p>\n<h3>R\u00e9sum\u00e9 en fran\u00e7ais<\/h3>\n<p>J&#8217;ai r\u00e9cemment cherch\u00e9 une solution pour faire de la d\u00e9tection de visage en PHP. Apr\u00e8s avec \u00e9cart\u00e9 plusieurs solutions qui ne sont pas facilement utilisable sur la plupart des h\u00e9bergeurs, j&#8217;ai finalement port\u00e9 du code Javascript+Canvas vers PHP+GD. Le r\u00e9sultat une class PHP qui est un peu lente, mais qui fonctionne sur la plupart des serveurs PHP. Si vous avez des id\u00e9es pour am\u00e9liorer le code, n&#8217;h\u00e9sitez-pas!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Une r\u00e9sum\u00e9 en fran\u00e7ais est disponible en fin d&#8217;article. Lately, I&#8217;ve been looking for ways to detect faces in photos with PHP. Nowadays, face detection is built in many consumer products (camera obviously, but also Google and iPhoto), and seems to be a pretty common job. So I expected to find many solutions for doing [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[26],"tags":[],"_links":{"self":[{"href":"https:\/\/svay.com\/blog\/wp-json\/wp\/v2\/posts\/2046"}],"collection":[{"href":"https:\/\/svay.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/svay.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/svay.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/svay.com\/blog\/wp-json\/wp\/v2\/comments?post=2046"}],"version-history":[{"count":2,"href":"https:\/\/svay.com\/blog\/wp-json\/wp\/v2\/posts\/2046\/revisions"}],"predecessor-version":[{"id":2334,"href":"https:\/\/svay.com\/blog\/wp-json\/wp\/v2\/posts\/2046\/revisions\/2334"}],"wp:attachment":[{"href":"https:\/\/svay.com\/blog\/wp-json\/wp\/v2\/media?parent=2046"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/svay.com\/blog\/wp-json\/wp\/v2\/categories?post=2046"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/svay.com\/blog\/wp-json\/wp\/v2\/tags?post=2046"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}