init assets and filemgt
This commit is contained in:
131
Assets/PDollar/Scripts/Demo.cs
Normal file
131
Assets/PDollar/Scripts/Demo.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
using PDollarGestureRecognizer;
|
||||
|
||||
public class Demo : MonoBehaviour {
|
||||
|
||||
public Transform gestureOnScreenPrefab;
|
||||
|
||||
private List<Gesture> trainingSet = new List<Gesture>();
|
||||
|
||||
private List<Point> points = new List<Point>();
|
||||
private int strokeId = -1;
|
||||
|
||||
private Vector3 virtualKeyPosition = Vector2.zero;
|
||||
private Rect drawArea;
|
||||
|
||||
private RuntimePlatform platform;
|
||||
private int vertexCount = 0;
|
||||
|
||||
private List<LineRenderer> gestureLinesRenderer = new List<LineRenderer>();
|
||||
private LineRenderer currentGestureLineRenderer;
|
||||
|
||||
//GUI
|
||||
private string message;
|
||||
private bool recognized;
|
||||
private string newGestureName = "";
|
||||
|
||||
void Start () {
|
||||
|
||||
platform = Application.platform;
|
||||
drawArea = new Rect(0, 0, Screen.width - Screen.width / 3, Screen.height);
|
||||
|
||||
//Load pre-made gestures
|
||||
TextAsset[] gesturesXml = Resources.LoadAll<TextAsset>("GestureSet/10-stylus-MEDIUM/");
|
||||
foreach (TextAsset gestureXml in gesturesXml)
|
||||
trainingSet.Add(GestureIO.ReadGestureFromXML(gestureXml.text));
|
||||
|
||||
//Load user custom gestures
|
||||
string[] filePaths = Directory.GetFiles(Application.persistentDataPath, "*.xml");
|
||||
foreach (string filePath in filePaths)
|
||||
trainingSet.Add(GestureIO.ReadGestureFromFile(filePath));
|
||||
}
|
||||
|
||||
void Update () {
|
||||
|
||||
if (platform == RuntimePlatform.Android || platform == RuntimePlatform.IPhonePlayer) {
|
||||
if (Input.touchCount > 0) {
|
||||
virtualKeyPosition = new Vector3(Input.GetTouch(0).position.x, Input.GetTouch(0).position.y);
|
||||
}
|
||||
} else {
|
||||
if (Input.GetMouseButton(0)) {
|
||||
virtualKeyPosition = new Vector3(Input.mousePosition.x, Input.mousePosition.y);
|
||||
}
|
||||
}
|
||||
|
||||
if (drawArea.Contains(virtualKeyPosition)) {
|
||||
|
||||
if (Input.GetMouseButtonDown(0)) {
|
||||
|
||||
if (recognized) {
|
||||
|
||||
recognized = false;
|
||||
strokeId = -1;
|
||||
|
||||
points.Clear();
|
||||
|
||||
foreach (LineRenderer lineRenderer in gestureLinesRenderer) {
|
||||
|
||||
lineRenderer.SetVertexCount(0);
|
||||
Destroy(lineRenderer.gameObject);
|
||||
}
|
||||
|
||||
gestureLinesRenderer.Clear();
|
||||
}
|
||||
|
||||
++strokeId;
|
||||
|
||||
Transform tmpGesture = Instantiate(gestureOnScreenPrefab, transform.position, transform.rotation) as Transform;
|
||||
currentGestureLineRenderer = tmpGesture.GetComponent<LineRenderer>();
|
||||
|
||||
gestureLinesRenderer.Add(currentGestureLineRenderer);
|
||||
|
||||
vertexCount = 0;
|
||||
}
|
||||
|
||||
if (Input.GetMouseButton(0)) {
|
||||
points.Add(new Point(virtualKeyPosition.x, -virtualKeyPosition.y, strokeId));
|
||||
|
||||
currentGestureLineRenderer.SetVertexCount(++vertexCount);
|
||||
currentGestureLineRenderer.SetPosition(vertexCount - 1, Camera.main.ScreenToWorldPoint(new Vector3(virtualKeyPosition.x, virtualKeyPosition.y, 10)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnGUI() {
|
||||
|
||||
GUI.Box(drawArea, "Draw Area");
|
||||
|
||||
GUI.Label(new Rect(10, Screen.height - 40, 500, 50), message);
|
||||
|
||||
if (GUI.Button(new Rect(Screen.width - 100, 10, 100, 30), "Recognize")) {
|
||||
|
||||
recognized = true;
|
||||
|
||||
Gesture candidate = new Gesture(points.ToArray());
|
||||
Result gestureResult = PointCloudRecognizer.Classify(candidate, trainingSet.ToArray());
|
||||
|
||||
message = gestureResult.GestureClass + " " + gestureResult.Score;
|
||||
}
|
||||
|
||||
GUI.Label(new Rect(Screen.width - 200, 150, 70, 30), "Add as: ");
|
||||
newGestureName = GUI.TextField(new Rect(Screen.width - 150, 150, 100, 30), newGestureName);
|
||||
|
||||
if (GUI.Button(new Rect(Screen.width - 50, 150, 50, 30), "Add") && points.Count > 0 && newGestureName != "") {
|
||||
|
||||
string fileName = String.Format("{0}/{1}-{2}.xml", Application.persistentDataPath, newGestureName, DateTime.Now.ToFileTime());
|
||||
|
||||
#if !UNITY_WEBPLAYER
|
||||
GestureIO.WriteGesture(points.ToArray(), newGestureName, fileName);
|
||||
#endif
|
||||
|
||||
trainingSet.Add(new Gesture(points.ToArray(), newGestureName));
|
||||
|
||||
newGestureName = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Assets/PDollar/Scripts/Demo.cs.meta
Normal file
8
Assets/PDollar/Scripts/Demo.cs.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 63465958d7bcc403e89a14826c19f7bc
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
8
Assets/PDollar/Scripts/PDollarGestureRecognizer.meta
Normal file
8
Assets/PDollar/Scripts/PDollarGestureRecognizer.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c92918eb0fa65af4a93fdc9056b24297
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
82
Assets/PDollar/Scripts/PDollarGestureRecognizer/Geometry.cs
Normal file
82
Assets/PDollar/Scripts/PDollarGestureRecognizer/Geometry.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* The $P Point-Cloud Recognizer (.NET Framework 4.0 C# version)
|
||||
*
|
||||
* Radu-Daniel Vatavu, Ph.D.
|
||||
* University Stefan cel Mare of Suceava
|
||||
* Suceava 720229, Romania
|
||||
* vatavu@eed.usv.ro
|
||||
*
|
||||
* Lisa Anthony, Ph.D.
|
||||
* UMBC
|
||||
* Information Systems Department
|
||||
* 1000 Hilltop Circle
|
||||
* Baltimore, MD 21250
|
||||
* lanthony@umbc.edu
|
||||
*
|
||||
* Jacob O. Wobbrock, Ph.D.
|
||||
* The Information School
|
||||
* University of Washington
|
||||
* Seattle, WA 98195-2840
|
||||
* wobbrock@uw.edu
|
||||
*
|
||||
* The academic publication for the $P recognizer, and what should be
|
||||
* used to cite it, is:
|
||||
*
|
||||
* Vatavu, R.-D., Anthony, L. and Wobbrock, J.O. (2012).
|
||||
* Gestures as point clouds: A $P recognizer for user interface
|
||||
* prototypes. Proceedings of the ACM Int'l Conference on
|
||||
* Multimodal Interfaces (ICMI '12). Santa Monica, California
|
||||
* (October 22-26, 2012). New York: ACM Press, pp. 273-280.
|
||||
*
|
||||
* This software is distributed under the "New BSD License" agreement:
|
||||
*
|
||||
* Copyright (c) 2012, Radu-Daniel Vatavu, Lisa Anthony, and
|
||||
* Jacob O. Wobbrock. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the names of the University Stefan cel Mare of Suceava,
|
||||
* University of Washington, nor UMBC, nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Radu-Daniel Vatavu OR Lisa Anthony
|
||||
* OR Jacob O. Wobbrock BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
||||
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
**/
|
||||
using System;
|
||||
|
||||
namespace PDollarGestureRecognizer
|
||||
{
|
||||
public class Geometry
|
||||
{
|
||||
/// <summary>
|
||||
/// Computes the Squared Euclidean Distance between two points in 2D
|
||||
/// </summary>
|
||||
public static float SqrEuclideanDistance(Point a, Point b)
|
||||
{
|
||||
return (a.X - b.X) * (a.X - b.X) + (a.Y - b.Y) * (a.Y - b.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the Euclidean Distance between two points in 2D
|
||||
/// </summary>
|
||||
public static float EuclideanDistance(Point a, Point b)
|
||||
{
|
||||
return (float)Math.Sqrt(SqrEuclideanDistance(a, b));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4ffb6ac588cc947cebfda99d6eb54783
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
208
Assets/PDollar/Scripts/PDollarGestureRecognizer/Gesture.cs
Normal file
208
Assets/PDollar/Scripts/PDollarGestureRecognizer/Gesture.cs
Normal file
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* The $P Point-Cloud Recognizer (.NET Framework 4.0 C# version)
|
||||
*
|
||||
* Radu-Daniel Vatavu, Ph.D.
|
||||
* University Stefan cel Mare of Suceava
|
||||
* Suceava 720229, Romania
|
||||
* vatavu@eed.usv.ro
|
||||
*
|
||||
* Lisa Anthony, Ph.D.
|
||||
* UMBC
|
||||
* Information Systems Department
|
||||
* 1000 Hilltop Circle
|
||||
* Baltimore, MD 21250
|
||||
* lanthony@umbc.edu
|
||||
*
|
||||
* Jacob O. Wobbrock, Ph.D.
|
||||
* The Information School
|
||||
* University of Washington
|
||||
* Seattle, WA 98195-2840
|
||||
* wobbrock@uw.edu
|
||||
*
|
||||
* The academic publication for the $P recognizer, and what should be
|
||||
* used to cite it, is:
|
||||
*
|
||||
* Vatavu, R.-D., Anthony, L. and Wobbrock, J.O. (2012).
|
||||
* Gestures as point clouds: A $P recognizer for user interface
|
||||
* prototypes. Proceedings of the ACM Int'l Conference on
|
||||
* Multimodal Interfaces (ICMI '12). Santa Monica, California
|
||||
* (October 22-26, 2012). New York: ACM Press, pp. 273-280.
|
||||
*
|
||||
* This software is distributed under the "New BSD License" agreement:
|
||||
*
|
||||
* Copyright (c) 2012, Radu-Daniel Vatavu, Lisa Anthony, and
|
||||
* Jacob O. Wobbrock. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the names of the University Stefan cel Mare of Suceava,
|
||||
* University of Washington, nor UMBC, nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Radu-Daniel Vatavu OR Lisa Anthony
|
||||
* OR Jacob O. Wobbrock BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
||||
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
**/
|
||||
using System;
|
||||
|
||||
namespace PDollarGestureRecognizer
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements a gesture as a cloud of points (i.e., an unordered set of points).
|
||||
/// Gestures are normalized with respect to scale, translated to origin, and resampled into a fixed number of 32 points.
|
||||
/// </summary>
|
||||
public class Gesture
|
||||
{
|
||||
public Point[] Points = null; // gesture points (normalized)
|
||||
public string Name = ""; // gesture class
|
||||
private const int SAMPLING_RESOLUTION = 32;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a gesture from an array of points
|
||||
/// </summary>
|
||||
/// <param name="points"></param>
|
||||
public Gesture(Point[] points, string gestureName = "")
|
||||
{
|
||||
this.Name = gestureName;
|
||||
|
||||
// normalizes the array of points with respect to scale, origin, and number of points
|
||||
this.Points = Scale(points);
|
||||
this.Points = TranslateTo(Points, Centroid(Points));
|
||||
this.Points = Resample(Points, SAMPLING_RESOLUTION);
|
||||
}
|
||||
|
||||
#region gesture pre-processing steps: scale normalization, translation to origin, and resampling
|
||||
|
||||
/// <summary>
|
||||
/// Performs scale normalization with shape preservation into [0..1]x[0..1]
|
||||
/// </summary>
|
||||
/// <param name="points"></param>
|
||||
/// <returns></returns>
|
||||
private Point[] Scale(Point[] points)
|
||||
{
|
||||
float minx = float.MaxValue, miny = float.MaxValue, maxx = float.MinValue, maxy = float.MinValue;
|
||||
for (int i = 0; i < points.Length; i++)
|
||||
{
|
||||
if (minx > points[i].X) minx = points[i].X;
|
||||
if (miny > points[i].Y) miny = points[i].Y;
|
||||
if (maxx < points[i].X) maxx = points[i].X;
|
||||
if (maxy < points[i].Y) maxy = points[i].Y;
|
||||
}
|
||||
|
||||
Point[] newPoints = new Point[points.Length];
|
||||
float scale = Math.Max(maxx - minx, maxy - miny);
|
||||
for (int i = 0; i < points.Length; i++)
|
||||
newPoints[i] = new Point((points[i].X - minx) / scale, (points[i].Y - miny) / scale, points[i].StrokeID);
|
||||
return newPoints;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translates the array of points by p
|
||||
/// </summary>
|
||||
/// <param name="points"></param>
|
||||
/// <param name="p"></param>
|
||||
/// <returns></returns>
|
||||
private Point[] TranslateTo(Point[] points, Point p)
|
||||
{
|
||||
Point[] newPoints = new Point[points.Length];
|
||||
for (int i = 0; i < points.Length; i++)
|
||||
newPoints[i] = new Point(points[i].X - p.X, points[i].Y - p.Y, points[i].StrokeID);
|
||||
return newPoints;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the centroid for an array of points
|
||||
/// </summary>
|
||||
/// <param name="points"></param>
|
||||
/// <returns></returns>
|
||||
private Point Centroid(Point[] points)
|
||||
{
|
||||
float cx = 0, cy = 0;
|
||||
for (int i = 0; i < points.Length; i++)
|
||||
{
|
||||
cx += points[i].X;
|
||||
cy += points[i].Y;
|
||||
}
|
||||
return new Point(cx / points.Length, cy / points.Length, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resamples the array of points into n equally-distanced points
|
||||
/// </summary>
|
||||
/// <param name="points"></param>
|
||||
/// <param name="n"></param>
|
||||
/// <returns></returns>
|
||||
public Point[] Resample(Point[] points, int n)
|
||||
{
|
||||
Point[] newPoints = new Point[n];
|
||||
newPoints[0] = new Point(points[0].X, points[0].Y, points[0].StrokeID);
|
||||
int numPoints = 1;
|
||||
|
||||
float I = PathLength(points) / (n - 1); // computes interval length
|
||||
float D = 0;
|
||||
for (int i = 1; i < points.Length; i++)
|
||||
{
|
||||
if (points[i].StrokeID == points[i - 1].StrokeID)
|
||||
{
|
||||
float d = Geometry.EuclideanDistance(points[i - 1], points[i]);
|
||||
if (D + d >= I)
|
||||
{
|
||||
Point firstPoint = points[i - 1];
|
||||
while (D + d >= I)
|
||||
{
|
||||
// add interpolated point
|
||||
float t = Math.Min(Math.Max((I - D) / d, 0.0f), 1.0f);
|
||||
if (float.IsNaN(t)) t = 0.5f;
|
||||
newPoints[numPoints++] = new Point(
|
||||
(1.0f - t) * firstPoint.X + t * points[i].X,
|
||||
(1.0f - t) * firstPoint.Y + t * points[i].Y,
|
||||
points[i].StrokeID
|
||||
);
|
||||
|
||||
// update partial length
|
||||
d = D + d - I;
|
||||
D = 0;
|
||||
firstPoint = newPoints[numPoints - 1];
|
||||
}
|
||||
D = d;
|
||||
}
|
||||
else D += d;
|
||||
}
|
||||
}
|
||||
|
||||
if (numPoints == n - 1) // sometimes we fall a rounding-error short of adding the last point, so add it if so
|
||||
newPoints[numPoints++] = new Point(points[points.Length - 1].X, points[points.Length - 1].Y, points[points.Length - 1].StrokeID);
|
||||
return newPoints;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the path length for an array of points
|
||||
/// </summary>
|
||||
/// <param name="points"></param>
|
||||
/// <returns></returns>
|
||||
private float PathLength(Point[] points)
|
||||
{
|
||||
float length = 0;
|
||||
for (int i = 1; i < points.Length; i++)
|
||||
if (points[i].StrokeID == points[i - 1].StrokeID)
|
||||
length += Geometry.EuclideanDistance(points[i - 1], points[i]);
|
||||
return length;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 315e77a99b01f483bac4a8055ae4bd44
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
128
Assets/PDollar/Scripts/PDollarGestureRecognizer/GestureIO.cs
Normal file
128
Assets/PDollar/Scripts/PDollarGestureRecognizer/GestureIO.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace PDollarGestureRecognizer
|
||||
{
|
||||
public class GestureIO
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads a multistroke gesture from an XML file
|
||||
/// </summary>
|
||||
/// <param name="xml"></param>
|
||||
/// <returns></returns>
|
||||
public static Gesture ReadGestureFromXML(string xml) {
|
||||
|
||||
XmlTextReader xmlReader = null;
|
||||
Gesture gesture = null;
|
||||
|
||||
try {
|
||||
|
||||
xmlReader = new XmlTextReader(new StringReader(xml));
|
||||
gesture = ReadGesture(xmlReader);
|
||||
|
||||
} finally {
|
||||
|
||||
if (xmlReader != null)
|
||||
xmlReader.Close();
|
||||
}
|
||||
|
||||
return gesture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a multistroke gesture from an XML file
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <returns></returns>
|
||||
public static Gesture ReadGestureFromFile(string fileName) {
|
||||
|
||||
XmlTextReader xmlReader = null;
|
||||
Gesture gesture = null;
|
||||
|
||||
try {
|
||||
|
||||
xmlReader = new XmlTextReader(File.OpenText(fileName));
|
||||
gesture = ReadGesture(xmlReader);
|
||||
|
||||
} finally {
|
||||
|
||||
if (xmlReader != null)
|
||||
xmlReader.Close();
|
||||
}
|
||||
|
||||
return gesture;
|
||||
}
|
||||
|
||||
private static Gesture ReadGesture(XmlTextReader xmlReader)
|
||||
{
|
||||
List<Point> points = new List<Point>();
|
||||
int currentStrokeIndex = -1;
|
||||
string gestureName = "";
|
||||
try
|
||||
{
|
||||
while (xmlReader.Read())
|
||||
{
|
||||
if (xmlReader.NodeType != XmlNodeType.Element) continue;
|
||||
switch (xmlReader.Name)
|
||||
{
|
||||
case "Gesture":
|
||||
gestureName = xmlReader["Name"];
|
||||
if (gestureName.Contains("~")) // '~' character is specific to the naming convention of the MMG set
|
||||
gestureName = gestureName.Substring(0, gestureName.LastIndexOf('~'));
|
||||
if (gestureName.Contains("_")) // '_' character is specific to the naming convention of the MMG set
|
||||
gestureName = gestureName.Replace('_', ' ');
|
||||
break;
|
||||
case "Stroke":
|
||||
currentStrokeIndex++;
|
||||
break;
|
||||
case "Point":
|
||||
points.Add(new Point(
|
||||
float.Parse(xmlReader["X"]),
|
||||
float.Parse(xmlReader["Y"]),
|
||||
currentStrokeIndex
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (xmlReader != null)
|
||||
xmlReader.Close();
|
||||
}
|
||||
return new Gesture(points.ToArray(), gestureName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a multistroke gesture to an XML file
|
||||
/// </summary>
|
||||
public static void WriteGesture(PDollarGestureRecognizer.Point[] points, string gestureName, string fileName)
|
||||
{
|
||||
using (StreamWriter sw = new StreamWriter(fileName))
|
||||
{
|
||||
sw.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>");
|
||||
sw.WriteLine("<Gesture Name = \"{0}\">", gestureName);
|
||||
int currentStroke = -1;
|
||||
for (int i = 0; i < points.Length; i++)
|
||||
{
|
||||
if (points[i].StrokeID != currentStroke)
|
||||
{
|
||||
if (i > 0)
|
||||
sw.WriteLine("\t</Stroke>");
|
||||
sw.WriteLine("\t<Stroke>");
|
||||
currentStroke = points[i].StrokeID;
|
||||
}
|
||||
|
||||
sw.WriteLine("\t\t<Point X = \"{0}\" Y = \"{1}\" T = \"0\" Pressure = \"0\" />",
|
||||
points[i].X, points[i].Y
|
||||
);
|
||||
}
|
||||
sw.WriteLine("\t</Stroke>");
|
||||
sw.WriteLine("</Gesture>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d76c99c311504a75835d36abebaddee
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
80
Assets/PDollar/Scripts/PDollarGestureRecognizer/Point.cs
Normal file
80
Assets/PDollar/Scripts/PDollarGestureRecognizer/Point.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* The $P Point-Cloud Recognizer (.NET Framework 4.0 C# version)
|
||||
*
|
||||
* Radu-Daniel Vatavu, Ph.D.
|
||||
* University Stefan cel Mare of Suceava
|
||||
* Suceava 720229, Romania
|
||||
* vatavu@eed.usv.ro
|
||||
*
|
||||
* Lisa Anthony, Ph.D.
|
||||
* UMBC
|
||||
* Information Systems Department
|
||||
* 1000 Hilltop Circle
|
||||
* Baltimore, MD 21250
|
||||
* lanthony@umbc.edu
|
||||
*
|
||||
* Jacob O. Wobbrock, Ph.D.
|
||||
* The Information School
|
||||
* University of Washington
|
||||
* Seattle, WA 98195-2840
|
||||
* wobbrock@uw.edu
|
||||
*
|
||||
* The academic publication for the $P recognizer, and what should be
|
||||
* used to cite it, is:
|
||||
*
|
||||
* Vatavu, R.-D., Anthony, L. and Wobbrock, J.O. (2012).
|
||||
* Gestures as point clouds: A $P recognizer for user interface
|
||||
* prototypes. Proceedings of the ACM Int'l Conference on
|
||||
* Multimodal Interfaces (ICMI '12). Santa Monica, California
|
||||
* (October 22-26, 2012). New York: ACM Press, pp. 273-280.
|
||||
*
|
||||
* This software is distributed under the "New BSD License" agreement:
|
||||
*
|
||||
* Copyright (c) 2012, Radu-Daniel Vatavu, Lisa Anthony, and
|
||||
* Jacob O. Wobbrock. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the names of the University Stefan cel Mare of Suceava,
|
||||
* University of Washington, nor UMBC, nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Radu-Daniel Vatavu OR Lisa Anthony
|
||||
* OR Jacob O. Wobbrock BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
||||
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
**/
|
||||
using System;
|
||||
|
||||
namespace PDollarGestureRecognizer
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements a 2D Point that exposes X, Y, and StrokeID properties.
|
||||
/// StrokeID is the stroke index the point belongs to (e.g., 0, 1, 2, ...) that is filled by counting pen down/up events.
|
||||
/// </summary>
|
||||
public class Point
|
||||
{
|
||||
public float X, Y;
|
||||
public int StrokeID;
|
||||
|
||||
public Point(float x, float y, int strokeId)
|
||||
{
|
||||
this.X = x;
|
||||
this.Y = y;
|
||||
this.StrokeID = strokeId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 27aaeb3219c2e4a3ea919281a1dfb8fa
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* The $P Point-Cloud Recognizer (.NET Framework 4.0 C# version)
|
||||
*
|
||||
* Radu-Daniel Vatavu, Ph.D.
|
||||
* University Stefan cel Mare of Suceava
|
||||
* Suceava 720229, Romania
|
||||
* vatavu@eed.usv.ro
|
||||
*
|
||||
* Lisa Anthony, Ph.D.
|
||||
* UMBC
|
||||
* Information Systems Department
|
||||
* 1000 Hilltop Circle
|
||||
* Baltimore, MD 21250
|
||||
* lanthony@umbc.edu
|
||||
*
|
||||
* Jacob O. Wobbrock, Ph.D.
|
||||
* The Information School
|
||||
* University of Washington
|
||||
* Seattle, WA 98195-2840
|
||||
* wobbrock@uw.edu
|
||||
*
|
||||
* The academic publication for the $P recognizer, and what should be
|
||||
* used to cite it, is:
|
||||
*
|
||||
* Vatavu, R.-D., Anthony, L. and Wobbrock, J.O. (2012).
|
||||
* Gestures as point clouds: A $P recognizer for user interface
|
||||
* prototypes. Proceedings of the ACM Int'l Conference on
|
||||
* Multimodal Interfaces (ICMI '12). Santa Monica, California
|
||||
* (October 22-26, 2012). New York: ACM Press, pp. 273-280.
|
||||
*
|
||||
* This software is distributed under the "New BSD License" agreement:
|
||||
*
|
||||
* Copyright (c) 2012, Radu-Daniel Vatavu, Lisa Anthony, and
|
||||
* Jacob O. Wobbrock. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the names of the University Stefan cel Mare of Suceava,
|
||||
* University of Washington, nor UMBC, nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Radu-Daniel Vatavu OR Lisa Anthony
|
||||
* OR Jacob O. Wobbrock BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
||||
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
**/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace PDollarGestureRecognizer
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements the $P recognizer
|
||||
/// </summary>
|
||||
public class PointCloudRecognizer
|
||||
{
|
||||
/// <summary>
|
||||
/// Main function of the $P recognizer.
|
||||
/// Classifies a candidate gesture against a set of training samples.
|
||||
/// Returns the class of the closest neighbor in the training set.
|
||||
/// </summary>
|
||||
/// <param name="candidate"></param>
|
||||
/// <param name="trainingSet"></param>
|
||||
/// <returns></returns>
|
||||
public static Result Classify(Gesture candidate, Gesture[] trainingSet)
|
||||
{
|
||||
float minDistance = float.MaxValue;
|
||||
string gestureClass = "";
|
||||
foreach (Gesture template in trainingSet)
|
||||
{
|
||||
float dist = GreedyCloudMatch(candidate.Points, template.Points);
|
||||
if (dist < minDistance)
|
||||
{
|
||||
minDistance = dist;
|
||||
gestureClass = template.Name;
|
||||
}
|
||||
}
|
||||
|
||||
return gestureClass == "" ? new Result() {GestureClass = "No match", Score = 0.0f} : new Result() {GestureClass = gestureClass, Score = Mathf.Max((minDistance - 2.0f) / -2.0f, 0.0f)};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements greedy search for a minimum-distance matching between two point clouds
|
||||
/// </summary>
|
||||
/// <param name="points1"></param>
|
||||
/// <param name="points2"></param>
|
||||
/// <returns></returns>
|
||||
private static float GreedyCloudMatch(Point[] points1, Point[] points2)
|
||||
{
|
||||
int n = points1.Length; // the two clouds should have the same number of points by now
|
||||
float eps = 0.5f; // controls the number of greedy search trials (eps is in [0..1])
|
||||
int step = (int)Math.Floor(Math.Pow(n, 1.0f - eps));
|
||||
float minDistance = float.MaxValue;
|
||||
for (int i = 0; i < n; i += step)
|
||||
{
|
||||
float dist1 = CloudDistance(points1, points2, i); // match points1 --> points2 starting with index point i
|
||||
float dist2 = CloudDistance(points2, points1, i); // match points2 --> points1 starting with index point i
|
||||
minDistance = Math.Min(minDistance, Math.Min(dist1, dist2));
|
||||
}
|
||||
return minDistance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the distance between two point clouds by performing a minimum-distance greedy matching
|
||||
/// starting with point startIndex
|
||||
/// </summary>
|
||||
/// <param name="points1"></param>
|
||||
/// <param name="points2"></param>
|
||||
/// <param name="startIndex"></param>
|
||||
/// <returns></returns>
|
||||
private static float CloudDistance(Point[] points1, Point[] points2, int startIndex)
|
||||
{
|
||||
int n = points1.Length; // the two clouds should have the same number of points by now
|
||||
bool[] matched = new bool[n]; // matched[i] signals whether point i from the 2nd cloud has been already matched
|
||||
Array.Clear(matched, 0, n); // no points are matched at the beginning
|
||||
|
||||
float sum = 0; // computes the sum of distances between matched points (i.e., the distance between the two clouds)
|
||||
int i = startIndex;
|
||||
do
|
||||
{
|
||||
int index = -1;
|
||||
float minDistance = float.MaxValue;
|
||||
for(int j = 0; j < n; j++)
|
||||
if (!matched[j])
|
||||
{
|
||||
float dist = Geometry.SqrEuclideanDistance(points1[i], points2[j]); // use squared Euclidean distance to save some processing time
|
||||
if (dist < minDistance)
|
||||
{
|
||||
minDistance = dist;
|
||||
index = j;
|
||||
}
|
||||
}
|
||||
matched[index] = true; // point index from the 2nd cloud is matched to point i from the 1st cloud
|
||||
float weight = 1.0f - ((i - startIndex + n) % n) / (1.0f * n);
|
||||
sum += weight * minDistance; // weight each distance with a confidence coefficient that decreases from 1 to 0
|
||||
i = (i + 1) % n;
|
||||
} while (i != startIndex);
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52f14a9a2853942ce85ce3cc1b870fa7
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace PDollarGestureRecognizer {
|
||||
|
||||
public struct Result {
|
||||
|
||||
public string GestureClass;
|
||||
public float Score;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 62a02aa6ce62e44d08814ea5d80f92c6
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
Reference in New Issue
Block a user