Como Disparar Delegates através de Gestos com o Intel SDK RealSense e C#?
Renato Haddad, André Carlucci
Abril, 2015
A cada dia as empresas lançam novos dispositivos com interfaces mais fáceis de se usar, acessar e se obter informações. Se você observar as novas gerações de crianças, parece que nascem sabendo usar qualquer dispositivo, ou ainda, aprendem em minutos. Mas será que isto não é devido às novas maneiras de entender como as coisas funcionam?
Usar gestos para navegar em uma aplicação é normal, ainda mais que os dispositivos como celulares, monitores de vídeo, painéis de carros, gps, etc. já estão preparados pra isto. Mas com certeza ter a possibilidade de reconhecimento de voz ou gestos com a mão ou a cabeça é mais intuitivo e rápido. Como sabemos, aplicações como jogos, caixas eletrônicos, gps, entre outras tantas, devem levar o usuário a atingir um objetivo. E para isto, é preciso programar fluxos dentro da aplicação. Quando falamos de códigos em C#, nos referimos aos delegates ou callbacks, onde são determinados e assinados eventos para que o próprio programa saiba qual código disparar.
Sendo assim, o objetivo deste artigo é criar um projeto de Console Application no Visual Studio em C#, utilizar o SDK Intel RealSense juntamente com a câmera para ler a mão esquerda e disparar delegates que irão fazer uma ação a partir de um movimento. Por exemplo, imagine que você esteja sentado a frente da sua televisão e deseje passar os canais apenas com um gesto com a mão para direita ou esquerda! Ou melhor, se você pensar numa pessoa com deficiência física, que tal mudar o canal com um sorriso através de um ou duplo piscar de olhos? Enfim, coloque a imaginação pra funcionar e siga-nos neste projeto.
Os pré-requisitos para o artigo são: Visual Studio .NET 2013 com o Update 3 ou 4, uma câmera 3D Intel® RealSense F200 (https://software.intel.com/pt-br/RealSense/F200Camera), e instalar o SDK Intel RealSense (https://software.intel.com/pt-br/intel-realsense-sdk).
Projeto
Abra o Visual Studio, selecione o menu File / New Project (Ctrl + Shift + N). Conforme a figura 1, selecione a linguagem Visual C#, o template de Console Application, o nome do projeto é GesturesEvents e você pode gravar em qualquer pasta que desejar. Clique no botão OK e aguarde o VS criar o projeto.
Figura 1 – Novo projeto de Console App
Em seguida é preciso referenciar duas DLLs, as quais já explicamos detalhadamente no artigo “Como Identificar Gestos com o Intel SDK RealSense e C#?”. No entanto, como são importantes e sem elas você não consegue nada, vale a pena relembrar. Uma vez instalado o SDK Intel RealSense, abra a pasta C:\Program Files (x86)\Intel\RSSDK\bin\x64 contendo todas as DLLs do SDK. No VS, adicione (Add / Reference no Solution Explorer) a referência à libpxcclr.cs.dll, conforme a figura 2, e clique no botão OK.
Figura 2 – Adicionar referência
Já a DLL libpxccpp2c.dll é preciso arrastar diretamente no Solution Explorer e configurar a propriedade (F4) “Copy to Output Directory” para “Copy Always”, conforme a figura 3. Isto ocorre porque a biblioteca libpxcclr é escrita em C#, é um wrapper para a libpxccpp2c, que está escrita em C++. Com isto, ao fazer o Deploy do projeto, a libpxccpp2c estará presente fisicamente na pasta o qual a aplicação for instalada.
Figura 3 – Propriedade Copy Always da biblioteca
Códigos C# da classe Hand
Para que possamos organizar as informações, adicione uma nova classe (Add / Class no Solution Explorer) chamada Hand com visibilidade pública. Veja a seguir o início da classe, onde temos a definição de quatro eventos, sendo Opened, Closed, Visible e NotVisible respectivamente. No C# temos esta notação do EventHandler que permite atribuir um bloco de código para tal evento. Por enquanto é apenas a definição do mesmo, nada de assinar nenhuma chamada.
using System;
namespace GesturesEvents
{
public class Hand
{
public event EventHandler Opened;
public event EventHandler Closed;
public event EventHandler Visible;
public event EventHandler NotVisible;
Em seguida, defina duas propriedades IsOpen e IsVisible. Para quem está acostumado a definir propriedades no C# com o get e o set apenas no modelo simplificado (propriedades automáticas), isto não é possível aqui porque é preciso mais informações no bloco do set {}. Veja que nos blocos dos sets, há condicionais que disparam métodos (FireOpen, FireClosed, FireVisible, FireNotVisible).
private bool _isOpen;
private bool _isVisible;
public bool IsOpen
{
get { return _isOpen; }
set
{
if (value == _isOpen)
{
return;
}
_isOpen = value;
if (_isOpen)
{
FireIsOpen();
}
else
{
FireClosed();
}
}
}
public bool IsVisible
{
get { return _isVisible; }
set
{
if (value == _isVisible)
{
return;
}
_isVisible = value;
if (_isVisible)
{
FireVisible();
}
else
{
FireNotVisible();
}
}
}
Estes quatro métodos preparam o evento (handler) para serem chamados e invocados quando necessário. Vejam que a assinatura do handler contém dois parâmetros, o sender que é do tipo Object, é a instância do objeto do gera o evento, neste caso é o this; e os argumentos, se houver, neste caso está declarado como EventArgs.Empty, afinal, não há dados para este evento. Caso tivessem parâmetros, este é um tipo derivado de EventArgs contendo os campos ou propriedades necessárias para armazenar os dados do evento. Vale dizer que o uso da assinatura de handler para o delegate de eventos define um método que não retorna um valor.
O uso do var na declaração retorna um tipo EventHandler, é só uma forma resumida de declaração do C#, onde chamamos de tipo implícito.
protected virtual void FireIsOpen()
{
var handler = Opened;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
protected virtual void FireClosed()
{
var handler = Closed;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
protected virtual void FireVisible()
{
var handler = Visible;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
protected virtual void FireNotVisible()
{
var handler = NotVisible;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
Códigos C# para o Program
Como este projeto é de Console, a classe Program é a principal a ser executada quando rodar o projeto. Precisamos apenas de dois using, conforme listados nos códigos a seguir. Depois definimos duas variáveis, uma para a sessão (_session), que é do tipo PXCMSession e outra para a o Manager (_manager), que é do tipo PXCMSenseManager.
A sessão é o contexto com os módulos. É possível criar uma única ou múltiplas sessões SDK, sendo que cada sessão mantém o seu próprio contexto de I/O e modelos de algoritmo. O PXCMSenseManager é o responsável para se conectar diretamente com a câmera e processar as ações.
Em seguida, definimos uma propriedade chamada LeftHand do tipo Hand, o qual é a classe criada anteriormente por nós, contendo os delegates que usaremos aqui. Já no construtor da classe Program, instanciamos um novo objeto do tipo Hand.
using System;
using System.Threading.Tasks;
namespace GesturesEvents
{
class Program
{
private PXCMSession _session;
private PXCMSenseManager _manager;
public Hand LeftHand { get; private set; }
public Program()
{
LeftHand = new Hand();
}
O método Main é o principal do Program, aqui é onde tudo inicia, ou seja, ao instanciar a classe Program na variável camera, o objeto Hand já é criado, afinal está no construtor. Já a variável hand (do tipo Hand) atribui a propriedade LeftHand do Program.
static void Main(string[] args)
{
var camera = new Program();
var hand = camera.LeftHand;
Agora veja que fantástico a sintaxe de atribuir os delegates. Nesta sintaxe você digita o objeto hand, o Intellisense do Visual Studio mostra todos os eventos EventHandler disponíveis (Opened, Closed, Visible e NotVisible), você escolhe o handler, seguido do += que expressa a assinatura, a atribuição do método, depois você informa quem é o objeto sender e o parâmetro de argumentos eventArgs.
E os códigos a serem executados, onde ficam? Você já ouviu falar em expressão Lambda? Se não, então corre porque jamais um desenvolvedor C# pode ficar sem codificar com Lambda. O símbolo => atribui o bloco de código a ser executado para este delegate, onde o bloco está inserido entre chaves {}. No nosso exemplo, simplesmente serão mostradas mensagens de acordo com cada delegate. Resumindo, os delegates assinam os eventos que ficam de plantão para serem executados quando chamados e cada delegate tem um bloco de código conforme o contexto.
Ao final deste código, temos o método Start (a ser criado) do objeto camera para iniciar a câmera, ler a mão e mostrar as informações. E o Console.Readline é apenas uma forma de parar o código C# numa janela de Command prompt.
hand.Opened += (sender, eventArgs) =>
{
Console.WriteLine("hand open");
};
hand.Closed += (sender, eventArgs) =>
{
Console.WriteLine("hand close");
};
hand.Visible += (sender, eventArgs) =>
{
Console.WriteLine("hand visible");
};
hand.NotVisible += (sender, eventArgs) =>
{
Console.WriteLine("hand not visible");
};
camera.Start();
Console.ReadLine();
}
Iniciar a câmera e ler os dados
Veja o bloco completo do método Start. Lá criamos a sessão, o manager com o CreateSenseManager, ativamos o módulo de mão no EnableHand, configuramos o módulo para pegar todos os gestos (EnableAllGestures), qual evento (OnGesture) será disparado quando um gesto for feito, ativamos as notificações (EnableAllAlerts) e ao final, aplicamos as configurações (ApplyChanges). Não quero ser repetitivo nas explicações, mas até o item 5 tem todos os detalhes no artigo anterior “Como Identificar Gestos com o Intel SDK RealSense e C#?”.
public void Start()
{
//1 - criar session
_session = PXCMSession.CreateInstance();
//2 - criar manager (responsável por adquirir os frames da leitura da câmera
_manager = _session.CreateSenseManager();
//3 - ativar módulo da mão
_manager.EnableHand();
//4 - pegar a instância da mão para fazer configurações antes de iniciar
PXCMHandModule handModule = _manager.QueryHand();
using (PXCMHandConfiguration config = handModule.CreateActiveConfiguration())
{
config.EnableAllGestures();
config.SubscribeGesture(OnGesture);
config.EnableAllAlerts();
config.ApplyChanges();
}
//5 - inicia o pipeline
if (_manager.Init() != pxcmStatus.PXCM_STATUS_NO_ERROR)
{
Console.WriteLine("Error initing camera");
}
A partir deste momento é criada uma tarefa (Task) que irá executar (Run) Threads de forma assíncrona. O código Task.Run aceita uma Action (delegate) no C#, ou seja, ele pega uma thread da thread pool e executa o código atribuído no bloco do => {} nela.
Já no bloco de código o objeto mão é criado com o PXCMHandData e é disparado um looping While até que não dê erro na leitura da câmera. O AcquireFrame e o ReleaseFrame lê cada frame e processa, ou seja, o AcquireFrame lê o frame atual, processa o que for preciso e o ReleaseFrame libera o frame. Lembre-se de liberar o frame o mais rápido possível.
O QueryHand e o Update irão ler os dados da mão e o Ihand pega apenas a mão esquerda. Dependendo da visibilidade da mão na frente da câmera, a propriedade IsVisible é setada como verdadeira ou falsa. Lembre-se que o handler irá mostrar uma mensagem na tela informando este estado. E caso estiver visível (LeftHand.IsVisible = true) verificamos através do valor do QueryOpenness o quanto que mão está aberta ou fechada. Este número varia de 0 (mão fechada) a 100 (mão totalmente aberta) e se for maior que 80, atribui a propriedade IsOpen para true. Caso contrário, false. A cada propriedade setada é mostrada uma mensagem na tela que foi disparada pelo delegate. Quando você executar este código, coloque breakpoints em todos os delegates e propriedades, use o F11 para executar o Debug passo a passo e entender o fluxo do código. E para finalizar, cada delegate é exatamente um bloco de códigos que você deve atribuir para executar quando ação, seja num jogo, numa aplicação, etc.
//6 - Executa a task
Task.Run(() =>
{
//cria objeto com dados da mão
PXCMHandData handData = handModule.CreateOutput();
while (_manager.AcquireFrame(true) >= pxcmStatus.PXCM_STATUS_NO_ERROR)
{
handModule = _manager.QueryHand();
if (handModule != null)
{
handData.Update();
PXCMHandData.IHand ihand;
if (handData.QueryHandData(PXCMHandData.AccessOrderType.ACCESS_ORDER_LEFT_HANDS, 0, out ihand) != pxcmStatus.PXCM_STATUS_NO_ERROR)
{
LeftHand.IsVisible = false;
}
else
{
LeftHand.IsVisible = true;
if (ihand.QueryOpenness() > 80)
{
//esse numero varia de 0 (mão fechada) até 100 (mão bem aberta)
LeftHand.IsOpen = true;
}
else
{
LeftHand.IsOpen = false;
}
}
}
_manager.ReleaseFrame();
}
});
}
Veja o método OnGesture atribuído no SubscribeGesture. Como não precisamos fazer nada com ele neste contexto, deixe-o sem nenhum código.
private void OnGesture(PXCMHandData.GestureData gesturedata)
{
}
Executar a Aplicação
Como ainda não configuramos o Build deste projeto, no Solution Explorer, dê um duplo clique em Properties, abra a guia Build e desmarque o checkbox Prefer 32-bit, conforme a figura 4.
Figura 4 – Build sem 32-bit
Compile a aplicação (F6 ou Ctrl + Shift + B). Se estiver tudo compilado com sucesso, certifique-se que a câmera esteja conectada à USB e pressione F5 para executar a aplicação. Faça com que a mão esquerda fique visível à câmera, abra e feche a mão, retire a mão da frente da câmera, e ao final, veja as mensagens disparadas pelos delegates que criamos, conforme a figura 5.
Figura 5 – Mensagens conforme os gestos da mão
Conclusão
Interagir com gestos nas aplicações é uma conquista que temos que explorar ao máximo dos dispositivos que permitem este recurso. Leia bastante a documentação do SDK Intel RealSense e da linguagem C# (delegates, action, expressões Lambda) a fim de criar cada vez mais códigos que atendam as expectativas das aplicações e dos usuários.
Agradecemos a oportunidade de poder compartilhar o conhecimento deste artigo com todos os desenvolvedores.
Sobre os Autores
Renato Haddad (rehaddad@msn.com – www.renatohaddad.com ) é MVP, MS Regional Director, MCPD e MCTS, Intel Innovator, palestrante em eventos da Microsoft em diversos países, ministra treinamentos focados em produtividade com o VS.NET 2013/2015, ASP.NET 4/5, Entity Framework, Windows Phone e Windows 8.1. Visite o blog https://weblogs.asp.net/renatohaddad.
André Carlucci (andrecarlucci@gmail.com – www.andrecarlucci.com) é MVP, Intel Innovator, Diretor de Tecnologia da Way2 e palestrante nos principais eventos de desenvolvimento do país. André é apaixonado por metodologias ágeis e projetos open-source. Siga-o no twitter @andrecarlucci.