Kinect Scol plugin
KinectUserHand.cpp
1 /*
2 -----------------------------------------------------------------------------
3 This source file is part of OpenSpace3D
4 For the latest info, see http://www.openspace3d.com
5 
6 Copyright (c) 2012 I-maginer
7 
8 This program is free software; you can redistribute it and/or modify it under
9 the terms of the GNU Lesser General Public License as published by the Free Software
10 Foundation; either version 2 of the License, or (at your option) any later
11 version.
12 
13 This program is distributed in the hope that it will be useful, but WITHOUT
14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
16 
17 You should have received a copy of the GNU Lesser General Public License along with
18 this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 Place - Suite 330, Boston, MA 02111-1307, USA, or go to
20 http://www.gnu.org/copyleft/lesser.txt
21 
22 -----------------------------------------------------------------------------
23 */
24 
33 #include "objects/KinectUserHand.h"
34 #include "core/DataSkeleton.h"
35 #include "DeviceManager.h"
36 #include "generator/User.h"
37 #include "objects/KinectDevice.h"
38 #include "openNiScolPlugin.h"
39 
40 // Constructors
41 KinectUserHand::KinectUserHand()
42 {
43 }
44 
45 KinectUserHand::KinectUserHand(KinectUser* user, nite::JointType type)
46 {
47  muser = user;
48  mtype = type;
49  mvisible = false;
50 }
51 
52 // Destructor
53 KinectUserHand::~KinectUserHand()
54 {
55 }
56 
57 bool KinectUserHand::GetHandContour(const cv::Mat &depthMat, cv::Mat &handMat, nite::Point3f v, vector<cv::Point> &handContour, cv::Point2f &center, cv::Mat *debugFrame)
58 {
59  const float handDepthRange = 300.0f; // in mm
60  const float depth = v.z; // hand depth
61  const int maxHandRadius = ((depth / 100.0f) > 0) ? (int) (700.0f / (depth / 100.0f)) : 128; // in px
62  const float dnear = depth - (handDepthRange / 2); // near clipping plane
63  const float dfar = depth + (handDepthRange / 2); // far clipping plane
64 
65  const double erodeCoef = depth > 0 ? 30.0f / (depth / 100) : 30.0f;
66  const double blurCoef = (depth / 100) <= 70 ? 11.0f : (depth / 100) <= 90 ? 6.0f : 2.0f;
67  const double epsilon = depth > 0 ? 65.0f / (depth / 100) : 65.0f; //17.6; // approximation accuracy (maximum distance between the original hand contour and its approximation)
68 
69  handMat.setTo(0);
70 
71  // extract hand region
72  cv::circle(handMat, cv::Point((int)v.x, (int)v.y), maxHandRadius, 255, CV_FILLED);
73  handMat = handMat & depthMat > dnear & depthMat < dfar;
74 
75  //remove noise
76  try
77  {
78  //erode picture to remove noise
79  //cv::GaussianBlur(handMat, handMat, cv::Size(blurCoef, blurCoef), 1, 2);
80  cv::Mat element = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size((int)(2.0f * erodeCoef + 1.0f), (int)(2.0f * erodeCoef + 1.0f)), cv::Point((int)erodeCoef, (int)erodeCoef));
81  cv::erode(handMat, handMat, element);
82  }
83  catch(cv::Exception)
84  {
85  }
86 
87  if (debugFrame)
88  {
89  int width;
90  int height;
91  muser->GetParentDevice()->GetGeneratorsSize(width, height);
92 
93  cv::Mat maskrgb(cv::Size(width, height), CV_8UC3);
94  cvtColor(handMat, maskrgb, CV_GRAY2BGR);
95  cv::add(maskrgb, *debugFrame, *debugFrame);
96  }
97 
98  // assume largest contour in hand region to be the hand contour
99  vector<vector<cv::Point>> contours;
100 
101  cv::findContours(handMat, contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE, cv::Point(0, 20));
102  int n = contours.size();
103  int maxI = -1;
104  int maxSize = -1;
105  for (int i=0; i<n; i++)
106  {
107  int size = contours[i].size();
108  if (size > maxSize)
109  {
110  maxSize = size;
111  maxI = i;
112  }
113  }
114 
115  //handContourFound
116  if (maxI >= 0)
117  {
118  vector<cv::Point> tHandContour = contours[maxI];
119  cv::approxPolyDP(cv::Mat(contours[maxI]), tHandContour, epsilon, true);
120 
121  //real hand center
122  float radius;
123  cv::minEnclosingCircle(tHandContour, center, radius);
124  handContour = tHandContour;
125 
126  if (debugFrame)
127  {
128  // hand angle
129  cv::Point2f recPoints[4];
130  cv::RotatedRect shapeRect = cv::fitEllipse(tHandContour);
131  shapeRect.points(recPoints);
132 
133  cv::circle(*debugFrame, cv::Point((int)center.x, (int)center.y), (int)radius, 0x00ff00, 1);
134  cv::line(*debugFrame, center, cv::Point((int)(center.x + (cos(SCOL_PI * shapeRect.angle / 180.0f) * radius)), (int)(center.y + (sin(SCOL_PI * shapeRect.angle / 180.0f) * radius))), 0x0000ff, 2);
135  for (int j=0; j<4; j++)
136  {
137  if (j != 0)
138  {
139  cv::line(*debugFrame, recPoints[j], recPoints[j-1], 0x00ff00, 2);
140  }
141  else
142  {
143  cv::line(*debugFrame, recPoints[0], recPoints[3], 0x00ff00, 2);
144  }
145  }
146 
147  for (int j=0; j<(int)handContour.size(); j++)
148  {
149  if (j != 0)
150  {
151  cv::line(*debugFrame, handContour[j], handContour[j-1], 128, 2);
152  }
153  else
154  {
155  cv::line(*debugFrame, handContour[0], handContour[handContour.size()-1], 128, 2);
156  }
157  }
158  cv::circle(*debugFrame, cv::Point((int)v.x, (int)v.y), maxHandRadius, 255, 2);
159  }
160  }
161 
162  return (maxI >= 0);
163 }
164 
165 void KinectUserHand::DetectFingerTips(cv::Mat &handMat, vector<cv::Point> &handContour, cv::Point2f &center, cv::Mat *debugFrame)
166 {
167  cv::Mat handContourMat(handContour);
168  const cv::Scalar debugFingerTipColor(0, 0, 255);
169 
170  int width = 0;
171  int height = 0;
172  muser->GetParentDevice()->GetGeneratorsSize(width, height);
173 
174  vector<int> hull;
175  vector<cv::Point> tmpFingerTips;
176 
177  cv::convexHull(handContourMat, hull, true, false);
178 
179  // find interior angles of hull corners
180  for (int j=0; (j<(int)hull.size()) && (tmpFingerTips.size() < 5); j++)
181  {
182  int idx = hull[j]; // corner index
183  int pdx = idx == 0 ? handContour.size() - 1 : idx - 1; // predecessor of idx
184  int sdx = idx == handContour.size() - 1 ? 0 : idx + 1; // successor of idx
185 
186  cv::Point v1 = handContour[sdx] - handContour[idx];
187  cv::Point v2 = handContour[pdx] - handContour[idx];
188 
189  float angle = acos((float)((v1.x*v2.x + v1.y*v2.y) / (norm(v1) * norm(v2))));
190 
191  // low interior angle + within upper 90% of region -> we got a finger
192  if (angle < 1.0f)
193  //&& handContour[idx].y < cutoff
194  {
195  int u = handContour[idx].x;
196  int v = handContour[idx].y;
197 
198  tmpFingerTips.push_back(cv::Point2i(u, v));
199  }
200  }
201 
202  // The K-curvature algorithm
203  /*
204  int curv = 20;
205  int contourRadius = 15;
206  for (std::vector<cv::Point>::iterator it = handContour.begin(); it != handContour.end(); ++it)
207  {
208  cv::Point i = *it;
209  std::vector<cv::Point>::iterator it1 = it;
210  for (int counter = 0; it1 != handContour.end() && counter < curv; ++it1, ++counter);
211  if (it1 != handContour.end())
212  {
213  cv::Point j = *it1;
214  std::vector<cv::Point>::iterator it2 = it1;
215  for (int counter = 0; it2 != handContour.end() && counter < curv; ++it2, ++counter);
216  if (it2 != handContour.end())
217  {
218  cv::Point k = *it2;
219  float angle = cvTools::getAngle(i, j, k);
220  if (angle > 25.0f && angle < 35.0f)
221  {
222  cv::Point2i center = cvTools::getCentroid(i, j, k);
223  for (int counter = 0; it != handContour.end() && counter < curv; ++it, ++counter);
224 
225  for(int l=0; l<hull.size(); l++)
226  {
227  cv::Point pt = handContour[hull[l]];
228  if(pt.y - contourRadius <= k.y && pt.y + contourRadius >= k.y && pt.x - contourRadius <= k.x && pt.x + contourRadius >= k.x)
229  {
230  tmpFingerTips.push_back(k);
231  }
232  }
233  }
234  }
235  }
236  }*/
237 
238  //rotate the vector
239  if(mfingerTipsHistory.size() > 5)
240  mfingerTipsHistory.erase(mfingerTipsHistory.begin());
241 
242  mfingerTipsHistory.push_back(tmpFingerTips);
243  std::vector<std::vector<cv::Point>> sortedtips;
244 
245  for (int j=0; j<(int)mfingerTipsHistory.size(); j++)
246  {
247  std::vector<cv::Point> ftips = mfingerTipsHistory[j];
248  for (int k=0; k<(int)ftips.size(); k++)
249  {
250  if (k > ((int)sortedtips.size() - 1))
251  {
252  std::vector<cv::Point> fv;
253  fv.push_back(ftips[k]);
254  sortedtips.push_back(fv);
255  }
256  else
257  {
258  sortedtips[k].push_back(ftips[k]);
259  }
260  }
261  }
262 
263  {
264  boost::mutex::scoped_lock l(handMutex);
265  //clear the current finger tips coords
266  mfingerTips.clear();
267 
268  for (int j=0; j<(int)sortedtips.size(); j++)
269  {
270  cv::Point fvec(0, 0);
271  for (int k=0; k<(int)sortedtips[j].size(); k++)
272  {
273  fvec += sortedtips[j][k];
274  }
275 
276  if(sortedtips[j].size() > 0)
277  {
278  fvec.x /= sortedtips[j].size();
279  fvec.y /= sortedtips[j].size();
280  }
281 
282  mfingerTips.push_back(fvec);
283  }
284  }
285 
286  if (debugFrame)
287  {
288  for (int j=0; j<(int)mfingerTips.size(); j++)
289  {
290  for (int k=0; k<(int)mfingerTips.size(); k++)
291  {
292  // draw fingertips
293  cv::circle(*debugFrame, mfingerTips[k], 3, debugFingerTipColor, -1);
294  cv::line(*debugFrame, center, mfingerTips[k], debugFingerTipColor);
295  }
296  }
297  }
298 /*
299  if (debugFrame)
300  {
301  // draw cutoff threshold
302  cv::line(*debugFrame, cv::Point(0, cutoff), cv::Point(width, cutoff), debugFingerTipColor);
303 
304  // draw approxCurve
305  for (int j=0; j<(int)handContour.size(); j++)
306  {
307  cv::circle(*debugFrame, handContour[j], 3, debugFingerTipColor);
308  if (j != 0)
309  {
310  cv::line(*debugFrame, handContour[j], handContour[j-1], debugFingerTipColor);
311  }
312  else
313  {
314  cv::line(*debugFrame, handContour[0], handContour[handContour.size()-1], debugFingerTipColor);
315  }
316  }
317 
318  // draw approxCurve hull
319  for (int j=0; j<(int)hull.size(); j++)
320  {
321  cv::circle(*debugFrame, handContour[hull[j]], 3, debugFingerTipColor, 3);
322  if(j == 0)
323  {
324  cv::line(*debugFrame, handContour[hull[j]], handContour[hull[hull.size()-1]], debugFingerTipColor);
325  }
326  else
327  {
328  cv::line(*debugFrame, handContour[hull[j]], handContour[hull[j-1]], debugFingerTipColor);
329  }
330  }
331  }*/
332 }
333 
334 bool KinectUserHand::Detect(cv::Mat depthMat, cv::Mat depthMatBgr)
335 {
336  const int minHandExtension = 80; // in milimeter
337  const int minTorsoExtension = 250; // in pixel
338  const double grabConvexity = 0.8;
339 
340  // torso
341  nite::Point3f tVec;
342  tVec.x = 0;
343  tVec.y = 0;
344  tVec.z = 0;
345 
346  //hand
347  nite::Point3f hVec;
348  hVec.x = 0;
349  hVec.y = 0;
350  hVec.z = 0;
351 
352  if (muser->GetSkeletonData()->GetBoneImgCoordinates(nite::JOINT_TORSO, tVec, 0.7))
353  {
354  try
355  {
356  int width;
357  int height;
358  muser->GetParentDevice()->GetGeneratorsSize(width, height);
359 
360  // hand
361  if ((muser->GetSkeletonData()->GetBoneImgCoordinates(mtype, hVec, 0.7)) /* confident detection */ &&
362  (hVec.z < tVec.z - minHandExtension) /* user extends hand towards screen */
363  && (hVec.y < (tVec.y + minTorsoExtension))) /* user raises his hand */
364  {
365  unsigned char shade = 255 - (unsigned char)((hVec.z / 1000.0f) * 128.0f);
366  cv::Scalar color(0, 0, shade);
367 
368  vector<cv::Point> handContour;
369  cv::Point2f center;
370  cv::Mat handMat(cv::Size(width, height), CV_8UC1);
371  if (GetHandContour(depthMat, handMat, hVec, handContour, center, 0/*&depthMatBgr*/))
372  {
373  if (!mvisible)
374  {
375  {
376  boost::mutex::scoped_lock l(handMutex);
377  mvisible = true;
378  }
379  OBJpostEvent(KINECT_USER_HAND_FOUND_CB, SCOL_PTR muser, (int)mtype);
380  }
381 
382  if(mhandPosHistory.size() > 5)
383  mhandPosHistory.erase(mhandPosHistory.begin());
384 
385  nite::Point3f hpos;
386  hpos.x = center.x;
387  hpos.y = center.y;
388  hpos.z = hVec.z;
389 
390  mhandPosHistory.push_back(hpos);
391  nite::Point3f smoothvec;
392  smoothvec.x = 0;
393  smoothvec.y = 0;
394  smoothvec.z = 0;
395  for (int j=0; j<(int)mhandPosHistory.size(); j++)
396  {
397  smoothvec.x += mhandPosHistory[j].x;
398  smoothvec.y += mhandPosHistory[j].y;
399  smoothvec.z += mhandPosHistory[j].z;
400  }
401 
402  if(mhandPosHistory.size() > 0)
403  {
404  smoothvec.x /= mhandPosHistory.size();
405  smoothvec.y /= mhandPosHistory.size();
406  smoothvec.z /= mhandPosHistory.size();
407  }
408 
409  {
410  boost::mutex::scoped_lock l(handMutex);
411  mtransVec.x = smoothvec.x - mhandPos.x;
412  mtransVec.y = smoothvec.y - mhandPos.y;
413  mtransVec.z = smoothvec.z - mhandPos.z;
414  mhandPos = smoothvec;
415  }
416 
417  bool grasp = cvTools::Convexity(handContour) > grabConvexity;
418  int thickness = grasp ? CV_FILLED : 3;
419  cv::circle(depthMatBgr, cv::Point((int)mhandPos.x, (int)mhandPos.y), 5, color, thickness);
420 
421  DetectFingerTips(handMat, handContour, center, &depthMatBgr);
422 
423  if ((mtransVec.x != 0) || (mtransVec.y != 0) || (mtransVec.z != 0))
424  OBJpostEvent(KINECT_USER_HAND_MOVE_CB, SCOL_PTR muser, (int)this);
425 
426  return true;
427  }
428  return false;
429  }
430  else
431  {
432  if (mvisible)
433  {
434  {
435  boost::mutex::scoped_lock l(handMutex);
436  mvisible = false;
437  }
438  OBJpostEvent(KINECT_USER_HAND_LOST_CB, SCOL_PTR muser, (int)mtype);
439  }
440  return false;
441  }
442  }
443  catch (cv::Exception &e)
444  {
445  MMechostr(MSKDEBUG, const_cast<char*>(e.what()));
446  return false;
447  }
448  }
449  return false;
450 }
451 
452 vector<cv::Point> KinectUserHand::GetFingersPos()
453 {
454  boost::mutex::scoped_lock l(handMutex);
455  vector<cv::Point> vFingers = mfingerTips;
456  return vFingers;
457 }
458 
459 nite::Point3f KinectUserHand::GetHandPos()
460 {
461  boost::mutex::scoped_lock l(handMutex);
462  nite::Point3f pos = mhandPos;
463  return pos;
464 }
465 
466 nite::Point3f KinectUserHand::GetLastTransVec()
467 {
468  boost::mutex::scoped_lock l(handMutex);
469  nite::Point3f transVec = mtransVec;
470  return transVec;
471 }
472 
473 bool KinectUserHand::IsVisible()
474 {
475  boost::mutex::scoped_lock l(handMutex);
476  return mvisible;
477 }
478 
479 nite::JointType KinectUserHand::GetType()
480 {
481  return mtype;
482 }
Kinect user handling. .
Definition: KinectUser.h:38