オプティカルフローでLチカ


オプティカルフローでLチカをします。
Webカメラから左右の動きを測定し、Arduinoへ飛ばしてLチカするようにします。

**環境**
openFrameworks (v0.9.3_osx_release)
Arduino 1.69

# openFrameworks
projectGeneratorのAddonsにofxOpenCvとofxOscを読み込んで、プロジェクトを作成します。
[ofxCX]("https://github.com/hardmanko/ofxCX")はGitHubからダウンロードし、addonsフォルダに入れるとprojectGeneratorから読み込むことができるようになります。

```c++:ofApp.h
#pragma once

#include "ofMain.h"
#include "ofxCv.h"

class ofApp : public ofBaseApp{

	public:
		void setup();
		void update();
		void draw();

		void keyPressed(int key);
		void keyReleased(int key);
		void mouseMoved(int x, int y );
		void mouseDragged(int x, int y, int button);
		void mousePressed(int x, int y, int button);
		void mouseReleased(int x, int y, int button);
		void mouseEntered(int x, int y);
		void mouseExited(int x, int y);
		void windowResized(int w, int h);
		void dragEvent(ofDragInfo dragInfo);
		void gotMessage(ofMessage msg);
    
        ofVideoGrabber vidGrabber;
    
        //オプティカルフロー
        ofxCv::FlowFarneback farneback;
        ofxCv::Flow* curFlow;
        
        //カメラの画面サイズ
        int camWidth;
        int camHeight;
        
        //シリアル通信
        ofSerial serial;   
};

```



```C++:ofApp.cpp
#include "ofApp.h"

using namespace cv;
using namespace ofxCv;

//--------------------------------------------------------------
void ofApp::setup(){
    camWidth = 320;
    camHeight = 240;
    
    ofSetWindowShape(camWidth, camHeight);
    ofSetWindowPosition(0, 0);
    
    ofSetVerticalSync(true);
    ofBackground(100, 100, 100);
    ofEnableSmoothing();

    //カメラの設定
    vidGrabber.setDeviceID(0);
    vidGrabber.setDesiredFrameRate(10);
    vidGrabber.initGrabber(camWidth, camHeight);
    
    curFlow = &farneback;
    
    //シリアル通信の情報
    ofSetLogLevel(OF_LOG_VERBOSE);
    serial.listDevices();
    vector <ofSerialDeviceInfo> deviceList = serial.getDeviceList();

    serial.setup("/dev/cu.usbmodem411",9600); 
}

//--------------------------------------------------------------
void ofApp::update(){
    ofBackground(100, 100, 100);

    vidGrabber.update();
    
    if(vidGrabber.isFrameNew()){
        curFlow = &farneback;
        curFlow -> calcOpticalFlow(vidGrabber);
        
        ofRectangle area;
        area.setSize(camWidth, camHeight);
        ofVec2f vector_val = farneback.getAverageFlowInRegion(area);
        
        vector_val = vector_val*10;
        cout << vector_val*10 << endl;
        
        serial.writeByte((unsigned char)vector_val.x);//キャストで0~255の値に
        
    }
    
}

//--------------------------------------------------------------
void ofApp::draw(){
    vidGrabber.draw(0, 0, ofGetWidth(), ofGetHeight());
    curFlow -> draw(0, 0, ofGetWidth(), ofGetHeight());
}

//--------------------------------------------------------------
void ofApp::keyPressed(int key){
    
    switch (key) {
        case ' ':
            ofToggleFullscreen();
            break;
            
        default:
            break;
    }

}
```

コメントが沢山あると読みにくいので、調べれば分かるような説明はできるだけ省きました。
何をしているのかなんとなく分かるように、コードの説明をメモしておきます。

**ofSerial**

```C++
ofSetLogLevel(OF_LOG_VERBOSE);//シリアル通信の状態を表示
serial.listDevices();//シリアル通信が利用できるデバイスをコンソールに表示
vector <ofSerialDeviceInfo> deviceList = serial.getDeviceList();//シリアル通信ができるデバイスのID、名前、パスを表示
serial.setup("/dev/cu.usbmodem411",9600); //通信ができるように設定。通信先のポートと通信速度を引数にとる Arduinoのシリアルポートと一致するように
```

```C++
serial.writeByte((unsigned char)vector_val.x);//繋がってるデバイスへの書き込み
```

**ofVideoGrabber**

```C++
vidGrabber.setDeviceID(0);//どこからキャプチャするのか。listDevices()のID番号が引数になる
vidGrabber.setDesiredFrameRate(10);
vidGrabber.initGrabber(camWidth, camHeight);//初期化
```

**ofxCv**

```C++
curFlow = &farneback;
curFlow -> calcOpticalFlow(vidGrabber);//前と今のフレームを呼び出して解析

ofRectangle area;
area.setSize(camWidth, camHeight);//ベクトルを取得したい範囲
ofVec2f vector_val = farneback.getAverageFlowInRegion(area);//ベクトルの平均値を取得 
```

```C++
curFlow -> draw(0, 0, camWidth, camHeight);//描画のみ ベクトルの計算はupdate()のところ
```

# Arduino
ベクトルの平均値、`vector_val.x`が0~255の範囲でArduinoに送信されます。
左のベクトルを感知すると12ピンのLEDが、右のベクトルを感知すると13ピンのLEDが点灯するようにします。


```C++
void setup() {
  Serial.begin(9600);
  pinMode(13, OUTPUT);
  pinMode(12, OUTPUT);
}

void loop() {

  if(Serial.available()>0){
    int c = Serial.read();
      Serial.println(c);
      
      if(3 <= c && c <= 125 ){
        digitalWrite(13,LOW);
        digitalWrite(12,HIGH);
        delay(100);
        
      }else if(c > 125 && c <= 252){
        digitalWrite(12,LOW);
        digitalWrite(13,HIGH);
        delay(100);
        
      }else{
        digitalWrite(12,LOW);
        digitalWrite(13,LOW);
        
      }
    }

}
```

送信されてくるシリアルの値は0~255(0が静止の状態)ですが、3~252の範囲で条件分岐をしています。
シリアルモニタを見てみると分かるのですが、動きを止めた瞬間に逆側のベクトル値を検出するためです。
ex)静止（0,0,0...）->動く(1,2,3,4...)->動きを止める(255,0,0,0....)

# 参考
[オプティカルフロー](http://opencv.jp/sample/optical_flow.html)
[Media Art II 2013 第6回：openFrameworks Addonを使う 2 - ofxOpenCV と ofxCv](http://www.slideshare.net/tado/media-art-ii-2013-6openframeworks-addon-2-ofxopencv-ofxcv)