1 /***********************************************************************
2 HandExtractor - Class to identify hands from a depth image.
3 Copyright (c) 2015-2016 Oliver Kreylos
4 Copyright (c) 2019 Scottsdale Community College
6 This file is part of the Augmented Reality Sandbox (SARndbox).
8 The Augmented Reality Sandbox is free software; you can redistribute it
9 and/or modify it under the terms of the GNU General Public License as
10 published by the Free Software Foundation; either version 2 of the
11 License, or (at your option) any later version.
13 The Augmented Reality Sandbox is distributed in the hope that it will be
14 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU General Public License along
19 with the Augmented Reality Sandbox; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 ***********************************************************************/
23 #include "HandExtractor.h"
25 #include <Misc/Utility.h>
26 #include <Misc/FunctionCalls.h>
27 #include <Math/Math.h>
28 #include <Math/Interval.h>
29 #include <Geometry/Vector.h>
40 struct Span
{ // Helper structure to extract foreground blobs from a depth image
43 unsigned int y
; // Row index of the span
44 unsigned int start
; // Starting column of span
45 unsigned int end
; // Ending column of span
46 unsigned int parent
; // Span's parent span
47 unsigned int numPixels
; // Number of pixels in the span's subtree
48 unsigned int blobId
; // Blob ID of a root span
51 struct BlobOrigin
{ // Helper structure to store a point on the border of a foreground blob
54 bool assigned
; // Flag if the blob origin has already been assigned
55 unsigned int x
, y
; // Coordinates of blob origin in depth frame
56 const unsigned short* biPtr
; // Pointer to blob origin in blob ID image
59 struct Corner
{ // Helper class to store corners in blob images
62 int cornerType
; // Corner type, +1: finger tip, -1: finger nook
63 unsigned start
; // Boundary pixel index at which the corner started
64 int x
, y
; // Corner position in depth frame
67 typedef Math::Interval
<float> Interval
;
68 typedef Geometry::Point
<float, 2> Point2
;
69 typedef Geometry::Vector
<float, 2> Vector2
;
75 void drawLine(Images::RGBImage
& image
, const Point2
& p0
, const Point2
& p1
,
76 const Images::RGBImage::Color
& color
) {
77 int w
= int(image
.getWidth());
78 int h
= int(image
.getHeight());
79 int x0
= int(Math::floor(p0
[0]));
80 int y0
= int(Math::floor(p0
[1]));
81 int x1
= int(Math::floor(p1
[0]));
82 int y1
= int(Math::floor(p1
[1]));
86 if(Math::abs(dx
) > Math::abs(dy
)) {
87 /* X direction leads: */
94 Images::RGBImage::Color
* lPtr
= image
.modifyPixels() + y0
* stride
+ x0
;
97 for(int x
= 0; x
<= dx
; ++x
, ++lPtr
) {
98 if(x0
+ x
>= 0 && x0
+ x
< w
&& y0
+ y
>= 0 && y0
+ y
< h
)
105 } else if(yf
<= -dx
) {
112 /* Y direction leads: */
119 Images::RGBImage::Color
* lPtr
= image
.modifyPixels() + y0
* stride
+ x0
;
122 for(int y
= 0; y
<= dy
; ++y
, lPtr
+= stride
) {
123 if(x0
+ x
>= 0 && x0
+ x
< w
&& y0
+ y
>= 0 && y0
+ y
< h
)
130 } else if(xf
<= -dy
) {
139 void drawCircle(Images::RGBImage
& image
, const Point2
& center
, float radius
,
140 const Images::RGBImage::Color
& color
) {
141 Images::RGBImage::Color
* imgBase
= image
.modifyPixels();
143 size
[0] = int(image
.getSize(0));
144 size
[1] = int(image
.getSize(1));
146 /* Draw the circle: */
147 int cx
= int(Math::floor(center
[0]));
148 int cy
= int(Math::floor(center
[1]));
149 int r
= int(Math::floor(radius
+ 0.5f
));
150 ptrdiff_t stride
= ptrdiff_t(size
[0]);
151 #if 0 // Draw a filled rain circle
152 int ymin
= cy
- r
>= 0 ? cy
- r
: 0;
153 int ymax
= cy
+ r
<= size
[1] - 1 ? cy
+ r
: size
[1] - 1;
154 for(int y
= ymin
; y
<= ymax
; ++y
) {
155 int ry
= int(Math::floor(Math::sqrt(float(r
* r
) - float((y
- cy
) * (y
- cy
))) + 0.5f
));
156 int xmin
= cx
- ry
>= 0 ? cx
- ry
: 0;
157 int xmax
= cx
+ ry
<= size
[0] - 1 ? cx
+ ry
: size
[0] - 1;
158 Images::RGBImage::Color
* iPtr
= imgBase
+ (y
* stride
+ xmin
);
159 for(int x
= xmin
; x
<= xmax
; ++x
, ++iPtr
)
162 #else // Draw a hollow rain circle
163 Images::RGBImage::Color
* imgCenter
= imgBase
+ (cy
* stride
+ cx
);
164 for(int y
= 0;; ++y
) {
165 int x
= int(Math::floor(Math::sqrt(float(r
* r
) - float(y
* y
)) + 0.5f
));
168 if(cy
+ y
< size
[1]) {
170 imgCenter
[y
* stride
+ x
] = color
;
172 imgCenter
[y
* stride
- x
] = color
;
174 if(cy
+ x
< size
[1]) {
176 imgCenter
[x
* stride
+ y
] = color
;
178 imgCenter
[x
* stride
- y
] = color
;
182 imgCenter
[-y
* stride
+ x
] = color
;
184 imgCenter
[-y
* stride
- x
] = color
;
188 imgCenter
[-x
* stride
+ y
] = color
;
190 imgCenter
[-x
* stride
- y
] = color
;
198 /**************************************
199 Static elements of class HandExtractor:
200 **************************************/
202 const unsigned short HandExtractor::invalidBlobId
= 0xffffU
;
203 const int HandExtractor::walkDx
[8] = { 1, 1, 0, -1, -1, -1, 0, 1};
204 const int HandExtractor::walkDy
[8] = { 0, 1, 1, 1, 0, -1, -1, -1};
206 /******************************
207 Methods of class HandExtractor:
208 ******************************/
210 void* HandExtractor::extractorThreadMethod(void) {
211 unsigned int lastInputFrameVersion
= 0;
214 Kinect::FrameBuffer frame
;
216 Threads::MutexCond::Lock
inputLock(inputCond
);
218 /* Wait until a new frame arrives or the program shuts down: */
219 while(runExtractorThread
&& lastInputFrameVersion
== inputFrameVersion
)
220 inputCond
.wait(inputLock
);
222 /* Bail out if the program is shutting down: */
223 if(!runExtractorThread
)
226 /* Work on the new frame: */
228 lastInputFrameVersion
= inputFrameVersion
;
231 /* Prepare a new output hand list: */
232 HandList
& newHandList
= extractedHands
.startNewValue();
234 /* Extract hands from the new input frame: */
235 extractHands(frame
.getData
<DepthPixel
>(), newHandList
, 0);
237 /* Finalize the new extracted hands list in the output buffer: */
238 extractedHands
.postNewValue();
240 /* Pass the new output frame to the registered receiver: */
241 if(handsExtractedFunction
!= 0)
242 (*handsExtractedFunction
)(newHandList
);
248 HandExtractor::HandExtractor(const unsigned int sDepthFrameSize
[2],
249 const HandExtractor::PixelDepthCorrection
* sPixelDepthCorrection
,
250 const PTransform
& sDepthProjection
)
251 : pixelDepthCorrection(sPixelDepthCorrection
), depthProjection(sDepthProjection
),
252 inputFrameVersion(0), runExtractorThread(false),
253 maxFgDepth(0x07ffU
- 1U), maxDepthDist(1), minBlobSize(1500), maxBlobSize(150000),
255 snakeLength(50), snake(0),
256 maxCornerEnterDist(28), minCenterDist(10), minCornerExitDist(32),
257 minHandProbability(0.15f
),
258 handsExtractedFunction(0) {
259 /* Copy the depth frame size: */
260 for(int i
= 0; i
< 2; ++i
)
261 depthFrameSize
[i
] = sDepthFrameSize
[i
];
263 /* Allocate the blob ID image: */
264 blobIdImage
= new unsigned short[(depthFrameSize
[1] + 2) * (depthFrameSize
[0] + 2)];
265 biStride
= depthFrameSize
[0] + 2;
267 /* Initialize the border of the blob ID image: */
268 unsigned short* biPtr
= blobIdImage
;
269 for(unsigned int x
= 1; x
< depthFrameSize
[0] + 2; ++x
, ++biPtr
)
270 *biPtr
= invalidBlobId
;
271 for(unsigned int y
= 1; y
< depthFrameSize
[1] + 2; ++y
, biPtr
+= biStride
)
272 * biPtr
= invalidBlobId
;
273 for(unsigned int x
= 1; x
< depthFrameSize
[0] + 2; ++x
, --biPtr
)
274 *biPtr
= invalidBlobId
;
275 for(unsigned int y
= 1; y
< depthFrameSize
[1] + 2; ++y
, biPtr
-= biStride
)
276 * biPtr
= invalidBlobId
;
278 /* Calculate the array of edge walking pointer offsets: */
279 for(int i
= 0; i
< 8; ++i
)
280 walkOffsets
[i
] = walkDy
[i
] * biStride
+ walkDx
[i
];
282 /* Initialize the edge walking snake: */
283 setSnakeLength(snakeLength
);
285 /* Start the hand extraction thread: */
286 runExtractorThread
= true;
287 extractorThread
.start(this, &HandExtractor::extractorThreadMethod
);
290 HandExtractor::~HandExtractor(void) {
291 /* Shut down the extraction thread: */
293 Threads::MutexCond::Lock
inputLock(inputCond
);
294 runExtractorThread
= false;
297 extractorThread
.join();
299 delete[] blobIdImage
;
303 void HandExtractor::setMaxFgDepth(DepthPixel newMaxFgDepth
) {
304 maxFgDepth
= newMaxFgDepth
;
307 void HandExtractor::setMaxDepthDist(unsigned int newMaxDepthDist
) {
308 maxDepthDist
= newMaxDepthDist
;
311 void HandExtractor::setBlobSizeRange(unsigned int newMinBlobSize
, unsigned int newMaxBlobSize
) {
312 minBlobSize
= newMinBlobSize
;
313 maxBlobSize
= newMaxBlobSize
;
316 void HandExtractor::setSnakeLength(unsigned int newSnakeLength
) {
317 snakeLength
= newSnakeLength
;
319 /* Re-allocate the snake array: */
321 snake
= new EdgePixel
[snakeLength
];
324 void HandExtractor::setCornerDists(int newMaxCornerEnterDist
, int newMinCenterDist
,
325 int newMinCornerExitDist
) {
326 maxCornerEnterDist
= newMaxCornerEnterDist
;
327 minCenterDist
= newMinCenterDist
;
328 minCornerExitDist
= newMinCornerExitDist
;
331 void HandExtractor::extractHands(const HandExtractor::DepthPixel
* depthFrame
,
332 HandExtractor::HandList
& hands
, Images::RGBImage
* blobImage
) {
333 Images::RGBImage::Color
* imgPtr
= 0;
335 /* Create the result image: */
336 blobImage
->clear(Images::RGBImage::Color(0, 0, 0));
337 imgPtr
= blobImage
->replacePixels();
340 /* Extract all four-connected foreground blobs from the given depth frame: */
341 std::vector
<Span
> spans
;
342 unsigned int numSpans
= 0;
343 unsigned int lastRowSpan
= 0;
344 const DepthPixel
* dfRowPtr
= depthFrame
;
345 for(unsigned int y
= 0; y
< depthFrameSize
[1]; ++y
, dfRowPtr
+= depthFrameSize
[0]) {
346 const DepthPixel
* dfPtr
= dfRowPtr
;
347 unsigned int rowSpan
= numSpans
;
350 /* Find the beginning of the next foreground span: */
351 for(; x
< depthFrameSize
[0] && *dfPtr
> maxFgDepth
; ++x
, ++dfPtr
)
353 if(x
>= depthFrameSize
[0])
356 /* Start a new foreground span: */
361 /* Trace out the current foreground span: */
362 DepthPixel lastDepth
= *dfPtr
;
365 for(; x
< depthFrameSize
[0] && *dfPtr
<= maxFgDepth
&& *dfPtr
+ maxDepthDist
>= lastDepth
366 && *dfPtr
<= lastDepth
+ maxDepthDist
; ++x
, ++dfPtr
)
369 /* Finalize and store the new foreground span: */
371 newSpan
.parent
= numSpans
;
372 newSpan
.numPixels
= newSpan
.end
- newSpan
.start
;
373 newSpan
.blobId
= invalidBlobId
;
374 spans
.push_back(newSpan
);
377 /* Skip any spans from the previous row that were just passed by: */
378 for(; lastRowSpan
< rowSpan
&& spans
[lastRowSpan
].end
< newSpan
.start
; ++lastRowSpan
)
381 /* Check if the current span links up with any from the previous row: */
382 for(unsigned int lrs
= lastRowSpan
; lrs
< rowSpan
&& spans
[lrs
].start
<= newSpan
.end
; ++lrs
) {
383 /* Check if the two spans have depth in common: */
384 unsigned int o1
= Misc::max(newSpan
.start
, spans
[lrs
].start
);
385 unsigned int o2
= Misc::min(newSpan
.end
, spans
[lrs
].end
);
386 const DepthPixel
* lrsPtr1
= dfRowPtr
+ o1
;
387 const DepthPixel
* lrsPtr0
= lrsPtr1
- depthFrameSize
[0];
388 bool canLink
= false;
389 for(unsigned int o
= o1
; o
< o2
&& !canLink
; ++o
, ++lrsPtr0
, ++lrsPtr1
)
390 canLink
= *lrsPtr0
+ maxDepthDist
>= *lrsPtr1
&& *lrsPtr0
<= *lrsPtr1
+ maxDepthDist
;
392 /* Merge the two spans if they can link: */
394 /* Find the roots of the two spans' respective subtrees: */
395 unsigned int root1
= lrs
;
396 while(root1
!= spans
[root1
].parent
)
397 root1
= spans
[root1
].parent
;
398 unsigned int root2
= numSpans
- 1;
399 while(root2
!= spans
[root2
].parent
)
400 root2
= spans
[root2
].parent
;
403 /* Make the first span the new root: */
404 spans
[root2
].parent
= root1
;
405 spans
[root1
].numPixels
+= spans
[root2
].numPixels
;
406 } else if(root1
> root2
) {
407 /* Make the second span the new root: */
408 spans
[root1
].parent
= root2
;
409 spans
[root2
].numPixels
+= spans
[root1
].numPixels
;
415 /* Skip any leftover spans from the previous row: */
416 lastRowSpan
= rowSpan
;
419 /* Assign consecutive blob IDs to all root spans: */
420 unsigned int nextBlobId
= 0;
421 for(unsigned int i
= 0; i
< numSpans
; ++i
) {
422 /* Check if the span is a root span: */
423 if(spans
[i
].parent
== i
) {
424 if(spans
[i
].numPixels
>= minBlobSize
&& spans
[i
].numPixels
<= maxBlobSize
) {
425 spans
[i
].blobId
= nextBlobId
;
429 /* Find the root of the span's subtree: */
430 unsigned int root
= spans
[i
].parent
;
431 while(root
!= spans
[root
].parent
)
432 root
= spans
[root
].parent
;
434 /* Assign the span's blob ID from the root: */
435 spans
[i
].blobId
= spans
[root
].blobId
;
441 /* Create the result color image: */
442 static const Images::RGBImage::Color blobColors
[18] = {
443 Images::RGBImage::Color(255, 0, 0),
444 Images::RGBImage::Color(255, 255, 0),
445 Images::RGBImage::Color(0, 255, 0),
446 Images::RGBImage::Color(0, 255, 255),
447 Images::RGBImage::Color(0, 0, 255),
448 Images::RGBImage::Color(255, 0, 255),
449 Images::RGBImage::Color(128, 0, 0),
450 Images::RGBImage::Color(128, 128, 0),
451 Images::RGBImage::Color(0, 128, 0),
452 Images::RGBImage::Color(0, 128, 128),
453 Images::RGBImage::Color(0, 0, 128),
454 Images::RGBImage::Color(128, 0, 128),
455 Images::RGBImage::Color(255, 128, 128),
456 Images::RGBImage::Color(255, 255, 128),
457 Images::RGBImage::Color(128, 255, 128),
458 Images::RGBImage::Color(128, 255, 255),
459 Images::RGBImage::Color(128, 128, 255),
460 Images::RGBImage::Color(255, 128, 255)
463 for(unsigned int i
= 0; i
< numSpans
; ++i
) {
464 /* Find the span's root: */
466 while(spans
[root
].parent
!= root
)
467 root
= spans
[root
].parent
;
469 if(spans
[root
].blobId
!= invalidBlobId
) {
470 /* Fill in the span: */
471 Images::RGBImage::Color
* cPtr
= result
.modifyPixelRow(spans
[i
].y
) + spans
[i
].start
;
472 for(int x
= spans
[i
].start
; x
< spans
[i
].end
; ++x
, ++cPtr
)
473 *cPtr
= blobColors
[spans
[root
].blobId
% 18];
479 /* Create an array of blob origin points: */
480 BlobOrigin
* blobOrigins
= new BlobOrigin
[nextBlobId
];
481 for(unsigned int i
= 0; i
< nextBlobId
; ++i
)
482 blobOrigins
[i
].assigned
= false;
484 /* Create the blob ID image: */
485 unsigned short* biRowPtr
= blobIdImage
+ biStride
+ 1;
486 unsigned int spanIndex
= 0;
487 for(unsigned int y
= 0; y
< depthFrameSize
[1]; ++y
, biRowPtr
+= biStride
) {
488 /* Process all spans and spaces between spans in the current row: */
490 unsigned short* biPtr
= biRowPtr
;
492 /* Find the start of the next span in the current row: */
493 unsigned int nextSpanStart
= depthFrameSize
[0];
494 if(spanIndex
< numSpans
&& spans
[spanIndex
].y
== y
)
495 nextSpanStart
= spans
[spanIndex
].start
;
497 /* Assign the invalid blob IDs until the start of the next span: */
498 for(; x
< nextSpanStart
; ++x
, ++biPtr
)
499 *biPtr
= invalidBlobId
;
501 /* Bail out if the current row is done: */
502 if(x
== depthFrameSize
[0])
505 /* Check if the current span's blob is valid, and encountered for the first time: */
506 unsigned int blobId
= spans
[spanIndex
].blobId
;
507 if(blobId
< nextBlobId
&& !blobOrigins
[blobId
].assigned
) {
508 /* Store the beginning of the current span as the blob's origin: */
509 blobOrigins
[blobId
].assigned
= true;
510 blobOrigins
[blobId
].x
= x
;
511 blobOrigins
[blobId
].y
= y
;
512 blobOrigins
[blobId
].biPtr
= biPtr
;
515 /* Assign the current span's blob ID: */
516 for(; x
< spans
[spanIndex
].end
; ++x
, ++biPtr
)
519 /* Go to the next span: */
524 /* Initialize the result list: */
527 /* Walk around the edges of all foreground blobs in counter-clockwise order and decide whether they are hand-shaped: */
528 EdgePixel
* snakeEnd
= snake
+ snakeLength
;
529 int enterDist2
= Math::sqr(maxCornerEnterDist
);
530 int centerDist2
= Math::sqr(minCenterDist
);
531 int exitDist2
= Math::sqr(minCornerExitDist
);
532 std::vector
<Corner
> corners
;
534 for(unsigned int blobId
= 0; blobId
< nextBlobId
; ++blobId
) {
535 /* Initialize the edge-walking snake: */
536 EdgePixel
* snakeHead
= snake
;
537 snakeHead
->x
= int(blobOrigins
[blobId
].x
);
538 snakeHead
->y
= int(blobOrigins
[blobId
].y
);
539 snakeHead
->biPtr
= blobOrigins
[blobId
].biPtr
;
540 unsigned int walkDir
=
541 0; // The blob origin is the bottom-left pixel of the blob, so 0 is the correct initial walking direction
542 for(unsigned int i
= 1; i
< snakeLength
; ++i
) {
543 /* Turn 90 degrees clockwise: */
544 walkDir
= (walkDir
+ 6) & 0x7U
;
546 /* Turn counter-clockwise until the next step stays in the same blob: */
547 while(snakeHead
->biPtr
[walkOffsets
[walkDir
]] != blobId
)
548 walkDir
= (walkDir
+ 1) & 0x7U
;
550 /* Walk one step along the blob edge: */
551 snakeHead
[1].x
= snakeHead
->x
+ walkDx
[walkDir
];
552 snakeHead
[1].y
= snakeHead
->y
+ walkDy
[walkDir
];
553 snakeHead
[1].biPtr
= snakeHead
->biPtr
+ walkOffsets
[walkDir
];
555 /* Move the snake head forward: */
558 EdgePixel
* snakeTail
= snake
;
559 EdgePixel
* snakeMid
= snake
+ snakeLength
/ 2;
561 /* Walk the snake exactly once around the blob: */
563 corner
.cornerType
= 0;
565 unsigned int pixelIndex
= 0;
566 int firstCornerDist2
= 0;
567 unsigned int firstCornerStart
= 0;
569 /* Check if the current snake sits on a corner: */
570 int newCornerType
= 0;
571 int headTailDist2
= Math::sqr(snakeHead
->x
- snakeTail
->x
) + Math::sqr(
572 snakeHead
->y
- snakeTail
->y
);
573 int centerElevation2
= 0;
574 if(headTailDist2
<= enterDist2
) {
575 /* Determine the type of corner by comparing the snake's center point against the line defined by its head and tail: */
576 int nx
= snakeTail
->y
- snakeHead
->y
;
577 int ny
= snakeHead
->x
- snakeTail
->x
;
578 int d
= nx
* (snakeMid
->x
- snakeTail
->x
) + ny
* (snakeMid
->y
- snakeTail
->y
);
579 if(Math::sqr(d
) >= centerDist2
* headTailDist2
) {
580 /* Enter corner state: */
582 newCornerType
= 1; // Finger tip
584 newCornerType
= -1; // Finger nook
585 if(headTailDist2
> 0)
586 centerElevation2
= Math::sqr(d
) / headTailDist2
;
588 centerElevation2
= Math::sqr(snakeMid
->x
- snakeTail
->x
) + Math::sqr(snakeMid
->y
- snakeTail
->y
);
592 /* Check if the snake changed corner type since the last step: */
593 if(corner
.cornerType
!= newCornerType
) {
594 if(corner
.cornerType
!= 0) {
595 /* If the previous corner is the first, remember its corner distance: */
597 firstCornerDist2
= cornerDist2
;
599 /* Store the previous corner: */
600 corners
.push_back(corner
);
603 if(newCornerType
!= 0) {
604 /* Start a new corner: */
605 corner
.start
= pixelIndex
;
606 corner
.x
= snakeMid
->x
;
607 corner
.y
= snakeMid
->y
;
608 cornerDist2
= centerElevation2
;
610 /* If this is the first corner, remember its starting pixel: */
612 firstCornerStart
= pixelIndex
;
615 /* Change the type of the current corner: */
616 corner
.cornerType
= newCornerType
;
617 } else if(corner
.cornerType
!= 0 && cornerDist2
< centerElevation2
) {
618 /* Update the current corner: */
619 corner
.x
= snakeMid
->x
;
620 corner
.y
= snakeMid
->y
;
621 cornerDist2
= centerElevation2
;
625 /* Draw the snake's center point: */
626 Images::RGBImage::Color
* cPtr
= imgPtr
+ (snakeMid
->y
* depthFrameSize
[0] + snakeMid
->x
);
627 if(corner
.cornerType
== 1)
628 *cPtr
= Images::RGBImage::Color(96, 160, 96);
629 else if(corner
.cornerType
== -1)
630 *cPtr
= Images::RGBImage::Color(160, 96, 160);
633 *cPtr
= Images::RGBImage::Color(128, 128, 128);
635 for(int i
= 0; i
< 3; ++i
)
636 (*cPtr
)[i
] = (*cPtr
)[i
] + (255U - (*cPtr
)[i
]) / 2;
641 /* Walk one step along the blob edge: */
642 walkDir
= (walkDir
+ 6) & 0x7U
; // Turn 90 degrees counter-clockwise
643 while(snakeHead
->biPtr
[walkOffsets
[walkDir
]] != blobId
)
644 walkDir
= (walkDir
+ 1) & 0x7U
;
645 snakeTail
->x
= snakeHead
->x
+ walkDx
[walkDir
];
646 snakeTail
->y
= snakeHead
->y
+ walkDy
[walkDir
];
647 snakeTail
->biPtr
= snakeHead
->biPtr
+ walkOffsets
[walkDir
];
649 /* Move the snake head forward: */
650 snakeHead
= snakeTail
;
651 if(++snakeMid
== snakeEnd
)
653 if(++snakeTail
== snakeEnd
)
657 } while(snakeTail
->biPtr
!= blobOrigins
[blobId
].biPtr
);
659 if(corner
.cornerType
!= 0) {
660 if(!corners
.empty() && firstCornerStart
== 0 && corners
.front().cornerType
== corner
.cornerType
) {
661 /* Merge the first and last corners: */
662 if(firstCornerDist2
< cornerDist2
) {
663 corners
.front().x
= corner
.x
;
664 corners
.front().y
= corner
.y
;
667 /* Store the last corner: */
668 corners
.push_back(corner
);
673 /* Draw all corners: */
674 for(std::vector
<Corner
>::iterator cIt
= corners
.begin(); cIt
!= corners
.end(); ++cIt
) {
675 Images::RGBImage::Color
* cPtr
= imgPtr
+ (cIt
->y
* depthFrameSize
[0] + cIt
->x
);
676 if(cIt
->cornerType
== 1)
677 *cPtr
= Images::RGBImage::Color(0, 255, 0);
678 else if(cIt
->cornerType
== -1)
679 *cPtr
= Images::RGBImage::Color(255, 0, 255);
683 /* Check if the extracted set of corners matches a hand model: */
684 float maxProb
= minHandProbability
;
685 Point2 center
= Point2::origin
; // Hand center point
686 float depth
= 0.0f
; // Hand's average depth value
687 float radius
= 0.0f
; // Hand radius
688 size_t numCorners
= corners
.size();
689 int handType
= 1; // default to rain hand
691 8) { // At least four finger tips, three nooks, and a thumb tip (thumb nook optional)
692 for(size_t i
= 0; i
< numCorners
; ++i
) {
693 /* Check if the current corner starts a sequence of four tips interleaved with three nooks: */
694 Corner
& t0
= corners
[i
];
695 Corner
& n1
= corners
[(i
+ 1) % numCorners
];
696 Corner
& t1
= corners
[(i
+ 2) % numCorners
];
697 Corner
& n2
= corners
[(i
+ 3) % numCorners
];
698 Corner
& t2
= corners
[(i
+ 4) % numCorners
];
699 Corner
& n3
= corners
[(i
+ 5) % numCorners
];
700 Corner
& t3
= corners
[(i
+ 6) % numCorners
];
701 if(t0
.cornerType
== 1 &&
702 n1
.cornerType
== -1 && t1
.cornerType
== 1 &&
703 n2
.cornerType
== -1 && t2
.cornerType
== 1 &&
704 n3
.cornerType
== -1 && t3
.cornerType
== 1) {
705 /* Construct a hand model: */
706 Point2
tp0(float(t0
.x
) + 0.5f
, float(t0
.y
) + 0.5f
);
707 Point2
np1(float(n1
.x
) + 0.5f
, float(n1
.y
) + 0.5f
);
708 Point2
tp1(float(t1
.x
) + 0.5f
, float(t1
.y
) + 0.5f
);
709 Point2
np2(float(n2
.x
) + 0.5f
, float(n2
.y
) + 0.5f
);
710 Point2
tp2(float(t2
.x
) + 0.5f
, float(t2
.y
) + 0.5f
);
711 Point2
np3(float(n3
.x
) + 0.5f
, float(n3
.y
) + 0.5f
);
712 Point2
tp3(float(t3
.x
) + 0.5f
, float(t3
.y
) + 0.5f
);
714 /* Calculate the range of finger tip distances: */
715 Interval
tipDistance(Geometry::dist(tp0
, tp1
));
716 tipDistance
.addValue(Geometry::dist(tp1
, tp2
));
717 tipDistance
.addValue(Geometry::dist(tp2
, tp3
));
719 /* Calculate the range of finger nook distances: */
720 Interval
nookDistance(Geometry::dist(np1
, np2
));
721 nookDistance
.addValue(Geometry::dist(np2
, np3
));
723 /* Calculate finger root points: */
724 Vector2 curve
= Geometry::mid(np1
, np3
) - np2
;
725 Point2 rp0
= np1
+ (np1
- np2
) * 0.5f
+ curve
;
726 Point2 rp1
= Geometry::mid(np1
, np2
);
727 Point2 rp2
= Geometry::mid(np2
, np3
);
728 Point2 rp3
= np3
+ (np3
- np2
) * 0.5f
+ curve
;
730 /* Calculate the range of finger lengths: */
731 Interval
fingerLength(Geometry::dist(tp0
, rp0
));
732 fingerLength
.addValue(Geometry::dist(tp1
, rp1
));
733 fingerLength
.addValue(Geometry::dist(tp2
, rp2
));
734 fingerLength
.addValue(Geometry::dist(tp3
, rp3
));
736 /* Calculate the probability that this is a hand: */
738 prob
*= Math::sqr(tipDistance
.getMin() / tipDistance
.getMax());
739 prob
*= nookDistance
.getMin() / nookDistance
.getMax();
740 prob
*= fingerLength
.getMin() / fingerLength
.getMax();
743 /* Calculate finger length to nook distance ratio: */
744 float fdNdRatio
= Math::mid(Geometry::dist(tp1
, rp1
), Geometry::dist(tp2
,
745 rp2
)) / Math::mid(Geometry::dist(np1
, np2
), Geometry::dist(np2
, np3
));
747 /* Calculate the hand center and radius: */
748 float centerOffset
= 1.0f
/ fdNdRatio
;
749 center
= Geometry::mid(Geometry::mid(rp0
+ (rp0
- tp0
) * centerOffset
,
750 rp1
+ (rp1
- tp1
) * centerOffset
),
751 Geometry::mid(rp2
+ (rp2
- tp2
) * centerOffset
, rp3
+ (rp3
- tp3
) * centerOffset
));
752 center
= Geometry::mid(rp1
+ (rp1
- tp1
) * centerOffset
, rp2
+ (rp2
- tp2
) * centerOffset
);
753 radius
= (Geometry::dist(center
, tp0
) + Geometry::dist(center
, tp1
) + Geometry::dist(center
,
754 tp2
) + Geometry::dist(center
, tp3
)) * 0.25f
;
756 /* Calculate the hand's average depth in depth-corrected depth image space: */
758 if(pixelDepthCorrection
!= 0) {
759 ptrdiff_t t0Off
= t0
.y
* depthFrameSize
[0] + t0
.x
;
760 depth
+= pixelDepthCorrection
[t0Off
].correct(float(depthFrame
[t0Off
]));
761 ptrdiff_t n1Off
= n1
.y
* depthFrameSize
[0] + n1
.x
;
762 depth
+= pixelDepthCorrection
[n1Off
].correct(float(depthFrame
[n1Off
]));
763 ptrdiff_t t1Off
= t1
.y
* depthFrameSize
[0] + t1
.x
;
764 depth
+= pixelDepthCorrection
[t1Off
].correct(float(depthFrame
[t1Off
]));
765 ptrdiff_t n2Off
= n2
.y
* depthFrameSize
[0] + n2
.x
;
766 depth
+= pixelDepthCorrection
[n2Off
].correct(float(depthFrame
[n2Off
]));
767 ptrdiff_t t2Off
= t2
.y
* depthFrameSize
[0] + t2
.x
;
768 depth
+= pixelDepthCorrection
[t2Off
].correct(float(depthFrame
[t2Off
]));
769 ptrdiff_t n3Off
= n3
.y
* depthFrameSize
[0] + n3
.x
;
770 depth
+= pixelDepthCorrection
[n3Off
].correct(float(depthFrame
[n3Off
]));
771 ptrdiff_t t3Off
= t3
.y
* depthFrameSize
[0] + t3
.x
;
772 depth
+= pixelDepthCorrection
[t3Off
].correct(float(depthFrame
[t3Off
]));
774 depth
+= float(depthFrame
[t0
.y
* depthFrameSize
[0] + t0
.x
]);
775 depth
+= float(depthFrame
[n1
.y
* depthFrameSize
[0] + n1
.x
]);
776 depth
+= float(depthFrame
[t1
.y
* depthFrameSize
[0] + t1
.x
]);
777 depth
+= float(depthFrame
[n2
.y
* depthFrameSize
[0] + n2
.x
]);
778 depth
+= float(depthFrame
[t2
.y
* depthFrameSize
[0] + t2
.x
]);
779 depth
+= float(depthFrame
[n3
.y
* depthFrameSize
[0] + n3
.x
]);
780 depth
+= float(depthFrame
[t3
.y
* depthFrameSize
[0] + t3
.x
]);
788 drawLine(*blobImage
, tp0
, rp0
, Images::RGBImage::Color(255, 255, 255));
789 drawLine(*blobImage
, tp1
, rp1
, Images::RGBImage::Color(255, 255, 255));
790 drawLine(*blobImage
, tp2
, rp2
, Images::RGBImage::Color(255, 255, 255));
791 drawLine(*blobImage
, tp3
, rp3
, Images::RGBImage::Color(255, 255, 255));
792 drawCircle(*blobImage
, center
, radius
, Images::RGBImage::Color(255, 255, 255));
799 else if (numCorners
== 3) { // Two tips and a nook make a V where the water drains away.
800 for (size_t i
= 0; i
< numCorners
; ++i
) {
801 /* Check if the current corner starts a sequence of two tips interleaved with one nook: */
802 Corner
&t0
= corners
[i
];
803 Corner
&n1
= corners
[(i
+ 1) % numCorners
];
804 Corner
&t1
= corners
[(i
+ 2) % numCorners
];
806 if (t0
.cornerType
== 1 && n1
.cornerType
== -1 && t1
.cornerType
== 1) {
807 /* Construct a hand model: */
808 Point2
tp0(float(t0
.x
) + 0.5f
, float(t0
.y
) + 0.5f
);
809 Point2
np1(float(n1
.x
) + 0.5f
, float(n1
.y
) + 0.5f
);
810 Point2
tp1(float(t1
.x
) + 0.5f
, float(t1
.y
) + 0.5f
);
812 /* Calculate the lengths of three sides of the triangle. */
813 // nook to the two tips
814 Interval
sideLengths(Geometry::dist(tp0
, np1
));
815 sideLengths
.addValue(Geometry::dist(np1
, tp1
));
816 // imaginary line connecting two finger tips
817 sideLengths
.addValue(Geometry::dist(tp0
, tp1
));
819 /* Calculate the probability that this is a drain hand. */
820 /* Look for a nearly (> 0.70 ratio between sides) equilateral triangle. */
821 float prob
= sideLengths
.getMin() / (sideLengths
.getMax() * 4.66f
);
823 if (maxProb
< prob
) {
824 /* Center drain tool on the nook. */
826 /* Enlarge radius by 1/3 to approximate a full hand (rain) circle size. */
827 radius
= 1.33 * sideLengths
.getMax();
829 /* Calculate the hand's average depth in depth-corrected depth image space: */
831 if (pixelDepthCorrection
!= 0) {
832 ptrdiff_t t0Off
= t0
.y
* depthFrameSize
[0] + t0
.x
;
833 depth
+= pixelDepthCorrection
[t0Off
].correct(float(depthFrame
[t0Off
]));
835 ptrdiff_t n1Off
= n1
.y
* depthFrameSize
[0] + n1
.x
;
836 depth
+= pixelDepthCorrection
[n1Off
].correct(float(depthFrame
[n1Off
]));
838 ptrdiff_t t1Off
= t1
.y
* depthFrameSize
[0] + t1
.x
;
839 depth
+= pixelDepthCorrection
[t1Off
].correct(float(depthFrame
[t1Off
]));
841 depth
+= float(depthFrame
[t0
.y
* depthFrameSize
[0] + t0
.x
]);
842 depth
+= float(depthFrame
[n1
.y
* depthFrameSize
[0] + n1
.x
]);
843 depth
+= float(depthFrame
[t1
.y
* depthFrameSize
[0] + t1
.x
]);
849 drawLine(*blobImage
, tp0
, np1
, Images::RGBImage::Color(255, 255, 255));
850 drawLine(*blobImage
, tp1
, np1
, Images::RGBImage::Color(255, 255, 255));
851 drawCircle(*blobImage
, center
, radius
, Images::RGBImage::Color(255, 255, 255));
854 /* Evaporate at twice the rainfall rate for fast clean up of water. */
859 /* Check if the blob matches a hand: */
860 if(maxProb
> minHandProbability
) {
862 // std::cout<<"Hand in depth space: "<<center[0]<<", "<<center[1]<<", "<<depth<<", "<<radius<<std::endl;
864 /* Store the hand in camera space: */
866 newHand
.center
= depthProjection
.transform(Point(center
[0], center
[1], depth
));
867 newHand
.radius
= Geometry::dist(newHand
.center
, depthProjection
.transform(Point(center
[0] + radius
,
869 newHand
.direction
= handType
;
870 hands
.push_back(newHand
);
873 // std::cout<<"Hand in camera space: "<<newHand.center[0]<<", "<<newHand.center[1]<<", "<<newHand.center[2]<<", "<<newHand.radius<<", "<<newHand.direction<<std::endl;
881 delete[] blobOrigins
;
884 void HandExtractor::setHandsExtractedFunction(HandExtractor::HandsExtractedFunction
*
885 newHandsExtractedFunction
) {
886 delete handsExtractedFunction
;
887 handsExtractedFunction
= newHandsExtractedFunction
;
890 void HandExtractor::receiveRawFrame(const Kinect::FrameBuffer
& newFrame
) {
891 Threads::MutexCond::Lock
inputLock(inputCond
);
893 /* Store the new buffer in the input buffer: */
894 inputFrame
= newFrame
;
897 /* Signal the background thread: */