Transformando superfícies em controles remotos com Kinect e Projetores

3 07 2013

Perdeu seu controle remoto denovo? Não se preocupe – crie outro no braço de seu sofá com um toque de sua mão. Enquanto você está fazendo isso, por que não transformar a mesinha de centro em um controle de luz, onde você pode diminuir ou aumentar a intensidade da iluminação sem se levantar? O sistema que faz isso acontecer chama-se WorldKit, foi criado por Robert Xiao and Chris Harrison na Universidade Carnegie Mellon, em Pittsburgh, Pennsylvania e tem previsão de chegar ao mercado em cinco anos.

O WorldKit combina câmeras, projetores e computadores para habilitar superfícies como paredes, mesas e portas a abrigar controles interativos para gadgets, incluindo TVs, câmeras de vídeo e iluminação ambiente.

O sistema usa o sensor de profundidade do Kinect para detectar o pressionamento das superfícies . Quando você move sua mão, é possível dizer em voz alta para o sistema qual tipo de controle você quer que sua superfície se transforme.

Reportagem original disponível no site da NewScientist

http://www.newscientist.com/article/mg21829146.200-kinect-plus-projector-makes-anything-a-remote-control.html#.UcxJgsokwsp





Desenvolvimento para Kinect – Utilização da biblioteca

23 05 2013

Para demonstrar como o uso do Kinect para interação com um aplicativo pode ser facilitado com o uso da biblioteca descrita anteriormente, usaremos um código para controlar uma aplicação de exibição de fotos.

Neste programa, após escolhido o diretório onde as imagens se localizam, é exibida a primeira imagem em ordem alfabética de nome. Na janela onde a estará sendo exibida a imagem, haverá dois botões, um para passar para a próxima imagem e outro para voltar a imagem anterior. Será permitido ainda nesse programa aplicar zoom em uma imagem ou girá-la em qualquer direção, como também avançar ou voltar na galeria apenas por deslizar a mão para a esquerda ou direita, respectivamente.

Abaixo, temos o código necessário para criar a nossa aplicação.

struct Botao{

       public Texture2D textura;
       public Rectangle retangulo;

      public bool estaDentro(int x, int y){

            return (x > retangulo.Left && x < retangulo.Right  && y >
                    retangulo.Top && y < retangulo.Bottom);
}

      public Botao(Texture2D text, Rectangle ret){

            textura = text;
            retangulo = ret;
      }
}


class Exemplo: Tela{

      Botao avancar;
      Botao voltar;
      int qualFoto;
      List<Texture2D> fotos;

     Rectangle areaFoto;
     CursorKinectDuplo cursor;

     Exemplo(Game1 slider) : base(slider) {

             avancar = new Botao(slider.Content.Load<Texture2D>("avancar"),new Rectangle(100, 100,50, 50));
             voltar = new Botao(slider.Content.Load<Texture2D>("voltar"), new Rectangle(200, 100, 50, 50));
             cursor = new CursorKinectDuplo(slider);
             fotos = carregaFotosDiretorio();

             qualFoto = 0;
             areaFoto = new Rectangle();

}

       public override void Update(GameTime gameTime){

              cursor.Update(gameTime);
              if (cursor.DelizaLeft && qualFoto < fotos.Count)
                   qualFoto++;
              else if (cursor.DelizaRight && qualFoto > 0)
                    qualFoto--;
              
              if ((avancar.estaDentro((int)cursor.DirX, (int)cursor.DirY) &&
                  cursor.BotaoDireito == CursorKinectDuplo.estadoPressao.PRESSIONADO)
                  ||
                (avancar.estaDentro((int)cursor.EsqX, (int)cursor.EsqY) && cursor.BotaoEsquerdo  
                 == CursorKinectDuplo.estadoPressao.PRESSIONADO)){

             if (qualFoto < fotos.Count)
                 qualFoto++;

            }else if ((voltar.estaDentro((int)cursor.DirX, (int)cursor.DirY) &&
                cursor.BotaoDireito == CursorKinectDuplo.estadoPressao.PRESSIONADO)
                ||
               (voltar.estaDentro((int)cursor.EsqX, (int)cursor.EsqY) &&
                cursor.BotaoEsquerdo == CursorKinectDuplo.estadoPressao.PRESSIONADO)){

             if(qualFoto > 0 )
                 qualFoto--;

            }

          if(cursor.ZoomIn)  
             aumentaZoom();
         else if(cursor.ZoomOut)     
             diminuiZoom();

         base.Update(gameTime);
}

         public override void Draw(GameTime gameTime){

               spriteBatch.Draw(avancar.textura, avancar.retangulo, Color.White);
               spriteBatch.Draw(voltar.textura, voltar.retangulo, Color.White);
               spriteBatch.Draw(fotos[qualFoto], areaFoto, Color.White);

               cursor.Draw(gameTime);

               base.Draw(gameTime);

          }

Nas primeiras linhas do nosso código, definimos um struct chamado Botao, que irá facilitar o manuseio desse tipo de objeto em uma aplicação feita em XNA. Neste struct, temos a textura (imagem) que representará o botão, sua posição e uma função para verificar se certo ponto está dentro do botão.

Em seguida, começamos a traçar nossa classe Exemplo, para exibir a interface do nosso programa. Note que essa classe deriva da classe Tela, mas isso não interfere no resultado que iremos demonstrar.

Iniciamos declarando os botões avancar e voltar, que serão responsáveis pela navegação na galeria. Em sequência, temos uma variável inteira qualFoto que serve para indicar a foto que está sendo atualmente exibida. A lista fotos e o Rectangle área fotos são apenas auxiliares no nosso contexto, sendo que a primeira conterá as fotos da nossa galeria e o segundo a área e posição de visualização.

Então, fazemos a declaração da variável cursor da classe KinectCursorDuplo. Ela será a responsável por nossa interação com o programa.

No construtor da classe, fazemos a inicialização das variáveis. A única inicialização relevante porém é a da variável cursor, que recebe uma variável da classe Slider no seu construtor. Essa classe é na verdade a classe onde está o loop principal do jogo XNA e pode ter qualquer nome, bastando para isso uma refatoração.

Partimos então para a função Update, que é onde as ações são realmente executadas, e a variável cursor vai ter seu poder destacado. A primeira coisa que é necessária se fazer é chamar a função Update  da variável cursor para atualizar os valores dentro desta. Feito isso, podemos usá-la.

A primeira coisa testada é se houve algum deslizamento horizontal, ou para esquerda, ou para direita. Para isso, basta chamar as propriedades DeslizaLeft e DeslizaRight das classes, que são variáveis booleanas, como mostrado anteriormente.

A seguir, temos uma ação com um grau de dificuldade maior, que é a verificação do clique. Primeiro obtemos a localização do cursor na tela, através das propriedades DirX, DirY, EsqX e EsqY. Passamos esses valores, de maneira separada para o caso da coordenada da mão direita e da mão esquerda para a função estaDentro do botão que desejamos verificar. Caso o cursor “esteja dentro” do botão, ou seja, o cursor está sobre o botão, resta saber se ele está o pressionado. Para isso usamos o valor da propriedade BotaoDireito ou BotaoEsquerdo e verificamos se é igual ao valor PRESSIONADO. Desta forma, usando treze linhas de código e sem nenhuma menção direta ao Kinect ou seu SDK, moldamos uma verificação de clique em dois botões da tela, realizado por qualquer uma das duas mãos.

Por fim, voltamos a simplicidade de verificação do movimento de zoom, realizado apenas com o valor de duas propriedades, ZoomIn e ZoomOut, que também são booleanas.

Como pode ser visto neste pequeno exemplo, a utilização do Kinect pode se tornar extremamente simples em muitos aspectos utilizando esta biblioteca.

 





Desenvolvimento para Kinect – Biblioteca KinectCursor

20 04 2013

Embora o SDK do Kinect para Windows permita obter dados do sensor de maneira razoavelmente simples, pode ser pouco agradável ter que lidar com esqueletos, traduzir posições em gestos, cuidar da conexão, entre outras coisas, em diversos pontos do software.

Para isso, nós criamos uma pequena biblioteca que permite ao programador detectar automaticamente alguns gestos feitos diante do Kinect e utilizá-los para criar comandos no seu programa. Os gestos mapeados são esses:

  • Clique: Detectado quando o usuário estica o braço para frente.
  • Deslizamentos: Detectado quando o usuário movimenta rapidamente a mão para direita, esquerda, para cima ou para baixo.
  • Rotação: Detectado quando o usuário realiza um movimento circular em torno do centro da tela.
  • Zoom: Detectado quando o usuário estica os braços para frente e depois separa ou junta as mãos na direção horizontal rapidamente.

A biblioteca conta com quatro classes, uma (InterpretadorDeComandos) responsável pela comunicação direta com o SDK do Kinect, realizando conexões, obtendo os dados e os encapsulando, outra (CursorKinect) responsável por simular um cursor de mouse controlado por apenas uma das mãos do usuário, a terceira (CursorKinectDuplo), que simula dois cursores de mouse, cada um controlado por uma mão, e por fim, uma apenas para prover estruturas necessária para prover o desenho dos cursores na tela.

Abaixo um diagrama que mostra o relacionamento das classes da biblioteca:

 Biblioteca CursorKinect

As variáveis das classes CursorKinect e CursorKinectDuplo servem para que os usuários destas saibam quando houve cada um dos gestos descritos anteriormente. Mais adiante neste tutorial iremos dar um exemplo de seu uso.

Você pode baixar essa biblioteca clicando aqui.





Desenvolvimento para Kinect – Obtenção e tratamento de dados

17 04 2013

Neste ponto, iremos falar sobre como tratar as informações recebidas pelo Kinect, mais especificamente, como lidar com o a identificação corporal e traçado de esqueletos.

Na seção anterior, mostramos que em um certo ponto era ajustado o evento de chegada de um novo frame de dados de skeleton. Mais especificamente, era realizado o seguinte comando:

sensor.SkeletonFrameReady+=newEventHandler
(trataFrameSkeleton);

Naquele momento, porém, não comentamos sobre o argumento passado: trataFrameSkeleton. Este argumento nada mais é do que a função que irá ser executada quando este evento acontecer.

O cabeçalho dessa função, obrigatoriamente deverá ser assim:

public static void trataFrameSkeleton(object sender,SkeletonFrameReadyEventArgs e)

O primeiro parâmetro nada mais é que o que disparou aquele evento, o que para nós não é relevante. O segundo são os dados que acompanham esse evento. Neste caso, os dados são justamente o que o Kinect rastreou em termos de skeleton.

Dentro da função, a primeira coisa que se deve fazer é atribuir a uma variável do tipo SkeletonFrame o frame que foi trago pelo parâmetro e usando a função OpenSkeletonFrame.

frameEsqueleto = e.OpenSkeletonFrame();

Eventualmente, essa função pode retornar null, então é importante verificar se isso não ocorreu antes de continuar. Depois disso, iremos inicializar um array de Skeleton e obter os dados sobre os esqueletos detectados conforme o código abaixo.

esqueletos = new Skeleton[frameEsqueleto.SkeletonArrayLength]; frameEsqueleto.CopySkeletonDataTo(esqueletos);

Desta forma, os dados obtidos do sensor já estão em mãos da aplicação. Agora resta apenas manipulá-los. Neste exemplo, iremos expor o que acontece em uma das classes da biblioteca que iremos expor a seguir:

foreach (Skeleton esquel in esqueletos)
   if (esquel.TrackingState == SkeletonTrackingState.Tracked &&    
        esquel.Joints[JointType.HandLeft].TrackingState == JointTrackingState.Tracked && 
        esquel.Joints[JointType.HandRight].TrackingState == JointTrackingState.Tracked &&
        esquel.Joints[JointType.Head].TrackingState ==JointTrackingState.Tracked){
      if (esquel.Joints[JointType.HandLeft].Position.Y >  
          esquel.Joints[JointType.HandRight].Position.Y){
                posX = esquel.Joints[JointType.HandLeft].Position.X;
                posY = esquel.Joints[JointType.HandLeft].Position.Y;
                posZ = (esquel.Joints[JointType.Head].Position.Z -
                esquel.Joints[JointType.HandLeft].Position.Z);
}else{
                posX = esquel.Joints[JointType.HandRight].Position.X;
                posY = esquel.Joints[JointType.HandRight].Position.Y;
                posZ = (esquel.Joints[JointType.Head].Position.Z – 
                       esquel.Joints[JointType.HandRight].Position.Z);
     }
   break;
}

Neste código, o vetor esqueletos é varrido. Para cada posição, o primeiro teste realizado é se aquele esqueleto tem sua propriedade TrackingState ajustada para o valor Tracked. Se isso for verdadeiro, significa que aquele corpo foi traçado e que existe informações sobre a posição de suas juntas (o que é irônico pelo fato da estrutura chamar Skeleton e não Joint). Após isso, é realizado o mesmo teste mas dessa vez com as mãos. Observe que dentro de um Skeleton, é acessado um vetor chamado Joints, e que ao invés de usarmos valores para cada posição desse vetor, usamos a enumeração JointType. Essa enumeração possui todas as articulações que podem ser detectadas pelo Kinect e é definida pelo próprio SDK. Neste caso, foi verificado se cada uma das mãos e a cabeça foram traçadas.

O que vem abaixo é apenas uma compilação da posição das juntas para variáveis quaisquer. Cabe salientar que os valores representam posições em um espaço tridimensional e que para no caso das coordenadas x e y, esses valores variam de -1 a 1.

A partir deste código, você já pode expandir para criar os primeiros rastreamentos de gestos que é o que realmente importa para interação entre o usuário e a aplicação via Kinect.





Desenvolvimento para Kinect – Conexão com o sensor

16 04 2013

Este post é o início de uma série de postagens sobre desenvolvimento de aplicativos que utilizem o Kinect. Não faremos nenhuma introdução formal sobre o funcionamento do sensor ou do SDK, pois há uma boa documentação sobre o assunto no neste site da Microsoft e partiremos do princípio de que você já possui instalados no seu computador o Visual C# 2010 (ou superior), o Kinect for Windows SDK e o Kinect for Windows Runtime.

Neste primeiro post, falaremos um pouco sobre o “baixo” nível do desenvolvimento, que é basicamente feita utilizando-se do namespace Microsoft.Kinect , fornecido pelo SDK do Kinect. Este namespace é muito poderoso, e permite fazer um bom trabalho com pouco esforço. A conexão entre o sensor e o Kinect é bastante facilitada pelo Kinect for Windows SDK, com apenas algumas poucas linhas de código você consegue estabelecer uma conexão e começar a receber os dados do sensor. Por exemplo, para realizar a conexão com um dispositivo basta este código:

KinectSensor sensor;
foreach (var kin in KinectSensor.KinectSensors){

     if (kin.Status == KinectStatus.Connected){
             sensor = kin;
             break;
}

Acima, o que vemos primeiramente é uma declaração de um objeto da classe KinectSensor que é fornecida pelo namespace Microsoft.Kinect , que pertence ao SDK do Kinect. Logo após disso, entra-se em um loop onde são varridos todos os sensores conectados ao computador (o SDK pode lidar com mais de um sensor simultaneamente). Ao encontrar o primeiro sensor que esteja conectado, ele é atribuído a variável sensor e o loop é interrompido.

Depois dessa inicialização, é feita a habilitação dos streams de dados, da seguinte forma:

if (sensor != null){
         sensor.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30);
         sensor.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30);
         sensor.SkeletonStream.Enable();

         sensor.SkeletonFrameReady +=
               new EventHandler<SkeletonFrameReadyEventArgs>(trataFrameSkeleton);
         sensor.Start();
}

Aqui é habilitada a recepção de dados de cor, profundidade e traçado de esqueleto. Nos dois primeiros casos é possível definir questões de resolução, esquema de cores e número de frames por segundo, através de enumerações fornecidas pelo SDK. No penúltimo comando, temos a atribuição de uma função para tratar o evento de recepção de um frame de dados de traçado de esqueleto oriundos do Kinect.

Para finalizar a conexão com o sensor, basta desfazer o que foi feito no trecho de código anterior, tal como abaixo:

if (sensor != null){
    sensor.ColorStream.Disable();
    sensor.SkeletonStream.Disable();
    sensor.Stop();
}

E por enquanto é só.

No próximo post mostraremos um pouco de como é feito o tratamento dos dados recebidos pelo sensor  em forma de frame de skeletons.