iSketchnote KickStarter

Dans le cadre des technologies innovantes, nous avons eu l’opportunité chez Sensorit de collaborer avec une équipe très enthousiaste du CEA de Grenoble qui développe une technologie très prometteuse.

Ils ont mis au point un système qui permet d’équiper un stylo tout à fait banal afin d’en capturer les tracés numériquement. Nous avons eu la chance de tester cette technologie qui est très précise, extrèmement rapide et surtout passive (pas d’équipement électronique dans le stylo). Nous parlons donc de technologie grand public, très simple et bas coût.

Si les domaines d’exploitation sont vastes, la première application qui a été retenue pour le lancement de leur kickStarter est un digitaliseur pour tablette.

Le système est ultra simple, vous écrivez/dessinez sur un vrai bloc note clippé sur l’étui de la tablette. Une application dédiée capture vos tracés en temps réel. Idéal pour sketcher tout en ayant le vrai ressenti de l’écriture !

Si vous voulez participer voici le lien du kickStarter: http://kck.st/16gJmjv

Catégories:Uncategorized

Techdays 2013, coding4fun : au delà du z-order…

15/02/2013 1 commentaire

Bonjour à tous,

Comme chaque année, ce fut un grand plaisir de participer à la session coding4fun avec mes anciens collègues de Microsoft. Cette fois-ci je vous ai proposé une solution pour résoudre une problématique de z-order complexe. Quelle que soit la technologie d’affichage, traditionnellement le z-order (ou z-index) est résolu par l’ordre de dessin. Il arrive parfois que l’on veuille afficher différents éléments avec une logique plus complexe.

image

ex: un diaphragme d’appareil photo.

J’ai donc mis en place un manager qui permet de définir des associations de superposition multiples entre contrôles, y compris si ces associations finissent par être circulaires. ( A > B > C > A).

 

image

Pour ce faire, j’ai mis en place un système de recalcul dynamique du clipping des éléments afin de respecter mes règles de superposition. Chaque élément est clipé par l’ensemble de ceux qui le superposent. Afin de calculer la géométrie de clipping, je commence par translater l’ensemble des géométries des éléments dans un repère commun afin de calculer leurs intersections potentielles. La géométrie résultante est retranslaté dans le repère du controle et lui est appliquée en tant que zone de clip.

 

public class ZOrderManager
{
    public ZOrderManager()
    {
        shapes = new Dictionary<Shape, List<Shape>>();
    }

    private Dictionary<Shape, List<Shape>> shapes;

    public void IsOver(Shape foreground, Shape background)
    {
        List<Shape> children = null;
        if (shapes.TryGetValue(background, out children))
            children.Add(foreground);
        else
        {
            children = new List<Shape>();
            children.Add(foreground);
            shapes.Add(background, children);
        }
    }

    public void UpdateClipping(UIElement relativeTo)
    {
        foreach (var element in shapes)
        {
            var geom = GetRelativeGeometry(element.Key, relativeTo);
            var clip =
                element.Value.Select(s => GetRelativeGeometry(s, relativeTo))
                    .Aggregate(geom, (acc, g) =>
                        Geometry.Combine(acc, g, 
                            GeometryCombineMode.Exclude, null));
            var trans = element.Key.TransformToAncestor(relativeTo).Inverse;
            element.Key.Clip =
                Geometry.Combine(clip, Geometry.Empty, 
                    GeometryCombineMode.Union, (Transform) trans);
        }
    }

    private Geometry GetRelativeGeometry(Shape shape, UIElement relativeTo)
    {
        var trans = shape.TransformToAncestor(relativeTo);
        return Geometry.Combine(shape.RenderedGeometry, Geometry.Empty,
            GeometryCombineMode.Union, (Transform) trans);
    }
}

On peut voir ici que chaque controle est clipé permettant d’obtenir un résultat impossible à avoir juste en jouant avec l’ordre de dessin.

image
J’ai également implémenté le pseudo diaphragme en utilisant la même solution.

image
Vous trouverez ici les sources de la solution: http://sdrv.ms/11KQSCX

Qques liens vers les autres démos de la session coding4fun:

Démos de David Catuhe

Démos de David Rousset

Mitsu

Catégories:Uncategorized

Lissage: OneEuroFilter, implémentation en C# et F#

Dès lors que vous développez avec des capteurs, il se peut que le signal renvoyé ne soit pas stable ou bruité et que vous ayez à le lisser afin d’en faciliter l’exploitation. Kinect par exemple est basé sur des caméras et c’est une source qui vibre par nature. A contrario, une souris renvoie un signal stable (aucune perturbation si vous n’y touchez pas par exemple).

Si comme pour moi, le traitement du signal est plutôt un souvenir d’école, voici quelques rappels.

Tout bon informaticien ayant à lisser une série de données utilise le plus souvent le calcul d’une moyenne mobile. Pour faire simple, vous allez remplacer chaque valeur de la série par la moyenne des valeurs qui l’entourent. Ce système fonctionne mais a plusieurs défauts:

- le filtrage exploite la liste des “n” dernières valeurs sur lesquelles il faut itérer et que vous devez maintenir.

- ce système génère de la latence. La courbe sera certes lissée mais les données proches du signal d’origine arriveront plus tard dans le temps.

- la moyenne de la série est conservée mais l’écart type est diminué.

http://fr.wikipedia.org/wiki/Moyenne_mobile

D’autres filtres plus complexes offrent un paramétrage plus riche qui vous permet de mieux adapter leur comportement suivant la nature de vos données.

Parmi eux, les filtres exponentiels sont simples et couramment utilisés.

http://en.wikipedia.org/wiki/Exponential_smoothing

Dans un filtre à moyenne mobile, les valeurs les plus éloignées de l’historique “valent” autant que la dernière valeur enregistrée. Les filtres à exponentiel font décroitre de manière exponentielle le poids des valeurs selon leur éloignement dans le temps. Ces filtres lissent les données mais offrent également un comportement prédictif.

Il existe beaucoup de systèmes de filtrage plus ou moins complexes à mettre en oeuvre et offrant plus ou moins de paramètres d’entrée.

Une équipe de chercheurs de l’INRIA de Lille avec qui nous avons l’occasion d’échanger dans le domaine des intéractions vient de publier un nouveau type de filtre que nous avons utilisé au sein des projets de Sensorit, notamment avec Kinect.

L’avantage de ce filtre est principalement sa simplicité d’utilisation avec deux paramètres d’entrée permettant de lisser puis de contrer la latence.

Tous les détails ici: CHI 2012 paper (PDF)

ainsi qu’une vidéo:

 

et une page exposant l’algorithme et des implémentations dans différents langages: http://www.lifl.fr/~casiez/1euro/

Ci-dessous, une démonstration illustrant l’usage de ce filtre sur une série de données fictive.

Vous trouverez une version de l’algorithme en C# et en F# ainsi qu’un fichier zip avec l’ensemble de la solution.

 

image

 

 

C#
public class OneEuroFilter
{
    public OneEuroFilter(double minCutoff, double beta)
    {
        firstTime = true;
        this.minCutoff = minCutoff;
        this.beta = beta;

        xFilt = new LowpassFilter();
        dxFilt = new LowpassFilter();
        dcutoff = 1;
    }

    protected bool firstTime;
    protected double minCutoff;
    protected double beta;
    protected LowpassFilter xFilt;
    protected LowpassFilter dxFilt;
    protected double dcutoff;

    public double MinCutoff
    {
        get { return minCutoff; }
        set { minCutoff = value; }
    }

    public double Beta
    {
        get { return beta; }
        set { beta = value; }
    }

    public double Filter(double x, double rate)
    {
        double dx = firstTime ? 0 : (x – xFilt.Last()) * rate;
        if (firstTime)
        {
            firstTime = false;
        }

        var edx = dxFilt.Filter(dx, Alpha(rate, dcutoff));
        var cutoff = minCutoff + beta * Math.Abs(edx);

        return xFilt.Filter(x, Alpha(rate, cutoff));
    }

    protected double Alpha(double rate, double cutoff)
    {
        var tau = 1.0 / (2 * Math.PI * cutoff);
        var te = 1.0 / rate;
        return 1.0 / (1.0 + tau / te);
    }
}

public class LowpassFilter
{
    public LowpassFilter()
    {
        firstTime = true;
    }

    protected bool firstTime;
    protected double hatXPrev;

    public double Last()
    {
        return hatXPrev;
    }

    public double Filter(double x, double alpha)
    {
        double hatX = 0;
        if (firstTime)
        {
            firstTime = false;
            hatX = x;
        }
        else
            hatX = alpha * x + (1 – alpha) * hatXPrev;

        hatXPrev = hatX;

        return hatX;
    }
}

 

F#
type LowpassFilter() =
    let mutable firstTime = true
    let mutable hatXPrev = 0.0
    member v.Last() = hatXPrev
    member v.Filter(x:float, alpha:float) =
        let mutable hatX = 0
        let hatX =
            if firstTime then
                firstTime <- false
                x
            else
                alpha * x + (1.0 – alpha) * hatXPrev
        hatXPrev <- hatX
        hatX

type OneEuroFilter(minCutoff:float, beta:float) =
    let mutable firstTime = true
    let xFilt = new LowpassFilter()
    let dxFilt = new LowpassFilter()
    let mutable dcutoff = 1.0

    let mutable _minCutoff = minCutoff
    let mutable _beta = beta

    member v.MinCutoff
        with get () = _minCutoff
        and set (value) = _minCutoff <- value

    member v.Beta
        with get () = _beta
        and set (value) = _beta <- value

    member v.Filter(x:float, rate:float) =
        let Alpha rate cutoff =
            let tau = 1.0 / (2.0 * Math.PI * cutoff)
            let te = 1.0 / rate
            1.0 / (1.0 + tau / te)
        let dx = if firstTime then 0.0 else (x – xFilt.Last()) * rate
        if firstTime then firstTime <- false
        let edx = dxFilt.Filter(dx, Alpha rate dcutoff)
        let cutoff = minCutoff + beta * Math.Abs(edx)
        xFilt.Filter(x, Alpha rate cutoff)

https://skydrive.live.com/embed?cid=7BD91AE7F5096B79&resid=7BD91AE7F5096B79%21207&authkey=AKqZ6OuAtDXOFE8

Catégories:Kinect, Uncategorized

Webcasts de mes sessions Techdays 2012

Les webcasts sont disponibles !

Pour rappel j’avais précédemment fourni les sources des démos ici.

Vous trouverez ici ma session sur C# avancé, ainsi que la session Kinect Effect.

 

Bon visionnage à tous !

Catégories:Uncategorized Tags:,

Techdays 2012 : démos C# avancé et coding4fun

08/02/2012 8 commentaires

Bonjour à tous,

Ce fut un très grand plaisir de participer encore un fois aux Techdays. Il a eu bien sûr Coding4Fun mais aussi C# avancé qui fut un grand moment. Je n’ai pas posté beaucoup depuis que j’ai ouvert ce blog à part quelques articles sur Kinect ici et , c’était donc  très sympa de repartager à nouveau avec vous.

A noter que des solutions comme le canvas infini et le framework de navigation basé sur async proviennent de réels projets développés dans le cadre de Sensorit. (je dis ça pour les taquins qui disent que je ne code que des démos !)

 

  • C# avancé : dont le principal sujet a été l’écriture d’un petite framework de navigation basé sur Async et permettant de centraliser tous les efforts de navigation à un seul endroit ou comment faire tenir toute une application dans le Loaded Smile

image

 

  • Coding4Fun

Le Canvas infini dont voici un screenshot hyper parlant !!

image

 

Et Arkanoid dans l’éditeur de code Wpf de Visual Studio, l’année prochaine ça va être vraiment très dur d’en faire un nouveau !

image

 

Pour ceux qui ont raté ou veulent revoir Coding4Fun la session est déjà en ligne car elle était diffusé en live: http://tdtv.microsoft.fr/j1/Live.aspx#fbid=t6BuyaYrGlX (dernier repère de la timeline)

Voici intégralité des sources dans un dossier skydrive.

Mitsu

Kinect: reconnaissance de la main en 3D

12/11/2011 3 commentaires

Dans mon précédent post nous avions utilisé une analyse 2D de la main afin d’enrichir les capacités de gestures. Etudions désormais une analyse 3D.

L’image de profondeur de Kinect renvoie un tableau en x et y de valeurs de profondeur. La méthode SkeletonEngine.SkeletonToDepthImage() du SDK Kinect permet de convertir chacune de ces coordonnées et profondeurs en coordonnées 3D (x,y,z) dans le repère de la caméra (x=0 et y=0 lorsque l’on est dans l’axe de la caméra). On est donc capable relativement facilement de récupérer une collection de points 3D (nuage de points).

Etudions une première problématique qui n’est déjà pas si simple : la recherche de plans.

Si pour l’œil humain il parait évident de reconnaitre lorsqu’une collection de points 3D rassemble à peu près les points d’un même plan, mathématiquement c’est un tout autre problème.

Il existe bon nombre de technologies de reconstruction 3D depuis des images 2D. Ce sont les technologies qui sont utilisées en réalité augmentée ou dans des logiciels tels que Photosynth. Les images 2D n’étant finalement que des collections de points de couleurs, ces algorithmes étudient les variations de couleurs, recherchent à détecter les bordures (ruptures de couleurs) et les lignes de fuite.

Dans l’analyse de nuages de points, nous sommes déjà dans un environnement 3D mais la recherche de surface est tout aussi complexe car il faut déduire les points qui sont potentiellement en « contact », segmenter les zones trouvées et enfin reconstruire les surfaces, rechercher des surfaces pour être plus exact (fitting).

Il existe un excellent projet open source sur le sujet que je vous recommande : http://pointclouds.org/

Vous trouverez également sur le net pas mal de littérature sur le sujet.

Dans notre cas, nous allons simplifier le problème car nous ne cherchons qu’une seule chose, le plan de la main et en plus nous avons un point de vue.

Pour sélectionner uniquement les points de la main, j’ai utilisé la même technique que dans l’article précédent.

La recherche du plan :

Pour simplifier les explications, posons-nous la même problématique en 2D : nous avons un nuage de points 2D à peu près alignés dans le plan et nous recherchons la droite qui représente au mieux cet ensemble.

Il existe une méthode d’approximation qui s’appelle la méthode des moindres carrés. (http://fr.wikipedia.org/wiki/M%C3%A9thode_des_moindres_carr%C3%A9s )

image

Sans trop entrer dans les détails, l’idée est de partir d’une droite quelconque passant par le centre de gravité du nuage de points et de calculer la sommes des écarts au carré de chacun des points par rapport à sa projection sur la droite. Le but est ensuite de minimiser cette valeur pour se rapprocher de la droite idéale.

Le calcul de minimum n’est pas simple car il faut faire une régression linéaire pour y parvenir. Notons que ça se complique bien plus encore si le modèle recherché est plus complexe qu’une droite (système non linéaire) ou si l’on veut définir des pondérations aux différents points.

J’ai adapté ces notions à notre cas et en le simplifiant pour la 3D.

Pour notre main, nous recherchons donc un plan. Pour rappel son équation cartésienne est a.x + b.y + c.z + d = 0.

Géométriquement parlant, un plan est caractérisé par son vecteur normal. L’idée est alors de calculer pour tous les triplets de points adjacents, le vecteur normal du plan qu’ils décrivent et de chercher à minimiser l’écart (l’angle) avec le vecteur normal du plan.

L’usage de Kinect simplifie une chose essentielle : nous avons un point de vue !

En effet, le nuage de points n’est pas juste une collection de points 3D quelconque, nous savons qu’il est issu de la vision d’une caméra. On peut en déduire des informations importantes :

- Il n’y a pas de points non visibles à la caméra (par exemple je ne peux pas avoir de points appartenant à mon dos si je suis face à la caméra).

- La recherche des points « adjacents » est aisée puisqu’il suffit de vérifier qu’ils sont voisins dans l’image de profondeur et que leur écart de profondeur ne dépasse pas un certain seuil.

- Le calcul de chaque plan élémentaire renvoie un vecteur normal qui peut être vers ou opposé à la caméra suivant l’ordre des 3 points choisis (règle de la main droite). Connaissant le point de vue de la caméra, il suffit de changer le signe du vecteur s’il est opposé à la caméra.

J’ai ajouté une petite option à cet algorithme qui est de conserver l’écart type des écarts ! Cela me permet alors de savoir lors du calcul du plan de la main si celle-ci est très à plat ou pas. Je considère même qu’au-delà un certain seuil mon plan n’est pas valide car la main n’est pas assez plate.

Voilà c’est à peu près tout. Au final le calcul est tout de même conséquent mais assez rapide et c’est le prix à payer pour faire une analyse basée sur les informations de profondeur.

Ci-dessous, le résultat en vidéo.

Catégories:Kinect

Kinect: reconnaissance de la main avec OpenCV

10/11/2011 6 commentaires

Le Ms Kinect SDK offre par défaut une API de reconnaissance du squelette. On peut bien évidemment baser un moteur de gesture sur ces informations. Le problème est souvent de pouvoir distinguer le début d’une gesture et ainsi de la dissocier des autres mouvements naturels de l’utilisateur. Afin de limiter les conflits, on peut bien évidemment utiliser d’autres interfaces comme la voix par exemple, en parallèle de la reconnaissance du squelette. Toujours dans cette optique il est très tentant d’enrichir la reconnaissance de Kinect avec une analyse de la main. De telles fonctionnalités n’existant pas dans le SDK Kinect (ni OpenNI d’ailleurs), il faut les implémenter.

Il y a plusieurs possibilités :

  • Capturer l’image vidéo (rgb) comme on le ferait avec une webcam classique et faire de l’analyse 2D afin d’analyser la main. OpenCv est une des librairies les plus répandues pour l’analyse en “computer vision”. Ce système, relativement classique n’exploite donc pas les informations de profondeur. Utilisant la caméra rgb classique de la Kinect, ce système est donc sensible à la luminosité ambiante et sera très perturbé par .
  • Utiliser l’image de profondeur. C’est une projection 2D des valeurs de profondeur dans l’espace de vision de la camera infra-rouge. Ce système ne renvoie aucune information sur la luminosité ou la coloration. Il a cependant un énorme avantage qui est la facilité pour détourer les éléments. Il est ensuite possible de reconstruire une image de la main et d’utiliser OpenCV afin de l’analyser. C’est la solution que nous étudierons ici.
  • Une dernière solution est de convertir les informations de l’image de profondeur en une collection de points 3D (x, y, z) dans le repère du squelette et d’en faire l’analyse.

Même si le système de reconnaissance final est une analyse 2D, nous allons tout de même profiter des informations de profondeur pour faciliter le détourage de l’image.

Première étape donc, isoler la main. L’analyse du squelette par le SDK Kinect nous renvoie la position des mains dans un espace 3D. Via le SDK toujours, nous avons une possibilité de retrouver les coordonnées 2D de l’image de profondeur correspondant à notre main. Encadrons la main dans un rectangle de taille cohérente et nous optenons l’image ci-dessous, qui sera toujours centrée autour de notre main. Notons que la coloration que vous voyez résulte du calcul d’un histogramme de couleur symbolisant la profondeur.

image

L’étape suivante est très simple, nous allons conserver uniquement les points dont la profondeur est proche de celle du point de la main, ignorant le reste. L’analyse 2D qui suivra s’appuiera principalement sur une recherche de contour, les différences de profondeur des points sélectionnés ne nous intéressent donc pas. Nous ne conserverons donc qu’une image noir et blanc, les points sélectionnés…ou pas.

image

L’image d’entrée pour OpenCV est donc totalement épurée. Sans entrer trop dans les détails, nous recherchons dans un premier temps un détourage grossier que nous affinerons ensuite. Nous finirons par être capable de compter le nombre de doigts tendus.

image

Cette technique à tout de même un coup non négligeable en CPU. De plus, contrairement à l’image vidéo, l’image de profondeur vibre beaucoup et à surtout des effets de trainée lorsque la main se déplace (ex: les doigts ont tendance à se “fusionner” lorsque la main se déplace rapidement), rendant l’analyse hasardeuse.
Nous avons été obligé d’appliquer à nos résultats un filtrage prédictif associé à la vitesse de déplacement afin de les stabiliser.

Ci-dessous, une petite illustration vidéo dans laquelle une seul doigt tendu simule un click.

Pour plus d’informations sur Sensorit : www.sensorit.fr

Catégories:Kinect
Suivre

Recevez les nouvelles publications par mail.