|
@@ -13,162 +13,147 @@ namespace bbiwarg.Detectors.Fingers
|
|
|
{
|
|
|
class Finger
|
|
|
{
|
|
|
- private List<Vector<int>> fingerPoints;
|
|
|
- private bool fingerEndPointsUpToDate = false;
|
|
|
- private bool lineUpToDate = false;
|
|
|
+ private Vector2D tipPoint;
|
|
|
+ private Vector2D handPoint;
|
|
|
+ private Vector2D direction;
|
|
|
private float length;
|
|
|
- private Vector<int> tipPoint;
|
|
|
- private Vector<int> handPoint;
|
|
|
- private Vector<float> direction;
|
|
|
- private Vector<float> pointOnLine;
|
|
|
- private Vector<float> lineEndPoint1;
|
|
|
- private Vector<float> lineEndPoint2;
|
|
|
-
|
|
|
- public Finger(List<Vector<int>> fingerPoints) {
|
|
|
- this.fingerPoints = fingerPoints;
|
|
|
+
|
|
|
+ public Finger(Vector2D p1, Vector2D p2) {
|
|
|
+ assignTipAndHandPoint(p1, p2);
|
|
|
}
|
|
|
|
|
|
public float getLength() {
|
|
|
- if (!fingerEndPointsUpToDate) updateFingerEndPoints();
|
|
|
return length;
|
|
|
}
|
|
|
|
|
|
- public int getNumFingerPoints() {
|
|
|
- return fingerPoints.Count;
|
|
|
- }
|
|
|
-
|
|
|
- public List<Vector<int>> getFingerPoints() {
|
|
|
- return fingerPoints;
|
|
|
- }
|
|
|
-
|
|
|
- public Vector<int> getTipPoint() {
|
|
|
- if (!fingerEndPointsUpToDate) updateFingerEndPoints();
|
|
|
+ public Vector2D getTipPoint() {
|
|
|
return tipPoint;
|
|
|
}
|
|
|
|
|
|
- public Vector<int> getHandPoint() {
|
|
|
- if (!fingerEndPointsUpToDate) updateFingerEndPoints();
|
|
|
+ public Vector2D getHandPoint() {
|
|
|
return handPoint;
|
|
|
}
|
|
|
|
|
|
- public Vector<float> getDirection()
|
|
|
- {
|
|
|
- if (!lineUpToDate) updateLine();
|
|
|
+ public Vector2D getDirection() {
|
|
|
return direction;
|
|
|
}
|
|
|
|
|
|
- public Vector<float> getLineEndPoint1()
|
|
|
+ public float getSimilarity(Finger compareFinger)
|
|
|
{
|
|
|
- if (!lineUpToDate) updateLine();
|
|
|
- return lineEndPoint1;
|
|
|
- }
|
|
|
+ //thresholds
|
|
|
+ float thresholdMaxAngle = (float)(30 * Math.PI / 180); // 30°
|
|
|
+ float thresholdMaxParallelDistance = 40;
|
|
|
+ float thresholdMaxVerticalDistance = 40;
|
|
|
|
|
|
- public Vector<float> getLineEndPoint2()
|
|
|
- {
|
|
|
- if (!lineUpToDate) updateLine();
|
|
|
- return lineEndPoint2;
|
|
|
- }
|
|
|
+ //check angle
|
|
|
+ float angle = direction.getMinAngleBetween(compareFinger.direction);
|
|
|
+ float angleSimilarity = Math.Max(1 - angle / thresholdMaxAngle, 0);
|
|
|
|
|
|
- public void addFingerPoint(Vector<int> fingerPoint) {
|
|
|
- fingerPoints.Add(fingerPoint);
|
|
|
- fingerEndPointsUpToDate = false;
|
|
|
- lineUpToDate = false;
|
|
|
- }
|
|
|
+ //check parallel distance
|
|
|
+ float minParallelDistance = getMinParallelDistance(compareFinger);
|
|
|
+ float parallelDistanceSimilarity = Math.Max(1 - minParallelDistance / thresholdMaxParallelDistance, 0);
|
|
|
|
|
|
- public bool isWithinDistance(Vector<int> point, float maxDistance) {
|
|
|
- foreach (Vector<int> fp in fingerPoints) {
|
|
|
- if (fp.subDistance(point, 2) <= maxDistance)
|
|
|
- return true;
|
|
|
- }
|
|
|
- return false;
|
|
|
+ //check vertical distance
|
|
|
+ float minVerticalDistance = getMinVerticalDistance(compareFinger);
|
|
|
+ float verticalDistanceSimilarity = Math.Max(1 - minVerticalDistance / thresholdMaxVerticalDistance, 0);
|
|
|
+
|
|
|
+ return (angleSimilarity + parallelDistanceSimilarity + verticalDistanceSimilarity) / 3;
|
|
|
}
|
|
|
|
|
|
- public float getSimilarity(Finger compareFinger)
|
|
|
+ public bool isMergeable(Finger mergeFinger)
|
|
|
{
|
|
|
- if (!fingerEndPointsUpToDate) updateFingerEndPoints();
|
|
|
- if (!lineUpToDate) updateLine();
|
|
|
-
|
|
|
- //startDistance
|
|
|
- float maxStartDistance = 100;
|
|
|
- float xDiffStart = lineEndPoint1.x - compareFinger.getLineEndPoint1().x;
|
|
|
- float yDiffStart = lineEndPoint1.y - compareFinger.getLineEndPoint1().y;
|
|
|
- float startDistance = (float)Math.Sqrt(xDiffStart * xDiffStart + yDiffStart * yDiffStart);
|
|
|
- float startSimilarity = Math.Max(1 - (startDistance / maxStartDistance), 0);
|
|
|
-
|
|
|
- //endDistance
|
|
|
- float maxEndDistance = 100;
|
|
|
- float xDiffEnd = lineEndPoint2.x - compareFinger.getLineEndPoint2().x;
|
|
|
- float yDiffEnd = lineEndPoint2.y - compareFinger.getLineEndPoint2().y;
|
|
|
- float endDistance = (float)Math.Sqrt(xDiffEnd * xDiffEnd + yDiffEnd * yDiffEnd);
|
|
|
- float endSimilarity = Math.Max(1 - (endDistance / maxEndDistance), 0);
|
|
|
-
|
|
|
- //direction
|
|
|
- float scalaProduct = direction * compareFinger.getDirection();
|
|
|
- float lengthDirection = direction.norm();
|
|
|
- float lengthCompareDirection = compareFinger.getDirection().norm();
|
|
|
- float directionSimilarity = Math.Abs(scalaProduct / (lengthDirection * lengthCompareDirection));
|
|
|
-
|
|
|
- return (startSimilarity + endSimilarity + directionSimilarity) / 3;
|
|
|
+ //thresholds
|
|
|
+ float thresholdMaxAngle = (float) (15 * Math.PI / 180); // 15°
|
|
|
+ float thresholdMaxParallelDistance = 10;
|
|
|
+ float thresholdMaxVerticalDistance = 20;
|
|
|
+
|
|
|
+ //check angle
|
|
|
+ float angle = direction.getMinAngleBetween(mergeFinger.direction);
|
|
|
+ if (angle > thresholdMaxAngle) return false;
|
|
|
|
|
|
+ //check parallel distance
|
|
|
+ float minParallelDistance = getMinParallelDistance(mergeFinger);
|
|
|
+ if (minParallelDistance > thresholdMaxParallelDistance) return false;
|
|
|
+
|
|
|
+ //check vertical distance
|
|
|
+ float minVerticalDistance = getMinVerticalDistance(mergeFinger);
|
|
|
+ if (minVerticalDistance > thresholdMaxVerticalDistance) return false;
|
|
|
+
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
- private void updateFingerEndPoints() {
|
|
|
- Vector<int> fp1 = fingerPoints[0];
|
|
|
- Vector<int> fp2 = fingerPoints[0];
|
|
|
- length = float.MinValue;
|
|
|
-
|
|
|
- foreach (Vector<int> fp in fingerPoints) {
|
|
|
- float distanceToFP1 = fp.subDistance(fp1, 2);
|
|
|
- float distanceToFP2 = fp.subDistance(fp2, 2);
|
|
|
-
|
|
|
- if (distanceToFP1 > length && distanceToFP1 >= distanceToFP2) {
|
|
|
- fp2 = fp;
|
|
|
- length = distanceToFP1;
|
|
|
- }
|
|
|
- else if (distanceToFP2 > length && distanceToFP2 > distanceToFP1) {
|
|
|
- fp1 = fp;
|
|
|
- length = distanceToFP2;
|
|
|
- }
|
|
|
- }
|
|
|
+ public void mergeFingers(Finger mergeFinger) {
|
|
|
+ Vector2D a1 = tipPoint;
|
|
|
+ Vector2D a2 = handPoint;
|
|
|
+ Vector2D b1 = mergeFinger.getTipPoint();
|
|
|
+ Vector2D b2 = mergeFinger.getHandPoint();
|
|
|
+
|
|
|
+ //find two points that are furthest apart
|
|
|
+ float distanceA1A2 = length;
|
|
|
+ float distanceA1B1 = a1.getDistanceTo(b1);
|
|
|
+ float distanceA1B2 = a1.getDistanceTo(b2);
|
|
|
+ float distanceA2B1 = a2.getDistanceTo(b1);
|
|
|
+ float distanceA2B2 = a2.getDistanceTo(b2);
|
|
|
+ float distanceB1B2 = mergeFinger.getLength();
|
|
|
+ float maxDistance = Math.Max(Math.Max(distanceA1A2, distanceA1B1), Math.Max(Math.Max(distanceA1B2, distanceA2B1), Math.Max(distanceA2B2, distanceB1B2)));
|
|
|
+
|
|
|
+ if(maxDistance == distanceA1A2) assignTipAndHandPoint(a1,a2);
|
|
|
+ else if(maxDistance == distanceA1B1) assignTipAndHandPoint(a1, b1);
|
|
|
+ else if(maxDistance == distanceA1B2) assignTipAndHandPoint(a1, b2);
|
|
|
+ else if(maxDistance == distanceA2B1) assignTipAndHandPoint(a2, b1);
|
|
|
+ else if(maxDistance == distanceA2B2) assignTipAndHandPoint(a2, b2);
|
|
|
+ else if(maxDistance == distanceB1B2) assignTipAndHandPoint(b1, b2);
|
|
|
+ }
|
|
|
|
|
|
- if (fp1.y < fp2.y)
|
|
|
+ private void assignTipAndHandPoint(Vector2D p1, Vector2D p2)
|
|
|
+ {
|
|
|
+ if (p1.y < p2.y)
|
|
|
{
|
|
|
- tipPoint = fp1;
|
|
|
- handPoint = fp2;
|
|
|
+ this.tipPoint = p1;
|
|
|
+ this.handPoint = p2;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- tipPoint = fp2;
|
|
|
- handPoint = fp1;
|
|
|
+ this.tipPoint = p2;
|
|
|
+ this.handPoint = p1;
|
|
|
}
|
|
|
|
|
|
- fingerEndPointsUpToDate = true;
|
|
|
+ direction = handPoint - tipPoint;
|
|
|
+ length = direction.getLength();
|
|
|
}
|
|
|
|
|
|
- private void updateLine() {
|
|
|
-
|
|
|
- if (!fingerEndPointsUpToDate) updateFingerEndPoints();
|
|
|
-
|
|
|
- //update direction+pointOnLine
|
|
|
- PointF[] pointArray = new PointF[fingerPoints.Count];
|
|
|
- int i = 0;
|
|
|
- foreach (Vector<int> fp in fingerPoints)
|
|
|
- {
|
|
|
- pointArray[i] = new PointF(fp.x, fp.y);
|
|
|
- ++i;
|
|
|
- }
|
|
|
- PointF tempDirection;
|
|
|
- PointF tempPointOnLine;
|
|
|
- PointCollection.Line2DFitting(pointArray, Emgu.CV.CvEnum.DIST_TYPE.CV_DIST_L2, out tempDirection, out tempPointOnLine);
|
|
|
- direction = new Vector<float>(tempDirection.X, tempDirection.Y);
|
|
|
- pointOnLine = new Vector<float>(tempPointOnLine.X, tempPointOnLine.Y);
|
|
|
-
|
|
|
- //update start+end
|
|
|
- lineEndPoint1 = Vector<float>.projectToLine(tipPoint, direction, pointOnLine);
|
|
|
- lineEndPoint2 = Vector<float>.projectToLine(handPoint, direction, pointOnLine);
|
|
|
-
|
|
|
- lineUpToDate = true;
|
|
|
+ private float getMinParallelDistance(Finger finger)
|
|
|
+ {
|
|
|
+ Vector2D a1 = tipPoint;
|
|
|
+ Vector2D a2 = handPoint;
|
|
|
+ Vector2D b1 = finger.getTipPoint();
|
|
|
+ Vector2D b2 = finger.getHandPoint();
|
|
|
+
|
|
|
+ //project on finger line
|
|
|
+ Vector2D p1 = Vector2D.projectToLine(b1, direction, a1);
|
|
|
+ Vector2D p2 = Vector2D.projectToLine(b2, direction, a1);
|
|
|
+
|
|
|
+ float distanceP1B1 = p1.getDistanceTo(b1);
|
|
|
+ float distanceP2B2 = p2.getDistanceTo(b2);
|
|
|
+ return Math.Min(distanceP1B1, distanceP2B2);
|
|
|
}
|
|
|
|
|
|
+ private float getMinVerticalDistance(Finger finger)
|
|
|
+ {
|
|
|
+ Vector2D a1 = tipPoint;
|
|
|
+ Vector2D a2 = handPoint;
|
|
|
+ Vector2D b1 = finger.getTipPoint();
|
|
|
+ Vector2D b2 = finger.getHandPoint();
|
|
|
+
|
|
|
+ //project on finger line
|
|
|
+ Vector2D p1 = Vector2D.projectToLine(b1, direction, a1);
|
|
|
+ Vector2D p2 = Vector2D.projectToLine(b2, direction, a1);
|
|
|
+
|
|
|
+ float distanceP1A1 = p1.getDistanceTo(a1);
|
|
|
+ float distanceP1A2 = p1.getDistanceTo(a2);
|
|
|
+ float distanceP2A1 = p2.getDistanceTo(a1);
|
|
|
+ float distanceP2A2 = p2.getDistanceTo(a2);
|
|
|
+ return Math.Min(Math.Min(distanceP1A1, distanceP1A2), Math.Min(distanceP2A1, distanceP2A2));
|
|
|
+ }
|
|
|
}
|
|
|
}
|