Browse Source

-rewritten HandDetector+Hand
-rewritten/added PalmDetector/PalmTracker/Palm/TrackedPalm
-added support for multiple touchevents at the same time in touchEventVisualizer
-rewritten FingerSlice/Finger.Contour/Finger.InnerContour

Alexander Hendrich 11 years ago
parent
commit
29da205386

+ 43 - 26
bbiwarg/Constants.cs

@@ -9,10 +9,19 @@ using bbiwarg.Utility;
 
 namespace bbiwarg
 {
+    public enum InputType {
+        Camera = 0,
+        Movie = 1
+    }
+
     class Constants
     {
+        // input
+        public static readonly InputType InputSource = InputType.Camera;
+        public static readonly String InputMoviePath = "..\\..\\videos\\touch\\4.skv";
+
         // Logger
-        public static readonly LogSubject LogLevel = LogSubject.None;
+        public static readonly LogSubject LogLevel = LogSubject.Timer;
         public static readonly int ConsoleWidth = 90;
         public static readonly int ConsoleHeight = 30;
 
@@ -37,7 +46,7 @@ namespace bbiwarg
         public static readonly int ConfidenceImageMinThreshold = 500;
 
         // depth image
-        public static readonly int DepthImageMedianSize = 3;
+        public static readonly int DepthImageMedianSize = 5;
         public static readonly int DepthImageDepthRange = 200; // <255
 
         // edge image
@@ -56,39 +65,46 @@ namespace bbiwarg
         public static readonly int FingerMinWidth = 5;
         public static readonly int FingerRemoveNumSlicesForCorrection = 5;
         public static readonly int FingerNumSlicesForRelativeDirection = FingerRemoveNumSlicesForCorrection;
-        public static readonly int FingerContourMargin = 2;
-        public static readonly int FingerSliceOverlapFactor = 2;
         public static readonly int FingerCrippleOutFactor = 8;
         public static readonly int FingerCrippleOutMinDifference = 20;
         public static readonly int FingerNumDirectionsForReverseCheck = 20;
+        public static readonly int FingerSliceOutMargin = 4;
 
         // finger tracking
+        public static readonly float FingerMinSimilarityForTracking = 0.8f;
         public static readonly int FingerNumFramesDetectedUntilTracked = 5;
         public static readonly int FingerNumFramesLostUntilDeleted = 10;
-        public static readonly float FingerMinSimilarityForTracking = 0.7f;
-        public static readonly float FingermXX = 0.00005f;
-        public static readonly float FingermXY = 0.0000f;
-        public static readonly float FingermYY = 0.00005f;
+        public static readonly float FingermXX = 0.0005f;
+        public static readonly float FingermXY = 0.0005f;
+        public static readonly float FingermYY = 0.0005f;
 
         // hand detection
         public static readonly float HandMaxSize = 0.7f;
+        public static readonly int HandNumColors = 3;
+        public static readonly float HandThumbDefectMaxDistanceToThumb = 2 * FingerMaxWidth;
+        public static readonly float HandThumbDefectMinThumbShortLengthRatio = 0.75f;
+        public static readonly float HandThumbDefectMaxThumbShortLengthRatio = 1.1f;
+        public static readonly float HandThumbDefectMinShortLongLengthRatio = 0.3f;
+        public static readonly float HandThumbDefectMaxShortLongLengthRatio = 0.7f;
+
+        // hand tracker
+        public static readonly float HandTrackerMinSimilarity = 0.7f;
+        public static readonly int HandTrackerNumFramesDetectedUntilTracked = 2;
+        public static readonly int HandTrackerNumFramesLostUntilDeleted = 5;
+        public static readonly float HandmXX = 0.0005f;
+        public static readonly float HandmXY = 0.0005f;
+        public static readonly float HandmYY = 0.0005f;
 
         // palm detection
-        public static readonly float PalmMinDefectMidFingerLineDistance = 20; // defects with mid point ((start + end) / 2) closer than this to a finger line are removed 
-        public static readonly float PalmMaxThumbDefectAngle = 110; // degree
-        public static readonly float PalmMaxThumbDefectStartEndLengthQuotient = 2.3f;
-        public static readonly float PalmMinThumbDefectStartEndLengthQuotient = 1.2f;
-        public static readonly float PalmMinTumbDefectDepthThumbLengthQuotient = 0.8f;
-        public static readonly float PalmMaxTumbDefectDepthThumbLengthQuotient = 1.2f;
-        public static readonly float PalmThumbDefectmXX = 50.0f;
-        public static readonly float PalmThumbDefectmXY = -25.0f;
-        public static readonly float PalmThumbDefectmYY = 50.0f;
-        public static readonly float PalmThumbDefectProcessNoise = 5.0e+1f;
-        public static readonly int PalmNumFramesNoHandReset = 5;
-        public static readonly float PalmMinAreaQuotient = 0.4f;
-        public static readonly float PalmMaxAreaQuotient = 1.8f;
-        public static readonly float PalmMinPrecentageQuadForeground = 0.8f;
-        public static readonly float PalmMaxPrecentageQuadForegroundReset = 0.5f;
+
+        // palm tracker
+        public static readonly float PalmTrackerMinSimilarity = 0.5f;
+        public static readonly int PalmTrackerNumFramesDetectedUntilTracked = 2;
+        public static readonly int PalmTrackerNumFramesLostUntilDeleted = 5;
+        public static readonly float PalmmXX = 0.00005f;
+        public static readonly float PalmmXY = 0.00005f;
+        public static readonly float PalmmYY = 0.00005f;
+
 
         //palm Grid
         public static readonly int PalmGridNumRows = 3;
@@ -104,9 +120,9 @@ namespace bbiwarg
         public static readonly float TouchProcessNoise = 3.0e-4f;
 
         // touch tracking
+        public static readonly float TouchEventMinSimilarityForTracking = 0.7f;
         public static readonly int TouchEventNumFramesDetectedUntilTracked = 1;
         public static readonly int TouchEventNumFramesLostUntilDeleted = 5;
-        public static readonly float TouchEventMinSimilarityForTracking = 0.7f;
         public static readonly float TouchmXX = 0.0065f;
         public static readonly float TouchmXY = 0.0f;
         public static readonly float TouchmYY = 0.0065f;
@@ -147,7 +163,8 @@ namespace bbiwarg
         public static readonly Color PalmConvexHullColor = Color.Green;
         public static readonly Color PalmThumbDefectColor = Color.Lime;
 
-        public static readonly Color HandRightColor = Color.Red;
-        public static readonly Color HandLeftColor = Color.Blue;
+        public static readonly Color[] HandColor = new Color[3] { Color.Red, Color.Blue, Color.Green};
+        public static readonly Color HandCentroidColor = Color.Yellow;
+        public static readonly Color HandIDColor = Color.White;
     }
 }

+ 10 - 10
bbiwarg/Graphics/OutputImage.cs

@@ -72,33 +72,33 @@ namespace bbiwarg.Graphics
             if (color.R != 0)
             {
                 if (color.R != byte.MaxValue)
-                    Image[0] = image.Mul((float)color.R / byte.MaxValue);
+                    Image[0] = Image[0].Or(image.Mul((float)color.R / byte.MaxValue));
                 else
-                    Image[0] = image;
+                    Image[0] = Image[0].Or(image);
             }
             if (color.G != 0)
             {
                 if (color.G != byte.MaxValue)
-                    Image[1] = image.Mul((float)color.G / byte.MaxValue);
+                    Image[1] = Image[1].Or(image.Mul((float)color.G / byte.MaxValue));
                 else
-                    Image[1] = image;
+                    Image[1] = Image[1].Or(image);
             }
             if (color.B != 0)
             {
                 if (color.B != byte.MaxValue)
-                    Image[2] = image.Mul((float)color.B / byte.MaxValue);
+                    Image[2] = Image[2].Or(image.Mul((float)color.B / byte.MaxValue));
                 else
-                    Image[2] = image;
+                    Image[2] = Image[2].Or(image);
             }
         }
 
         public void drawQuadrangleGrid(Quadrangle quad, Color borderColor, Color gridColor, int numRows, int numCols)
         {
 
-            Vector2D a = quad.BottomLeft;
-            Vector2D b = quad.TopLeft;
-            Vector2D c = quad.TopRight;
-            Vector2D d = quad.BottomRight;
+            Vector2D a = quad.TopLeft;
+            Vector2D b = quad.TopRight;
+            Vector2D c = quad.BottomRight;
+            Vector2D d = quad.BottomLeft;
 
             Vector2D relAB = (b - a) / numCols;
             Vector2D relDC = (c - d) / numCols;

+ 36 - 30
bbiwarg/Graphics/TouchEventVisualizer.cs

@@ -17,47 +17,58 @@ namespace bbiwarg.Graphics
         private int width, height;
         private Stopwatch timer;
 
-        private Dictionary<int, List<Vector2D>> currentPositions;
-        private Dictionary<long, List<Vector2D>> oldPositions;
+        private Dictionary<int, int> idTranslations;
+        private Dictionary<int, List<Vector2D>> positions;
+        private Dictionary<int, long> lastUpdates;
+        private int nextFreeID;
 
 
         public TouchEventVisualizer(int width, int height)
         {
             this.width = width;
             this.height = height;
-            this.timer = new Stopwatch();
-            this.currentPositions = new Dictionary<int, List<Vector2D>>();
-            this.oldPositions = new Dictionary<long, List<Vector2D>>();
+            reset();
 
-            this.timer.Start();
         }
 
         public void reset()
         {
-            currentPositions.Clear();
+            timer = new Stopwatch();
+            timer.Start();
+
+            nextFreeID = 1;
+            idTranslations = new Dictionary<int, int>();
+            positions = new Dictionary<int, List<Vector2D>>();
+            lastUpdates = new Dictionary<int, long>();
+
         }
 
         public void touchDown(object sender, TouchEventArgs tea)
         {
-            List<Vector2D> posList = new List<Vector2D>();
-            posList.Add(tea.RelativePosition);
-            currentPositions.Add(tea.TrackID, posList);
+            int id = nextFreeID;
+            nextFreeID++;
+
+            idTranslations.Add(tea.TrackID, id);
+            positions.Add(id, new List<Vector2D>());
+            positions[id].Add(tea.RelativePosition);
+            lastUpdates.Add(id, timer.ElapsedMilliseconds);
         }
 
 
         public void touchMove(object sender, TouchEventArgs tea)
         {
-            currentPositions[tea.TrackID].Add(tea.RelativePosition);
+            int id = idTranslations[tea.TrackID];
+            positions[id].Add(tea.RelativePosition);
+            lastUpdates[id] = timer.ElapsedMilliseconds;
         }
 
         public void touchUp(object sender, TouchEventArgs tea)
         {
-            currentPositions[tea.TrackID].Add(tea.RelativePosition);
+            int id = idTranslations[tea.TrackID];
+            positions[id].Add(tea.RelativePosition);
+            lastUpdates[id] = timer.ElapsedMilliseconds;
 
-            List<Vector2D> posList = currentPositions[tea.TrackID];
-            oldPositions.Add(timer.ElapsedMilliseconds, posList);
-
-            currentPositions.Remove(tea.TrackID);
+            idTranslations.Remove(tea.TrackID);
         }
 
         public void updateImage()
@@ -65,15 +76,14 @@ namespace bbiwarg.Graphics
             //remove old positions
             long currentTime = timer.ElapsedMilliseconds;
 
-            List<long> removeKeys = new List<long>();
-            foreach (long lastUpdateTime in oldPositions.Keys)
-            {
-                if (currentTime - lastUpdateTime > Constants.TouchEventVisualizerFadeOutTime)
-                    removeKeys.Add(lastUpdateTime);
+            List<int> ids = new List<int>(lastUpdates.Keys);
+            for (int i = ids.Count - 1; i >= 0; i--) {
+                int id = ids[i];
+                if (currentTime - lastUpdates[id] > Constants.TouchEventVisualizerFadeOutTime) {
+                    positions.Remove(id);
+                    lastUpdates.Remove(id);
+                }
             }
-            foreach (long lastUpdateTime in removeKeys)
-                oldPositions.Remove(lastUpdateTime);
-
 
             OutputImage = new OutputImage(width, height);
 
@@ -104,12 +114,8 @@ namespace bbiwarg.Graphics
                 }
             }
 
-
-            foreach (int id in currentPositions.Keys)
-                drawTouchGesture(currentPositions[id], 1);
-            foreach (long lastUpdate in oldPositions.Keys)
-                drawTouchGesture(oldPositions[lastUpdate], 1 - ((currentTime - lastUpdate) / (float)Constants.TouchEventVisualizerFadeOutTime));
-
+            foreach (int id in positions.Keys)
+                drawTouchGesture(positions[id], 1 - ((currentTime - lastUpdates[id]) / (float)Constants.TouchEventVisualizerFadeOutTime));
         }
 
         public void drawTouchGesture(List<Vector2D> positions, float opacity)

+ 5 - 2
bbiwarg/Images/DepthImage.cs

@@ -4,6 +4,7 @@ using System.Drawing;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using System.Runtime.InteropServices;
 using Emgu.CV;
 using Emgu.CV.Structure;
 using bbiwarg.Graphics;
@@ -29,15 +30,17 @@ namespace bbiwarg.Images
             Height = image.Height;
             BottomRight = new Vector2D(Width - 1, Height - 1);
 
+            //image = image.SmoothBlur(3,3,true);
             image = image.SmoothMedian(Constants.DepthImageMedianSize);
-
+            
             //threshold min&maxDepth
             MinDepth = findMinDepth(image);
             MaxDepth = (Int16)(MinDepth + Constants.DepthImageDepthRange);
 
             //smooth+threshold (dst = (src > (MaxDepth - MinDepth)) ? MaxDepth - MinDepth : src)
             Image = (image - MinDepth).ThresholdTrunc(new Gray(MaxDepth - MinDepth)).Convert<Gray, byte>();
-
+            
+            //Image = Image.SmoothBlur(3,3, true);
             Image = Image.SmoothMedian(Constants.DepthImageMedianSize);
         }
 

+ 41 - 28
bbiwarg/InputHandler.cs

@@ -40,6 +40,8 @@ namespace bbiwarg
         private TouchDetector touchDetector;
 
         private FingerTracker fingerTracker;
+        private HandTracker handTracker;
+        private PalmTracker palmTracker;
         private TouchTracker touchTracker;
 
         private TouchEventVisualizer touchEventVisualizer;
@@ -58,9 +60,9 @@ namespace bbiwarg
 
         private void initializeConsistentObjects()
         {
-            palmDetector = new PalmDetector();
-
             fingerTracker = new FingerTracker();
+            handTracker = new HandTracker();
+            palmTracker = new PalmTracker();
             touchTracker = new TouchTracker();
 
             if (Constants.OutputEnabled)
@@ -85,9 +87,9 @@ namespace bbiwarg
 
         private void resetConsistentObjects()
         {
-            palmDetector.reset();
-
             touchTracker.reset();
+            handTracker.reset();
+            palmTracker.reset();
             fingerTracker.reset();
 
             if (Constants.OutputEnabled)
@@ -116,7 +118,10 @@ namespace bbiwarg
                 trackFingers();
 
                 detectHands();
+                trackHands();
+
                 detectPalm();
+                trackPalm();
 
                 detectTouchEvents();
                 trackTouchEvents();
@@ -197,21 +202,35 @@ namespace bbiwarg
         private void detectHands()
         {
             Timer.start("detectHands");
-            handDetector = new HandDetector(edgeImage, fingerDetector.Fingers);
+            handDetector = new HandDetector(depthImage, edgeImage, fingerTracker.Fingers);
             Timer.stop("detectHands");
         }
 
+        private void trackHands()
+        {
+            Timer.start("trackHands\t");
+            handTracker.updateFrame(handDetector.Hands);
+            Timer.stop("trackHands\t");
+        }
+
         private void detectPalm()
         {
-            Timer.start("detectPalmQuad");
-            palmDetector.findPalmQuad(handDetector.Hands, handDetector.HandMask);
-            Timer.stop("detectPalmQuad");
+            Timer.start("detectPalm\t");
+            palmDetector = new PalmDetector(handTracker.Hands);
+            Timer.stop("detectPalm\t");
+        }
+
+        private void trackPalm()
+        {
+            Timer.start("trackPalm\t");
+            palmTracker.updateFrame(palmDetector.Palms);
+            Timer.stop("trackPalm\t");
         }
 
         private void detectTouchEvents()
         {
             Timer.start("detectTouchEvents");
-            touchDetector = new TouchDetector(depthImage, fingerTracker.Fingers, palmDetector.PalmQuad);
+            touchDetector = new TouchDetector(depthImage, fingerTracker.Fingers, palmTracker.OptimizedPalms);
             Timer.stop("detectTouchEvents");
         }
 
@@ -260,32 +279,26 @@ namespace bbiwarg
             }
 
             //image2
-            foreach (Hand h in handDetector.Hands)
+            foreach (Hand h in handTracker.Hands)
             {
-                if (h.Side == Hand.HandSide.Right)
-                    OutputImages[2].drawImage(h.Mask.ThresholdBinary(new Gray(0), new Gray(255)), Constants.HandRightColor);
-                else
-                    OutputImages[2].drawImage(h.Mask.ThresholdBinary(new Gray(0), new Gray(255)), Constants.HandLeftColor);
+                OutputImages[2].drawImage(h.Mask.ThresholdBinary(new Gray(0), new Gray(255)), Constants.HandColor[h.ZIndex % Constants.HandNumColors]);
+                OutputImages[2].fillCircle(h.Centroid.IntX, h.Centroid.IntY, 5, Constants.HandCentroidColor);
+                OutputImages[2].drawText(h.Centroid.IntX, h.Centroid.IntY, h.TrackID.ToString(), Constants.HandIDColor);
+
+                if (h.ThumbDefect != null)
+                {
+                    OutputImages[2].fillCircle(h.ThumbDefect.Inner.IntX, h.ThumbDefect.Inner.IntY, 4, Color.CornflowerBlue);
+                    OutputImages[2].fillCircle(h.ThumbDefect.OuterShort.IntX, h.ThumbDefect.OuterShort.IntY, 4, Color.CornflowerBlue);
+                    OutputImages[2].fillCircle(h.ThumbDefect.OuterLong.IntX, h.ThumbDefect.OuterLong.IntY, 4, Color.CornflowerBlue);
+                }
             }
 
             //image3
             OutputImages[3].drawImage((depthImage.MaxDepth - depthImage.MinDepth) - depthImage.Image.Or(255 - handDetector.HandMask.ThresholdBinary(new Gray(0), new Gray(255))), Constants.DepthImageColor);
             foreach (TouchEvent te in touchTracker.TouchEvents)
                 OutputImages[3].fillCircle(te.Position.IntX, te.Position.IntY, 5, Constants.TouchEventTrackedColor);
-            if (palmDetector.PalmContour != null && palmDetector.PalmContour.Count<Point>() > 0)
-            {
-                OutputImages[3].drawContour(palmDetector.PalmContour, Constants.PalmConturColor);
-                OutputImages[3].drawPoints(palmDetector.PalmContour.GetConvexHull(Emgu.CV.CvEnum.ORIENTATION.CV_CLOCKWISE), Constants.PalmConvexHullColor);
-            }
-            if (palmDetector.PalmQuad != null)
-            {
-                OutputImages[3].fillCircle(palmDetector.ThumbDefectStart.IntX, palmDetector.ThumbDefectStart.IntY, 3, Color.Red);
-                OutputImages[3].fillCircle(palmDetector.ThumbDefectEnd.IntX, palmDetector.ThumbDefectEnd.IntY, 3, Color.Red);
-                OutputImages[3].fillCircle(palmDetector.ThumbDefectDepth.IntX, palmDetector.ThumbDefectDepth.IntY, 3, Color.Red);
-
-                OutputImages[3].drawLineSegment(new Utility.LineSegment2D(palmDetector.ThumbDefectDepth, (palmDetector.ThumbDefectStart + palmDetector.ThumbDefectEnd) / 2.0f), Constants.PalmThumbDefectColor, 1);
-
-                OutputImages[3].drawQuadrangleGrid(palmDetector.PalmQuad, Constants.PalmQuadColor, Constants.PalmGridColor, Constants.PalmGridNumRows, Constants.PalmGridNumColumns);
+            foreach (Palm p in palmTracker.OptimizedPalms) {
+                OutputImages[3].drawQuadrangleGrid(p.Quad, Constants.PalmQuadColor, Constants.PalmGridColor, Constants.PalmGridNumRows, Constants.PalmGridNumColumns);
             }
 
             //image4

+ 13 - 15
bbiwarg/InputProviders/InputProvider.cs

@@ -20,10 +20,22 @@ namespace bbiwarg.InputProviders
         protected IDataHandle<Iisu.Data.IImageData> depthImage;
         protected IDataHandle<Iisu.Data.IImageData> confidenceImage;
 
-        public void initialize()
+
+        public void start()
         {
             createDevice();
             registerHandles();
+
+            device.Start();
+            device.UpdateFrame(true);
+            ImageWidth = (int)depthImage.Value.ImageInfos.Width;
+            ImageHeight = (int)depthImage.Value.ImageInfos.Height;
+            device.ReleaseFrame();
+        }
+
+        public void stop()
+        {
+            device.Stop(true);
         }
 
         protected void createDevice()
@@ -56,19 +68,5 @@ namespace bbiwarg.InputProviders
 
             CurrentFrame = new InputFrame(CurrentFrameID, ImageWidth, ImageHeight, rawDepthData, rawConfidenceData);
         }
-
-        public void start()
-        {
-            device.Start();
-            device.UpdateFrame(true);
-            ImageWidth = (int)depthImage.Value.ImageInfos.Width;
-            ImageHeight = (int)depthImage.Value.ImageInfos.Height;
-            device.ReleaseFrame();
-        }
-
-        public void stop()
-        {
-            device.Stop(true);
-        }
     }
 }

+ 5 - 3
bbiwarg/MainBBWIWARG.cs

@@ -17,9 +17,11 @@ namespace bbiwarg
             Console.SetWindowSize(Constants.ConsoleWidth, Constants.ConsoleHeight);
 
             InputProvider inputProvider;
-            inputProvider = new InputProvider(); // camera
-            //inputProvider = new VideoInputProvider("..\\..\\videos\\touch\\4.skv"); // video
-            inputProvider.initialize();
+            if (Constants.InputSource == InputType.Movie)
+                inputProvider = new VideoInputProvider(Constants.InputMoviePath);
+            else
+                inputProvider = new InputProvider();
+
             inputProvider.start();
 
 

+ 1 - 0
bbiwarg/Recognition/FingerRecognition/Finger.cs

@@ -25,6 +25,7 @@ namespace bbiwarg.Recognition.FingerRecognition
         public LineSegment2D LineSegment { get { return SliceTrail.LineSegment; } }
         public FingerSliceTrail SliceTrail { get; private set; }
         public Contour<Point> Contour { get { return SliceTrail.Contour; } }
+        public Contour<Point> InnerContour { get { return SliceTrail.InnerContour; } }
         public Hand Hand { get; private set; }
         public TouchEvent TouchEvent { get; private set; }
 

+ 6 - 10
bbiwarg/Recognition/FingerRecognition/FingerDetector.cs

@@ -160,21 +160,17 @@ namespace bbiwarg.Recognition.FingerRecognition
         }
         private FingerSlice findFingerSliceFromStartEdge(Vector2D start, Vector2D direction)
         {
-            Vector2D searchStart = start + Constants.FingerSliceOverlapFactor * direction;
+            Vector2D searchStart = start + Constants.FingerSliceOutMargin * direction;
             Vector2D end = edgeImageAdapted.findNextEdge(searchStart, direction, Constants.FingerMaxWidth);
-            if (end == null) return null;
+            if (end == null) 
+                return null;
 
             return getFingerSlice(start, end);
         }
 
         private FingerSlice getFingerSlice(Vector2D start, Vector2D end)
         {
-            Vector2D direction = (end - start).normalize();
-            Vector2D directionInv = direction.getInverse();
-            Vector2D beforeStart = (start + Constants.FingerSliceOverlapFactor * directionInv).moveInBound(Vector2D.Zero, depthImage.BottomRight, direction);
-            Vector2D behindEnd = (end + Constants.FingerSliceOverlapFactor * direction).moveInBound(Vector2D.Zero, depthImage.BottomRight, directionInv);
-
-            FingerSlice slice = new FingerSlice(beforeStart, behindEnd);
+            FingerSlice slice = new FingerSlice(start, end);
             if (slice.Length >= Constants.FingerMinWidth && slice.Length <= Constants.FingerMaxWidth && fingerSliceDepthTest(slice))
                 return slice;
 
@@ -183,9 +179,9 @@ namespace bbiwarg.Recognition.FingerRecognition
 
         private bool fingerSliceDepthTest(FingerSlice fingerSlice)
         {
-            Int16 depthStart = depthImage.getDepthAt(fingerSlice.Start);
+            Int16 depthStart = depthImage.getDepthAt(fingerSlice.StartOuter);
             Int16 depthMid = depthImage.getDepthAt(fingerSlice.Mid);
-            Int16 depthEnd = depthImage.getDepthAt(fingerSlice.End);
+            Int16 depthEnd = depthImage.getDepthAt(fingerSlice.EndOuter);
             return (depthStart > depthMid && depthMid < depthEnd);
         }
 

+ 7 - 2
bbiwarg/Recognition/FingerRecognition/FingerSlice.cs

@@ -12,9 +12,11 @@ namespace bbiwarg.Recognition.FingerRecognition
         private LineSegment2D lineSegment;
         private bool lineSegmentUpToDate;
 
-        public Vector2D Start { get; private set; }
         public Vector2D Mid { get; private set; }
+        public Vector2D Start { get; private set; }
+        public Vector2D StartOuter { get; private set; }
         public Vector2D End { get; private set; }
+        public Vector2D EndOuter { get; private set; }
         public LineSegment2D LineSegment { get { if (!lineSegmentUpToDate) updateLineSegment(); return lineSegment; } }
         public Vector2D Direction { get { return LineSegment.Direction; } }
         public float Length { get { return LineSegment.Length; } }
@@ -24,12 +26,15 @@ namespace bbiwarg.Recognition.FingerRecognition
             Start = start;
             End = end;
             Mid = (start + end) / 2;
-            lineSegmentUpToDate = false;
+            updateLineSegment();
+            StartOuter = (Start + Constants.FingerSliceOutMargin * Direction.getInverse()).moveInBound(Vector2D.Zero, Constants.MaxPixel, Direction);
+            EndOuter = (End + Constants.FingerSliceOutMargin * Direction).moveInBound(Vector2D.Zero, Constants.MaxPixel, Direction.getInverse());
         }
 
         private void updateLineSegment()
         {
             lineSegment = new LineSegment2D(Start, End);
+            lineSegmentUpToDate = true;
         }
 
     }

+ 33 - 2
bbiwarg/Recognition/FingerRecognition/FingerSliceTrail.cs

@@ -14,8 +14,10 @@ namespace bbiwarg.Recognition.FingerRecognition
         private List<FingerSlice> slices;
         private LineSegment2D lineSegment;
         private Contour<Point> contour;
+        private Contour<Point> innerContour;
         private bool lineSegmentUpToDate;
         private bool contourUpToDate;
+        private bool innerContourUpToDate;
 
         public FingerSlice StartSlice { get { return slices[0]; } }
         public FingerSlice MidSlice { get { return slices[NumSlices / 2]; } }
@@ -24,6 +26,7 @@ namespace bbiwarg.Recognition.FingerRecognition
         public int NumSlices { get { return slices.Count; } }
         public LineSegment2D LineSegment { get { if (!lineSegmentUpToDate) updateLineSegment(); return lineSegment; } }
         public Contour<Point> Contour { get { if (!contourUpToDate) updateContour(); return contour; } }
+        public Contour<Point> InnerContour { get { if (!innerContourUpToDate) updateInnerContour(); return innerContour; } }
 
         public FingerSliceTrail(FingerSlice slice)
         {
@@ -69,6 +72,7 @@ namespace bbiwarg.Recognition.FingerRecognition
         private void updateLineSegment()
         {
             lineSegment = new LineSegment2D(EndSlice.Mid, StartSlice.Mid);
+            lineSegmentUpToDate = true;
         }
 
         private void updateContour()
@@ -78,8 +82,8 @@ namespace bbiwarg.Recognition.FingerRecognition
 
             foreach (FingerSlice slice in slices)
             {
-                pointsA.Add(slice.Start + Constants.FingerContourMargin * slice.Direction.getInverse());
-                pointsB.Add(slice.End + Constants.FingerContourMargin * slice.Direction);
+                pointsA.Add(slice.StartOuter);
+                pointsB.Add(slice.EndOuter);
             }
 
             pointsA.Reverse();
@@ -90,6 +94,33 @@ namespace bbiwarg.Recognition.FingerRecognition
             {
                 contour.Push(p);
             }
+            contourUpToDate = true;
         }
+
+        private void updateInnerContour()
+        {
+            List<Point> pointsA = new List<Point>();
+            List<Point> pointsB = new List<Point>();
+
+            foreach (FingerSlice slice in slices)
+            {
+                pointsA.Add(slice.Start);
+                pointsB.Add(slice.End);
+            }
+
+            pointsA.Reverse();
+            pointsA.AddRange(pointsB);
+
+            innerContour = new Contour<Point>(new MemStorage());
+            foreach (Point p in pointsA)
+            {
+                innerContour.Push(p);
+            }
+            innerContourUpToDate = true;
+        }
+
+
+
+
     }
 }

+ 103 - 12
bbiwarg/Recognition/HandRecognition/Hand.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Drawing;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
@@ -7,27 +8,37 @@ using Emgu.CV;
 using Emgu.CV.Structure;
 using bbiwarg.Recognition.FingerRecognition;
 using bbiwarg.Utility;
+using bbiwarg.Recognition.Tracking;
+using bbiwarg.Recognition.PalmRecognition;
 
 namespace bbiwarg.Recognition.HandRecognition
 {
-    class Hand
+    public enum HandSide {
+        Undefined = 0,
+        Right = 1,
+        Left = 2
+    }
+
+    class Hand : TrackableObject
     {
-        public enum HandSide
-        {
-            Left,
-            Right
-        }
+        private List<ConvexityDefect> convexityDefects;
 
-        public Vector2D Centroid { private set; get; }
-        public HandSide Side { get; set; }
+        public int ZIndex { get; private set; }
+        public Vector2D Centroid { get; private set; }
+        public Vector2D CentroidInHand { get; private set; }
         public Image<Gray, byte> Mask { get; private set; }
         public List<Finger> Fingers { get; private set; }
+        public ConvexityDefect ThumbDefect { get; private set; }
+        public Palm Palm { get; private set; }
+        public HandSide Side { get; private set; }
 
-        public Hand(Image<Gray, byte> mask)
+        public Hand(Image<Gray, byte> mask, Finger finger)
         {
             Mask = mask;
-            Centroid = getCentroid();
             Fingers = new List<Finger>();
+
+            addFinger(finger);
+            findCentroids();
         }
 
         public bool isInside(Vector2D point)
@@ -40,10 +51,90 @@ namespace bbiwarg.Recognition.HandRecognition
             Fingers.Add(finger);
         }
 
-        private Vector2D getCentroid()
+        public void setZIndex(int zIndex)
+        {
+            ZIndex = zIndex;
+        }
+
+        public void findThumbDefect()
         {
+            if (isThumbOnly())
+            {
+                findConvexityDefects();
+                filterThumbDefect();
+            }
+            determineHandSide();
+        }
+
+        public void setPalm(Palm palm)
+        {
+            Palm = palm;
+        }
+
+        private bool isThumbOnly()
+        {
+            return (Fingers.Count == 1);
+        }
+
+        private void findConvexityDefects()
+        {
+            Contour<Point> contour = Mask.FindContours(Emgu.CV.CvEnum.CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE, Emgu.CV.CvEnum.RETR_TYPE.CV_RETR_EXTERNAL);
+            List<MCvConvexityDefect> mcvConvexityDefects = new List<MCvConvexityDefect>(contour.GetConvexityDefacts(new MemStorage(), Emgu.CV.CvEnum.ORIENTATION.CV_CLOCKWISE));
+            
+            convexityDefects = new List<ConvexityDefect>();
+            foreach (MCvConvexityDefect defect in mcvConvexityDefects)
+                convexityDefects.Add(new ConvexityDefect(defect));
+        }
+
+        private void filterThumbDefect()
+        {
+            if (convexityDefects.Count > 0)
+            {
+                convexityDefects.Sort((cd1, cd2) => (cd2.Depth.CompareTo(cd1.Depth)));
+
+                Finger thumb = Fingers[0];
+
+                foreach (ConvexityDefect defect in convexityDefects)
+                {
+                    if (defect.isPossibleThumbDefect(thumb))
+                    {
+                        ThumbDefect = defect;
+                        break;
+                    }
+                }
+            }
+        }
+
+        private void determineHandSide() {
+            if (ThumbDefect != null)
+            {
+                if (ThumbDefect.VectorShort.crossProduct(ThumbDefect.VectorLong) > 0)
+                    Side = HandSide.Left;
+                else
+                    Side = HandSide.Right;
+            }
+            else
+                Side = HandSide.Undefined;
+        }
+
+        private void findCentroids()
+        {
+            //find centroid
             MCvPoint2D64f gravityCenter = Mask.GetMoments(true).GravityCenter;
-            return new Vector2D((float)gravityCenter.x, (float)gravityCenter.y);
+            Centroid = new Vector2D((float)gravityCenter.x, (float)gravityCenter.y);
+
+            //find centroid in hand
+            if (isInside(Centroid))
+                CentroidInHand = Centroid;
+            else
+            {
+                Finger finger = Fingers[0];
+                Vector2D direction = (finger.HandPoint - Centroid).normalize();
+                CentroidInHand = Centroid + direction;
+                while (!isInside(CentroidInHand))
+                    CentroidInHand += direction;
+            }
         }
+
     }
 }

+ 43 - 46
bbiwarg/Recognition/HandRecognition/HandDetector.cs

@@ -11,48 +11,49 @@ using bbiwarg.Utility;
 using Emgu.CV;
 using Emgu.CV.Structure;
 
+
+
 namespace bbiwarg.Recognition.HandRecognition
 {
     class HandDetector
     {
+        private DepthImage depthImage;
         private EdgeImage edgeImage;
+        private Image<Gray, byte> modifiedHandDepthImage;
         private List<Finger> fingers;
+
         public List<Hand> Hands { get; private set; }
         public Image<Gray, byte> HandMask { get; private set; }
 
-        public HandDetector(EdgeImage edgeImage, List<Finger> fingers)
+        public HandDetector(DepthImage depthImage, EdgeImage edgeImage, List<Finger> fingers)
         {
+            this.depthImage = depthImage;
             this.edgeImage = edgeImage;
             this.fingers = fingers;
 
-            detectHands();
+            createModifiedHandEdgeImage();
+            findHands();
+            setZIndexes();
+            createHandMask();
+            findThumbDefects();
         }
 
-        private void detectHands()
+        private void createModifiedHandEdgeImage()
         {
-            int width = edgeImage.Width;
-            int height = edgeImage.Height;
-            int maxArea = width * height;
-            Image<Gray, byte> image = edgeImage.Image.Copy().Dilate(2).Erode(2).Mul(255);
-            HandMask = image.CopyBlank();
+            modifiedHandDepthImage = depthImage.Image.Copy();
 
-            //draw top finger slice
             foreach (Finger finger in fingers)
             {
-                Contour<Point> contour = finger.Contour;
-                image.FillConvexPoly(contour.ToArray(), new Gray(0));
-                image.DrawPolyline(finger.Contour.ToArray(), true, new Gray(255), 1);
-
-                FingerSlice slice = finger.SliceTrail.EndSlice;
-                Vector2D direction = slice.LineSegment.Line.Direction;
-                Vector2D start = slice.Start + (Constants.FingerContourMargin + Constants.FingerSliceOverlapFactor) * direction;
-                Vector2D end = slice.End - (Constants.FingerContourMargin + Constants.FingerSliceOverlapFactor) * direction;
-                image.Draw(new Emgu.CV.Structure.LineSegment2D(start, end), new Gray(0), 2);
-
-                //FingerSlice slice = finger.SliceTrail.Slices[1];
-                //image.Draw(new Emgu.CV.Structure.LineSegment2D(slice.Start, slice.End), new Gray(255), 2);
+                Point[] contour = finger.InnerContour.ToArray();
+                modifiedHandDepthImage.DrawPolyline(contour, false, new Gray(0), 2);
             }
+        }
 
+        private void findHands()
+        {
+            int width = edgeImage.Width;
+            int height = edgeImage.Height;
+            int maxArea = width * height;
             Hands = new List<Hand>();
 
             foreach (Finger finger in fingers)
@@ -73,42 +74,38 @@ namespace bbiwarg.Recognition.HandRecognition
                 {
                     Image<Gray, byte> mask = new Image<Gray, byte>(width + 2, height + 2);
                     MCvConnectedComp comp = new MCvConnectedComp();
-                    CvInvoke.cvFloodFill(image, finger.HandPoint, new MCvScalar(255), new MCvScalar(1), new MCvScalar(1), out comp, Emgu.CV.CvEnum.CONNECTIVITY.FOUR_CONNECTED, Emgu.CV.CvEnum.FLOODFILL_FLAG.DEFAULT, mask);
+                    CvInvoke.cvFloodFill(modifiedHandDepthImage, finger.HandPoint, new MCvScalar(255), new MCvScalar(2), new MCvScalar(2), out comp, Emgu.CV.CvEnum.CONNECTIVITY.EIGHT_CONNECTED, Emgu.CV.CvEnum.FLOODFILL_FLAG.DEFAULT, mask);
                     if (comp.area < maxArea * Constants.HandMaxSize)
                     {
-                        Image<Gray, byte> cropedMask = mask.Copy(new Rectangle(1, 1, width, height)).Dilate(1);
-                        Hand hand = new Hand(cropedMask);
+                        Image<Gray, byte> cropedMask = mask.Copy(new Rectangle(1, 1, width, height));
+                        Hand hand = new Hand(cropedMask, finger);
                         Hands.Add(hand);
-                        hand.addFinger(finger);
                         finger.setHand(hand);
-
-                        HandMask = HandMask.Or(cropedMask);
                     }
                 }
             }
+        }
 
-            float minX = float.MaxValue;
-            foreach (Hand hand in Hands)
-            {
-                if (hand.Centroid.X < minX)
-                    minX = hand.Centroid.X;
-            }
+        private void setZIndexes()
+        {
+            //sort depending on depth of centroid (far->near)
+            Hands.Sort((h1, h2) => depthImage.getDepthAt(h2.CentroidInHand).CompareTo(depthImage.getDepthAt(h1.CentroidInHand)));
+
+            for (int i = 0; i < Hands.Count; i++)
+                Hands[i].setZIndex(i);
+        }
 
+        private void createHandMask()
+        {
+            HandMask = new Image<Gray, byte>(depthImage.Width, depthImage.Height);
             foreach (Hand hand in Hands)
-            {
-                if (hand.Centroid.X == minX)
-                    hand.Side = Hand.HandSide.Left;
-                else
-                    hand.Side = Hand.HandSide.Right;
-            }
+                HandMask = HandMask.Or(hand.Mask);
+        }
 
-            if (Hands.Count == 1 && Hands[0].Fingers.Count == 1)
-            {
-                if (Hands[0].Centroid.X < Hands[0].Fingers[0].HandPoint.X)
-                    Hands[0].Side = Hand.HandSide.Right;
-                else
-                    Hands[0].Side = Hand.HandSide.Left;
-            }
+        private void findThumbDefects()
+        {
+            foreach (Hand hand in Hands)
+                hand.findThumbDefect();
         }
     }
 }

+ 25 - 0
bbiwarg/Recognition/HandRecognition/HandTracker.cs

@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using bbiwarg.Recognition.Tracking;
+
+namespace bbiwarg.Recognition.HandRecognition
+{
+    class HandTracker : Tracker<Hand, TrackedHand>
+    {
+        public List<Hand> Hands { get { return getCurrentObjectsWithState(TrackingState.Tracked); } }
+
+
+        public HandTracker()
+            : base(Constants.HandTrackerMinSimilarity)
+        {
+        }
+
+        protected override TrackedHand createTrackedObject(Hand detectedObject)
+        {
+            return new TrackedHand(idPool.getNextUnusedID(), detectedObject, Constants.HandTrackerNumFramesDetectedUntilTracked, Constants.HandTrackerNumFramesLostUntilDeleted);
+        }
+    }
+}

+ 52 - 0
bbiwarg/Recognition/HandRecognition/TrackedHand.cs

@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using bbiwarg.Recognition.Tracking;
+using bbiwarg.Utility;
+
+namespace bbiwarg.Recognition.HandRecognition
+{
+    class TrackedHand : TrackedObject<Hand>
+    {
+        private Kalman2DPositionFilter centroidKalman;
+
+        public Vector2D CentroidPrediction { get { return centroidKalman.getPrediction(); } }
+
+        public TrackedHand(int id, Hand detectedHand, int numFramesDetectedUntilTracked, int numFramesLostUntilDeleted)
+            : base(id, detectedHand, numFramesDetectedUntilTracked, numFramesLostUntilDeleted)
+        {
+            centroidKalman = new Kalman2DPositionFilter(Constants.HandmXX, Constants.HandmXY, Constants.HandmYY);
+            centroidKalman.setInitialPosition(detectedHand.Centroid);
+
+            logStateChange();
+        }
+
+        public override void updateFrame(Hand detectedHand)
+        {
+            base.updateFrame(detectedHand);
+
+            if (NumFramesInCurrentState == 1)
+                logStateChange();
+
+            if (detectedHand != null)
+                centroidKalman.getCorrectedPosition(detectedHand.Centroid);
+        }
+
+        public override float calculateSimilarity(Hand detectedHand)
+        {
+            //centroid position
+            float centroidDistance = CentroidPrediction.getDistanceTo(detectedHand.Centroid);
+            float centroidSimilarity = Math.Max(0, 1 - centroidDistance / Constants.ImageDiagonalLength);
+
+            return centroidSimilarity;
+        }
+
+        private void logStateChange()
+        {
+            String stateAsString = CurrentState.ToString().ToLower();
+            Logger.log(String.Format("Hand #{0} {1}", this.ID, stateAsString), LogSubject.HandTracker);
+        }
+    }
+}

+ 44 - 0
bbiwarg/Recognition/PalmRecognition/Palm.cs

@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using bbiwarg.Recognition.HandRecognition;
+using bbiwarg.Recognition.Tracking;
+using bbiwarg.Utility;
+
+namespace bbiwarg.Recognition.PalmRecognition
+{
+    class Palm : TrackableObject
+    {
+
+        public Hand Hand { get; private set; }
+        public Vector2D WristUpper { get; private set; }
+        public Vector2D WristLower { get; private set; }
+        public Vector2D FingersUpper { get; private set; }
+        public Vector2D FingersLower { get; private set; }
+        public Quadrangle Quad { get; private set; }
+
+        public Palm(Hand hand, Vector2D wristUpper, Vector2D wristLower, Vector2D fingersLower, Vector2D fingersUpper)
+        {
+            Hand = hand;
+            WristUpper = wristUpper;
+            WristLower = wristLower;
+            FingersUpper = fingersUpper;
+            FingersLower = fingersLower;
+
+            if (Hand.Side == HandSide.Left)
+                Quad = new Quadrangle(WristUpper, FingersUpper, FingersLower, WristLower);
+            else
+                Quad = new Quadrangle(FingersUpper, WristUpper, WristLower, FingersLower);
+        }
+
+        public Vector2D getRelativePosition(Vector2D absolutePosition) {
+            return Quad.getRelativePosition(absolutePosition);
+        }
+
+        public bool isInside(Vector2D position) {
+            return Quad.isInside(position);
+        }
+    }
+}

+ 26 - 261
bbiwarg/Recognition/PalmRecognition/PalmDetector.cs

@@ -3,294 +3,59 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
-using System.Drawing;
-
-using Emgu.CV;
-using Emgu.CV.Structure;
-
-using bbiwarg.Utility;
-using bbiwarg.Images;
-using bbiwarg.Recognition.FingerRecognition;
 using bbiwarg.Recognition.HandRecognition;
-using bbiwarg.Graphics;
+using bbiwarg.Utility;
+using Emgu.CV.Structure;
+using Emgu.CV;
 
 namespace bbiwarg.Recognition.PalmRecognition
 {
     class PalmDetector
     {
-        public Contour<Point> PalmContour { get; private set; }
-        public Vector2D ThumbDefectStart { get; private set; }
-        public Vector2D ThumbDefectEnd { get; private set; }
-        public Vector2D ThumbDefectDepth { get; private set; }
-        private Hand palmHand, pointingHand;
-        private List<MCvConvexityDefect> convexityDefects;
-        private Kalman2DPositionFilter thumbDefectDepthFilter, thumbDefectStartFilter, thumbDefectEndFilter;
-        private int numFramesNoHandFound;
-        private Quadrangle lastPalmQuad;
         private List<Hand> hands;
+        private List<Hand> palmHands;
 
-        public Quadrangle PalmQuad { get; private set; }
-        public Hand.HandSide PalmHandSide { get; private set; }
-
-        public PalmDetector()
-        {
-            thumbDefectDepthFilter = new Kalman2DPositionFilter(Constants.PalmThumbDefectmXX, Constants.PalmThumbDefectmXY,
-                                                                Constants.PalmThumbDefectmYY, Constants.PalmThumbDefectProcessNoise);
-            thumbDefectStartFilter = new Kalman2DPositionFilter(Constants.PalmThumbDefectmXX, Constants.PalmThumbDefectmXY,
-                                                                Constants.PalmThumbDefectmYY, Constants.PalmThumbDefectProcessNoise);
-            thumbDefectEndFilter = new Kalman2DPositionFilter(Constants.PalmThumbDefectmXX, Constants.PalmThumbDefectmXY,
-                                                                Constants.PalmThumbDefectmYY, Constants.PalmThumbDefectProcessNoise);
-        }
+        public List<Palm> Palms { get; private set; }
 
-        public void findPalmQuad(List<Hand> hands, Image<Gray, Byte> foregroundMask)
+        public PalmDetector(List<Hand> hands)
         {
             this.hands = hands;
 
-            if (hands.Count == 1 && hands[0].Fingers.Count == 1)
-            {
-                palmHand = hands[0];
-                pointingHand = null;
-            }
-            else if (hands.Count == 2)
-            {
-                if (hands[0].Fingers.Count > 1)
-                {
-                    pointingHand = hands[0];
-                    palmHand = hands[1];
-                }
-                else if (hands[1].Fingers.Count > 1)
-                {
-                    pointingHand = hands[1];
-                    palmHand = hands[0];
-                }
-                else if (hands[0].Fingers[0].LineSegment.Length > hands[1].Fingers[0].LineSegment.Length)
-                {
-                    pointingHand = hands[0];
-                    palmHand = hands[1];
-                }
-                else
-                {
-                    pointingHand = hands[1];
-                    palmHand = hands[0];
-                }
-            }
-
-            if ((hands.Count == 1 && hands[0].Fingers.Count == 1) || hands.Count == 2)
-            {
-                findLongestPalmContour();
-                findConvexityDefectsSortedByDepth();
-                if (pointingHand != null)
-                    removeConvexityDefectsCausedByFingers();
-                findHandPoints(foregroundMask);
-            }
-
-            if (hands.Count == 0)
-            {
-                ++numFramesNoHandFound;
-                if (numFramesNoHandFound == Constants.PalmNumFramesNoHandReset)
-                    reset();
-            }
-            else
-            {
-                numFramesNoHandFound = 0;
-            }
-        }
-
-        public void reset()
-        {
-            thumbDefectDepthFilter.reset();
-            thumbDefectStartFilter.reset();
-            thumbDefectEndFilter.reset();
-            PalmContour = null;
-            PalmQuad = null;
-            PalmHandSide = Hand.HandSide.Left;
-            numFramesNoHandFound = 0;
-            lastPalmQuad = null;
+            findPalmHands();
+            createPalms();
         }
 
-        private void findLongestPalmContour()
+        private void findPalmHands()
         {
-            Contour<Point> contour = palmHand.Mask.FindContours(Emgu.CV.CvEnum.CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE,
-                                                                Emgu.CV.CvEnum.RETR_TYPE.CV_RETR_EXTERNAL);
+            palmHands = new List<Hand>();
 
-            PalmContour = contour;
-            double maxPerimeter = 0;
-            while (contour != null)
+            foreach (Hand hand in hands)
             {
-                if (contour.Perimeter > maxPerimeter)
-                {
-                    maxPerimeter = contour.Perimeter;
-                    PalmContour = contour;
-                }
-                contour = contour.HNext;
+                if (hand.ThumbDefect != null)
+                    palmHands.Add(hand);
             }
         }
 
-        private void findConvexityDefectsSortedByDepth()
+        private void createPalms()
         {
-            convexityDefects = new List<MCvConvexityDefect>(PalmContour.GetConvexityDefacts(new MemStorage(), Emgu.CV.CvEnum.ORIENTATION.CV_CLOCKWISE));
-            convexityDefects.Sort(delegate(MCvConvexityDefect d1, MCvConvexityDefect d2)
+            Palms = new List<Palm>();
+            foreach (Hand palmHand in palmHands)
             {
-                if (d1.Depth < d2.Depth)
-                    return 1;
-                else if (d1.Depth > d2.Depth)
-                    return -1;
-                return 0;
-            });
-        }
+                ConvexityDefect thumbDefect = palmHand.ThumbDefect;
 
-        private void removeConvexityDefectsCausedByFingers()
-        {
-            List<MCvConvexityDefect> newDefects = new List<MCvConvexityDefect>();
-            foreach (MCvConvexityDefect d in convexityDefects)
-            {
-                bool fingerIntersectsStartEnd = false;
-                float minFingerLineDist = float.MaxValue;
-                foreach (Finger f in pointingHand.Fingers)
-                {
-                    Utility.LineSegment2D defectLine = new Utility.LineSegment2D(new Vector2D(d.StartPoint), new Vector2D(d.EndPoint));
-                    Vector2D intersection = defectLine.Line.getIntersection(f.LineSegment.Line);
-                    if (intersection != null &&
-                        intersection.isInBox(defectLine.P1, defectLine.P2) && intersection.isInBox(f.LineSegment.P1, f.LineSegment.P2))
-                    {
-                        fingerIntersectsStartEnd = true;
-                        break;
-                    }
-
-                    Vector2D mid = (new Vector2D(d.StartPoint) + new Vector2D(d.EndPoint)) / 2.0f;
-                    float dist = f.LineSegment.getDistanceTo(mid);
-                    if (dist < minFingerLineDist)
-                        minFingerLineDist = dist;
-                }
-
-                if (minFingerLineDist >= Constants.PalmMinDefectMidFingerLineDistance && !fingerIntersectsStartEnd)
-                    newDefects.Add(d);
-            }
-            convexityDefects = newDefects;
-        }
-
-        private MCvConvexityDefect? findThumbDefect()
-        {
-            foreach (MCvConvexityDefect d in convexityDefects)
-            {
-                Vector2D depth = new Vector2D(d.DepthPoint);
-                Vector2D start = new Vector2D(d.StartPoint);
-                Vector2D end = new Vector2D(d.EndPoint);
-
-                float angle = (float)((depth - start).getAngleBetween(depth - end) * 180 / Math.PI);
+                Vector2D handLength = 1.5f*thumbDefect.VectorLong;
+                Vector2D handWidth = 0.45f*handLength.getOrthogonal(palmHand.Side == HandSide.Right);
 
-                float l1 = (depth - start).Length;
-                float l2 = (depth - end).Length;
-                float startEndLengthQuotient = Math.Max(l1, l2) / Math.Min(l1, l2);
+                Vector2D fingersUpper = thumbDefect.Inner + 0.75f*handLength;
+                Vector2D wristUpper = thumbDefect.Inner - 0.25f*handLength;
+                Vector2D wristLower = wristUpper + handWidth;
+                Vector2D fingersLower = wristLower + 0.75f * handLength - 0.2f * handWidth;
 
-                float depthThumbLengthQuotient = d.Depth / palmHand.Fingers[0].LineSegment.Length;
+                Palm palm = new Palm(palmHand, wristUpper, wristLower, fingersLower, fingersUpper);
+                Palms.Add(palm);
+                palmHand.setPalm(palm);
 
-                if (angle <= Constants.PalmMaxThumbDefectAngle &&
-                    startEndLengthQuotient >= Constants.PalmMinThumbDefectStartEndLengthQuotient &&
-                    startEndLengthQuotient <= Constants.PalmMaxThumbDefectStartEndLengthQuotient &&
-                    depthThumbLengthQuotient >= Constants.PalmMinTumbDefectDepthThumbLengthQuotient &&
-                    depthThumbLengthQuotient <= Constants.PalmMaxTumbDefectDepthThumbLengthQuotient)
-                {
-                    return d;
-                }
             }
-
-            return null;
-        }
-
-        float getForegroundPixelPercentage(Quadrangle quad, Image<Gray, Byte> foregroundMask)
-        {
-            float numPixelsInMask = quad.Mask.CountNonzero()[0];
-            float numPixelsInMaskInForeground = (quad.Mask & foregroundMask).CountNonzero()[0];
-
-            return numPixelsInMaskInForeground / numPixelsInMask;
-        }
-
-        private void findHandPoints(Image<Gray, Byte> foregroundMask)
-        {
-            MCvConvexityDefect? thumbDefect = findThumbDefect();
-
-            if (thumbDefect != null)
-            {
-                ThumbDefectDepth = new Vector2D(thumbDefect.Value.DepthPoint);
-                ThumbDefectStart = new Vector2D(thumbDefect.Value.StartPoint);
-                ThumbDefectEnd = new Vector2D(thumbDefect.Value.EndPoint);
-
-                if (!thumbDefectDepthFilter.Initialized)
-                {
-                    thumbDefectDepthFilter.setInitialPosition(ThumbDefectDepth);
-                    thumbDefectStartFilter.setInitialPosition(ThumbDefectStart);
-                    thumbDefectEndFilter.setInitialPosition(ThumbDefectEnd);
-                }
-                else
-                {
-                    ThumbDefectDepth = thumbDefectDepthFilter.getCorrectedPosition(ThumbDefectDepth);
-                    ThumbDefectStart = thumbDefectStartFilter.getCorrectedPosition(ThumbDefectStart);
-                    ThumbDefectEnd = thumbDefectEndFilter.getCorrectedPosition(ThumbDefectEnd);
-                }
-
-                Vector2D handLength, handWidth, longestLineEndpoint, topLeft, bottomLeft, bottomRight, topRight;
-
-                Vector2D startDepth = ThumbDefectStart - ThumbDefectDepth;
-                Vector2D endDepth = ThumbDefectEnd - ThumbDefectDepth;
-
-                if (startDepth.Length > endDepth.Length)
-                {
-                    handLength = startDepth;
-                    longestLineEndpoint = ThumbDefectStart;
-                    if (hands.Count == 1)
-                    {
-                        if (ThumbDefectStart.X > ThumbDefectDepth.X)
-                            hands[0].Side = Hand.HandSide.Left;
-                        else
-                            hands[0].Side = Hand.HandSide.Right;
-                    }
-                }
-                else
-                {
-                    handLength = endDepth;
-                    longestLineEndpoint = ThumbDefectEnd;
-                    if (hands.Count == 1)
-                    {
-                        if (ThumbDefectEnd.X > ThumbDefectDepth.X)
-                            hands[0].Side = Hand.HandSide.Left;
-                        else
-                            hands[0].Side = Hand.HandSide.Right;
-                    }
-                }
-
-                Quadrangle quad;
-                if (palmHand.Side == Hand.HandSide.Left)
-                {
-                    handWidth = 0.85f * new Vector2D(-handLength.Y, handLength.X);
-                    topLeft = longestLineEndpoint + 0.15f * handLength;
-                    bottomLeft = ThumbDefectDepth - 0.4f * handLength;
-                    bottomRight = bottomLeft + handWidth;
-                    topRight = bottomRight + 1.2f * handLength - 0.3f * handWidth;
-                    quad = new Quadrangle(bottomLeft, topLeft, topRight, bottomRight, foregroundMask.Width, foregroundMask.Height);
-                }
-                else
-                {
-                    handWidth = 0.85f * new Vector2D(handLength.Y, -handLength.X);
-                    topRight = longestLineEndpoint + 0.15f * handLength;
-                    bottomRight = ThumbDefectDepth - 0.4f * handLength;
-                    bottomLeft = bottomRight + handWidth;
-                    topLeft = bottomLeft + 1.2f * handLength - 0.3f * handWidth;
-                    quad = new Quadrangle(topRight, bottomRight, bottomLeft, topLeft, foregroundMask.Width, foregroundMask.Height);
-                }
-
-                if ((lastPalmQuad == null ||
-                    (quad.Area / lastPalmQuad.Area >= Constants.PalmMinAreaQuotient && quad.Area / lastPalmQuad.Area <= Constants.PalmMaxAreaQuotient)) &&
-                    getForegroundPixelPercentage(quad, foregroundMask) >= Constants.PalmMinPrecentageQuadForeground)
-                {
-                    PalmQuad = quad;
-                    PalmHandSide = palmHand.Side;
-                    lastPalmQuad = PalmQuad;
-                }
-            }
-
-            if (lastPalmQuad != null && getForegroundPixelPercentage(lastPalmQuad, foregroundMask) <= Constants.PalmMaxPrecentageQuadForegroundReset)
-                reset();
         }
     }
 }

+ 33 - 0
bbiwarg/Recognition/PalmRecognition/PalmTracker.cs

@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using bbiwarg.Recognition.Tracking;
+
+namespace bbiwarg.Recognition.PalmRecognition
+{
+    class PalmTracker : Tracker<Palm, TrackedPalm>
+    {
+        public List<Palm> Palms { get { return getCurrentObjectsWithState(TrackingState.Tracked); } }
+        public List<Palm> OptimizedPalms { get { return getOptimizedPalms(); } }
+
+        public PalmTracker()
+            : base(Constants.PalmTrackerMinSimilarity)
+        {
+        }
+
+        protected override TrackedPalm createTrackedObject(Palm detectedPalm)
+        {
+            return new TrackedPalm(idPool.getNextUnusedID(), detectedPalm, Constants.PalmTrackerNumFramesDetectedUntilTracked, Constants.PalmTrackerNumFramesLostUntilDeleted);
+        }
+
+        private List<Palm> getOptimizedPalms() {
+            List<Palm> optimizedPalms = new List<Palm>(); 
+            foreach(TrackedPalm tp in TrackedObjects) {
+                optimizedPalms.Add(tp.OptimizedPalm);
+            }
+            return optimizedPalms;
+        }
+    }
+}

+ 96 - 0
bbiwarg/Recognition/PalmRecognition/TrackedPalm.cs

@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using bbiwarg.Recognition.Tracking;
+using bbiwarg.Recognition.HandRecognition;
+using bbiwarg.Utility;
+
+namespace bbiwarg.Recognition.PalmRecognition
+{
+    class TrackedPalm : TrackedObject<Palm>
+    {
+        private Kalman2DPositionFilter wristUpperKalman;
+        private Kalman2DPositionFilter wristLowerKalman;
+        private Kalman2DPositionFilter fingersUpperKalman;
+        private Kalman2DPositionFilter fingersLowerKalman;
+
+        public Vector2D WristUpperPrediction { get { return wristUpperKalman.getPrediction(); } }
+        public Vector2D WristLowerPrediction { get { return wristLowerKalman.getPrediction(); } }
+        public Vector2D FingersUpperPrediction { get { return fingersUpperKalman.getPrediction(); } }
+        public Vector2D FingersLowerPrediction { get { return fingersLowerKalman.getPrediction(); } }
+        public Palm OptimizedPalm { get; private set; }
+
+        public TrackedPalm(int id, Palm detectedPalm, int numFramesDetectedUntilTracked, int numFramesLostUntilDeleted)
+            : base(id, detectedPalm, numFramesDetectedUntilTracked, numFramesLostUntilDeleted)
+        {
+            wristUpperKalman = new Kalman2DPositionFilter(Constants.PalmmXX, Constants.PalmmXY, Constants.PalmmYY);
+            wristLowerKalman = new Kalman2DPositionFilter(Constants.PalmmXX, Constants.PalmmXY, Constants.PalmmYY);
+            fingersUpperKalman = new Kalman2DPositionFilter(Constants.PalmmXX, Constants.PalmmXY, Constants.PalmmYY);
+            fingersLowerKalman = new Kalman2DPositionFilter(Constants.PalmmXX, Constants.PalmmXY, Constants.PalmmYY);
+            wristUpperKalman.setInitialPosition(detectedPalm.WristUpper);
+            wristLowerKalman.setInitialPosition(detectedPalm.WristLower);
+            fingersUpperKalman.setInitialPosition(detectedPalm.FingersUpper);
+            fingersLowerKalman.setInitialPosition(detectedPalm.FingersLower);
+
+            updateOptimizedPalm();
+            logStateChange();
+        }
+
+        public override void updateFrame(Palm detectedPalm)
+        {
+            base.updateFrame(detectedPalm);
+
+            if (NumFramesInCurrentState == 1)
+                logStateChange();
+
+            if (detectedPalm != null)
+            {
+                wristUpperKalman.getCorrectedPosition(detectedPalm.WristUpper);
+                wristLowerKalman.getCorrectedPosition(detectedPalm.WristLower);
+                fingersUpperKalman.getCorrectedPosition(detectedPalm.FingersUpper);
+                fingersLowerKalman.getCorrectedPosition(detectedPalm.FingersLower);
+
+                updateOptimizedPalm();
+            }
+        }
+
+        private void updateOptimizedPalm() {
+            OptimizedPalm = new Palm(LastObject.Hand, WristUpperPrediction, WristLowerPrediction, FingersLowerPrediction, FingersUpperPrediction);
+        }
+
+        public override float calculateSimilarity(Palm detectedPalm)
+        {
+            //hand
+            float handSimilarity = (detectedPalm.Hand.TrackID == LastObject.Hand.TrackID) ? 1 : 0;
+
+            //wristUpper
+            float wristUpperDistance = WristUpperPrediction.getDistanceTo(detectedPalm.WristUpper);
+            float wristUpperSimilarity = Math.Max(0, 1 - wristUpperDistance / Constants.ImageDiagonalLength);
+
+            //wristLower
+            float wristLowerDistance = WristLowerPrediction.getDistanceTo(detectedPalm.WristLower);
+            float wristLowerSimilarity = Math.Max(0, 1 - wristLowerDistance / Constants.ImageDiagonalLength);
+
+            //fingersUpper
+            float fingersUpperDistance = FingersUpperPrediction.getDistanceTo(detectedPalm.FingersUpper);
+            float fingersUpperSimilarity = Math.Max(0, 1 - fingersUpperDistance / Constants.ImageDiagonalLength);
+
+            //fingersLower
+            float fingersLowerDistance = FingersLowerPrediction.getDistanceTo(detectedPalm.FingersLower);
+            float fingersLowerSimilarity = Math.Max(0, 1 - fingersLowerDistance / Constants.ImageDiagonalLength);
+
+            float similarity = handSimilarity * wristUpperSimilarity * wristLowerSimilarity * fingersUpperSimilarity * fingersLowerSimilarity;
+
+            return similarity;
+        }
+
+        private void logStateChange()
+        {
+            String stateAsString = CurrentState.ToString().ToLower();
+            Logger.log(String.Format("Palm #{0} {1}", this.ID, stateAsString), LogSubject.PalmTracker);
+        }
+
+    }
+}

+ 18 - 14
bbiwarg/Recognition/TouchRecognition/TouchDetector.cs

@@ -18,18 +18,18 @@ namespace bbiwarg.Recognition.TouchRecognition
     {
         private DepthImage depthImage;
         private List<Finger> fingers;
-        private Quadrangle palmQuad;
+        private List<Palm> palms;
 
         public List<TouchEvent> TouchEvents { get; private set; }
 
-        public TouchDetector(DepthImage depthImage, List<Finger> fingers, Quadrangle palmQuad)
+        public TouchDetector(DepthImage depthImage, List<Finger> fingers, List<Palm> palms)
         {
             this.depthImage = depthImage;
             this.fingers = fingers;
-            this.palmQuad = palmQuad;
+            this.palms = palms;
             TouchEvents = new List<TouchEvent>();
 
-            if (palmQuad != null)
+            if (palms.Count > 0)
                 detectTouch();
         }
 
@@ -42,19 +42,23 @@ namespace bbiwarg.Recognition.TouchRecognition
                 Vector2D tipPointInside = (tipPoint + Constants.TouchEventTipInsideFactor * directionInv).moveInBound(Vector2D.Zero, depthImage.BottomRight, direction);
                 Vector2D tipPointOutside = (tipPoint + Constants.TouchEventTipOutsideFactor * direction).moveInBound(Vector2D.Zero, depthImage.BottomRight, directionInv);
 
-                if(palmQuad.isInside(tipPointOutside)) {
+                foreach (Palm palm in palms)
+                {
+                    if (palm.isInside(tipPointOutside))
+                    {
 
-                    Image<Gray, byte> touchMask = getTouchMask(tipPointInside);
+                        Image<Gray, byte> touchMask = getTouchMask(tipPointInside);
 
-                    int touchPixels = touchMask.CountNonzero()[0];
-                    int numPixels = touchMask.Width * touchMask.Height;
-                    float touchValue = touchPixels / (float)numPixels;
+                        int touchPixels = touchMask.CountNonzero()[0];
+                        int numPixels = touchMask.Width * touchMask.Height;
+                        float touchValue = touchPixels / (float)numPixels;
 
-                    if (touchValue > Constants.TouchEventMinTouchValue)
-                    {
-                        TouchEvent touchEvent = new TouchEvent(tipPointOutside, touchMask, finger, palmQuad);
-                        TouchEvents.Add(touchEvent);
-                        finger.setTouchEvent(touchEvent);
+                        if (touchValue > Constants.TouchEventMinTouchValue)
+                        {
+                            TouchEvent touchEvent = new TouchEvent(tipPointOutside, touchMask, finger, palm);
+                            TouchEvents.Add(touchEvent);
+                            finger.setTouchEvent(touchEvent);
+                        }
                     }
                 }
             }

+ 5 - 4
bbiwarg/Recognition/TouchRecognition/TouchEvent.cs

@@ -4,6 +4,7 @@ using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 using bbiwarg.Recognition.FingerRecognition;
+using bbiwarg.Recognition.PalmRecognition;
 using bbiwarg.Recognition.Tracking;
 using bbiwarg.Utility;
 using Emgu.CV;
@@ -17,16 +18,16 @@ namespace bbiwarg.Recognition.TouchRecognition
         public Vector2D RelativePalmPosition { get; private set; }
         public Image<Gray, byte> TouchMask { get; private set; }
         public Finger Finger { get; private set; }
-        public Quadrangle PalmQuad { get; private set; }
+        public Palm Palm { get; private set; }
 
 
-        public TouchEvent(Vector2D absolutePosition, Image<Gray, byte> touchMask, Finger finger, Quadrangle palmQuad)
+        public TouchEvent(Vector2D absolutePosition, Image<Gray, byte> touchMask, Finger finger, Palm palm)
         {
             Position = absolutePosition;
-            RelativePalmPosition = palmQuad.getRelativePosition(absolutePosition);
+            RelativePalmPosition = palm.getRelativePosition(absolutePosition);
             TouchMask = touchMask;
             Finger = finger;
-            PalmQuad = palmQuad;
+            Palm = palm;
         }
 
         public float getTouchValue()

+ 54 - 0
bbiwarg/Utility/ConvexityDefect.cs

@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using bbiwarg.Recognition.FingerRecognition;
+using Emgu.CV.Structure;
+
+namespace bbiwarg.Utility
+{
+    class ConvexityDefect
+    {
+        public Vector2D OuterShort { get; private set; }
+        public Vector2D OuterLong { get; private set; }
+        public Vector2D Inner { get; private set; }
+        public Vector2D VectorShort { get; private set; }
+        public Vector2D VectorLong { get; private set; }
+        public LineSegment2D OuterLineSegment { get; private set; }
+        public float Depth { get; private set; }
+
+        public ConvexityDefect(MCvConvexityDefect mcvCOnvexityDefect) {
+            Inner = new Vector2D(mcvCOnvexityDefect.DepthPoint);
+            Vector2D p1 = new Vector2D(mcvCOnvexityDefect.StartPoint);
+            Vector2D p2 = new Vector2D(mcvCOnvexityDefect.EndPoint);
+
+            if ((p1 - Inner).Length > (p2 - Inner).Length)
+            {
+                OuterLong = p1;
+                OuterShort = p2;
+            }
+            else {
+                OuterLong = p2;
+                OuterShort = p1;
+            }
+
+            VectorLong = OuterLong - Inner;
+            VectorShort = OuterShort - Inner;
+            OuterLineSegment = new LineSegment2D(OuterLong, OuterShort);
+            Depth = OuterLineSegment.getDistanceTo(Inner);
+        }
+
+        public bool isPossibleThumbDefect(Finger thumb) {
+            float tipDistance = thumb.TipPoint.getDistanceTo(OuterShort);
+            float handDistance = thumb.HandPoint.getDistanceTo(Inner);
+            float thumbShortLengthRatio = thumb.LineSegment.Length / VectorShort.Length;
+            float shortLongLengthRatio = VectorShort.Length / VectorLong.Length;
+
+            return (tipDistance <= Constants.HandThumbDefectMaxDistanceToThumb &&
+                    handDistance <= Constants.HandThumbDefectMaxDistanceToThumb &&
+                    thumbShortLengthRatio <= Constants.HandThumbDefectMaxThumbShortLengthRatio && thumbShortLengthRatio >= Constants.HandThumbDefectMinThumbShortLengthRatio &&
+                    shortLongLengthRatio <= Constants.HandThumbDefectMaxShortLongLengthRatio && shortLongLengthRatio >= Constants.HandThumbDefectMinShortLongLengthRatio);
+        }
+    }
+}

+ 5 - 0
bbiwarg/Utility/LineSegment2D.cs

@@ -25,6 +25,11 @@ namespace bbiwarg.Utility
             Length = P1.getDistanceTo(P2);
         }
 
+        public bool intersectsWith(LineSegment2D ls) {
+            Vector2D intersection = Line.getIntersection(ls.Line);
+            return (intersection != null && intersection.isInBox(P1, P2) && intersection.isInBox(ls.P1, ls.P2));
+        }
+
         public float getParallelDistanceTo(LineSegment2D line)
         {
             if (Line.onSide(line.P1) != Line.onSide(line.P2)) return 0;

+ 3 - 1
bbiwarg/Utility/Logger.cs

@@ -18,7 +18,9 @@ namespace bbiwarg.Utility
         TUIOServer = 64,
         Timer = 128,
         TouchEvents = 256,
-        VideoControls = 512
+        VideoControls = 512,
+        HandTracker = 1024,
+        PalmTracker = 2048
     }
     static class Logger
     {

+ 8 - 26
bbiwarg/Utility/Quadrangle.cs

@@ -15,36 +15,18 @@ namespace bbiwarg.Utility
 {
     class Quadrangle
     {
-        public Vector2D[] Vertices { get { return new Vector2D[4] { BottomLeft, TopLeft, TopRight, BottomRight }; } }
-        public Vector2D BottomLeft { get; private set; }
+        public Vector2D[] Vertices { get { return new Vector2D[4] { TopLeft, TopRight, BottomRight, BottomLeft }; } }
         public Vector2D TopLeft { get; private set; }
         public Vector2D TopRight { get; private set; }
         public Vector2D BottomRight { get; private set; }
-        public float Area { get; private set; }
-        public Image<Gray, Byte> Mask { get; private set; }
+        public Vector2D BottomLeft { get; private set; }
 
-        public Quadrangle(Vector2D bottomLeft, Vector2D topLeft, Vector2D topRight, Vector2D bottomRight, int width, int height)
+        public Quadrangle(Vector2D topLeft, Vector2D topRight, Vector2D bottomRight, Vector2D bottomLeft)
         {
-            BottomLeft = bottomLeft;
             TopLeft = topLeft;
             TopRight = topRight;
             BottomRight = bottomRight;
-
-            Contour<PointF> contourPoints = new Contour<PointF>(new MemStorage());
-            contourPoints.Push(BottomLeft);
-            contourPoints.Push(TopLeft);
-            contourPoints.Push(TopRight);
-            contourPoints.Push(BottomRight);
-            Area = (float)contourPoints.Area;
-
-            Mask = new Image<Gray, byte>(width, height);
-
-            Point[] polyPoints = new Point[4];
-            polyPoints[0] = BottomLeft;
-            polyPoints[1] = TopLeft;
-            polyPoints[2] = TopRight;
-            polyPoints[3] = BottomRight;
-            Mask.FillConvexPoly(polyPoints, new Gray(255));
+            BottomLeft = bottomLeft;
         }
 
 
@@ -52,10 +34,10 @@ namespace bbiwarg.Utility
         {
             Vector2D a, b, c, d;
 
-            a = BottomLeft;
-            b = TopLeft;
-            c = TopRight;
-            d = BottomRight;
+            a = TopLeft;
+            b = TopRight;
+            c = BottomRight;
+            d = BottomLeft;
 
             float C = (a.Y - p.Y) * (d.X - p.X) - (a.X - p.X) * (d.Y - p.Y);
             float B = (a.Y - p.Y) * (c.X - d.X) + (b.Y - a.Y) * (d.X - p.X) - (a.X - p.X) * (c.Y - d.Y) - (b.X - a.X) * (d.Y - p.Y);

+ 4 - 0
bbiwarg/Utility/Vector2D.cs

@@ -113,6 +113,10 @@ namespace bbiwarg.Utility
             return new Vector2D(Math.Abs(X), Math.Abs(Y));
         }
 
+        public Vector2D copy() {
+            return new Vector2D(X, Y);
+        }
+
         public override string ToString()
         {
             return "(" + X + "|" + Y + ")";

+ 6 - 0
bbiwarg/bbiwarg.csproj

@@ -80,7 +80,12 @@
     <Compile Include="Recognition\FingerRecognition\TrackedFinger.cs" />
     <Compile Include="Recognition\HandRecognition\Hand.cs" />
     <Compile Include="Recognition\HandRecognition\HandDetector.cs" />
+    <Compile Include="Recognition\HandRecognition\HandTracker.cs" />
+    <Compile Include="Recognition\HandRecognition\TrackedHand.cs" />
+    <Compile Include="Recognition\PalmRecognition\Palm.cs" />
     <Compile Include="Recognition\PalmRecognition\PalmDetector.cs" />
+    <Compile Include="Recognition\PalmRecognition\PalmTracker.cs" />
+    <Compile Include="Recognition\PalmRecognition\TrackedPalm.cs" />
     <Compile Include="Recognition\TouchRecognition\TouchDetector.cs" />
     <Compile Include="Recognition\TouchRecognition\TouchEvent.cs" />
     <Compile Include="Recognition\TouchRecognition\TouchTracker.cs" />
@@ -107,6 +112,7 @@
     <Compile Include="Server\TUIO\TuioPoint.cs" />
     <Compile Include="Server\TUIO\TuioServer.cs" />
     <Compile Include="Server\TUIO\TuioTime.cs" />
+    <Compile Include="Utility\ConvexityDefect.cs" />
     <Compile Include="Utility\Kalman2DPositionFilter.cs" />
     <Compile Include="Utility\Line2D.cs" />
     <Compile Include="Utility\LineSegment2D.cs" />