Take’s diary

Macとマイコンに関すること--ワクワクの製作日記

Jetson Nano で AI応用ソフトを作る

今回は 

Jetson nanoにインストールしたOpenFrameworksから、OpecCVDarknet(YOLO)を動かす方法を書きます。

    f:id:TAKEsan:20190613150516j:plain

 Jetson nanoでAI系のソフトをインストールして動かしてみたけれど、これを利用して自分の目標とする「何か」を作るとき、その先膨大な解説と格闘しなければならず、大概行き詰まってしまいます。また、nanoはPI3に比べれば早いといってもIntel の汎用CPUに比べると1/4位のスピード。AIエンジンを利用して応用ソフトを組む場合、インタープリタ型言語であるPython等を使うと、応用部分であきらかに遅くなってしまう傾向がある点は否めません。CythonやSWIGを使えば早くなりますが、結局C言語に戻ってしまうことになります。やはり最初からCやC++等を使って、なるべくCPU処理部分のスピードを上げるのがnanoでは得策と思われます。AIの研究者でもないので基本的な構造学習作業をワープして、即応用に繋がる方法はないものなのでしょうか?。今回は一例としてOpenframeworksとDarknet(yolo)を使って、簡単に応用ソフトを作るためのきっかけがつかめたら良いと考えて記事を書きます。

                 

                                             今回作成したYOLOv2-tinyの動画です。

                 
YOLOv2の動画。認識スピードは遅いものの結構正確でした。認識部分を別スレッドで動かしているので、動画に後から追いつく感じ...

なぜOFとDarknet(YOLO)を結びつけるのか?

 C++はコンパイラ型言語ですから、できあがったアプリの全体スピードが速いことは常識です。ところがプログラム自体も、コンパイル作業も、一般的に非常に煩雑で、専門外の人間にはに取り付き難いことが欠点でもあります。これらの欠点を大きく改善したものがOF(OpenframeWorks)だと私は認識しています。

 OFの優れている点は、オープンソースであることと、文法構成の工夫で初級段階でも高度なグラフィックソフトが出来てしまうこと。さらに先人の作った様々な画像系・サウンド系・その他のライブラリがAdoonという形で簡単に利用できることです。様々なディバイスでアプリが作れますが、私の経験ではMacのXcode環境よりLinuxの方が構造が簡単でプログラムし易いように思います。初心者にとってはコンパイルが時として上手くいかなかったりすることがありますが、日本では2人の先生が初歩から応用までわかりやすく解説していますので参考にしてください。

前橋工科大学 田所先生 

               yoppa org – 第2回 : クリエイティブコーディング基本 – openFrameworks 1

東北大学 小嶋先生 こじ研(openFrameworks)

 YOLOC言語を使って開発されていますから、OFを使ってYOLOが簡単に使えたら、どんなこともできそうな気がします。nanoでAI応用プログラムを作ってとても感無量ってことを前回の記事で書きました。

SWAPを作成する

以降のコンパイル作業時にメモリが不足して、止まってしまう可能性がありますので、SWIPファイルを最初に作成するのがベターです。nanoでの設定方法は、Jetsonの世界では有名な方(私はサメのおじさんと呼んでます)以下で説明しています。

Jetson Nano - Use More Memory! - JetsonHacks

nanoにOFをインストールする

 前回の記事でも少しヒントを書いたのですが、以前私が書いた記事を参考にして、nanoへのインストール手順をまとめた方がいます。

openframeworks jetson nano instructions · GitHub

 実はこの部分の説明をどうしたものか迷っていたので大変助かりました。

 この記事はOFのコンパイル環境を作る説明だけなので、さらに手順が必要です。 以降はOFのディレクトリに入りINSTALL.mdを確認して下さい。前回実行したサンプルを含めてOFのexampleが殆どすべて実行できます。サンプルの内容はmigizoさんが紹介しています。nanoはGPU(OpenGL)の性能がなかなかなので、最新のMac Book Proと比較しても遜色ないスピードで動きます。

openFrameworks(v0.9.8)Examples一覧 - Qiita

OFでOpenCVを使う

 YOLOを使うためにはOpenCVライブラリが必要です。機能や性能が限られます(contribやGPUが使えない)が、OFにはofxOpenCvofxCvという有名なaddonがあります。ただしこれらのaddonを使わなくともnanoに元々インストールされているOpenCVライブラリや、最新のOpenCVがOFから簡単に導入できます。nanoに入っているのはバージョン3.3ですが、それ以降のOpenCVを使う場合は別途インストールが必要です。

  LinuxでOFアプリを作る場合は、myAppsディレクトリの中のemptyExampleをコピーして作って行くことになります。以下の作業でOFからOpenCVが使えるようになります。

1.コピーしたemptyExampleディレクトリの名称を変える。

      ディレクトリ名が最終的なアプリ名称になります

     中に入っているconfig.makeに次の2行を入れます(79行目と107行目を修正)

       PROJECT_LDFLAGS=-DOPENCV `pkg-config --libs opencv`

       PROJECT_CFLAGS = -DOPENCV `pkg-config --cflags opencv`

2.ヘッダーファイルを指定する

     src/ofApp.h  を開き #include "ofMain.h" の後に

     #include "opencv2/core/utility.hpp"

    等、OpenCVに必要なヘッダーファイルを追加します。後はofApp.cppのsetup()、update()、draw()にOpenCVの様々な関数を書いて実行することができます。さらにusiing namespace cv; を宣言すればもっと使いやすくなります。

反則ですが慣れて来れば、ofApp.cppの先頭に書き込んでもOKです。ofApp.cppに自分で用意した関数を使う場合はそちらの方が簡単かもしれません(今回はofApp.cppにヘッダー宣言を入れています)

3.OFとOpenCVの画像データの受け渡し

 一番問題になるのはこの辺りで、OFとOpenCVでは画像データの構造が違うので、変換処理が必要です。この部分を簡単にしたのがofxCVアドオンですが、今回はこのアドオンを使用しない(使えない)ので、基本は次の様な感じで変換します。

 OF image形式からOpenCV mat形式 に変換

  cv::Mat mat;

  ofImage img;

  mat=cv::Mat(img.getHeight(),img.getWidth(),CV_8UC3,img.getPixels().getData());

      ※ CV_8UC3の部分はOpenCVの使用する関数によって変更する必要あり

 OpenCV mat形式からOF image形式 に変換

  img.setFromPixels( mat.ptr(),mat.cols,  mat.rows, OF_IMAGE_COLOR, false);

この2行の変換処理に関して、全体のスピードには殆ど影響が無いようです。

これさえできれば、最新OpenCVの画像処理をOFから自在に利用できるようになります。

Darknetのインストール

 このソフトは全体がC言語で開発されていることと、導入が非常に簡単な点が大きな利点です。Nvidiaではnano用に簡単に導入できるAIエンジン(Tensorflowやtorch)も公開していますが、短縮版です。一般的に著名な標準版のCaffe、Tensorflow、torch等をnanoに移植するには、非常な煩雑なインストール手順と長大なコンパイル時間を否応にも経験しなければなりません。しかも最終的にはGPUメモリ容量の問題でアウト!。その点Darknetは、Makefileを少し修正すればmakeコマンドだけで標準版がインストールできてしまい、コンパイル時間もnanoの場合5〜6分で終わってしまいます。コンパイル後はコマンド一発で動画や静止画の認識テストや学習まで出来て、画像認識性能や認識スピードも現在の最新のモノと遜色ないと言ったらどんな不満が出てくるんでしょうか?しかもnanoのメモリ4GBでギリギリセーフ。

 前から紹介してきたDarknetは、16ビット小数点演算指定ができるので、スピードではまさにnano向きなのです。一方NvidiaではJetson infarencceというjetsonシリーズで非常に有効なTensorRTを利用した3種類の画像認識が出来るソースを公開しています。(よく知られているオレンジやバナナの認識....)でも、前から思っていたのですがWebカメラを使って実行したところ、さほど認識スピードが速くないのです。特にSegNetは顕著。これはTX1,TX2,Xavierでも同じ印象を受けます。また自分で作ったデータを学習させる場合もDIGITSを使った場合、DIGITS本体のインストール作業や実行上の独特の手順が要求されます。でも最終的に出来たモノはスピードや認識精度共YOLOと互角又は性能が落ちる様に思えます。みなさんも是非試してみることをおすすめます。ある意味これがメーカー水準と言うことになることになると思います。

 ではYOLOは完全にnano向きかといえばそうでもありません。今回のDarknetは過去のバージョン(V2)も試せますが、標準データではやはり遅くなるので、アプリを作るのであればTiny yolo v3やTiny yolo v2を使うことになると思います。認識結果もさほど悪く無いことが分かっています。インストールは前回の記事を参考にしてください。

Jetson Nano を使ってみる!! - Take’s diary

今回はMakefileのLIBSO=1 がミソです。

ジャーこれを使ってOFでプログラムを書くには

どうすればいいんでしょうか?Darknetは頻繁に修正を行っているので、いつも最新版を利用したいのですが、addonにするとそういうわけにはいかなくなります。そこでDarknetのsoファイル(Shared Objectファイル)を直接リンクすることにしました。

以下の様にします。

Darknetフォルダが ~/darknet であることを想定しています

1.config.makeに次の2行を入れます(79行目と107行目を修正)

PROJECT_LDFLAGS=-DOPENCV `pkg-config --libs opencv` -lm -pthread -L/usr/local/cuda/lib64 -lcuda -lcudart -lcublas -lcurand -lstdc++  -L ./ ~/darknet/libdarknet.so

 PROJECT_CFLAGS = -DOPENCV `pkg-config --cflags opencv` -DGPU -I/usr/local/cuda/include/ -DCUDNN -DCUDNN_HALF -Wall -Wfatal-errors -Wno-unused-result -Wno-unknown-pragmas -DGPU -DCUDNN_HALF

2. OFのプロジェクトフォルダにリンクファイルを作る

プロジェクトフォルダ/src の中に以下の様にsrc1という名称のリンクフォルダを作成します。リンク先はdarknet/src です。

srcフォルダの中でターミナルを起動して、以下のコマンドを実行します

ln -s ~/darknet/src ./src1   

f:id:TAKEsan:20190613131906p:plain

3.ofApp.cppにヘッダーファイルを指定する

   以下のWebCameraを使った例のofApp.cppを参照して下さい。ここではOpenCVのヘッダーも入れています。

#include "ofApp.h"

#include "opencv2/core/utility.hpp"

//using namespace std;

//using namespace cv;

#include "src1/../include/yolo_v2_class.hpp"    // imported functions from DLL

4.bashrc に以下の1行を追加

多分これでコンパイルが通るはずですが、yolo_v2class.hppが無いという様なエラーが出た場合は、.bashrc に LD_LIBRARY_PATHにyolo_v2class.hpp の入っているディレクトリを追加して下さい。

LD_LIBRARY_PATH=/home/????/darknet/include:$(LD_LIBRARY_PATH}

5.必要ファイルを所定のフォルダにコピーする。

OFで動いたYOLOのweightsサンプルデータは以下の3種類です。一応テスト用にダウンロードしてbinフォルダにコピーします。

https://pjreddie.com/media/files/yolov3-tiny.weights

https://pjreddie.com/media/files/yolov2.weights

https://pjreddie.com/media/files/yolov2-tiny.weights

さらに以下のファイルをdarknet/cfgから

yolov3-tiny.cfg

yolov2.cfg

yolov2-tiny.cfg

darknet/dataから以下のファイルをコピーします

coco.names

また、テスト用にbin/dataの中にフォントファイルcooperBlack.ttfをコピーして下さい。

cooperBlack.ttfは、of/examples/graphics/fontShapesExample/bin/dataに入っています。 

f:id:TAKEsan:20190613135204p:plain

    余計なファイルも入っていますが、binフォルダの中はこんな感じになります。

 DarknetをOFからそっくりそのまま使うので、認識スピードがDarknet本体より遅くなることがありません。以下は最も単純なWebCameraを使った画像認識の例です。これを発展させれば音声や画像、GPIOを含めた様々な応用ソフトが作れることになります。ビデオデータをmapに変換している部分もありますし、何より画像認識部分はOFで簡単に書けるマルチスレッドを利用してます。カメラの表示スピードは殆ど落ちないし、2つの認識スレッドを空きを見ながら処理してるので、条件が良ければ標準のものよりスピードがかなり上がります。(v2とv3に関してはtiny版が5fps位上がるようだ。なぜか標準版は変わりなし)

以下src/ofApp.h

#pragma once

#include "ofMain.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 video;

ofImage img;

};

以下src/main.h

#include "ofMain.h"

#include "ofApp.h"

 

//========================================================================

int main( ){

usleep(2000000); //YOLOがGPUの競合で止まるのを防ぐため2秒のDELAYを設ける

ofSetupOpenGL(1024,768, OF_WINDOW); // <-------- setup the GL context

 

// this kicks off the running of my app

// can be OF_WINDOW or OF_FULLSCREEN

// pass in width and height too:

ofRunApp( new ofApp());

 

}

以下ofApp.cpp          Xcodeからそのままコピーしたのでインデントがおかしいですがご勘弁。OFでは実質update()とdraw()を繰り返しているだけですから、AIを使うと言っても下記のようにこの部分のソース自体は非常に簡潔になります。

#include "ofApp.h"

#include "opencv2/core/utility.hpp"

//using namespace std;

//using namespace cv;

#include "src1/../include/yolo_v2_class.hpp"    // imported functions from DLL

 

//std::string  names_file = "coco.names"; //yolov2を動かす場合

//std::string  cfg_file = "yolov2.cfg";

//std::string  weights_file = "yolov2.weights";

std::string  names_file = "coco.names"; //yolov3-tinyを動かす場合

std::string  cfg_file = "yolov3-tiny.cfg";

std::string  weights_file = "yolov3-tiny.weights";

//std::string  names_file = "coco.names";  //yolov2-tinyを動かす場合

//std::string  cfg_file = "yolov2-tiny.cfg";

//std::string  weights_file = "yolov2-tiny.weights";

float const thresh = 0.20;//この数値を変えることで認識の閾値を調整する

cv::Mat mat;

ofTrueTypeFont cop20,cop50;

 

Detector detector(cfg_file, weights_file);//yoloの初期設定

 

std::vector<bbox_t> result_vec; //認識した結果のバウンディングボックスの座標

float ttt,ttt1;  //Time測定で使用

//以下オブジェクト(クラス)名称を読み込むための関数

std::vector<std::string> objects_names_from_file(std::string const filename) {

    std::ifstream file(filename);

    std::vector<std::string> file_lines;

    if (!file.is_open()) return file_lines;

    for(std::string line; getline(file, line);) file_lines.push_back(line);

    std::cout << "object names loaded \n";

    file.close();

    return file_lines;

}

 

std::vector<std::string> obj_names;  //オブジェクト名称の配列

//以下認識結果のバウンディングボックス座標を元にバウンディングボックスを表示する関数

//画像の中の認識した物体名、座標、大きさや数がわかるので様々な応用が可能

void show_console_result(std::vector<bbox_t> const result_vec, std::vector<std::string> const obj_names) {

 

    for (auto &i : result_vec) {

ofNoFill();

ofSetLineWidth(2);

//Color Set!!

int const colors[6][3] = { { 1,0,1 },{ 0,0,1 },{ 0,1,1 },{ 0,1,0 },{ 1,1,0 },{ 1,0,0 } };

int const offset = i.obj_id * 123457 % 6;

int const color_scale = 150 + (i.obj_id * 123457) % 100;

ofSetColor(colors[offset][0]*color_scale, colors[offset][1]*color_scale, colors[offset][2]*color_scale);

ofDrawRectRounded(i.x,i.y,i.w,i.h,5);

 

string ss;

ss=" "+ obj_names[i.obj_id]+" "+ofToString(i.prob*100,1);

ofSetColor(255);

cop20.drawString(ss, i.x,i.y+15);

    }

}

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

//  DetectNet部分をマルチスレッドにする。

class matmat: public ofThread {

public:

void threadedFunction(){

 

ttt=ofGetElapsedTimef();

//ok=false;

cv::Mat imgx;

cv::cvtColor(mat, imgx, cv::COLOR_RGB2BGR);//OpenCV画像用Mat配列を認識用にRGBからBGRに変換

ok=false;

result_vec = detector.detect(imgx,thresh,false);//一番肝心な認識関数バウンディングボックスの座標列をresult_vecに格納

ok=true;

ttt1=1.0f/(ofGetElapsedTimef()-ttt); //以下FPS表示

std::stringstream stm;

stm<<"Framerate : "<< ofToString(ofGetFrameRate(),2)<<" FPS      YOLO : "<<ofToString(ttt1,2)<<" FPS";

ofSetWindowTitle(stm.str());

 

stopThread();

}

bool ok;

};

matmat Found_X,Found_Y;//2つのタスクを宣言

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

void ofApp::setup(){

obj_names = objects_names_from_file(names_file);//オブジェクト名称をファイルから読み込む

    cop20.load("cooperBlack.ttf",10,true,true,true);//字体の初期設定

    cop50.load("cooperBlack.ttf",20,true,true,true);//字体の初期設定

    video.setDeviceID( 0 );//WebCamera ディバイス番号 通常は0

video.setup(960,720,OF_PIXELS_RGBA);//WebCameraの初期設定

Found_X.ok=true;//マルチスレッドの前処理

Found_Y.ok=true;//マルチスレッドの前処理

 

}

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

void ofApp::update(){

video.update();

if(video.isFrameNew()==true){

        //ビデオフレームが更新されたら2つのタスクの空いている方でAI認識させる

if (Found_X.ok ){

            mat=cv::Mat(video.getHeight(),video.getWidth(),CV_8UC3,video.getPixels().getData());//認識用の画像をOpencv Mat形式に変更

Found_X.stopThread();Found_X.startThread();;

}

    else   if (Found_Y.ok ){

            mat=cv::Mat(video.getHeight(),video.getWidth(),CV_8UC3,video.getPixels().getData());

Found_Y.stopThread();Found_Y.startThread();

}

    }

}

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

void ofApp::draw(){

   ofSetColor(255);

   video.draw( 0, 0 );//WebCamera画像を表示する

   Found_X.lock();//ここで他のタスクをロックしないとプログラムがダウンする

   Found_Y.lock();

    show_console_result(result_vec, obj_names);//バウンディングボックスを描画する

   Found_X.unlock();

   Found_Y.unlock();

   

}

 

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

void ofApp::keyPressed(int key){

 

}

 

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

void ofApp::keyReleased(int key){

 

}

 

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

void ofApp::mouseMoved(int x, int y){

 

}

 

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

void ofApp::mouseDragged(int x, int y, int button){

 

}

 

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

void ofApp::mousePressed(int x, int y, int button){

 

}

 

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

void ofApp::mouseReleased(int x, int y, int button){

 

}

 

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

void ofApp::mouseEntered(int x, int y){

 

}

 

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

void ofApp::mouseExited(int x, int y){

 

}

 

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

void ofApp::windowResized(int w, int h){

 

}

 

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

void ofApp::gotMessage(ofMessage msg){

 

}

 

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

void ofApp::dragEvent(ofDragInfo dragInfo){ 

 

}

OFでYOLOを使う場合の条件

  • yolov3はメモリが不足して使えない。
  • yolov2は設定の変更でなんとか動くが、途中で止まる場合は何回か再実行を試みる。

   yolov2.cfg の最初の方のパラメーターを4箇所変更してみて下さい

     batch=1

                  subdivisions=64

                  width=320

                  height=320

      ※width,heightは32の倍数。nanoでは416が限度

  • yolo v3 tinyは応用できるが現行のサンプルデータはなんか変。プログラム上のバグがある模様。yolov2 tinyより認識率がかなり落ちるようだが、データ数か学習が失敗してる可能性もある。(私の学習させたジャンケンではそんなことはなかった)
  • yolov2 tiny が良いみたい。ただし誤認識が多い

ということを考慮するとでtinyを使った場合、nanoで問題なくアプリが作れます。それもyolov2-tinyが良い様です。

 これ以上を望むならTX2やXavierということになりますが、nanoでも殆ど問題ないことが分かりますでしょうか。

以下画像は、このアプリを使って実際に実行させた結果です。

f:id:TAKEsan:20190613142223p:plain

  yolov2を使った画像認識 yolov2.cfgは上記設定。treash=0.2  かなり良いが遅い->5fps程度。ただし動画は60fps。本来のカメラ性能は30fpsなので、まだまだCPU処理部分に余裕がある=凝ったアプリが作れる

f:id:TAKEsan:20190613142254p:plain

yolov3-tinyを使った画像認識 cfg内容は変更なし。treash=0.1  18~25fpsくらいで認識するがバウンディングボックスの範囲が実際より小さく多重認識がある。前にオリジナルデーターで学習させた時の経験上、使ったcfgファイルが今回のサンプルデータと合っていない可能性がある。(自分で学習させた時は全く問題なかった)

f:id:TAKEsan:20190613142310p:plain

yolov2-tinyを使った画像認識 cfg内容は変更なし。treash=0.5  この数値以下にするとかなり小さい対象物も認識するが誤認識も多くなる。これも18~25fpsくらいで認識する。

最後に外付けSSDに開発環境を移行する。

  SDカードでも単独開発できますが、寿命や信頼性の問題、さらにリードライトスピードが遅いので、開発には向きません。SSDにそっくりそのまま移行した方が、OSのレスポンスも早くなります。

 以下「サメおじさんのブログ」を参考にして、全く問題なく全ての環境がSSDに移行できました。

Jetson Nano - Run on USB Drive - JetsonHacks

 SDカードに最終環境が残っているので、外部で使う時などは、またSD環境に戻してSSD無しで使えることが利点です。SDブートに戻すには、

SD側の/boot/extlinux/extlinux.confの中身

APPEND ${cbootargs} rootfstype=ext4 root=/dev/sda1 rw rootwait

の sda1をmmcblk0p1に変更

SSDブートの場合はsda1に変更します。ただしスペルを間違うと2度と起動しなくなるので注意が必要です。(その場合でも他のUbuntuマシンを使って修正は可能)

 この環境ではブート時に一旦SDを見に行ってからSSDにOSを移行する設定なので、SDは取り付けたままにします。SSDを外した場合起動しなくなるので注意が必要。再度SSDを接続してリブートすれば問題なく動きます。

 Jetsonフォーラムで、M.2スロットに変換ボードを接続すればSSDが接続できるかも....とメーカー側の投稿があり、試してみたら全くディスクとして認識されませんでした。 (ダメ元でもう少しトライしてみますけど)

 f:id:TAKEsan:20190614072135j:plain   f:id:TAKEsan:20190614073157j:plain

アーくやしい!!

 

この頃気候のせいか体調があまり芳しくなく、頭がボーとしていて文章がまとまりません。分かりにくいところはコメントください。

                            ではでは。

 

Jetson Nano を使ってみる!!

Jetson nanoが発売されました。

f:id:TAKEsan:20190408231608j:plain

 一応NvidiaですからAI分野に特化したボードってことになりますが、Pi3 B+にMobidiusを追加した価格より、機能面を考慮すると大幅に安いというような衝撃的な仕様でもあります。

 実際はどうなのかってのが今回の内容。NvidiaのJetson関連ボードはTK1から始まってTX1、TX2、Xavierとなってますが、今回のボードはTK1とTX1の中間ぐらいの構成になってます。

 いつものように、メーカーの宣伝が派手なので本当のところは?って、誰でも思うところだと思います。私の理想とする環境はかなり偏ってるので、そのつもりで読んでいただければと思います。

OSのインストールは超簡単。

 今までのTXシリーズは内部eMMc起動のために煩わしい手順が必要でした。今回はそれをやめてSSDブートに変更したため、インストールで引っかかりにくくなりました。メーカーのインストール手順で何の問題もありませんが、最初にUbuntuが立ち上がるまではPi3より遙かに早かったとだけは言えます。インストール完了時点でCUDAもCUDNNもOPENCV3.3もインストールされてるので文句の付け様がありません。

 標準ではWIFIやBlutoothは付いていません。CPUを取り外すとM2コネクタが付いているので汎用のWIFIカードを付けるか、USBドングルってことになります。今回は手っ取り早く余ってるWIFIドングルを使ってみました。

 Jetsonシリーズの常で、日本語環境には向かないので、インストール時は注意してください。ある程度環境を作ってから日本語化する方が無難。マーSDカード(32GB以上)枚数が経済的に許す限りOSをたくさん作っておけば良いことですから、そんなに心配する必要も無いとは思います。

CPUのスピードは?

  いつもの簡易テストでメインコアのCPUテストをしてみるとPI3(Pi3B+1200hzsでオーバークロック)が56.30secに対して 、Nanoは最大能力にすると17.48secでした。そんなに早くはありませんが、それでもPI3B+の約3.2倍!!

 Nanoのオーバークロック方法は、今までのJetsonシリーズとはとは違い、コマンド化されたようです。USB電源から起動可能ですがオーバークロックさせると、システムが止まってしまいます。事前に5V 4A電源(これが上限のようです)とジャンパーピンを取り付ける必要があります。さらにFANを付け足すと一応安心。

 実際動かしてみると、普通に使うんであればこれ以上のスピードはいらないんじゃないかって思いました。でもそこはLunux。汎用にするには少し問題もあることは使ってる方なら分かると思います。

f:id:TAKEsan:20190409083836p:plain

最大の能力を引き出すには、以下2つのコマンドを続けて実行します。

sudo nvpmodel -m 0

sudo jetson_clocks

nvpmodelはNanoの場合0か1の2者選択のみのようでした。0が最大で1が最小です。また、現在の設定値(CPU,GPUの周波数)を確認するには  sudo jetson_clocks --show  を実行します、

 以下の動画はプレインストールされているCUDA Example(GPUを使ったサンプル)を実行させたものですが、この段階でスピード的にはTX2でした(思っていたよりかなり早かった)。

      

Openframeworksが動くか?

 動きました。過去に紹介したTX2の手順で基本的に10.01のインストールが可能です。ただし公式のものは、Ubuntu 18.04でコンパイルエラーになるので、今のところnightly buildsを使う必要があります。また、tess2及びkissのライブラリも再コンパイルが必要でした。すぐにバージョンアップされる可能性があるので、インストール方法は時期を改めて紹介します。OFのnightly builds内容を見ると次期バージョンでは標準でOpenCV4(注目すべきはdnn!!)が使えるようです。この画像は実際にNanoで実行させたものですが、今持ってる最新のMacBook Proとスピードが変わらない....。早っ!!

     

DARKNET(YOLO)のインストール

 今までの経験上インストールにはさほど問題なかったのですが、今回も、これを使わせていただきました。ソース内容が時間単位で変更されています、この方は一体どのくらいの能力を秘めてるんでしょう。DARKNETを利用したC++でのソフト開発はなかなかハードルが高いのですが、最後の説明のように本来のスピードを保ったままOpenframeworksに移植できています。

Nanoで使うには、Makefileの修正が必要です。以下最初の方で赤文字部分の修正が必要(6行の修正)。NanoはTX1とCUDAアーキテクチャが同じのようです(ここでちょっと手こずりました)。修正後 make コマンド一発で、一応DARKNETが動く環境ができます。

4/27:追記----------------------------------------------------------------------------

.bashにCUDAのパスを追加しないとnvccが使えないのでエラーが出ます。なので以下2行の追加必要。

export PATH=/usr/local/cuda/bin:${PATH}

export LD_LIBRARY_PATH=/usr/local/cuda/lib64:${LD_LIBRARY_PATH}

----------------------------------------------------------------------------

 

***********************************************************

GPU=1

CUDNN=1

CUDNN_HALF=1

OPENCV=1

AVX=0

OPENMP=1

LIBSO=1

ZED_CAMERA=0

 

# set GPU=1 and CUDNN=1 to speedup on GPU

# set CUDNN_HALF=1 to further speedup 3 x times (Mixed-precision on Tensor Cores) GPU: Volta, Xavier, Turing and higher

# set AVX=1 and OPENMP=1 to speedup on CPU (if error occurs then set AVX=0)

 

DEBUG=0

 

ARCH= -gencode arch=compute_30,code=sm_30 \

      -gencode arch=compute_35,code=sm_35 \

      -gencode arch=compute_50,code=[sm_50,compute_50] \

      -gencode arch=compute_52,code=[sm_52,compute_52] \

#   -gencode arch=compute_61,code=[sm_61,compute_61]

 

OS := $(shell uname)

 

# Tesla V100

# ARCH= -gencode arch=compute_70,code=[sm_70,compute_70]

 

# GeForce RTX 2080 Ti, RTX 2080, RTX 2070, Quadro RTX 8000, Quadro RTX 6000, Quadro RTX 5000, Tesla T4, XNOR Tensor Cores

# ARCH= -gencode arch=compute_75,code=[sm_75,compute_75]

 

# Jetson XAVIER

# ARCH= -gencode arch=compute_72,code=[sm_72,compute_72]

 

# GTX 1080, GTX 1070, GTX 1060, GTX 1050, GTX 1030, Titan Xp, Tesla P40, Tesla P4

# ARCH= -gencode arch=compute_61,code=sm_61 -gencode arch=compute_61,code=compute_61

 

# GP100/Tesla P100 - DGX-1

# ARCH= -gencode arch=compute_60,code=sm_60

 

# For Jetson TX1, Tegra X1, DRIVE CX, DRIVE PX - uncomment:

 ARCH= -gencode arch=compute_53,code=[sm_53,compute_53]

 

# For Jetson Tx2 or Drive-PX2 uncomment:

# ARCH= -gencode arch=compute_62,code=[sm_62,compute_62]

 

 *********************************************************

ジャー肝心のAI認識です。

 結論的にはTX2とかXavier並の性能に追いつかせることは無理のようです、いつものようにコツが必要。YOLO3とかYOLO2では満足なスピードは得られません。つまりかなり遅いし、YOLO3に至ってはアプリに組み込むとメモリが完全に不足する雰囲気です。でもNvidiaでの公式発表ではTiny YOLO3がかなりのスピードで実行できているのが気になるところ。

 Tiny YOLO3の学習済みデータの大きさは、標準(大体200MB前後)のものより1/6(35MB)程度なので認識率は正直?です。今回は今まで作ったジャンケンのアノテーション済みデータを元にTiny YOLO3を試してみました

f:id:TAKEsan:20190409072556p:plain

    データ:16,115枚

    クラス数:4 (サーモグラフィー白黒画像を使ったグーチョキパーと顔の認識)

    母艦:intel i7 6700K、 GPU GTX 1080Ti

lossが最小値から上がってきているの8000回目で止めています。ちなみに採用したデータは6000回目のもので、この時の計算時間はなんとわずか45分でした。ただしグラフが大きく振れているので認識のばらつきがある気配...。以下はYolov2の学習状況ですが、比べてみるとLossの下限を含めてずいぶん違うことが分かると思います。v2でも6000回がベターで学習時間は2時間程度でした。今回はCUDNN_HALF設定(16ビット浮動小数点)なので前回より早かった可能性があります。でも、今回のように本格的な学習作業(画像の大きなモノ)は、nanoではトライするだけ無駄と思われますのでご注意を.....。多分メモリーが不足して初期段階で止まってしまうと思われます。

f:id:TAKEsan:20190409073929p:plain

 今回nanoテスト用に使ったサンプルソース(以下の動画)は、元々XavierでYOLO3を動かすために自分で作ったものです。PI3で赤外線センサーカメラ表示処理をした後(左側のディスプレイ)、WIFIで画像データをNanoに送り、認識が確実になるよう画像拡大補間やノイズ処理などを施した上で、画像認識をさせた後(右側のディスプレイ)、その認識座標他を再度PI3に送って認識結果を表示させています。「おうちクラウドAI」と名付けました。つまり、離れたところにPI3を置けば、PI3でリアルタイム画像認識をしているような感覚です。  

 赤外線センサーは、Nano直付けでも良い(ただしSPI設定はかなり難しいので今後に期待)のですが、使用用途から考えてWiFiの方が断然有利なので、あえて手間のかかる処理を加えて画像と各種データの送受信をしています。この程度のAI認識ではTinyでも特に問題が無いことが分かります。PI3、Nano双方でGPIOが使えるので応用は無限大!!

      

何よりスピードが早い。今回はLEPTON3(FLIRの赤外線センサーカメラ)の画像読み込みにPIを使ってますが、ESP8266でも可能。3年間積み上げてきたものです。たった1万円強のJetson nanoで動くとなると感慨もひとしおです。

つまりJetson Nanoって....。

 判断は皆さんにお任せします。

 

Jetson Nanoの記事を追加しました。 

takesan.hatenablog.com

 

追伸

おかげさまで、今まで作ったすべてのモノをさらに進化させて展示することができました。いろいろな意見をいただいて、とても有意義な2日間となりました。みなさんありがとうございました。

f:id:TAKEsan:20190510223431p:plain

 様々なディバイスで独立した処理を実行しています。BosonとLeptonは今僕にできるサイコウの詳細な表示が実現できました。XavierもnanoもiPhoneやm5Stackでも!!。この画像に写ってるだけでも「おうちクラウドAI」は2系統で同時実行してます。6種類のディスプレイで違った処理をしてるのですが判別できるでしょうか?(設定と起動までが大変な作業でしたが....)

 

 

 

素敵な花が咲きました。

20年ほど前東京で仕事をしていた頃

 世界洋ラン展で買ってきたパフィオペディラムです。本当はすべてが小豆色のはずだったのですが、2年間育ててやっと咲いた花が「点花」と呼ばれる斑点の付いた花びらでした。パフィオペディラムには原種と整形花があって、この花は整形花に属します。要するに人の手によって改良が加えられた花です。

 愛好家では必衰の掛け合わせによる親株の名前も全く分からなくなりました。すなわち価値はゼロです。

 この種の花は独特の人工的な表情があって、好き嫌いがあります。私もどちらかというとあまり好きではありませんでした。

 購入してから2年目に咲いた花は、少し小さめでしたが、透き通るような白と清楚な緑。そして鮮やかで全体にちりばめられた小豆色の点と形が、派手と言うよりも清々しくとても印象的でした。

f:id:TAKEsan:20190319182721j:plain

 月日がたち、毎年咲いていた株も少しずつ小さくなって、5年ぐらい前から花が全く咲かなくなりました。夏場は毎年玄関先の日陰に置いて、冬はいつもの窓際。私にとってはあまりにも素晴らしかったこの花の印象が忘れられなかったんですね。その間に株がどんどん大きくなりました。

 去年の11月花芽が付いてとても喜んでから5ヶ月。こんな花が咲きました。縦は10cm。今までで一番大きな花です。

f:id:TAKEsan:20190319182900j:plain

乾杯!!

 

YOLOオリジナルデータの学習その2(追加学習)

今回は

 Joseph Chet Redmonさんの本家Darknet  https://pjreddie.com/darknet/yolo/  ではなく、AlexeyAB さんのDarknetを使って追加学習させてみました。前回の記事ではHow to train YOLOv2 to detect custom objectsの解説が全てと書いていますが AlexeyAB さんのDarknetの場合はhttps://github.com/AlexeyAB/darknetが全てです。私が確認した範囲では、認識率は両者ほぼ互角。単精度浮動小数点数指定ができるため、認識スピードがかなり向上できる他に、最近分かったことですが、学習の最適ポイントが掴みやすいなど、かなりの優れものでした(解説を良く読んでみればわかるのですが.....)。

 前回の学習では

takesan.hatenablog.com

 ジャンケンの3クラスのみの学習をさせたのですが、どうしてもグーが人間の顔と勘違いしやすい傾向がありました。元画像が赤外線カメラなので、そのような傾向があるかとは思いますが、新たに顔を追加学習させたら、さらに良い結果になりそうな気配です。じゃー「どうしたら追加学習できるのか?」ということが、今回のテーマです。

                    f:id:TAKEsan:20190111114732p:plain

      顔がGooになってしまうことがある....のですが、顔データを追加学習させると、こうなります

       

この画像はLEPTON3.0はPI3に接続。同一LAN内につないだJetson Xavierで画像表示とジャンケン認識をさせてます。

くどくど説明を書いてもかえって分かりにくくなるので、今回は要点のみとします。

ラベリング作業

  • 顔の動画を撮影して、2000枚程度の画像を作成した。
  • labelimg/data の中にあるpredefined_class.txtに4番目のクラス(今回はFace)を追加する。
  • とりあえず別のフォルダで顔のみをラベリングして作ったデータを、前回のデータフォルダにコピーする。
  • 前回のアノテーション済みの画像の中に顔が写っている場合は、追加指定を行う。
  • 入力ミスで思い当たりのないクラスが増えていることがある。自分で設定したクラスがひとりでにできていたら、前に遡って必ずその部分を消すことが必要。これをしないと、学習中意味不明のエラーまたはメモリオーバーが表示されて、悩まされることになる。

f:id:TAKEsan:20190111114852p:plain

  • 今回は精度の高い最新のyolov3で学習。単精度浮動小数点演算機能を外してビルドし直した。(つまり倍精度演算)

Yolov3_voc.fgの変更点

  • 認識精度を上げる方法はいくつかあって、https://github.com/AlexeyAB/darknetkこの中のHow to improve object detection:  を参照。
  • 一番有効なのは、WEIGHTとHEIGHTの数値を大きくしてトレーニングすること。
  • 一般的には416x416。精度を上げるには608x608または832x832とする。ただしyolov3の場合608x608で学習させると、私の環境ではメモリーオーバーで止まる。今回618x618の場合は subdivisions=16 とした。
  • classesの数値を3箇所変更(今回のクラス追加で4に変更した)
  • filtersの数値は YOLOv3の場合(classes + 5)x3)となる。これも3箇所変更。今回は27。本家Yolo(こちらはYOLOv2で(classes+5)x5)とは、この部分の計算方法が違う
  • cfgファイルは、yolov3.cfgでもyolov3_voc.cfgでもあまり変わらないようだ(ただしパラメーターが微妙に違う)。解説の通りyolov3_voc.cfgを使う方が無難。

obj.namesの変更点

  • obj.namesにクラス名を1つ追加。今回はFaceとした。

obj.dataの変更点

  • obj.data のclassesの数値を変更。今回は4。これが忘れがちで、学習時原因不明エラーの要因となる。

学習

  • AlexeyAB さんのdarknetは状況を確認するためにlossの数値と学習回数を関連付けたグラフが表示される。
  • 最適学習状況の判別機能が備わっている。学習コマンド実行時最後に-map オプションの指定によりmApの現状の数値がグラフに書き込まれる。
  • 学習は8000回目ぐらいが上限で良い様だ。5000前後でmap コマンドを実行して確認しiouとmapの一番高いものを選ぶ
  • Lossは小さい方が良いが、データによりそれ以上下がらない場合あり。0.3以下が望ましい
  • mApが同じでも認識が良好である場合とそうでない場合がある。なるべく7000回ぐらい試してサンプル画像をTESTしてみるのがベターの様だ。
  • Lossが1以下に下がらない場合、クラス数設定が各部で違っている場合が多い=使えない。
  • mapが90%を越すころから使い物になってくる。開始から2.5時間から3時間の間。
  • 最初の1000回の所要時間で学習終了時刻が、ほぼ予想できる(当たり前か...)。

同一データで、416x416と608x608とした場合の学習内容比較

母艦の性能  CPU:i7 6700K(16GB)GPU:  GTX1080Ti  ハードディスク主体

cfgファイルのWEIGHTとHEIGHTについて416x416指定の場合

  • 以下の結果で1000回あたりほぼ50分必要だった。

      時刻           回数           iou              mAp

       8:48            1000           測定しない    測定しない

      10:26           3000           75.66           90.5

      11:14           4000           75.97           90.64

      12:07           5000           79.16           90.76

      12:57           6000           78.83           90.76

      13:43           7000           78.88           90.77

      14:04           7400           79.26           90.77      この時点でloss=0.273

 

f:id:TAKEsan:20190111114940p:plain

  • なぜかmapが正しく表示されていない。416x416は7000回目のデータを採用した。

cfgファイルのWEIGHTとHEIGHTについて608x608指定の場合

  • メモリーオーバー対策にsubdivisions=16とした。1000回あたり約130分必要。

      時刻           回数           iou              mAp

        17.09           1000           測定しない    測定しない

        22:37           3000           76.77           90.4

      0:29            4000           79.45           90.5

      2:24            5000           79.57           90.71

      4:16            6000           78.55           90.70

      5:59            7000           79.52           90.71

      7:47            7400           78.60           90.72      この時点でloss=0.2766

  • グラフでもわかる様に収束が遅くばらつきがある。

f:id:TAKEsan:20190111115555p:plain

  • 608x608は6000回目のデータを採用した。

考察

  • 画像でテストしてみると、今回はなぜか608x608は、416x416より認識率が落ちる様だ。もともとLEPTON画像は160x120の画像なので、少し無理があるのかもしれない。最終的に採用したデータは416x416で学習させた7000回目のものとした。(最初に貼り付けた動画はこのデータを使ってます)
  • 認識時はcfgファイル内容を608x608に変更すると認識率は上がる。ただしスピードは犠牲となる。
  • テストデータの比率を10%としたが、これを10%以上に上げると多少認識率が上がる可能性はある。
  • 倍精度指定での学習データでも、単精度画像認識が可能だった。
  • 倍精度浮動小数点指定と、単精度浮動小数点指定で学習結果と時間に差が出るのかどうかのテストはまだだが、Jetson Xavierで単精度浮動小数点指定でビルドしたDarknetの学習をさせてみた場合、 YoloV2のジャンケン3クラス(416x416)の場合で1000回あたり約60分だった。これはJetson Xavier単独でも学習作業が可能であることを示す。

 世の中クラウド学習が方々で話題になってますが、色々制約があるのも事実だと思われます。身近な機材で目の前で刻々と変化する学習状態を確認するのも、また、オツなものですよ。(2日も3日も動かすわけじゃないので)

                      ってことで。今回はおしまい。

 

 

 

 

 

Jetson TX2 にインストールした OpenFremeworks でも YOLO その2 (Yolo on Jetson TX2 with OpenFremeworks(Part 2))

  1. 前回の記事で、 In the last article,

 Jetson TX2 にインストールした OpenFremeworks でも 16bit浮動小数点YOLOを動かしてみました。が、アプリを立ち上げた途端止まってしまったり、早そうだけどそうでも無いような感じでした。しかも2つのスレッドで動かしているため、認識スピードの計測が難しそうなので無視していました。

    f:id:TAKEsan:20181007074429j:plain

 YOLO3は今回作成したソフトの場合、TX2ではメモリーオーバーで動かないようですが、本来のYOLO3が遅いながらも実行できるので、もう少しトライしてみる価値がありそうです。

 で、引き続きYOLO2のweightデータを使います。インストール方法は前回の通りです。

Even OpenFremeworks installed on Jetson TX 2 tried running 16 bit floating point YOLO. Although it stopped as soon as the application was launched, it seemed to be fast, but it seemed like it was not so. Moreover, because it moves with two threads, it seems to be difficult to measure recognition speed, so I ignored it.
In the case of software created this time YOLO 3 seems not to work with memory over in TX 2, but since original YOLO 3 can be executed slowly, it seems to be worth a little more trying.
We will continue to use YOLO 2's weight data. The installation method is as last time.

まず、立ち上げた時頻繁に止まる現象は First, the phenomenon that stops frequently when launched

 yolov2.chgの設定が原因のようでした。最初の方のbachとsubdivisionsの設定数値を下記のようにすると、最悪2回程度のアプリ立ち上げ直しで上手く動きます。要はメモリー設定がらみです。

It seemed to be caused by setting yolov2.chg. If you set the setting values ​​of bach and subdivisions of the first one like the following, it will work well with restarting the app about twice the worst. The point is in memory setting.

f:id:TAKEsan:20181006232037p:plain

今回は、This time,

 より正確に複数スレッドを動作させるために修正したソースofApp.cppを最後に付けました。これで、かなり正確に認識スピードが計測できるようになりました。また、認識スピードや認識の正確さに関係のあるyolov2.cfgのweightとheightの値を両方とも試しに352に設定してみます。これで実行した動画が........。

Lastly we added the modified source ofApp.cpp to make multiple threads work more accurately. With this, it is possible to measure recognition speed fairly accurately. Also try setting both the weight and height values ​​of yolov2.cfg, which are related to recognition speed and accuracy of recognition, to 352 for testing. The video that I ran in this .........

    

         画像上左上のターミナル画面が認識スピード

 ここで確認できるのは、後半ターボが効いているように最高30~40fpsくらいで画像の認識をしている?こと。画像で写しているMacBook上の動画は、早回ししているものを利用していますが十分追従しているようです。つまり、エエーホントかよーでした。ここで使っているWEB CAMERAは最高30fpsなので、1コマあたり2回ぐらい認識している場合があるようです。動画前半のように、画像全体が白っぽいと15fpsぐらいに下がってしまうところが興味深いのですが、このあたりはガンマ補正で修正できる範囲です。前回と違って明らかにクラスを表示しているボックスの表示が早いことが確認できると思います。しかも結構正確!!。認識したいクラスを絞っで、独自の学習をさせたデータを利用すると、かなりの物が作れそうです。

※2019/6/20   Jetpack4.2  及び最新のDarknetでは14fpsが上限でした。なぜこの当時早くなったか只今調査中です。

 ちなみに608x608では5.5fps前後、416x416では10.8fps前後でした。こちらの方は画像のホワイトバランスの影響が出ないので、TX2にとって352x352(32の倍数)前後の設定がもっとも効率が良いのかもしれませんね。現場で今回のソースを応用する場合、Xavierは必要ないかもしれません。(安くなったし....Switchで成功してしばらく販売継続しそうだし...)

As you can see here, do you recognize the image as much as 30 to 40 fps as the second half turbo works? about. The movie on the MacBook shot in the image seems to be tracking enough although it is using the one being fast-forwarded. Really ?Since WEB CAMERA used here has a maximum of 30 fps, it seems there are cases where it is recognized about twice per frame. As in the first half of the movie, if the entire image is whitish, it is interesting that it goes down to around 15 fps, but this area is a range that can be corrected by gamma correction. Unlike the last time I think that it is possible to confirm that the display of the box clearly showing the class is fast. And pretty accurate! ! .

By the way it was around 5.5 fps at 608 x 608, around 10.8 fps at 416 x 416. As this case has no influence of the white balance of the image, the setting around 352 x 352 (multiples of 32) for TX 2 may be the most efficient. Xavier may not be necessary when applying this source for the case. (It became cheap and it seems to continue selling for a while after succeeding at Nintendo Switch ...)

以下ofApp.cpp

 ここでのビデオ入力画像の大きさを640x480(setup()内)に設定しています。これを大きくすることでYOLOの認識率がアップするようですが、TX2にとっては結構負担になるので入力画像は小さくしています。今回YOLO側の読み込み画像が352x352なのであまり問題にならないようです。


#include "ofApp.h"
#include "src1/yolo_v2_class.hpp"    // imported functions from DLL

std::string  names_file = 	"data/coco.names";
std::string  cfg_file = 	"cfg/yolov2.cfg";
std::string  weights_file = "yolov2.weights";

float const thresh = 0.25;

cv::Mat 		mat;
ofTrueTypeFont cop20,cop50;
Detector detector(cfg_file, weights_file);
std::vector result_vec,result_vec1 ,result_vec2;
image_t xxx,yyy;
std::vector<bbox_t> objects_names_from_file(std::string const filename) {
    std::ifstream file(filename);
    std::vector file_lines;
    if (!file.is_open()) return file_lines;
    for(std::string line; getline(file, line);) file_lines.push_back(line);
    std::cout << "object names loaded \n";
    file.close();
    return file_lines;
}
std::vector obj_names;
  
void show_console_result(std::vector<bbox_t> const result_vec, std::vector<std::string> const obj_names) {

    for (auto &i : result_vec) {
        if (obj_names.size() > i.obj_id) 
			ofNoFill();
			ofSetLineWidth(1);
        
			//Color Set!!
			int const colors[6][3] = { { 1,0,1 },{ 0,0,1 },{ 0,1,1 },{ 0,1,0 },{ 1,1,0 },{ 1,0,0 } };
			int const offset = i.obj_id * 123457 % 6;
			int const color_scale = 150 + (i.obj_id * 123457) % 100;
			ofSetColor(colors[offset][0]*color_scale, colors[offset][1]*color_scale, colors[offset][2]*color_scale);
			ofDrawRectRounded(i.x,i.y,i.w,i.h,5);
			//Mozi Draw!!
			string ss;
			ss=" "+ obj_names[i.obj_id]+" "+ofToString(i.prob*100,1);
			//ofFill();
			//ofDrawRectangle(i.x,i.y,i.w,20);
			ofSetColor(255);
			cop20.drawString(ss, i.x,i.y+15);
			
    }
}
static image_t make_empty_image(int w, int h, int c)
    {
        image_t out;
        out.data = 0;
        out.h = h;
        out.w = w;
        out.c = c;
        return out;
    }
static image_t make_image_custom(int w, int h, int c)
    {
        image_t out = make_empty_image(w, h, c);
        out.data = (float *)calloc(h*w*c, sizeof(float));
        return out;
    }
static image_t ipl_to_image(IplImage* src)
    {
        unsigned char *data = (unsigned char *)src->imageData;
        int h = src->height;
        int w = src->width;
        int c = src->nChannels;
        int step = src->widthStep;
        image_t out = make_image_custom(w, h, c);
        int count = 0;
        for (int k = 0; k < c; ++k) {
            for (int i = 0; i < h; ++i) {
                int i_step = i*step;
                for (int j = 0; j < w; ++j) {
                    out.data[count++] = data[i_step + j*c + k] / 255.;
                }
            }
        }
        return out;
    }
//--------------------------------------------------------------
void detect_x(image_t x)
{
	    result_vec = detector.detect_resized(x,640,480,thresh,false);
}
void detect_y(image_t x)
{
	    result_vec1 = detector.detect_resized(x,640,480,thresh,false);
}
//--------------------------------------------------------------
//  DetectNet部分をマルチスレッドにする。No1
class Detect_x: public ofThread {
public:
	void threadedFunction(){
		ok=false;
		detect_x(xxx);
		ok=true;
	}
	bool ok;
};
Detect_x Found_X;
//--------------------------------------------------------------
//  DetectNet部分をマルチスレッドにする。No2
class Detect_y: public ofThread {
public:
	void threadedFunction(){
		ok1=false;
		detect_y(yyy);
		ok1=true;
	}
	bool ok1;
};
Detect_y Found_Y;
int a1=0,b1=0;
//--------------------------------------------------------------
void ofApp::setup(){
    obj_names = objects_names_from_file(names_file);
    cop20.load("cooperBlack.ttf",10,true,true,true);
    cop50.load("cooperBlack.ttf",30,true,true,true);
    img.allocate(640,480,OF_IMAGE_COLOR);
    video.setDeviceID( 0 );
	video.setup(640,480,OF_PIXELS_RGBA);
	Found_X.ok=true;
	Found_Y.ok1=true;
}
//--------------------------------------------------------------
void ofApp::update(){
bool q1,q2;
	video.update();
	if(video.isFrameNew()==true){
    img.setFromPixels(video.getPixels().getData(),video.getWidth(),video.getHeight(),OF_IMAGE_COLOR);
		mat=ofxCv::toCv(img);
        q1=false;
	    q2=false;
	  while(q1==false && q2==false){  //2つのスレッドのどちらかがスタートできるまで待つ-->全体のFPSが下がるが正確な計測ができる。
            if (Found_X.ok){
                cv::Mat imgx;
                cv::cvtColor(mat,imgx, cv::COLOR_RGB2BGR);
                std::shared_ptr<image_t> image_ptr(new image_t, [](image_t *imgx) { detector.free_image(*imgx); delete imgx; });
			    std::shared_ptr<IplImage> ipl_small = std::make_shared<IplImage>(imgx);
                *image_ptr = ipl_to_image(ipl_small.get());
                xxx=*image_ptr;
                       
                Found_X.startThread();
                q1=true;
            }
            else if(Found_Y.ok1)  {      //elseが必要!! これでないとプログラムがハングする。
                cv::Mat imgy;
                cv::cvtColor(mat,imgy, cv::COLOR_RGB2BGR);
                std::shared_ptr<image_t> image_ptr(new image_t, [](image_t *imgy) { detector.free_image(*imgy); delete imgy; });
			    std::shared_ptr<IplImage> ipl_small = std::make_shared<IplImage>(imgy);
                *image_ptr = ipl_to_image(ipl_small.get());
                yyy=*image_ptr;
                Found_Y.startThread();
                q2=true;
            }
      }
    }
}
//--------------------------------------------------------------
void ofApp::draw(){

    ofLog() << ofGetFrameRate();  //zissitu FPS!!
    ofSetColor(255);
    video.draw( 0, 0 );
    bool q1=false;
    bool q2=false;
    while(q1==false && q2==false){
        if (Found_X.ok){
            Found_X.lock();
            show_console_result(result_vec, obj_names);
            result_vec2=result_vec;
            Found_X.unlock();
            q1=true;
        }
        if(Found_Y.ok1) {  //els は必要なし!!スレッドが動いていなければどちらか又は両方表示させる。--->ちらつき防止
            Found_Y.lock();
            show_console_result(result_vec1, obj_names);
            result_vec2=result_vec1;
            Found_Y.unlock();
            q2=true;
        }

    }
}
//--------------------------------------------------------------
void ofApp::keyPressed(int key){
}
//--------------------------------------------------------------
void ofApp::keyReleased(int key){
}
//--------------------------------------------------------------
void ofApp::mouseMoved(int x, int y){
}
//--------------------------------------------------------------
void ofApp::mouseDragged(int x, int y, int button){
}
//--------------------------------------------------------------
void ofApp::mousePressed(int x, int y, int button){
}
//--------------------------------------------------------------
void ofApp::mouseReleased(int x, int y, int button){
}
//--------------------------------------------------------------
void ofApp::mouseEntered(int x, int y){
}
//--------------------------------------------------------------
void ofApp::mouseExited(int x, int y){
}
//--------------------------------------------------------------
void ofApp::windowResized(int w, int h){
}
//--------------------------------------------------------------
void ofApp::gotMessage(ofMessage msg){
}
//--------------------------------------------------------------
void ofApp::dragEvent(ofDragInfo dragInfo){
}

               では。また.......。 See you !

 

Jetson TX2 にインストールした OpenFremeworks でも YOLOを動かす。

TX2でYOLOがどのくらいのスピードになるか?

f:id:TAKEsan:20181004220526j:plain

      ちょっと埃をかぶってますが、以下の動画は紛れもなくこのTX2で実行してます。

 これは、weightデータがTiny YOLOではなく標準のYOLO V2です。使ったデータは今回の場合80クラスを認識しますが、コンピューターにとって全世界が80項目しかないので誤認識があります。でもハマったものはなかなか素晴らしい結果でした。

     今回TX2でYOLOを実行させた結果です。画像のスピードは問題なし。YOLOの認識速度もなかなか!!(画像がボケているのにこの辺りがAIですかね......)

 前の記事でJetson XvierにインストールしたopenFrameworksYOLOを動かしてみましたが、なかなか良い結果が出たので、じゃーTX2で実行したらどうなるのかってのが今回の実験です。

TX2へのOpenframeworksのインストールはこの記事を参照して下さい。

takesan.hatenablog.com

 で、YOLOのインストールは

takesan.hatenablog.com

TX2Xavierに勝とうっていうのは所詮無理な話で、

 少しでもXavierに近づけるためにマルチスレッドを利用します(OF上ではofThreadを利用)。Xavierでも裏スレッドを1個使って実現させましたが、今回は2個使いますGPUはマルチスレッド対応していないということが頭に入ってました。でも、「微妙に時間をずらすことでGPUをうまく使えないか」ってことが今回の発想です。ここでもやはりデータ変換が鍵を握ってて、ちょっと時間がかかりましたが、なんとなく狙い通りになってるようです。

まずyolov2.cfgの設定ですが

最初の方を

batch=32

subdivisions=8

weight=416

height=416

にしました。weight、heightとも608にするのが理想的ですが、あまり認識に変化が無いようなのでスピード重視です。yolov2.cfgが何者かは、前の記事を確認して下さい。

 また、Web CameraのopenFrameworks側設定画像は640x480。相当粗い画像ですがYOLOはそれでも満足の行く結果を出してしまいます。これもスピード対策。openFrameworkeのソースは次の通りです。

 ただし、まだバグが残っているようでmake runしてもエラーが出る場合があります。その場合は、何度もmake run してみて下さい(Xavierも同じ)

main.cpp


#include "ofMain.h"
#include "ofApp.h"
//========================================================================
int main( ){

	ofSetupOpenGL(640,480, OF_WINDOW);			// <-------- setup the GL context

	// this kicks off the running of my app
	// can be OF_WINDOW or OF_FULLSCREEN
	// pass in width and height too:
	ofRunApp( new ofApp());

}

で、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 video;
		ofImage img;

最後にofApp.cpp


#include "ofApp.h"
#include "src1/yolo_v2_class.hpp"    // imported functions from DLL

std::string  names_file = 	"data/coco.names";
std::string  cfg_file = 	"cfg/yolov2.cfg";
std::string  weights_file = "yolov2.weights";

float const thresh = 0.20;

cv::Mat 		mat;
ofTrueTypeFont cop20,cop50;
Detector detector(cfg_file, weights_file);
std::vector result_vec,result_vec1,result_vec2 ;
float ttt,ttt1,ttt2,sss,sss1;  //Time
image_t xxx,yyy,zzz;
std::vector objects_names_from_file(std::string const filename) {
    std::ifstream file(filename);
    std::vector file_lines;
    if (!file.is_open()) return file_lines;
    for(std::string line; getline(file, line);) file_lines.push_back(line);
    std::cout << "object names loaded \n";
    file.close();
    return file_lines;
}

std::vector obj_names;
  
void show_console_result(std::vector const result_vec, std::vector const obj_names) {

    for (auto &i : result_vec) {
        if (obj_names.size() > i.obj_id) 
			ofNoFill();
			ofSetLineWidth(1);
        
			//Color Set!!
			int const colors[6][3] = { { 1,0,1 },{ 0,0,1 },{ 0,1,1 },{ 0,1,0 },{ 1,1,0 },{ 1,0,0 } };
			int const offset = i.obj_id * 123457 % 6;
			int const color_scale = 150 + (i.obj_id * 123457) % 100;
			ofSetColor(colors[offset][0]*color_scale, colors[offset][1]*color_scale, colors[offset][2]*color_scale);
			ofDrawRectRounded(i.x,i.y,i.w,i.h,5);
			//Mozi Draw!!
			string ss;
			ss=" "+ obj_names[i.obj_id]+" "+ofToString(i.prob*100,1);
			//ofFill();
			//ofDrawRectangle(i.x,i.y,i.w,20);
			ofSetColor(255);
			cop20.drawString(ss, i.x,i.y+15);
    }
}
static image_t make_empty_image(int w, int h, int c)
    {
        image_t out;
        out.data = 0;
        out.h = h;
        out.w = w;
        out.c = c;
        return out;
    }

static image_t make_image_custom(int w, int h, int c)
    {
        image_t out = make_empty_image(w, h, c);
        out.data = (float *)calloc(h*w*c, sizeof(float));
        return out;
    }
static image_t ipl_to_image(IplImage* src)
    {
        unsigned char *data = (unsigned char *)src->imageData;
        int h = src->height;
        int w = src->width;
        int c = src->nChannels;
        int step = src->widthStep;
        image_t out = make_image_custom(w, h, c);
        int count = 0;

        for (int k = 0; k < c; ++k) {
            for (int i = 0; i < h; ++i) {
                int i_step = i*step;
                for (int j = 0; j < w; ++j) {
                    out.data[count++] = data[i_step + j*c + k] / 255.;
                }
            }
        }

        return out;
    }

void detect_x(image_t x)
{
	    result_vec = detector.detect(x,thresh,false); 
}
void detect_y(image_t x)
{
	    result_vec1 = detector.detect(x,thresh,true); 
}
void detect_z(image_t x)
{
	    result_vec2 = detector.detect(x,thresh,true); 
}
//--------------------------------------------------------------
//  DetectNet部分をマルチスレッドにする。
class Detect_x: public ofThread {
public:
	
	void threadedFunction(){

		ok=false;
		detect_x(xxx);    
                ok2=true  ;       
		ok=true;
	}
	bool ok;
};

Detect_x Found_X;
//--------------------------------------------------------------
//  DetectNet部分をマルチスレッドにする。
class Detect_y: public ofThread {
public:
	
	void threadedFunction(){

		ok1=false;
		
			cv::Mat imgy;
			cv::cvtColor(mat, imgy, cv::COLOR_RGB2BGR);
			std::shared_ptr image_ptr1(new image_t, [](image_t *imgy) { detector.free_image(*imgy); delete imgy; });
			std::shared_ptr ipl_small1 = std::make_shared(imgy);
			*image_ptr1 = ipl_to_image(ipl_small1.get());
			yyy=*image_ptr1;

		detect_y(yyy);    
		ok3=true  ;       
		ok1=true;
	}
	bool ok1;
};

Detect_y Found_Y;

//--------------------------------------------------------------
void ofApp::setup(){
obj_names = objects_names_from_file(names_file);
    cop20.load("cooperBlack.ttf",10,true,true,true);
    cop50.load("cooperBlack.ttf",30,true,true,true);
    img.allocate(640,480,OF_IMAGE_COLOR);
    video.setDeviceID( 0 );
	//video.setDesiredFrameRate( 30 );
	video.setup(640,480,OF_PIXELS_RGBA);
	Found_X.ok=true;
	Found_Y.ok1=true;
	ok2=true;
	ok3=true;
}

//--------------------------------------------------------------
void ofApp::update(){
//ofLog() << ofGetFrameRate();
	video.update();
	if(video.isFrameNew()==true){
		img.setFromPixels(video.getPixels().getData(),video.getWidth(),video.getHeight(),OF_IMAGE_COLOR);
		mat=ofxCv::toCv(img);
                if (Found_X.ok){
			cv::Mat imgx;
			cv::cvtColor(mat,imgx, cv::COLOR_RGB2BGR);
			std::shared_ptr image_ptr(new image_t, [](image_t *imgx) { detector.free_image(*imgx); delete imgx; });
			std::shared_ptr ipl_small = std::make_shared(imgx);
			*image_ptr = ipl_to_image(ipl_small.get());
			xxx=*image_ptr;
                        
			Found_X.startThread();
                }
		else if(Found_Y.ok1)  Found_Y.startThread();
    }
}
//--------------------------------------------------------------
void ofApp::draw(){
   ofSetColor(255);
   video.draw( 0, 0 );
   Found_X.lock();
   show_console_result(result_vec, obj_names);
   Found_X.unlock();
   Found_Y.lock();
   show_console_result(result_vec1, obj_names);
   Found_Y.unlock();
}
//--------------------------------------------------------------
void ofApp::keyPressed(int key){
}
//--------------------------------------------------------------
void ofApp::keyReleased(int key){
}
//--------------------------------------------------------------
void ofApp::mouseMoved(int x, int y){
}
//--------------------------------------------------------------
void ofApp::mouseDragged(int x, int y, int button){
}
//--------------------------------------------------------------
void ofApp::mousePressed(int x, int y, int button){
}
//--------------------------------------------------------------
void ofApp::mouseReleased(int x, int y, int button){
}
//--------------------------------------------------------------
void ofApp::mouseEntered(int x, int y){
}
//--------------------------------------------------------------
void ofApp::mouseExited(int x, int y){
}
//--------------------------------------------------------------
void ofApp::windowResized(int w, int h){
}
//--------------------------------------------------------------
void ofApp::gotMessage(ofMessage msg){
}
//--------------------------------------------------------------
void ofApp::dragEvent(ofDragInfo dragInfo){ 
}

FLIR LEPTON のホームページに私たちのThermal Cam Depthが掲載された!!

Thermal Camは、ドローンへ搭載するために作ったモノです。

f:id:TAKEsan:20180925133820p:plain

f:id:TAKEsan:20180924212322p:plain

 全国に大規模な太陽電池発電所が建設されています。いろいろ批判もあるでしょうが現実的に多く存在しています。

 ヒトの作ったモノは、点検が不可欠。万一故障するとパネルが他の部分より高温になるそうです。規模が大きければ不良箇所を発見することが困難。直流電流が漏電した場合、遮断が難しいため点検員の逃げ場が無くなり、かなり危険な人命を掛けた作業でもあります。そこで高解像度サーモグラフィカメラを取り付けたドローンが登場。専属の操縦士と数百万円にも及ぶ機材が必要でした。機体が大きいため万が一落下した場合、太陽電池や高価な機体の破損にもつながる非常にリスクのある作業です。

f:id:TAKEsan:20180925134543p:plain

        現在改良中の、ソフト・ハードを搭載したドローンからの画像と、途中からiPhon で受信したLEPtON3.5の合成画像が白黒で挿入されています。LEPTON3.5の160x120画素でも、この用途ではほとんど問題ない事が理解できると思います。しかもドローンは自動飛行!!(大澤さん撮影)

 これが市販の軽量ドローンで可能であれば、そして安価であれば大幅なリスク回避が可能となります。そう考えた方がいらっしゃって(W&TのW=渡辺さん)、たまたま私のブログを見て「こりゃー行けるかも」と思ったそうです。そのころ私も赤外線カメラLEPTON1での試行結果から、すぐに実現可能だと思っていました。ただし、LEPTON1では解像度が不足し、最低でもLEPTON3と、省電力軽量、安価なESP8266を使うことが条件。しかし、LEPTON3の特殊性やESP8266の限界、SPIの安定性、2.4Ghz帯の電波干渉、そして経験の無いAppleのアプリ審査という厳しい現実が待っていました。私たちは、専門家のいないアマチュア集団です。ブログやYOUTUBEで知り合った、仙台(小野)、大阪(渡辺)、東京(大澤)、京都の4名がそれぞれ得意分野で知恵を出し合うことになりました。全員がそろうのは1年に1回。Maker Faire Tokyo !!。

            f:id:TAKEsan:20170613224819j:plainf:id:TAKEsan:20180704191549j:plain

 会場の電波状況の悪さに、去年も今年も落胆と改良の日々が続きました。今までは地上では電波が伸びても上空ではダメ。家電量販店や駐車場ではESP8266が止まってしまう。渡辺さんの操縦するドローンがESP8266 WIFIとの混線で落ちたのも2度や3度ではありません。開発からもう1年半が経過し、最新の基盤とソフトができあがったころ、つい先日東京の大澤さんから朗報が来ました。

f:id:TAKEsan:20180925134753p:plain

     

        ドローンからiPodに送ってきた画像です。(この動画は20m付近)

 いままで地上だけでしか実現できなかった長距離接続が可能になった...!?。しかもドローンとの電波干渉が無い?。はたして当初目標としていた到達点に来たのだろうか?

 こんな時、今年のメーカフェアに出展した内容が、サーモグラフィーの世界的企業であるFLIRのホームページに掲載される事になったんです。今年はドローンを切り離してLEPtON3をiPhoneに2個パラレルWIFI接続。赤外線画像でのステレオ(Depth)とAIへの挑戦でした。https://lepton.flir.com/community-showcase-items/thermal-stereo-cam/

f:id:TAKEsan:20180925135101p:plain

f:id:TAKEsan:20180924211357p:plain

 私たちにとってはこの2つの事件に後押しをされて、さらなる改良を続けることにしました。Thermal Cam専用のブレないジンバルを渡辺さんが作っています。大澤さんはThermal Camドローン搭載に向けて現場実証とカメラの取り付け方法を模索しています。私は赤外線画像とAIが結びつかないかを検証しています。私たちの目標は。現場で終結できるコンパクトで安価なモノを作る!!です。

※Thermal Cam (LEPTON1専用)、Thermal Cam 3(LEPTON3、3.5専用)はAppleアプリストアでダウンドードできます。Thermal Cam 専用基盤はW&T Thermal Cam PCB - スイッチサイエンスで手に入れることが可能。ジンバルやドローン機体は今後の経過をこのブログでご期待下さい。