Agora vamos começar a fazer coisas mais interessantes com o Kinect, claro que isso também significa que nossos posts estarão um pouco mais complexos… mas não se preocupem! Pois eu vou tentar simplificar o máximo possível!
Tutorial
Nível: Intermediário
Objetivo: Mostrar a distancia de cada ponto na frente do kinect.
Vídeo:
Introdução
A câmera de profundidade é uma das mais usadas do Kinect e se você quer virar um programador super sayajin para Kinect é fundamental que você saiba como ela funciona. O Kinect é composto (dentre outras coisas) por um emissor de infra vermelho e por um sensor que lê esses raios infra vermelhos. São esses camaradas que fazem o Kinect “ver em 3D”:
Então o que o depth irá nos retornar exatamente? Bom, o depth “retorna pixels que contêm a distância (em milímetros) a partir do plano da câmera e o jogador”. Mas o que isso significa? Que cada ponto que o depth retorna traz a profundidade do objeto e o Kinect (em mm), alem do numero do jogador (se for parte de um jogador).
Vamos a um exemplo mais pratico então! Usando o exemplo depth basics do Kinect developer toolkit vamos ter esse resultado:
Os objetos mais perto do Kinect vão ficando pretos enquanto mais longe vão ficando brancos. Isso só é possível porque cada pontinho (pixel) desses vai ter uma informação de posicionamento (X,Y e Z) e com isso eu mudo a cor de todos dependendo da distância.
Criando o aplicativo
É agora que o bicho pega! Vamos dar uma olhada no passo a passo de como fazer um aplicativo assim. Primeiro passo é criar um projeto no Visual Studio 2010 ( no meu caso vou criar um WPF ). Lembrando que tem que ser .NET 4.0. E depois vou adicionar a referência da biblioteca Microsoft.Kinect ao meu projeto (tudo isso já fizemos no artigo anterior).
Começando a programar
O primeiro passo vai ser adicionar uma imagem no XAML e o evento do window_closing (ou unloaded):
<Window x:Class="CameraDepth.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="480" Width="640" Closing="Window_Closing">
<Grid>
<Image Height="440" HorizontalAlignment="Left" Name="imgDepth" Stretch="Fill" VerticalAlignment="Top" Width="620" />
</Grid>
</Window>
E agora no codebehind vamos pegar o kinect ativo e já ativar a câmera de profundidade (depthstream):
public MainWindow()
{
InitializeComponent();
// Verificamos quantos sensores estão conectado
if (KinectSensor.KinectSensors.Count > 0)
{
// Pega o sensor conectado
sensor = KinectSensor.KinectSensors[0];
// Habilita a camera de profundidade
sensor.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30);
// Associo o evento ao AllFrameReady
sensor.AllFramesReady += new EventHandler<AllFramesReadyEventArgs>(sensor_AllFramesReady);
// Inicio o sensor
sensor.Start();
}
}
E só para lembrar já vamos implementar o evento windows_closing:
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
// Se existir o sensor, para ele
if (sensor != null)
{
sensor.Stop();
sensor.Dispose();
}
}
Ótimo! Tudo normal ate agora né? Só falta nosso evento AllFramesReady agora! Bom… É aqui que o bicho pega ( só um pouquinho):
/// <summary>
/// Cada vez que o kinect detectar uma imagem ele passa por aqui
/// </summary>
void sensor_AllFramesReady(object sender, AllFramesReadyEventArgs e)
{
// Pegamos o DepthFrame
using (DepthImageFrame dframe = e.OpenDepthImageFrame())
{
// Caso não venha nenhum stream sai do método
if (dframe == null)
return;
// Gera os bytes coloridos bonitinhos para a nossa imagem
byte[] pixels = GerarBytesDeCor(dframe);
// Numero de bytes por linha width * 4 (R,G,B, Vazio)
int stride = dframe.Width * 4;
// Cria a imagem passando o tamanho da imagem (640x480) o dpi (to passando um padrão de 96x96) o formato (32bits)
// a paleta (nula) o array de bytes da imagem (bytesp) e o stride
imgDepth.Source = BitmapImage.Create(640, 480, 96, 96, PixelFormats.Bgr32, null, pixels, stride);
}
}
Então vocês podem reparar que tem algo diferente nesse método, no ultimo artigo nós usávamos o CopyPixelDataTo mas agora em vez disso estamos chamando um método que criamos… então, o que mudou?
Depth não retorna os nossos 32 bits
O motivo de eu não fazer do mesmo jeito que antes é porque o depth não me retorna uma imagem de 32 bits, bom na verdade ele quase não me retorna imagem nenhuma. “Quase”. Bom, veja bem (e essa é uma parte complicada) o depth me retorna uma imagem de 16bits (cada pixel tem 16bits) em branco e nós que colorimos ela baseada na proximidade de cada ponto.
Certo… e como nós pegamos a proximidade do ponto? Bom ai que ta! Ele esta “embutido” nos bits do ponto junto com o jogador a qual (se) pertence!
É… nem queria programar para kinect mesmo.
Calma filhão! Só parece complicado mas na verdade é muito fácil (mentira!*).
Vamos montar a nossa função GerarBytesDeCor parte por parte e você vai entender tudo que eu falei ai em cima:
* É fácil mesmo.
/// <summary>
/// Função que convertera o resultado do depth para RGB
/// </summary>
/// <param name="dFrame"></param>
/// <returns></returns>
private byte[] GerarBytesDeCor(DepthImageFrame dFrame)
{
// tamanho do frame do Depth
// cada ponto tem 16bits
short[] rawDepthData = new short[dFrame.PixelDataLength];
// Copia a informação para o nosso raw
dFrame.CopyPixelDataTo(rawDepthData);
//Nosso array de byte que sera a imagem
//Height x Width x 4 (Red, Green, Blue, vazio)
Byte[] pixels = new byte[dFrame.Height * dFrame.Width * 4];
Então, essa é a primeira parte da função, o importante aqui é que o CopyPixelDataTo do depth retorna um array de short e não de bytes como a do RGB, porque isso? Bom ai chega a parte que eu expliquei la em cima, para cada ponto/pixel que o depth detectar ele vai retornar esses 16 bits (tamanho do short), entendeu?
Então se a resolução for 80×80 ele vai retornar 6400 pontos e cada um com 16 bits de informação. Agora vai a bomba: os 13 últimos bits representam a profundidade do ponto enquanto os 3 primeiros representam o jogador. Como o Kinect só reconhece 6 pessoas você só precisa de 3 bits ( 00000111 = 7) para poder representar essa quantidade.
Então beleza, vai ficar ainda mais claro no código. Vamos ver o resto da função:
//Nosso array de byte que sera a imagem
//Height x Width x 4 (Red, Green, Blue, vazio)
Byte[] pixels = new byte[dFrame.Height * dFrame.Width * 4];
// Posição das cores
int colorPixelIndex = 0;
// Nossa profundidade
short depth = 0;
// Variaveis que vamos jogar os valores das cores
byte blue, red, green;
// Um loop para converter cada ponto
for (int i = 0; i < rawDepthData.Length; ++i)
{
// Pega só a parte que contem a informação da profundidade
depth = (short)(rawDepthData[i] >> DepthImageFrame.PlayerIndexBitmaskWidth);
// Apaga os valores
blue = 0;
red = 0;
green = 0;
// 90 CM
if (depth <= 900)
blue = 255;
// 90 cm a 2 metros
else if (depth > 900 && depth < 2000)
green = 255;
// maior que 2 metros
else if (depth > 2000)
red = 255;
// Escreve o azul
pixels[colorPixelIndex++] = blue;
// Escreve o verde
pixels[colorPixelIndex++] = green;
// Escreve o vermelho
pixels[colorPixelIndex++] = red;
//O ultimo byte é vazio
++colorPixelIndex;
}
return pixels;
Agora, está vendo essa linha aqui?
depth = (short)(rawDepthData[i] >> DepthImageFrame.PlayerIndexBitmaskWidth);
O operador “<<“ (Shift) serve para deslocar os bits para esquerda ou direita ( << ou >>), e o DepthImageFrame.PlayerIndexBitmaskWidth é uma constante com o valor de 3. Então fazemos isso para mover os bits 3 casas para esquerda, ignorando assim os 3 bits do jogador e só pegando o da profundidade.
E depois eu converto o valor da profundidade para short.
Depois disso basta verificar a distância, lembrando que o resultado é em milímetros (1000mm = 1 metro). Mas e as cores e esse colorPixelIndex ?
Como nos vamos converter para uma imagem 32BIT, temos que preencher as cores (RGB). Alias nossa imagem vai ser BGR32 e cada pixel é formato por 32 bits (4 bytes): azul, verde, vermelho e vazio. E por isso que temos o colorPixelIndex ele vai ser o nosso contador e cada volta no loop ele aumenta em 4 preenchendo todos os bytes coloridos e vazios.
Então, pronto! Basta você executar e o resultado será algo assim:
Pois é, só fico na mesma posição
Vou adicionar o vídeo o mais rápido possível, seguindo o mesmo modelo do outro.
Espero que tenham gostado, até mais.
[EDITADO]
Segue a coleção completa de artigos (até agora):
- Camera RGB
- Camera de Profundidade
- Inclinação
- Detectando Esqueletos






Comment
Bruno Ritter
27 de junho de 2012 at 18:38Olá boa tarde, preciso entrar em contato com o André Castro, podem me procurar pelo email que eu usei para comentar.
obrigado
Douglas Santos
8 de agosto de 2012 at 3:59Valeu André, está de parabéns pelos vídeos. Não consegui achar nenhum material tão bem explicativo como esse. Estou esperando o próximo com o mapeamento do esqueleto :}
Só uma observação: na minha aplicação fiz “stride = dframe.Width * 4;” (BGRV)
No seu exemplo deu um erro porque foi multiplicado por 3.
Até mais!
André Castro
8 de agosto de 2012 at 9:17É verdade Douglas, o stride no artigo tava errado! Obrigado pela observação, vou tentar fazer o resto dos videos. Valew pelo feedback!
Wellington Ferreira
13 de agosto de 2012 at 22:48André Castro, parabéns pelos tutoriais. Preciso de um help seu no desenvolvimento 3d utilizando kinect. Pode me retornar pelo e-mail? Grato.
Matheus
22 de agosto de 2012 at 9:20Olá André, estamos pensando em fazer o nosso TCC do curso técnico usando Kinect SDK, mas não estamos entendendo muito o código do programa. Será que poderia nos ajudar?
E outra pergunta, a montagem do programa demora muito tempo?
Se quiser contate por e-mail.
Obrigado.
Marcelo de Oliveira
10 de outubro de 2012 at 17:11André, estou desenvolvendo um projeto com kinect e estou com uma dúvida. Pode me ajudar por email?
Carlos
14 de maio de 2013 at 9:07Ola Andre i have many problems to removed background to player i see this code bad when the try implemente not work i use sdk 1.7