Ejemplo de uso de Kinect con Delphi


Esta es una aplicación Delphi que permite manejar Kinect mostrando varios tipos de imágenes (de profundidad, del esqueleto del jugador, o ambas mezcladas), creo que puede servir de base para futuros desarrollos en los que se integre la imaginación , la creatividad y la tecnología.

Basado en el código de Jim McKeeth  y mejorado por mí, este software no lleva ningún driver especial, sólo las units necesarias para manejar kinect: NuiApi.pas , NuiSensor.pas , NuiSkeleton.pas , NuiImageCamera.pas .
Probado en Delphi 2009 con Kinect v.1.0,  recordar que existe una distancia mínima a partir de la cual kinect reconoce la figura, con kinect v1.0 y Windows 8.1 era 1,5 mts por lo no os acerquéis mucho al sensor.


A continuación se muestran varias capturas de pantalla de la aplicación:
Pantalla inicial del programa, se muestra cada punto del esqueleto, distinguiendo la de la mano derecha (color rojo) y la mano izquierda (color verde)


Captura de pantalla de la imagen de profundidad, superpuesta con los puntos clave del esqueleto, observe los puntos de la mano derecha e izquierda (verde y rojo) , kinect es capaz de seguirlos perfectamente aunque se crucen entre ellos.


La misma imagen de profundidad pero omitiendo los 10 puntos inferiores del esqueleto , esto es útil cuando el jugador está sentado, ya que kinect sólo muestra los puntos de la cabeza y brazos, con lo que se consigue un seguimiento más rápido de los movimientos del cuerpo

La imagen de la cámara mezclada con la imagen del esqueleto (sólo los puntos de la cabeza y brazos, ya que el jugador está sentado)

Empezamos con el procedimiento LiberarMemoria,


IF assigned(Fsensor) THEN

BEGIN

Fsensor.NuiSkeletonTrackingDisable;

Fsensor.NuiShutdown;

END;



IF FskeletonEvent <> INVALID_HANDLE_VALUE THEN

CloseHandle(FskeletonEvent);

IF FdepthEvent <> INVALID_HANDLE_VALUE THEN

CloseHandle(FdepthEvent);



IF assigned(FskeletonEvents) THEN

BEGIN

FskeletonEvents.Terminate;

FskeletonEvents.WaitFor;

FskeletonEvents.Free;

END;



IF assigned(FdepthEvents) THEN

BEGIN

FdepthEvents.Terminate;

FdepthEvents.WaitFor;

FdepthEvents.Free;

END;

//añadido



IF assigned(lbitmap) THEN

LBitmap.free;


Después tendremos que cargar la biblioteca de kinect LoadNuiLibrary, conectamos el sensor y creamos el Bitmap donde proyectaremos la imagen de profundidad o la imagen de la cámara o ambas superpuestas. Por las pruebas que he hecho con una resolución distinta a 640x480 no funciona el sensor.



FskeletonEvent := INVALID_HANDLE_VALUE;
FdepthEvent := INVALID_HANDLE_VALUE;
FdepthStream := INVALID_HANDLE_VALUE;

IF NOT LoadNuiLibrary THEN
exit;

openFirstSensor;

LBitmap := TBitmap.Create;
LBitmap.Width := 640;
LBitmap.Height := 480;
LBitmap.PixelFormat := pf32bit;
Image1.Picture.Graphic := LBitmap;



Mención especial merece la función OpenFirstSensor, que inicia kinect y lo prepara para su uso en el programa.
Para definir el formato de imagen se usa el tipo NUI_IMAGE_RESOLUTION, que puede tomar los valores 
NUI_IMAGE_RESOLUTION_640x480, 
NUI_IMAGE_RESOLUTION_1280x960,
NUI_IMAGE_RESOLUTION_80x60, 
posteriormente se inicializará con la función NuiInitialize que tomará como parámetro alguno de los siguientes (según MSDN)
ConstantDescription
NUI_INITIALIZE_DEFAULT_HARDWARE_THREADThis flag was deprecated in version 1.5; it is no longer used.
NUI_INITIALIZE_FLAG_USES_AUDIOInitialize the sensor to provide audio data.
NUI_INITIALIZE_FLAG_USES_COLORInitialize the sensor to provide color data.
NUI_INITIALIZE_FLAG_USES_DEPTHInitialize the sensor to provide depth data.
NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEXInitialize the sensor to provide depth data with a player index.
NUI_INITIALIZE_FLAG_USES_SKELETONInitialize the sensor to provide skeleton data.
Después tenemos que decir que tipo de seguimiento de la figura queremos hacer con NuiSkeletonTrackingEnable con alguno de los siguientes parámetros
NUI_SKELETON_TRACKING_FLAG_SUPPRESS_NO_FRAME_DATAPrevents the INuiSensor::NuiSkeletonGetNextFramemethod from returning E_NUI_FRAME_NO_DATA errors. Instead, calls to NuiSkeletonGetNextFrameblock execution until data is available or the timeout period passes.
NUI_SKELETON_TRACKING_FLAG_TITLE_SETS_TRACKED_SKELETONSDisables the default player selection mode and enables the title to choose which detected skeletons are tracked.
NUI_SKELETON_TRACKING_FLAG_ENABLE_SEATED_SUPPORTUses seated skeleton tracking mode. The 10 lower-body joints of each skeleton will not be tracked.
Lo siguiente es configurar el tipo de imagen con NuiImageStreamOpen con los siguientes parámetros:

NUI_IMAGE_TYPE_COLOR
NUI_IMAGE_TYPE_COLOR_INFRARED Muestra la imagen infrarroja
NUI_IMAGE_TYPE_COLOR_RAW_BAYER
NUI_IMAGE_TYPE_COLOR_RAW_YUV  *marca en un color diferente la figura de cada jugador
NUI_IMAGE_TYPE_COLOR_YUV Muestra la imagen de la cámara
NUI_IMAGE_TYPE_DEPTH  Muestra la imagen de profundidad
NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX  marca en un color diferente la figura de
cada jugador

Y por último ponemos la resolución de la imagen con NuiImageResolutionToSize



FUNCTION TMain1.openFirstSensor: boolean;
VAR
opcion: tnui_image_type;
sensorEnum: INuiSensor;
count,
i: integer;
w, h: cardinal;
format: NUI_IMAGE_RESOLUTION;

BEGIN
result := false;
Fsensor := NIL;

format := NUI_IMAGE_RESOLUTION_640x480;

//Devuelve el número de sensores conectados al PC
IF failed(NuiGetSensorCount(count)) THEN
exit;

//Crea una instancia del sensor que será la que la aplicación use
FOR i := 0 TO count - 1 DO
BEGIN
IF failed(NuiCreateSensorByIndex(i, sensorEnum)) THEN
continue;

IF sensorEnum.NuiStatus = S_OK THEN
BEGIN
Fsensor := sensorEnum;
break;
END;
END;

IF NOT assigned(Fsensor) THEN
BEGIN
showmessage('no se ha asignado Fsensor');
exit;
END;

//Inicializa el sensor
IF failed(Fsensor.NuiInitialize(
NUI_INITIALIZE_FLAG_USES_COLOR //añadido
OR NUI_INITIALIZE_FLAG_USES_SKELETON
OR NUI_INITIALIZE_FLAG_USES_SKELETON
OR NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX 



)) THEN
exit;

//Creamos los eventos
FskeletonEvent := CreateEvent(NIL, true, false, NIL);
FdepthEvent := CreateEvent(NIL, true, false, NIL);

CASE RGPuntos1.itemindex OF
0:
BEGIN
Fsensor.NuiSkeletonTrackingEnable(FskeletonEvent,
NUI_SKELETON_TRACKING_FLAG_SUPPRESS_NO_FRAME_DATA
OR NUI_SKELETON_TRACKING_FLAG_ENABLE_SEATED_SUPPORT);
END;
1:
BEGIN
Fsensor.NuiSkeletonTrackingEnable(FskeletonEvent,
NUI_SKELETON_TRACKING_FLAG_SUPPRESS_NO_FRAME_DATA
OR NUI_SKELETON_TRACKING_FLAG_ENABLE_IN_NEAR_RANGE);
END;
END;

FskeletonEvents := TEventDispatcherThread.createWith(Handle,FskeletonEvent);

CASE RGImagen1.itemindex OF
0: opcion := NUI_IMAGE_TYPE_COLOR;
1: opcion := NUI_IMAGE_TYPE_COLOR_YUV;
2: opcion := NUI_IMAGE_TYPE_DEPTH;
3: opcion := NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX;
END;


IF failed(Fsensor.NuiImageStreamOpen(
opcion, //tipo de imagen (depende de -2-)     NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX
format, //resolucion
0, //imagen frame flags  
2, //numero de frames del buffer
FdepthEvent, //handle del siguiente frame event  
FdepthStream //puntero al handle del stream
)) THEN
BEGIN
showmessage(' no se puede abrir la camara de kinect ');
exit;
END;


IF failed(Fsensor.NuiImageStreamSetImageFrameFlags(
FdepthStream, //
NUI_IMAGE_STREAM_FLAG_SUPPRESS_NO_FRAME_DATA 


)) THEN
exit;

FdepthEvents := TEventDispatcherThread.createWith(Handle,
FdepthEvent);

NuiImageResolutionToSize(format, w, h);

Fbitmap := TBitmap.Create;
Fbitmap.Width := w;
Fbitmap.Height := h;
Fbitmap.PixelFormat := pf32bit;


result := true;

END;

Y ahora vamos a ver que ocurre cuando kinect crea un nuevo Frame:



PROCEDURE TMain1.OnNewDepthFrame;
VAR
imageFrame: NUI_IMAGE_FRAME;
texture: INuiFrameTexture;
lock: NUI_LOCKED_RECT;

depth: pword;
color: pinteger;
x, y: integer;
t1, t2, freq: int64;

w, h: cardinal;

// añadido
LBitmap: TBitmap;
pdest, psource: pIntegerArray;

BEGIN
QueryPerformanceCounter(t1);

IF (FdepthStream = INVALID_HANDLE_VALUE) OR failed(Fsensor.
NuiImageStreamGetNextFrame(FdepthStream, 0, @imageFrame)) THEN
exit;

NuiImageResolutionToSize(imageFrame.eResolution, w, h);

TRY
texture := imageFrame.pFrameTexture;
IF NOT assigned(texture) THEN
exit;
// Bloquea los datos del frame obtenido para que  Kinect conozca que no hay que modificarlo// hasta que se termine de leer
IF failed(texture.LockRect(0, @lock, NIL, 0)) THEN
exit;

TRY

IF CBSoloJugador1.checked THEN
ColorJugador := 1
ELSE
ColorJugador := 255;

CASE RGImagen1.itemindex OF
2, 3: //imagen de profundidad
BEGIN
//pitch = The number of bytes of data in a row
IF cardinal(lock.Pitch) <> (2 * w) THEN
exit;
END;
END;

CASE RGImagen1.itemindex OF
0, 1: //imagen en color
BEGIN
label3.caption := inttoStr(cardinal(lock.Pitch));
label4.caption := inttoStr(cardinal(lock.size));
//para que se vea la imagen en el bitmap
LBitmap := TBitmap(Image1.Picture.Graphic);

IF @ImageFrame <> NIL THEN
BEGIN
//pbits = A pointer to the upper-left corner of the rectangle.
psource := lock.pbits;
//depth
FOR y := 0 TO 479 DO //479
BEGIN
pDest := LBitmap.ScanLine[y];
//color
FOR x := 0 TO 639 DO //639
BEGIN

pDest[x] := pSource[x];

END;
Inc(NativeInt(pSource), Lock.Pitch);
END;

Image1.Refresh;
END;
END;
2, 3:
BEGIN
label3.caption := inttoStr(cardinal(lock.Pitch));
label4.caption := inttoStr(cardinal(lock.size));
depth := lock.pBits;
//pbits es un puntero a la esquina superior izquierda del rectangulo
FOR y := 0 TO h - 1 DO
BEGIN
color := Fbitmap.ScanLine[y];
FOR x := 0 TO w - 1 DO
BEGIN

IF (depth^ AND NUI_IMAGE_PLAYER_INDEX_MASK) <> 0 THEN
color^ := $FF
ELSE IF (depth^ >= NUI_IMAGE_DEPTH_MINIMUM_NEAR_MODE)
AND
(depth^ <= NUI_IMAGE_DEPTH_MAXIMUM_NEAR_MODE) THEN
BEGIN

color^ := round(((depth^ SHR
NUI_IMAGE_PLAYER_INDEX_SHIFT)
-
(NUI_IMAGE_DEPTH_MINIMUM_NEAR_MODE SHR
NUI_IMAGE_PLAYER_INDEX_SHIFT)) /
(NUI_IMAGE_DEPTH_MAXIMUM_NEAR_MODE SHR
NUI_IMAGE_PLAYER_INDEX_SHIFT) * ColorJugador);
//255

color^ := (color^ AND $FF) OR ((color^ AND $FF) SHL 8)
OR
((color^ AND $FF) SHL 16);

END
ELSE
color^ := 0; //0

inc(color);
inc(depth);
END;

END; //2,3
END;
END; //case rgimagen1.itemindex

CASE RGImagen1.itemindex OF
2, 3:
BEGIN
canvas.Draw(0, 0, Fbitmap);
END;
END;

FINALLY
texture.UnlockRect(0);
END;

FINALLY
Fsensor.NuiImageStreamReleaseFrame(FdepthStream, @imageFrame);
END;

QueryPerformanceCounter(t2);
QueryPerformanceFrequency(freq);
Frecuencia1.Caption := FormatFloat('0,', (t2 - t1) * 1000000 DIV Freq);

END;

Y con el siguiente procedimiento tratamos los datos de profundidad del frame y del esqueleto de la figura










PROCEDURE TMain1.OnNewSkeletonFrame;
VAR
frame: NUI_SKELETON_FRAME;
sp: NUI_TRANSFORM_SMOOTH_PARAMETERS;

index,
i: integer;
px, py: single;
BEGIN
fillchar(frame, sizeof(frame), 0);

IF failed(Fsensor.NuiSkeletonGetNextFrame(0, @frame)) THEN
exit;

index := -1;
FOR i := 0 TO NUI_SKELETON_COUNT - 1 DO
IF frame.SkeletonData[i].eTrackingState =
NUI_SKELETON_TRACKED THEN
BEGIN
index := i;
break;
END;

IF index < 0 THEN
exit;

sp.fSmoothing := 0.7;
sp.fCorrection := 0.3;
sp.fPrediction := 0.4;
sp.fJitterRadius := 1.0;
sp.fMaxDeviationRadius := 0.5;

//si se suprime la linea de abajo los puntos del cuerpo
//siguen con mayor rapidez a la figura humana
IF NOT (rapido1.checked) THEN
IF failed(Fsensor.NuiTransformSmooth(@frame, @sp)) THEN
exit;


PosicionaParteDelCuerpo(frame, sp, index, 11, rightPanel);
PosicionaParteDelCuerpo(frame, sp, index, 7, LeftPanel);
PosicionaParteDelCuerpo(frame, sp, index, 1, Columna);
PosicionaParteDelCuerpo(frame, sp, index, 2, HombroCentro);
PosicionaParteDelCuerpo(frame, sp, index, 4, HombroIzq);
PosicionaParteDelCuerpo(frame, sp, index, 8, HombroDer);
PosicionaParteDelCuerpo(frame, sp, index, 9, CodoDer);
PosicionaParteDelCuerpo(frame, sp, index, 5, CodoIzq);
PosicionaParteDelCuerpo(frame, sp, index, 10, MunecaDer);
PosicionaParteDelCuerpo(frame, sp, index, 6, MunecaIzq); 
PosicionaParteDelCuerpo(frame, sp, index, 12, CaderaDer); 
PosicionaParteDelCuerpo(frame, sp, index, 16, CaderaIzq);
PosicionaParteDelCuerpo(frame, sp, index, 17, RodillaDer);
PosicionaParteDelCuerpo(frame, sp, index, 13, RodillaIzq); 
PosicionaParteDelCuerpo(frame, sp, index, 18, TobilloDer); 
PosicionaParteDelCuerpo(frame, sp, index, 14, TobilloIzq);
PosicionaParteDelCuerpo(frame, sp, index, 19, PieDer); 
PosicionaParteDelCuerpo(frame, sp, index, 15, PieIzq);
PosicionaParteDelCuerpo(frame, sp, index, 3, cabeza);

END;

PROCEDURE tMain1.PosicionaParteDelCuerpo(VAR frame: NUI_SKELETON_FRAME;
VAR sp: NUI_TRANSFORM_SMOOTH_PARAMETERS; VAR index: integer;
Parte: integer; Panel: tpanel);
VAR
px, py: single;
BEGIN
IF frame.SkeletonData[index].eSkeletonPositionTrackingState[
Parte] =
NUI_SKELETON_POSITION_TRACKED THEN
BEGIN

NuiTransformSkeletonToDepthImage(frame.SkeletonData[index].
SkeletonPositions[Parte], px, py,
NUI_IMAGE_RESOLUTION_640x480);

Panel.Visible := true;
//  Panel.Left := round(px) ;
// Panel.Top := round(py);

Panel.Left := round(px) - Panel.Width SHR 1; //esto lo ponia antes
Panel.Top := round(py) - Panel.Height SHR 1; //esto lo ponia antes

END
ELSE
Panel.Visible := false;
END;






Descargar fuente









Relacionado:

No hay comentarios:

Publicar un comentario