FingerDetector.cs 14 KB

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