TA的每日心情 | 开心 2017-5-15 14:59 |
---|
签到天数: 8 天 连续签到: 1 天 [LV.3]偶尔看看II
|
本帖最后由 灯灯灯 于 2017-5-19 14:14 编辑
概述:
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");
- }
复制代码 |
|