|
@@ -1,6 +1,7 @@
|
|
using System;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics;
|
|
|
|
+using System.Drawing;
|
|
using System.Linq;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Threading.Tasks;
|
|
@@ -16,211 +17,259 @@ namespace bbiwarg.Detectors.Fingers
|
|
private DepthImage depthImage;
|
|
private DepthImage depthImage;
|
|
private EdgeImage edgeImage;
|
|
private EdgeImage edgeImage;
|
|
private FingerImage fingerImage;
|
|
private FingerImage fingerImage;
|
|
- private List<FingerSliceTrail> horizontalFingerSliceTrails;
|
|
|
|
- private List<FingerSliceTrail> verticalFingerSliceTrails;
|
|
|
|
public List<Finger> Fingers { get; private set; }
|
|
public List<Finger> Fingers { get; private set; }
|
|
|
|
|
|
-
|
|
|
|
public FingerDetector(DepthImage depthImage, EdgeImage edgeImage, FingerImage fingerImage)
|
|
public FingerDetector(DepthImage depthImage, EdgeImage edgeImage, FingerImage fingerImage)
|
|
{
|
|
{
|
|
this.depthImage = depthImage;
|
|
this.depthImage = depthImage;
|
|
- this.edgeImage = edgeImage;
|
|
|
|
|
|
+ this.edgeImage = edgeImage.copy();
|
|
this.fingerImage = fingerImage;
|
|
this.fingerImage = fingerImage;
|
|
|
|
|
|
- findHorizontalFingerSliceTrails();
|
|
|
|
- findVerticalFingerSliceTrails();
|
|
|
|
- transformFingerSliceTrailsToFingers();
|
|
|
|
|
|
+ findFingers();
|
|
}
|
|
}
|
|
|
|
|
|
- private void findHorizontalFingerSliceTrails()
|
|
|
|
|
|
+ private void findFingers()
|
|
{
|
|
{
|
|
-
|
|
|
|
- horizontalFingerSliceTrails = new List<FingerSliceTrail>();
|
|
|
|
-
|
|
|
|
|
|
+ int minNumSlices = 20;
|
|
int width = depthImage.Width;
|
|
int width = depthImage.Width;
|
|
int height = depthImage.Height;
|
|
int height = depthImage.Height;
|
|
|
|
+ int maxX = width - 1;
|
|
|
|
+ int maxY = height - 1;
|
|
|
|
|
|
- int minFingerSize = 10;
|
|
|
|
- int maxFingerSize = 30;
|
|
|
|
|
|
+ Fingers = new List<Finger>();
|
|
|
|
|
|
- //search horizontal finger-slices
|
|
|
|
- for (int y = 0; y < height; y++)
|
|
|
|
|
|
+ for (int y = 1; y < maxY; y++)
|
|
{
|
|
{
|
|
- int x = 0;
|
|
|
|
- while (x <= width - minFingerSize)
|
|
|
|
|
|
+ for (int x = 1; x < maxX; x++)
|
|
{
|
|
{
|
|
- if (edgeImage.isEdgeAt(x, y) && depthImage.getDepthAt(x, y) > depthImage.getDepthAt(x + 1, y))
|
|
|
|
|
|
+ if (edgeImage.isEdgeAt(x, y))
|
|
{
|
|
{
|
|
- int sliceX1 = x;
|
|
|
|
- int maxSliceX2 = Math.Min(sliceX1 + maxFingerSize, width - 1);
|
|
|
|
- x++;
|
|
|
|
- while (x <= maxSliceX2)
|
|
|
|
|
|
+ Vector2D edgePoint = new Vector2D(x, y);
|
|
|
|
+ Vector2D edgeDirection = getEdgeDirection(edgePoint);
|
|
|
|
+ if (edgeDirection != null)
|
|
{
|
|
{
|
|
- if (edgeImage.isEdgeAt(x, y))
|
|
|
|
|
|
+ Vector2D dir = edgeDirection.getOrthogonal(true);
|
|
|
|
+ if (depthImage.getDepthAt(edgePoint - dir) < depthImage.getDepthAt(edgePoint + dir))
|
|
|
|
+ dir = dir.getInverse();
|
|
|
|
+
|
|
|
|
+ FingerSlice slice = findFingerSliceFromStartEdge(edgePoint, dir);
|
|
|
|
+ if (slice != null)
|
|
{
|
|
{
|
|
- int sliceX2 = x;
|
|
|
|
- Vector2D sliceStart = new Vector2D(Math.Max(sliceX1 - 1, 0), y);
|
|
|
|
- Vector2D sliceEnd = new Vector2D(Math.Min(sliceX2 + 1, width - 1), y);
|
|
|
|
- FingerSlice fingerSlice = new FingerSlice(sliceStart, sliceEnd);
|
|
|
|
- if (fingerSlice.Size > minFingerSize && fingerSliceDepthTest(fingerSlice))
|
|
|
|
- {
|
|
|
|
- addHorizontalFingerSlice(fingerSlice);
|
|
|
|
- fingerImage.drawLine(fingerSlice.LineSegment, FingerImageState.possibleFingerSlice);
|
|
|
|
- }
|
|
|
|
- break;
|
|
|
|
|
|
+ FingerSliceTrail trail = findFingerSliceTrail(slice, edgeDirection);
|
|
|
|
+ if (trail != null && trail.NumSlices > minNumSlices)
|
|
|
|
+ createFingerFromTrail(trail);
|
|
}
|
|
}
|
|
- else x++;
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- else
|
|
|
|
- x++;
|
|
|
|
-
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- private void findVerticalFingerSliceTrails()
|
|
|
|
|
|
+ private Vector2D getEdgeDirection(Vector2D edgePoint)
|
|
{
|
|
{
|
|
- verticalFingerSliceTrails = new List<FingerSliceTrail>();
|
|
|
|
|
|
+ int x = edgePoint.IntX;
|
|
|
|
+ int y = edgePoint.IntY;
|
|
|
|
|
|
- int width = depthImage.Width;
|
|
|
|
- int height = depthImage.Height;
|
|
|
|
|
|
+ if (edgeImage.isEdgeAt(x, y - 1) && edgeImage.isEdgeAt(x, y + 1)) return new Vector2D(0, 1);
|
|
|
|
+ else if (edgeImage.isEdgeAt(x - 1, y) && edgeImage.isEdgeAt(x + 1, y)) return new Vector2D(1, 0);
|
|
|
|
+ else if (edgeImage.isEdgeAt(x - 1, y - 1) && edgeImage.isEdgeAt(x + 1, y + 1)) return new Vector2D(1, 1).normalize();
|
|
|
|
+ else if (edgeImage.isEdgeAt(x + 1, y - 1) && edgeImage.isEdgeAt(x - 1, y + 1)) return new Vector2D(1, -1).normalize();
|
|
|
|
+ else return null;
|
|
|
|
+ }
|
|
|
|
|
|
- int minFingerSize = 10;
|
|
|
|
- int maxFingerSize = 30;
|
|
|
|
|
|
+ private FingerSliceTrail findFingerSliceTrail(FingerSlice startSlice, Vector2D startDirection)
|
|
|
|
+ {
|
|
|
|
+ int minNumSlicesForCorrection = 15;
|
|
|
|
+ int numRemoveForCorrection = 3;
|
|
|
|
+
|
|
|
|
+ int maxX = depthImage.Width - 1;
|
|
|
|
+ int maxY = depthImage.Height - 1;
|
|
|
|
+
|
|
|
|
+ FingerSliceTrail trail = new FingerSliceTrail(startSlice);
|
|
|
|
+
|
|
|
|
+ Vector2D position = startSlice.Mid + startDirection;
|
|
|
|
+ Vector2D direction = startDirection;
|
|
|
|
|
|
- //search vertical finger-slices
|
|
|
|
- for (int x = 0; x < width; x++)
|
|
|
|
|
|
+ if (position.isWithin(0, 0, maxX, maxY))
|
|
{
|
|
{
|
|
- int y = 0;
|
|
|
|
- while (y <= height - minFingerSize)
|
|
|
|
|
|
+ FingerSlice nextSlice = findFingerSliceFromMid(position, direction);
|
|
|
|
+ if (nextSlice != null)
|
|
{
|
|
{
|
|
- if (edgeImage.isEdgeAt(x, y) && depthImage.getDepthAt(x, y) > depthImage.getDepthAt(x, y + 1))
|
|
|
|
|
|
+ trail.addSlice(nextSlice);
|
|
|
|
+ trail = expandTrail(trail);
|
|
|
|
+
|
|
|
|
+ if (trail.NumSlices > minNumSlicesForCorrection)
|
|
{
|
|
{
|
|
- int sliceY1 = y;
|
|
|
|
- int maxSliceY2 = Math.Min(sliceY1 + maxFingerSize, height - 1);
|
|
|
|
- y++;
|
|
|
|
- while (y <= maxSliceY2)
|
|
|
|
- {
|
|
|
|
- if (edgeImage.isEdgeAt(x, y))
|
|
|
|
- {
|
|
|
|
- int sliceY2 = y;
|
|
|
|
- Vector2D sliceStart = new Vector2D(x, Math.Max(sliceY1 - 1, 0));
|
|
|
|
- Vector2D sliceEnd = new Vector2D(x, Math.Min(sliceY2 + 1, height - 1));
|
|
|
|
- FingerSlice fingerSlice = new FingerSlice(sliceStart, sliceEnd);
|
|
|
|
- if (fingerSlice.Size > minFingerSize && fingerSliceDepthTest(fingerSlice))
|
|
|
|
- {
|
|
|
|
- addVerticalFingerSlice(fingerSlice);
|
|
|
|
- fingerImage.drawLine(fingerSlice.LineSegment, FingerImageState.possibleFingerSlice);
|
|
|
|
- }
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- else y++;
|
|
|
|
- }
|
|
|
|
|
|
+ trail.Slices.Reverse();
|
|
|
|
+ trail.Slices.RemoveRange(0, numRemoveForCorrection);
|
|
|
|
+ trail = expandTrail(trail);
|
|
|
|
+ trail.Slices.Reverse();
|
|
|
|
+ return trail;
|
|
}
|
|
}
|
|
- else
|
|
|
|
- y++;
|
|
|
|
-
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ return null;
|
|
|
|
+
|
|
}
|
|
}
|
|
|
|
|
|
- private void transformFingerSliceTrailsToFingers()
|
|
|
|
|
|
+ private FingerSliceTrail expandTrail(FingerSliceTrail trail)
|
|
{
|
|
{
|
|
- int minNumSlices = 15;
|
|
|
|
|
|
+ int maxX = depthImage.Width - 1;
|
|
|
|
+ int maxY = depthImage.Height - 1;
|
|
|
|
|
|
- Fingers = new List<Finger>();
|
|
|
|
- List<FingerSliceTrail> fingerSliceTrails = new List<FingerSliceTrail>();
|
|
|
|
- fingerSliceTrails.AddRange(horizontalFingerSliceTrails);
|
|
|
|
- fingerSliceTrails.AddRange(verticalFingerSliceTrails);
|
|
|
|
|
|
+ int numDirectionsForAverage = 10;
|
|
|
|
+
|
|
|
|
+ List<Vector2D> directions = new List<Vector2D>();
|
|
|
|
+ int numDirections = Math.Min(trail.NumSlices - 1, numDirectionsForAverage);
|
|
|
|
+ for (int i = 0; i < numDirections; i++) {
|
|
|
|
+ directions.Add(trail.Slices[i + 1].Mid - trail.Slices[i].Mid);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Vector2D currentDirection = Vector2D.mean(directions).normalize();
|
|
|
|
+ Vector2D currentPosition = trail.End.Mid + currentDirection;
|
|
|
|
+
|
|
|
|
+ int gapCounter = 0;
|
|
|
|
+ int numSlices = trail.NumSlices;
|
|
|
|
|
|
- foreach (FingerSliceTrail trail in fingerSliceTrails)
|
|
|
|
|
|
+ FingerSlice lastSlice = trail.End;
|
|
|
|
+ FingerSlice nextSlice;
|
|
|
|
+
|
|
|
|
+ while (currentPosition.isWithin(0, 0, maxX, maxY) && gapCounter <= Math.Min(numSlices / 2, 10))
|
|
{
|
|
{
|
|
- if (trail.NumSlices >= minNumSlices)
|
|
|
|
|
|
+ nextSlice = findFingerSliceFromMid(currentPosition, currentDirection);
|
|
|
|
+ if (nextSlice != null && (nextSlice.Length < lastSlice.Length + 5 && nextSlice.Length > lastSlice.Length - 5))
|
|
{
|
|
{
|
|
- //reorder finger slice so that it goes tip->hand
|
|
|
|
- /*Int16 depthAtTip = depthImage.getDepthAt((int)trail.Start.Mid.X, (int)trail.Start.Mid.Y);
|
|
|
|
- Int16 depthAtHand = depthImage.getDepthAt((int)trail.End.Mid.X, (int)trail.End.Mid.Y);
|
|
|
|
- if (depthAtTip < depthAtHand)
|
|
|
|
- trail.reverse();*/
|
|
|
|
-
|
|
|
|
- Finger finger = new Finger(trail);
|
|
|
|
-
|
|
|
|
- //filter double hits (horizontal+vertical finger)
|
|
|
|
- bool addFinger = true;
|
|
|
|
- for (int i = Fingers.Count - 1; i >= 0; i--)
|
|
|
|
- {
|
|
|
|
- Finger f = Fingers[i];
|
|
|
|
- Utility.LineSegment2D lineA = finger.LineSegment;
|
|
|
|
- Utility.LineSegment2D lineB = f.LineSegment;
|
|
|
|
|
|
+ gapCounter = 0;
|
|
|
|
+ numSlices++;
|
|
|
|
+ trail.addSlice(nextSlice);
|
|
|
|
+ directions.Add((nextSlice.Mid - lastSlice.Mid));
|
|
|
|
|
|
- if(lineA.getVerticalDistanceTo(lineB) < 5 && lineA.getParallelDistanceTo(lineB) < 5)
|
|
|
|
- {
|
|
|
|
- if (finger.SliceTrail.NumSlices > f.SliceTrail.NumSlices)
|
|
|
|
- Fingers.RemoveAt(i);
|
|
|
|
- else
|
|
|
|
- addFinger = false;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ if (directions.Count == numDirectionsForAverage)
|
|
|
|
+ directions.RemoveAt(0);
|
|
|
|
|
|
- if (addFinger)
|
|
|
|
- {
|
|
|
|
- Fingers.Add(finger);
|
|
|
|
- fingerImage.drawFinger(finger, FingerImageState.fingerDetected);
|
|
|
|
- }
|
|
|
|
|
|
+ currentDirection = Vector2D.mean(directions).normalize();
|
|
|
|
+ currentPosition = nextSlice.Mid + currentDirection;
|
|
|
|
|
|
|
|
+ lastSlice = nextSlice;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ gapCounter++;
|
|
|
|
+ currentPosition += currentDirection;
|
|
}
|
|
}
|
|
-
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ return trail;
|
|
}
|
|
}
|
|
|
|
|
|
- private bool fingerSliceDepthTest(FingerSlice fingerSlice)
|
|
|
|
|
|
+ private FingerSlice findFingerSliceFromMid(Vector2D position, Vector2D direction)
|
|
{
|
|
{
|
|
- Int16 depthStart = depthImage.getDepthAt(fingerSlice.Start);
|
|
|
|
- Int16 depthMid = depthImage.getDepthAt(fingerSlice.Mid);
|
|
|
|
- Int16 depthEnd = depthImage.getDepthAt(fingerSlice.End);
|
|
|
|
- return (depthStart > depthMid && depthMid < depthEnd);
|
|
|
|
|
|
+ if (edgeImage.isEdgeAt(position)) return null;
|
|
|
|
+
|
|
|
|
+ Vector2D dirStart = direction.getOrthogonal(true);
|
|
|
|
+ Vector2D dirEnd = direction.getOrthogonal(false);
|
|
|
|
+
|
|
|
|
+ Vector2D start = findNextEdge(position, dirStart);
|
|
|
|
+ if (start == null) return null;
|
|
|
|
+
|
|
|
|
+ Vector2D end = findNextEdge(position, dirEnd);
|
|
|
|
+ if (end == null) return null;
|
|
|
|
+
|
|
|
|
+ return getFingerSlice(start, end);
|
|
|
|
+ }
|
|
|
|
+ private FingerSlice findFingerSliceFromStartEdge(Vector2D start, Vector2D direction)
|
|
|
|
+ {
|
|
|
|
+
|
|
|
|
+ Vector2D end = findNextEdge(start, direction);
|
|
|
|
+ if (end == null) return null;
|
|
|
|
+
|
|
|
|
+ return getFingerSlice(start, end);
|
|
}
|
|
}
|
|
|
|
|
|
- private void addHorizontalFingerSlice(FingerSlice slice)
|
|
|
|
|
|
+ private Vector2D findNextEdge(Vector2D start, Vector2D direction)
|
|
{
|
|
{
|
|
- int maxYGap = 5;
|
|
|
|
- int maxXDifference = 5;
|
|
|
|
|
|
+ int maxX = depthImage.Width - 1;
|
|
|
|
+ int maxY = depthImage.Height - 1;
|
|
|
|
+
|
|
|
|
+ int maxFingerSize = 30;
|
|
|
|
+
|
|
|
|
+ int maxStepsX;
|
|
|
|
+ if (direction.X > 0)
|
|
|
|
+ maxStepsX = (int)((maxX - start.X) / direction.X);
|
|
|
|
+ else if (direction.X < 0)
|
|
|
|
+ maxStepsX = (int)(start.X / Math.Abs(direction.X));
|
|
|
|
+ else
|
|
|
|
+ maxStepsX = int.MaxValue;
|
|
|
|
+
|
|
|
|
+ int maxStepsY;
|
|
|
|
+ if (direction.Y > 0)
|
|
|
|
+ maxStepsY = (int)((maxY - start.Y) / direction.Y);
|
|
|
|
+ else if (direction.Y < 0)
|
|
|
|
+ maxStepsY = (int)(start.Y / Math.Abs(direction.Y));
|
|
|
|
+ else
|
|
|
|
+ maxStepsY = int.MaxValue;
|
|
|
|
|
|
- bool assigned = false;
|
|
|
|
- foreach (FingerSliceTrail trail in horizontalFingerSliceTrails)
|
|
|
|
|
|
+ int maxStepsLength = (int)(maxFingerSize / direction.Length);
|
|
|
|
+
|
|
|
|
+ int maxSteps = Math.Min(maxStepsLength, Math.Min(maxStepsX, maxStepsY));
|
|
|
|
+
|
|
|
|
+ Vector2D end = new Vector2D(start);
|
|
|
|
+ for (int i = 0; i < maxSteps; i++)
|
|
{
|
|
{
|
|
- FingerSlice trailEnd = trail.End;
|
|
|
|
- if (slice.Mid.Y - trailEnd.Mid.Y <= maxYGap && Math.Abs(trailEnd.Mid.X - slice.Mid.X) <= maxXDifference)
|
|
|
|
|
|
+ end += direction;
|
|
|
|
+
|
|
|
|
+ if (edgeImage.isEdgeAt(end))
|
|
{
|
|
{
|
|
- trail.addSlice(slice);
|
|
|
|
- assigned = true;
|
|
|
|
|
|
+ return end;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- if (!assigned)
|
|
|
|
- horizontalFingerSliceTrails.Add(new FingerSliceTrail(slice));
|
|
|
|
|
|
+ return null;
|
|
}
|
|
}
|
|
|
|
|
|
- private void addVerticalFingerSlice(FingerSlice slice)
|
|
|
|
|
|
+ private FingerSlice getFingerSlice(Vector2D start, Vector2D end)
|
|
{
|
|
{
|
|
- int maxXGap = 5;
|
|
|
|
- int maxYDifference = 5;
|
|
|
|
|
|
+ int maxX = depthImage.Width - 1;
|
|
|
|
+ int maxY = depthImage.Height - 1;
|
|
|
|
|
|
- bool assigned = false;
|
|
|
|
- foreach (FingerSliceTrail trail in verticalFingerSliceTrails)
|
|
|
|
- {
|
|
|
|
- FingerSlice trailEnd = trail.End;
|
|
|
|
- if (slice.Mid.X - trailEnd.Mid.X <= maxXGap && Math.Abs(trailEnd.Mid.Y - slice.Mid.Y) <= maxYDifference)
|
|
|
|
- {
|
|
|
|
- trail.addSlice(slice);
|
|
|
|
- assigned = true;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ int minFingerSize = 5;
|
|
|
|
+ int maxFingerSize = 30;
|
|
|
|
+
|
|
|
|
+ Vector2D direction = (end - start).normalize();
|
|
|
|
+ Vector2D beforeStart = start - direction;
|
|
|
|
+ Vector2D behindEnd = end + direction;
|
|
|
|
+
|
|
|
|
+ start = beforeStart.isWithin(0, 0, maxX, maxY) ? beforeStart : start;
|
|
|
|
+ end = behindEnd.isWithin(0, 0, maxX, maxY) ? behindEnd : end;
|
|
|
|
+
|
|
|
|
+ FingerSlice slice = new FingerSlice(start, end);
|
|
|
|
+ if (slice.Length >= minFingerSize && slice.Length <= maxFingerSize && fingerSliceDepthTest(slice))
|
|
|
|
+ return slice;
|
|
|
|
+
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private bool fingerSliceDepthTest(FingerSlice fingerSlice)
|
|
|
|
+ {
|
|
|
|
+ Int16 depthStart = depthImage.getDepthAt(fingerSlice.Start);
|
|
|
|
+ Int16 depthMid = depthImage.getDepthAt(fingerSlice.Mid);
|
|
|
|
+ Int16 depthEnd = depthImage.getDepthAt(fingerSlice.End);
|
|
|
|
+ return (depthStart > depthMid && depthMid < depthEnd);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void createFingerFromTrail(FingerSliceTrail trail)
|
|
|
|
+ {
|
|
|
|
+ Finger finger = new Finger(trail);
|
|
|
|
+
|
|
|
|
+ //add finger
|
|
|
|
+ Fingers.Add(finger);
|
|
|
|
+
|
|
|
|
+ //draw finger
|
|
|
|
+ fingerImage.drawFinger(finger, FingerImageState.fingerDetected);
|
|
|
|
|
|
- if (!assigned)
|
|
|
|
- verticalFingerSliceTrails.Add(new FingerSliceTrail(slice));
|
|
|
|
|
|
+ //remove edges around detected finger to improve performance
|
|
|
|
+ edgeImage.removeFingerEdges(finger);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|