GloveGRASP User's Guide

Document Version 1.0 Beta
Software Version 1.0 Beta
August 1996



Contents
1. Introduction
2. Software Installation
2.1 Quick Start
3. Device Driver Functions
3.1 Device Driver Sample Code
4 C++ Device Driver Library
4.1 OpenInventor Sample Code
5. Networking Functions
5.1 Networking Sample Code
6. Gesture Input
6.1 Gesture Training
6.2 Gesture Training Sample Code
6.3 Gesture Recognition
6.4 Gesture Recognition Sample Code
7. GRASPmodel
Appendix
a. Function Listings
b. Serial Cable Pin Outs

Copyright 1996 General Reality Company. Unpublished rights reserved under the copyright laws of the United States. ALL RIGHTS RESERVED.

1 Introduction


GloveGRASP is a set of C functions and C++ class libraries that will allow application developers to integrate the 5DT 5th Glove '95 into their SGI applications. The 5th Glove '95 is a low cost glove input device which measures finger flexure and the roll and pitch of the users hand. The affordability and accuracy of the 5th Glove make it an ideal alternative input device for many virtual reality and 3D graphics applications.

The GloveGRASP package is an essential toolkit for programmers wanting to add 5th Glove support to their applications. GloveGRASP has the following components:

  • A Reliable SGI Device Driver
  • C++ Libraries for Gesture Training
  • C++ Libraries for Gesture Recognition
  • TCP/IP Networking Functions for Client Server Applications

There are also sample application source code for:

  • Networked Gesture Input
  • A Stand Alone Device Driver
  • A Gesture Based Modeling Program

The low level device driver and networking libraries are written in C to ensure that they can be incorporated easily into existing applications while the gesture training and recognition functions are written in C++. The object libraries and sample source code have been successfully tested with IRIX 5.2 and 6.2.

This manual describes the GloveGRASP software in detail with example source code showing how to use each of the functions. This manual is available in html, postscript and Word6.0 format in the GloveGRASP distribution.

NOTE: The GloveGRASP software and documentation is currently in Beta form. Any bugs or errors in the software or documentation should be reported to General Reality, email support@genreality.com. The GloveGRASP 1.0 final release will be December 1996.


2 Installation


GloveGRASP is distributed as tarred and compressed files on one floppy disk. These files include source code, make files, binary executables and object files in separate directories. The file structure within the tar file is as follows:

GloveGRASP
GloveGRASP/README
GloveGRASP/demo
GloveGRASP/demo/DDriver.c
GloveGRASP/demo/GRivHand.cxx
GloveGRASP/demo/GRivHand.h
GloveGRASP/demo/GestureClient.c
GloveGRASP/demo/GestureRecog.cxx
GloveGRASP/demo/GestureServer.c
GloveGRASP/demo/GestureTrain.cxx
GloveGRASP/demo/IvDemo
GloveGRASP/demo/IvDemo.cxx
GloveGRASP/demo/IvDemo32
GloveGRASP/demo/IvDemo64
GloveGRASP/demo/Makefile
GloveGRASP/demo/README
GloveGRASP/demo/test.txt
GloveGRASP/demo/GRASPmodel
GloveGRASP/demo/GRASPmodel/GRASPObjectList.cxx
GloveGRASP/demo/GRASPmodel/GRASPdefines.h
GloveGRASP/demo/GRASPmodel/GRASPdummy.cxx
GloveGRASP/demo/GRASPmodel/GRASPdummy.h
GloveGRASP/demo/GRASPmodel/GRASPfastrak.cxx
GloveGRASP/demo/GRASPmodel/GRASPlist.cxx
GloveGRASP/demo/GRASPmodel/GRASPmain.cxx
GloveGRASP/demo/GRASPmodel/GRASPmain.h
GloveGRASP/demo/GRASPmodel/Makefile
GloveGRASP/demo/GRASPmodel/README
GloveGRASP/demo/GRASPmodel/aux.h
GloveGRASP/demo/GRASPmodel/demo.txt
GloveGRASP/demo/GRASPmodel/rasters.h
GloveGRASP/demo/GRASPmodel/seriallib.c
GloveGRASP/demo/GRASPmodel/seriallib.h
GloveGRASP/demo/GRASPmodel/trakit.c
GloveGRASP/demo/GRASPmodel/trakit.h
GloveGRASP/doc
GloveGRASP/doc/README
GloveGRASP/doc/manual.ps
GloveGRASP/doc/manual.doc
GloveGRASP/include
GloveGRASP/include/GR.h
GloveGRASP/include/GR5glove.h
GloveGRASP/include/GRCandidate.h
GloveGRASP/include/GRContext.h
GloveGRASP/include/GRDeviceData.h
GloveGRASP/include/GRFrame.h
GloveGRASP/include/GRGrasp.h
GloveGRASP/include/GRMath5D.h
GloveGRASP/include/GRNet.h
GloveGRASP/include/GRTrainRecog.h
GloveGRASP/lib
GloveGRASP/lib/README
GloveGRASP/lib/libgrasp.a
GloveGRASP/lib/libgrasp32.a
GloveGRASP/lib/libgrasp64.a

The include directory contains the necessary header files with the function definitions of all the GloveGRASP functions. These header files should be included in the path searched by the -I flag of your C/C++ compiler. The functions themselves are contained in the binary file libgrasp.a. The directory this file is contained in should be included in the path searched by the -L flag on your C/C++ compiler. The makefile in the demo directory shows how to compile the GloveGRASP libraries with your applications. The *.c and *.cxx files in the demo directory are sample applications showing how the GloveGRASP libraries can be used in practice.

In order to install the software the following steps should be followed:

1/ Change to the directory where you want the GloveGRASP files to be located. All the files will go into a GloveGRASP directory structure below this current directory.

2/ Copy the GloveGRASP compressed file, GRASP_t.z into the current directory.

3/ Copy the file GRASP_t.z to GRASP_t.Z
mv GRASP_t.z GRASP_t.Z

4/ Uncompress the file:
uncompress GRASP_t.Z

5/ Extract the GloveGRASP files:
tar xvf GRASP_t

These instructions can also be found in the file README on the GloveGRASP disk. The tar command will produce a complete listing of files as it is extracting them from the tar file. Sample programs ready for compilation can be found in the demo directory and all directories have README files explaining what files are in that directory and how to use them.

2.1 Quick Start

The executable IvDemo in the demo directory is an Inventor application that can be run directly without compilation. This is a simple example of the type of applications that can be written using the GloveGRASP libraries. Typing IvDemo in the demo directory will start the application. There is also a 64 bit version of the application which can be run by typing IvDemo64. Users must then enter the name of the serial port the 5th Glove is connected to, and go through a simple calibration routine.

grof@kodiak/demo> IvDemo

IvDemo - 5DT Glove Inventor Demo
Input the port that the glove is connected to : /dev/ttyd2
Ready to initialise min values (y/n)y
Ready to initialise max values(y/n)y

When the software asks if they are ready to initialise the minimum glove values the user should rest their hand flat on the table and enter 'y', this measures the minimum bend of each finger. Similarly when asked for the maximum glove values the user should make a tight fist and enter 'y', this measures the maximum bend of each finger.

After the calibration is complete an Inventor Examiner window will open with a simple virtual hand model, as shown below. Moving the fingers on the glove will cause the virtual fingers to move in the same way. Note that this application is written for a right handed glove, so user's with a left handed glove will see the opposite fingers moving. The hand object can be manipulated using the standard Examiner window widgets.


Figure 1.0 IvDemo screen snapshot showing the virtual hand.


3 Device Driver Functions


GloveGRASP has five device driver functions which allows access to all the 5th Glove input and output modes as described in Annexure B of the 5th Glove documentation.

These functions are:
FILE *GRopenPort(char *Port_name)
int GRwritePort(char *str,FILE *open_port)
int GRreadPort(FILE *open_port, int num_bytes, char *buf)
int GRreadHandData(FILE *file_port,int *hand_data)
int GRportClose(FILE *port)

and their prototypes can be found in the header file GR.h.

In order for the user to access raw glove data the serial port the glove is connected to must first be opened. This is accomplished with the GRopenPort(char *Port_name) function. This returns either a pointer to the port file name or NULL if the port couldn't be opened.

Once the port has been opened the user can use GRwritePort and GRreadPort to write and read data to and from the glove. The glove begins in command mode. The valid strings that can be sent to the glove in this mode are summarized below:

Command String Returns Description
'A'55HEXReset the glove into command mode
'Bxx''xx'Used to test the serial i/o
'C'<glove data> Set report data mode
'D'<glove data> Set continuous data mode
'G'<info>Request the glove info
'H' <data>55HEX Upload gestures
'I'<data>Download Gestures

Using these command strings the glove can either be set into report or continuous mode for reading raw data. In Report mode raw data is returned whenever the glove is polled by sending it any character except 'A', while in Continuous mode the glove streams raw data continuously to the serial port. In both case the string 'A' returns the glove back to command mode.

During Report mode:
Command String Returns Description
'A'55HEX Return to command mode
else<glove data> Return the glove data


During Continuous Mode:
Command String Returns Description
'A'55HEX Return to command mode
elsenothingignored

The raw data string returned from the glove consists of 9 bytes in the following format:
sy f1 f2 f3 f4 f5 tp tr cs

where:
sy is the leading header value and should always be 80 hex.

f1-f5 are the 8 bit (0-255) raw flex values for each of the five fingers.
For the right hand f1 corresponds to the thumb, f2 the index finger ...f5 the pinky finger. For the left hand the ordering is reversed with f5 the thumb, down to f1 the pinky.

tp is the 8 bit raw pitch value of the hand. The pitch sensor can measure pitch from -60 to +60 degrees and returns a value of 128 when it's centered at pitch = 0.

tr is the raw roll value of the hand. The roll sensor can measure roll from -60 to +60 degrees and returns a value of 128 when it's centered at roll = 0.

cs is the checksum value used to check the correctness of the other values. It should be equal to the xored result of f1 to f5 and tp and tr.

Sometimes the serial port may already have data in it's buffer when the glove is initialized and so the first few readings could be in error. This can be detected by using the checksum value. If the checksum value isn't equal to the xored result of f1 to f5 and tp and tr then the data bytes are not being read from the correct starting value. If this is the case more characters can be read until the checksum value is correct. The function GRreadHandData uses this approach to always ensure that the data values it returns back are correct. GRreadHandData returns back an array of nine integers, one for each of the raw data bytes. It is slightly slower than the GRreadPort function, but it always returns back the correct data string.

Once the user has finished accessing the 5th Glove the serial port can be closed by using GRportClose.

3.1 Device Driver Sample Code

The following partial listing taken from DDRiver.c shows how the device driver functions can be used in a simple application. DDriver.c simply opens the serial port, reads raw data from the glove and prints it to the screen. Complete code and a makefile is in the demo directory.

The serial port first needs to be opened using GRopenPort, and then the glove initialized:

  #include "GR.h"
  FILE *file_port;
  char Port_name[20],buff[20];
 
  int iterations;

  /* query the port name */
  printf("Input the port that the glove is connected to : ");
  scanf("%s",&Port_name);
  printf(" Input how many raw data values wanted : ");
  scanf("%d",&iterations);

  /* open the serial port */
  file_port = GRopenPort(Port_name);

  /* initialize the glove */
  GRwritePort("A",file_port);

  if(GRreadPort(file_port,1,buff)!=GR_ERROR)
	printf("\n 5DT glove opened on port %s\n",Port_name);
  else {
	printf("\n 5DT glove couldn't by opened on port %s - Quitting ..\n",Port_name);
 
  return(GR_ERROR);
 }

Data can be read in three ways; first by setting the glove into report mode and polling:

  int i;
  char buff[9],c;

  printf("\n First I'm going to let you Poll the glove for data \n");
  printf("  - hit 'q' to quit, (return) for data \n\n");
  c = getchar();

  /* setting it into report mode */
  while(c!='q'){
	GRwritePort("C",file_port);
	GRreadPort(file_port,9,buff);
	printf(" read : ");
	
	for(j=0;j<9;j++)
		printf(" %d ",(int)buff[j]);
		
	c = getchar();
  }

Note that in Report mode a character needs to be written to the port each time raw data is polled for. GRreadPort returns a array of characters which may need to be converted into integers for later use.

Data can also be read by setting the glove into continuous mode: int i,j;

  char buff[9];

  /* now set the glove into continuous mode*/
  printf("\n\n Now I'm going to read %d values in Continuous Mode \n",iterations);
  printf("  - hit (return) for data \n");
  c = getchar();
 
  /* setting it into continuous mode */
  GRwritePort("D",file_port);

  /* read values explicitly */
  for(i=0;i<iterations;i++){
	GRreadPort(file_port,9,buff);
	printf("\n read : ");

  	for(j=0;j<8;j++)
		printf(" %d ",(int)buff[j]);
  }

Raw data is streamed back continuously from the glove until the 'A' character is sent to set the glove into Command mode.

Finally, GRreadHandData can be used in either continuous or report mode:

  int i,j,data[9];
  
  /* read data values */
  for(i=0;i<iterations;i++){
	/* going into report mode */
	GRwritePort("C",file_port);
	GRreadHandData(file_port,data);
  }
  
  printf("\n read : ");
  for(j=0;j<9;j++)
	printf(" %d ",data[j]);
  }

Note that GRreadHandData returns an array of integers, in contrast to GRreadPort.

When the application is finished the port needs to be closed:

GRclosePort(file_port);

When the DDriver application is running it produces the following output:

kodiak/demo> DDriver

GloveGRASP 5DT Device Driver Demo Code

Input the port that the glove is connected to : /dev/ttyd2
Input how many raw data values wanted : 10

5DT glove opened on port /dev/ttyd2

First I'm going to let you Poll the glove for data - hit 'q' to quit, (return) for data

read :  128    0    0    0    0    0  128  128    0
read :  128    0    0    0    0    0  128  128    0
read :  128   46   59   43   39   66  115  136  160
  ....
read :  128  178  255  255  247  238   95  140  120
read :  128  176  255  255  247  238   95  140  122q

Now I'm going to read 10 values in Continuous Mode - hit (return) for data

read :  128  1  0  0  0  6  -7.03   0.00  246
read :  128  1  0  0  0  7  -7.03   0.00  247
 ....
read :  128  1  0  0  0  6  -7.03   6.56  248
read :  128  2  0  0  0  6  -6.56   6.56  248

Finally, I'm going to use GRreadHandData for 10 totally correct values - hit (return) for data

GR_ERROR: GRreadHandData: Checksum error - Correcting

read :  128  1  0  0  0  6  114  142  251
read :  128  2  0  0  0  6  114  142  248
 ....
read :  128  2  0  0  0  6  113  142  251
read :  128  2  0  0  0  6  113  142  251

5DT glove closed


4 C++ Device Driver Class


The GloveGRASP C device driver code is encapsulated with some additional functions in the C++ class, GR5glove. This class is defined in the header file GR5glove.h and has the following methods:

Low Level Operations:
char * GR5glove::DefaultDevice()
int GR5glove::GRopenPort(char *Port_name)
int GR5glove::GRwritePort(char *test_str)
int GR5glove::GRreadPort(int num_bytes, char *buf )
int GR5glove::GRreadHandData(int *hand_data)
int GR5glove::GRclosePort( )

High Level Operations:
int GR5glove::Reset( )
int GR5glove::Read(GRDeviceData& fval)
int GR5glove::ReadCheck(GRDeviceData& fval)

The low level operations are equivalent to the C functions described in the previous section, except GR5glove::DefaultDevice() which returns a pointer to the string "/dev/ttyd2", the default port.

The high level methods GR5glove::ReadCheck, and GR5glove::Read, set the glove into Report mode and poll for values with and without using the check sum value respectively. GR5glove::Reset can be used to reset the glove into command mode.

GR5glove::DefaultDevice returns "/dev/ttyd2", while the other methods return GR_OK if they complete successfully or GR_ERROR if not.

4.1 Open Inventor Sample Code

An example of how to use the GR5glove class is shown in the IvDemo.cxx and GRivHand.cxx files. IvDemo.cxx is a simple Inventor application that displays a virtual hand model which responds to glove input. It is linked with the GRivHand.cxx code and GRivHand.h which defines the GRInventorHand class.

In developing a gesture based application there are several basic steps that must be followed, as shown in IvDemo.cxx. After initializing the graphics window, the glove serial port should be opened, the glove reset and calibrated.

  //initialize inventor - and make a scene containing a hand
  Widget myWindow = SoXt::init(argv[0]);
  if(myWindow==NULL) exit(1);
  SoSeparator *root = new SoSeparator;
  
  //connect the glove, reset and calibrate
  hand->ConnectGlove();
  hand->InitGlove();
  hand->CallibrateGlove();

Glove calibration involves finding the maximum and minimum joint bend values of each finger. The minimum bend values are measured by getting the user to make a flat hand gesture and averaging raw data over several readings. Similarly the maximum values can be found by getting the user to make a fist and averaging over several values. Care must be taken to calibrate the thumb which can assume a wide range of different positions with similar bend values. The GRivHand.cxx file contains simple calibration code for the four fingers only:

  // calibrate the hand
  int GRInventorHand::CalibrateGlove(){
  	int i,j;
	char c;

 	//check to make sure the glove is connected
 	if(glove_connected){
 		//read the minimum bend values 
 		// - user should have flat hand
 		cout<<"Ready to initialize min values (y/n)";
 		cin>>c;
 		
 		for(i=0;i<10;i++){
 			ReadData(); 
 			for(j=0;j<NUM_FINGERS;j++)
 				min_data[j] = min_data[j]+rawdata[j+1];
 		}
		cout<<endl;
		
		//read the maximum bend values 
		// - user should have a fist
		cout<<"ready to initialize max values(y/n)";
		cin>>c;
		
		for(i=0;i<10;i++){
			ReadData(); 
			for(j=0;j<NUM_FINGERS;j++)
				max_data[j] = max_data[j]+rawdata[j+1];
		}

		//average the data
		for(i=0;i<NUM_FINGERS;i++){
			in_data[i]=min_data[i]/10;
			max_data[i]=max_data[i]/10;
		}
		return(GR_OK);
	}
	else{
		//the glove mustn't be connected
		cout<<"The glove isn't connected.."<<endl;
		return(GR_ERROR);
	}
  }

Once the glove has been calibrated, an Inventor timer sensor is set to ensure the glove data is read 30 times a second. This timer sensor uses the callback function readDataCallBack to read the current glove data and update the graphical model.

  //reading the data and update the model
  static void readDataCallBack(void *data, SoSensor *){
  	((GRInventorHand *)data)->ReadData();
  	((GRInventorHand *)data)->UpDateModel();
  }
  
  //start the data sampling
  SoRotation *myRotation = new SoRotation;
  SoTimerSensor *DataCollectionSensor = new SoTimerSensor(readDataCallBack,(void *)hand);
  DataCollectionSensor->setInterval(TICK_RATE);
  DataCollectionSensor->schedule();

The 5th Glove only returns a single bend value for each finger, so an approximation must be made to update the joint rotations for each of the three joints. In this case we assume that each joint bends a maximum of 90 degrees. The actual rotation value can be found by using the maximum and minimum finger bend values to scale the glove raw data to fall in the range 0-90 degrees.

This is done in the UpDateModel function from GRivHand.cxx:

  int GRInventorHand::UpDateModel(){
	static float angle[NUM_FINGERS];
	
	//Look for rotation types
	cout<<" Rotating : ";
	
	for(int i=0;i<NUM_FINGERS;i++){
		angle[i] = -M_PI*(rawdata[i+1]-min_data[i])/(2*(max_data[i]-min_data[i]));

		//rotate object
		myRotation[i]->rotation.setValue(xaxis,angle[i]);
		cout<<" "<<angle[i];
	}
	cout<<endl;
	
	return(GR_OK);
  }

myRotation[NUM_FINGERS] is a global array containing the rotation about the x axis of each finger. We assume all the joints in a finger are rotated the same, so the myRotation value is applied at each joint.

The final step is to add a hand model to the scene, open the examiner window and begin the simulation loop:

  //add the hand model to the scene
  root->ref();
  root->addChild(hand->CreateHand());
  
  //Set up viewer
  SoXtExaminerViewer *myViewer = new SoXtExaminerViewer(myWindow);
  myViewer->setSceneGraph(root);
  myViewer->setTitle("Hand View");
  myViewer->show();
  SoXt::show(myWindow);
  SoXt::mainLoop();

The CreateHand method from GRivHand.cxx is used to create the geometry for a simple virtual hand from Inventor box and cylinder primitives. An improvement would to allow the user to specify more complicated geometry files that could be read in by the application.

IvDemo is designed to work with a right-handed glove; for a left-handed glove the rawdata indices will need to be changed since rawdata[1] is now the bend value of the left pinky, not the thumb.

When IvDemo is run the user will be prompted for connection and calibration information, when this is entered an Inventor examiner window containing a virtual hand model will appear. This is shown in figure 1.0 below. Moving the fingers of the glove glove will produce the corresponding motion in the virtual hand.

grof@kodiak/demo> IvDemo

IvDemo - 5DT Glove Inventor Demo

Input the port that the glove is connected to : /dev/ttyd2
Ready to initialise min values (y/n)y
Ready to initialise max values(y/n)y


Figure 2.0 IvDemo Examiner window.


5 Networking Functions


GloveGRASP provides TCP/IP networking functions that can be used to build client/server applications. This allows users to use one machine for gesture recognition and run their application on another.

The functions are defined in GRNet.h and are:

GRsocket *GRnetOpen(char *hostname, int port,int buffersize,char *mode, char *buffer);
char *GRnetRead(GRsocket *g);
void GRnetWrite(GRsocket *g, char *buffer);
void GRnetClose(GRsocket *g);
void GRsendNetData(GRsocket *s,char *message);
char *GRreadNetData(GRsocket *s);

In order for the user to run a network application the TCP/IP socket must be opened first by using the GRnetOpen function. Sockets can be opened in either read or write mode according to whether the user wants to retrieve data from them or send data to them. Once a socket is opened data can be written to it using GRnetWrite or GRsendNetData and read from it using GRnetRead or GRreadNetData. The connection can be closed by using GRnetClose. The maximum length of a string that can be read from or written to a socket is defined by the variable GR_SOC_SIZE, set in GRNet.h.

5.1 Networking Example

An example of how these functions can be used is in the files GestureServer.c and GestureClient.c, both in the demo directory. GestureServer.c illustrates how to write a simple gesture server while Client.c contains sample client side code. GestureServer reads raw data from the glove and sends it across the network to GestureClient.

5.1.1 Server Code

In order to write gesture server code there are only four steps that need to be taken:

First, the network connection should be opened in write mode:

  GRsocket *soc;
  char readbuf[2],address[256],Port_name[20],buff[20];
  int port;
  FILE *file_port;
  
  /* read the network address and open the connection */
  printf(" Remote Client Network Address-> ");
  scanf("%s",address);
  printf(" Remote Client Port Number ->");
  scanf("%d",&port);
  soc=GRnetOpen(address,port,GR_SOC_SIZE,"w",NULL);<
  if(!soc){
  	printf("Couldn't open the network connection\n");
  	return(GR_ERROR);
  }

Second, connect to the glove serial port:

  /* now we need to open the glove serial port */
  /* query the port name */
  printf("\n Input the port that the glove is connected to -> ");
  scanf("%s",&Port_name);
  
  /* open the serial port */
  file_port = GRopenPort(Port_name);
  if(!file_port){
  	printf("Couldn't open the serial port \n");
  	return(GR_ERROR);
  }
  
  /* initialize the glove and check if port is opened OK */
  GRwritePort("A",file_port);
  if(GRreadPort(file_port,1,buff) == GR_OK)
  	printf("\n 5DT glove opened on port %s\n",Port_name);
  else {
  	printf(" The port %s couldn't be opened - quitting \n",Port_name);
  	return(GR_ERROR);
  }

Then send data to the socket connection, using the user defined readandsendData function:

  int readandsendData(FILE *file_port,GRsocket *soc){<
	int data[9];
	char sendbuff[GR_SOC_SIZE];
	
	/* going into report mode */
	GRwritePort("C",file_port);
	GRreadHandData(file_port,data);
	
	sprintf(sendbuff,"h:%d f0:%d f1:%d f2:%d f3:%d f4:%d tp:%d tr:%d cd:%d \n",
		data[0],data[1],data[2],data[3],data[4],data[5],data[6],data[7],data[8]);
	printf("   Data Sent : %s",sendbuff);


	/* Send the data */
	if(!GRsendNetData(soc, sendbuff)){
		printf("Error sending the data string \n");
		return(GR_ERROR);
	}
	
	return(GR_OK);
  }

readandsendData is called from the main module:

  /* main loop - loop until sent a 'q'*/
  while (strcmp(readbuf, "q")!=0){
  	printf("\n Data to send (q=end) (c=continuous)(ret = data) -> ");
	gets(readbuf); /* read input string */
	
	/* going into report mode and sending the data */
	readandsendData(file_port,soc);
	if(strcmp(readbuf, "c")==0){/* send 50 values continuously */
 		for(i=0;i<50;i++)
 			readandsendData(file_port,soc);<
 	}
  }

Finally, close the network connection:

  /* send quit message to client */
  GRsendNetData(soc, "quit");
  /* close the network connection */
  GRnetClose(soc);
  return(GR_OK);

5.1.2 Client Code

On the client side there are three steps that need to be made:

First, the client connection should be opened in read mode:

  int port;
  char address[32];
  GRsocket *soc;

  printf("\n GloveGRASP Client Demo Code \n\n");
  printf(" Client Network Address (localhost)-> ");
  scanf("%s",&address);
  printf(" Client Port Number (1027) ->");
  scanf("%d",&port);
  
  soc = GRnetOpen(address,port,SIZE,"r",NULL);
  if(!soc){
  	printf("ERROR: couldn't open socket for reading \n");
	exit(1);
  }

Next, data should be read from the port until a "quit" message is received:

  char oldtmp[GR_SOC_SIZE] = "nil";
  char tmp[GR_SOC_SIZE] = "nil";

  void read_network(GRsocket *soc){
  	/* read data from the socket */
  	strcpy(tmp,GRreadNetData(soc));
  	/* check to see if it is new - print it out if it is*/
	if (strcmp(tmp,oldtmp)!=0){ 
		printf(" Received message -> %s \n", tmp);
	}
	strcpy(oldtmp, tmp);
  } /** read_network **/ 

  while((strcmp(tmp,"quit")!=0)){
	read_network(soc);
  }

Finally, closing the socket connect when the application is finished:

GRnetClose(soc);

When GestureServer and GestureClient are run, the server remote machine address and port numbers must be the same as the client local machine address and port number. To run GestureServer simply type GestureServer and enter the remote SGI machine name and the port you want to connect to:

kodiak/GloveGRASP/demo>GestureServer

GloveGRASP Server Demo Code

Remote Client Network Address -> Nanook
Remote Client Port Number -> 4500

Input the port that the glove is connected to -> /dev/ttyd2
5DT glove opened on port /dev/ttyd2

Data to send (q=end) (c=continuous) (ret= data) ->
Data Sent : h:128 f0:72 f1:0 f2:0 f3:0 f4:0 tp:149 tr:240 cd:44
Data to send (q=end) (c=continuous) (ret= data) ->
Data Sent : h:128 f0:73 f1:0 f2:0 f3:0 f4:0 tp:145 tr:242 cd:42
Data to send (q=end) (c=continuous) (ret= data) ->
Data Sent : h:128 f0:75 f1:0 f2:0 f3:0 f4:0 tp:130 tr:242 cd:59
Data to send (q=end) (c=continuous) (ret= data) ->q

At the same time GestureClient should be run on the client SGI machine:

nanook/GloveGRASP/demo>GestureServer

GloveGRASP Client Demo Code

Client Network Address -> nanook
Client Port Number -> 4500

Received message ->
Received message ->h:128 f0:72 f1:0 f2:0 f3:0 f4:0 tp:149 tr:240 cd:44
Received message ->h:128 f0:73 f1:0 f2:0 f3:0 f4:0 tp:145 tr:242 cd:42
Received message ->h:128 f0:75 f1:0 f2:0 f3:0 f4:0 tp:130 tr:242 cd:59
Received message ->quit
Client Quiting..


6 Gesture Input


The GloveGRASP library contains functions for accurate gesture recognition based on template matching. Gestural input involves two phases; creating a user-specific template file containing sample gestures, and using this template files for real-time gesture recognition within an application.

6.1 Gesture Training

Gesture training is supported by three functions, defined in the GRTrainRecog.h file:
void FingerCalibration(flex_slope, flex_min,glove,num_of_test, finger_threshold);
GRMultiContext * GestureTraining(GRMultiContext* m_context_ptr, GRInt5& flex_min,GRFloat5& flex_slope,bool which_hand,int num_to_train)
void SaveTrainingData(GRMultiContext * m_context_ptr)

GRMultiContext is used to create the template structure from sample gestural input, while GRSaveTrainingData saves the training data to an ASCII file for later use by the recognition functions. FingerCalibration calculates the scaling parameter for scaling the raw glove data, the minimum bend for each finger, and the gesture recognition threshold level.

GloveGRASP creates the template files from sample gestures input by the user. The user forms the desired gesture a number of times and the GestureTraining function records statistical information about each of the finger bend values. The number of training samples for each gesture is determined by the parameter num_to_train, flex_min is an array of minimum bend values, flex_slope a scaling parameter, and which_hand a boolean specifying which hand is being trained for (RIGHT_HAND or LEFT_HAND). The trained template is stored as several linked lists of values and gesture names, the head of which is pointed to by m_context_ptr.

The values of flex_min and flex_slope are found by calling the FingerCalibration function. This function measures the minimum and maximum joint bends values from the glove as the user bends and relaxes their hand a number of times. The number of hand measurements taken during calibration is set by num_of_test while finger_threshold determines a minimum bend threshold that the joint values should exceed. If this threshold isn't exceeded then default values are used.

GloveGRASP is unique in that it supports context dependent gesture recognition. This means that a given gesture can be interpreted differently according to the current context. For example, when the user makes a fist with their virtual hand in space it may be interpreted as a 'Show Menu' gesture, but when that same gesture is made with the virtual hand inside an object it could be interpreted as a 'Pick' gesture, simply because the interaction contexts are different. The use of context dependent recognition means that a large number of gestural interpretations can be applied to a small number of gestures. Using a small number of well defined gestures improves the recognition rate and makes it easier for the user to remember the gesture set.

The GestureTraining function will prompt the user for both the current context name and the name of each gesture as it is to be added to the context. Gesture names may be any single word of up to 20 characters in length, while conext names may be up to eight characters in length.

6.1.1 Training Example

The sample program GestureTrain.cxx shows how the gesture training functions can be used in practice. First the glove serial port needs to be opened and the glove reset:

  // Open the port for 5glove
  printf("Opening port:  %s\n", filename);
  if ( (glove.GRopenPort(filename)) == GR_ERROR) {
  	cerr << "Couldn't open glove device, exit\n";
	exit (EXIT_FAILURE);    // couldn't open file
	}
  else if ( (glove.Reset()) == GR_ERROR ){
	cerr << "Couldn't reset glove device, exit\n";
	exit (EXIT_FAILURE);<
  }

Then the FingerCalibration function is called to calculate the minimum bend values and raw data scaling parameters:

  char c;

  cout <<"Hit return when for Calibration: "
  cin >>c;
  cout << endl;
  
  // Now calibrate the finger flex values
  FingerCalibration(flex_slope,flex_min,glove,num_of_test,finger_threshold);
  

The gesture training function, GestureTraining, can now be called:

  //Construct a class for the training data
  GRMultiContext * m_context_ptr = new GRMultiContext();
  
  //Call gesture training function
  m_context_ptr = GestureTraining(m_context_ptr,glove,flex_min, flex_slope, which_hand,num_to_train);

Finally, the trained data set can be written out to a file and the serial port closed:

  // Save the gesture training data into a file
  SaveTrainingData(m_context_ptr);
   
  // Printout the training results
  cout << m_context_ptr << "\n";

  // Clean up
  delete m_context_ptr;
  glove.GRclosePort ();

The GestureTrain executable uses command line arguments to set the user definable parameters; allowable parameters are:

GestureTrain [ device_name ] [ -l ] [ -t num ] [ -n num ] [ -f num ] [ -h ]

where:
device_name = The glove serial port device name, the default is /dev/ttyd2

-l = Left hand training, the default is Right hand training

-t num = num is the number of initial calibration samples, the default is 300

-n num = num is the number of training samples for each gesture; default 5

-f num = num is the minimum noticeable finger bending, the default is 35

-h = Help for command-line definitions

For example if we wanted to train some left handed gestures using a glove connected on the serial port /dev/ttyd3 and only taking two training samples for each gesture we would type the following: > xinyu@kodiak/GloveGRASP> train /dev/ttyd3 -l -n 2

The output produced would be:

53 grof@kodiak/demo> GestureTrain

GestureTrain - gesture training example

Opening port: /dev/ttyd2

Hit return when ready for calibration ...

Glove calibration starts, strech and relax your hand .....

Summary of the glove calibration

    Max Flex are:    255 255 255 255 255
    Min Flex are:      0 0 0 0 0
    =================================================

During glove calibration the user needs to repeatedly make alternating flat hand and fist gestures so the maximum and minimum bend for each flex sensor can be measured. Following calibration the user can begin training:

  Start Hand Shape Training.......
  Train each gesture for 2 times .....
  Enter the next context name:  collided
  Enter the next gesture name (e.g., one):one
  Form the gesture, hit any key after ready:
  Form the gesture, hit any key after ready:
  
  ------------------  SUMMARY --------------------
  MEAN:      (0)251  (1)203  (2)255  (3)102 (4)104
  TOLERANCE: (0)4  (1)3  (2)0  (3)10  (4)9
  MAXIMUM:   (0)255  (1)205  (2)255  (3)112 (4)113
  MINIMUM:   (0)249  (1)200  (2)255  (3)95  (4)99
  
  Add gesture to current context (default YES)? (y/n):y
  Enter the next gesture name (e.g., one): two
  
  Form the gesture, hit any key after ready:
  Form the gesture, hit any key after ready:

  -------------------  SUMMARY --------------------
     MEAN:      (0)239  (1)206  (2)98  (3)63  (4)103
     TOLERANCE: (0)5  (1)6  (2)13  (3)5  (4)9
     MAXIMUM:   (0)244  (1)212  (2)111  (3)63 (4)112
     MINIMUM:   (0)234  (1)200  (2)88  (3)58  (4)97
     
     Add gesture to current context (default YES)? (y/n):n

At the start of training users will be prompted to enter the new context name and the name of the first gesture they are training for. Users are prompted for the name of each gesture before the training samples for that gesture are taken. After all the training samples have been taken a summary is printed out, showing the mean, tolerance, maximum and minimum raw finger values measured. The numbers in the brackets show which finger the data is for. For example, the 'one' gesture above is a single finger point so fingers (3) and (4), the index and thumb, have low bend values while the other three fingers are high. The mean value shows the average finger bend while the tolerance is the maximum deviation of samples.

If the users want to add an additional context or quit the training program they just need to type 'n' at the prompt "Add gesture to current context (default YES)? ".

   Add gesture to current context (default YES)? (y/n):n
  Add new context (default YES)? (y/n):y
  Enter the next context name:  free
      Enter the next gesture name (e.g., one): fist
    
    Form the gesture, hit any key after ready:
    Form the gesture, hit any key after ready:

    -------------------  SUMMARY --------------------
        MEAN:      (0)221  (1)218  (2)255  (3)255 (4)124
        TOLERANCE: (0)5  (1)3  (2)0  (3)0  (4)8
        MAXIMUM:   (0)226  (1)221  (2)255  (3)255 (4)132
        MINIMUM:   (0)217  (1)216  (2)255  (3)255 (4)120
        
    Add gesture to current context (default YES)? (y/n):y
    Enter the next gesture name (e.g., one): flat
    
    Form the gesture, hit any key after ready:
    Form the gesture, hit any key after ready:

  -------------------  SUMMARY --------------------
    MEAN:      (0)1  (1)29  (2)135  (3)65 (4)33
    TOLERANCE: (0)2  (1)1  (2)1  (3)1  (4)8
    MAXIMUM:   (0)3  (1)30  (2)136  (3)66 (4)41
    MINIMUM:   (0)0  (1)28  (2)134  (3)64 (4)29
    
    Add gesture to current context (default YES)? (y/n):n
    Add new context (default YES)? (y/n):n
    Save the training data (default YES)? (y/n):y
    Enter the file name:  test.txt

After the training data has been saved out to a file, the contents of the file are printed to the screen. There is currently no way to go back and retrain gesture in a template file, or reload a template file to add additional gestures, but the files can be edited by hand if needed. The template files are simply a list of the contexts and gestures with their corresponding mean and tolerance values. Each context ends with a semi-colon except for the last which has two semi-colons.

Collide

      fist  1     148   3     255   0     255   0     243   1     226   0
     point  1     238   1       0   0     255   0     255   0     213   1
      flat  1     201   7       0   0       0   0       0   0       0   0 ;

Free

      grab  1     181  24     255   0     255   0     251   4     241   1
       fly  1     200   6       2   7     254   1     255   0     207   4
 twofinger  1     185   4       0   0       0   0     230  25     219  12 ;;

The file test.txt in the demo directory is a sample template file.

6.2 Gesture Recognition

Once a template file has been created it can be used by the GloveGRASP gesture recognition functions for gesture recognition. These recognition functions are defined in GRTrainRecog.h and include:

bool SingleGestureRecog(char * matched_gesture, bool which_hand, int finger_threshold, int recog_threshold, GRInt5& in_flex_min, GRFloat5& in_flex_slope, GRDeviceData & fval,
GRCandidate * candi_ptr, GROneContext *context_ptr );
GROneContext * ContextSwitch (char * in_buffer, GRMultiContext * m_context_ptr, GROneContext* context_ptr )
GRMultiContext * LoadTrainingData(GRMultiContext* m_context_ptr);

SingleGestureRecog is the function called to actually perform the gesture recognition. It returns TRUE if the recognition was successful, FALSE otherwise. After a successful recognition the pointer candi_ptr will point to the name of the gesture closest to the input raw data. In order to prevent incorrect recognition the values finger_threshold and recog_threshold can be set to the minimum change in raw data that must be exceeded before the gesture recognition will be attempted. The template matching uses the gestures defined in the currently active context. This current context is pointed at by context_ptr and can be changed by calling the ContextSwitch function. In this function context_ptr is the pointer to the current context, in_buffer the pointer to the new context name and m_context_ptr the pointer to the entire gesture template structure. LoadTrainingData is used to load in a gesture template structure.

There are two threshold levels set to minimize the chance of an incorrect recognition, or a recognition when no gesture was made at all. The first, finger_threshold is used to detect when individual fingers have changed significantly since the last reading. This means that even subtle changes in gesture involving only a single finger can be detected. The second threshold recog_threshold is used to detect changes in the entire gesture. The lower these threshold values are the more likely the gesture recognition will incorrectly identify gestures. However high threshold values will make the system less likely to recognize valid gestures when they do occur.

6.2.1 Gesture Recognition Sample Code

The sample file GestureRecog.cxx shows how these functions can be used in a simple gesture recognition application.

First, the glove port needs to be opened, the hand calibrated and a previously trained gesture template loaded in:

  if ( (glove.GRopenPort(filename)) == GR_ERROR) {
     cerr << "Couldn't open glove device, exit\n";
     exit (EXIT_FAILURE);    // couldn't open file<
  } 
  else if ( (glove.Reset()) == GR_ERROR ) {
     cerr << "Couldn't reset glove device, exit\n";
     exit (EXIT_FAILURE);
  }
  
  cout <<"Ready for Calibration(y/n): "
  cin >>c;
  cout << endl;
  FingerCalibration(flex_slope,flex_min, glove,num_of_test, finger_threshold);
 
  // Construct a class for the training data
  GRMultiContext * m_context_ptr = new GRMultiContext();

  // Read in the training data file and print it out
  m_context_ptr = LoadTrainingData(m_context_ptr);
  cout << "Training data file: " << m_context_ptr << '\n';
  

Once the training file has been read the gesture recognition function can be either be called continuously or at the user's request.

  // Repeating gesture recognition
  cout << "\n==========================================\n";
  cout << "Gesture recognition using default context:\n";
  assert ( (context_ptr = m_context_ptr->GetHead()));

  forever {// Loop to detect each gesture input
  	if(!is_continue) {
  	    cout << "--------------------------------\n";
  	    cout << "Form gesture, hit return after ready ....";
	    cin.getline(answer, answer_len);
	}
	
	// Call the gesture recognition function<
	glove.ReadCheck(fval);
	SingleGestureRecog( matched_gesture, which_hand, finger_threshold, recog_threshold,flex_min,flex_slope,fval,candi_ptr,context_ptr);
	printf("RECOGNIZED:   %s\n",matched_gesture);
  }

The user can also change the recognition context between gestures:

  if(!is_continue) { 
    cout << "Change context (default No)? (y/n): ";
    cin.getline(answer, answer_len);
    
    if ((strcmp(answer,"y")==0)||(strcmp(answer,"Y")==0)) {
              cout << "New context name:  ";
              cin >> new_context_name;
              cin.get(); 
              context_ptr = ContextSwitch(new_context_name,m_context_ptr,context_ptr);
           
              //Print out the new context
              GRprintConext(context_ptr);
     } // if(getchar())
  } // if(!is_continue) 

Command line options are used to set the parameters in the GestureRecog executable. These options are:

GestureRecog[ device_name ][ -l ][ -d ][ -f num ][ -r num ][-h ]

where:
device_name = The glove serial port device name, the default is /dev/ttyd2

-l = Left handed recognition; the default is Right handed recognition

-d = Discrete or Continuous recognition; the default is continuous.

-f num = num is the minimum finger threshold level; the default is 35

-r num = num is the minimum recognition threshold; the default is 300

-h = Help for command-line definitions

If the program is set to run with continuous recognition then it performs real-time recognition, continuously streaming the currently recognized gesture. Unfortunately with this setting it is impossible for the user to enter a change of context. However with discrete recognition the user can form a gesture and then hit a key to do the recognition or type in a new context to change the recognition context.

If we wanted to use the program to do right handed gesture recognition with a glove connected to serial port /dev/ttyd3 and setting the threshold for gesture recognition at 400, we would enter the following command line:

kodiak/demo> GestureRecog /dev/ttyd3 -d-r 400

The output produced would be:

GestureRecog - sample gesture recognition program

Opening port: /dev/ttyd3

Hit return when ready to calibrate the glove..
Glove calibration starts, stretch and relax your hand .....

Summary of the glove calibration
   Max Flex are:    233 255 255 255 255
   Min Flex are:      8 0 0 0 0

Enter the context file name:  test.txt
Training data file:   

Once the template file name has been entered in the gestures contained in that file will be printed to screen:

Collide

      fist  1     148   3     255   0     255   0     243   1     226   0
     point  1     238   1       0   0     255   0     255   0     213   1
      flat  1     201   7       0   0       0   0       0   0       0   0 ;

Free

      grab  1     181  24     255   0     255   0     251   4     241   1
       fly  1     200   6       2   7     254   1     255   0     207   4
 twofinger  1     185   4       0   0       0   0     230  25     219  12 ;;

Now the user can attempt discrete gesture recognition by forming the test gesture and hitting a key to initiate the recognition. The recognized gesture will be printed on screen.

===================================================
Gesture recognition using default context:
----------------------------------------------
Form gesture, hit return after ready ....
RECOGNIZED:  Non
Change context (default No)? (y/n): n
----------------------------------------------
Form gesture, hit return after ready ....
RECOGNIZED:   fist
Change context (default No)? (y/n): y

Using discrete recognition they can also change the recognition context, causing the new context and gesture set to be displayed.

New context name:  Collide
New context:
      grab  1   181  24   255   0   255  0   251   4   241   1
      fly  1   200   6     2   7   254   1   255   0   207   4
      finger  1   185   4     0   0     0  0   230  25   219  12 ;
----------------------------------------------
Form gesture, hit return after ready ....
RECOGNIZED:   grab
Change context (default No)? (y/n): n
----------------------------------------------
Form gesture, hit return after ready ....
RECOGNIZED:   fly
Change context (default No)? (y/n): 

91 grof@kodiak/demo>

6.3 Choosing Your Gesture Set

The set of gestures chosen largely determines the accuracy of the gesture recognition. Several factors should be considered when choosing gestures for your application, including the limitations of the hardware, the difficulty of forming certain gestures, and the most natural gestures for your particular application. For example in a modeling application a fist gesture would be a natural choice for picking objects up - it is easily detected by the hardware, a natural action and simple to remember.

For applications where gestures are going to be used as the primary command input, the developer should try and use context switching as much as possible to minimize the number of gestures that a user must remember. The average user will have difficulty in remembering more than six gestures, however these could be translated into dozens of commands by using different contexts.

It is also wise to use gestures that are markedly different from one another, for example gestures that bend different fingers such as a fist and pointing hand. Not only are these gestures easier to remember, but also almost impossible for the recognition system to misinterpret. Figure 3.0 shows some typical gestures that produce good recognition rates. Note how these getsures are as different as possible to ensure good recognition results.

Fist Flat Hand Horns
One Finger Point Two Finger Point Three Finger Point

Figure 3.0 Sample Gestures


7 GRASPmodel


7.1 Introduction

GRASPmodel is a more complex OpenGL-based modelling application which shows how gesture recognition can be used in practice. GRASPmodel allows the user to use two handed input to create simple 3D scenes from a set of four primitives (cone, box, sphere, cylinder). The right hand is used for gestural commands while the left is used for 3D cursor control and specification of command parameters. Two separate interaction contexts are defined so although there are almost a dozen available commands the user needs only remember six gestures.

7.2 Installation

All the relevant files for the GRASPmodel are found in the /demo/GRASPmodel directory, and should include:

GRASPmain.cxx GRASPdummy.cxx GRASPObjectList.cxx
GRASPfastrak.cxx GRASPlist.cxx trakit.c
seriallib.c demo.txt Makefile
README

and the header files:
aux.h GRASPdefines.h GRASPmain.h rasters.h
GRASPdummy.h seriallib.h trakit.h

GRASPmodel uses the OpenGL Programming Guide Auxilary Library for some of it's graphics functions. This library can be obtained for free from the sgigate.sgi.com ftp site. After ftping to the site type the following commands;

cd pub/opengl
binary
get opengl.tar.Z
bye

The file opengl.tar.Z is compressed, but the auxilary library can be extracted using the following commands:
uncompress opengl.tar
tar xf opengl.tar

Sample OpenGL programs and the auxilary library will then be created as subdirectories from the current directories. The makefile will need to be altered to point to the auxilary library directory before GRASPmodel can be compiled. The relevant line in the makefile is marked.

In addition to software, the GRASPmodel code requires a right handed 5DT glove and a Polhemus Fastrak with at least two sensors. If a Fastrak is not available then the tracking functions in GRASPmain.cxx will need to be modified to use another six degree of freedom tracker.

GRASPmodel is designed to be used with the fastrak tracker source mounted upside down, the axis aligned as shown below in figure 4.0. The first fastrak sensor should be mounted on the back of the right hand glove, and sensor two held in the left hand. It may be useful to mount the source beneath a flat surface such as a piece of wood to give the user a convenient working surface.

Figure 4.0 Polhemus Source Orientation for GRASPmodel

7.3 Training

GRASPmodel takes advantage of GloveGRASP's context sensitive gesture recognition. There are two contexts defined, "Collide" for when the users virtual hand is collided with an object in the scene, and "Free" for when it isn't. These contexts are dynamically changed according to where the user's hand is while the application is running.

There are a total of 11 gesture commands that can be issued across these contexts, from a set of six gestures. The table below shows the six gestures and their function in each of the contexts. Note how the same gesture is interpreted differently according to the interaction context.

Gesture
Context: Collide
Context: Free
Fist Pick Menu
One Finger PointCopy N/A
Two Finger PointScale Object Scale World
Three Finger PointColor Object Color Object
Flat handDrop Hide Menu
Horns Delete Object Delete Scene

These gestures are the same as shown in figure 3.0 previously. As can be seen they have been chosen to be as simple as possible and to minimize the recognition errors.

Before GRASPmodel can be run these 11 gestures should be trained using the Gesturetrain program. Other gestures can be substituted for those above, but the context and gesture names can only be different if the GRASPmain.cxx source code is modified.

The table below shows the gesture names for each of the gestures. The file demo.txt is an example of the type of training file that could be used with GRASPmodel.

Gesture
Context: Collide
Context: Free
Fist Pick Menu
One Finger PointCopy N/A
Two Finger PointScaleObj ScaleWorld
Three Finger PointColour Colour
Flat handDrop Drop
Horns ResetDelete

Gesture names used in GRASPmodel.

7.4 Running the Application

After gesture training, the GRASPmodel executable can be compiled by simply typing make GRASPmodel:
kodiak/GloveGRASP/demo/GRASPmodel>make GRASPmodel

When the code has been compiled, type GRASPmodel to run the application. Enter the serial ports the glove and fastrak are connected to and the desired display size.

kodiak/GRASPmodel> GRASPmodel

GRASPmodel - gesture recognition demo

Input the desired display size (width,height): 800 600
Input the fastrak port : /dev/ttyd3

Reseting fastrak
10..9..8..7..6..5..4..3..2..1..0..DONE
tracker started
Are you ready to boresight the fastrak (y/n)?y

Input the glove serial port : /dev/ttyd2
Glove opened on port /dev/ttyd2

Boresighting is used to zero the angles in the tracker. The user should hold their gloved hand flat and aligned with the tracker axis before boresighting. Once the serial ports have been opened and data read to make sure the devices are working correctly, glove calibration takes place and the gesture template file loaded.

Hit return to begin finger calibration
Glove calibration starts, stretch and relax your hand .....
. . . . . . . . . . . . . . . . . . . . .
Hit return to set the initial hand position..
Summary of the glove calibration

    Max Flex are:    255 255 255 255 250
    Min Flex are:     11 10 0 0 0

Enter the context file name: demo.txt

The application will now be running - users should see a graphical hand on the screen which moves and responds to their real hand motions. Figure 5.0 shows a screen snapshot of the application running. The title bar at the top of the screen shows the currently active context and the last recognized gesture.


Figure 5.0 GRASPmodel running

There are a number of interface elements that will appear in response to user gestures. The following sections explains each gestural command, and the corresponding element that should appear on-screen. In all cases the right (gloved) hand is used for inputing gestural commands while the left hand, holding the second tracker, is used for specifying the parameters for these commands. The virtual hand should follow the right hand motion at all times. For some interface elements a white 3D cursor will appear that will following the users left hand motion. However the 3D cursor movement is often constrained within a particular region or direction to make the interface more intuitive.

When the user's virtual hand collides with an object a wireframe bounding box the same color as the object will appear. The size of the box depends on the objects maximum dimension so it may often be several times larger than the object itself. It is collisions with other objects that changes the recognition context from "Free" to "Collide".

A very simple algorithm is used for collision detection so bounding boxes may appear even when the virtual hand is not in contact with an object. This is especially true for large objects.

7.5 GRASPmodel Interface Elements


Gesture:Fist

Figure 6.0 The Object Primitive Menu

Context: FreeSpace

When a fist is made in free space a menu of four object primitives will appear, as shown in the figure above. The 3D cursor also appears and is constrained to move along the x axis between the object primitives. Moving the cursor causes object selection; the object the cursor is currently colliding with is selected and a copy of it attacted to the user's virtual hand. The menu remains active until the Flat hand gesture is made.

Context: Collided

When the user collides with an object and makes a fist, the object is picked up and becomes attacted to the palm of the virtual hand. It with follow the user's hand motions until released it by making a Flat hand gesture.


Gesture: Flat hand

Context: Collided or FreeSpace

The Flat hand gesture is used to drop objects that are attached to the hand, or remove interface elements such as the object primitive menu from the screen. If an object is dropped onto another existing object it becomes a child of that object and is attacted to it. Picking the parent will cause both objects to be attached to the virtual hand.


Gesture: One finger point

Figure 7.0 The Object Copying Command

Context: Collided

When the hand is collided with an object a one fingered point makes a copy of the object and attaches it to the user's palm as shown in the figure above. If the object has children they will also be copied. This gesture has no effect when the hand is in free space.


Gesture: Two fingered point

Figure 8.0 The World Scaling Widget

Context: FreeSpace

When the user makes a two fingered point in free space a widget for scaling the entire scene will appear, as shown. The white 3D cursor follows the motions of the second fastrak sensor, and is constrained to move along the x axis below the two arrows. The cursor starts in space between the arrows. When it moves below the blue up arrow the scene will scale up, all the objects getting uniformly larger and the distances between them increasing proportionately. Moving the cusor below the down red arrow has the opposite effect. Scaling is stopped when the cursor is moved into the space between the arrows, or the widget deleted by making a flat hand gesture.

Figure 9.0 The Object Scaling Widget

Context: Collided

This will cause scaling to occur on the currently selected object. A cube shaped scaling widget will appear as shown in the figure. The widget has three different colored regions, black, red and blue. The innermost black region is where the white 3D cursor starts. While the cursor is in this region there is no change of object scale, but when the user moves the cursor into the adjacent red region the currently selected object will continuously change scale along one or more axis. Scaling can be stoped by moving the 3D cursor back to the black region or removing the widget by making the flat hand gesture. In order to scale the object along one particular axis the user needs only move the 3D cursor along that axis. Objects can be scaled along multiple axes by moving the cursor diagonally through space. If the 3D cursor is moved into the blue region, then continuous shrinking will occur instead. The 3D cursor is constrained to move within the scale widget, regardless of the user's real hand motions.


Gesture: Three fingered point

Context: FreeSpace

A color cube will appear with the 3D cursor located at the origin (0,0,0). As the user moves the 3D cursor through the cube it will change color to that of it's current location. X,Y,Z values in the colour cube are mapped onto R,G,B color values, so the origin (0,0,0) is black while the far corner (1,1,1) is white. Only three sides of the cube are shown, but colors within the cube volume are shown on the 3D cursor. If the user's virtual hand collides with another object when the colour cube is active the object will change colour to that currently selected by the 3D cursor. The colour cube can only be dismissed by making the flat hand gesture.

Context: Collided

This causes the same color cube to appear as above. However since the user is currently collided with an object, both that object and the 3D cursor will change colour as the cursor is moved through the color cube. As before,the colour cube can only be dismissed by making the flat hand gesture. Figure 10.0 shows the appearance of the color cube.

Figure 10.0 The Colour Cube Widget


Gesture: Horns

Context: FreeSpace

All of the current scene will be deleted and the interface reset into its original state. This gesture is deliberately difficult to make to prevent accidental deletions.

Context: Collided

The currently selected object will be deleted.

7.6 Extending the Application

GRASPmodel is a very simple application that can be extended in numerous ways. There are several fundamental functions missing, namely the ability to save or read in files, to scale the children of objects when the parents are scaled, and to change the users viewpoint. Other improvements could include the use of right and left handed gloves together for gestural input, more intuitive interface widgets and better collision detection and object selection. Six gestures may be too many for novice users to remember, and by adding extra contexts the size of the gesture set could be reduced while retaining the same command set. Despite these limitations, GRASPmodel shows how intuitive gestural input can be for 3D interactive graphics applications.

Full source code for GRASPmodel is included so the programmer can extend the application in any way they desire. This code and any applications based on it can be freely distributed with no licensing fees. The GRASPmodel files contain the following code:

GRASPmain.cxx

Contains the main module, sets up the virtual world and controls all user interactions. Once the devices and graphics have be initialized GRASPmain spends most of it's time in the display function, display(void). This function loops endlessly, reading the input devices, checking for collisions and context changes, recognizing gestures and updating the graphics. GRASPmain also contains all the functions called in response to gestural commands, such as changing object size and color or adding and deleting objects.

GRASPObjectList.cxx

Contains the object linked list class and method definitions. All the objects in the scene are add or removed from a single linked list. GRASPObjectList contains functions for adding and deleting list elements, and searching the list. There is also a smaller linked list used for the objects on the object primitive menu.

GRASPdummy.cxx

Contains the GObject and GHand class definitions. GObject is the base class for all the objects in the virtual world and GHand a subclass with methods for drawing a virtual hand, and glove initialisation functions. The cone, box, cylinder and sphere objects are also all sub-classes of the GObject class.

GRASPlist.cxx

Contains the color and geometry definitions for all the interface widgets.

rasters.h

Defines the raster bitmap for the font used in the title bar.

GRASPfastrak.cxx

Contains fastrak functions for initializing and shutting down the fastrak. GRASPfastrak uses functions from trakit.c and seriallib.c for actually reading the fastrak data.

trakit.c

A set of fastrak device driver functions for reading raw fastrak data. This includes fucntions for reading from multiple sensors, boresighting sensors and setting the fastrak into different operating modes.

seriallib.c

A simple serial port library that is used by trakit.c to access the serial port the fastrak is connected to.

Appendix



For product information and sales inquiries please email Sales@genreality.com
Please direct all technical and configuration questions to Support@genreality.com
Copyright (c) 1996 by General Reality Company.
Unpublished rights reserved under the copyright laws of the United States.
ALL RIGHTS RESERVED. All other trademarks are the property of their registered owners.