using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using bbiwarg.Images; using bbiwarg.Recognition.FingerRecognition; using bbiwarg.Output; using bbiwarg.Utility; using bbiwarg.Input.InputHandling; using Emgu.CV; using Emgu.CV.Structure; namespace bbiwarg.Recognition.HandRecognition { /// /// 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. /// class HandDetector { /// /// the depth image of the current frame /// private DepthImage depthImage; /// /// the modified depth image of the current frame (hand flooding changes depth image) /// private Image modifiedHandDepthImage; /// /// the fingers in the current frame /// private List fingers; /// /// a mapping of hands and list of fingers, that don't belong to that specific hand /// private Dictionary> otherHandsFingers; /// /// the hands in the current frame /// private List hands; /// /// Detects Hands in the current frame and stores found hands in frameData.detectedHands /// /// the current frame public void detectHands(FrameData frameData) { depthImage = frameData.DepthImage; fingers = frameData.TrackedFingers; createModifiedHandEdgeImage(); findHands(); fixOverlappingFingers(); findCentroids(); frameData.DetectedHands = hands; } /// /// 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). /// private void createModifiedHandEdgeImage() { modifiedHandDepthImage = depthImage.Image.Copy(); foreach (Finger finger in fingers) { Int16 depthAtHand = depthImage.getDepthAt(finger.HandPoint); Point[] contour = finger.getContour(0f).ToArray(); modifiedHandDepthImage.DrawPolyline(contour, false, new Gray(depthAtHand), 1); } } /// /// 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. /// private void findHands() { hands = new List(); otherHandsFingers = new Dictionary>(); List assignedFingers = new List(); foreach (Finger finger in fingers) { if(!assignedFingers.Contains(finger)) { Image handMask = getHandMask(finger.HandPoint); int numPixels = handMask.CountNonzero()[0]; if (numPixels > Parameters.HandMaxSize * depthImage.Size.NumPixels) { assignedFingers.Add(finger); break; } List fingersOnHand = new List(); List fingersOnOtherHand = new List(); foreach (Finger f in fingers) { if (!assignedFingers.Contains(f) && handMask.Data[f.HandPoint.IntY, f.HandPoint.IntX, 0] != 0) { fingersOnHand.Add(f); assignedFingers.Add(f); } else fingersOnOtherHand.Add(f); } Hand hand = new Hand(handMask, fingersOnHand); otherHandsFingers.Add(hand, fingersOnOtherHand); hands.Add(hand); } } } /// /// Flood fills from a given point and returns the filled area as mask. /// /// flood fill starting point /// the filled area as mask private Image getHandMask(Vector2D p) { Image mask = new Image(depthImage.Size.Width + 2, depthImage.Size.Height + 2); MCvConnectedComp comp = new MCvConnectedComp(); 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); return mask.Copy(new Rectangle(1, 1, depthImage.Size.Width, depthImage.Size.Height)); } /// /// Fixes overlapping fingers by merging two hands if they are seperated by a finger and/or fills holes caused by overlapping fingers. /// private void fixOverlappingFingers() { extendOrMergeThroughOverlappingFingers(); fillOverlappingFingers(); } /// /// Merges two hands if they are seperatd by an overlapping finger or extends the hand mask through an overlapping finger. /// private void extendOrMergeThroughOverlappingFingers() { List mergedHands = new List(); foreach (Hand hand in hands) { if (!mergedHands.Contains(hand)) { List mergeHands = new List(); foreach (Finger overlappingFinger in otherHandsFingers[hand]) { FingerSlice midSlice = overlappingFinger.SliceTrail.MidSlice; Vector2D midOut1 = midSlice.Start.moveWithinBound(depthImage.Size, midSlice.Direction.getInverse(), Parameters.FingerOutMargin); Vector2D midOut2 = midSlice.End.moveWithinBound(depthImage.Size, midSlice.Direction, Parameters.FingerOutMargin); Int16 depthAtMidOut1 = depthImage.getDepthAt(midOut1); Int16 depthAtMidOut2 = depthImage.getDepthAt(midOut2); bool midOut1InHand = hand.isInside(midOut1); bool midOut2InHand = hand.isInside(midOut2); Int16 maxDepth = depthImage.MaxDepth; if (midOut1InHand != midOut2InHand && depthAtMidOut1 != maxDepth && depthAtMidOut2 != maxDepth && Math.Abs(depthAtMidOut1 - depthAtMidOut2) < Parameters.HandExtendMaxDifference) { Vector2D pHand, pHandExtension; if (midOut1InHand) { pHand = midOut1; pHandExtension = midOut2; } else { pHand = midOut2; pHandExtension = midOut1; } //check if pHandExtension is in other hand (if so -> merge with hand) bool merge = false; foreach (Hand mergeHand in hands) { if (mergeHand.isInside(pHandExtension) && !mergedHands.Contains(mergeHand)) { mergeHands.Add(mergeHand); merge = true; break; } } //if no merge, extend hand if (!merge) extendToHand(hand, pHandExtension); } } foreach (Hand mergeHand in mergeHands) { mergeToHand(hand, mergeHand); mergedHands.Add(mergeHand); } } } foreach (Hand mergedHand in mergedHands) hands.Remove(mergedHand); } /// /// Merges two hands together and updates the list of other hands fingers. /// /// the first hand (other hand will be merged to this one) /// the second hand (this hand will be dropped afterwards) private void mergeToHand(Hand hand, Hand mergeHand) { hand.mergeWith(mergeHand); foreach (Finger finger in mergeHand.Fingers) otherHandsFingers[hand].Remove(finger); otherHandsFingers.Remove(mergeHand); } /// /// Extends the hand mask of a given hand by flood filling starting from the given point. /// /// the hand that should be extended /// the flood fill starting point private void extendToHand(Hand hand, Vector2D p) { Image extendMask = getHandMask(p); int numPixels = extendMask.CountNonzero()[0]; if (numPixels <= Parameters.HandExtensionMaxRelativeSize * depthImage.Size.NumPixels) hand.extendMask(extendMask); } /// /// Fills holes caused by overlapping fingers. /// private void fillOverlappingFingers() { foreach (Hand hand in hands) { hand.fillOverlappingFingers(otherHandsFingers[hand]); } } /// /// Finds the hands centroids. /// private void findCentroids() { foreach (Hand hand in hands) hand.findCentroid(); } } }