Você já pensou em criar um programa para reconhecer gestos através de uma camera integrada à sua aplicação, de forma que as ações da app são disparadas através de gestos com as mãos? Este é o objetivo deste artigo: explorar passo a passo tudo o que é preciso para um desenvolvedor C# iniciar a codificação para este fantástico mundo do Intel® RealSense™.
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 e instalar o Intel® RealSense™ SDK.
A primeira coisa a fazer é entender o que é o mundo do RealSense, os tipos de apps que você pode criar, alguns showcases e uma visão geral. Tudo isto está disponível no link http://www.intel.com/content/www/us/en/architecture-and-technology/realsense-overview.html. O SDK é um conjunto de bibliotecas e exemplos com documentações que você encontrará para várias tecnologias como C++, C#, Unity, JavaScript e Java. É só fazer o download e instalar. Vale a dica que você precisa ter a câmera para que o SDK já a reconheça no momento da instalação, isto tudo evitará problemas de reconhecimento do device.
Neste projeto, vamos criar desde o início uma Console Application em C#, adicionar todas as referências necessárias das bibliotecas do SDK, criar códigos para identificar a câmera, ativar o módulo da mão, verificar se a câmera reconhece a mão esquerda e quais gestos a mão está fazendo.
Projeto
Para criar o projeto, abra o Visual Studio, selecione o menu File / New Project (Ctrl + Shift + N), e na janela aberta, conforme a Figura 1, selecione a linguagem Visual C#, o template de Console Application. Coloque o nome do projeto como ArticleGesture 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
Como você já instalou o Intel® RealSense™ SDK, abra a pasta C:\Program Files (x86)\Intel\RSSDK\bin\x64 e veja que há diversas DLLs. Iremos precisar apenas de duas DLLs (libpxcclr.cs.dll e libpxccpp2c.dll). No projeto do VS vamos adicionar apenas a referência à libpxcclr.cs.dll. Sendo assim, no Solution Explorer selecione Add / Reference, aponte para a pasta citada acima, selecione a libpxcclr.cs.dll conforme a figura 2 e clique no botão OK.
Figura 2– Adicionar referência
Em seguida, no Windows Explorer selecione a libpxccpp2c.dll e arraste para dentro do Solution Explorer do projeto. No entanto, abra a janela de propriedades (F4) e configure a propriedade “Copy to Output Directory” para “Copy Always”, conforme a figura 3. E qual a explicação disto? A biblioteca libpxcclr é escrita em C# e é apenas um wrapper para a libpxccpp2c, que está escrita em C++. Sendo assim, quando você fizer o deploy do projeto, a libpxccpp2c estará sempre presente fisicamente na pasta o qual a aplicação for instalada.
Figura 3– Propriedade Copy Always da biblioteca
Pronto, basicamente precisamos apenas destas duas e assim já temos acesso a todos os módulos que precisamos. E como você é um desenvolver pesquisador, que tal clicar com o botão direito na libpxcclr e selecionar View in Object Browser. Veja na Figura 4 que a classe PXCMHandData já mostra tudo o que precisamos para manipular as mãos e é justamente esta classe que iremos explorar nos exemplos.
Figura 4– Classe PXCMHandData
Códigos C#
Como é uma aplicação de Console, a estrutura básica do Program.cs está descrita abaixo, contendo a classe Program e o método Main. Justamente no comentário códigos é onde você deverá adicionar todo e qualquer código C#.
using System; namespace ArticleGesture { class Program { static void Main(string[] args) { // códigos } } }
A primeira coisa é referenciar uma sessão através da classe PXCMSession e do método CreateInstance. Mas o que é uma sessão? O SDK foi construído em módulos de I/O e algoritmo que implementam as interfaces SDK. Sendo assim, entendemos que uma sessão SDK é 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.
Uma sessão não tem conhecimento de outras sessões, ou seja, o ciclo de vida do módulo é a sua duração, afinal você irá criar, usar e destruir a instância do módulo. Esta sessão deve ser a primeira instância a ser criada antes de qualquer operação do módulo e depois você tem que destruí-la (Dispose).
static void Main(string[] args) { //1 - criar uma session PXCMSession session = PXCMSession.CreateInstance(); }
Em seguida é preciso criar o SenseManager através do método CreateSenseManager da classe PXCMSenseManager. Ele é o responsável para se conectar diretamente com a câmera e processar ações como a análise do rosto e reconhecimento de voz. Se você pensar que a câmera tem 30 frames por segundo, alguém tem que ler os frames para se tomar algumas ações e o SenseManager é que faz isto. Geralmente você precisa configurar o pipeline, inicializar, executar e disparar callbacks e/ou delegates.
static void Main(string[] args) { //1 - criar uma session PXCMSession session = PXCMSession.CreateInstance(); //2 - criar manager (responsável por adquirir os frames da leitura da câmera) PXCMSenseManager manager = session.CreateSenseManager(); }
Em seguida é preciso dizer ao manager o que você quer controlar. No nosso caso será a mão, então, nada mais justo que ativar o módulo EnableHand. Você tem diversas opções, por exemplo, Enable3Dscan, Enable3Dseg, EnableEmotion, EnableFace, EnableModule, EnableStream e EnableTracker.
static void Main(string[] args) { //1 - criar uma session PXCMSession session = PXCMSession.CreateInstance(); //2 - criar manager (responsável por adquirir os frames da leitura da câmera) PXCMSenseManager manager = session.CreateSenseManager(); //3 - ativar módulo da mão manager.EnableHand(); }
Uma vez com o objeto da mão, você pode configurar algumas funções para fazer o tracking. Para isto é preciso instanciar o QueryHand do módulo PXCMHandModule. Quando o módulo não está ocupado processando os dados de entrada, temos dois momentos:
- Setup: ocorre entre a chamada do EnableHand e do AcquireFrame, onde o QueryHand retorna a instância módulo permitindo aplicar configurações;
- Leitura de dados: ocorre entre a chamada do AcquireFrame e do ReleaseFrame onde o QueryHand retorna uma instância de módulo válido, se concluiu o processamento do frame atual, ou seja, leu os dados.
O EnableGesture permite a notificação de eventos para um gesto específico. Há uma sobrecarga para este método, onde você pode informar se a notificação de eventos será contínua ou não. Se for false (padrão), ativa a notificação apenas no início e para no ponto do reconhecimento do gesto.
E que tal pensar em disparar um evento de acordo com o gesto? Quem faz isto é o SubscribeGesture, o qual você atribui um evento, no nosso exemplo será o método OnGesture. Vale dizer que o ciclo de vida ocorre até a chamada do UnsubscribeGesture.
O ApplyChanges aplica as configurações para o rastreamento. Não se esqueça deste método, senão as configurações não terão efeito algum.
//4 - pegar a instância da mão para fazer configurações antes de iniciar PXCMHandModule handModule = manager.QueryHand(); using (PXCMHandConfiguration config = handModule.CreateActiveConfiguration()) { // permite a notificação de eventos config.EnableAllGestures(); // define um evento para o gesto, o OnGesture config.SubscribeGesture(OnGesture); // deixa em alerta todas as notificações config.EnableAllAlerts(); // aplica as configurações config.ApplyChanges(); }
O evento OnGesture atribuído no SubscribeGesture ainda não existe. Então, no Visual Studio, dê um CTRL + ponto (.) sobre o nome do método que ele é criado automaticamente. Resta-nos atribuir o bloco de código, conforme a seguir. Note que o parâmetro é do tipo GestureData, contendo tudo sobre o gesto. Com isto, você consegue capturar as seguintes informações: timestamp – o momento em 100ns que ocorreu o gesto; handId – o ID da mão; frameNumber – número do frame; state – estado do tracking (início, em progresso ou finalizado); name – nome da string do gesto.
private static void OnGesture(PXCMHandData.GestureData gesturedata) { Console.WriteLine("Gesture: {0}-{1}-{2}", gesturedata.name, gesturedata.handId, gesturedata.state); }
Em seguida vamos iniciar o manager e ao mesmo tempo já verificar o estado do mesmo, se está ou não com erro. Para você não se perder no código, esta etapa 5 está logo abaixo do using da configuração. Veja no código que caso dê erro será mostrada uma mensagem ao usuário.
//4 - pegar a instância da mão para fazer configurações antes de iniciar PXCMHandModule handModule = manager.QueryHand(); using (PXCMHandConfiguration config = handModule.CreateActiveConfiguration()) { … } //5 – verifica e inicia o pipeline if (manager.Init() != pxcmStatus.PXCM_STATUS_NO_ERROR) { Console.WriteLine("Error initing camera"); }
Lendo Informações da Mão
Agora vem o melhor de tudo: ler os dados da mão. Para isto é preciso instanciar o método CreateOutput do PXCMHandData. Em seguida, montar um looping While para rodar enquanto não der erro. Imagine que a câmera irá adquirir diversos frames e enquanto isto estiver ativo (true) ela estará lendo informações. O código está descrito a seguir assim como a explicação detalhada.
O método principal aqui é o AcquireFrame, o qual o parâmetro true indica para aguardar até que todos os dados sejam concluídos (são as solicitações de I/O do módulo) para o frame atual. Agora, se durante a leitura houver algum pedido de leitura de módulos diferentes como cor, aúdio ou profundidade, o método se encarrega de aguardar tais leituras de forma sincronizada. Se o parâmetro for false, o método aguarda os pedidos de I/O pendentes, pois pode ser que ocorram várias solicitações.
O AcquireFrame trabalha diretamente com o ReleaseFrame, afinal cada frame deve ser processado e liberado para continuar a leitura dos próximos frames. Então, como regra, use o ReleaseFrame o mais rápido possível, nada de processamentos demorados. Tudo o que estiver no meio destes dois métodos é o processamento que o frame irá realizar.
E no caso de um processamento de streaming ao vivo, como fazer? Sabemos que isto pode ser uma tarefa longa, então é possível que haja perda do frame. Você terá que efetuar testes e usar o SetRealtime (false).
O AcquireFrame tem os seguintes estados:
- PXCM_STATUS_NO_ERROR – dipsositivo está conectado e disponível para ler dados;
- PXCM_STATUS_EXEC_TIMEOUT – ocorreu o timeout antes de ler dados; PXCM_STATUS_DEVICE_LOST – dispositivo está desconectado;
- PXCM_STATUS_EXEC_INPROGRESS – roda uma threading paralela para adquirir streaming;
- PXCM_STREAM_CONFIG_CHANGED – a configuração do dispositivo foi alterada, é preciso reiniciar;
- PXCM_STATUS_DEVICE_BUSY – o I/O do dipsositivo está sendo usado por outra aplicação.
Sendo assim, teremos um looping onde cada frame será executado o bloco de códigos. Veja que o objeto QueryHand do manager é testado se é diferente de nulo. Caso verdadeiro, o Update preenche o objeto com todos os dados da mão, ou seja, pense que um scanner lê a sua mão e obtém todos os dados.
Para isto, temos a classe PXCMHandData, o qual contém a interface IHand que gerencia os dados da mão rastreada. E já que temos duas mãos, você pode definir rastrear dados de apenas uma ou das duas. Quem define isto é o enumerador AccessOrderType do QueryHandData, sendo:
- ACCESS_ORDER_BY_ID – captura os IDs das mãos;
- ACCESS_ORDER_LEFT_HANDS – acessa somente a mão esquerda;
- ACCESS_ORDER_RIGHT_HANDS – acessa somente a mão direita;
- ACCESS_ORDER_NEAR_TO_FAR – ordem das mãos que vai da mais próxima para a mais distante na cena;
- ACCESS_ORDER_BY_TIME – ordem do mais velho para o mais novo na cena;
- ACCESS_ORDER_FIXED – se identificadas as mãos, pega pelo índice 0 ou 1.
Enfim, no nosso código vamos focar apenas na mão esquerda (ACCESS_ORDER_LEFT_HANDS). Veja que a sintaxe no C# é no estilo TryParse, você diz qual o objeto a ser lido (mão esquerda), o índice que pega todas as mãos, mas neste caso o zero (0) representa o ACCESS_ORDER_BY_ID e o out é o tipo de objeto que será retornado do tipo ihand. É claro que tudo isto está numa condicional se for diferente de erro, mostrando a mensagem que a mão esquerda não está visível e caso contrário que encontrou a mão, seguido do QueryOpenness.
O QueryOpenness retorna um valor de 0 a 100, sendo 0 para mão fechada (dedos completamente dobrados) e 100 para mão totalmente aberta (dedos totalmente esticados).
//6 - 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) { //popula objeto de dados da mão com valores atuais handData.Update(); //pega dados de uma mão especifica PXCMHandData.IHand ihand; if (handData.QueryHandData(PXCMHandData.AccessOrderType.ACCESS_ORDER_LEFT_HANDS, 0, out ihand) != pxcmStatus.PXCM_STATUS_NO_ERROR) { Console.WriteLine("mão esquerda não visivel"); } //nesse ponto sabemos que a mão está visivel else { Console.WriteLine("mão esquerda visivel: " + ihand.QueryOpenness()); } } manager.ReleaseFrame(); } manager.Dispose();
Pronto, tudo o que precisamos para testar o reconhecimento da mão esquerda e se está com mão aberta ou fechada, já temos. Resta apenas fazer um ajuste no Build do projeto. No Solution Explorer, dê um duplo clique em Properties, abra a guia Build e desmarque o checkbox Prefer 32-bit, conforme a figura 5. Isso serve porque o SDK só vai funcionar em x64.
Figura 5– 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. Deixe a mão esquerda fora do alcance da câmera e veja conforme a figura 6, que a mão não está visivel.
Figura 6– Mão esquerda invisível
Já na figura 7 temos as mensagens que a mão esquerda está visível e os dados do método OnGesture, que mostra o nome, o ID e o estado do gesto.
Figura 7– Mão esquerda identificada
Agora que está funcionando e reconhecendo a mão esquerda, altere o bloco de códigos do else, conforme o código a seguir, de forma que seja identificado pelo número do QueryOpenness o quanto que a mão está aberta ou fechada. Se estiver maior que 80, mostra a mensagem que a mão esquerda está aberta.
if (handData.QueryHandData(PXCMHandData.AccessOrderType.ACCESS_ORDER_LEFT_HANDS, 0, out ihand) != pxcmStatus.PXCM_STATUS_NO_ERROR) { Console.WriteLine("mão esquerda não visivel"); } //nesse ponto sabemos que a mão está visivel else { //Console.WriteLine("mão esquerda visivel: " + ihand.QueryOpenness()); if (ihand.QueryOpenness() > 80) { //esse numero varia de 0 (mão fechada) até 100 (mão bem aberta) Console.WriteLine("mão esquerda está aberta"); } }
Execute a aplicação novamente (F5) e veja que na figura 8 há um breakpoint dentro do IF, a janela imediata aberta com o código ihand.QueryOpenness() retornando o valor 92, e a janela do command prompt exibindo a mensagem “mão esquerda está aberta”. Inserir breakpoints no projeto é uma boa prática para se explorar estes novos objetos e métodos, ver quais os retornos, os tipos de dados, enfim, explorar.
Figura 8– Análise dos dados com um Breakpoint no código
Conclusão
Este mundo do Intel® RealSense™ está presente em diversos dispositivos e as necessidades de soluções para ajudar as pessoas é constante. Utilizar deste recurso de leitura de gestos, com certeza irá agregar muito valor à sua aplicação e facilitar a usabilidade, que tanto se fala em UI inteligíveis. Este SDK da Intel® com a câmera está disponível no mercado e a implementação necessita de poucos códigos, como vimos neste artigo.
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 http://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.