Take’s diary

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

Edison と スイッチサイエンス版Eaglet で iPhone をWifiで連携させる(MEMS非接触温度センサー応用編その3:iPhon実機テスト)

 このシリーズでどうしてもクリアしなければならないハードルは、iPhoneの実機テストです。今はごく簡単に実機テストできるようです。

Xcode - 誰でも無料でiPhoneの実機を使ってiOSアプリ開発する方法 - Qiita

 私の場合は昨年テストしたので、登録料が必要でしたが、その場合でもかなり簡単に登録できるようになっていました。

 

        

        ofxBox2dのサンプルをiPhone実機テストしているところ。

 実行するためのプログラム環境はOpenframeworksです。なぜか?もう歳なので標準のXcode環境では英語のしかも、昔は考えられなかった長文単語に割り当てられている変数の考え方が理解できなかったからです。かのC言語の創始者はその著書に「極限までシンプルに」と「他人の作ったソースをとことん利用するため」に当時のfortranの改革を実現しました。現状はどうなんでしょうか?

 能書きはどうあれ、iPhoneで実機テストできたときは、思わず感動してしまいます。皆さんもぜひ実現させてみてください。感覚では私のiMacでのiPhoneシュミレーションより実機の方が少し早い気がします。また、Pi2と比較しても同じプログラムソースでfacetrakkerなどを動作させると、明らかにスピード面で有利です。さらに、商業ベースで成功しているOSだけに、GUIの動作と、OSそのものが持っている表現力は、さすがと思わずにいられないものがあります。 

Openframeworks iOS版について

Xcodeをインストールしたら、ここからダウンロードするだけです。

openFrameworksJpiOS用を選択)

 Exampleを実行するだけなら簡単に実機テストできます。ただし、先に進もうとするとがやはりiOS版のクセがあり、かなり苦労しました。現状iOSのためOpenframeworksの注意事項について、まとめてみます。

  Openframeworksでは、projectGenerator_iosを使って新しいプロジェクトの作成を推奨していますが、バグがあります。ただし、Mac用のAddonを使うときは、これを使わないと上手くコンパイルできない場合があります。また、Mac用のAddonでもマイナーなものはビルドできない場合が多いようです。

 projectGenerator_ios について

 作成された新プロジェクトのiOS Deployment Tagetが3.1になってしまうので8.1以降に直す必要が有ります。(Examplesの中のファイルもバージョンが違っている場合があるので修正が必要)

f:id:TAKEsan:20150820140759p:plain

       iOS Deployment Tagetが3.1になっているので最新版を選択する 

新しいaddonを入れてもprojectGenerator_iosで認識されない場合があります。こんな場合は、出来上がったプロジェクトにaddonを手動で入れてしまいます。

f:id:TAKEsan:20150820141425p:plain

 プロジェクトのaddonsフォルダを右クリック。Add Files to "⚪︎⚪︎⚪︎⚪︎⚪︎⚪︎⚪︎⚪︎" を選択し、追加したいaddonフォルダを指定します。また、プロジェクト名にアンダーライン等、記号が含まれるとコンパイルエラーになることがあります。

Openframeworks iOS版で、 結構重要なこと

 Openframeworksの看板addon である ofxFaceTrackerやofxCVやofxBox2dをiOS版にビルドするとき、「Undefined symbols for architecture i386:」 みたいなエラーが、盛大に出るときは、of_v0.8.4_ios_release /addons/ofxiOS / src/app のofAppiOSWindow.mmについて、下記を見て#import文7行の後にコードを追加してみます。

参考:http://qiita.com/yasuraok/items/a2e22e446801dfeaa707

 

 /Users/xono1/Desktop/openframeworks/of_v0.8.4_ios_release/addons/ofxiOS/src/app/ofAppiOSWindow.mmを開いて以下の赤部分を追加する。

#import "ofMain.h"
#import "ofGLProgrammableRenderer.h"
#import "ofAppiOSWindow.h"
#import "ofxiOSEAGLView.h"
#import "ofxiOSAppDelegate.h"
#import "ofxiOSViewController.h"
#import "ofxiOSExtras.h"
extern "C"{
size_t fwrite$UNIX2003( const void *a, size_t b, size_t c, FILE *d ){
return fwrite(a, b, c, d);
}
char* strerror$UNIX2003( int errnum ){
return strerror(errnum);
}
time_t mktime$UNIX2003(struct tm * a){
return mktime(a);
}
double strtod$UNIX2003(const char * a, char ** b){
return strtod(a, b);
}
}

 Examplesの中のサンプルプログラムは、この下処理でほとんどコンパイルできますが、実行してみると、表示が下図のようにほとんどダメ(縦横が逆)。

           f:id:TAKEsan:20150820143041p:plain

 これをどのように直すのかなかなか分かりませんでした。ネット上にはこの点に関するプログラム修正の方法がいろいろありますが、あまり参考にはなりませんでした。結局なんてことなし。Deployment info の中の PortraitとUpside Downのチェックを下図のように外すだけです。

f:id:TAKEsan:20150820143919p:plain

 この現象は、プログラム上にofSetOrientation関数が記述されている場合に起こります。

           f:id:TAKEsan:20150820144637p:plain

メジャーaddonであるBOX2D,facetrakker について

 BOX2Dは、iOSのEXAMPLE は全く動きません。BOX2D全体がバージョンアップされ、このサンプルだけが昔のコードを使っているためなので、試しにprojectGeneratorで作り直すと一般サンプルもiPhoneで問題なく動きます。下図はiOSシュミレーターのスクリーンショットです。

          f:id:TAKEsan:20150820145704p:plain

 ofxFaceTrackerのExampleもこの方法で実行可能。ただしダウンロードしたofxFaceTracker のreadmeにも書いてありますが、modelディレクトリを各exampleに入れておかないと動かないので注意です。

        

ofxCVのサンプル「example-face-follow」をiPhone実機テストしているところ。前に紹介したEdisonのSimpleCVの顔認識とほぼ同じ動作。iPhoneだとかなり早いことがわかる。

 

 前に紹介した田所先生の顔認識プログラムを少し変更して実現させてみます。

       

前に紹介した「Raspberry Pi2でofxFaceTrackerを実現させる」のソース内容を少し変更してiPhone実機テストしているところ。やはりiPhoneだとかなり早いことがわかる。

 

どうでしょうか? Openframeworks iOS版もコツさえ分かれば、面白いように実機で動き出します。

 

Xcode 7.0にバージョンアップすると、実機テストビルド時リンカーエラーとなって、こんなメッセージが出ます。

You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target. for architecture armv7

 

こんな時は Target->Build Settings->Build Options->Enable BitcodeをNoにすると、ビルドできます。(ターゲットは違いますが、下図参照 NO にします)

f:id:TAKEsan:20150924151956p:plain

 

 

 

 

 

 次に顔認識プログラムの内容を記入します。所定(/bin/data/images/of_logo/)の場所に顔写真ファイルがないと動きませんので注意。また、背景にはイメージ(/bin/data/images/kawa.png)ファイルが必要です。iPhon内蔵カメラを使っているので、実機にビルドしないと、コンパイルエラーになります。インストール方法など、詳しくは、「Raspberry Pi2でofxFaceTrackerを実現させる」を参考にしてください。

takesan.hatenablog.com

 

 main.mm

iPhone 6 plus レティーナの解像度に合わせるにはmain.mmを以下のように変更。

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

#include "ofMain.h"

#include "ofApp.h"

int main(){

    ofAppiOSWindow * window = new ofAppiOSWindow();

    window->enableAntiAliasing(2);

    window->enableRetina();

    window->enableRendererES2();

    ofSetupOpenGL(1704,960, OF_FULLSCREEN);         // <-------- setup the GL context

    

    ofRunApp(new ofApp);

}

 

ofApp.h

最後の部分を修正します。

 

#pragma once

 

#include "ofMain.h"

#include "ofxiOS.h"

#include "ofxiOSExtras.h"

#include "ofxOpenCv.h"

#include "ofxCv.h"

//#include "ofxFaceTracker.h"

#include "ofxFaceTracker.h"

 

class ofApp : public ofxiOSApp {

 

    public:

        void setup();

        void update();

        void draw();

        void exit();

 

        void touchDown(ofTouchEventArgs & touch);

        void touchMoved(ofTouchEventArgs & touch);

        void touchUp(ofTouchEventArgs & touch);

        void touchDoubleTap(ofTouchEventArgs & touch);

        void touchCancelled(ofTouchEventArgs & touch);

 

        void lostFocus();

        void gotFocus();

        void gotMemoryWarning();

        void deviceOrientationChanged(int newOrientation);

 

    ofVideoGrabber cam;

    ofxFaceTracker tracker;

    ofxFaceTracker imgTracker;

    ofxCvColorImage colorCv;

    ofImage faceImage,kawa;

  

    int nImages;

    ofImage * images;

    ofDirectory DIR;

    ofTrueTypeFont verdana30,testFont2;

    int currentImage,xx,yy,ww,hh;

//    ofxiOSImagePicker camera; 

};

ofApp.mm

iPhone で実行させるには少なくともupdate()の内容とdraw()の内「texCoordを正規化」の部分を変更しないと動きません。他詳しくはコメント参照。

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

#include "ofApp.h"

using namespace ofxCv;

 

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

void ofApp::setup(){

    

    verdana30.loadFont("verdana.ttf", 30, true, true);

    verdana30.setLineHeight(34.0f);

    verdana30.setLetterSpacing(1.035);

    testFont2.loadFont("cooperBlack.ttf", 60, true, true, true);

    

    //画面基本設定

    ofSetVerticalSync(true);

    ofEnableAlphaBlending();

    ofBackground(54, 54, 54, 255);

    

    nImages = DIR.listDir("images/of_logo");

    images = new ofImage[nImages];

    //you can now iterate through the files as you like

    for(int i = 0; i < nImages; i++){

        images[i].loadImage(DIR.getPath(i));

    }

    currentImage = 0;

    //カメラを初期化

    cam.setDeviceID(1);   // 0又は省略で通常カメラ 1はfacetime カメラ

    cam.initGrabber(360,480, OF_IMAGE_COLOR);  //この解像度固定の模様!! 数値を修正してもだめ

    colorCv.allocate(cam.getWidth(),cam.getHeight());  //これを入れないとダメ

    

    faceImage.allocate(360,480, OF_IMAGE_COLOR);

    faceImage= images[currentImage];

    

    kawa.allocate(960, 1704, OF_IMAGE_COLOR);

    kawa.loadImage("images/kawa.png");

 

    //カメラ映像のフェイストラッカーのセットアップ

    tracker.setup();

    // 認識する際の画像をリスケール(小さくするほど高速)

    tracker.setRescale(.2);

    //合成する顔画像のフェイストラッカーのセットアップ

    imgTracker.setup();

     imgTracker.setRescale(1.0);

    imgTracker.update(toCv(faceImage));

}

 

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

void ofApp::update(){

    //カメラ更新

    cam.update();

    if(cam.isFrameNew()) {

        //フェイストラッカーの更新 この代入をしないと実行時エラーとなる

        colorCv=cam.getPixels();

        tracker.update(toCv(colorCv));

    }

}

 

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

void ofApp::draw(){

    //カメラ映像を描画

    //printf("me no ookisa %f \n",tracker.getGesture(ofxFaceTracker::ofxFaceTracker::MOUTH_HEIGHT));

    ofSetColor(255);

    kawa.draw(0, 0);

    

    cam.draw(120, 120,cam.getWidth()*2,cam.getHeight()*2);

    

    ofSetColor(0);

    verdana30.drawString(ofToString*1, 20, 50);

 

    //ofDrawBitmapString(ofToString*2, 10, 20);

    

    //ofRectRounded(120, 120, cam.getWidth()*2+120, cam.getHeight()*2+120, 50);

    ww=faceImage.width;

    hh=faceImage.height;

    float scale_xx=(ofGetWidth()/3.0)/ww;

    ofSetColor(255);

    faceImage.draw(500, 1130ww*scale_xx,  hh*scale_xx);

    

    ofSetColor(0, 90, 60);

    testFont2.drawString("Hello!! FaceTraccer", 15, 120);

    testFont2.drawString("      by Takeshi..", 15, 1080);

    ofSetColor(225, 225, 100);

    testFont2.drawString("Sample->", 15, 1290);

    ofSetColor(0);

    verdana30.drawString("                       .....2015.1.17", 15, 1660);

    

    //もしカメラの映像に顔が検出されたら以下の処理をする

    if(tracker.getFound()) {

        

        //カメラ映像からメッシュを作成

        ofMesh objectMesh = tracker.getObjectMesh();

        //合成する顔の画像からメッシュを作成

        ofMesh imgMesh = imgTracker.getObjectMesh();

        /* texCoordを正規化 ofNextPow2を入れないと写真側のトラッキングが上手くいかない  */

        for(int i=0; i<  objectMesh .getTexCoords().size(); i++) {

            ofVec2f &texCoord = imgMesh .getTexCoords()[i];

            texCoord.x /=ofNextPow2(ww);

            texCoord.y /=ofNextPow2(hh);

        }

 

        //静止画のメッシュの頂点情報を、カメラから生成したメッシュのものに変換

        //つまり現在の顔の表情を、静止画のメッシュに適用

        for (int i = 0; i < objectMesh.getNumVertices(); i++) {

            imgMesh.setVertex(i, objectMesh.getVertex(i));

        }

        

        //画面の3Dのパースをなしに 以下のコマンド意味なし

       // ofSetupScreenOrtho(560, 360, OF_ORIENTATION_DEFAULT, true, -1000, 1000); これだと上手くいかない

        //カメラで検出された顔の、位置、大きさ、傾きを取得

        ofVec2f positon = tracker.getPosition();

        float scale = tracker.getScale()*1.9;

        ofVec3f orientation = tracker.getOrientation();

        

        //静止画のメッシュをカメラの位置、大きさ、傾きにあわせる

        ofPushMatrix();

        ofTranslate(positon.x*2+120, positon.y*2+120);

        ofScale(scale, scale, scale);

        ofRotateX(orientation.x * 45.0f);

        ofRotateY(orientation.y * 45.0f);

        ofRotateZ(orientation.z * 45.0f);

        

        //静止画から生成メッシュを配置して、合成する画像をマッピング

        ofSetColor(255, 255, 255);

        faceImage.getTextureReference().bind();

        imgMesh.draw();

        faceImage.getTextureReference().unbind();

        ofPopMatrix();

        

    

    }

}

 

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

void ofApp::exit(){

 

}

 

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

void ofApp::touchDown(ofTouchEventArgs & touch){

    if (nImages > 0){

        currentImage++;

        currentImage %= nImages;

 

        faceImage= images[currentImage];

        imgTracker.setup();

        // 認識する際の画像をリスケール(小さくするほど高速 ただし写真は0.5が限界

         imgTracker.setRescale(1.0);

        imgTracker.update(toCv(faceImage));

        int coun1=0;

        while ( ! imgTracker.getFound()) {     //これを入れないと認識されないことがありプログラムが止まる!!

            

          imgTracker.update(toCv(faceImage));

            if(coun1>5) break;

        }

 

    }

}

 

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

void ofApp::touchMoved(ofTouchEventArgs & touch){

 

    }

 

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

void ofApp::touchUp(ofTouchEventArgs & touch){

 

}

 

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

void ofApp::touchDoubleTap(ofTouchEventArgs & touch){

 

}

 

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

void ofApp::touchCancelled(ofTouchEventArgs & touch){

    

}

 

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

void ofApp::lostFocus(){

 

}

 

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

void ofApp::gotFocus(){

 

}

 

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

void ofApp::gotMemoryWarning(){

 

}

 

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

void ofApp::deviceOrientationChanged(int newOrientation){

 

}

 

今回シリーズの基本的な考え方

 このあたりで、本題である「Edisonとスイッチサイエンス版EagletでiPhoneWifiで連携させる(MEMS非接触温度センサー応用編)」の説明に戻ります。

  当初BLEでデータを操作したかったのですが、私の現スキルでは無理。自分自身がバージョンアップしてからにします。ここでは一番実現しやすい(しかしめんどくさい)WifiとOSCとbonjourlを使う方法。すなわちbonjourで相手のipアドレスを手に入れ、そのアドレスを使ってOSCプロトコルを使ってWifi送信する。です。なぜbonjouralを使うかは、他の皆さんが書いていらっしゃいますのでここでは省略。 

 Openframeworks,Python,Arduino環境で、この3つのライブラリが存在すると分かったのが大きな力となりました。ただし、ためしにArduinoを使ってArduino環境でOSCとbonjourlライブラリをコンパイルしてみたのですが、エラーの続出で手に負えないので断念。結局Edison側はPythonで動かすことにしました。iOSでOSCサーバーを実現すれば、Edison側からはOSCで単純にOMURON D6Tのデータを送信するだけなので、なんとかなるような気がしただけです。

 OpenframeworksのOSCは、標準でExampleがiOS版にも入っていますので、簡単に実験できました(ただし修正が必要->次回)。

次回はiOSとEdisonのソースを一挙公開!!

takesan.hatenablog.com

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

*1:int) ofGetFrameRate(

*2:int) ofGetFrameRate(