回答

收藏

[原创] 【CurieNano项目1】CuriePME 神经元 手势识别

DFROBOT DFROBOT 4758 人阅读 | 0 人回复 | 2017-05-15

本帖最后由 灯灯灯 于 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***
      



目前功能:
支持:
       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] 基于加速度计的手势识别 代宏斌


代码:


  1. /*
  2. *  Author      : 灯灯
  3. *  HardWare    : Intel Curie
  4. *  Description : Hand Gesture Training and classifing
  5. */

  6. #include <CurieIMU.h>
  7. #include <CuriePME.h>
  8. #include <SerialFlash.h>

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

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

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



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



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

  47. const int FlashChipSelect = 21;


  48. // 数据预处理类
  49. // 功能:手势片段截取、平滑滤波、时间归一化、幅值归一化、数据量精简、三轴数据拼接
  50. // 预处理后的数据才能扔进神经网络
  51. class dataPreProcessor{
  52.   
  53. private:

  54.   int index;
  55.   int x, y, z;
  56.   int lx, ly, lz;
  57.   int xWindow[WSIZE], yWindow[WSIZE], zWindow[WSIZE];
  58.   int xBuffer[ LEN ], yBuffer[ LEN ], zBuffer[ LEN ];
  59.   int xMax, xMin, yMax, yMin, zMax, zMin;
  60.   int xAvg, yAvg, zAvg;

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

  68.   // 数据放入循环数组,并计算是否存在手势动作,存在则返回true,否则返回false
  69.   bool push(){
  70.     readRawData();
  71.    
  72.     xWindow[index] = x; yWindow[index] = y; zWindow[index] = z;
  73.     index = (index+1) % WSIZE;

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

  75.     xAvg = 0; yAvg = 0; zAvg = 0;

  76.     for(int i=1; i<WSIZE; i++){
  77.       xAvg += xWindow[i];
  78.       yAvg += yWindow[i];
  79.       zAvg += zWindow[i];
  80.       if(xMax<xWindow[i]) xMax = xWindow[i];
  81.       if(xMin>xWindow[i]) xMin = xWindow[i];
  82.       if(yMax<yWindow[i]) yMax = yWindow[i];
  83.       if(yMin>yWindow[i]) yMin = yWindow[i];
  84.       if(zMax<zWindow[i]) zMax = zWindow[i];
  85.       if(zMin>zWindow[i]) zMin = zWindow[i];
  86.     }

  87.     xAvg /= WSIZE;  yAvg /= WSIZE;  zAvg /= WSIZE;
  88.    
  89.     return ( (xMax-xMin) + (yMax-yMin) + (zMax-zMin) ) > THRESHODE ;
  90.   }

  91.   // 填满窗口
  92.   void restart(){
  93.     index = 0;
  94.     for(uint16_t cnt=WSIZE; cnt>0; cnt--){
  95.       push();
  96.     }
  97.   }

  98. public:

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

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

  108.     int  xA = xAvg,  yA = yAvg,  zA = zAvg;
  109.     int xMa=x, xMi=x, yMa=y, yMi=y, zMa=z, zMi=z;
  110.    
  111.     int len = 0;

  112.     LED_ON;
  113.    
  114.     while( push()==true ){
  115.       xBuffer[len] = x; yBuffer[len] = y; zBuffer[len] = z;
  116.       if( (++len) >= LEN )
  117.           return false;
  118.       if(xMa<x) xMa = x;
  119.       if(xMi>x) xMi = x;
  120.       if(yMa<y) yMa = y;
  121.       if(yMi>y) yMi = y;
  122.       if(zMa<z) zMa = z;
  123.       if(zMi>z) zMi = z;
  124.     };

  125.     LED_OFF;
  126.    
  127.     len -= WSIZE;
  128.     if( len < MINLEN ) return false;

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

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

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

  142.     /*
  143.     // 可在“串口绘图器”上显示不同的手势经预处理后的数据
  144.     for(int i=0; i<(LLEN*3); i++){
  145.       DEBUG_PRT(data[i]);  DEBUG_CRLF;
  146.     }
  147.     */
  148.    
  149.     return true;
  150.   }
  151.   
  152. };

  153. // 实例化数据预处理类
  154. dataPreProcessor processor;


  155. // 校正3轴加速度计值
  156. void resetAccelerometer(){
  157.   CurieIMU.autoCalibrateAccelerometerOffset(X_AXIS, 0);
  158.   CurieIMU.autoCalibrateAccelerometerOffset(Y_AXIS, 0);
  159.   CurieIMU.autoCalibrateAccelerometerOffset(Z_AXIS, 1);
  160. }


  161. bool ask(const char* question){
  162.   Serial.print(question);
  163.   Serial.println(" (y|n)");
  164.   int c;
  165.   do{
  166.     c = Serial.read();
  167.   }while(c!='y' && c!='n');
  168.   return c=='y';
  169. }


  170. #define FILE_NAME "NeurData.dat"


  171. void setup() {
  172.   LED_READY;
  173.   
  174.   Serial.begin(115200);
  175.   while(!Serial);
  176.   
  177.   CurieIMU.begin();
  178.   CurieIMU.setAccelerometerRange(5);
  179.   Serial.println("Initialized CurieIMU!");
  180.   
  181.   // 校正3轴加速度计值(可选)
  182.   // resetAccelerometer();
  183.   
  184.   CuriePME.begin();
  185.   Serial.println("Initialized CuriePME!");

  186.   bool flashAvailable = SerialFlash.begin(FlashChipSelect);

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

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

  205.   // 开始进行训练
  206.   for(int i=0; i<GESTURE_CNT; i++){
  207.     Serial.print("Learn gesture "); Serial.print(i+1); Serial.print("/"); Serial.println(GESTURE_CNT);
  208.     Serial.print("gesture name: "); Serial.println(gestures[i]);
  209.     for(int j=0; j<LEARN_CNT; j++){
  210.       Serial.print(j+1); Serial.print("/"); Serial.print(LEARN_CNT); Serial.print("  Now do this gesture...");  
  211.       while(processor.nextGesture()==false){
  212.         Serial.print("invalid gesture,try again...");  
  213.       };
  214.       CuriePME.learn(processor.data, 3*LLEN, i+1);
  215.       Serial.println("OK!");
  216.     }
  217.     Serial.println("Done with this gesture!");
  218.     Serial.println();
  219.   }

  220.   if(!flashAvailable)
  221.     flashAvailable = SerialFlash.begin(FlashChipSelect);
  222.   
  223.   if( flashAvailable && ask("Do you want to save trained data? this may cover the old trained data.") ){
  224.     saveNetworkKnowledge();
  225.   }

  226.   // 训练结束,在loop函数里进行手势识别
  227.   Serial.println("Now do gestures. Arduino 101 will classify them.");
  228.   Serial.println();
  229. }




  230. // 持续手势识别...
  231. void loop(){
  232.   while(processor.nextGesture()==false);
  233.   
  234.   int answer = CuriePME.classify(processor.data, 3*LLEN);
  235.   if( answer == CuriePME.noMatch ){
  236.     Serial.println("Unknown Gesture");
  237.   }else{
  238.     Serial.println(gestures[answer-1]);
  239.   }
  240. }



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

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

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

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

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

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

  263.       neuronFields[0] = neuronData.context;
  264.       neuronFields[1] = neuronData.influence;
  265.       neuronFields[2] = neuronData.minInfluence;
  266.       neuronFields[3] = neuronData.category;

  267.       file.write( (void*) neuronFields, 8);
  268.       file.write( (void*) neuronData.vector, 128 );
  269.     }
  270.   }

  271.   CuriePME.endSaveMode();
  272.   Serial.print("Knowledge Set Saved. \n");
  273. }


  274. bool create_if_not_exists(const char *filename, uint32_t fileSize){
  275.   if (!SerialFlash.exists(filename)) {
  276.     Serial.println("Creating file " + String(filename));
  277.     return SerialFlash.createErasable(filename, fileSize);
  278.   }

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



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

  286.   Intel_PMT::neuronData neuronData;

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

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

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

  297.       neuronData.context = neuronFields[0] ;
  298.       neuronData.influence = neuronFields[1] ;
  299.       neuronData.minInfluence = neuronFields[2] ;
  300.       neuronData.category = neuronFields[3];

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

  303.       fileNeuronCount++;

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

  308.       Serial.print( neuronFields[0] );
  309.       Serial.print( "\t");
  310.       Serial.print( neuronFields[1] );
  311.       Serial.print( "\t");
  312.       Serial.print( neuronFields[2] );
  313.       Serial.print( "\t");
  314.       Serial.print( neuronFields[3] );
  315.       Serial.print( "\t");

  316.       Serial.print( neuronData.vector[0] );
  317.       Serial.print( "\t");
  318.       Serial.print( neuronData.vector[1] );
  319.       Serial.print( "\t");
  320.       Serial.print( neuronData.vector[2] );

  321.       Serial.print( "\n");
  322.       CuriePME.iterateNeuronsToRestore( neuronData );
  323.     }
  324.   }

  325.   CuriePME.endRestoreMode();
  326.   Serial.print("Knowledge Set Restored. \n");
  327. }
复制代码
分享到:
回复

使用道具 举报

您需要登录后才可以回帖 注册/登录

本版积分规则

关闭

站长推荐上一条 /2 下一条