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
Wissen ist Macht. Das ändert aber so gut wie nichts an der Übermacht der Dummheit.