Einzelnen Beitrag anzeigen

Benutzerbild von ich2
ich2

Registriert seit: 7. Dez 2005
Ort: Würzburg
54 Beiträge
 
#1

Fingertip Detection für Microsoft Kinect unter Delphi 7

  Alt 27. Dez 2010, 23:11
Hallo zusammen,

seitdem Microsoft vor einigen Wochen seine neue controllerlose Steuerung auf den Markt gebracht hat, ist so einiges passiert:
es hat nur 2 Wochen gedauert, bis der erste Open-Source-Treiber da war:
http://codelaboratories.com/nui

von da an wurde schon einige spektakuläre Videos gezeigt:
http://kinecthacks.net

...ich konnte nicht widerstehen und habe mir auch eine solche 3D-Kamera zugelegt und auch gleich ein wenig damit herumgespielt...

wenn erstmal die Treiber installiert sind, dann kann man recht einfach auf die Kamera zugreifen:
http://files.codes-sources.com

(Datei im Anhang: UKinect.pas)
Delphi-Quellcode:
...

type
  CLNUIMotor = record
    handle: ^Integer;
  end;

  CLNUICamera = record
    handle: ^Integer;
  end;

  CLNUIDepth = record
    table: array [ 0..640, 0..480 ] of ^Byte;
  end;

  CLNUIcolor = record
    table: array [ 0..640, 0..480 ] of ^Byte;
  end;

  TAcceleroMeter = packed record
    X, Y, Z: Short;
  end;

var
  KinectHandle: CLNUIMotor;
  KinectSerial: String;
  KinectCameraHandle: CLNUICamera;

  function CreateNUIMotor (): CLNUIMotor; cdecl; external 'CLNUIDevice.dll';
  function DestroyNUIMotor ( _handle: CLNUIMotor ): Boolean; cdecl; external 'CLNUIDevice.dll';
  function GetNUIMotorSerial ( _handle: CLNUIMotor ): PAnsiChar; cdecl; external 'CLNUIDevice.dll';
  function SetNUIMotorPosition ( _handle: CLNUIMotor; _value: Integer ): Boolean; cdecl; external 'CLNUIDevice.dll';
  function GetNUIMotorAccelerometer ( _handle: CLNUIMotor; var value_X: short; var value_Y: short; var value_Z: short ): Boolean; cdecl; external 'CLNUIDevice.dll';
  function SetNUIMotorLED ( _handle: CLNUIMotor; value_Led: Byte ): Boolean; cdecl; external 'CLNUIDevice.dll';

  function CreateNUICamera (): CLNUICamera; cdecl; external 'CLNUIDevice.dll';
  function DestroyNUICamera ( _handle: CLNUICamera ): Boolean; cdecl; external 'CLNUIDevice.dll';
  function StartNUICamera ( _handle: CLNUICamera ): Boolean; cdecl; external 'CLNUIDevice.dll';
  function StopNUICamera ( _handle: CLNUICamera ): Boolean; cdecl; external 'CLNUIDevice.dll';
  function GetNUICameraDepthFrameRAW ( _handle: CLNUICamera; var pData: CLNUIDepth; waitTimeout: integer = 2000 ): Boolean; cdecl; external 'CLNUIDevice.dll';
  function GetNUICameraDepthFrameRGB32 ( _handle: CLNUICamera; var pData: CLNUIDepth; waitTimeout: integer = 2000 ): Boolean; cdecl; external 'CLNUIDevice.dll';
  function GetNUICameraColorFrameRAW ( _handle: CLNUICamera; var pData: CLNUIcolor; waitTimeout: integer = 2000 ): Boolean; cdecl; external 'CLNUIDevice.dll';
  function GetNUICameraColorFrameRGB24 ( _handle: CLNUICamera; var pData: CLNUIcolor; waitTimeout: integer = 2000 ): Boolean; cdecl; external 'CLNUIDevice.dll';
  function GetNUICameraColorFrameRGB32 ( _handle: CLNUICamera; var pData: CLNUIcolor; waitTimeout: integer = 2000 ): Boolean; cdecl; external 'CLNUIDevice.dll';

...

...mich interessierte es nun, wie man auf 'einfache' Art und Weise eine Hand-Detection bzw. Finger-Detection realisiert.
ich möchte hier einmal meine Idee vorstellen und hoffe auf weitere Anregungen:

Im Grunde wird einem das Schwerste von der Kamera selbst abgenommen: das Herausfiltern der Hand!

Man bekommt von der Kinect-Kamera 2 Daten-Streams: einmal ein 'normales' WebCam-Bild und einmal das dazugehörige Tiefen-Bild (bild1)

Mit Hilfe der Tiefen-kodierung kann man sich die Hand herausfiltern (hier gehe ich davon aus, dass die hand vom Körper nach vorne gestreckt wird). Als Ergebnis bekommt man die Hand ans sich (bild2).

Als nächstes möchte ich nur die Konturen der Hand haben. Dafür lasse ich einen Laplace-Filter drüberlaufen (Faltung mit einem 3x3-Filterkern). das Ergebnis sind die Konturen der Hand (bild3).

So weit so gut...um nun die Finger oder besser ersteinmal die Hand zu detektieren, verwende ich die Hough-Transformation für Kreise. Das Transformierte Bild sieht dann für die Fingererkennung etwa folgendermaßen aus: (bild4). Es wird um jeden Punkt der Hand-Kontur ein Kreis mit einem bestimmten Radius gezeichnet. In dem sogenannten Akkumulatorraum werden die Schnittpunkte aller Kreise ausgewertet und mit einem Schwellwert ausgelesen.

Die gesammelten Punkte sind aber noch zu ungenau und werden mit einem quasi k-means-Algorithmus gewichet und weiter eingegrenzt.

Für die Handerkennung geht man ähnlich vor, man verwendet um Grunde nur einen anderen Radius für die Hough-Transformation.

...hier mal eine kleine Übersicht:
also erstmal ein paar Definitionen:
Delphi-Quellcode:
procedure findfinger3 ( const bmpSrc: TPicture; th, r, th2, th3, th4: Integer );
type
  TMeanPoints = packed record
    vec: TVector2D;
    count: Integer;
    value: Real;
  end;
  TMeanPoints_Type = array of TMeanPoints;

  TAkkumulator = packed record
    v1: SmallInt;
  end;

var
  counter, counter2, _th, _r, X, Y, Color: Integer;
  LigS, LigD: pLigScan;
  _Akkumulator: array [ 1..640, 1..480 ] of TAkkumulator;
  points: array of TVector2D;
  points_count: Integer;
  meanpoints: TMeanPoints_Type;
  meanpoints_count: Integer;
  test: Boolean;
  d, t: real;
  phi: SmallInt;
...hier kommt jetzt die Hough-Transformation: ein Bresenham-Algo für den Kreis kommt hier zum Einsatz:

Delphi-Quellcode:
  procedure Add_Akkumulator ( _X, _Y, _R, _v: Integer );
  var
    x_, y_, d, dx, dxy: Integer;
  begin
    x_ := 0;
    y_ := _r;
    d := 1 - _r;
    dx := 3;
    dxy := -2 * _r + 5;

    while ( y_ >= x_ ) do begin

      inc ( _Akkumulator [ _X + x_, _Y + y_ ].v1, _v );
      inc ( _Akkumulator [ _X + y_, _Y + x_ ].v1, _v );
      inc ( _Akkumulator [ _X + y_, _Y - x_ ].v1, _v );
      inc ( _Akkumulator [ _X + x_, _Y - y_ ].v1, _v );
      inc ( _Akkumulator [ _X - x_, _Y - y_ ].v1, _v );
      inc ( _Akkumulator [ _X - y_, _Y - x_ ].v1, _v );
      inc ( _Akkumulator [ _X - y_, _Y + x_ ].v1, _v );
      inc ( _Akkumulator [ _X - x_, _Y + y_ ].v1, _v );

      if ( d < 0 ) then begin
        d := d + dx;
        dx := dx + 2;
        dxy := dxy + 2;
        inc ( x_ )
      end else begin
        d := d + dxy;
        dx := dx + 2;
        dxy := dxy + 4;
        inc ( x_ );
        dec ( y_ );
      end;
    end;
  end;
...dann wieder ein wenig Zeug...

Delphi-Quellcode:
begin

  points_count := 0;
  _r := r;
  _th := th;

  if bmpSrc = nil then
    EXIT;

  try
    for X := 1 to 640 do
      for y := 1 to 480 do
        _Akkumulator [ X, Y ].v1 := 0;
...hier werden die Konturen ausgelesen und für jeden Punkt wird der Kreis (hier werden 2 Kreise) gezeichnet...

Delphi-Quellcode:
    // collect all points
    for Y := _r + 1 to bmpSrc.height - _r - 2 do begin
      LigS := bmpSrc.Bitmap.ScanLine [ Y ];
      for X := _r + 1 to bmpSrc.width - _r - 2 do begin

        if LigS [ X ].rgbtBlue > 0 then begin

          Add_Akkumulator ( X, Y, _r, 5 );
          Add_Akkumulator ( X, Y, _r-1, 5 );
        end;

      end;
    end;
...dann werden die besten Werte ausgelesen und in weiteren Punkten zwischengespeichert...

Delphi-Quellcode:

    // get the maxima
    for Y := 1 to 480 do //160 do
      for X := 1 to 640 do begin

        if ( ( _Akkumulator [ X, Y ].v1 > _th ) ) then begin

          inc ( points_count );
          setlength ( points, points_count );
          points [ points_count - 1 ] := Vector2D ( X, Y );
          if form1.CheckBox4.Checked then
            Form1.Image3.Canvas.Pixels [ X, Y ] := _Akkumulator [ X, Y ].v1;
        end else if _Akkumulator [ X, Y ].v1 <= -1 then begin
          Form1.Image3.Canvas.Pixels [ X, Y ] := clWhite;
        end;

      end;
...hier werden nun die gewicheteten Mittel-Punkte berechnet...

Delphi-Quellcode:

    t := th2 / 10;

    // compute the mean-points
    meanpoints_count := 0;
    meanpoints := nil;
    for counter := 1 to points_count do begin
      test := false;
      for counter2 := 1 to meanpoints_count do begin
        d := Get_Distance ( points [ counter - 1 ], meanpoints [ counter2 - 1 ].vec );
        if d < t{1.4*r} then begin
          inc ( meanpoints [ counter2 - 1 ].count );
          meanpoints [ counter2 - 1 ].value := meanpoints [ counter2 - 1 ].value + d;
          meanpoints [ counter2 - 1 ].vec := Add_Vector ( meanpoints [ counter2 - 1 ].vec, Scale_Vector ( Sub_Vector ( points [ counter - 1 ], meanpoints [ counter2 - 1 ].vec ), 1 / meanpoints [ counter2 - 1 ].count ) );
          test := true;
          break;
        end
      end;

      if not test then begin
        inc ( meanpoints_count );
        setlength ( meanpoints, meanpoints_count );
        meanpoints [ meanpoints_count - 1 ].vec := points [ counter - 1 ];
        meanpoints [ meanpoints_count - 1 ].count := 1;
        meanpoints [ meanpoints_count - 1 ].value := 0;
      end;

    end;
...und schließlich werden die Punkte nochmal gefiltert und dann entgültig gespeichert...

Delphi-Quellcode:
    x := th3;
    d := th4/10;

    r := _r;
    for counter := 1 to meanpoints_count do begin
      if ( ( ( meanpoints [ counter - 1 ].value/meanpoints [ counter - 1 ].count ) < d ) and ( meanpoints [ counter - 1 ].count > x ) ) then begin

        inc ( hands_count );
        setlength ( hands, hands_count );
        hands [ hands_count - 1 ].coords := meanpoints [ counter - 1 ].vec;
        hands [ hands_count - 1 ].value := round ( meanpoints [ counter - 1 ].value/meanpoints [ counter - 1 ].count );
      end;

    end;
  except
    exit;
  end;
end;
Auf weiteren Bildern (bilder) kann man bereits erkennen, dass die Hände schon relativ gut erkannt werden und auch der Status 'Hand offen' und 'Hand geschlossen' erkennbar ist (weiße und rote Einrahmung).
Die Finger können dann 'fast' genau erkannt werden...da muss noch ein wenig verbessert werden.

...im Anhang ist das fertig kompilierte Projekt für Delphi7 zu finden...

viel Spaß damit

Grüße
Angehängte Dateien
Dateityp: rar kinect5.rar (416,7 KB, 94x aufgerufen)
Wissen ist Macht. Das ändert aber so gut wie nichts an der Übermacht der Dummheit.
  Mit Zitat antworten Zitat