CCVを使ってみよう

CV系のライブラリだと、[OpenCV](http://opencv.org/)が圧倒的にシェア率が高く、使いやすいが、最近「CCV」というライブラリを使う機会があったので、紹介したいと思います。

## CCVとは
C/C++用のコンピュータービジョン系のライブラリです。
[http://libccv.org/](http://libccv.org/)

OpenCVが1999年から使われ始めたのに対し、CCVは2010年に誕生したそうです。

特徴として、
- マルチプラットフォームで動く。
- 最先端の画像認識アルゴリズムが使われている。
- インタフェースがクリーンで分かりやすい。
みたいな感じです。

- Track Learn Detect（物体の追跡認識）
- Stroke Width Transform（画像からの文字抽出）
- Conv Net（畳み込みニューラルネットワーク）
- Integral Channel Features（輝度、色、スペクトラムなど多くの情報から認識を行うアルゴリズム）

など、まあまあ近年の論文で発表されて、中にはOpenCVにはデフォルトでは実装されていないアルゴリズムが、きれいな関数で実装されています。
僕は使ってないのでわからないのですが、[HTTP用のREST-ful API](http://libccv.org/doc/doc-http/)も用意されているみたいで、サービスでの応用もやりやすそうです。（ライセンスがBSDなのでご注意ください。）

学習モデルは、使用するときに必ずSQLite形式のデータベースファイルの読み込み処理が必要になります。
上で挙げた機能や、顔認識、ImageNetやAlexNet用のモデルはSampleからダウンロードできます。
自分でモデルを実装したい場合は、[ここ](http://libccv.org/doc/doc-convnet/)を参考にしてください。
ここでは、デフォルトで提供されているモデルを使って、応用の仕方を説明していきます。


## みんな大好きopenFrameworksで使ってみる
なんと[Kyle Mcdonald](http://kylemcdonald.net/)さんが、oFのAddonを作ってくれていました。
[https://github.com/kylemcdonald/ofxCcv](https://github.com/kylemcdonald/ofxCcv)

今回はopenFrameworksでこのアドオンを使って、手軽に試してみます。

### 歩行者認識（Integral Channel Featuresを使った実装方法）
`classifyPedestrians()`を使用します。
認識するためのコードは以下のたったこれだけです。

```cpp:main.cpp
#include "ofMain.h"
#include "ofxCcv.h"

class ofApp : public ofBaseApp {
public:
    ofxCcv ccv;
    vector<ofRectangle> results;
    ofVideoPlayer player;
    
    void setup() {
        ccv.setupPedestrians("pedestrian.icf");
        player.load("movie.mp4");
        player.play();
    }
    void update() {
        player.update();
        if(player.isFrameNew()) {
            results = ccv.classifyPedestrians(player);
        }
    }
    void draw() {
        player.draw(0, 0);
        ofSetWindowTitle(ofToString(ofGetFrameRate()));
        ofPushStyle();
        ofNoFill();
        ofSetLineWidth(2);
        for(int i = 0; i < results.size(); i++) {
            ofDrawRectangle(results[i].x,results[i].y,results[i].getWidth(),results[i].getHeight());
            ofDrawBitmapStringHighlight(ofToString(i), results[i].getPosition() + ofPoint(-5,-5));
        }
        ofPopStyle();
    }
};

int main() {
    ofSetupOpenGL(1200, 800, OF_WINDOW);
    ofRunApp(new ofApp());
    
}
```

すると、
<img width="582" alt="スクリーンショット 2016-12-22 12.18.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/106081/231d4140-5d35-19c3-5c70-61f37898beeb.png">
ちゃんと取れてますね。めちゃ手軽です。
pedestrian.icfはモデルデータです。ofxCcvのサンプルの中に落ちています。
ただし、本格的に使う場合は、ccv.classifyPedestrians()はスレッドを分けて使う必要がありそうです。（実行するときにフレームが1~2まで落ちてしまう。）

### 顔認識
`classifyFace()`を使用します。
これもコードが以下のようにとても少なくすみます。

```cpp:main.cpp
#include "ofMain.h"
#include "ofxCcv.h"

class ofApp : public ofBaseApp {
public:
    ofxCcv ccv;
    
    vector<ofRectangle> results;
    ofImage image;
    
    void setup() {
        ccv.setupFace("face.sqlite3");
        image.load("image.jpg");
    }
    void update() {
        if(ofGetKeyPressed()) {
            results = ccv.classifyFace(image);
        }
    }
    void draw() {
        image.draw(0, 0);
        
        ofPushStyle();
        ofNoFill();
        ofSetLineWidth(2);
        ofPushStyle();
        ofSetColor(0, 0, 255);
        for(int i = 0; i < results.size(); i++) {
            ofDrawRectangle(results[i]);
            ofDrawBitmapStringHighlight(ofToString(i), results[i].getPosition() + ofPoint(-5,-5));
        }
        ofPopStyle();
        ofPopStyle();
        
        ofDrawBitmapStringHighlight("press any key to detect", 10,20);
    }
};

int main() {
    ofSetupOpenGL(1140, 641, OF_WINDOW);
    ofRunApp(new ofApp());
    
}
```

<img width="1138" alt="スクリーンショット 2016-12-22 14.01.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/106081/dbc2d38c-215a-1e22-748b-87e2f5dd807c.png">
後ろの人は鼻と口が隠れているので取れていないですが、他の人はちゃんと取れています。

### 畳みこみニューラルネット
これは、少々コードが多くなるので、処理の根幹の部分だけ書きます。
ofxCcvの中に、example-viewerというプロジェクトがあり、そのコードで説明していきます。

```cpp:
ofxCcv ccv;  //ofxCcvのインスタンス

ccv.setup("image-net-2012.sqlite3");
/*
CNNのモデルの読み込み
*/

ccv.encode(image, ccv.numLayers());
/*
画像を入力し、認識情報を更新する。
- image: ofPixels（入力画像）
*/

ccv.getFeatureMaps(layer_num)
/*
指定した番号のレイヤー情報を、FeatureMapとして取得する。
- layer_num: int（レイヤーの番号）
*/

// Feature Map
class FeatureMap {
public:
    vector<float> acts;
    int rows;
    int cols;
    float max;
    void getImage(ofImage & img, bool autoBrighten=true);
};

```

<img width="809" alt="スクリーンショット 2016-12-22 16.03.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/106081/6f0ab11e-e9ff-dd94-f7c3-6a30b4c41a89.png">

こういう感じで、ネットワーク内での畳みこみ画像を取得し、表示することもできます。
自分でモデルを作るのはかなりしんどいですが、IamgeNetなどの既存のモデルを動かすだけなら、実質
`ccv.encode(image, ccv.numLayers())`
だけで動きます。

### その他
[ml4a-ofx](https://github.com/ml4a/ml4a-ofx)というプロジェクトがあり、ofxCcvのサンプルが使いやすいようにまとめられています。

## 雑感
既存の認識処理モデルを動かすのは、とてもシンプルに実装できて好きでした。
CaffeやTensorflowで作ったモデルを.sqlite3に直して、Ccvで実行するみたいなことができるといいなと思いました。
時間のあるときに、文字抽出とhttpAPIのインターフェースをofxCcvに実装しようと思います