Junpei Qawasaki

博報堂アイ・スタジオという会社でテクノロジーをベースに新規事業とかインタラクティブなものとかを色々とやっております

【EYE TRIBE】視線でのマウス操作をスムーズにしてみた

なんとEYETRIBEを借りました。
起動にはSDKが必要だったりするのですがそのあたりが知りたい方は貸主の1Fさんブログをご覧下さい。

EYETRIBEを起動してみた!EYETRIBEを起動してみた! | HACKist

開発環境はVisualStudioなので別に未知の領域ではないのですが正直C#はそんなに得意じゃないです。
なので無茶はしないでとりあえず講演用にマウス操作を視線で行いたいな、と。

もともとのソースはこちら
TheEyeTribe - The Eye Tribeで視線でマウスカーソル動かす(C#) - Qiita
から拝借しました。参照設定とかも書かれているのですごく参考になります。

ただこの参考ソースはセンサーの値がそのままなので動きがピーキー過ぎて使いこなせません。
そこで、センサーからのとった値の移動平均値計算処理いれました。

現在の値 = 0.9 * ひとつ前の値 + 0.1 * センサの値

所詮は暫定対処のため実用化、というほどのクオリティには届いていませんが「視線操作出来てる体験」は出来るくらいになりました。でもちょっと丸められすぎなのかも。

加重平均値という話も出たけれど多分、手間をかけてもそこまで改善する気がしないので今回はやめておきます。

合わせて右目閉じたらクリックって処理にしてみたのですが、こちらもコンソールログみると拾い過ぎなので5回に1回の割合で拾ってます。

ソース全体はこんな感じ。

using System;
using TETCSharpClient;
using TETCSharpClient.Data;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;

public class EyeCursorControl : IGazeListener
{
    public double before_screenX = 0; //一つ前の座標(平均値用)
    public double before_screenY = 0;
    public double lowpassX = 0;
    public double lowpassY = 0; 

    public int CursorX = 0; //最終的にカーソル位置になる座標
    public int CursorY = 0;
    public int eyeclose_cnt = 0; //右目閉じカウンタ

    public bool Enabled { get; set; }
    public bool Smooth { get; set; }
    public Screen ActiveScreen { get; set; }

    public EyeCursorControl() : this(Screen.PrimaryScreen, false, false) { }

    public EyeCursorControl(Screen screen, bool enabled, bool smooth)
    {
        GazeManager.Instance.AddGazeListener(this);
        ActiveScreen = screen;
        Enabled = enabled;
        Smooth = smooth;
    }

    public void OnGazeUpdate(GazeData gazeData)
    {
        if (!Enabled)
        {
            return;
        }

        // start or stop tracking lost animation
        if ((gazeData.State & GazeData.STATE_TRACKING_GAZE) == 0 &&
           (gazeData.State & GazeData.STATE_TRACKING_PRESENCE) == 0)
        {
            //Console.WriteLine("start or stop tracking lost animation");
            return;
        }

        // tracking coordinates
        var x = ActiveScreen.Bounds.X;
        var y = ActiveScreen.Bounds.Y;
        var gX = Smooth ? gazeData.SmoothedCoordinates.X : gazeData.RawCoordinates.X;
        var gY = Smooth ? gazeData.SmoothedCoordinates.Y : gazeData.RawCoordinates.Y;

        //センサ値
        var sensorX = (int)Math.Round(x + gX, 0);
        var sensorY = (int)Math.Round(y + gY, 0);
        Console.WriteLine(gX + "," + gY);

        if (gazeData.RightEye.SmoothedCoordinates.X == 0 && gazeData.RightEye.SmoothedCoordinates.Y == 0 && eyeclose_cnt > 5)
        {
            Console.WriteLine("right eye close done!");
            eyeclose_cnt = 0;
            NativeMethods.LeftClick();
        }
        else if (gazeData.RightEye.SmoothedCoordinates.X == 0 && gazeData.RightEye.SmoothedCoordinates.Y == 0 && eyeclose_cnt < 5)
        {
            Console.WriteLine("right eye close count");
            eyeclose_cnt++;
        }
        else
        {
            //指数移動平均計算
            lowpassX = 0.9 * before_screenX + 0.1 * sensorX;
            lowpassY = 0.9 * before_screenY + 0.1 * sensorY;

            //カーソル命令のメソッドがintじゃないと落ちるのでcast
            CursorX = (int)lowpassX;
            CursorY = (int)lowpassY;

            //スクリーン座標にマウスポイントを移動
            NativeMethods.SetCursorPos(CursorX, CursorY);

            //ひとつ前の値を保持しとく
            before_screenX = lowpassX;
            before_screenY = lowpassY;
        }
    }

    public class NativeMethods
    {
        [System.Runtime.InteropServices.DllImportAttribute("user32.dll", EntryPoint = "SetCursorPos")]
        [return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)]

        public static extern bool SetCursorPos(int x, int y);

        [DllImport("user32.dll")]

        private static extern void SendInput(int nInputs, ref INPUT pInputs, int cbsize);

        public static void LeftClick()
        {
            const int num = 2;
            INPUT[] inp = new INPUT[num];

            // マウスの左ボタンを押す
            inp[0].type = INPUT_MOUSE;
            inp[0].mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
            inp[0].mi.dx = 0;
            inp[0].mi.dy = 0;

            inp[0].mi.mouseData = 0;
            inp[0].mi.dwExtraInfo = 0;
            inp[0].mi.time = 0;

            // マウスの左ボタンを離す
            inp[1].type = INPUT_MOUSE;
            inp[1].mi.dwFlags = MOUSEEVENTF_LEFTUP;
            inp[1].mi.dx = 0;
            inp[1].mi.dy = 0;
            inp[1].mi.mouseData = 0;
            inp[1].mi.dwExtraInfo = 0;
            inp[1].mi.time = 0;

            SendInput(num, ref inp[0], Marshal.SizeOf(inp[0]));
        }

        private const int INPUT_MOUSE = 0;                  // マウスイベント
        private const int MOUSEEVENTF_MOVE = 0x1;           // マウスを移動する
        private const int MOUSEEVENTF_ABSOLUTE = 0x8000;    // 絶対座標指定
        private const int MOUSEEVENTF_LEFTDOWN = 0x2;       // 左 ボタンを押す
        private const int MOUSEEVENTF_LEFTUP = 0x4;         // 左 ボタンを離す

        [StructLayout(LayoutKind.Explicit)]
        private struct INPUT
        {
            [FieldOffset(0)]
            public int type;
            [FieldOffset(4)]
            public MOUSEINPUT mi;
        };

        // マウスイベント(mouse_eventの引数と同様のデータ)
        [StructLayout(LayoutKind.Sequential)]
        private struct MOUSEINPUT
        {
            public int dx;
            public int dy;
            public int mouseData;
            public int dwFlags;
            public int time;
            public int dwExtraInfo;
        };
    }

    static void Main(string[] args)
    {
        GazeManager.Instance.Activate(GazeManager.ApiVersion.VERSION_1_0, GazeManager.ClientMode.Push);
        if (!GazeManager.Instance.IsConnected)
        {
            Console.WriteLine("EyeTribe Server has not been started");
        }
        else if (GazeManager.Instance.IsCalibrated)
        {
            Console.WriteLine(GazeManager.Instance.LastCalibrationResult);
            Console.WriteLine("Re-Calibrate");
        }
        else
        {
            Console.WriteLine("Start");
        }
        EyeCursorControl cursor = new EyeCursorControl(Screen.PrimaryScreen, true, true);
    }
}

【Unity】Macで作成したものがWindowsでコンパイルエラーになる件

タイトルの通りでハマりました。singletonのクラスのメンバ変数にアクセス出来ないよ、的なエラーが出る。

結果、Mac側とWindowsの改行コード設定が違う、というオチでした。

両方utf-8でcr+lf、bom無しで設定一致していないと駄目です。仮にコンパイル通ってもシンタックスエラーとか出るとかいう事例も見かけました。

今回はこんな感じで解消しましたが、そもそもエディタが違うとか、build設定とか、.NETのバージョンがWindows 8だとデフォルトで4.0だとか疑わしい場所が多くて時間かかったので悔しいすね。




勉強会の集客が突然好調になったのでその理由を考えてみた。

僕は開発者向けのコミュニティ運営を2つばかしやらせてもらっています。

1つは1年半前くらいに作った「Windows 8 Developers」で、Windows上で動くいわゆるストアアプリが主体のコミュニティです。
そのイベント(勉強会)を3/14にやるのですが、今回、過去イベントよりも俄然、集客の「引き」が強いので、その理由の考察をするというのがこのエントリの主旨です。

イベントを開けど集客に難あり

設立当初のイベントは正直集客も思ったように集められんかったのです。
今をときめくJavaScriptでネイティブのアプリ開発が出来るストアアプリにWeb業界の人はさぞ興味深々であろう。などと考え、意気揚々とFacebookグループ立てて第1回カンファレンスを定員90人で開催してみました。

結果、50人の申し込みで実参加者は40人くらいという微妙な感じのコミュニティデビューになり、続けてハッカソンをやってみましたが、こっちはもっと悲惨で10人集めるのが精一杯でした。

人集めりゃ良いって事も無いのですが、やはり「即完」してみたいのが人の性。
とはいえ扱っている内容がメジャーじゃない(言い過ぎ)ので無条件でハネる事はまず無い訳で。

高校生の文化祭ライブじゃないので闇雲に開催して人を誘う、というのも如何なものかと思い、イベントを2回やった後で何か手を考えないと行けないなーと思ったのです。

ハッカソンに付加価値をつけてみる

まず、ハッカソンも「みんなでいっしょに開発しよう」みたいな前時代的な考えでは駄目だと思い、企画ルールとして「ストアアプリ×何か」という風に技術×技術で"お題"を決めました。たとえばこんな感じです。

ハッカソン2回目 「ストアアプリ × enchant.js」
ハッカソン3回目 「ストアアプリ × KINECT

2回目はJavaScript界隈でゲーム向けライブラリで有名なenchant.jsと当時公開されたばかりのSkyDrive APIをターゲットにしました。このタイトルは結構当たって、20名枠に対して25人参加でした。

3回目はまた手法を変えました。そもそもストアアプリと連携出来ないKINECTを無理矢理連携させるチャレンジハッカソンとして定員を5名に絞って超小規模で開催しました。結果、WebSocketでKINECTを繋ぐアプリが出来て、イベント内容に対してコミュニティの活性化が出来たと思います。

他にも某コンテストと連携して「参加賞の景品」を用意したハッカソンも行ってみました。結果としては複数回開催して50人くらい集まったので悪くはなかったのですが景品ではモチベーションは上がらないような気がしました。

なんとなく、先端を取り入れてみる

カンファレンスは会場を抑えたり集客をしたりで準備に手間と時間がかかります。
とはいえ時間を空けすぎるとコミュニティ自体が活性化せず人も離れてってしまいます。なので開催の楽なハッカソンを2回やってカンファレンスを1回やるくらいのペースでイベントを打っていくことにしました。
メンバーが150人を超えたくらいで3回目のカンファレンスを打ってみたところ、やはり4〜50人くらいから集客が伸びず、また次の手を考えきゃいかんなーと思ったので、カンファレンスの時にこんなハッカソンを発表してみました。

f:id:jkawasaki:20140121193703p:plain

ちょうどFirefox OSが発表されて実機が手に入るようになった頃です。「WindowsFIrefox OS」という意外性を持たせて、場所もMozilaJapanさんをお借りしました。結果、このハッカソンはコミュニティ過去最多の40人規模になりました。

この時に発見したのは下記です。

"いかにイベントの第一印象で興味を引き、参加したいと思わせるか"


まぁ、基本と言われれば基本なんですが。


で、そこからは半年くらいコミュニティ寝かせていました。
そろそろイベントを打たないとなーと思い、久々にカンファレンスイベントをやろうと思い、相変わらずの集客の不安を引きずりながら今までのイベントを見つめ直して企画したイベントがこちらです。



f:id:jkawasaki:20140121194407j:plain



すると、

なんということでしょう。

発表から数日で定員の60席が埋まりました。

今回変えた事といえばこんな感じです。

①メインビジュアルをインパクト系(2次元キャラ)にしてみた
②開催日を平日(しかも金曜)の夜2時間だけに絞った
③推したいワードを盛り込んだ(Xamarin,Unity,モーションデバイス)

まず、開発者の属性を考えてFacebook/Twitterとかでシェアしたくなるものをチョイスというのが①の狙いです。結果として今までのイベントでは殆どされなかった「いいね」とツイートをしてもらえ、発信元(Facebookグループ)と関係ない人の参加率が高くなっています。
次にイベント自体をなるべくコンパクトにする為の平日開催ですが、結果的に2時間という制限で気軽に参加しやすくなったのかもしれないです。人と繋がるのが目的な人は「懇親会」に参加してもらえれば良い訳ですし。

で、あとはセッション内容ですがこれはシンプルに「自分がいま技術者として聴きたい事」の3つに絞りました。勿論、ストアアプリに絡めた話をしてもらうのですが、Unity等の「分かり易い」ワードを前面に出しました。

そう、色んな意味でコンテンツが重要って事に今更気付いた訳です。

どんな分野でも変わらないものなのですね。


こんな所がいままでのイベントの振り返りと今回の集客UPの考察です。

たまたま好調なだけかもしれないし、イベントもこれからなのでまだまだ継続して推進出来る手を考える必要はあるのですが、コミュ運営って結構大変なので同じような心境のどなたかの参考にでもなればと思いって書いた次第です。

今回、とりあげてたイベントはまだATNDから参加出来ます。ご興味ある方は是非。
イベントサイトは色々な種類があるのですがATNDはFacebookとの親和性悪いのと、イベントページ編集の操作性が悪いのでそろそろ限界かなーと思ってます。

ちなみに2つやっているコミュニティのもう1つは2013年末に立ち上げた「Unity/OpenCV/WebGLで遊ぶ会」です。Facebookグループでどなたでも参加可能になってますので、お気軽にご参加ください。こっちのコミュニティは名前の通り技術属性強めなので、もうちょっと練ってからイベントやりたいと思ってます。

では。

【Android】PhoneGap3.3でアプリパッケージをつくる

今度はPhoneGap3.3でAndroidのプロジェクト作るのにハマったのでメモります。
公式のコミュニティサイトでも古い2.x系の情報しかないので面倒だったので。

1.土台となるディレクトリを作成する

下記コマンドを実行して任意の場所にディレクトリを作成する。

$ cordova create moyomoyo com.kirin gorogoro

上記を実行するとmoyomoyoという名前でディレクトリが作られ、その下にこんな感じにディレクトリ色々出来上がります。
f:id:jkawasaki:20140107184957p:plain

"gorogoro"はアプリ名で"com.kirin"はパッケージ名です。それぞれ好きな名前を付けてください。

で、moyomoyo配下に移動します。

$ cd moyomoyo

2.Androidのパッケージを作成

コマンドは下記です。

$ cordova platform add android

しかしなんとこんなエラーが出ました。
Error: The command "android" failed. Make sure you have the latest Android SDK installed,
場所がいけない?いやそんなはずはない。

調べてみたら単純でtoolsにPATHが通ってないのでコマンドが使えないよって事でした。
下記コマンドでPATHを通します。

export PATH=${PATH}:[ADTのいるディレクトリ]/adt-bundle-mac-x86_64-20131030/sdk/tools

気を取り直して実行。

次はこんなエラーが出ました。
Error: Please install Android target 19 (the Android newest SDK).
SDKのターゲット19が入っていないと駄目って事らしいす。
下記コマンドを実行してターゲットをインストールします。

$ android

こんな感じでSDK Managerが上がってくるので19をインストールしましょう。
f:id:jkawasaki:20140107185143p:plain

で、もう一度コマンドを叩くと
f:id:jkawasaki:20140107185200p:plain

出来ました。
[moyomoyo]➡[platform]➡[android]配下にプロジェクト一式が出来上がってます。
f:id:jkawasaki:20140107185215p:plain

出来上がったアプリ名は最初のコマンドで指定したパッケージ名とアプリ名が反映されてます。
あとはここのassetsにJSとかHTMLを突っ込めば動きます。

めんどくさ。

PhoneGap3.3をターミナルからインストールする方法

ちょっとPhoneGapの最新版をインストールしようとして面倒な事になったのでメモ。
PhoneGap2.9.1まではzipでインストール出来るんだけど、最新版はターミナルを使わないと落とせない。
しかも説明を読んでみると
「To Install 3.3: Ensure that you have NodeJS installed, then open your commandline and run the following:」
とある。
つまりNodeJS入れてないと駄目(というかnpmコマンド使えない)という事で、Nodeを入れる手順を読んだらHomebrew入れろとか、超面倒くさい。
まー他に対応策も分からずイチからやるしかないので仕方なく実施した手順を記載します。

1.HomeBrewのインストール

HomebrewはMacPortsと同じパッケージ管理システムですが、MacPortsと比べて依存関係でインストールされるソフトウェアが少ないとらしいす。
MacPortsは依存するソフトウェアを、Mac OS Xに標準でインストールされているソフトウェアを利用せずに、新規にインストールするという特徴があるが、Homebrewは既にあるものは、できるだけそれを利用するように設計されているとの事です。まぁ、いいやって事で先日OpenCVの件でMacPorts入れたばかりですが下記コマンドを実行。

ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"

成功すると
瑳 /usr/local/Cellar/node/0.10.24: 1119 files, 16M, built in 103 seconds
こんな感じで出力されるかと思います。
確認したい人は下記コマンドでインストールされてるbrewのバージョンが確認出来ます。

brew -v

2.npmのインストール

次にnpm(Node Package Manager)をインストールします。コマンドは下記です。

curl https://npmjs.org/install.sh | sudo sh

ちなみにこのコマンド、httpって記載しているサイトがいくつかあったのですがエラーでます。httpsでいけました。
こちらも確認したい場合はバージョンを確認しましょう。

npm --version

ちなみにこのエントリ書いているとkの最新は1.3.21でした。

3.PhoneGapのインストール

さて、いよいよ目的のPhoneGapです。これ書いているときの最新版は3.3です。
PhoneGapは精力的にアップデートされるのでその都度最新版が良いです。なぜならバージョン古いと対応していない事とかもあってアプリの動作に明確な差が出る事があります。
「あーやっぱ無理かぁ」とか思う機能でも意外と最新版だと動いたりするのです。
では、下記コマンドでインストールしてください。

sudo npm install -g phonegap

これで無事インストール出来ました。

おわり。


<参考情報>
PhoneGap公式
Homebrew公式
Mac OS X Lionにnpm(Node Package Manager)をインストールする
Homebrew導入と使い方

【OpenCV】OpenCVで画像2値化(エッジ抽出)サンプルを試してみた

OpenCVを使ってやってみたかった事の一つに画像の二値化(エッジ抽出)があってそのサンプルを入手して試してみました。二値化って僕も専門外なので最近知ったのですがある決まった値(閾値)を元にその上か下かで分けてしまうという、白黒はっきりさせます的な処理です。
これをやるとどんな画像が出来るのかというと、こんな感じになります。
f:id:jkawasaki:20140105221516p:plain

では、中身を見てみましょう。
■サンプルコード

#include <iostream>
#include <opencv2/opencv.hpp>

#define IMAGE(x, y) img.at<float>(y, x)
#define	THRESHOLD	85		// 閾値

void Thinning(cv::Mat &img)
{
    const int width = img.cols;
    const int height = img.rows;
    cv::rectangle(img, cv::Point(0, 0), cv::Point(width - 1, height - 1),
                  CV_RGB(0, 0, 0));
    cv::Mat four_neighbor = cv::getStructuringElement(cv::MORPH_CROSS,
                                                      cv::Size(3, 3));
    
    int step = 0;
    while (1) {
        step++;
        cv::Mat candidate = img.clone();
        cv::erode(candidate, candidate, four_neighbor);
        candidate = img - candidate;
        
        int	num_delete = 0;
        for (int y = 1; y < height - 1; y++) {
            for (int x = 1; x < width - 1; x++) {
                if (candidate.at<uchar>(y, x) == 0) {
                    continue;	                }
 
                int dx[8] = {1,  1,  0, -1, -1, -1,  0,  1};
                int dy[8] = {0, -1, -1, -1,  0,  1,  1,  1};
                unsigned char n[8];
                int num = 0;
                for (int i = 0; i < 8; i++) {
                    n[i] = img.at<uchar>(y + dy[i], x + dx[i]) ? 1 : 0;
                    num += n[i];
                }
                if (num < 3) {
                    continue;
                }
                //連結数の確認
                int m = 0;
                for (int i = 0; i < 4; i++) {
                    int k0 = i * 2;
                    int k1 = (k0 + 1) % 8;
                    int k2 = (k1 + 1) % 8;
                    m += n[k0] * (1 - n[k1] * n[k2]);
                }
                if (m == 1) {
                    img.at<uchar>(y, x) = 0;
                    num_delete++;
                }
            }
        }
        // 終了判定
        if (num_delete == 0) {
            break;
        }
    }
}
int main(int argc, char **argv)
{
    char *window_capture = "capture";
    cv::namedWindow(window_capture, CV_WINDOW_AUTOSIZE);
    char *window_output = "output";
    cv::namedWindow(window_output, CV_WINDOW_AUTOSIZE);
	
    cv::VideoCapture cap;
    cap.open(0);
    if (!cap.isOpened()) {
        std::cerr << "cannot find camera or file" << std::endl;
        return -1;
    }
    while (1) {
        cv::Mat frame;
        cap >> frame;
        
        cv::Mat gray, sobel, edge, edge_x, edge_y, result;
        cv::cvtColor(frame, gray, CV_BGR2GRAY);
        cv::Sobel(gray, sobel, CV_16S, 1, 0, 3);
        cv::convertScaleAbs(sobel, edge_x);
        cv::Sobel(gray, sobel, CV_16S, 0, 1, 3);
        cv::convertScaleAbs(sobel, edge_y);
        edge = edge_x + edge_y;
        cv::threshold(edge, result, THRESHOLD, 255, CV_THRESH_TOZERO);
        
        Thinning(result);
        cv::imshow(window_capture, frame);
        cv::imshow(window_output, result);
        
        int key = cv::waitKey(5);
        if (key == 3 || key == 27 || key == 'q') {
            break;
        }
    }   
    return 0;
}

今回はちょっとサンプルを変更して閾値として定義している値をTHRESHOLDを85にしてみました。
ちょうど半分だと白の割合が多くなっちゃったので。
色味についてはcvtColorの引数になっているCV_BGR2GRAYを変更すると代わりますが、そもそもRGBとかになってしまうと二値じゃなくなるので今回はCV_BGR2GRAYだけで試します。閾値も85のまま設定だけ変更してみます。

それ以外にthreshold関数の引数を変更する事で風合いが変わります。設定出来るパラメータと内容は下記の通りです。
————————
THRESH_BINARY : 閾値を超えるピクセルは MAXに,それ以外のピクセルは 0 になります.
THRESH_BINARY_INV : 閾値を超えるピクセルは 0 に,それ以外のピクセルは MAXになります.
THRESH_TRUNC : 閾値を超えるピクセルは threshold に,それ以外のピクセルは変更されません.
THRESH_TOZERO : 閾値を超えるピクセルは変更されず,それ以外のピクセルは 0 になります.
THRESH_TOZERO_INV : 閾値を超えるピクセルは 0 に,それ以外のピクセルは変更されません.
————————

実際に試すとこんな感じ。
f:id:jkawasaki:20140105220846p:plain

画像自体がちょっと怖めになったり、カメラ画像そのまま映すと動きがヌルヌルして見えるのでなんだか色々使い道があるような気がします。
静止画だと伝わらない気がしたので今回は動画をUPしてみました。

設定によってはレンダリングに時間がかかってカクついたりしてしまうのが難点です。
プログラム自体はシンプルなものですが使い方次第では色々使えるような気がしてます。

ただ、白黒反転したかったけどまだやり方分かんないので引き続きOpenCV、修行します。

【OpenCV】OpenCV2.4.7をMacにインストールしてサンプルを動かしてみる

OpenCVを始めるにあたってインストールと動作確認までが無知だと結構厳しい感じだったので噛み砕いた内容を一応書いておこうと思います。備忘録てきに。

最初はOpenCVを適当にインストールしときゃ良いんでしょくらいに考えていてWindowsでチャレンジしたら環境変数とかが結構面倒で、しかも僕の環境はWindows8.1+VisualStudio2013なので何かうまく行かずにとりあえず断念。今回入れたのはOpenCV 2.4.7でMac(OS X 10.9)とXcode5.0.2を使ってます。

1.準備

まず準備ですがMacにXcodeが入っている事が前提になります。AppleさんがもはやXcode5以下は許さないという強硬なスタンスなのですが、Xcode5.0.2はLionだと入れられなかったのでMavericksにアップデートさせられました。OpenCVやりたいだけなのに何故かPCが最新化されていく。ご存知かと思いますがMavericksは無償でアップデートできます。ちなみにOSアップデートに通信環境良くても数時間かかって、Xcodeのインストールと合わせると半日コースになるのでご注意を。

2.MacPortsのインストール

OS側の環境が整ったらMacPortsをインストールします。MacPortsOS X用のパッケージ管理システムで使わなくてもOpenCVの環境は作れますが使った方が俄然簡単です。MacPortsのダウンロードサイトに行ってインストールを行いましょう。

MacPortsダウンロードサイト

http://www.macports.org/install.php

f:id:jkawasaki:20140105164426p:plain

MacPortsは/opt/local/bin配下にインストールされます。僕はそのままでもインストール出来たのですが参考ドキュメントを見る限りだと「.bashrcに下記を記入してくれ」という記載があったので一応。

export PATH=$PATH:/opt/local/bin

パスを通すって事ですね。通した後に下記コマンドを実行します(bash前提)。

$ source .bashrc

ちなみにここから先はターミナルでの作業になりますのでターミナル慣れてない方はちょっと厳しいと思いますが頑張ってください。

3.OpenCVインストール

さて、いよいよOpenCVを入れていきます。手始めにMacPortsを使ってOpenCVの情報を下記コマンドで確認してみましょう。

$ port info opencv

こんな感じの結果が出ると思います。

f:id:jkawasaki:20140105164803p:plain

まーライセンスとかその他色々です。このコマンド効かなかったらMacPortsが使えてないってことになります。

次にOpenCVをインストールする前に必要なライブラリパッケージをインストールします。下記コマンドを実行しましょう。sudoはパスワードを聞かれるので適宜入力してください。パスワードが分からない場合はちょっとこの先には進めないのでその場合は親御さんに相談してください。

$ sudo port install pkgconfig zlib

するとわらわらと何やらインストールが始まります。

f:id:jkawasaki:20140105164909p:plain

そしていよいよOpenCVのインストールを行います。下記コマンドを実行しましょう。

$ sudo port install opencv

わらわらとインストールが始まります。結構長い(15分くらい?)です。

4.サンプルアプリの作成

無事インストールが出来たという事で実際にOpenCVが使えるようになっているかをXCodeからサンプルプログラムで試してみます。

今回はカメラを使ってみるのでMac OS X用の「Command Line Tool」アプリを作成します。

f:id:jkawasaki:20140105165736p:plain

プロジェクト名は適当に付けて新規プロジェクトを作成してください。Bundleとかはサンプルなので正直なんでも良いです。

やるべき事は5つです。

①Header Search Pathの設定

TARGETのBuileSettingで検索窓に”header”といれて検索するとHeader Search Pathという項目が見つかります。ここから下記パスを追加してください。

/opt/local/include

f:id:jkawasaki:20140105165715p:plain

f:id:jkawasaki:20140105165918p:plain

オプション部分はnon-recursiveで設定してください。recursiveだと謎のエラーが出ますので。これでOpenCVのヘッダーファイルを参照するパスの設定が出来ました。


②Library Searh Pathの設定
次にライブラリの参照設定をします。これを行わないとリンカーの設定が上手くいかないでビルドした時に下記のようなエラーが出ちゃいます。最初見たドキュメントにこの記載がなかったのでちょっとハマりました。設定箇所はヘッダーの時と同様にLibraryで検索すればすぐ見つかります。下記パスを設定しましょう

/opt/local/lib
ターミナルで見ると分かるのですが、このディレクトリ配下にOpenCVのライブラリ群が揃っています。

③ライブラリへのリンク指定
Build Phaseの「Link Binary With Libraries」という項目からリンクライブラリを選択します。追加するのは下記3つのライブラリです。

libopencv_core.2.4.7.dylib
libopencv_highgui.2.4.7.dylib
libopencv_imgproc.2.4.7.dylib

+ボタンを選択すると一覧が出てくるのですが僕の場合は何故か表示されなかったので”Add Other”から直接ファイルを探して追加しました。

f:id:jkawasaki:20140105170010p:plain

④サンプルプログラム(main.cpp)を記述

main.cppにサンプルプログラムとして下記を記述します。

#include <iostream>
#include "opencv2/opencv.hpp"

using namespace cv;
using namespace std;

int main()
{
    VideoCapture cap(0);
    Mat frame;

    while (waitKey(1)!='q') {
        cap >> frame;
        imshow("sample", frame);
    }
    return 0;
}

⑤その他
ここまででビルドする準備は整ったのですが、最新環境でそのまま実行するとDevelopment Targetが10.9になっているので失敗します。このサンプルは10.7向けになっているので変更してあげてください。

あと諸々のセッティングでエラーが出る場合には一度[product]➡[clean]してみてください

いざ実行。

f:id:jkawasaki:20140105165627p:plain

無事表示されました。

iPhoneでも同じプログラムが実行出来ると思うのですがそれを試すくらいならば先に進んだ方が良さそうなので今回は試さないです。

これでOpenCVが最低限動く環境となったのであとは好きに遊んでいきましょう。