FingerDetector.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. using BBIWARG.Images;
  2. using BBIWARG.Input.InputHandling;
  3. using BBIWARG.Utility;
  4. using System;
  5. using System.Collections.Generic;
  6. namespace BBIWARG.Recognition.FingerRecognition
  7. {
  8. /// <summary>
  9. /// Detects fingers in the given depth and edge images. The finger detection searches for edges in the edge image and tries to find an initial finger slice. For each found finger slice, the finger detector tries to move along the finger direction to extend the finger slice trail. If the trail reaches its end, the finger detector removes the first few slices and starts the trail expansion in opposite direction. If the complete slice trail is long enough, the finger slices are sorted into correct order and the edges around the finger are removed to increase performance for the next finger detection.
  10. /// </summary>
  11. internal class FingerDetector
  12. {
  13. /// <summary>
  14. /// the coordinateConverter (used to calculate finger width (in millimeters))
  15. /// </summary>
  16. private CoordinateConverter coordinateConverter;
  17. /// <summary>
  18. /// the current depthImage
  19. /// </summary>
  20. private DepthImage depthImage;
  21. /// <summary>
  22. /// the current edge image in its adapted form (edges around detected fingers will be removed)
  23. /// </summary>
  24. private EdgeImage edgeImageAdapted;
  25. /// <summary>
  26. /// the current edge image in its original form
  27. /// </summary>
  28. private EdgeImage edgeImageOriginal;
  29. /// <summary>
  30. /// the detected fingers
  31. /// </summary>
  32. private List<Finger> fingers;
  33. /// <summary>
  34. /// Initializes a new instance of the FingerDetector class.
  35. /// </summary>
  36. /// <param name="coordinateConverter">The coordinate converter.</param>
  37. public FingerDetector(CoordinateConverter coordinateConverter)
  38. {
  39. this.coordinateConverter = coordinateConverter;
  40. }
  41. /// <summary>
  42. /// Detects fingers in the current frame using the depth and edge image. Afterwards the detected fingers are stored in the given frame data (detectedFingers).
  43. /// </summary>
  44. /// <param name="frameData">the current frame</param>
  45. public void detectFingers(FrameData frameData)
  46. {
  47. depthImage = frameData.DepthImage;
  48. edgeImageOriginal = frameData.EdgeImage;
  49. edgeImageAdapted = frameData.EdgeImage.copy();
  50. fingers = new List<Finger>();
  51. Vector2D maxPixel = depthImage.Size.MaxPixel;
  52. int maxX = maxPixel.IntX;
  53. int maxY = maxPixel.IntY;
  54. for (int y = 1; y < maxY; y += 4)
  55. {
  56. for (int x = 1; x < maxX; x += 2)
  57. {
  58. if (edgeImageAdapted.isEdgeAt(x, y))
  59. {
  60. Vector2D edgePoint = new Vector2D(x, y);
  61. Vector2D edgeDirection = getEdgeDirection(edgePoint);
  62. if (edgeDirection != null)
  63. {
  64. Vector2D dir = edgeDirection.getOrthogonal();
  65. if (depthImage.getDepthAt(edgePoint - dir) < depthImage.getDepthAt(edgePoint + dir))
  66. dir = dir.getInverse();
  67. FingerSlice slice = findFingerSliceFromStartEdge(edgePoint, dir);
  68. if (slice != null)
  69. {
  70. FingerSliceTrail trail = findFingerSliceTrail(slice, edgeDirection);
  71. if (trail != null && trail.NumSlices > Parameters.FingerMinNumSlices)
  72. {
  73. createFingerFromTrail(trail);
  74. }
  75. }
  76. }
  77. }
  78. }
  79. }
  80. frameData.DetectedFingers = fingers;
  81. }
  82. /// <summary>
  83. /// Sorts the finger slices in correct order an checks if the finger is a valid finger (<see cref="isCrippleFinger(Finger)"/>). If it is valid the finger is added to the list of detected fingers. Afterwards the edges around the finger are removed to suppress a new finger search for the same finger.
  84. /// </summary>
  85. /// <param name="trail">the slice trail of the possible finger</param>
  86. private void createFingerFromTrail(FingerSliceTrail trail)
  87. {
  88. // bring finger in correct direction Tip->Hand
  89. trail = orderTrailTipToHand(trail);
  90. // create finger
  91. Finger finger = new Finger(trail);
  92. // add finger
  93. if (!isCrippleFinger(finger))
  94. fingers.Add(finger);
  95. // remove edges around detected finger to improve performance
  96. edgeImageAdapted.removeEdgesInsidePolygon(finger.getContour(Parameters.FingerContourMargin).ToArray());
  97. }
  98. /// <summary>
  99. /// Tries to expand a trail along its direction.
  100. /// </summary>
  101. /// <param name="trail">the given finger slice trail</param>
  102. /// <param name="reversed">indicates in which direction the trail should be expanded</param>
  103. /// <returns>the expanded finger slice trail</returns>
  104. private FingerSliceTrail expandTrail(FingerSliceTrail trail, bool reversed = false)
  105. {
  106. if (reversed)
  107. trail.reverse();
  108. Vector2D currentDirection = trail.getEndDirection();
  109. Vector2D currentPosition = trail.EndSlice.Mid + currentDirection;
  110. int gapCounter = 0;
  111. int numSlices = trail.NumSlices;
  112. FingerSlice lastSlice = trail.EndSlice;
  113. FingerSlice nextSlice;
  114. while (currentPosition.isInBound(depthImage.Size) && gapCounter < Math.Min(numSlices, Parameters.FingerMaxGapCounter))
  115. {
  116. nextSlice = findFingerSliceFromMid(currentPosition, currentDirection, reversed);
  117. if (nextSlice != null && Math.Abs(nextSlice.Length - lastSlice.Length) <= Parameters.FingerMaxSliceLengthDifferencePerStep)
  118. {
  119. gapCounter = 0;
  120. numSlices++;
  121. trail.addSlice(nextSlice);
  122. currentDirection = trail.getEndDirection();
  123. currentPosition = nextSlice.Mid + currentDirection;
  124. lastSlice = nextSlice;
  125. }
  126. else
  127. {
  128. gapCounter++;
  129. currentPosition += currentDirection;
  130. }
  131. }
  132. if (reversed)
  133. trail.reverse();
  134. return trail;
  135. }
  136. /// <summary>
  137. /// Tries to find a finger slice from a given position by searching in both orthogonal directions for an edge.
  138. /// </summary>
  139. /// <param name="position">the position somewhere in the middle of the possible finger</param>
  140. /// <param name="direction">the finger direction</param>
  141. /// <param name="reversed">indicates whether start and end should be swapped</param>
  142. /// <returns>the found finger slice or null</returns>
  143. private FingerSlice findFingerSliceFromMid(Vector2D position, Vector2D direction, bool reversed = false)
  144. {
  145. if (edgeImageAdapted.isRoughEdgeAt(position)) return null;
  146. UInt16 depth = depthImage.getDepthAt(position);
  147. int maxWidth2D = (int)coordinateConverter.convertLength3Dto2D(Parameters.FingerMaxWidth3D, depth);
  148. Vector2D dirStart = direction.getOrthogonal(reversed);
  149. Vector2D dirEnd = direction.getOrthogonal(!reversed);
  150. Vector2D startPositionStart = position + 0.5f * Parameters.FingerMinWidth2D * dirStart;
  151. Vector2D start = edgeImageAdapted.findNextRoughEdge(startPositionStart, dirStart, maxWidth2D);
  152. if (start == null) return null;
  153. Vector2D endPositionStart = position + 0.5f * Parameters.FingerMinWidth2D * dirEnd;
  154. Vector2D end = edgeImageAdapted.findNextRoughEdge(position, dirEnd, maxWidth2D);
  155. if (end == null) return null;
  156. FingerSlice slice = new FingerSlice(start, end);
  157. if (!fingerSliceDepthTest(slice) || slice.Length > maxWidth2D)
  158. return null;
  159. return slice;
  160. }
  161. /// <summary>
  162. /// Tries to find a finger slice from one point towards the given direction (searches for an edge).
  163. /// </summary>
  164. /// <param name="start">the start position</param>
  165. /// <param name="direction">the slice direction</param>
  166. /// <returns>the found finger slice or null</returns>
  167. private FingerSlice findFingerSliceFromStartEdge(Vector2D start, Vector2D direction)
  168. {
  169. Vector2D searchStart = start + Parameters.FingerMinWidth2D * direction;
  170. UInt16 depth = depthImage.getDepthAt(start);
  171. int maxWidth2D = (int)coordinateConverter.convertLength3Dto2D(Parameters.FingerMaxWidth3D, depth);
  172. Vector2D end = edgeImageAdapted.findNextRoughEdge(searchStart, direction, maxWidth2D);
  173. if (end == null)
  174. return null;
  175. FingerSlice slice = new FingerSlice(start, end);
  176. if (!fingerSliceDepthTest(slice))
  177. return null;
  178. return slice;
  179. }
  180. /// <summary>
  181. /// Creates a new FingerSliceTrail and expands it along its direction.
  182. /// </summary>
  183. /// <param name="startSlice">the initial finger slice</param>
  184. /// <param name="startDirection">the initial finger direction</param>
  185. /// <returns>the found FingerSliceTrail or null</returns>
  186. private FingerSliceTrail findFingerSliceTrail(FingerSlice startSlice, Vector2D startDirection)
  187. {
  188. FingerSliceTrail trail = new FingerSliceTrail(startSlice);
  189. Vector2D direction = startDirection;
  190. Vector2D position = startSlice.Mid + direction;
  191. if (position.isInBound(depthImage.Size))
  192. {
  193. FingerSlice nextSlice = findFingerSliceFromMid(position, direction);
  194. if (nextSlice != null)
  195. {
  196. trail.addSlice(nextSlice);
  197. trail = expandTrail(trail);
  198. if (trail.NumSlices > Parameters.FingerMinNumSlices / 2)
  199. {
  200. trail.removeFirstSlices(Parameters.FingerRemoveNumSlicesForCorrection);
  201. trail = expandTrail(trail, true);
  202. return trail;
  203. }
  204. }
  205. }
  206. return null;
  207. }
  208. /// <summary>
  209. /// Checks if a possible finger slice is located on a finger. To pass this test, the depth value at the mid has to be lower than on the outside (start and end).
  210. /// </summary>
  211. /// <param name="slice">the possible finger slice</param>
  212. /// <returns>whether the slice is located on a finger</returns>
  213. private bool fingerSliceDepthTest(FingerSlice slice)
  214. {
  215. UInt16 depthStart = depthImage.getDepthAt(slice.Start.moveWithinBound(depthImage.Size, slice.Direction.getInverse(), Parameters.FingerContourMargin));
  216. UInt16 depthMid = depthImage.getDepthAt(slice.Mid);
  217. UInt16 depthEnd = depthImage.getDepthAt(slice.End.moveWithinBound(depthImage.Size, slice.Direction, Parameters.FingerContourMargin));
  218. return depthStart > depthMid && depthMid < depthEnd;
  219. }
  220. /// <summary>
  221. /// Gets the edge direction of the given edge point. The edge direction is either horizontal, vertical, diagonal (both) or null.
  222. /// </summary>
  223. /// <param name="edgePoint">the edge point</param>
  224. /// <returns>the edge direction at the given point</returns>
  225. private Vector2D getEdgeDirection(Vector2D edgePoint)
  226. {
  227. int x = edgePoint.IntX;
  228. int y = edgePoint.IntY;
  229. if (edgeImageAdapted.isEdgeAt(x, y - 1) && edgeImageAdapted.isEdgeAt(x, y + 1)) return new Vector2D(0, 1);
  230. else if (edgeImageAdapted.isEdgeAt(x - 1, y) && edgeImageAdapted.isEdgeAt(x + 1, y)) return new Vector2D(1, 0);
  231. else if (edgeImageAdapted.isEdgeAt(x - 1, y - 1) && edgeImageAdapted.isEdgeAt(x + 1, y + 1)) return new Vector2D(1, 1).normalize();
  232. else if (edgeImageAdapted.isEdgeAt(x + 1, y - 1) && edgeImageAdapted.isEdgeAt(x - 1, y + 1)) return new Vector2D(1, -1).normalize();
  233. else return null;
  234. }
  235. /// <summary>
  236. /// Checks whether the finger has enough space around itself (fingers from a closed hand shouldn't bee detected).
  237. /// </summary>
  238. /// <param name="finger">the finger</param>
  239. /// <returns>whether the finger has enough space around itself</returns>
  240. private bool isCrippleFinger(Finger finger)
  241. {
  242. FingerSlice midSlice = finger.SliceTrail.MidSlice;
  243. Vector2D out1 = midSlice.Start.moveWithinBound(depthImage.Size, midSlice.Direction.getInverse(), Parameters.FingerOutMargin);
  244. Vector2D out2 = midSlice.End.moveWithinBound(depthImage.Size, midSlice.Direction, Parameters.FingerOutMargin);
  245. UInt16 depthAtFinger = depthImage.getDepthAt(finger.MidPoint);
  246. UInt16 depthAtOut1 = depthImage.getDepthAt(out1);
  247. UInt16 depthAtOut2 = depthImage.getDepthAt(out2);
  248. int minDepthDifference = Math.Min(Math.Abs(depthAtFinger - depthAtOut1), Math.Abs(depthAtFinger - depthAtOut2));
  249. return minDepthDifference < Parameters.FingerMaxCrippleDifference;
  250. }
  251. /// <summary>
  252. /// Sorts a slice trail in the correct order, so that the start is at the finger tip and the end is at the hand. To guess the correct order the width of the first and last slice are compared.
  253. /// </summary>
  254. /// <param name="trail">the slice trail of the finger</param>
  255. /// <returns>the slice trail of the finger in correct order</returns>
  256. private FingerSliceTrail orderTrailTipToHand(FingerSliceTrail trail)
  257. {
  258. float startLength = trail.StartSlice.Length;
  259. float endLength = trail.EndSlice.Length;
  260. if (startLength > endLength)
  261. trail.reverse();
  262. return trail;
  263. }
  264. }
  265. }