Maurice Svay

Technologie

Nouvelles technologies

Fil des billets - Fil des commentaires

mercredi, février 17 2010

Prendre régulièrement une photo avec son Mac et l'uploader

Et si on prenait des photos à intervalles réguliers et qu'on les mettait en ligne? Pourquoi faire? Pour faire de la vidéo surveillance, monter un film stop-motion ou retrouver qui a volé votre Mac par exemple. Si vous avez un Mac équipé d'une webcam, c'est facile.

Prendre des photos en ligne de commande

Tout d'abord, téléchargez ImageSnap et mettez-le dans /usr/local/bin par exemple. N'importe quel dossier dans votre PATH fera également l'affaire.

Uploader l'image vers un serveur

Ensuite, créez un fichier snap.sh avec le code suivant:

#!/bin/sh
upload="http://example.com/upload.php"
imagesnap snapshot.jpg
curl -F "photo=@snapshot.jpg" $upload

Vous pouvez mettre de dossier où vous voulez, dans votre dossier personnel par exemple. Ce script permet de prendre une photo, et de l'uploader vers http://example.com/upload.php en POST avec CURL.

Récuperer l'image uploadée et la stocker

Maintenant, mettez sur votre serveur le script PHP upload.php suivant. Il permet de récupérer l'upload et de stocker l'image sur le serveur. Créez également un dossier img à côté de votre script. C'est dans ce dossier que seront mises les photos.

<?php

$path = dirname(__FILE__).'/img';

$year_folder = $path . '/' . date('Y');
if (!is_dir($year_folder)) {
    mkdir($year_folder);
}

$month_folder = $year_folder . '/' . date('m');
if (!is_dir($month_folder)) {
    mkdir($month_folder);
}

$day_folder = $month_folder . '/' . date('d');
if (!is_dir($day_folder)) {
    mkdir($day_folder);
}

$file_path = $day_folder . '/' . date('Ymd-His') . '_' . $_SERVER['REMOTE_ADDR'] . '.jpg';

if (move_uploaded_file($_FILES['photo']['tmp_name'], $file_path)) {
    echo "OK\n";
} else {
    echo "Error\n";
}

Notez que les photos seront stockées dans des dossiers comme dans cet exemple: img/2010/02/17/20100210-120000_127.0.0.1.jpg. Le fichier comporte donc la date, l'heure et l'IP d'où la photo est envoyée.

Automatiser la tâche

Une fois que tout est en place, il suffit d'exécuter snap.sh pour prendre la photo et l'uploader. Pour automatiser ça, on peut ajouter cette action dans cron. Pour cela, faites :

crontab -e

et ajoutez la ligne suivante (à adapter selon l'emplacement de snap.sh):

0 * * * * /Users/mauricesvay/snap.sh

Voilà. Votre mac prendra des photos toutes les heures pour les mettre sur votre serveur. Facile.

vendredi, juin 19 2009

Face detection in pure PHP (without OpenCV)

Une résumé en français est disponible en fin d'article.

Lately, I'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 OpenCV, 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.

Learning about face detection

So I started to think about implementing it myself. I read a articles, scientific papers, etc. The website http://www.facedetection.com/ 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'm lazy, you know.

Always look at what others are doing

Then I looked for existing implementations in other languages. Let'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 http://blog.kpicturebooth.com/?p=8. The code looked fairly compact and simple. Shouldn't be hard to port to PHP.

The code

Once the code converted to PHP, here's the result:

<?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;
    }
}

And you simply use the class this way:

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

Which gives the following result:

Face detection result

The code requires GD and is a bit slow but should work on most PHP servers. You'll also need the data file: http://svay.com/experiences/face-detection/detection.dat. Let me know if you have ideas for improving the code :)

Résumé en français

J'ai récemment cherché une solution pour faire de la détection de visage en PHP. Après avec écarté plusieurs solutions qui ne sont pas facilement utilisable sur la plupart des hébergeurs, j'ai finalement porté du code Javascript+Canvas vers PHP+GD. Le résultat une class PHP qui est un peu lente, mais qui fonctionne sur la plupart des serveurs PHP. Si vous avez des idées pour améliorer le code, n'hésitez-pas!

samedi, septembre 30 2006

Blog et dépôt SVN pour moonmoon

À partir de maintenant, vous n'entendrez plus parler de moonmoon sur ce blog. Non, je n'abandonne pas le projet, j'ai simplement décidé d'ouvrir un blog à part entière pour en parler. Désormais, le nouveau site de moonmoon dispose donc d'un blog mais aussi d'un dépot SVN couplé à Trac gentillement fourni par webs. Il ne reste plus qu'à faire avancer le projet.

dimanche, septembre 17 2006

Barcamp Paris 4 : compte-rendu

BarCamp Comme promis, voilà un petit compte rendu du BarCamp qui s'est tenu samedi dans les locaux de Mandriva.

Lire la suite...

vendredi, septembre 15 2006

Barcamp Paris 4

BarCamp Je ferai un saut demain à la quatrième édition de BarCamp Paris. Si j'en crois la liste des inscrits, ce sera l'occasion de rencontrer des personnes aux projets intéressants, dont certaines que je connais déjà. J'espère ne pas rater la présentation sur l'IHM Tiki® qui attire particulièrement mon attention. J'essaierai de vous faire un petit compte-rendu à mon retour.

dimanche, août 27 2006

Un mois avec un Mac

Cela fait maintenant près d'un moins que j'ai un MacBook. Après un mois d'utilisation, je peux déjà faire un petit bilan sur la machine elle-même et sur le système Mac OS X. Pour les pressés, je trouve cet ordinateur portable tout simplement excellent. Pour les détails, ça se passe dans la suite du billet.

Lire la suite...

vendredi, juillet 28 2006

Google Code - Project hosting

Quand on s'appelle Google et qu'on ne sait pas quoi faire avec son infrastructure, on peut faire des choses sympas comme proposer un service d'hébergement de code pour les projets libres. D'après l'annonce, ça s'adresse à des petits projets et ne se positionne pas comme concurrent de Source Forge. Mis à part le fait que je n'ai pas trouvé les conditions d'utilisation, ça pourrait être intéressant de profiter de l'infrastructure de Google pour avoir un petit dépôt vraiment fiable.

samedi, juin 24 2006

moonmoon 0.1.5 beta

La version 0.1.5 beta de moonmoon est (enfin) disponible. Je pensais sortir une version 0.2, mais je crois qu'il va falloir attendre de voir comment avance SimplePie pour décider si je conserve ce parser ou non. Peu importe, voyons ce qu'il y'a de nouveau:

Lire la suite...

- page 1 de 8