HandDetector.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. using BBIWARG.Images;
  2. using BBIWARG.Input.InputHandling;
  3. using BBIWARG.Recognition.FingerRecognition;
  4. using BBIWARG.Utility;
  5. using Emgu.CV;
  6. using Emgu.CV.Structure;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Drawing;
  10. namespace BBIWARG.Recognition.HandRecognition
  11. {
  12. /// <summary>
  13. /// Finds Hands by iterating over all fingers and flooding them. Each filled region is considered to be one hand and each finger belongs to one hand. To improve the hand contours, the hand mask are filled with the defects caused by overlapping fingers.
  14. /// </summary>
  15. internal class HandDetector
  16. {
  17. /// <summary>
  18. /// the depth image of the current frame
  19. /// </summary>
  20. private DepthImage depthImage;
  21. /// <summary>
  22. /// the fingers in the current frame
  23. /// </summary>
  24. private List<Finger> fingers;
  25. /// <summary>
  26. /// the hands in the current frame
  27. /// </summary>
  28. private List<Hand> hands;
  29. /// <summary>
  30. /// the modified depth image of the current frame (hand flooding changes depth image)
  31. /// </summary>
  32. private Image<Gray, byte> modifiedHandDepthImage;
  33. /// <summary>
  34. /// a mapping of hands and list of fingers, that don't belong to that specific hand
  35. /// </summary>
  36. private Dictionary<Hand, List<Finger>> otherHandsFingers;
  37. /// <summary>
  38. /// Detects Hands in the current frame and stores found hands in frameData.detectedHands
  39. /// </summary>
  40. /// <param name="frameData">the current frame</param>
  41. public void detectHands(FrameData frameData)
  42. {
  43. depthImage = frameData.DepthImage;
  44. fingers = frameData.TrackedFingers;
  45. createModifiedHandEdgeImage();
  46. findHands();
  47. fixOverlappingFingers();
  48. findCentroids();
  49. frameData.DetectedHands = hands;
  50. }
  51. /// <summary>
  52. /// Creates the modified hand image. The image is a copy of the original depth image with a contour around each finger (to prevent flood fill from filling through fingers).
  53. /// </summary>
  54. private void createModifiedHandEdgeImage()
  55. {
  56. modifiedHandDepthImage = depthImage.Image.Copy();
  57. foreach (Finger finger in fingers)
  58. {
  59. Int16 depthAtHand = depthImage.getDepthAt(finger.HandPoint);
  60. Point[] contour = finger.getContour(0f).ToArray();
  61. modifiedHandDepthImage.DrawPolyline(contour, false, new Gray(depthAtHand), 1);
  62. }
  63. }
  64. /// <summary>
  65. /// Merges two hands if they are separated by an overlapping finger or extends the hand mask through an overlapping finger.
  66. /// </summary>
  67. private void extendOrMergeThroughOverlappingFingers()
  68. {
  69. List<Hand> mergedHands = new List<Hand>();
  70. foreach (Hand hand in hands)
  71. {
  72. if (!mergedHands.Contains(hand))
  73. {
  74. List<Hand> mergeHands = new List<Hand>();
  75. foreach (Finger overlappingFinger in otherHandsFingers[hand])
  76. {
  77. FingerSlice midSlice = overlappingFinger.SliceTrail.MidSlice;
  78. Vector2D midOut1 = midSlice.Start.moveWithinBound(depthImage.Size, midSlice.Direction.getInverse(), Parameters.FingerOutMargin);
  79. Vector2D midOut2 = midSlice.End.moveWithinBound(depthImage.Size, midSlice.Direction, Parameters.FingerOutMargin);
  80. Int16 depthAtMidOut1 = depthImage.getDepthAt(midOut1);
  81. Int16 depthAtMidOut2 = depthImage.getDepthAt(midOut2);
  82. bool midOut1InHand = hand.isInside(midOut1);
  83. bool midOut2InHand = hand.isInside(midOut2);
  84. Int16 maxDepth = depthImage.MaxDepth;
  85. if (midOut1InHand != midOut2InHand && depthAtMidOut1 != maxDepth && depthAtMidOut2 != maxDepth && Math.Abs(depthAtMidOut1 - depthAtMidOut2) < Parameters.HandExtendMaxDifference)
  86. {
  87. Vector2D handPoint, handExtensionPoint;
  88. if (midOut1InHand)
  89. {
  90. handPoint = midOut1;
  91. handExtensionPoint = midOut2;
  92. }
  93. else
  94. {
  95. handPoint = midOut2;
  96. handExtensionPoint = midOut1;
  97. }
  98. // check if pHandExtension is in other hand (if so -> merge with hand)
  99. bool merge = false;
  100. foreach (Hand mergeHand in hands)
  101. {
  102. if (mergeHand.isInside(handExtensionPoint) && !mergedHands.Contains(mergeHand))
  103. {
  104. mergeHands.Add(mergeHand);
  105. merge = true;
  106. break;
  107. }
  108. }
  109. // if no merge, extend hand
  110. if (!merge)
  111. extendToHand(hand, handExtensionPoint);
  112. }
  113. }
  114. foreach (Hand mergeHand in mergeHands)
  115. {
  116. mergeToHand(hand, mergeHand);
  117. mergedHands.Add(mergeHand);
  118. }
  119. }
  120. }
  121. foreach (Hand mergedHand in mergedHands)
  122. hands.Remove(mergedHand);
  123. }
  124. /// <summary>
  125. /// Extends the hand mask of a given hand by flood filling starting from the given point.
  126. /// </summary>
  127. /// <param name="hand">the hand that should be extended</param>
  128. /// <param name="p">the flood fill starting point</param>
  129. private void extendToHand(Hand hand, Vector2D p)
  130. {
  131. Image<Gray, byte> extendMask = getHandMask(p);
  132. int numPixels = extendMask.CountNonzero()[0];
  133. if (numPixels <= Parameters.HandExtensionMaxRelativeSize * depthImage.Size.NumPixels)
  134. hand.extendMask(extendMask);
  135. }
  136. /// <summary>
  137. /// Fills holes caused by overlapping fingers.
  138. /// </summary>
  139. private void fillOverlappingFingers()
  140. {
  141. foreach (Hand hand in hands)
  142. {
  143. hand.fillOverlappingFingers(otherHandsFingers[hand]);
  144. }
  145. }
  146. /// <summary>
  147. /// Finds the hands centroids.
  148. /// </summary>
  149. private void findCentroids()
  150. {
  151. foreach (Hand hand in hands)
  152. hand.findCentroid();
  153. }
  154. /// <summary>
  155. /// Finds hands by flood filling from each finger (mask). All unassigned fingers that lie within the hand are assigned to the hand, all other fingers are mapped as fingers that don't belong to that specific hand.
  156. /// </summary>
  157. private void findHands()
  158. {
  159. hands = new List<Hand>();
  160. otherHandsFingers = new Dictionary<Hand, List<Finger>>();
  161. List<Finger> assignedFingers = new List<Finger>();
  162. foreach (Finger finger in fingers)
  163. {
  164. if (!assignedFingers.Contains(finger))
  165. {
  166. Image<Gray, byte> handMask = getHandMask(finger.HandPoint);
  167. int numPixels = handMask.CountNonzero()[0];
  168. if (numPixels > Parameters.HandMaxSize * depthImage.Size.NumPixels)
  169. {
  170. assignedFingers.Add(finger);
  171. break;
  172. }
  173. List<Finger> fingersOnHand = new List<Finger>();
  174. List<Finger> fingersOnOtherHand = new List<Finger>();
  175. foreach (Finger f in fingers)
  176. {
  177. if (!assignedFingers.Contains(f) && handMask.Data[f.HandPoint.IntY, f.HandPoint.IntX, 0] != 0)
  178. {
  179. fingersOnHand.Add(f);
  180. assignedFingers.Add(f);
  181. }
  182. else
  183. fingersOnOtherHand.Add(f);
  184. }
  185. Hand hand = new Hand(handMask, fingersOnHand);
  186. otherHandsFingers.Add(hand, fingersOnOtherHand);
  187. hands.Add(hand);
  188. }
  189. }
  190. }
  191. /// <summary>
  192. /// Fixes overlapping fingers by merging two hands if they are separated by a finger and/or fills holes caused by overlapping fingers.
  193. /// </summary>
  194. private void fixOverlappingFingers()
  195. {
  196. extendOrMergeThroughOverlappingFingers();
  197. fillOverlappingFingers();
  198. }
  199. /// <summary>
  200. /// Flood fills from a given point and returns the filled area as mask.
  201. /// </summary>
  202. /// <param name="p">flood fill starting point</param>
  203. /// <returns>the filled area as mask</returns>
  204. private Image<Gray, byte> getHandMask(Vector2D p)
  205. {
  206. Image<Gray, byte> mask = new Image<Gray, byte>(depthImage.Size.Width + 2, depthImage.Size.Height + 2);
  207. MCvConnectedComp comp = new MCvConnectedComp();
  208. CvInvoke.cvFloodFill(modifiedHandDepthImage, p, new MCvScalar(255), new MCvScalar(Parameters.HandFloodFillDownDiff), new MCvScalar(Parameters.HandFloodFillUpDiff), out comp, Emgu.CV.CvEnum.CONNECTIVITY.FOUR_CONNECTED, Emgu.CV.CvEnum.FLOODFILL_FLAG.DEFAULT, mask);
  209. return mask.Copy(new Rectangle(1, 1, depthImage.Size.Width, depthImage.Size.Height));
  210. }
  211. /// <summary>
  212. /// Merges two hands together and updates the list of other hands fingers.
  213. /// </summary>
  214. /// <param name="hand">the first hand (other hand will be merged to this one)</param>
  215. /// <param name="mergeHand">the second hand (this hand will be dropped afterwards)</param>
  216. private void mergeToHand(Hand hand, Hand mergeHand)
  217. {
  218. hand.mergeWith(mergeHand);
  219. foreach (Finger finger in mergeHand.Fingers)
  220. otherHandsFingers[hand].Remove(finger);
  221. otherHandsFingers.Remove(mergeHand);
  222. }
  223. }
  224. }