読者です 読者をやめる 読者になる 読者になる

遥かへのスピードランナー

シリコンバレーでAndroidアプリの開発してます。コンピュータービジョン・3D・アルゴリズム界隈にもたまに出現します。

Preview for Glassが公式Glasswareになりました

Google Glass

今年3月から公開していたGoogle Glass用Glassware「Preview for Glass」が、約半年のレビュープロセスを経てようやく公式Glasswareになりました。10/2時点で公式Glasswareは全世界合計で94個しかなく、そのうちの1つということになります。

f:id:thorikawa:20141003100548p:plain

公式ページ: http://www.preview-anything.com/

MyGlassのページ: https://glass.google.com/glassware/14547069768774444625

おそらく多くの読者が知らないと思うので説明しておくと、PreviewはGoogle Glassで映画のポスターを画像認識してトレーラーを再生するGlasswareです。ポスター画像の特徴データベースやトレーラーとの紐付などはすべてクラウド上にあり、自動で最新の映画情報が追加されていくのが特徴です。なお現時点ではUSの映画・ポスターにのみ対応しており、また昨年以前の映画はほとんどデータが入っていないのであしからず。

Google Glassの公式Glasswareになるためには、Googleにレビューを受ける必要があります。現時点で、日本語でこのレビューの体験談を書けるのはたぶん自分しかいないと思うので、せっかくなので記しておこうと思います。

1月〜3月プロトタイプ&ベータ公開

今年の1月に画像認識を使ったGoogle Glassアプリを作ろうと思い立ち、とりあえず2日くらいでプロトタイプを作ってデモ動画を公開した。

Glassware "Preview for glass" - recognize the movie image and play its trailer video - YouTube

この動画にGoogle+などで思ったより反響があったので、ちゃんと公開しようと思い、認識ロジックのクラウドへの移行を行って、3月にベータ版を公開した。この時はapkをユーザーに直接ダウンロードしてもらう、いわゆる「野良アプリ」として配布していた。

3/18 音声コマンド申請

当時MyGlassで配布している公式Glasswareは30くらいしかなく、公式になるのはなかなか敷居が高そうだったが、面白そうなのでチャレンジしてみることにした。申請のために第一に必要なのが音声コマンドのレビューだ。

音声コマンドはいわゆる「Ok glass」の後に続くGlasswareを起動するためのコマンドだが、標準で用意されたコマンド以外のものを使うときは、別途申請する必要がある。Previewの場合、当時はぴったり合うコマンドがなく、「Preview the movie」という音声コマンドが使いたかったので申請することにした。

4/3 音声コマンドレビュー結果受領

2週間ほどたって音声コマンドのレビュー結果が来た。「Recognize this」という音声コマンドをそのうち追加するからそっちを使えとのこと。最初みたときは、あまりユーザー目線に立ってないコマンドだなあと思ったのだが、しばらく考えた末に受け入れることにする。

そしてGlassware本体のレビュー申請もするかーと思ったタイミングで、GDKのGlasswareの申請がまだ始まっていないことに気づく。当時はサードパーティからのGlasswareはMirror APIを使ったものしか受け付けていなかったのだ。

4/17〜4/20 Glassware申請

XE16の公開とほぼ同時に、GDK製Glasswareの申請も受け付けつけるようになったので、早速申請する。申請にあたり必要な画像が何パターンかあったのと、プライバシーポリシーのページとかも用意しなければいけなかったので、少し時間がかかりサブミットできたのは結局4/20になった。

〜8/25 休眠期間

サブミットしてから数ヶ月ほとんど音沙汰がなかった。何度かapkをアップデートする必要があって、直接メールのやりとりをしたが、それ以外は「もうすぐ知らせるからね!」と言うばかりで、自分もほぼ諦めかけていた。6月末にGoogle I/OがあったのでGoogle Glass Advocateの人に直接聞いてみたが、「プロセスが遅いのは認識している。issue trackerを立ち上げてスピードアップするからちょっと待ってて!」みたいなことを言われただけだった。

8/25〜10/2 Issue trackerでのやりとり、そして公開

そんなこんなで公式化をほとんど諦めかけていたが、8月末にようやくレビューチームからIssue Trackerを作ったから確認してくれ、というメールが届いた。Issue Trackerといってもただのスプレッドシートである。開くと20個弱のissueが列挙されており、ほとんどが、Glassのブランドと、UIの挙動の統一化に関する内容だった。

Issueはたとえば以下のような感じだ。
・Webサイトのドメイン名にglassを使わない(for-glassとかならOK)
Google GlassではなくGlassという名称を使う
・その際GlassのGは大文字にする
・最初のGlassに™をつける
・Applicationの代わりにGlasswareという名称を使う
・起動時のスプラッシュ画面の代わりに他のアプリも使っている共通のロード画面を使う(サンプルコード&xmlも添付)
・エラー画面で共通のエラーダイアログを使う(サンプルコード&xmlも添付)
・タップしても何も起こらない画面ではタップ時に警告音をならす

最初これらのIssueを見た時は、自分の中のモチベーションも失われかけていて1週間ほど放置してしまったが、レビューチームから「早く対応してね」とやる気溢れるプッシュも受けたので頑張って対応することにした。結局3回くらい修正→確認のやりとりをしてようやく全部OKをもらえて、今日の公開に至るのであった。

所感

ひと通りレビュープロセスを経験して感じたのが、GoogleはGlassのブランドやUIに対してとても慎重だということです。以前Googleが「Glass」という商標を取ろうとしていると話題になってましたが、今回のレビューで「Google Glass」ではなく「Glass」に統一すること・最初の「Glass」に™を付けることなどを指摘されたのを見るともう通ったのでしょうか。(商標データベースの調べ方がいまいちわかっていない)いずれにしても、そこから伺えるのは「Google Glass」という一部のGeekしか興味を持たないデバイスのイメージを改め、「Glass」という既存のメガネという概念に近づけ、それを置き換える方向で頑張っているんじゃないかと思います。Google Glassは死んだとか、Android Wearの影で忘れ去られているとかいろいろ言われていますが、最近はGlasswareもいいペースで増えてきているし、彼らはまだまだ頑張っている印象を受けるので、これからに期待しています。まあ僕も普段は装着してないんですけどね。

スマホから送信したNotificationでAndroid Wear上のActivityを開く方法

android android-wear

最近Android Wearをいじっているのでその話題。スマホからWearに対して送信したNotificationで、Android Wear上のActivityを開く方法について書きます。

Google カメラアプリの挙動

先日、Googleのカメラアプリがアップデートされて、Wearからシャッターを切れるようになりました。この挙動がわりと面白くて、スマホ側でカメラアプリを起動するとWearに通知が表示され、その通知をクリックすると、Wear上でシャッターを押すための専用Activityが起動される流れになっています。

f:id:thorikawa:20140712163747p:plain

この挙動を実装しようとすると、なかなか一筋縄ではいきません。現状のNotification APIの仕様ではスマホ側から発行したNotificationは、スマホ側のActivityしか開くことができないからです。

これを解決する方法の一つが、スマホからWearへの通知をNotification APIではなくData APIなどのデータ送受信APIで行い、Wear側でNotification APIを使って通知を発行するという方法です。Googleのカメラアプリもこの方法を使っているようです。

f:id:thorikawa:20140712165848p:plain

実装方法

実際のコードは以下のようになります。(プロジェクト全体はthorikawa/WearNotificationSample · GitHubにあげてあります)


スマホ側では、Data APIを利用してデータ変更を行います。

    PutDataMapRequest dataMapRequest = PutDataMapRequest.create(Constants.NOTIFICATION_PATH);
    dataMapRequest.getDataMap().putString(Constants.NOTIFICATION_TITLE, "This is the title");
    dataMapRequest.getDataMap().putString(Constants.NOTIFICATION_CONTENT, "This is a notification with some text.");
    // Set timestamp so that it always trigger onDataChanged event
    dataMapRequest.getDataMap().putLong(Constants.NOTIFICATION_TIME, System.currentTimeMillis());
    PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
    PendingResult<DataApi.DataItemResult> pendingResult = Wearable.DataApi.putDataItem(mGoogleApiClient, putDataRequest);

一方、Wear側ではデータの変更イベントをServiceで受け取るようにします。

AndrodManifest.xmlでデータ変更時に発行されるActionを受け取るServiceを定義します。

    <service
        android:name=".NotificationUpdateService"
        android:exported="true">
        <intent-filter>
            <action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
        </intent-filter>
    </service>

このServiceはWearableListenerServiceを継承しており、onDataChangedでスマホ側からのデータ変更を検知し、実際にWear上で表示されるNotificationを発行します。その際にaddActionやsetContentIntentなどで、Wear上のActivityに対するPendingIntentを設定してやれば、NotificationをクリックしてWear上でActivityを開くことができます。

public class NotificationUpdateService extends WearableListenerService {
...
    @Override
    public void onDataChanged(DataEventBuffer dataEvents) {
        for (DataEvent dataEvent : dataEvents) {
            if (dataEvent.getType() == DataEvent.TYPE_CHANGED) {
                if (Constants.NOTIFICATION_PATH.equals(dataEvent.getDataItem().getUri().getPath())) {
                    DataMapItem dataMapItem = DataMapItem.fromDataItem(dataEvent.getDataItem());
                    String title = dataMapItem.getDataMap().getString(Constants.NOTIFICATION_TITLE);
                    String content = dataMapItem.getDataMap().getString(Constants.NOTIFICATION_CONTENT);
                    sendNotification(title, content);
                }
            }
        }
    }

    private void sendNotification(String title, String content) {

        // this intent will open the activity when the user taps the "open" action on the notification
        Intent viewIntent = new Intent(this, MyActivity.class);
        PendingIntent pendingViewIntent = PendingIntent.getActivity(this, 0, viewIntent, 0);

        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.drawable.ic_launcher)
                .setContentTitle(title)
                .setContentText(content)
                .addAction(R.drawable.ic_launcher, "Open", pendingViewIntent)
                .setLocalOnly(true)
                .extend(new NotificationCompat.WearableExtender().setContentAction(0).setHintHideIcon(true));

        Notification notification = builder.build();

        NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(this);
        notificationManagerCompat.notify(notificationId++, notification);
    }
...
}

まとめ

このような感じで、Wearで利用できるAPIは限られていますが、組み合わせによってはまだまだ面白い使い方ができそうです。

AKAZE特徴量の紹介と他特徴量との比較

CV 画像認識

1月6日追記:作者のPablo氏とメールのやり取りをする中で、当初掲載していたスピードのベンチマークコンパイラの最適化オプションが指定されていなかったことに気づきましたので、最適化オプションを指定して再度計測し、結果を差し替えました。

2012年のComputer Vision Advent Calendarで、さかな前線 » ECCV2012で発表されたKAZE局所特徴量を試してみた という記事を見て以来、ずっと気になっていた、KAZE特徴量を自分でも使ってみようと色々試していたところ、KAZE特徴量を高速化したAKAZE特徴量が公開されていることに気づきました。

AKAZE Features

僕の用途は、Android上のカメラプレビューからの特定物体認識なのですが、SIFTやSURFは特許問題があるし、KAZE特徴量はとにかく遅くて困っていた*1ので、これは渡りに船です。さっそく試してみたので、AKAZEの簡単な紹介と、他特徴量との比較ベンチマークを公開したいと思います。

AKAZE特徴量とは

作者のページによると、AKAZEのもととなっているKAZEのアイデアは、SIFTやSURFで使われているGaussian filterによるスケールスペースは、Gaussian filterが等方的であるため、オブジェクトのエッジもぼやかしてしまい、局所的な特徴をうまくとれないことがある。それを解決するために、非線形で非等方的なスケールスペースを使いましょう、というもの*2

さらにAKAZEでは、Feature Descriptorとして、Modified-Local Difference Binary (M-LDB)という独自のDescriptorを使用し、さらにピラミッド構造の計算を高速化するための独自の工夫を組み入れることで、ロバスト性の向上と高速化を図った、ということです。

 

AKAZEのOpenCVへの組み込み

AKAZEのソースコードは、githubに公開されています。

AKAZE - https://github.com/pablofdezalc/akaze

素の状態でもわりと使いやすいのですが、OpenCV feature2dのcommon interfaceに対応しているとさらに使いやすいと思ったので、それに対応したバージョンを以下で公開しています*3

AKAZE - cv::Feature2D API wrapper: https://github.com/thorikawa/akaze-opencv

詳しい使い方は、main.cppを見てください。以下のような使い方ができます。

Mat img = imread(...);
std::vector<KeyPoint> keypoints;
Mat descriptors;
Ptr<FeatureDetector> detector = FeatureDetector::create("AKAZE");
detector->detect(img, keypoints);
Ptr<DescriptorExtractor> extractor = DescriptorExtractor::create("AKAZE");
extractor->compute(img, keypoints, descriptors);

比較ベンチマーク

実際どれだけの性能なのか、他の特徴量との比較ベンチマークをとってみました。ベンチマークツールとしては、thorikawa/OpenCV-Features-Comparison · GitHub
を利用しており*4、Lenaさん画像を回転させたりスケール変化させたりしながら、特徴点対応の正確さを計算しています。なお、パラメータは全てデフォルトですので、チューニング次第で結果は異なります。(当たり前ですが)

スケール変化耐性

f:id:thorikawa:20140105204614p:plain
[f:id:thorikawa:20140105195310p:original]

回転耐性

f:id:thorikawa:20140105195306p:plain

輝度変化耐性

f:id:thorikawa:20140105195302p:plain

Blur耐性

f:id:thorikawa:20140105195254p:plain

スピード

注意:当初掲載していたスピードのベンチマークは、コンパイラの最適化オプションを指定せず計測したものでしたので、最適化オプション付きで再度計測し、結果を差し替えました。

Algorithm Average time per Frame (ms) Average time per KeyPoint (ms)
AKAZE 30.4636 0.208972
KAZE 108.736 0.553694
ORB 5.78174 0.015048
SIFT 44.0598 0.161384
SURF 20.917 0.0444678

スピードに関して1点注意事項があります。AKAZE/KAZEのコードはOpenMPでの並列化に対応しているのですが、このベンチマークではOpenMPを利用していません*5。Pablo氏からの指摘によれば、AKAZEのロジックは並列化に向いているため、OpenMPの利用により大幅なスピードアップが見込めるとのことです。OpenCVのSIFTとSURFのコードを見たところ、SIFTのコードは並列化していませんが、SURFのコードは並列化しています。上記ベンチマークのSURFの速さは、それも起因していると思われます。

参考:最適化オプション指定前の計測結果

Algorithm Average time per Frame (ms) Average time per KeyPoint (ms)
AKAZE 117.458 0.797077
KAZE 741.112 3.75878
ORB 5.77404 0.0151431
SIFT 41.0728 0.147772
SURF 18.4614 0.0391613

結論

画像変化のロバスト性については、スケール・回転・輝度・Blur全てにおいて、AKAZEが最も良い数値を示していることが分かります。実際使ってみた感じでも、かなり正確にトラッキングしている感じがします。

スピードに関しては、SIFTやSURFに比べてもかなり遅いです。スピードに関しても、SIFTやSURFに比べて同程度、もしくはそれ以上です。作者のPablo氏によると現在も改良を続けているとのことで、今後も期待できます。

このブログによると、SIFTやSURFよりもライセンス的に使いやすいオープンソースな特徴量を目指して作られたということで、画像認識で商用化を狙っている人たちには朗報ではないでしょうか。

Pablo氏は、もしAKAZE/KAZEを利用する場合は自分に教えてほしい、と言っています。実績づくりや、今度の改善に貢献するためにも、興味のある方はgithubに記載されているアドレスに連絡をとってあげてください。

*1:平均的な画像サイズで、特徴点検出〜マッチングまで10秒以上かかる

*2:どのように非線形なスケールスペースを構築するのか、というところまでは理解しきれていません

*3:作者のPablo氏もOpenCVに対応させると言っていますが、まだ対応されていないようです

*4:Ievgen氏が公開されている https://github.com/BloodAxe/OpenCV-Features-Comparison のfork

*5:OpenMPに対応していない、Androidでの利用をメインに考えているため

Coursera Machine Learning Study Group at Silicon Valley

機械学習

最近、courseraというオンライン大学が流行っていますが、ベイエリアでももちろん流行っていて、courseraで学んだことをオフラインでディスカッションしようぜ、みたいなmeetupもちらほら開催されてたりします。で、僕も先週から始まったAndrew先生のMachine Learningクラスをとっているので、そのmeetupに参加してきました。

僕が参加したのは、Silicon ValleyエリアのMachine Learning study groupで、Ruckus wirelessという会社の会議室に20人程が集まりました。先週第一回が行われて、今回が二回目の開催。僕は今回が初参加だったので、他の初参加者と一緒に、機械学習との接点や、今やっていることなど軽く自己紹介をしました。話を聞くとビッグデータをバックグランドにしている人が多いようです。

自己紹介が終わると、今回のメインセッション、PG&EのパフォーマンスエンジニアStephenによるプレゼンです。お題はMachine Learningのキャパシティプランニングやパフォーマンス解析への応用。サーバーのパフォーマンスをLinear Regressionを使って予測したり、サーバーのCPU Usage HistoryをSVMでパターン分けしたりといった実例を、Rのコードを使ってデモしてくれました。機械学習の実用、というと疑問に上がってくるのが「そんな仮定で大丈夫か?」という点で、例えば今回の例だと、例えばサーバーパフォーマンスをLinear functionと仮定していいのかとかという点が疑問でしたが、その辺りについては残念ながら特に深い考察はないようでした。(Stephen曰く、それがスタンダードだからLinear Regressionを使っているよ、とのことでした)SVMの方も、Rのsvm関数とtune.svmを使ってチューニングしているだけのシンプルな使い方をしているようです。

何より印象的だったのが、こちらの人々は本当によく質問や議論をする。Stephenのセッションはもともと45分の予定だったのですが、結局1時間半くらいかかりました。ここはちょっと分からないから後で自分で調べてみよう、なんて遠慮は一切なく、皆が皆、疑問点は余さず聞いてきます。まあそういうことが自然にできるのが、こちらの人の強み(で逆にできないのが日本人の弱み)だと良く言われますが、個人的には一長一短かなあと思っていて、結果的に全ての工程で分からない人に合わせて進めることになるので、効率が悪いと感じることもあります。

今回はプレゼンだけでタイムオーバーになってしまいましたが、今後はcourseraの課題を議論したり、ハッカソンをしたりと色々やっていく予定のようです。僕もPRML読書会落第生の名に恥じないように頑張っていく所存です。

ってなわけで、今シリコンバレーでモバイルエンジニアをやってます。まだまだ英語に四苦八苦していますが、なんとか生きてます。

ライブラリに依存するプロジェクトのテストプロジェクトをantで実行できない件+AOSPにパッチを送ってみた件

android

Android SDK r14以降で、テストプロジェクトおよび、テスト対象の本体プロジェクトの両方がライブラリプロジェクトのクラスを呼び出している場合、antでのテスト実行で実行が失敗します。このままではJenkinsでテストを自動実行するときなどに困ってしまうので、Android SDKbuild.xmlを書き換えて対応します。

以下この問題の詳細についてです。

問題の概要

問題が起こるのは、以下のようなプロジェクト構成の場合です。

  • ライブラリプロジェクト
  • 本体プロジェクト(ライブラリプロジェクトのクラスを呼び出す)
  • テストプロジェクト(ライブラリプロジェクトのクラスを呼び出す)

このようなプロジェクト構成をビルドしようとした場合に、取り得る手段は2通り考えられます。

  1. 本体プロジェクトのビルドパスにライブラリプロジェクトを追加して、本体プロジェクト側でライブラリプロジェクトをExportすることでテストプロジェクトは、ライブラリプロジェクトを参照させる
  2. 本体プロジェクト・テストプロジェクト両方のビルドパスにライブラリプロジェクトを追加する

ただ、これは両方ともうまくいきません。1はEclipse上ではうまく動きますが、antビルド時にはテストプロジェクトのコンパイルエラーが発生します。2はantテスト実行の際に、実行時例外が発生します。以下詳細を見てみます。

1のExport設定はEclipseのビルドパス設定で行うことができます。

この設定を行えば、Eclipse上でのテスト実行はうまくいきます。しかし、このExport設定が有効なのはEclipse上に限った話で、antからビルドしする際には読み込んでくれません。
そのため、テストプロジェクトをantでビルドしようとするとクラス解決がうまくいかず、コンパイルエラーになります。

$ ant clean debug install test 
-compile:
    [javac] Compiling 2 source files to /workspace/AndroidExampleTest/bin/classes
    [javac] /workspace/AndroidExampleTest/src/com/polysfactory/antbuild/sample/test/TestCaseClass.java:5: シンボルを見つけられません。
    [javac] シンボル: クラス LibraryClass
    [javac] 場所    : com.polysfactory.andbuild.library の パッケージ
    [javac] import com.polysfactory.andbuild.library.LibraryClass;
    [javac]                                         ^
    [javac] /workspace/AndroidExampleTest/src/com/polysfactory/antbuild/sample/test/TestCaseClass.java:10: シンボルを見つけられません。
    [javac] シンボル: 変数 LibraryClass
    [javac] 場所    : com.polysfactory.antbuild.sample.test.TestCaseClass の クラス
    [javac]         String s1 = LibraryClass.test();
    [javac]                     ^
    [javac] エラー 2 個

2の両方にライブラリプロジェクトを参照する方法は、Eclipseでもantでも、テスト実行に失敗します。
以下のような実行時例外が発生するはずです。

test:
     [echo] Running tests ...
     [exec] 
     [exec] com.polysfactory.antbuild.sample.test.TestCaseClass:.
     [exec] Error in testCase:
     [exec] java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
     [exec] 	at com.polysfactory.antbuild.sample.LibraryClassCaller.callLibraryClass(LibraryClassCaller.java:7)
     [exec] 	at com.polysfactory.antbuild.sample.test.TestCaseClass.testCase(TestCaseClass.java:12)
     [exec] 	at java.lang.reflect.Method.invokeNative(Native Method)
     [exec] 	at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:169)
     [exec] 	at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:154)
     [exec] 	at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:529)
     [exec] 	at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1448)
     [exec] 
     [exec] Test results for InstrumentationTestRunner=..E
     [exec] Time: 0.086
     [exec] 
     [exec] FAILURES!!!
     [exec] Tests run: 2,  Failures: 0,  Errors: 1
     [exec] 
     [exec] 

これは、本体プロジェクトから参照しているライブラリクラスと、テストプロジェクトから参照しているライブラリクラスが異なるために発生するエラーです。apkを解凍してみれば分かりますが、ライブラリプロジェクトのクラス群が本体プロジェクトのapkと、テストプロジェクトのapkの両方に含まれてしまっています。

Android SDK r13以前ではこの方法でうまくいっていました。Android SDK r14からAndroidのライブラリプロジェクトの扱い方が変わり、このような現象が発生するようになったようです。

ライブラリプロジェクトのクラス群は、本体apkのみに含まれて、テストapkには含まれないのが正しい動きです。しかしAndroid SDKに含まれるbuild.xmlは、そのようなビルドプロセスをサポートしていない為、この問題を解決するにはAndroid SDKbuild.xmlを書き換える必要があります。

解決方法

build.xmlを書き換えます。antのビルドプロセスとして、テストプロジェクトのビルド->subantの呼び出しで本体プロジェクトのビルド->subantの呼び出しでライブラリプロジェクトのビルドとなっていますが、antではsubantの呼び出しをまたいで変数を共有できないので、一時ファイルにライブラリプロジェクトのjarのパスを保存しておくアプローチを取ります。

↓r16のbuild.xmlをベースにしたdiffファイル

<     <!-- Libraries property file -->
<     <property name="out.libraries.prop.file.name" value="libraries.prop" />
452c450
<                 
---
> 
524d521
< 
554,571d550
< 
<         <!-- save libraries' jar paths to a temporary file  -->
<         <propertyfile file="${out.absolute.dir}/${out.libraries.prop.file.name}">
< 	        <entry key="libraries.jars" value="${toString:project.libraries.jars}" />
<         </propertyfile>
< 
<         <!-- load tested project's libraries path if this is test project -->
<         <if>
<             <condition>
<                 <and>
<                     <isset property="tested.project.absolute.dir" />
<                 </and>
<             </condition>
<             <then>
<                 <property file="${tested.project.absolute.dir}/${out.dir}/${out.libraries.prop.file.name}"/>
<             </then>
<         </if>
< 
626d604
< 
628c606
<                     value="${tested.project.absolute.dir}/bin/classes;${libraries.jars}"
---
>                     value="${tested.project.absolute.dir}/bin/classes"

AOSPにパッチ送ってみた

せっかくAndroid SDKを修正したので、これを機にAndroid Open Source Projectにパッチを送ってみました。もし応援してくれる人がいたらスターとかつけちゃってください!
https://android-review.googlesource.com/33230

Windows VistaでKinect SDKを動かす方法

Kinect

先日ついにMicrosoft公式のKinectSDKが発表されましたね!

さっそく試してみたかったのですが、KinectSDKはWindows7のみサポート、そして僕の持っているのはWindowsVistaのみ・・・。
ということで何とかWindowsVistaで動かせないものかと頑張ってみました。

で、結論から言うと、

  • 深度センサー・モーションセンサーの部分はVistaでも動かすことが可能
  • 音声入力・認識の部分はVistaでは動かせなかった

という結果になります。
サンプルプログラムで言うと、SkeletalViewerはまったく問題なく動きます。ShapeGameは音声入力の機能以外は動きます。
もうちょっと頑張れば音声の部分も動かせるかもしれませんが、セットアップにそこまで時間をかける気力もなく、僕は諦めてWindows7を買ってしまいましたが、一応記録として方法を記しておきます。以下32ビット版での手順ですので、64ビットの場合ファイル名を読み替えてください。

手順1:msiファイルを展開する

KinectSDKのインストーラーKinectSDK32.msiを好きな場所にダウンロードします。
http://research.microsoft.com/en-us/um/redmond/projects/kinectsdk/download.aspx

そのあとコマンドプロンプトを立ち上げて、ダウンロードした場所まで移動し、以下のコマンドを発行します。
C:\Kinect\SDK\Directory> msiexec /a KinectSDK32.msi targetdir="C:\temp"
ここでtargetdirには展開したい場所を指定します。どこでもいいのですが、ここではC:\tempを指定しています。
このコマンドを実行すると以下のように5つのファイル・フォルダがtargetdirに展開されます。

手順2:フォルダの移動とパスの設定

手順1でできたPFilesX86フォルダを、C:\Program Files\Microsoft Research KinectSDKのパスに名前を変えて移動します。
移動が完了したら、環境変数のPathに「C:\Program Files\Microsoft Research KinectSDK」を追加します。

手順3:ドライバのインストール

Kinectを接続するとドライバのインストールが求められますので、「ドライバの場所を指定する」を選択して、「C:\Program Files\Microsoft Research KinectSDK\Drivers」を指定します。自動的に必要なドライバがインストールされます。
ドライバのインストール後、再起動が必要です。

完了

これだけで完了です!
C:\Program Files\Microsoft Research KinectSDK\SkeletalViewer.exe

C:\Program Files\Microsoft Research KinectSDK\ShapeGame.exe
をクリックすると以下のように動くことが確認できると思います。

追伸

音声入力・認識部分をVistaで動かすことができた方はご連絡下さいませ。

AndroidとOpenCVで試す特定物体認識

android CV

6月2日に開催されたDevLOVEさんと弊社の共同開催勉強会で、「Android×ComputerVision」というお題で発表してきました。
要はOpenCVをAndroidアプリに組み込んで特定物体認識を試そう、というもの。

資料は以下です。

ソースはgithubで公開してます。
https://github.com/thorikawa/AndroidObjectRecognition/

概要

資料にも記載していますが、カメラのプレビュー画像からSURFの特徴点を検出して、LSHで再近傍検索→特定物体認識というのを毎フレーム行っています。
「物体」はCDのジャケット画像を5枚の内から認識して、それぞれの画像にあった音を鳴らす、というデモを行い、うまく認識することができました。

構成・ビルド方法

チェックアウト後のソースは

  1. OpenCVをAndroidから利用しやすくするためのOpenCVプロジェクト(android-jni以下)
  2. アプリのプロジェクト(apps/ObjectRecognition以下)
  3. 検出対象の画像から特徴点を検出しておくユーティリティ(dump_keypoints.cpp)

の3つから構成されています。
上記1番はAndroidライブラリプロジェクトとなっており、2番から参照されているため、Eclipseでビルドする場合は、両プロジェクトをimportしておく必要があります。
また、検出対象となる物体の特徴ベクトルはapps/ObjectRecognition/assets/keypoints以下にテキストファイルが配置されています。追加したい場合は、dump_keypointsでダンプした結果をここに追加し、2番のJavaソースで読み込んでいる箇所を修正してみてください。

チェックアウト後のソースには、ビルド後のバイナリも含まれていますが、自分でビルドしたい場合、
apps/ObjectRecognition
に移動してantコマンドを実行することでAndroidアプリをビルドすることができます。

$ cd ${checkoutdir}/apps/ObjectRecognition
$ ant debug

改変方法

ソースを自分でいじりたい場合、アプリ側のJavaソースだけならチェックアウト後のソースをそのまま変更することができます。
c++部分のソースをいじりたい場合は、AndroidをターゲットにしてOpenCVをビルドできる環境が必要です。
まだ環境を構築していない人は、
http://opencv.willowgarage.com/wiki/Android
を参考に環境構築してみてください。

課題と考察

SURFの特徴点検出がとにかく遅いです。濃淡の差が細かく激しい画像だと、1回の検出処理に約3sec程かかります。
OpenCVは、他にもデフォルトでSTARやFASTなどの特徴点検出アルゴリズムを搭載しており、これらの方がSURFよりも速いのですが、精度のチューニングが間に合わなかったため、SURFで実装しています。
実用化に向けては、速度と精度のバランスを取ったチューニングを行う必要があります。(←そりゃそうだ)

また、今回は毎回LSHに突っ込む特徴ベクトルを毎回読み込んでいるため、

  • 特徴ベクトルのデータが膨大
  • 特徴ベクトルの読み込みに時間がかかる

という問題があります。
原理的には計算されたハッシュ値だけをデータとして持たせておけば再近傍の計算はできるはずなので、その部分も課題となります。