查看: 3304|回复: 0

【CurieNano项目1】CuriePME 神经元 手势识别

[复制链接]
  • TA的每日心情
    开心
    2017-5-15 14:59
  • 签到天数: 8 天

    连续签到: 1 天

    [LV.3]偶尔看看II

    发表于 2017-5-15 15:39:17 | 显示全部楼层 |阅读模式
    分享到:
    本帖最后由 灯灯灯 于 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] 基于加速度计的手势识别 代宏斌


    代码:


    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 下一条



    手机版|小黑屋|与非网

    GMT+8, 2024-4-20 15:35 , Processed in 0.116398 second(s), 15 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2020, Tencent Cloud.