Este artigo foi traduzido por máquina.

Kinect

Comunicação multimodal com o Kinect

Leland Holmquest

Baixar o exemplo de código

Na edição de Abril (msdn.microsoft.com/magazine/hh882450) eu apresentei para "Lily", um assistente virtual destinada a ajudar os trabalhadores de escritório em suas tarefas diárias. Eu demonstrei como usar o Kinect da Microsoft para Windows SDK para criar diálogo sensível ao contexto, permitindo Lily escutar e responder de forma apropriada com relação às intenções do usuário.

Agora eu vou levá-lo através do próximo passo na consecução de uma interface de usuário natural, utilizando o dispositivo Kinect esquelético de controle para facilitar a interação do usuário através de gestos. Então vou unir os dois conceitos e demonstrar a comunicação multimodal por ter saída de Lily dependente não só que gesto foi feito, mas também qual comando falado foi emitido. Combinando estes dois modos de comunicação, o usuário vem afastado com uma experiência muito mais rica e Obtém um passo mais perto de ubíqua computação. A apresentação de Lily é sob a forma de um aplicativo de Windows Presentation Foundation (WPF).

Inicializando Kinect

O primeiro passo para usar o dispositivo Kinect é construir um tempo de execução, definir vários parâmetros. Figura 1 mostra a configuração que eu escolhi para Lily.

Figura 1 Kinect Runtime construção

// Initialize Kinect
nui = Runtime.Kinects[0];// new Runtime();
nui.Initialize(RuntimeOptions.UseDepthAndPlayerIndex |    
  RuntimeOptions.UseDepth | RuntimeOptions.UseColor |
  RuntimeOptions.UseSkeletalTracking);
nuiInitialized = true; nui.SkeletonEngine.TransformSmooth = true;
nui.SkeletonEngine.SmoothParameters = new TransformSmoothParameters
{
  Smoothing = 0.75f,
  Correction = 0.0f,
  Prediction = 0.0f,
  JitterRadius = 0.05f,
  MaxDeviationRadius = 0.04f
};
nui.VideoStream.Open(ImageStreamType.Video, 2, 
  ImageResolution.Resolution640x480, ImageType.Color);
nui.DepthStream.Open(ImageStreamType.Depth, 2,
  ImageResolution.Resolution320x240, ImageType.DepthAndPlayerIndex);

Ao configurar um dispositivo Kinect, várias opções estão disponíveis. Primeiro, observe a primeira linha do código em Figura 1. O Kinect for Windows SDK beta 2 tem um construtor diferente para o tempo de execução. Fazendo referência a um índice (Runtime.Kinects[0];), é simple anexar várias unidades de Kinect ao aplicativo. Nesta aplicação eu já limitados de TI em um único dispositivo Kinect, portanto, por definição, o tempo de execução deve ser no local [0]. Você pode iterar através da coleção de Runtime.Kinects para lidar com várias unidades de Kinect se disponível. Em seguida, eu preciso dizer o dispositivo Kinect que recursos estão indo para ser usado. Isso é feito, passando os recursos desejados para o método Initialize. Há quatro valores para escolher:

  • UseColor permite que o aplicativo processar as informações de imagem de cor.
  • UseDepth permite que o aplicativo tornar as informações de imagem de profundidade.
  • UseDepthAndPlayerIndex permite que o aplicativo para fazer uso de informações de imagem de profundidade, como também o índice gerado pelo mecanismo de rastreamento de esqueleto.
  • UseSkeletalTracking permite que o aplicativo para usar o esqueleto dados de controle.

Passagem nesses valores diz a API quais subsistemas no dispositivo Kinect vai ser usado para que as partes apropriadas da tubulação multicelulares do tempo de execução podem ser iniciadas. É importante notar que você não pode acessar recursos posteriormente no aplicativo que não estão declarados durante a inicialização. Por exemplo, se a única opção selecionada foi RuntimeOptions.UseColor e mais tarde usando a informação de profundidade foi exigido, não iria estar disponível. Portanto, eu tenho passado todos os valores disponíveis, indicando que eu pretendo usar todos os recursos do dispositivo Kinect.

Controle de usuários

Antes de discutir a próxima seção do código, vejamos o que o dispositivo Kinect realmente está nos dando. Ao usar o esqueleto capacidade de controle, o dispositivo Kinect pode controlar até dois seres humanos ativos interagindo com o sistema. Ele consegue isso criando uma coleção de 20 articulações e associar um ID de cada um. Figura 2 mostra quais articulações estão sendo modeladas.

The 20 Joints that Are Modeled in Kinect
Figura 2 A 20 articulações que são modelados no Kinect

Figura 3 é uma imagem das articulações sendo capturado de dois usuários distintos.

Two Active Skeletons
Figura 3 dois esqueletos ativo

Em ordem para um esqueleto para se tornar ativo, o dispositivo Kinect deve ser capaz de ver o usuário da cabeça aos pés. Uma vez um esqueleto está ativo, se uma articulação sai do modo de exibição, o dispositivo Kinect irá tentar interpolar onde é a parte do esqueleto. Se você estiver indo para construir aplicativos habilitados para Kinect, eu vivamente que você criar um aplicativo simples apenas para assistir os fluxos de esqueleto e interagir com o dispositivo Kinect. Verifique se você tem vários usuários participar e configurar cenários onde obstruções vir entre seus usuários e o dispositivo Kinect — cenários que imitam o que seu aplicativo irá experimentar uma vez implantados. Isto lhe dará um excelente entendimento de como funciona o rastreamento de esqueleto e que é capaz de, bem como quais as limitações que você pode querer endereço. Você vai ver rapidamente como a tecnologia é incrível e como criativo pode ser na interpolação.

Em alguns cenários (como o representado pelo projeto Lily) a velocidade e o choppiness do presente interpolação podem ser perturbador e improdutiva. Por conseguinte, a API expõe a capacidade de controlar um nível de suavização. Referindo-se a Figura 1 novamente, primeiro use o SkeletonEngine no tempo de execução para definir o TransformSmooth como true. Isso informa o dispositivo Kinect que você deseja afetar a suavidade dos dados está sendo processados. Em seguida, defina o SmoothParameters. Aqui está uma breve descrição de cada um do TransformSmoothParameters:

  • Correção controla a quantidade de correção com valores que variam de 0 a 1.0 e um valor de padrão de. 5.
  • JitterRadius controla o raio de redução de variação. O valor passado representa o raio em metros. O valor padrão é definido como 0,05, que se traduz em 5 cm. Qualquer variação que ultrapassa este raio está presa ao raio.
  • Controles de MaxDeviationRadius o raio máximo (em metros) que corrigido posições pode desviar-se a partir dos dados brutos. O valor padrão é 0,04.
  • Previsão controla o número de quadros previstos.
  • Suavização controla a quantidade de suavização com um intervalo de 0 a 1.0. A documentação faz um ponto específico que suavização tem repercussões na latência; aumento da suavização de latência de aumentos. O valor padrão é 0,5. Definindo o valor como 0 faz com que os dados em bruto a ser retornado.

Vídeo e fluxos de profundidade

Você vai querer experimentar com essas configurações em seu próprio aplicativo, dependendo dos requisitos que você está cumprindo. A última coisa necessária para esta aplicação é abrir o VideoStream e o DepthStream. Isso facilita a visualização de imagens de vídeo vindo da câmera de cor e as imagens de profundidade vinda da câmera de profundidade, respectivamente. Mais tarde eu vou lhe mostrar como isso obtém conectado para o aplicativo WPF.

O método aberto requer quatro parâmetros. O primeiro é streamType. Representa o tipo de fluxo que está sendo aberto (por exemplo, vídeo). O segundo parâmetro é Tamanho_do_pool. Isso representa o número de quadros que o tempo de execução é a reserva. O valor máximo é 4. O terceiro parâmetro é a resolução, que representa a resolução das imagens desejadas. Os valores incluem 80 x 60, 640 x 480, 320 x 240 e 1280 x 1024 para atender às suas necessidades. E o último parâmetro indica o tipo desejado da imagem (por exemplo, cor).

Kinect eventos

Com o tempo de execução foi inicializado com êxito, é hora de conectar os eventos disponibilizados desde o tempo de execução para o aplicativo. Para Lily, os dois primeiros eventos que serão tratados são usados simplesmente para dar ao usuário final uma exibição gráfica das imagens em cores e as imagens de profundidade. Primeiro, vamos olhar para o método que está manipulando o evento Runtime.VideoFrameReady. Esse evento passa um ImageFrameReadyEventArgs como seu argumento de evento. O método nui_VideoFrameReady é onde Lily manipula o evento, conforme mostrado no código a seguir:

void nui_VideoFrameReady(object sender, ImageFrameReadyEventArgs e)
{
  // Pull out the video frame from the eventargs and
  // load it into our image object.
PlanarImage image = e.ImageFrame.Image;
  BitmapSource source =
    BitmapSource.Create(image.Width, image.Height, 96, 96,
    PixelFormats.Bgr32, null, image.Bits,
    image.Width * image.BytesPerPixel);
  colorImage.Source = source;
}

O Kinect for Windows API torna este método simples. O ImageFrameReadyEventArgs contém um ImageFrame.Image. Eu que converter um BitmapSource e, em seguida, passar nesse BitmapSource para um controle de imagem no aplicativo WPF. O quadro provenientes de câmera de cor do dispositivo Kinect assim é exibido no aplicativo, como o que você vê na Figura 3.

O evento DepthFrameReady, que está sendo manipulado por nui_DepthFrameReady, é semelhante, mas precisa um pouco mais de trabalho para obter uma apresentação útil. Você pode olhar este método no download de código, que é o mesmo que artigo do mês passado (archive.msdn.microsoft.com/mag201204Kinect). Eu não criar este método eu mesmo, mas encontrei usados em um número de exemplos em linha.

O manipulador de eventos que realmente começa a ficar interessante é o método de nui_SkeletonFrameReady. Esse método manipula o evento SkeletonFrameReady e obtém passado no SkeletonFrameReadyEventArgs, como mostrado na Figura 4.

Figura 4 nui_SkeletonFrameReady

void nui_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
{
  renderSkeleton(sender, e);
  if (!trackHands)
    return;// If the user doesn't want to use the buttons, simply return.
if (e.SkeletonFrame.Skeletons.Count() == 0)
    return;// No skeletons, don't bother processing.
SkeletonFrame skeletonSet = e.SkeletonFrame;
  SkeletonData firstPerson = (from s in skeletonSet.Skeletons
                              where s.TrackingState ==
                              SkeletonTrackingState.Tracked
                              orderby s.UserIndex descending
                              select s).FirstOrDefault();
  if (firstPerson == null)
    return;// If no one is being tracked, no sense in continuing.
JointsCollection joints = firstPerson.Joints;
  Joint righthand = joints[JointID.HandRight];
  Joint lefthand = joints[JointID.HandLeft];
  // Use the height of the hand to figure out which is being used.
Joint joinCursorHand = (righthand.Position.Y > lefthand.Position.Y)
    ?
righthand
    : lefthand;
  float posX = joinCursorHand.ScaleTo((int)SystemParameters.PrimaryScreenWidth,
    (int)SystemParameters.PrimaryScreenHeight).Position.X;
  float posY = joinCursorHand.ScaleTo((int)SystemParameters.PrimaryScreenWidth,
    (int)SystemParameters.PrimaryScreenHeight).Position.Y;
  Joint scaledCursorJoint = new Joint
  {
    TrackingState = JointTrackingState.Tracked,
    Position = new Microsoft.Research.Kinect.Nui.Vector
    {
      X = posX,
      Y = posY,
      Z = joinCursorHand.Position.Z
    }
  };
  OnButtonLocationChanged(kinectButton, buttons, 
    (int)scaledCursorJoint.Position.X,
    (int)scaledCursorJoint.Position.Y);
}

Uma coisa que achei necessária para pôr nesta aplicação foi esse primeiro condicional em Figura 4. Quando o usuário não quiser que o aplicativo para rastrear seus movimentos de mão, há comandos falados que defina a variável trackHands, que por sua vez determina se as mãos são controladas. Se trackHands é definido como false, o código simplesmente retorna deste método. Se Lily controle as mãos do usuário quando isso não é o comportamento desejado, ele rapidamente se torna tedioso e cansativo.

Da mesma forma, se não há esqueletos estão sendo controlados (houver usuários, ou eles estão fora do intervalo de exibição do dispositivo Kinect) então não há nenhum sentido em continuar a avaliar os dados, portanto, o código retorna out do método. No entanto, se lá é um esqueleto e o usuário quer mãos controladas e, em seguida, o código continua a avaliar. O projeto de HoverButton (bit.ly/nUA2RC) vem com o código de exemplo. A maior parte deste método vieram esses exemplos. Uma das coisas interessantes acontecendo neste método é que o código verifica para ver que mão no usuário é fisicamente superior. Em seguida, faz a suposição de que a mão mais alta é a que está sendo usado para potencialmente selecione um botão. O código, em seguida, vai para determinar se um botão está sendo pairado sobre e processa uma "mão" sobre a tela no lugar que é representante da tela com relação à localização da mão do usuário. Em outras palavras, enquanto o usuário se move sua mão, uma mão gráfica é movida ao redor da tela em como a moda. Isso dá ao usuário uma interface natural, não mais vinculado pelo cabo do mouse. O usuário é o responsável pelo tratamento.

O próximo item de interesse é quando o sistema determina que um do HoverButtons é clicado. Lily tem um total de oito botões na tela. Cada um tem um manipulador de evento on_click ligado em. Neste momento, eu preciso cobrir três classes especiais: ButtonActionEvaluator, LilyContext e MultiModalReactions.

A ação de clicar em um botão tem um correspondente evento associado a ele, mas Lily leva esta única ação e verifica se ele pode ser acoplado a um comando de áudio correspondente para avaliar como uma comunicação multimodal que levaria a um nível superior de significado. Por exemplo, clicando em um do HoverButtons representa a intenção de selecionar um projeto. Com essa informação, a única ação requerida pelo sistema é observar que o contexto, com relação ao projeto sendo trabalhado, mudou. Nenhuma ação adicional é desejada. No entanto, se o usuário anteriormente apresentou um pedido insatisfeito "abrir o plano do projeto" ou posteriormente faz o mesmo pedido, o aplicativo deve colocar estas duas peças distintas de dados juntos para criar uma ordem superior de significado (a comunicação provenientes de dois modos separados torna essa comunicação multimodal) e responder em conformidade. Para fazer isso todos ocorrem de forma perfeita, o seguinte design foi implementado.

A classe ButtonActionEvaluator é implementada como um singleton e implementa a interface INotifyPropertyChanged. Essa classe também expõe um evento PropertyChanged que é manipulado pela classe LilyContext (também um singleton). O código a seguir provavelmente requer um pouco de explicação, mesmo que parece bastante inócuo:

void buttonActionEvaluator_PropertyChanged(
  object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
  if (MultiModalReactions.ActOnMultiModalInput(
    buttonActionEvaluator.EvaluateCriteria()) == 
    PendingActionResult.ClearPendingAction)
    buttonActionEvaluator.ActionPending = 
      PendingAction.NoneOutstanding;
}

Avaliar o estado de Lily

Em primeiro lugar, o código precedente chama o método EvaluateCriteria da classe buttonActionEvaluator. Esse método simplesmente retorna uma representação numérica para o estado como definido pelas propriedades ActionPending e SelectedButton. Este é o cerne de como o aplicativo é capaz de inferir o significado através da utilização de comunicação multimodal. Em aplicações tradicionais, a ação desejada é avaliada, observando o estado de um único evento ou propriedade (por exemplo, button1.clicked). Mas com Lily, o estado está sendo avaliado (a partir da perspectiva multimodal) é a combinação de duas propriedades de outra forma separadas. Em outras palavras, cada propriedade tem importância e requer ações de forma independente, mas quando avaliados juntos, eles assumem um novo nível mais elevado significado.

Representação numérica do Estado combinado é então passada para o método de ActOnMultiModalInput sobre o MultiModal­classe de reações. Esse método implementa uma instrução de switch grande que manipula todas as permutações possíveis. (Isso é uma implementação rudimentar que foi usada para ilustrar o ponto. Iterações de futuro de Lily irão substituir essa implementação com técnicas mais avançadas, como máquinas de Estado e máquina de aprendizagem melhorar a experiência e usabilidade geral.) Se esse método resulta na intenção do usuário estar satisfeito (por exemplo, o utilizador tenciona para o sistema abrir o plano do projeto para o projeto Lily), o tipo de retorno é PendingActionResult.ClearPendingAction. Isso deixa o contexto do sistema ainda no quadro de referência de projeto Lily, mas não há nenhuma ação esperando para ser executada na fila. Se a intenção do usuário é ainda insatisfeita, a PendingActionResult.LeavePendingActionInPlace é retornado, dizer o sistema que foi tomada qualquer acção não tenha satisfeito a intenção do usuário e ainda para, portanto, não é claro a ação pendente.

No primeiro artigo eu mostrei como criar gramáticas que são específicas para um determinado domínio ou contexto. A unidade de Kinect, aproveitando o mecanismo de reconhecimento de fala, usado essas gramáticas, carga e descarga-los para atender as necessidades do usuário. Isto criou um aplicativo que não requer o usuário a manter uma interação com scripts. O usuário pode ir em qualquer direção ela desejos e mudar direções sem ter que reconfigurar o aplicativo. Isto criou uma maneira natural de estabelecer o diálogo entre o usuário humano e aplicativo de computador.

Nível superior de significado

Neste artigo, demonstrei como acoplar ações resultantes de gramáticas context-aware para o forte física do usuário gesturing sob a forma de selecionar botões por uma focalização da mão sobre um botão. Cada evento (speechDetected e buttonClicked) pode ser tratado individualmente e de forma independente. Mas, além disso, os dois eventos possam ser correlacionados pelo sistema, trazendo um maior nível de significado para os acontecimentos e agir em conformidade.

Eu espero que você esteja tão animado sobre os recursos que Kinect coloca em nossas mãos como eu sou. Eu acho que a Microsoft nos trouxe para a borda onde interfaces computação humanas podem dar saltos em frente. Como testemunho para isso, como eu desenvolvi Lily, houve vezes quando eu estava testando diferentes componentes e seções de código. Como o aplicativo amadureceu e eu era capaz de realmente "conversar" com Lily, gostaria encontrar algo errado, mudar para o meu segundo monitor e começar a procurar algo na documentação ou em outros lugares. Mas eu iria continuar a interagir verbalmente com Lily, pedindo-lhe para executar tarefas ou até mesmo para desligar para baixo. Achei que quando Lily não estava disponível, eu me tornei perturbado porque a quantidade de habilitação que Lily representada era significativa — tendo mesquinhas tarefas fora de minhas mãos através de comunicações verbais simples.

E incorporar pequenos "truques" o mecanismo de diálogo (por exemplo, respostas aleatórias mas contextualmente e sintaticamente corretos) a adopção da aplicação intuitiva e satisfatória. Kinect realmente faz seu corpo do controlador. Onde você vai com ela é limitada apenas pela sua imaginação. O que você vai Kinect?

Leland Holmquest é um consultor de estratégia empresarial Microsoft. Anteriormente, ele trabalhou para a Naval superfície Warfare Center Dahlgren. Ele está trabalhando em seu Ph.d. em tecnologia da informação na George Mason University.

Graças ao seguinte especialista técnico para revisão deste artigo: Mark Schwesinger