Fala filhotes,

Ate que enfim vamos falar sobre como trabalhar com os esqueletos do Kinect, então vamos começar!

Tutorial

Nível: Intermediário

Objetivo: Entender os conceitos e criar uma aplicação que detecta a mão direita de um jogador

Vídeo:  Sendo criado…

Introdução

A detecção de esqueleto é extremamente necessária se você tem interesse em fazer aplicativos para o Kinect o conteúdo é um pouco extenso, mas de pouco em pouco nós vamos subindo nessa montanha, agora vamo que vamo pois o  esqueleto é sem duvida uma das partes mais importantes do Kinect.

He-Man sempre foi um mero coadjuvante

Conceitos

Antes de entrarmos nas classes e nos códigos vamos falar sobre os conceitos do esqueleto e suas articulações. Quando o Kinect detecta alguém nós falamos que ele detectou um esqueleto. Ele detecta 20 articulações do seu corpo (joints) assim montando um esqueleto.

Na própria documentação mostra todos os pontos que o  Kinect detecta:

Esqueletos

O Kinect consegue detectar até 6 esqueletos, sendo 2 completos ( todas as 20 juntas)  e 4 apenas as posições. Cada esqueleto também tem um TrackingID  que você pode usar para identificar o esqueleto alem do SkeletonTrackingState que determina se o esqueleto esta completo, apenas a posição ou se não foi encontrado.

 Articulações

Como disse antes cada esqueleto vai ter uma coleção de 20 articulações Cada articulação (Joint) possui um tipo (braço direito, ombro esquerdo, etc..), um TrackingState e a posição ( Y, X e Z) em METROS.

O TrackingState da articulação pode ser 3 tipos:

  • Tracked : A articulação foi detectada normalmente.
  • Not Tracked: A articulação não foi detectada.
  • Inferred: O Kinect não conseguiu detectar essa articulação mas presume que ela está em um ponto baseando-se nas articulações ligadas a ela. Ex. Seu cotovelo esta escondido mas ele presume que esta na posição X por causa do seu ombro e do seu braço.

Sabe quando você esta jogando o Kinect e de vez em quando seu avatar se contorce mais do que uma vaca com coceira atrás da orelha? Pois é, provavelmente ele “perdeu” alguma articulação e presumiu que ela estava em algum lugar maluco e por isso seu avatar ficou daquele jeito, o importante é você ver quais articulações estão com esse problema ( tipo Inferred ) e reagir a isso.

O desafio mesmo é tirar espinha das costas

EL CODIGO

Bora ver códigos!!! Então, vamos criar um novo projeto WPF e na nossa página principal vamos ter um canvas e 2 imagens, sendo que a que ta no fundo pegará a imagem da câmera RGB e a outra irá ser a imagem de uma mão que seguirá a mão direita do camarada. Então vamos ao XAML primeiro:

<Window x:Class="SkeletonWPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"  Height="520" Width="660" >
    <Grid>
        <Canvas>
            <Image Stretch="Fill" Name="image1" Height="480" Width="640"  />
            <Image Canvas.Left="321" Canvas.Top="285" Height="70" Name="imgHand" Stretch="Fill" Width="84" Source="/SkeletonWPF;component/images/hand1.png" />
        </Canvas>
    </Grid>
</Window>

Estou usando essa imagem, dentro da pasta “images” do meu projeto:

Agora vamos ao code-behind:

Primeiro, vamos adicionar nossas variáveis e iniciar o Kinect no construtor:

 // Nosso Sensor
        KinectSensor nui;
        // Bitmap que pode ser reescrito
        private WriteableBitmap bitmapWave;

        public MainWindow()
        {
            InitializeComponent();

            if (KinectSensor.KinectSensors.Count > 0)
            {
                var colorList = new List<Color> { Colors.Black, Colors.Green, Colors.Blue };

                // Nosso bitmap vai ter o tamanho
                bitmapWave = new WriteableBitmap(640, 480, 96, 96, PixelFormats.Bgr32, new BitmapPalette(colorList));

                nui = KinectSensor.KinectSensors[0];
                // Para o esqueleto funcionar nós precisamos do depth ligado também
                nui.SkeletonStream.Enable();
                nui.DepthStream.Enable();
                // Ativamos a camera RGB
                nui.ColorStream.Enable();

                nui.AllFramesReady += new EventHandler<AllFramesReadyEventArgs>(nui_AllFramesReady);

                nui.Start();
            }
        }

É importante ressaltar que para rastrear o esqueleto você precisa do DepthStream ativado! Então não basta apenas ativar o SkeletonStream.  Bom, até ai nos estamos acostumados, ativamos as funções que queremos do Kinect, e agora vamos criar a função AllFramesReady:

        /// <summary>
        /// Função que sera executada toda hora que o kinect detectar algo (esqueleto,video,etc...)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void nui_AllFramesReady(object sender, AllFramesReadyEventArgs e)
        {
            // Abre os frames de profundidade e do esqueleto
            var depth = e.OpenDepthImageFrame();
            var skelframe = e.OpenSkeletonFrame();

            try
            {
                // Abrindo o colorframe para pegar a imagem de video do Kinect
                using (ColorImageFrame cif = e.OpenColorImageFrame())
                {
                    if (cif == null)
                        return;

                    byte[] bytesp = new byte[cif.PixelDataLength];

                    cif.CopyPixelDataTo(bytesp);

                    //Numero de bytes por linha width * 4 (R,G,B,Vazio)
                    int stride = cif.Width * 4;

                    // Escreve os Pixels
                    bitmapWave.WritePixels(new Int32Rect(0, 0, 640, 480), bytesp, stride, 0);

                    //Cria a imagem
                    image1.Source = bitmapWave;

                }

                if ((depth != null) && (skelframe != null))
                {
                    // Crio um array de esqueletos (lembrando que são sempre 6 esqueletos)
                    Skeleton[] skeletonGroup = new Skeleton[6];

                    // Copia os dados dos esqueletos encontrados para o nosso array
                    skelframe.CopySkeletonDataTo(skeletonGroup);

                    // Uso o Linq para pegar apenas o primeiro esqueleto detectado
                    Skeleton myskeleton = (from s in skeletonGroup where s.TrackingState == SkeletonTrackingState.Tracked select s).FirstOrDefault();

                    // Se não tiver nenhum esqueleto na tela
                    if (myskeleton == null)
                        return;

                    // Converto a posição do esqueleto para depth 
                    DepthImagePoint dip = depth.MapFromSkeletonPoint(myskeleton.Joints[JointType.HandRight].Position);
                    // Converto o depth para ColorImagePoint
                    ColorImagePoint headColorPoint = depth.MapToColorImagePoint(dip.X, dip.Y, ColorImageFormat.RgbResolution640x480Fps30);
                    // Mudo a imagem da mão para onde esta a nossa mão na imagem
                    Canvas.SetLeft(imgHand, headColorPoint.X - imgHand.Width / 2);
                    Canvas.SetTop(imgHand, headColorPoint.Y - imgHand.Height / 2);
                }              
            }
            catch (Exception ex)
            {
                throw;
            }
            finally
            {
                // Finaliza os objetos
                if (depth != null)
                    depth.Dispose();

                if (skelframe != null)
                    skelframe.Dispose();

            }
        }

Otimo! Agora vamos as explicações. Quando você abre o frame dos esqueletos sempre terá um array de 6 esqueletos, mesmo que todos os esqueletos sejam nulos! E nós temos que copiar esses dados para o nosso próprio array.
Como eu disse antes cada esqueleto tem seu TrackingState e seu TrackingId (apesar de não estarmos usando agora) e com isso você sabe quantos esqueletos foram detectados, alem de ter o controle sobre qual esqueleto controla o que.

Outra coisa importante é como nós pegamos a mão do camarada, nós usamos o  myskeleton.Joints[JointType.HandRight], por isso é muito simples pegar a joint que você quer analisar, basta passar o JointType da parte do corpo que você quer e pegar a position.

Bom, mas ai que chega a segunda parte, essa posição que nós pegamos das juntas são em metros, o que não faz diferença para gente afinal o que a gente quer é que a imagem da mão siga a a mão do cara no video, então precisamos de um “velho e bom” X e Y em pixels.
Ai que esta o pulo do gato! Fazemos uma conversão dessa posição para DepthImagePoint só para podermos passar para o ColorImagePoint, ou seja, a imagem que vem da câmera.

Pronto! É só isso! O resultado será esse:


Mas André, você só tem essa camisa?  – Sim.

 

O conteúdo é muito longo, por isso nós ainda vamos a voltar a falar (muito) sobre os esqueletos. Ja estou preparando o vídeo completo e os novos tutoriais, abraços!

[EDITADO]

Segue a coleção completa de artigos (até agora):

  1. Camera RGB
  2. Camera de Profundidade
  3. Inclinação
  4. Detectando Esqueletos