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

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

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

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 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を動かす方法

先日ついに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で試す特定物体認識

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に突っ込む特徴ベクトルを毎回読み込んでいるため、

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

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

MacとNexus OneでAndroid Oepn Accessoryを試してみる

先日サンフランシスコで行われたGoogle I/OのKeynoteでは様々な発表がありましたが、その中で、僕が特に面白いと思ったのが「Android Open Accessory」です。
簡単に言うと、Androidデバイスに接続するUSB周辺機器を、誰でも簡単に作ることができる仕組みです。

Google I/Oではこの仕組みを試すための開発ボード「ADK」が無償で配られたり、日本のメーカーからも発売されたりしていますが、電子工作ということで敷居が高いと感じている人も多いんじゃないかと思います。

しかし、Android Open Accessoryが適用できるのは、別に電子工作に限った話ではありません。PCとAndroid端末さえあれば、今すぐに試してみることができます。というわけで、早速デモを作ってみたので簡単に解説していきます。

用意するもの

  • AndroidOS 2.3.4にアップデートしたNexus One(テストしてないけど、多分Nexus Sでも問題なし)
  • libusb-1.0インストール済みのMac OS搭載PC (テストしてないけど、その他のUNIX系OSでも動く可能性あり)

今回作るものの概要

Mac PCにUSBで接続されたNexus One上のAndroidアプリと、Mac PC上の実行ファイルとの間で、USBポートを介してデータの通信を行います。
ただ単にデータ通信するだけでは面白くないので、KeyNoteのデモのように、端末の動きをゲームに反映させてみます。

アーキテクチャは以下の図のような感じで、Androidの傾きセンサーやボタンクリックを検出して、その値をUSBを介して通信し、最終的にJavaScriptで作ったゲームに端末の傾きの値を反映させたり、アプリのボタンをクリックするとボールが追加されるようにします。
JavaScriptのゲームはBox2DJSのデモをちょこっと改造した力学演算系のゲームです。

実際に動かしてみるとこんな感じになります。


ソース全体

github: https://github.com/thorikawa/android-openaccessory-with-pc-sample
にアップロードしています。

アプリ側のソース

android-app-usbフォルダがアプリ側のソースとなっています。
基本的には、USB Accessory | Android Developersに記載されている内容に準拠します。

AndroidManifestの記述

USB周辺機器(今回はPC)が端末に接続されたイベントを捕捉できるように、AndroidManifestにIntent-Filterを記述します。

<activity android:name=".Top" android:label="@string/app_name"
  <intent-filter>
    <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
  </intent-filter>
  <meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" android:resource="@xml/accessory_filter" />
</activity>

meta-dataのresourceにxmlファイルを指定していますが、これは別途作成します。

filtering用のxmlファイル作成

/res/xml以下に、上記Intent-Filterのmeta-dataに指定したaccessory_filter.xmlを作成します。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <usb-accessory manufacturer="Poly's Factory" model="Android Oepn Accessory Demo" version="1.0" />
</resources>

ここで指定するManufacturer・Model・Versionによって、Activityが扱えるUSB Accessoryがフィルタリングされます。後で解説するPC側のプログラムでも、同じ値を指定します。

Activity作成

Activity側では、接続されたUSB Accessoryと通信するために、2通りの方法でUSB Accessoryオブジェクトを取得できます。
1.Intentから取得する場合
USB機器接続時のIntentからActivityが呼び出された場合、IntentからUSB Accessoryoオブジェクトを取得することができます。

  Intent intent = getIntent();
  if (UsbManager.ACTION_USB_ACCESSORY_ATTACHED.equals(intent.getAction())) {
    mUsbAccessory = UsbManager.getAccessory(intent);
  }

2.任意のタイミングで取得する場合
USB機器接続後に、任意のタイミングでUSB Accessoryオブジェクトを取得することもできます。

  mUsbManager = UsbManager.getInstance(this);
  UsbAccessory[] accessoryList = mUsbManager.getAccessoryList();
  for (UsbAccessory usbAccessory : accessoryList) {
    // 条件に合致するusbAccessoryを選択
    ....
  }

USB Accessory取得後はUsbManager.openAccessory()で、ParcelFileDescriptorを取得することで、普通のファイルストリームと同じように扱うことができます。

  ParcelFileDescriptor pfd = mUsbManager.openAccessory(mUsbAccessory);
  FileDescriptor fd = pfd.getFileDescriptor();
  FileInputStream fis = new FileInputStream(fd);
  FileOutputStream fos = new FileOutputStream(fd);
  fos.write(....);

実際のデモプログラムの中では、傾きセンサーで取得された値と、ボール追加ボタンが押されたかどうかを、バイト列にしてFileOutputStreamに出力しています。

PC側のソース

pc-usb.cがPC側のソースになっています。libusbとリンクする必要があるので "gcc -lusb-1.0 pc-usb.c" などでビルドして下さい。
PC側では、Android Open Accessory専用のプロトコルである、Android accessory protocolで通信を行う必要があります。
プロトコルの内容は、Android Open Accessory Development Kit | Android Developersを見ればわかりますが、Using Android in Industrial Automation » Turn your Linux computer into a huge Android USB Accessoryの記事で、Cとlibusbを使ったサンプルソースを公開している方がいらっしゃるので、ここではそれを参考にしてプログラムを書いてみます。

Android端末との接続

まず、USBで繋がっているAndroid端末の接続ハンドルを取得します。

#define VID 0x18D1
#define PID 0x4E12

libusb_init(NULL);
libusb_device_handle* handle = libusb_open_device_with_vid_pid(NULL, VID, PID);

ここでVendorIDとProductIDを指定して、ハンドルをオープンしていますが、このIDは機種ごとに異なり、また同じ端末でも、USBデバッグモードとそうでない時で異なるので注意してください。
上記のIDはNexus Oneのデバッグモードで指定するIDです。

VendorID・ProductIDは、Mac OSであれば
/Developer/Applications/Utilities/USB Prober.app/Contents/MacOS/USB Prober
などを使って簡単に調べることができます。

Accessory Modeのset up

次に、Android端末はそのままではAccessoryと通信を行うモードになっていないので、モードを変更します。
Attempt to start the device in accessory modeに記載されている通信を、libusbを介して行います。
手順は、(1)端末がAndroid accessory protocolに対応しているかどうかの判定 (2)Accessory Identificationの送信 (3)端末をAccessory modeに変更 です。

(1)端末がAndroid accessory protocolに対応しているかどうかの判定

libusb_control_transfer(handle,0xC0,51,0,0,ioBuffer,2,0);

ioBufferにサポートするaccessory protocolのバージョンが返り、この値が0以外ならば端末がaccessory protocolをサポートしていると判断します。(現在サポートしている場合のバージョンは"1"のみ存在する)

(2)Accessory Identificationの送信

libusb_control_transfer(handle,0x40,52,0,0,(char*)manufacturer,strlen(manufacturer),0);
libusb_control_transfer(handle,0x40,52,0,1,(char*)modelName,strlen(modelName)+1,0);
libusb_control_transfer(handle,0x40,52,0,2,(char*)description,strlen(description)+1,0);
libusb_control_transfer(handle,0x40,52,0,3,(char*)version,strlen(version)+1,0);
libusb_control_transfer(handle,0x40,52,0,4,(char*)uri,strlen(uri)+1,0);
libusb_control_transfer(handle,0x40,52,0,5,(char*)serialNumber,strlen(serialNumber)+1,0);

この命令によって、USB機器を識別するためのManifactorer・Model・Versionなどの情報が端末に送信されます。accessory_filter.xmlに記載されたフィルタリング情報は、ここで送信された値と一致するかで判定します。

(3)端末をAccessory modeに変更

libusb_control_transfer(handle,0x40,53,0,0,NULL,0,0);
Accessory Modeでのデータ送受信

以上で端末がAccessory Modeに変更されました。
このタイミングで端末のProduct IDが変更されるので

libusb_close(handle);

によって、Accessory Mode変更前の接続ハンドルをクローズし、

#define ACCESSORY_PID 0x2D01
handle = libusb_open_device_with_vid_pid(NULL, VID, ACCESSORY_PID)

によって、新しいAccessory ModeのPIDで接続ハンドルを作り直します。

ここまで行えば、後はlibusb_bulk_transfer関数を通じて、端末とバイトストリームをやりとりすることができます。

#define IN 0x83
#define OUT 0x03
#define BUFFER 1024

unsigned char buffer[BUFFER];
static int transferred;
while (libusb_bulk_transfer(handle, IN, buffer, BUFFER, &transferred, 0) == 0) {
  ...
}

ここで定数INとOUTは、それぞれAndroid側のUSBインターフェースを識別する値で、これも端末ごとに異なっています。今回はNexusOneの値を採用しています。
他の端末の場合、ProductIDと同じようにUSB Proberなどで確認してください。

PC側プログラムではここで読み込んだ値を、JavaScriptからも読み込めるように、一旦テキストファイルに書きだしています。

JavaScriptゲーム側

box2d-with-usbフォルダ以下が、ブラウザで実行するJavaScriptゲームのソースになっています。
詳細は省きますが、PC側プログラムがテキストファイルに書きだした取得された傾きセンサーの値と、ボールが追加されたかどうかのフラグを参照して、それに応じた力学アニメーションを行うようになっています。

まとめ

Android Open Accessoryは「オリジナルな周辺機器を自分で作れる」という面に目が行きがちですが、PCとAndroid端末でも簡単に通信できるよ!ということでサンプルの解説をしてみました。
電子工作が苦手な人でも、USB通信の中身を完全に自作できる、と考えると夢が広がるのではないでしょうか!

CV最先端ガイド勉強会でSVMについて発表してきました

本日開催された第9回「コンピュータビジョン最先端ガイド」勉強会@関東SVMの章を発表してきました。
SVMPRML読書会でも発表しているので、資料は半分くらい流用です。悪しからず。

SVMをちゃんと勉強しようと思うと、双対定理から二次計画問題、カーネル法などかなり幅広い知識が必要で、2回目の発表でも、自分自身勉強不足を痛感させられます。今回は特に、ラグランジュ未定定数法と双対定理については、内容を諳んじれるように頭に叩き込むようにしました。未だに参考書がないと内容が出てこないので。またlibsvmなどのライブラリを自分でも使い始めたので、パラメータとSVMの汎化性能については身を持って知ることができた点は良かったと思っています。

勉強会では、特にカーネルの選び方について、参加者の間でも熱い議論が交わされました。僕は資料に「ガウスカーネルだけで問題ないでしょ?」的なことを書いていたんですが、僕の前の発表でその説は叩きのめされ、僕も考えを改めることになりました。

たとえば、第3章6節には、畳み込みカーネルという(実数ベクトル以外の)構造化データに対するカーネルの例が上げられています。このような例を見たのは初めてで、なるほどガウスカーネル以外のカーネルも重要なのだなあと実感することができました。

また、実数ベクトルではガウスカーネルが使われることが多いと思うのですが、多項式カーネルの方が綺麗に線形分離できるケースもあるだろうという意見もありました。まあそういうケースもあるかもしれない、とは思うのですが、いまいち実例が思いつきません。例えば、スイスロールを例に上げると、ちょうどスイスロール上の境界線で線形分離できるようなカーネルを見つける、みたいなイメージでしょうか。このあたりも今後実践経験で学んでいければよいな、と思います。