现在的位置: 首页资讯>正文
如何使用CurieNano实现手势的训练和识别的解决方案
2017年06月14日 作者: 暂无评论 82+ 0

CurieNano

概述:

Curie芯片本身就是为智能可穿戴设计,它内部自带加速度计陀螺仪(IMU)和模式识别引擎(PME),也可称为硬件神经网络),我们可以采集来自CurieIMU的加速度计数据,对数据进行预处理后扔进模式识别引擎,对手势进行学习训练,训练后的模式识别引擎,可以在人做出手势后进行手势分类。手势识别功能在智能穿戴、智能手环上会获得很好的应用。 因为神经网络为我们提供了学习的灵活性,我们不需要任何对代码的改动就能让Curie学习任意手势。目前识别率高的手势有:上下左右滑动,顺逆时针划圈,双击,三击。

另外,我这个代码的亮点还在于,它能区分静止和运动两种状态,自动识别人是否在做手势。我阅读了官网上给出的例程,它是需要外接一个按钮,人做手势的时候需要按下按钮。显然我提供的例程比它方便多了

目的:

使用CurieNano实现手势的训练和识别。 

硬件/软件需求:

硬件需求: CurieNano/Arduino 101 、 电脑

软件需求: Arduino 101 2.x+库 、 CuriePME库 、 SerialFlash库

代码下载:

请在帖子最后查看代码。 或前往我的git库下载:https://git.ustclug.org/WangXuan.c/gesture-learn/

其中Arduino代码的路径为:gesture-learn/code/Gesture

要想上传代码,首先确认你拥有Arduino 101的2.x版本的库,其次你需要两个第三方库:

Curie模式识别库:https://github.com/01org/Intel-Pattern-Matching-Technology

SerialFlash库:https://github.com/PaulStoffregen/SerialFlash

以上两个库下载解压后需要放在这个目录下:Arduino目录\libraries

 展示视频:

视频也在git库里,克隆git库后,视频路径为:gesture-learn/video/5-11.mp4

目前功能:

支持:

1、不需要任何其他外设,只需要一台电脑和一个CurieNano,自动捕捉动态手势,对若干个任意的手势进行训练。

2、可以自定义任意手势,目前识别率较高的有:左移并返回、右移并返回、上移并返回、下移并返回、顺时针画圈、逆时针画圈、左右摇晃、双击、三击

3、将训练的数据存入板载SPI FLASH,断电不丢失

4、从SPI FLASH里读取上次训练的数据,一次学习,多次使用。

局限性:

因为只使用了一个CurieNano,因此只支持3个自由度的手势识别(比如绑在手腕上),不支持负责的手指动作识别。

使用方法:

下载程序后,打开串口,如果你是第一次运行,则直接进入手势训练模式,请在串口提示信息下做手势,对CurieNano进行训练。训练结束后,串口会提示是否将数据存储到板载Flash芯片,请在串口监视器上打y/n进行选择。之后就进入识别模式,你每做一个手势,101会给出识别结果。

若你已经保存了一份手势数据,上电运行时可以在提示下按y读取,然后直接进入识别模式。

请注意CurieNano板载的LED灯(13号引脚的LED灯),灯亮则认为用户在做手势,灯灭则代表进入基本静止(无手势)的状态。当你开始做一个手势时,请等待板载LED灯熄灭后再开始。当你做完一个手势时,也请确保LED灯熄灭。

一个手势不能太长,也不能太短。以0.8秒到3秒为宜。

编程思路:

主要参考论文《基于加速度计的手势识别》 代宏斌。大体思路相同,但具体实现有所不同,主要为以下几步:

加速度计数据采集

数据预处理: 1、删除相邻重复数据、平滑滤波 2、提取动作片段 3、时间轴归一化,压缩数据维数到固定值 4、三轴数据拼接为一个向量 5、幅度轴归一化,压缩数据使加速度值范围为0~255,均值为128,最小值和最大值相差128

预处理的数据交付神经网络进行训练或识别。

参考文献:

[1] 基于加速度计的手势识别 代宏斌

代码:

/*
* Author : 灯灯
* HardWare : Intel Curie
* Description : Hand Gesture Training and classifing
*/

#include <CurieIMU.h>
#include <CuriePME.h>
#include <SerialFlash.h>

// 注意:
// 做所有手势前必须保持静止,手势做完也有保持静止,程序会自动从两个静止中提取动作数据。
// 不要连续做两个动作,动作中间一定要有至少半秒的停顿
// 一个动作不能太长也不能太短,控制在半秒到2秒之内
// 做动作的时候尽量保持101指向一个方向,不要翻转它
// 在此处添加更多的手势
char *gestures[] = {
"Right shift and go back", // 右划并返回原位
"Left shift and go back", // 左划并返回原位
"Up shift and go back",
"Down shift and go back",
"Draw circle anticlockwise", // 逆时针画圈
"Draw circle clockwise", // 顺时针画圈
"Double click",
"Triple click"
};

const int GESTURE_CNT = ( sizeof(gestures) / sizeof(*gestures) ) ;

// 板载LED灯控制
// 当101识别到手势时,LED灯亮
#define LED_PIN 13
#define LED_READY { pinMode(LED_PIN, OUTPUT);digitalWrite(LED_PIN, LOW); }
#define LED_ON digitalWrite(LED_PIN, HIGH)
#define LED_OFF digitalWrite(LED_PIN, LOW)

#define DEBUG_PRT(x) Serial.print(x)
#define DEBUG_CRLF Serial.println()
#define ASSERT(x,msg) { if(!(x)) {Serial.print("*** Error! "); Serial.println(msg); } }

// 在WSIZE次采样内极差不超过THRESHODE,认为无手势
#define WSIZE 30
// 支持的最长手势片段
#define LEN 230
// 支持的最短手势片段
#define MINLEN 30
// 时间归一化后的向量长度
#define LLEN 30
// 认为无手势的门限
#define THRESHODE 2000
// 单个手势的学习次数
#define LEARN_CNT 12

const int FlashChipSelect = 21;
// 数据预处理类
// 功能:手势片段截取、平滑滤波、时间归一化、幅值归一化、数据量精简、三轴数据拼接
// 预处理后的数据才能扔进神经网络
class dataPreProcessor{

private:

int index;
int x, y, z;
int lx, ly, lz;
int xWindow[WSIZE], yWindow[WSIZE], zWindow[WSIZE];
int xBuffer[ LEN ], yBuffer[ LEN ], zBuffer[ LEN ];
int xMax, xMin, yMax, yMin, zMax, zMin;
int xAvg, yAvg, zAvg;

// 读取三轴加速度计数据
void readRawData(){
do{
CurieIMU.readAccelerometer(x, y, z);
}while(x==lx && y==ly && z==lz);
lx = x; ly = y; lz = z;
}

// 数据放入循环数组,并计算是否存在手势动作,存在则返回true,否则返回false
bool push(){
readRawData();

xWindow[index] = x; yWindow[index] = y; zWindow[index] = z;
index = (index+1) % WSIZE;

xMax=xWindow[0], xMin=xWindow[0], yMax=yWindow[0], yMin=yWindow[0], zMax=zWindow[0], zMin=zWindow[0];

xAvg = 0; yAvg = 0; zAvg = 0;

for(int i=1; i<WSIZE; i++){
xAvg += xWindow[i];
yAvg += yWindow[i];
zAvg += zWindow[i];
if(xMax<xWindow[i]) xMax = xWindow[i];
if(xMin>xWindow[i]) xMin = xWindow[i];
if(yMax<yWindow[i]) yMax = yWindow[i];
if(yMin>yWindow[i]) yMin = yWindow[i];
if(zMax<zWindow[i]) zMax = zWindow[i];
if(zMin>zWindow[i]) zMin = zWindow[i];
}

xAvg /= WSIZE; yAvg /= WSIZE; zAvg /= WSIZE;

return ( (xMax-xMin) + (yMax-yMin) + (zMax-zMin) ) > THRESHODE ;
}

// 填满窗口
void restart(){
index = 0;
for(uint16_t cnt=WSIZE; cnt>0; cnt--){
push();
}
}

public:

// 存放预处理结果,即神经网络用于训练的向量
uint8_t data[LLEN*3];

// 识别下一个动作,若动作不合法则返回false,合法则返回true,同时更新data向量。
bool nextGesture(){
restart();
// 等待直到持续无动作
while( push()==true );
// 等待直到出现动作
while( push()==false );

int xA = xAvg, yA = yAvg, zA = zAvg;
int xMa=x, xMi=x, yMa=y, yMi=y, zMa=z, zMi=z;

int len = 0;

LED_ON;

while( push()==true ){
xBuffer[len] = x; yBuffer[len] = y; zBuffer[len] = z;
if( (++len) >= LEN )
return false;
if(xMa<x) xMa = x;
if(xMi>x) xMi = x;
if(yMa<y) yMa = y;
if(yMi>y) yMi = y;
if(zMa<z) zMa = z;
if(zMi>z) zMi = z;
};

LED_OFF;

len -= WSIZE;
if( len < MINLEN ) return false;

int delta = max( max( (xMa-xMi) , (yMa-yMi) ) , (zMa-zMi) );

xA = (xA+xAvg)/2; yA = (yA+yAvg)/2; zA = (zA+zAvg)/2;

// 进行均值滤波、时间归一化、幅度归一化,得到预处理的最终步骤
for(int i=0; i<LLEN; i++){
int j = (i*len) / LLEN ;
int k = ((1+i)*len) / LLEN ;
data[i] = 128; data[i+LLEN] = 128; data[i+LLEN*2] = 128;
for(int s=j; s<k; s++){
data[i] += ( (xBuffer[s]-xA) * 128 ) / delta / (k-j);
data[i+LLEN] += ( (yBuffer[s]-yA) * 128 ) / delta / (k-j);
data[i+LLEN*2] += ( (zBuffer[s]-zA) * 128 ) / delta / (k-j);
}
}

/*
// 可在“串口绘图器”上显示不同的手势经预处理后的数据
for(int i=0; i<(LLEN*3); i++){
DEBUG_PRT(data[i]); DEBUG_CRLF;
}
*/

return true;
}

};

// 实例化数据预处理类
dataPreProcessor processor;
// 校正3轴加速度计值
void resetAccelerometer(){
CurieIMU.autoCalibrateAccelerometerOffset(X_AXIS, 0);
CurieIMU.autoCalibrateAccelerometerOffset(Y_AXIS, 0);
CurieIMU.autoCalibrateAccelerometerOffset(Z_AXIS, 1);
}
bool ask(const char* question){
Serial.print(question);
Serial.println(" (y|n)");
int c;
do{
c = Serial.read();
}while(c!='y' && c!='n');
return c=='y';
}
#define FILE_NAME "NeurData.dat"
void setup() {
LED_READY;

Serial.begin(115200);
while(!Serial);

CurieIMU.begin();
CurieIMU.setAccelerometerRange(5);
Serial.println("Initialized CurieIMU!");

// 校正3轴加速度计值(可选)
// resetAccelerometer();

CuriePME.begin();
Serial.println("Initialized CuriePME!");

bool flashAvailable = SerialFlash.begin(FlashChipSelect);

if (!flashAvailable) {
Serial.println("Unable to access SPI Flash chip. You can only train new gestures");
}else{
Serial.println("Initialized CurieSerialFlash!");
if( SerialFlash.exists(FILE_NAME) && ask("Old trained data found! Do you want to load learned data?(y) or train new gestures?(n)") ){
restoreNetworkKnowledge();
Serial.println("\nNow do gestures. Arduino 101 will classify them.");
Serial.println();
return;
}
}

// 倒计时过程中,用户把Arduino 101拿在手里,准备学习
Serial.println("Start Training after 5s, prepare to keep your Arduino 101 in static...");
for(int i=5;i>0;i--){
Serial.print(i);Serial.println("...");
delay(1000);
}
Serial.println();

// 开始进行训练
for(int i=0; i<GESTURE_CNT; i++){
Serial.print("Learn gesture "); Serial.print(i+1); Serial.print("/"); Serial.println(GESTURE_CNT);
Serial.print("gesture name: "); Serial.println(gestures[i]);
for(int j=0; j<LEARN_CNT; j++){
Serial.print(j+1); Serial.print("/"); Serial.print(LEARN_CNT); Serial.print(" Now do this gesture...");
while(processor.nextGesture()==false){
Serial.print("invalid gesture,try again...");
};
CuriePME.learn(processor.data, 3*LLEN, i+1);
Serial.println("OK!");
}
Serial.println("Done with this gesture!");
Serial.println();
}

if(!flashAvailable)
flashAvailable = SerialFlash.begin(FlashChipSelect);

if( flashAvailable && ask("Do you want to save trained data? this may cover the old trained data.") ){
saveNetworkKnowledge();
}

// 训练结束,在loop函数里进行手势识别
Serial.println("Now do gestures. Arduino 101 will classify them.");
Serial.println();
}
// 持续手势识别...
void loop(){
while(processor.nextGesture()==false);

int answer = CuriePME.classify(processor.data, 3*LLEN);
if( answer == CuriePME.noMatch ){
Serial.println("Unknown Gesture");
}else{
Serial.println(gestures[answer-1]);
}
}

void saveNetworkKnowledge(){
const char *filename = "NeurData.dat";
SerialFlashFile file;

Intel_PMT::neuronData neuronData;
uint32_t fileSize = 128 * sizeof(neuronData);

Serial.print( "File Size to save is = ");
Serial.print( fileSize );
Serial.print("\n");

create_if_not_exists( filename, fileSize );
// Open the file and write test data
file = SerialFlash.open(filename);
file.erase();

CuriePME.beginSaveMode();
if (file) {
// iterate over the network and save the data.
while( uint16_t nCount = CuriePME.iterateNeuronsToSave(neuronData)) {
if( nCount == 0x7FFF)
break;

Serial.print("Saving Neuron: ");
Serial.print(nCount);
Serial.print("\n");
uint16_t neuronFields[4];

neuronFields[0] = neuronData.context;
neuronFields[1] = neuronData.influence;
neuronFields[2] = neuronData.minInfluence;
neuronFields[3] = neuronData.category;

file.write( (void*) neuronFields, 8);
file.write( (void*) neuronData.vector, 128 );
}
}

CuriePME.endSaveMode();
Serial.print("Knowledge Set Saved. \n");
}
bool create_if_not_exists(const char *filename, uint32_t fileSize){
if (!SerialFlash.exists(filename)) {
Serial.println("Creating file " + String(filename));
return SerialFlash.createErasable(filename, fileSize);
}

Serial.println("File " + String(filename) + " already exists");
return true;
}

void restoreNetworkKnowledge(){
const char *filename = "NeurData.dat";
SerialFlashFile file;
int32_t fileNeuronCount = 0;

Intel_PMT::neuronData neuronData;

// Open the file and write test data
file = SerialFlash.open(filename);

CuriePME.beginRestoreMode();
if (file) {
// iterate over the network and save the data.
while(1) {
Serial.print("Reading Neuron: ");

uint16_t neuronFields[4];
file.read( (void*) neuronFields, 8);
file.read( (void*) neuronData.vector, 128 );

neuronData.context = neuronFields[0] ;
neuronData.influence = neuronFields[1] ;
neuronData.minInfluence = neuronFields[2] ;
neuronData.category = neuronFields[3];

if (neuronFields[0] == 0 || neuronFields[0] > 127)
break;

fileNeuronCount++;

// this part just prints each neuron as it is restored,
// so you can see what is happening.
Serial.print(fileNeuronCount);
Serial.print("\n");

Serial.print( neuronFields[0] );
Serial.print( "\t");
Serial.print( neuronFields[1] );
Serial.print( "\t");
Serial.print( neuronFields[2] );
Serial.print( "\t");
Serial.print( neuronFields[3] );
Serial.print( "\t");

Serial.print( neuronData.vector[0] );
Serial.print( "\t");
Serial.print( neuronData.vector[1] );
Serial.print( "\t");
Serial.print( neuronData.vector[2] );

Serial.print( "\n");
CuriePME.iterateNeuronsToRestore( neuronData );
}
}

CuriePME.endRestoreMode();
Serial.print("Knowledge Set Restored. \n");
}

原文链接:http://www.eeboard.com/news/curienano-3/

搜索"爱板网"加关注,每日最新的开发板、智能硬件、开源硬件、活动等信息可以让你一手全掌握。推荐关注!

【微信扫描下图可直接关注】

toaal

科技早知道:

英特尔警告微软高通:用ARM处理器模拟x86可能侵权

这个品牌的手机性能配置很一般,但销量却很好,你猜出它是谁了吗?

大神之作——Espressobin 使用USB无线网卡

技术活,该赏!基于Win10 UWP的BLE应用——与CurieNano交互

固体火箭+陀螺仪稳定器:这款仅450克小型机器人将成为行星探索利器

  

给我留言

您必须 [ 登录 ] 才能发表留言!