HandDetector.cs 10 KB

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