openFrameworks・Kinect v1を用いた人の足の検知

#はじめに
歩く、止まる、ジャンプする人の動作を用いたインタラクティブアートを制作するため、OpenframeworksによるKINECTv1の制御をしました。

開発にあたって、こじ研（小島研究所）さんのサイトを参考にさせていただいています。
https://www.ei.tohoku.ac.jp/xkozima/lab/ofTutorial5.html
#コード
```ruby:.hpp
#ifndef KN_BASIC
#define KN_BASIC
#include <stdio.h>
#include "ofMain.h"
#include "ofxOpenNI.h"
#endif

class KN_basic {
public:
    void setup();
    void update();
    int draw(ofVec2f _manPosition[][2]);
    
    void keyPressed(int key);
    ofxOpenNI kinect;
    XnPoint3D XnProject[640], XnWorld[640];
    unsigned short backZ[640];
    
    int frameRate = 60;
    int mhorizonY = 400;     //  set height sensing
    int mInterval=55;        //  set leg interval
    int mMaxLegInterval=45;  // set max leg interval

    bool flag[640];
    float scrX,scrY;
    float manX[1000];
    float manY[1000];
    
    ofVec2f mLegPosition[1000];
    int mManCount;
    int mLegCount;
};
```
```ruby:.cpp
#include "KN_basic.hpp"
//--------------------------------------------------------------
void KN_basic::setup(){
    ofBackground(0, 0, 0);
    ofSetFrameRate(frameRate);
    ofSetWindowShape(1280, 480);
    kinect.setup();
    kinect.setRegister(true);
    kinect.addDepthGenerator();
    kinect.start();

    for (int x = 0; x < 640; x++)
        backZ[x] = 100000;
//--------------------------------------------------------------
void KN_basic::update(){
    kinect.update();
    unsigned short *depthData = kinect.getDepthRawPixels().getData();
    for (int x = 0; x < 640; x++) {
        XnProject[x].X = x;
        XnProject[x].Y = mhorizonY;
        XnProject[x].Z = depthData[mhorizonY * 640 + x];
        
        unsigned short depth = depthData[mhorizonY * 640 + x];
        if(backZ[x] - depth < 70){
            flag[x]=false;
        }else{
            flag[x]=true;
        }
        XnProject[x].Z = (backZ[x] - depth >= 100)? depth: 0;
    }
    kinect.getDepthGenerator().ConvertProjectiveToRealWorld(640, XnProject, XnWorld);
}

//--------------------------------------------------------------

int KN_basic::draw(ofVec2f _manPosition2[][2]){
    kinect.drawDepth(0,0,640,480);
    ofDrawLine(0,mhorizonY,639,mhorizonY);

    ofSetColor(100, 200, 100);
    mLegCount=0;
    mManCount=0;
    for (int x = 0; x < 640;x++) {
        XnPoint3D world = XnWorld[x];
        scrX = world.X * 0.1 + 320, scrY = 479 - world.Z * 0.1;
        if (0 <= scrX && scrX < 640 && 0 <= scrY && scrY < 475 && flag[x]==true){
            mLegPosition[mLegCount].x=scrX;
            mLegPosition[mLegCount].y=scrY;
            mLegCount++;
            x+=mInterval;
        }
    }
    int count[100];
    for(int i= 0; i<mLegCount;i++){
        for(int j = 0; j<mLegCount;j++){
            if(i<j){
        float dist = ofDist(mLegPosition[i].x,mLegPosition[i].y,mLegPosition[j].x,mLegPosition[j].y);
            if(dist<mMaxLegInterval){ 
                _manPosition2[mManCount][0]=mLegPosition[i];
                _manPosition2[mManCount][1]=mLegPosition[j];
                mManCount++;
                break;
               }
            }
        }
    }
    for(int i = 0; i< mManCount;i++){
            if(_manPosition2[i][0] ==_manPosition2[i][1]){
                ofDrawCircle(_manPosition2[i][0].x, _manPosition2[i][0].y, 8);
                ofDrawCircle(_manPosition2[i][1].x, _manPosition2[i][1].y, 8);
            }else{
            ofDrawCircle(_manPosition2[i][0].x, _manPosition2[i][0].y, 8);
            ofDrawCircle(_manPosition2[i][1].x, _manPosition2[i][1].y, 8);
            }
    }
    return mManCount;
}

//--------------------------------------------------------------
void KN_basic::keyPressed(int key){
    if (key == 's') {
        unsigned short *depthData = kinect.getDepthRawPixels().getData();
        for (int x = 0; x < 640; x++)
            backZ[x] = depthData[mhorizonY * 640 + x];
    }
}
```
#開発にあたって
本コードは、ヒトの足を片方ずつ検知させることを目的に開発しました。KINECTにもともと搭載されているトラッキングを使うともっと簡単にできると思うのですが、それだと、

- 検知できる人数が限られている
- 全身をカメラでとらえないと使えない
- 以上によりセンサシング区間が狭くなる

という問題点がありました。目標の制作物は足だけをKINECTでとらえ、検知したかったのです。そこでKINECTの深度センサーを使うことにしました。
仕組みとしては、

1. 深度センサーで決められた距離（展示スペースに合わせて奥行きをあらかじめ設定。今回は画面の1ピクセル当たり１センチメートル で計算しているので奥行き５メートル）までにある障害物の場所を検知する
2. その障害物の大きさや物体間の距離から片足ずつ判別していく

です、が。。。正直に言うと精度に問題アリです。雑音が入ってしまいうまく判別してくれません。（中古でKINECTを購入したのが問題だったかも...）
精度はよくありませんが、使えることは使えます。このコードをもとに、止まる、歩くなどの動作を検知していきます。