This commit is contained in:
@@ -1,155 +0,0 @@
|
||||
/*
|
||||
* wiigee - accelerometerbased gesture recognition
|
||||
* Copyright (C) 2007, 2008, 2009 Benjamin Poppinga
|
||||
*
|
||||
* Developed at University of Oldenburg
|
||||
* Contact: wiigee@benjaminpoppinga.de
|
||||
*
|
||||
* This file is part of wiigee.
|
||||
*
|
||||
* wiigee is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package org.wiigee.logic;
|
||||
|
||||
import java.util.Vector;
|
||||
import org.wiigee.event.AccelerationEvent;
|
||||
|
||||
/**
|
||||
* This class represents ONE movement trajectory in a
|
||||
* concrete instance.
|
||||
*
|
||||
* @author Benjamin 'BePo' Poppinga
|
||||
*/
|
||||
|
||||
public class Gesture implements Cloneable {
|
||||
|
||||
/** Min/MaxAcceleration setup manually? */
|
||||
private boolean minmaxmanual;
|
||||
private double minacc;
|
||||
private double maxacc;
|
||||
|
||||
/** The complete trajectory as WiimoteAccelerationEvents
|
||||
* as a vector. It's a vector because we don't want to
|
||||
* loose the chronology of the stored events.
|
||||
*/
|
||||
private Vector<AccelerationEvent> data;
|
||||
|
||||
/**
|
||||
* Create an empty Gesture.
|
||||
*/
|
||||
public Gesture() {
|
||||
this.data = new Vector<AccelerationEvent>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a deep copy of another Gesture object.
|
||||
*
|
||||
* @param original Another Gesture object
|
||||
*/
|
||||
public Gesture(Gesture original) {
|
||||
this.data = new Vector<AccelerationEvent>();
|
||||
Vector<AccelerationEvent> origin = original.getData();
|
||||
for (int i = 0; i < origin.size(); i++) {
|
||||
this.add((AccelerationEvent) origin.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a new acceleration event to this gesture.
|
||||
*
|
||||
* @param event The WiimoteAccelerationEvent to add.
|
||||
*/
|
||||
public void add(AccelerationEvent event) {
|
||||
this.data.add(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last acceleration added to this gesture.
|
||||
*
|
||||
* @return the last acceleration event added.
|
||||
*/
|
||||
public AccelerationEvent getLastData() {
|
||||
return (AccelerationEvent) this.data.get(this.data.size() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the whole chronological sequence of accelerations as
|
||||
* a vector.
|
||||
*
|
||||
* @return chronological sequence of accelerations.
|
||||
*/
|
||||
public Vector<AccelerationEvent> getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the first element of the acceleration queue of a gesture
|
||||
*/
|
||||
public void removeFirstData() {
|
||||
this.data.remove(0);
|
||||
}
|
||||
|
||||
public int getCountOfData() {
|
||||
return this.data.size();
|
||||
}
|
||||
|
||||
public void setMaxAndMinAcceleration(double max, double min) {
|
||||
this.maxacc = max;
|
||||
this.minacc = min;
|
||||
this.minmaxmanual = true;
|
||||
}
|
||||
|
||||
public double getMaxAcceleration() {
|
||||
if(!this.minmaxmanual) {
|
||||
double maxacc = Double.MIN_VALUE;
|
||||
for(int i=0; i<this.data.size(); i++) {
|
||||
if(Math.abs(this.data.get(i).getX()) > maxacc) {
|
||||
maxacc=Math.abs(this.data.get(i).getX());
|
||||
}
|
||||
if(Math.abs(this.data.get(i).getY()) > maxacc) {
|
||||
maxacc=Math.abs(this.data.get(i).getY());
|
||||
}
|
||||
if(Math.abs(this.data.get(i).getZ()) > maxacc) {
|
||||
maxacc=Math.abs(this.data.get(i).getZ());
|
||||
}
|
||||
}
|
||||
return maxacc;
|
||||
} else {
|
||||
return this.maxacc;
|
||||
}
|
||||
}
|
||||
|
||||
public double getMinAcceleration() {
|
||||
if(!this.minmaxmanual) {
|
||||
double minacc = Double.MAX_VALUE;
|
||||
for(int i=0; i<this.data.size(); i++) {
|
||||
if(Math.abs(this.data.get(i).getX()) < minacc) {
|
||||
minacc=Math.abs(this.data.get(i).getX());
|
||||
}
|
||||
if(Math.abs(this.data.get(i).getY()) < minacc) {
|
||||
minacc=Math.abs(this.data.get(i).getY());
|
||||
}
|
||||
if(Math.abs(this.data.get(i).getZ()) < minacc) {
|
||||
minacc=Math.abs(this.data.get(i).getZ());
|
||||
}
|
||||
}
|
||||
return minacc;
|
||||
} else {
|
||||
return this.minacc;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
/*
|
||||
* wiigee - accelerometerbased gesture recognition
|
||||
* Copyright (C) 2007, 2008, 2009 Benjamin Poppinga
|
||||
*
|
||||
* Developed at University of Oldenburg
|
||||
* Contact: wiigee@benjaminpoppinga.de
|
||||
*
|
||||
* This file is part of wiigee.
|
||||
*
|
||||
* wiigee is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package org.wiigee.logic;
|
||||
|
||||
import java.util.Vector;
|
||||
import org.wiigee.event.AccelerationEvent;
|
||||
import org.wiigee.util.Log;
|
||||
|
||||
/**
|
||||
* This Class units a Quantizer-Component and an Model-Component.
|
||||
* In this implementation a k-mean-algorithm for quantization and
|
||||
* a hidden markov model as instance for the model has been used.
|
||||
*
|
||||
* @author Benjamin 'BePo' Poppinga
|
||||
*/
|
||||
public class GestureModel {
|
||||
|
||||
/** The number of states the hidden markov model consists of */
|
||||
private int numStates;
|
||||
|
||||
/** The number of observations for the hmm and k-mean */
|
||||
private int numObservations;
|
||||
|
||||
/** The quantization component */
|
||||
private Quantizer quantizer;
|
||||
|
||||
/** The statistical model, hidden markov model */
|
||||
private HMM markovmodell;
|
||||
|
||||
/** The default probability of this gesturemodel,
|
||||
* needed for the bayes classifier */
|
||||
private double defaultprobability;
|
||||
|
||||
|
||||
/** Creates a Unit (Quantizer&Model).
|
||||
*
|
||||
* @param id
|
||||
* int representation of a gesture "name"/class.
|
||||
*/
|
||||
public GestureModel() {
|
||||
this.numStates=8; // n=8 states empirical value
|
||||
this.numObservations=14; // k=14 observations empirical value
|
||||
this.markovmodell = new HMM(numStates, numObservations); // init model
|
||||
this.quantizer = new Quantizer(numStates); // init quantizer
|
||||
}
|
||||
|
||||
/**
|
||||
* Trains the model to a set of motion-sequences, representing
|
||||
* different evaluations of a gesture
|
||||
*
|
||||
* @param trainsequence a vector of gestures
|
||||
*/
|
||||
public void train(Vector<Gesture> trainsequence) {
|
||||
// summarize all vectors from the different gestures in one
|
||||
// gesture called sum.
|
||||
double maxacc=0;
|
||||
double minacc=0;
|
||||
Gesture sum = new Gesture();
|
||||
|
||||
for(int i=0; i<trainsequence.size(); i++) {
|
||||
Vector<AccelerationEvent> t = trainsequence.elementAt(i).getData();
|
||||
|
||||
// add the max and min acceleration, we later get the average
|
||||
maxacc+=trainsequence.elementAt(i).getMaxAcceleration();
|
||||
minacc+=trainsequence.elementAt(i).getMinAcceleration();
|
||||
|
||||
// transfer every single accelerationevent of each gesture to
|
||||
// the new gesture sum
|
||||
for(int j=0; j<trainsequence.elementAt(i).getData().size(); j++) {
|
||||
sum.add(t.elementAt(j));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// get the average and set it to the sum gesture
|
||||
sum.setMaxAndMinAcceleration(maxacc/trainsequence.size(), minacc/trainsequence.size());
|
||||
|
||||
// train the centeroids of the quantizer with this master gesture sum.
|
||||
this.quantizer.trainCenteroids(sum);
|
||||
|
||||
// convert gesture vector to a sequence of discrete values
|
||||
Vector<int[]> seqs = new Vector<int[]>();
|
||||
for(int i=0; i<trainsequence.size(); i++) {
|
||||
seqs.add(this.quantizer.getObservationSequence(trainsequence.elementAt(i)));
|
||||
}
|
||||
|
||||
// train the markov model with this derived discrete sequences
|
||||
this.markovmodell.train(seqs);
|
||||
|
||||
// set the default probability for use with the bayes classifier
|
||||
this.setDefaultProbability(trainsequence);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the probability that a gesture matches to this
|
||||
* gesture model.
|
||||
*
|
||||
* @param gesture a gesture to test.
|
||||
* @return probability that the gesture belongs to this gesture
|
||||
* model.
|
||||
*/
|
||||
public double matches(Gesture gesture) {
|
||||
int[] sequence = quantizer.getObservationSequence(gesture);
|
||||
return this.markovmodell.getProbability(sequence);
|
||||
}
|
||||
|
||||
/**
|
||||
* For debug purposes or very technical interested people. :)
|
||||
*/
|
||||
public void printMap() {
|
||||
Log.write("Gesture Quantizer-Map:");
|
||||
this.quantizer.printMap();
|
||||
}
|
||||
|
||||
/***
|
||||
* For debug purposes or very technical interested people. :)
|
||||
* @return
|
||||
*/
|
||||
public void print() {
|
||||
Log.write(Log.DEBUG, "HMM-Print:", this);
|
||||
this.markovmodell.print();
|
||||
Log.write(Log.DEBUG, "Quantizer-Print:", this);
|
||||
this.quantizer.printMap();
|
||||
}
|
||||
|
||||
public int getNumStates() {
|
||||
return this.numStates;
|
||||
}
|
||||
|
||||
public int getNumObservations() {
|
||||
return this.numObservations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the model probability for bayes.
|
||||
*
|
||||
* @return
|
||||
* the model probability
|
||||
*/
|
||||
public double getDefaultProbability() {
|
||||
return this.defaultprobability;
|
||||
}
|
||||
|
||||
/**
|
||||
* Since the bayes classifier needs a model probability for
|
||||
* each model this has to be set once after training. As model
|
||||
* probability the average probability value has been choosen.
|
||||
*
|
||||
* TODO: try lowest or highest model probability as alternative
|
||||
*
|
||||
* @param defsequence the vector of training sequences.
|
||||
*/
|
||||
private void setDefaultProbability(Vector<Gesture> defsequence) {
|
||||
double prob=0;
|
||||
for(int i=0; i<defsequence.size(); i++) {
|
||||
prob+=this.matches(defsequence.elementAt(i));
|
||||
}
|
||||
|
||||
this.defaultprobability=(prob)/defsequence.size();
|
||||
}
|
||||
|
||||
public void setDefaultProbability(double prob) {
|
||||
this.defaultprobability = prob;
|
||||
Log.write("def-prob. set to = "+this.defaultprobability);
|
||||
}
|
||||
|
||||
public Quantizer getQuantizer() {
|
||||
return this.quantizer;
|
||||
}
|
||||
|
||||
public void setQuantizer(Quantizer q) {
|
||||
this.quantizer = q;
|
||||
}
|
||||
|
||||
public HMM getHMM() {
|
||||
return this.markovmodell;
|
||||
}
|
||||
|
||||
public void setHMM(HMM hmm) {
|
||||
this.markovmodell = hmm;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,314 +0,0 @@
|
||||
/*
|
||||
* wiigee - accelerometerbased gesture recognition
|
||||
* Copyright (C) 2007, 2008, 2009 Benjamin Poppinga
|
||||
*
|
||||
* Developed at University of Oldenburg
|
||||
* Contact: wiigee@benjaminpoppinga.de
|
||||
*
|
||||
* This file is part of wiigee.
|
||||
*
|
||||
* wiigee is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package org.wiigee.logic;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Vector;
|
||||
import org.wiigee.util.Log;
|
||||
|
||||
/**
|
||||
* This is a Hidden Markov Model implementation which internally provides
|
||||
* the basic algorithms for training and recognition (forward and backward
|
||||
* algorithm). Since a regular Hidden Markov Model doesn't provide a possibility
|
||||
* to train multiple sequences, this implementation has been optimized for this
|
||||
* purposes using some state-of-the-art technologies described in several papers.
|
||||
*
|
||||
* @author Benjamin 'BePo' Poppinga
|
||||
*
|
||||
*/
|
||||
|
||||
public class HMM {
|
||||
/** The number of states */
|
||||
protected int numStates;
|
||||
|
||||
/** The number of observations */
|
||||
protected int numObservations;
|
||||
|
||||
/** The initial probabilities for each state: p[state] */
|
||||
protected double pi[];
|
||||
|
||||
/** The state change probability to switch from state A to
|
||||
* state B: a[stateA][stateB] */
|
||||
protected double a[][];
|
||||
|
||||
/** The probability to emit symbol S in state A: b[stateA][symbolS] */
|
||||
protected double b[][];
|
||||
|
||||
/**
|
||||
* Initialize the Hidden Markov Model in a left-to-right version.
|
||||
*
|
||||
* @param numStates Number of states
|
||||
* @param numObservations Number of observations
|
||||
*/
|
||||
public HMM(int numStates, int numObservations) {
|
||||
this.numStates = numStates;
|
||||
this.numObservations = numObservations;
|
||||
pi = new double[numStates];
|
||||
a = new double[numStates][numStates];
|
||||
b = new double[numStates][numObservations];
|
||||
this.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the Hidden Markov Model to the initial left-to-right values.
|
||||
*
|
||||
*/
|
||||
private void reset() {
|
||||
int jumplimit = 2;
|
||||
|
||||
// set startup probability
|
||||
pi[0] = 1;
|
||||
for(int i=1; i<numStates; i++) {
|
||||
pi[i] = 0;
|
||||
}
|
||||
|
||||
// set state change probabilities in the left-to-right version
|
||||
// NOTE: i now that this is dirty and very static. :)
|
||||
for(int i=0; i<numStates; i++) {
|
||||
for(int j=0; j<numStates; j++) {
|
||||
if(i==numStates-1 && j==numStates-1) { // last row
|
||||
a[i][j] = 1.0;
|
||||
} else if(i==numStates-2 && j==numStates-2) { // next to last row
|
||||
a[i][j] = 0.5;
|
||||
} else if(i==numStates-2 && j==numStates-1) { // next to last row
|
||||
a[i][j] = 0.5;
|
||||
} else if(i<=j && i>j-jumplimit-1) {
|
||||
a[i][j] = 1.0/(jumplimit+1);
|
||||
} else {
|
||||
a[i][j] = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// emission probability
|
||||
for(int i=0; i<numStates; i++) {
|
||||
for(int j=0; j<numObservations; j++) {
|
||||
b[i][j] = 1.0/(double)numObservations;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trains the Hidden Markov Model with multiple sequences.
|
||||
* This method is normally not known to basic hidden markov
|
||||
* models, because they usually use the Baum-Welch-Algorithm.
|
||||
* This method is NOT the traditional Baum-Welch-Algorithm.
|
||||
*
|
||||
* If you want to know in detail how it works please consider
|
||||
* my Individuelles Projekt paper on the wiigee Homepage. Also
|
||||
* there exist some english literature on the world wide web.
|
||||
* Try to search for some papers by Rabiner or have a look at
|
||||
* Vesa-Matti Mäntylä - "Discrete Hidden Markov Models with
|
||||
* application to isolated user-dependent hand gesture recognition".
|
||||
*
|
||||
*/
|
||||
public void train(Vector<int[]> trainsequence) {
|
||||
|
||||
double[][] a_new = new double[a.length][a.length];
|
||||
double[][] b_new = new double[b.length][b[0].length];
|
||||
// re calculate state change probability a
|
||||
for(int i=0; i<a.length; i++) {
|
||||
for(int j=0; j<a[i].length; j++) {
|
||||
double zaehler=0;
|
||||
double nenner=0;
|
||||
|
||||
for(int k=0; k<trainsequence.size(); k++) {
|
||||
int[] sequence = trainsequence.elementAt(k);
|
||||
|
||||
double[][] fwd = this.forwardProc(sequence);
|
||||
double[][] bwd = this.backwardProc(sequence);
|
||||
double prob = this.getProbability(sequence);
|
||||
|
||||
double zaehler_innersum=0;
|
||||
double nenner_innersum=0;
|
||||
|
||||
|
||||
for(int t=0; t<sequence.length-1; t++) {
|
||||
zaehler_innersum+=fwd[i][t]*a[i][j]*b[j][sequence[t+1]]*bwd[j][t+1];
|
||||
nenner_innersum+=fwd[i][t]*bwd[i][t];
|
||||
}
|
||||
zaehler+=(1/prob)*zaehler_innersum;
|
||||
nenner+=(1/prob)*nenner_innersum;
|
||||
} // k
|
||||
|
||||
a_new[i][j] = zaehler/nenner;
|
||||
} // j
|
||||
} // i
|
||||
|
||||
// re calculate emission probability b
|
||||
for(int i=0; i<b.length; i++) { // zustaende
|
||||
for(int j=0; j<b[i].length; j++) { // symbole
|
||||
double zaehler=0;
|
||||
double nenner=0;
|
||||
|
||||
for(int k=0; k<trainsequence.size(); k++) {
|
||||
int[] sequence = trainsequence.elementAt(k);
|
||||
|
||||
double[][] fwd = this.forwardProc(sequence);
|
||||
double[][] bwd = this.backwardProc(sequence);
|
||||
double prob = this.getProbability(sequence);
|
||||
|
||||
double zaehler_innersum=0;
|
||||
double nenner_innersum=0;
|
||||
|
||||
|
||||
for(int t=0; t<sequence.length-1; t++) {
|
||||
if(sequence[t]==j) {
|
||||
zaehler_innersum+=fwd[i][t]*bwd[i][t];
|
||||
}
|
||||
nenner_innersum+=fwd[i][t]*bwd[i][t];
|
||||
}
|
||||
zaehler+=(1/prob)*zaehler_innersum;
|
||||
nenner+=(1/prob)*nenner_innersum;
|
||||
} // k
|
||||
|
||||
b_new[i][j] = zaehler/nenner;
|
||||
} // j
|
||||
} // i
|
||||
|
||||
this.a=a_new;
|
||||
this.b=b_new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Traditional Forward Algorithm.
|
||||
*
|
||||
* @param o the observationsequence O
|
||||
* @return Array[State][Time]
|
||||
*
|
||||
*/
|
||||
protected double[][] forwardProc(int[] o) {
|
||||
double[][] f = new double[numStates][o.length];
|
||||
for (int l = 0; l < f.length; l++) {
|
||||
f[l][0] = pi[l] * b[l][o[0]];
|
||||
}
|
||||
for (int i = 1; i < o.length; i++) {
|
||||
for (int k = 0; k < f.length; k++) {
|
||||
double sum = 0;
|
||||
for (int l = 0; l < numStates; l++) {
|
||||
sum += f[l][i-1] * a[l][k];
|
||||
}
|
||||
f[k][i] = sum * b[k][o[i]];
|
||||
}
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the probability that a observation sequence O belongs
|
||||
* to this Hidden Markov Model without using the bayes classifier.
|
||||
* Internally the well known forward algorithm is used.
|
||||
*
|
||||
* @param o observation sequence
|
||||
* @return probability that sequence o belongs to this hmm
|
||||
*/
|
||||
public double getProbability(int[] o) {
|
||||
double prob = 0.0;
|
||||
double[][] forward = this.forwardProc(o);
|
||||
// add probabilities
|
||||
for (int i = 0; i < forward.length; i++) { // for every state
|
||||
prob += forward[i][forward[i].length - 1];
|
||||
}
|
||||
return prob;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Backward algorithm.
|
||||
*
|
||||
* @param o observation sequence o
|
||||
* @return Array[State][Time]
|
||||
*/
|
||||
protected double[][] backwardProc(int[] o) {
|
||||
int T = o.length;
|
||||
double[][] bwd = new double[numStates][T];
|
||||
/* Basisfall */
|
||||
for (int i = 0; i < numStates; i++)
|
||||
bwd[i][T - 1] = 1;
|
||||
/* Induktion */
|
||||
for (int t = T - 2; t >= 0; t--) {
|
||||
for (int i = 0; i < numStates; i++) {
|
||||
bwd[i][t] = 0;
|
||||
for (int j = 0; j < numStates; j++)
|
||||
bwd[i][t] += (bwd[j][t + 1] * a[i][j] * b[j][o[t + 1]]);
|
||||
}
|
||||
}
|
||||
return bwd;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Prints everything about this model, including
|
||||
* all values. For debug purposes or if you want
|
||||
* to comprehend what happend to the model.
|
||||
*
|
||||
*/
|
||||
public void print() {
|
||||
DecimalFormat fmt = new DecimalFormat();
|
||||
fmt.setMinimumFractionDigits(5);
|
||||
fmt.setMaximumFractionDigits(5);
|
||||
for (int i = 0; i < numStates; i++)
|
||||
Log.write(Log.DEBUG, "pi(" + i + ") = " + fmt.format(pi[i]), this);
|
||||
Log.write(Log.DEBUG, "", this);
|
||||
for (int i = 0; i < numStates; i++) {
|
||||
for (int j = 0; j < numStates; j++)
|
||||
Log.write(Log.DEBUG, "a(" + i + "," + j + ") = "
|
||||
+ fmt.format(a[i][j]) + " ", this);
|
||||
Log.write(Log.DEBUG, "", this);
|
||||
}
|
||||
Log.write("");
|
||||
for (int i = 0; i < numStates; i++) {
|
||||
for (int k = 0; k < numObservations; k++)
|
||||
Log.write(Log.DEBUG, "b(" + i + "," + k + ") = "
|
||||
+ fmt.format(b[i][k]) + " ", this);
|
||||
Log.write(Log.DEBUG, "", this);
|
||||
}
|
||||
}
|
||||
|
||||
public double[] getPi() {
|
||||
return this.pi;
|
||||
}
|
||||
|
||||
public void setPi(double[] pi) {
|
||||
this.pi = pi;
|
||||
}
|
||||
|
||||
public double[][] getA() {
|
||||
return this.a;
|
||||
}
|
||||
|
||||
public void setA(double[][] a) {
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
public double[][] getB() {
|
||||
return this.b;
|
||||
}
|
||||
|
||||
public void setB(double[][] b) {
|
||||
this.b=b;
|
||||
}
|
||||
}
|
||||
@@ -1,449 +0,0 @@
|
||||
/*
|
||||
* wiigee - accelerometerbased gesture recognition
|
||||
* Copyright (C) 2007, 2008, 2009 Benjamin Poppinga
|
||||
*
|
||||
* Developed at University of Oldenburg
|
||||
* Contact: wiigee@benjaminpoppinga.de
|
||||
*
|
||||
* This file is part of wiigee.
|
||||
*
|
||||
* wiigee is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package org.wiigee.logic;
|
||||
import java.text.*;
|
||||
import java.util.Vector;
|
||||
import java.lang.Math;
|
||||
import org.wiigee.util.Log;
|
||||
|
||||
/**
|
||||
* This is a Hidden Markov Model implementation which internally provides
|
||||
* the basic algorithms for training and recognition (forward and backward
|
||||
* algorithm). Since a regular Hidden Markov Model doesn't provide a possibility
|
||||
* to train multiple sequences, this implementation has been optimized for this
|
||||
* purposes using some state-of-the-art technologies described in several papers.
|
||||
*
|
||||
* @author Benjamin 'BePo' Poppinga
|
||||
*
|
||||
*/
|
||||
|
||||
public class PreciseHMM {
|
||||
/** The number of states */
|
||||
private int numStates;
|
||||
|
||||
/** The number of observations */
|
||||
private int sigmaSize;
|
||||
|
||||
/** The initial probabilities for each state: p[state] */
|
||||
public double pi[];
|
||||
|
||||
/** The state change probability to switch from state A to
|
||||
* state B: a[stateA][stateB] */
|
||||
public double a[][];
|
||||
|
||||
/** The probability to emit symbol S in state A: b[stateA][symbolS] */
|
||||
public double b[][];
|
||||
|
||||
/**
|
||||
* Initialize the Hidden Markov Model in a left-to-right version.
|
||||
*
|
||||
* @param numStates Number of states
|
||||
* @param sigmaSize Number of observations
|
||||
*/
|
||||
public PreciseHMM(int numStates, int sigmaSize) {
|
||||
this.numStates = numStates;
|
||||
this.sigmaSize = sigmaSize;
|
||||
pi = new double[numStates];
|
||||
a = new double[numStates][numStates];
|
||||
b = new double[numStates][sigmaSize];
|
||||
this.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the Hidden Markov Model to the initial left-to-right values.
|
||||
*
|
||||
*/
|
||||
private void reset() {
|
||||
int jumplimit = 2;
|
||||
|
||||
// set startup probability
|
||||
pi[0] = 1.0;
|
||||
for(int i=1; i<numStates; i++) {
|
||||
pi[i] = 0;
|
||||
}
|
||||
|
||||
// set state change probabilities in the left-to-right version
|
||||
// NOTE: i now that this is dirty and very static. :)
|
||||
for(int i=0; i<numStates; i++) {
|
||||
for(int j=0; j<numStates; j++) {
|
||||
if(i==numStates-1 && j==numStates-1) { // last row
|
||||
a[i][j] = 1.0;
|
||||
} else if(i==numStates-2 && j==numStates-2) { // next to last row
|
||||
a[i][j] = 0.5;
|
||||
} else if(i==numStates-2 && j==numStates-1) { // next to last row
|
||||
a[i][j] = 0.5;
|
||||
} else if(i<=j && i>j-jumplimit-1) {
|
||||
a[i][j] = 1.0/(jumplimit+1);
|
||||
} else {
|
||||
a[i][j] = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// emission probability
|
||||
for(int i=0; i<numStates; i++) {
|
||||
for(int j=0; j<sigmaSize; j++) {
|
||||
b[i][j] = 1.0/(double)sigmaSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trains the Hidden Markov Model with multiple sequences.
|
||||
* This method is normally not known to basic hidden markov
|
||||
* models, because they usually use the Baum-Welch-Algorithm.
|
||||
* This method is NOT the traditional Baum-Welch-Algorithm.
|
||||
*
|
||||
* If you want to know in detail how it works please consider
|
||||
* my Individuelles Projekt paper on the wiigee Homepage. Also
|
||||
* there exist some english literature on the world wide web.
|
||||
* Try to search for some papers by Rabiner or have a look at
|
||||
* Vesa-Matti Mäntylä - "Discrete Hidden Markov Models with
|
||||
* application to isolated user-dependent hand gesture recognition".
|
||||
*
|
||||
*/
|
||||
public void train(Vector<int[]> trainsequence) {
|
||||
|
||||
double[][] a_new = new double[a.length][a.length];
|
||||
double[][] b_new = new double[b.length][b[0].length];
|
||||
// re calculate state change probability a
|
||||
for(int i=0; i<a.length; i++) {
|
||||
for(int j=0; j<a[i].length; j++) {
|
||||
double zaehler=0;
|
||||
double nenner=0;
|
||||
|
||||
for(int k=0; k<trainsequence.size(); k++) {
|
||||
//this.reset();
|
||||
int[] sequence = trainsequence.elementAt(k);
|
||||
|
||||
double[] sf = this.calculateScalingFactor(sequence);
|
||||
double[][] fwd = this.scaledForwardProc(sequence);
|
||||
double[][] bwd = this.scaledBackwardProc(sequence, sf);
|
||||
|
||||
double zaehler_innersum=0;
|
||||
double nenner_innersum=0;
|
||||
|
||||
for(int t=0; t<sequence.length-1; t++) {
|
||||
zaehler_innersum+=fwd[i][t]*a[i][j]*b[j][sequence[t+1]]*bwd[j][t+1]*sf[t+1];
|
||||
nenner_innersum+=fwd[i][t]*bwd[i][t];
|
||||
}
|
||||
zaehler+=zaehler_innersum;
|
||||
nenner+=nenner_innersum;
|
||||
} // k
|
||||
|
||||
a_new[i][j] = zaehler/nenner;
|
||||
} // j
|
||||
} // i
|
||||
|
||||
// re calculate emission probability b
|
||||
for(int i=0; i<b.length; i++) { // zustaende
|
||||
for(int j=0; j<b[i].length; j++) { // symbole
|
||||
double zaehler=0;
|
||||
double nenner=0;
|
||||
|
||||
for(int k=0; k<trainsequence.size(); k++) {
|
||||
//this.reset();
|
||||
int[] sequence = trainsequence.elementAt(k);
|
||||
|
||||
double[] sf = this.calculateScalingFactor(sequence);
|
||||
double[][] fwd = this.scaledForwardProc(sequence);
|
||||
double[][] bwd = this.scaledBackwardProc(sequence, sf);
|
||||
|
||||
double zaehler_innersum=0;
|
||||
double nenner_innersum=0;
|
||||
|
||||
for(int t=0; t<sequence.length-1; t++) {
|
||||
if(sequence[t]==j) {
|
||||
zaehler_innersum+=fwd[i][t]*bwd[i][t]*sf[t];
|
||||
}
|
||||
nenner_innersum+=fwd[i][t]*bwd[i][t]*sf[t];
|
||||
}
|
||||
zaehler+=zaehler_innersum;
|
||||
nenner+=nenner_innersum;
|
||||
} // k
|
||||
|
||||
b_new[i][j] = zaehler/nenner;
|
||||
} // j
|
||||
} // i
|
||||
|
||||
this.a=a_new;
|
||||
this.b=b_new;
|
||||
}
|
||||
|
||||
|
||||
private double[] calculateScalingFactor(int[] sequence) {
|
||||
// for all indexing: [state][time]
|
||||
double[][] fwd = this.forwardProc(sequence); // normal
|
||||
double[][] help = new double[fwd.length][fwd[0].length];
|
||||
double[][] scaled = new double[fwd.length][fwd[0].length];
|
||||
double[] sf = new double[sequence.length];
|
||||
|
||||
// ************** BASIS *************
|
||||
// Basis, fixed t=0
|
||||
// setup, because needed for further calculations
|
||||
for(int i=0; i<help.length; i++) {
|
||||
help[i][0] = fwd[i][0];
|
||||
}
|
||||
|
||||
// setup initial scaled array
|
||||
double sum0 = 0;
|
||||
for(int i=0; i<help.length; i++) {
|
||||
sum0+=help[i][0];
|
||||
}
|
||||
|
||||
for(int i=0; i<scaled.length; i++) {
|
||||
scaled[i][0] = help[i][0] / sum0;
|
||||
}
|
||||
|
||||
// calculate scaling factor
|
||||
sf[0] = 1/sum0;
|
||||
|
||||
// **************** INDUCTION ***************
|
||||
// end of fixed t = 0
|
||||
// starting with t>1 to sequence.length
|
||||
// induction, further calculations
|
||||
for(int t=1; t<sequence.length; t++) {
|
||||
// calculate help
|
||||
for(int i=0; i<help.length; i++) {
|
||||
for(int j=0; j<this.numStates; j++) {
|
||||
help[i][t]+=scaled[j][t-1]*a[j][i]*b[i][sequence[t]];
|
||||
}
|
||||
}
|
||||
|
||||
double sum = 0;
|
||||
for(int i=0; i<help.length; i++) {
|
||||
sum+=help[i][t];
|
||||
}
|
||||
|
||||
for(int i=0; i<scaled.length; i++) {
|
||||
scaled[i][t] = help[i][t] / sum;
|
||||
}
|
||||
|
||||
// calculate scaling factor
|
||||
sf[t] = 1 / sum;
|
||||
|
||||
} // t
|
||||
|
||||
return sf;
|
||||
} // calculateScalingFactor
|
||||
|
||||
/***
|
||||
* Returns the scaled Forward variable.
|
||||
* TODO: Maybe try out if the other precalculated method is faster.
|
||||
* @param sequence
|
||||
* @return
|
||||
*/
|
||||
private double[][] scaledForwardProc(int[] sequence) {
|
||||
double[][] fwd = this.forwardProc(sequence);
|
||||
double[][] out = new double[fwd.length][fwd[0].length];
|
||||
for(int i=0; i<fwd.length; i++) {
|
||||
for(int t=0; t<sequence.length; t++) {
|
||||
double sum = 0;
|
||||
for(int j=0; j<fwd.length; j++) {
|
||||
sum+=fwd[j][t];
|
||||
}
|
||||
out[i][t] = fwd[i][t] / sum;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private double[][] scaledBackwardProc(int[] sequence, double[] sf) {
|
||||
double[][] bwd = this.backwardProc(sequence);
|
||||
double[][] out = new double[bwd.length][bwd[0].length];
|
||||
for(int i=0; i<bwd.length; i++) {
|
||||
for(int t=0; t<sequence.length; t++) {
|
||||
out[i][t]=1;
|
||||
for(int r=t+1; r<sequence.length; r++) {
|
||||
out[i][t]*=sf[r]*bwd[i][t];
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the probability that a observation sequence O belongs
|
||||
* to this Hidden Markov Model without using the bayes classifier.
|
||||
* Internally the well known forward algorithm is used.
|
||||
*
|
||||
* @param o observation sequence
|
||||
* @return probability that sequence o belongs to this hmm
|
||||
*/
|
||||
public double getProbability(int[] o) {
|
||||
return scaledViterbi(o);
|
||||
//return sProbability(o);
|
||||
/*double prob = 0.0;
|
||||
double[][] forward = this.forwardProc(o);
|
||||
// add probabilities
|
||||
for (int i = 0; i < forward.length; i++) { // for every state
|
||||
prob += forward[i][forward[i].length - 1];
|
||||
}
|
||||
return prob;*/
|
||||
}
|
||||
|
||||
public double sProbability(int[] o) {
|
||||
double prod = 1.0;
|
||||
double[][] fwd = this.scaledForwardProc(o);
|
||||
for(int t=0; t<o.length; t++) {
|
||||
double sum = 0.0;
|
||||
for(int i=0; i<this.numStates; i++) {
|
||||
sum+=fwd[i][t];
|
||||
}
|
||||
sum = 1/sum;
|
||||
prod*=sum;
|
||||
}
|
||||
return 1/prod;
|
||||
}
|
||||
|
||||
public double scaledViterbi(int[] o) {
|
||||
double[][] phi = new double[this.numStates][o.length]; //phi[states][oseq]
|
||||
// init
|
||||
for(int i=0; i<this.numStates; i++) {
|
||||
phi[i][0] = Math.log(pi[i]) + Math.log(b[i][o[0]]);
|
||||
}
|
||||
// induction
|
||||
for(int t=1; t<o.length; t++) {
|
||||
for(int j=0; j<this.numStates; j++) {
|
||||
double max = Double.NEGATIVE_INFINITY;
|
||||
for(int i=0; i<this.numStates; i++) {
|
||||
double val = phi[i][t-1] + Math.log(this.a[i][j]);
|
||||
if(val>max) {
|
||||
max = val;
|
||||
}
|
||||
}
|
||||
|
||||
phi[j][t] = max + Math.log(this.b[j][o[t]]);
|
||||
}
|
||||
}
|
||||
// conclusion
|
||||
double lp = Double.NEGATIVE_INFINITY;
|
||||
for(int i=0; i<this.numStates; i++) {
|
||||
if(phi[i][o.length-1]>lp) {
|
||||
lp = phi[i][o.length-1];
|
||||
}
|
||||
}
|
||||
|
||||
//Log.write("log p = "+lp);
|
||||
//return lp;
|
||||
// we now have log10(p) calculated, transform to p.
|
||||
Log.write("prob = "+Math.exp(lp));
|
||||
return Math.exp(lp);
|
||||
//return Math.pow(10, lp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Traditional Forward Algorithm.
|
||||
*
|
||||
* @param o the observationsequence O
|
||||
* @return Array[State][Time]
|
||||
*
|
||||
*/
|
||||
private double[][] forwardProc(int[] o) {
|
||||
double[][] f = new double[numStates][o.length];
|
||||
for (int l = 0; l < f.length; l++) {
|
||||
f[l][0] = pi[l] * b[l][o[0]];
|
||||
}
|
||||
for (int i = 1; i < o.length; i++) {
|
||||
for (int k = 0; k < f.length; k++) {
|
||||
double sum = 0;
|
||||
for (int l = 0; l < numStates; l++) {
|
||||
sum += f[l][i-1] * a[l][k];
|
||||
}
|
||||
f[k][i] = sum * b[k][o[i]];
|
||||
}
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Backward algorithm.
|
||||
*
|
||||
* @param o observation sequence o
|
||||
* @return Array[State][Time]
|
||||
*/
|
||||
private double[][] backwardProc(int[] o) {
|
||||
int T = o.length;
|
||||
double[][] bwd = new double[numStates][T];
|
||||
/* Basisfall */
|
||||
for (int i = 0; i < numStates; i++)
|
||||
bwd[i][T - 1] = 1;
|
||||
/* Induktion */
|
||||
for (int t = T - 2; t >= 0; t--) {
|
||||
for (int i = 0; i < numStates; i++) {
|
||||
bwd[i][t] = 0;
|
||||
for (int j = 0; j < numStates; j++)
|
||||
bwd[i][t] += (bwd[j][t + 1] * a[i][j] * b[j][o[t + 1]]);
|
||||
}
|
||||
}
|
||||
return bwd;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Prints everything about this model, including
|
||||
* all values. For debug purposes or if you want
|
||||
* to comprehend what happend to the model.
|
||||
*
|
||||
*/
|
||||
public void print() {
|
||||
DecimalFormat fmt = new DecimalFormat();
|
||||
fmt.setMinimumFractionDigits(10);
|
||||
fmt.setMaximumFractionDigits(10);
|
||||
for (int i = 0; i < numStates; i++)
|
||||
Log.write("pi(" + i + ") = " + fmt.format(pi[i]));
|
||||
Log.write("");
|
||||
for (int i = 0; i < numStates; i++) {
|
||||
for (int j = 0; j < numStates; j++)
|
||||
Log.write("a(" + i + "," + j + ") = "
|
||||
+ fmt.format(a[i][j]) + " ");
|
||||
Log.write("");
|
||||
}
|
||||
Log.write("");
|
||||
for (int i = 0; i < numStates; i++) {
|
||||
for (int k = 0; k < sigmaSize; k++)
|
||||
Log.write("b(" + i + "," + k + ") = "
|
||||
+ fmt.format(b[i][k]) + " ");
|
||||
Log.write("");
|
||||
}
|
||||
}
|
||||
|
||||
public double[][] getA() {
|
||||
return this.a;
|
||||
}
|
||||
|
||||
public void setA(double[][] a) {
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
public double[][] getB() {
|
||||
return this.b;
|
||||
}
|
||||
|
||||
public void setB(double[][] b) {
|
||||
this.b=b;
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package org.wiigee.logic;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
import org.wiigee.event.AccelerationEvent;
|
||||
import org.wiigee.event.ButtonPressedEvent;
|
||||
import org.wiigee.event.ButtonReleasedEvent;
|
||||
import org.wiigee.event.AccelerationListener;
|
||||
import org.wiigee.event.ButtonListener;
|
||||
import org.wiigee.event.GestureEvent;
|
||||
import org.wiigee.event.GestureListener;
|
||||
import org.wiigee.event.MotionStartEvent;
|
||||
import org.wiigee.event.MotionStopEvent;
|
||||
import org.wiigee.util.Log;
|
||||
|
||||
public abstract class ProcessingUnit implements AccelerationListener, ButtonListener {
|
||||
|
||||
// Classifier
|
||||
protected Classifier classifier;
|
||||
|
||||
// Listener
|
||||
private Vector<GestureListener> gesturelistener = new Vector<GestureListener>();
|
||||
|
||||
public ProcessingUnit() {
|
||||
this.classifier = new Classifier();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an GestureListener to receive GestureEvents.
|
||||
*
|
||||
* @param g
|
||||
* Class which implements GestureListener interface.
|
||||
*/
|
||||
public void addGestureListener(GestureListener g) {
|
||||
this.gesturelistener.add(g);
|
||||
}
|
||||
|
||||
protected void fireGestureEvent(boolean valid, int id, double probability) {
|
||||
GestureEvent w = new GestureEvent(this, valid, id, probability);
|
||||
for (int i = 0; i < this.gesturelistener.size(); i++) {
|
||||
this.gesturelistener.get(i).gestureReceived(w);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void accelerationReceived(AccelerationEvent event);
|
||||
|
||||
public abstract void buttonPressReceived(ButtonPressedEvent event);
|
||||
|
||||
public abstract void buttonReleaseReceived(ButtonReleasedEvent event);
|
||||
|
||||
public abstract void motionStartReceived(MotionStartEvent event);
|
||||
|
||||
public abstract void motionStopReceived(MotionStopEvent event);
|
||||
|
||||
/**
|
||||
* Resets the complete gesturemodel. After reset no gesture is known
|
||||
* to the system.
|
||||
*/
|
||||
public void reset() {
|
||||
if (this.classifier.getCountOfGestures() > 0) {
|
||||
this.classifier.clear();
|
||||
Log.write("### Model reset ###");
|
||||
} else {
|
||||
Log.write("There doesn't exist any data to reset.");
|
||||
}
|
||||
}
|
||||
|
||||
// File IO
|
||||
public abstract void loadGesture(String filename);
|
||||
|
||||
public abstract void saveGesture(int id, String filename);
|
||||
}
|
||||
@@ -1,309 +0,0 @@
|
||||
/*
|
||||
* wiigee - accelerometerbased gesture recognition
|
||||
* Copyright (C) 2007, 2008, 2009 Benjamin Poppinga
|
||||
*
|
||||
* Developed at University of Oldenburg
|
||||
* Contact: wiigee@benjaminpoppinga.de
|
||||
*
|
||||
* This file is part of wiigee.
|
||||
*
|
||||
* wiigee is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package org.wiigee.logic;
|
||||
|
||||
import java.util.Vector;
|
||||
import org.wiigee.event.AccelerationEvent;
|
||||
import org.wiigee.util.Log;
|
||||
|
||||
/**
|
||||
* This class implements a quantization component. In this case a
|
||||
* k-mean-algorithm is used. In this case the initial values of the algorithm
|
||||
* are ordered as two intersected circles, representing an abstract globe with
|
||||
* k=14 elements. As a special feature the radius of this globe would be
|
||||
* calculated dynamically before the training of this component.
|
||||
*
|
||||
* @author Benjamin 'BePo' Poppinga
|
||||
*/
|
||||
public class Quantizer {
|
||||
|
||||
/** This is the initial radius of this model. */
|
||||
private double radius;
|
||||
|
||||
/** Number of states from the following Hidden Markov Model */
|
||||
private int numStates;
|
||||
|
||||
/** The representation of the so called Centeroids */
|
||||
private double[][] map;
|
||||
|
||||
/** True, if map is already trained. */
|
||||
private boolean maptrained;
|
||||
|
||||
/**
|
||||
* Initialize a empty quantizer. The states variable is necessary since some
|
||||
* algorithms need this value to calculate their values correctly.
|
||||
*
|
||||
* @param numStates
|
||||
* number of hidden markov model states
|
||||
*/
|
||||
public Quantizer(int numStates) {
|
||||
this.numStates = numStates;
|
||||
this.map = new double[14][3];
|
||||
this.maptrained = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trains this Quantizer with a specific gesture. This means that the
|
||||
* positions of the centeroids would adapt to this training gesture. In our
|
||||
* case this would happen with a summarized virtual gesture, containing all
|
||||
* the other gestures.
|
||||
*
|
||||
* @param gesture
|
||||
* the summarized virtual gesture
|
||||
*/
|
||||
public void trainCenteroids(Gesture gesture) {
|
||||
Vector<AccelerationEvent> data = gesture.getData();
|
||||
double pi = Math.PI;
|
||||
this.radius = (gesture.getMaxAcceleration() + gesture
|
||||
.getMinAcceleration()) / 2;
|
||||
Log.write("Using radius: " + this.radius);
|
||||
|
||||
// x , z , y
|
||||
if (!this.maptrained) {
|
||||
this.maptrained = true;
|
||||
this.map[0] = new double[] { this.radius, 0.0, 0.0 };
|
||||
this.map[1] = new double[] { Math.cos(pi / 4) * this.radius, 0.0,
|
||||
Math.sin(pi / 4) * this.radius };
|
||||
this.map[2] = new double[] { 0.0, 0.0, this.radius };
|
||||
this.map[3] = new double[] { Math.cos(pi * 3 / 4) * this.radius,
|
||||
0.0, Math.sin(pi * 3 / 4) * this.radius };
|
||||
this.map[4] = new double[] { -this.radius, 0.0, 0.0 };
|
||||
this.map[5] = new double[] { Math.cos(pi * 5 / 4) * this.radius,
|
||||
0.0, Math.sin(pi * 5 / 4) * this.radius };
|
||||
this.map[6] = new double[] { 0.0, 0.0, -this.radius };
|
||||
this.map[7] = new double[] { Math.cos(pi * 7 / 4) * this.radius,
|
||||
0.0, Math.sin(pi * 7 / 4) * this.radius };
|
||||
|
||||
this.map[8] = new double[] { 0.0, this.radius, 0.0 };
|
||||
this.map[9] = new double[] { 0.0, Math.cos(pi / 4) * this.radius,
|
||||
Math.sin(pi / 4) * this.radius };
|
||||
this.map[10] = new double[] { 0.0,
|
||||
Math.cos(pi * 3 / 4) * this.radius,
|
||||
Math.sin(pi * 3 / 4) * this.radius };
|
||||
this.map[11] = new double[] { 0.0, -this.radius, 0.0 };
|
||||
this.map[12] = new double[] { 0.0,
|
||||
Math.cos(pi * 5 / 4) * this.radius,
|
||||
Math.sin(pi * 5 / 4) * this.radius };
|
||||
this.map[13] = new double[] { 0.0,
|
||||
Math.cos(pi * 7 / 4) * this.radius,
|
||||
Math.sin(pi * 7 / 4) * this.radius };
|
||||
}
|
||||
|
||||
int[][] g_alt = new int[this.map.length][data.size()];
|
||||
int[][] g = new int[this.map.length][data.size()];
|
||||
|
||||
do {
|
||||
// Derive new Groups...
|
||||
g_alt = this.copyarray(g);
|
||||
g = this.deriveGroups(gesture);
|
||||
|
||||
// calculate new centeroids
|
||||
for (int i = 0; i < this.map.length; i++) {
|
||||
double zaehlerX = 0;
|
||||
double zaehlerY = 0;
|
||||
double zaehlerZ = 0;
|
||||
int nenner = 0;
|
||||
for (int j = 0; j < data.size(); j++) {
|
||||
if (g[i][j] == 1) {
|
||||
zaehlerX += data.elementAt(j).getX();
|
||||
zaehlerY += data.elementAt(j).getY();
|
||||
zaehlerZ += data.elementAt(j).getZ();
|
||||
nenner++;
|
||||
}
|
||||
}
|
||||
if (nenner > 1) { // nur wenn der nenner>0 oder >1??? ist muss
|
||||
// was
|
||||
// geaendert werden
|
||||
// Log.write("Setze neuen Centeroid!");
|
||||
this.map[i] = new double[] {(zaehlerX / (double) nenner),
|
||||
(zaehlerY / (double) nenner),
|
||||
(zaehlerZ / (double) nenner) };
|
||||
// Log.write("Centeroid: "+i+": "+newcenteroid[0]+":"+newcenteroid[1]);
|
||||
}
|
||||
} // new centeroids
|
||||
|
||||
} while (!equalarrays(g_alt, g));
|
||||
|
||||
// Debug: Printout groups
|
||||
/*
|
||||
* for (int i = 0; i < n; i++) { for (int j = 0; j < this.data.size();
|
||||
* j++) { Log.write(g[i][j] + "|"); } Log.write(""); }
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This methods looks up a Gesture to a group matrix, used by the
|
||||
* k-mean-algorithm (traincenteroid method) above.
|
||||
*
|
||||
* @param gesture
|
||||
* the gesture
|
||||
*/
|
||||
public int[][] deriveGroups(Gesture gesture) {
|
||||
Vector<AccelerationEvent> data = gesture.getData();
|
||||
int[][] groups = new int[this.map.length][data.size()];
|
||||
|
||||
// Calculate cartesian distance
|
||||
double[][] d = new double[this.map.length][data.size()];
|
||||
double[] curr = new double[3];
|
||||
double[] vector = new double[3];
|
||||
for (int i = 0; i < this.map.length; i++) { // zeilen
|
||||
double[] ref = this.map[i];
|
||||
for (int j = 0; j < data.size(); j++) { // spalten
|
||||
|
||||
curr[0] = data.elementAt(j).getX();
|
||||
curr[1] = data.elementAt(j).getY();
|
||||
curr[2] = data.elementAt(j).getZ();
|
||||
|
||||
vector[0] = ref[0] - curr[0];
|
||||
vector[1] = ref[1] - curr[1];
|
||||
vector[2] = ref[2] - curr[2];
|
||||
d[i][j] = Math.sqrt((vector[0] * vector[0])
|
||||
+ (vector[1] * vector[1]) + (vector[2] * vector[2]));
|
||||
// Log.write(d[i][j] + "|");
|
||||
}
|
||||
// Log.write("");
|
||||
}
|
||||
|
||||
// look, to which group a value belongs
|
||||
for (int j = 0; j < data.size(); j++) {
|
||||
double smallest = Double.MAX_VALUE;
|
||||
int row = 0;
|
||||
for (int i = 0; i < this.map.length; i++) {
|
||||
if (d[i][j] < smallest) {
|
||||
smallest = d[i][j];
|
||||
row = i;
|
||||
}
|
||||
groups[i][j] = 0;
|
||||
}
|
||||
groups[row][j] = 1; // guppe gesetzt
|
||||
}
|
||||
|
||||
// Debug output
|
||||
/*
|
||||
* for (int i = 0; i < groups.length; i++) { // zeilen for (int j = 0; j
|
||||
* < groups[i].length; j++) { Log.write(groups[i][j] + "|"); }
|
||||
* Log.write(""); }
|
||||
*/
|
||||
|
||||
return groups;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* With this method you can transform a gesture to a discrete symbol
|
||||
* sequence with values between 0 and granularity (number of observations).
|
||||
*
|
||||
* @param gesture
|
||||
* Gesture to get the observationsequence to.
|
||||
*/
|
||||
public int[] getObservationSequence(Gesture gesture) {
|
||||
int[][] groups = this.deriveGroups(gesture);
|
||||
Vector<Integer> sequence = new Vector<Integer>();
|
||||
|
||||
// Log.write("Visible symbol sequence: ");
|
||||
|
||||
for (int j = 0; j < groups[0].length; j++) { // spalten
|
||||
for (int i = 0; i < groups.length; i++) { // zeilen
|
||||
if (groups[i][j] == 1) {
|
||||
// Log.write(" "+ i);
|
||||
sequence.add(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// die sequenz darf nicht zu kurz sein... mindestens so lang
|
||||
// wie die anzahl der zustände. weil sonst die formeln nicht klappen.
|
||||
// english: this is very dirty! it have to be here because if not
|
||||
// too short sequences would cause an error. i've to think about a
|
||||
// better resolution than copying the old value a few time.
|
||||
while (sequence.size() < this.numStates) {
|
||||
sequence.add(sequence.elementAt(sequence.size() - 1));
|
||||
// Log.write(" "+sequence.elementAt(sequence.size()-1));
|
||||
}
|
||||
|
||||
// Log.write("");
|
||||
|
||||
int[] out = new int[sequence.size()];
|
||||
for (int i = 0; i < sequence.size(); i++) {
|
||||
out[i] = sequence.elementAt(i);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints out the current centeroids-map. Its for debug or technical
|
||||
* interests.
|
||||
*/
|
||||
public void printMap() {
|
||||
Log.write(Log.DEBUG, "Centeroids:", this);
|
||||
for (int i = 0; i < this.map.length; i++) {
|
||||
Log.write(Log.DEBUG, i + ". :" + this.map[i][0] + ":"
|
||||
+ this.map[i][1] + ":" + this.map[i][2], this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to deepcopy an array.
|
||||
*/
|
||||
private int[][] copyarray(int[][] alt) {
|
||||
int[][] neu = new int[alt.length][alt[0].length];
|
||||
for (int i = 0; i < alt.length; i++) {
|
||||
for (int j = 0; j < alt[i].length; j++) {
|
||||
neu[i][j] = alt[i][j];
|
||||
}
|
||||
}
|
||||
return neu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to look if the two arrays containing the same values.
|
||||
*/
|
||||
private boolean equalarrays(int[][] one, int[][] two) {
|
||||
for (int i = 0; i < one.length; i++) {
|
||||
for (int j = 0; j < one[i].length; j++) {
|
||||
if (!(one[i][j] == two[i][j])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public double getRadius() {
|
||||
return this.radius;
|
||||
}
|
||||
|
||||
public double[][] getHashMap() {
|
||||
return this.map;
|
||||
}
|
||||
|
||||
public void setUpManually(double[][] map, double radius) {
|
||||
this.map = map;
|
||||
this.radius = radius;
|
||||
}
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
/*
|
||||
* wiigee - accelerometerbased gesture recognition
|
||||
* Copyright (C) 2007, 2008, 2009 Benjamin Poppinga
|
||||
*
|
||||
* Developed at University of Oldenburg
|
||||
* Contact: wiigee@benjaminpoppinga.de
|
||||
*
|
||||
* This file is part of wiigee.
|
||||
*
|
||||
* wiigee is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
package org.wiigee.logic;
|
||||
|
||||
import java.util.Vector;
|
||||
import org.wiigee.event.*;
|
||||
import org.wiigee.util.Log;
|
||||
|
||||
/**
|
||||
* This class analyzes the AccelerationEvents emitted from a Wiimote
|
||||
* and further creates and manages the different models for each type
|
||||
* of gesture.
|
||||
*
|
||||
* @author Benjamin 'BePo' Poppinga
|
||||
*/
|
||||
public class TriggeredProcessingUnit extends ProcessingUnit {
|
||||
|
||||
// gesturespecific values
|
||||
private Gesture current; // current gesture
|
||||
|
||||
private Vector<Gesture> trainsequence;
|
||||
|
||||
// State variables
|
||||
private boolean learning, analyzing;
|
||||
|
||||
public TriggeredProcessingUnit() {
|
||||
super();
|
||||
this.learning=false;
|
||||
this.analyzing=false;
|
||||
this.current=new Gesture();
|
||||
this.trainsequence=new Vector<Gesture>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Since this class implements the WiimoteListener this procedure is
|
||||
* necessary. It contains the filtering (directional equivalence filter)
|
||||
* and adds the incoming data to the current motion, we want to train
|
||||
* or recognize.
|
||||
*
|
||||
* @param event The acceleration event which has to be processed by the
|
||||
* directional equivalence filter and which has to be added to the current
|
||||
* motion in recognition or training process.
|
||||
*/
|
||||
public void accelerationReceived(AccelerationEvent event) {
|
||||
if(this.learning || this.analyzing) {
|
||||
this.current.add(event); // add event to gesture
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is from the WiimoteListener interface. A button press
|
||||
* is used to control the data flow inside the structures.
|
||||
*
|
||||
*/
|
||||
public void buttonPressReceived(ButtonPressedEvent event) {
|
||||
this.handleStartEvent(event);
|
||||
}
|
||||
|
||||
public void buttonReleaseReceived(ButtonReleasedEvent event) {
|
||||
this.handleStopEvent(event);
|
||||
}
|
||||
|
||||
public void motionStartReceived(MotionStartEvent event) {
|
||||
// this.handleStartEvent(event);
|
||||
}
|
||||
|
||||
public void motionStopReceived(MotionStopEvent event) {
|
||||
// this.handleStopEvent(event);
|
||||
}
|
||||
|
||||
public void handleStartEvent(ActionStartEvent event) {
|
||||
|
||||
// TrainButton = record a gesture for learning
|
||||
if((!this.analyzing && !this.learning) &&
|
||||
event.isTrainInitEvent()) {
|
||||
Log.write("Training started!");
|
||||
this.learning=true;
|
||||
}
|
||||
|
||||
// RecognitionButton = record a gesture for recognition
|
||||
if((!this.analyzing && !this.learning) &&
|
||||
event.isRecognitionInitEvent()) {
|
||||
Log.write("Recognition started!");
|
||||
this.analyzing=true;
|
||||
}
|
||||
|
||||
// CloseGestureButton = starts the training of the model with multiple
|
||||
// recognized gestures, contained in trainsequence
|
||||
if((!this.analyzing && !this.learning) &&
|
||||
event.isCloseGestureInitEvent()) {
|
||||
|
||||
if(this.trainsequence.size()>0) {
|
||||
Log.write("Training the model with "+this.trainsequence.size()+" gestures...");
|
||||
this.learning=true;
|
||||
|
||||
GestureModel m = new GestureModel();
|
||||
m.train(this.trainsequence);
|
||||
// m.print(); // Prints model details after training
|
||||
this.classifier.addGestureModel(m);
|
||||
|
||||
this.trainsequence=new Vector<Gesture>();
|
||||
this.learning=false;
|
||||
} else {
|
||||
Log.write("There is nothing to do. Please record some gestures first.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void handleStopEvent(ActionStopEvent event) {
|
||||
if(this.learning) { // button release and state=learning, stops learning
|
||||
if(this.current.getCountOfData()>0) {
|
||||
Log.write("Finished recording (training)...");
|
||||
Log.write("Data: "+this.current.getCountOfData());
|
||||
Gesture gesture = new Gesture(this.current);
|
||||
this.trainsequence.add(gesture);
|
||||
this.current=new Gesture();
|
||||
this.learning=false;
|
||||
} else {
|
||||
Log.write("There is no data.");
|
||||
Log.write("Please train the gesture again.");
|
||||
this.learning=false; // ?
|
||||
}
|
||||
}
|
||||
|
||||
else if(this.analyzing) { // button release and state=analyzing, stops analyzing
|
||||
if(this.current.getCountOfData()>0) {
|
||||
Log.write("Finished recording (recognition)...");
|
||||
Log.write("Compare gesture with "+this.classifier.getCountOfGestures()+" other gestures.");
|
||||
Gesture gesture = new Gesture(this.current);
|
||||
|
||||
int recognized = this.classifier.classifyGesture(gesture);
|
||||
if(recognized!=-1) {
|
||||
double recogprob = this.classifier.getLastProbability();
|
||||
this.fireGestureEvent(true, recognized, recogprob);
|
||||
Log.write("######");
|
||||
Log.write("Gesture No. "+recognized+" recognized: "+recogprob);
|
||||
Log.write("######");
|
||||
} else {
|
||||
this.fireGestureEvent(false, 0, 0.0);
|
||||
Log.write("######");
|
||||
Log.write("No gesture recognized.");
|
||||
Log.write("######");
|
||||
}
|
||||
|
||||
this.current=new Gesture();
|
||||
this.analyzing=false;
|
||||
} else {
|
||||
Log.write("There is no data.");
|
||||
Log.write("Please recognize the gesture again.");
|
||||
this.analyzing=false; // ?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadGesture(String filename) {
|
||||
GestureModel g = org.wiigee.util.FileIO.readFromFile(filename);
|
||||
this.classifier.addGestureModel(g);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveGesture(int id, String filename) {
|
||||
org.wiigee.util.FileIO.writeToFile(this.classifier.getGestureModel(id), filename);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* To change this template, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package org.wiigee.logic;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author bepo
|
||||
*/
|
||||
public class XHMM extends HMM {
|
||||
|
||||
// the temporal values for scaling
|
||||
int[] currSequence;
|
||||
double[][] currForward;
|
||||
double[][] scaledForward;
|
||||
double[][] currBackward;
|
||||
double[][] scaledBackward;
|
||||
double[] currScaling;
|
||||
double[][] currHelper;
|
||||
|
||||
public XHMM(int numStates, int numObservations) {
|
||||
super(numStates, numObservations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void train(Vector<int[]> trainsequence) {
|
||||
|
||||
}
|
||||
|
||||
private double[][] getScaledForward(int[] sequence) {
|
||||
double[][] fwd = this.forwardProc(sequence);
|
||||
double[][] retVal = new double[fwd.length][fwd[0].length];
|
||||
for(int t=0; t<fwd.length; t++) {
|
||||
for(int i=0; i<fwd[0].length; i++) {
|
||||
|
||||
// build sum
|
||||
double sum = 0.0;
|
||||
for(int n=0; n<this.numStates; n++) {
|
||||
sum += fwd[t][n];
|
||||
}
|
||||
|
||||
retVal[t][i] = fwd[t][i] / sum;
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private double[][] getScaledBackward(int[] sequence) {
|
||||
double[][] fwd = this.forwardProc(sequence);
|
||||
double[][] bwd = this.backwardProc(sequence);
|
||||
double[][] retVal = new double[bwd.length][bwd[0].length];
|
||||
for(int t=0; t<bwd.length; t++) {
|
||||
for(int i=0; i<bwd[0].length; i++) {
|
||||
|
||||
// build sum
|
||||
double sum = 0.0;
|
||||
for(int n=0; n<this.numStates; n++) {
|
||||
sum += fwd[t][n];
|
||||
}
|
||||
|
||||
retVal[t][i] = bwd[t][i] / sum;
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private double getScalingDenominator(int t, int[] sequence) {
|
||||
double retVal = 0.0;
|
||||
double[][] fwd = this.forwardProc(sequence);
|
||||
double[][] sfwd = this.getScaledForward(sequence);
|
||||
double[][] helper = new double[sfwd.length][sfwd[0].length];
|
||||
|
||||
if(t==0) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user