Introducción a Kinect y procesamiento

El sensor Kinect de Microsoft es un dispositivo periférico (diseñado para XBox y PC con Windows) que funciona muy parecido a una cámara web. Sin embargo, además de proporcionar una imagen RGB, también proporciona un mapa de profundidad. Es decir, para cada píxel visto por el sensor, el Kinect mide la distancia desde el sensor. Esto hace que una variedad de problemas de visión por computadora, como la eliminación de fondo, la detección de manchas, y más fácil y divertido!

El sensor Kinect solo mide el color y la profundidad. Sin embargo, una vez que esa información está en su computadora, se puede hacer mucho más, como el seguimiento de «esqueleto» (es decir, detectar un modelo de una persona y rastrear sus movimientos). Para hacer el seguimiento de esqueletos, deberá usar el libray de procesamiento Kinect v2 de Thomas Lengling, solo para Windows. Sin embargo, si estás en un Mac y todo lo que quieres son datos sin procesar de Kinect, ¡estás de suerte! Esta biblioteca utiliza controladores de código abierto libfreenect y libfreenect2 para acceder a esos datos para Mac OS X (próximamente compatibilidad con Windows).

¿Qué hardware necesito?

Primero necesita un kinect «independiente». No es necesario comprar una Xbox.

  • Sensor Kinect v1 independiente. Creo que este viene con la fuente de alimentación, por lo que no necesita un adaptador separado que se indica a continuación. Sin embargo, si tiene un kinect v1 que viene con una XBox, no incluirá la fuente de alimentación del sensor Kinect.
  • Sensor Kinect v2 independiente. Probablemente también necesite el adaptador Kinect para Windows. No te desanimes, aunque dice Windows, esto te permitirá conectarlo a tu mac a través de USB. Finalmente, también querrá asegurarse de que su computadora sea compatible con USB 3. La mayoría de las máquinas modernas lo hacen, pero si no está seguro, puede obtener más información aquí para Mac OS X.

Algunas notas adicionales sobre diferentes modelos:

  • Kinect 1414: Este es el kinect original y funciona con la biblioteca documentada en esta página en la serie beta Processing 3.0.
  • Kinect 1473: Se ve idéntico al 1414, pero es un modelo actualizado. Debería funcionar con esta biblioteca, pero no tengo una para probar. Por favor, hágamelo saber si lo hace o no!
  • Kinect para Windows versión 1:???? Ayuda? ¿Esto funciona?
  • Kinect para Windows versión 2: Este es el nuevo kinect de la marca con todas las características que se encuentran en XBox One Kinect. También funciona con esta biblioteca!

SimpleOpenNI

También podría considerar usar la biblioteca SimpleOpenNI y leer el libro de Greg Borenstein Haciendo que las cosas vean. OpenNI tiene características (seguimiento de esqueleto, reconocimiento de gestos, etc.) que no están disponibles en esta biblioteca. Desafortunadamente, OpenNI fue comprado recientemente por Apple y, aunque pensé que estaba cerrado, ¡parece que hay algunos esfuerzos para revivirlo!. No está claro cuál será el futuro de OpenNI y SimpleOpenNI.

Estoy listo para comenzar ahora mismo

La forma más fácil de instalar la biblioteca es con el Gestor de Contribuciones de procesamiento Sketch → Importar bibliotecas → Agregar biblioteca y buscar «Kinect». Aparecerá un botón con la etiqueta «instalar».Si desea instalarlo manualmente, descargue la versión más reciente y extráigala en la carpeta bibliotecas. Reinicie el procesamiento, abra uno de los ejemplos en la carpeta de ejemplos y ¡listo!

¿Qué es el procesamiento?

Processing es un lenguaje de programación y entorno de código abierto para personas que desean crear imágenes, animaciones e interacciones. Desarrollado inicialmente para servir como un cuaderno de bocetos de software y para enseñar los fundamentos de la programación de computadoras dentro de un contexto visual, el Procesamiento también se ha convertido en una herramienta para generar trabajo profesional terminado. Hoy en día, hay decenas de miles de estudiantes, artistas, diseñadores, investigadores y aficionados que utilizan el procesamiento para el aprendizaje, la creación de prototipos y la producción.

¿Qué pasa si no quiero usar Procesamiento?

Si se siente cómodo con C++, le sugiero que considere usar openFrameworks o Cinder con Kinect. Estos entornos tienen algunas características adicionales y también puede obtener una ventaja de velocidad de C++ al procesar los datos de profundidad, etc.:

  • ofxKinect
  • Kinect CinderBlock
  • Más recursos de: El proyecto OpenKinect

¿Qué código escribo?

Lo primero es incluir las instrucciones de importación adecuadas en la parte superior de su código:

import org.openkinect.processing.*;

Así como una referencia a un objeto Kinect, p. ej.

Kinect kinect;

Luego, en setup() puede inicializar ese objeto kinect:

void setup() { kinect = new Kinect(this); kinect.initDevice();}

Si está utilizando un Kinect v2, utilice un objeto Kinect2 en su lugar.

Kinect2 kinect2;void setup() { kinect2 = new Kinect2(this); kinect2.initDevice();}

Una vez hecho esto, puede comenzar a acceder a los datos del sensor kinect. Actualmente, la biblioteca pone los datos a su disposición de cinco maneras:

  • PImage (RGB) de la cámara de vídeo kinect.
  • PImage (grayscale) de la cámara IR kinect.
  • PImage (grayscale) con el brillo de cada píxel asignado a la profundidad (más brillante = más cercano).
  • PImage (RGB) con el tono de cada píxel asignado a la profundidad.
  • int array con datos de profundidad sin procesar (números de 11 bits entre 0 y 2048).

Veamos estos de uno en uno. Si desea utilizar el Kinect como una cámara web antigua normal, ¡puede acceder a la imagen de video como un PImage!

PImage img = kinect.getVideoImage();image(img, 0, 0);

Simplemente puede solicitar esta imagen en draw(), sin embargo, si también puede usar videoEvent() para saber cuándo está disponible una nueva imagen.

void videoEvent(Kinect k) { // There has been a video event!}

Si quieres la imagen IR:

kinect.enableIR(true);

Con kinect v1 no se puede obtener tanto la imagen de vídeo como la imagen IR. Ambos se devuelven a través de getVideoImage (), por lo que el que se haya habilitado más recientemente es el que obtendrá. Sin embargo, con Kinect v2, ambos están disponibles como métodos separados:

PImage video = kinect2.getVideoImage();PImage ir = kinect2.getIrImage();

Ahora, si desea la imagen de profundidad, puede solicitar la imagen en escala de grises:

PImage img = kinect.getDepthImage();image(img, 0, 0);

Así como los datos de profundidad en bruto:

int depth = kinect.getRawDepth();

Para el kinect v1, los valores de profundidad bruta oscilan entre 0 y 2048, para el kinect v2 el rango está entre 0 y 4500.

Para la imagen de profundidad de color, utilice kinect.enableColorDepth(true);. Y al igual que con la imagen de video, hay un evento de profundidad al que puedes acceder si es necesario.

void depthEvent(Kinect k) { // There has been a depth event!}

Desafortunadamente, b / c la cámara RGB y la cámara IR no están ubicadas físicamente en el mismo lugar, hay un problema de visión estéreo. El píxel XY en una imagen no es el mismo XY en una imagen de una cámara una pulgada a la derecha. El Kinect v2 ofrece lo que se denomina una imagen «registrada» que alinea todos los valores de profundidad con los de la cámara RGB. A esto se puede acceder de la siguiente manera:

PImage img = kinect2.getRegisteredImage()

Por último, para kinect v1 (pero no v2), también puede ajustar el ángulo de la cámara con el método setTilt().

float angle = kinect.getTilt();angle = angle + 1;kinect.setTilt(angle);

Por lo tanto, aquí lo tienen, aquí están todas las funciones útiles que puede necesitar para usar la biblioteca de procesamiento de kinect:

  • initDevice() — iniciar todo (vídeo, profundidad, IR)
  • activateDevice(int) – activar un dispositivo específico cuando se conectan varios dispositivos
  • initVideo() — iniciar solo vídeo
  • enableIR(boolean) — encender o apagar la imagen de la cámara IR (solo v1)
  • initDepth() — solo profundidad de inicio
  • enableColorDepth(boolean) — activar o desactivar los valores de profundidad como imagen en color
  • enableMirror(boolean) — reflejar la imagen y los datos de profundidad (solo v1)
  • PImage getVideoImage() — toma la imagen de vídeo RGB (o IR para v1)
  • PImage getIrImage() — toma la imagen IR (solo v2)
  • PImage getDepthImage() — toma el mapa de profundidad imagen
  • PImage getRegisteredImage() — toma la imagen de profundidad registrada (solo v2)
  • int getRawDepth() — agarra los datos de profundidad sin procesar
  • float getTilt() — obtener el ángulo del sensor de corriente (entre 0 y 30 grados) (solo v1)
  • setTilt(float) — ajuste el ángulo del sensor (entre 0 y 30 grados) (solo v1)

Para todo lo demás, también puedes echar un vistazo a la referencia de javadoc.

Ejemplos

Hay cuatro ejemplos básicos para v1 y v2.

Mostrar imágenes RGB, IR y de profundidad

Código para v1: Prueba de profundidad RGB

Código para v2:RGBDepthTest2

Este ejemplo utiliza todas las funciones enumeradas anteriormente para mostrar los datos del sensor kinect.

Múltiples dispositivos

Tanto v1 como v2 tienen compatibilidad con varios kinect.

Código para v1:MultiKinect

Código para v2:MultiKinect2

Nube de puntos

Código para v1: PointCloud

Código para v2: PointCloud

Aquí, estamos haciendo algo un poco más elegante. Número uno, estamos usando las capacidades de procesamiento 3D para dibujar puntos en el espacio. Querrás familiarizarte con translate (), rotate (), pushMatrix (), popMatrix (). Este tutorial también es un buen lugar para comenzar. Además, el ejemplo utiliza un PVector para describir un punto en el espacio 3D. Más información aquí: tutorial de PVector.

El verdadero trabajo de este ejemplo, sin embargo, no viene de mí en absoluto. Los valores de profundidad en bruto del kinect no son directamente proporcionales a la profundidad física. Más bien, escalan con el inverso de la profundidad de acuerdo con esta fórmula:

depthInMeters = 1.0 / (rawDepth * -0.0030711016 + 3.3309495161);

En lugar de hacer este cálculo todo el tiempo, podemos precomputar todos estos valores en una tabla de búsqueda, ya que solo hay 2048 valores de profundidad.

float depthLookUp = new float;for (int i = 0; i < depthLookUp.length; i++) { depthLookUp = rawDepthToMeters(i);}float rawDepthToMeters(int depthValue) { if (depthValue < 2047) { return (float)(1.0 / ((double)(depthValue) * -0.0030711016 + 3.3309495161)); } return 0.0f;}

Gracias a Matthew Fisher por la fórmula anterior. (Nota: para que los resultados sean más precisos, tendría que calibrar su dispositivo kinect específico, pero la fórmula es lo suficientemente cercana para mí, así que me quedo con ella por ahora. Más información sobre calibración en un momento.)

Finalmente, podemos dibujar algunos puntos basados en los valores de profundidad en metros:

 for(int x = 0; x < w; x += skip) { for(int y = 0; y < h; y += skip) { int offset = x + y * kinect.width; // Convert kinect data to world xyz coordinate int rawDepth = depth; PVector v = depthToWorld(x, y, rawDepth); stroke(255); pushMatrix(); // Scale up by 200 float factor = 200; translate(v.x * factor, v.y * factor, factor-v.z * factor); // Draw a point point(0,0); popMatrix(); } }

Seguimiento de puntos promedio

La verdadera magia del kinect radica en sus capacidades de visión por computadora. Con la información de profundidad, puedes hacer todo tipo de cosas divertidas, como decir: «el fondo es algo más de 5 pies. ¡Ignóralo!»Sin profundidad, la eliminación de fondo implica todo tipo de comparaciones minuciosas de píxeles. Como una demostración rápida de esta idea, aquí hay un ejemplo muy básico que calcula la ubicación xy promedio de cualquier píxel frente a un umbral de profundidad dado.

Fuente para v1: Seguimiento de puntos medios

Fuente para v2: AveragePointTracking2

En este ejemplo, declaro dos variables para sumar todas las x e y apropiadas y una variable para hacer un seguimiento de cuántas hay.

float sumX = 0;float sumY = 0;float count = 0;

Luego, cada vez que encontramos un punto dado que cumple con nuestro umbral, añado la x y la y a la suma:

 if (rawDepth < threshold) { sumX += x; sumY += y; count++; }

Cuando terminemos, calculamos el promedio y dibujamos un punto.

if (count != 0) { float avgX = sumX / count; float avgY = sumY / count; fill(255, 0, 0); ellipse(avgX, avgY, 16, 16);}

¿Qué falta?

  • Todo está siendo rastreado a través de problemas de github.

FAQ

  1. ¿Qué hay sombras en la imagen de profundidad (v1)? Diagrama de sombras de Kinect
  2. ¿Cuál es el rango de profundidad que puede ver el kinect? (v1) ~0,7-6 metros o 2,3-20 pies. Tenga en cuenta que obtendrá píxeles negros (o un valor de profundidad sin procesar de 2048) en ambos elementos que están demasiado lejos y demasiado cerca.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.