ESP  0.1
The Example-based Sensor Predictions (ESP) system tries to bring machine learning to the maker community.
user_accelerometer_gestures.cpp

Gesture detection using accelerometers. See the associated tutorial.

#include <ESP.h>
GestureRecognitionPipeline pipeline;
TcpOStream oStream("localhost", 5204);
bool haveUprightData = false, haveUpsideDownData = false;
double range;
vector<double> zeroGs(3);
vector<double> processAccelerometerData(vector<double> input)
{
vector<double> result(3);
for (int i = 0; i < 3; i++) {
result[i] = (input[i] - zeroGs[i]) / range;
}
return result;
}
CalibrateResult calibrate(const MatrixDouble& data) {
// Run checks on newly collected sample.
// take average of X and Y acceleration as the zero G value
double zG = (data.getMean()[0] + data.getMean()[1]) / 2;
double oG = data.getMean()[2]; // use Z acceleration as one G value
double r = abs(oG - zG);
vector<double> stddev = data.getStdDev();
if (abs(data.getMean()[0] - data.getMean()[1]) / r > 0.1)
"X and Y axes differ by " + std::to_string(
abs(data.getMean()[0] - data.getMean()[1]) / r * 100) +
" percent. Check that accelerometer is flat.");
if (stddev[0] / r > 0.05 ||
stddev[1] / r > 0.05 ||
stddev[2] / r > 0.05)
"Accelerometer seemed to be moving; consider recollecting the "
"calibration sample.");
// If we have both samples, do the actual calibration.
for (int i = 0; i < 3; i++) {
zeroGs[i] =
(uprightData.getMean()[i] + upsideDownData.getMean()[i]) / 2;
}
// use half the difference between the two z-axis values (-1 and +1)
// as the range
range = (uprightData.getMean()[2] - upsideDownData.getMean()[2]) / 2;
}
return result;
}
CalibrateResult uprightDataCollected(const MatrixDouble& data)
{
uprightData = data;
return calibrate(data);
}
CalibrateResult upsideDownDataCollected(const MatrixDouble& data)
{
return calibrate(data);
}
{
if (in.getNumRows() < 10)
"Warning: Sample is short. Did you hold down the key for "
"the whole time you were making the gesture?");
VectorDouble stddev = in.getStdDev();
if (*max_element(stddev.begin(), stddev.end()) < 0.1)
"Warning: Gesture contains very little movement.");
}
int timeout = 500; // milliseconds
double null_rej = 0.4;
void updateVariability(double new_null_rej) {
pipeline.getClassifier()->setNullRejectionCoeff(new_null_rej);
pipeline.getClassifier()->recomputeNullRejectionThresholds();
}
void updateTimeout(int new_timeout) {
ClassLabelTimeoutFilter *filter =
dynamic_cast<ClassLabelTimeoutFilter *>
(pipeline.getPostProcessingModule(0));
assert(filter != nullptr);
filter->setTimeoutDuration(new_timeout);
}
void setup()
{
//useInputStream(stream);
calibrator.addCalibrateProcess("Upright",
"Rest accelerometer upright on flat surface.", uprightDataCollected);
calibrator.addCalibrateProcess("Upside Down",
"Rest accelerometer upside down on flat surface.", upsideDownDataCollected);
useCalibrator(calibrator);
DTW dtw(false, true, null_rej);
dtw.enableTrimTrainingData(true, 0.1, 75);
pipeline.setClassifier(dtw);
pipeline.addPostProcessingModule(ClassLabelTimeoutFilter(timeout));
registerTuneable(null_rej, 0.1, 5.0, "Variability",
"How different from the training data a new gesture can be and "
"still be considered the same gesture. The higher the number, the "
"more different it can be.", updateVariability);
registerTuneable(timeout, 1, 3000,
"Timeout",
"How long (in milliseconds) to wait after recognizing a "
"gesture before recognizing another one.", updateTimeout);
}