ホーム > プログラミング > .NET Framework

.NET Frameworkのアーカイブ

Raspberry PiでMono+OpenNIを動かして、XtionのデータをWireless LAN経由でJitterに突っ込む

XtionとかKinectで色々遊んでると、USBの取り回しがめんどくなってくる。というわけで、小型安価な1board PCであるRaspberry Piを使って無線化してみた。
前にRaspberryPi上でMonoを動かすことはできていたので、試しにC#で書く。遅いのは知ってる。
最初はUser detectionまでやろうかと思ったが、OpenNIのMiddlewareであるNITEがUser detectionを担当しているらしく、NITEはOpen sourceでは無いらしく、諦めた。CortexAなど用のARMバイナリを配布している人は居たが、RaspberryPi用は見つからなかった。方法があれば誰か教えて下さい。
というわけでUser detectionは諦め、とりあえずDepth情報のみをNetworkで投げてみることにした。

ついでに、プロトコルをJitterに合わせることで、Max/MSPで簡単に受信できるようにした。
Jitterで流れている情報はRGBXなフォーマットなので、とりあえずGBにDepthの2byteをそのまま埋める。手抜き。

紆余曲折を経たが、基本的にHard-float coreを使わなければOK。ちゃんと動いた。
xtionjitter
無線LANとXtionを駆動するので、Self-powerのUSB hubは必須。Raspberry Pi本体の電源もHubから取れた。
コンパクトに無線化できたので、余ってたプラダンでガワを作って三脚に設置してみた。
xtionraspberrypi
VNCで無線LANからGUI操作できるので、外にはUSB hubの電源だけが出る感じ。5V – 2AのAC Adapterなので、USB Mobile Batteryとか使えば完全無線化できる気がする。こんど買ってこよう。

裏からの図。青いLEDが光ってるのがUSB Hub。緑に光ってるのが無線LANアダプタ。
RaspberryPi本体のUSBにUSB Hubと無線LANアダプタ、USB HubにRaspberryPiの電源のMicroUSBとXtionが刺さっている。
xtionraspberrypi_back

おまけ
xtionjitter_neko

.NET Framework on Raspberry Pi

MonoとMonoDevelopがRaspberryPiで動くことを確認した。
latest kernelを使っていると、Hard-float ABI絡みのバグでMonoが起動しないが、公式ページからダウンロードできるSoft-float版を使うと難なく起動できた。
WinFormsのプロパティコントロールとか、重そうなGUIが難なく動くのは感動する。動作はそりゃあ遅いが、そのへんは使い方次第。

ついでに、自分がRaspberry Piにしている設定をメモしておく。

  • Setup Wireless LAN driver

たいてい、まずは無線LAN化したい。すべてはこの方が書いてくれている。
しかも、PlanexのUS-GW MicroNというUSB無線LANアダプタは秋葉原で300円で叩き売られてたのを以前まとめ買いしたので、家にありました。
ドライバを登録したら、wicdをインストールしてさくっと無線LANパスワードなどを設定できます。もちろんAuto-Connectにしとく。固定IPにしとくとあとで色々楽。
いまどき有線LANとHDMIディスプレイが同じ所には無いので、最初に無線化しておけばディスプレイやテレビの裏に放置して作業できる。

  • Setup on-screen keyboard

いちいちキーボードをつなぐのは面倒なので、matchbox-keyboardをインストールしてマウスのみで操作できるように。
sudo apt-get install matchbox-keyboard するだけ。

  • X11vncを自動起動させる

そうこうしていると、PCからRaspberryPiを操作したくなってくる。メインのPCでぐぐってコピペしたいときとか。
x11vncを入れると、HDMIで表示している画面をリモートから操作できる。
自動起動するようにしておけば、電源だけ入れてRaspberryPi本体をどっかに放置しとけば、母艦PCからいじれる。
本家のBBSに自動起動の方法がある。.desktopファイルを作るのがポイント。
http://www.raspberrypi.org/phpBB3/viewtopic.php?p=108862#p108862

  • Display sizeを変更する

VNCで接続すると、ワイド大画面で優秀さをアピールしてくるRaspberryPiちゃんを、適度な画面サイズに調教する。/boot/config.txtのframebuffer_widthとheightを800*600ぐらいにしておく。

  • Synaptic

GUIでSearchしながらいろいろInstallしたい。sudo apt-get install synapticしておくと、普通に使える。

  • 日本語Fontのインストールとlocaleの変更

ttf-takaoなど、日本語フォントをSynapticでインストールしてから、sudo raspi-configでlocaleを変更する。フォントを先に入れないと文字化けする。

このへんまでやっとけば、あとはいろいろなアプリケーションを入れて遊べると思う。
ついでのつもりが、Raspberry Pi初期設定がメインになってしまった。。

Kinect互換デバイスXtion Proで遊んでる

Xtionで色々遊んでいます。
とりあえず、ジェスチャー認識で色々やってみてたりする人がたくさん居る中で、新しくて使えそうなことをやりたかったので、ちょっと数学を使う分野に手を出してみている。
やりたい事は、プロジェクターとのカップリング。プロジェクターで映像が映せるとこに持って行って、適当に設置して、ちょっとキャリブレーションするだけでスクリーンの位置を認識して、なんかスクリーンの前に立つ人とのインタラクションする。
Xtionでとった3次元空間から平面っぽいところを認識して、人がプロジェクターの映像の四隅を指さしたりすることで、プロジェクター画面の座標と3次元座標を対応付ける予定。

いつか書いた開発環境についてはVisual Studio on WindowsとMonoDevelop on Linuxを横断して、Dropboxでプロジェクトファイルごと同期しながら書いてる。特に安物ノートPCのUbuntu上でこれほど簡単に空間認識できるのはヤバい。
後はMacの動作確認をしたい所。

Kinect互換デバイス購入して開発環境作った

Kinect hackっぽいことをしてみようと思っています。
数年前に流行りましたが、今ではもはやHackでも何でもなく、Microsoftから公式SDKも公開されている。

Hardware

とりあえずKinect sensorを買っても良かったんですが、互換デバイスであるXtionを買うことにしてみた。
本家Kinectとの違いは以下の通り。機能は多少犠牲になるが、モバイル性能重視でXtionにした。

  • バスパワー駆動。USBケーブル1本で接続完了!
  • 小さい。Kinectの半分ぐらいに見える。
  • 普通のカメラが無く、RGB画像は撮れない。Depth画像のみ。
  • チルトモーターが無い。本家は首がモーターで上下に傾くらしい。

Software library

とりあえずOpenNIをいじっている。本家KinectだとKinect SDKがいいかもしれない。
OpenNIは骨格認識の際に、上半身だけとかでも認識できるらしい。ライセンス的にもOpenNIの方がよさそう。何よりLinux対応の良さが重要。

Development Environment

開発に使うOSと開発環境(開発言語)は迷うところ。
KinectやXtionを使う人はメディアアート界隈の人が多くて、openFrameworksなどからプラグイン経由で使ってる人とかが多いのかな?
MaxとかjitterとかopenFrameworksとか、そのへんの仕組みは面白そうだし、cross platformなのも魅力だが、変化が激しいし、コードを書くのに慣れた人には逆にとっつきにくい。

で、結局いつものパターンで開発言語はC#を使いたい。慣れてるから。現在、LinuxなMobile PCとWindowsなDesktopを使っているので、両方に対応したい。てことでMonoDevelopってことになる。
Xtionは情報が少なくて、特にMonoで動かす事例があんまり無い。公式にもLinuxでの対応言語にC#は無い。
最悪C++でいいや!と思って買っちゃったが、WindowsでビルドできたプロジェクトをLinuxでも普通にビルド&実行できた。
(落としてきたまんまのサンプルのバイナリは動かなかったが、理由は追っていない。)

Gtk#でWorker threadからのGUI更新のInvokeがうまくいかない

最近悩まされたBugのメモ。

最近メインで使うのがLinuxなので、Cross-platformなGUI ToolkitとしてGtk#を使っている。MonoDevelopにGUIデザイナが統合されていて、Linuxでコード書くときには非常に便利。
ところが、最近悩まされていたメモリリークの原因がどうやらGUI周りだったようで、僕の勉強不足なのか、ToolkitのBugなのかはよくわからん。内容はよくあるWorker threadからの定期的なGUI更新。Windows FormsでいうところのBeginInvokeする場面。プログレスバー表示とかの時のアレね。
Gtk#の場合にどうやってやるかというと、Mono Projectのサイト内に書いてある通りにGtk.Application.Invokeしたり、Gtk.ThreadNotify使ったりすればよい。はずだった。。

気づいた過程としては、なぜかGC.Collect()してるのにメモリ使用量がどんどん増えていて、CLR Profilerで見てみるとManagedメモリは正常。パフォーマンスモニタでUnmanagedなメモリがリークしてる。Unmanagedな処理は極力避けてるぞ?と思って思い当たったのがGtk#でした。

しかしこれからGUI Toolkitどうしよう。Cross-platformでC#から呼べるのってQtとかが思いつくけど、QtのC#ラッパーがUbuntu11.10のリポジトリには見つからないぞ?

今回、CLR Profilerとかパフォーマンスモニタとかで、メモリの中身を調査する方法がかなり理解できたので、まあ勉強にはなった。

.NET FrameworkでLOH断片化によるメモリリークもどき

ちょっと前に、画像とかをManagedな配列で持つと、処理は遅いけどGarbage Collectionしてくれるし良いかも的な事を書いたけど、どうやら遅いだけでは無いらしい。

Garbage Collectionがメモリの配置をゴニョゴニョ弄ってくれるおかげでメモリリークをある程度気にしなくて良いかと思いきや、やはり巨大なデータをゴニョゴニョするのは時間がかかるらしく、C#(というかCLR)では85kbよりでかい配列などのメモリはLarge Object Heap (LOH)に入れて、再配置はしないようになってるそうです。
http://msdn.microsoft.com/ja-jp/magazine/cc534993.aspx

てことで、画像とかGC任せで管理するのは危険とおもはれる。
アプリを開いてから閉じるまでの枚数が限られてたりする場合はいいけど、そうじゃない場合は断片化してメモリ食っちゃう可能性がありそう。
まあ考えてみれば当然ですよね。世の中の画処理ライブラリもそうなってるし。。
でも僕のような素人のために画処理の本の最初に書いといて欲しい。

結局Marshal使ってunmanageで頑張るしか無いらしい。
msec単位で時間かかるとしても、GCにLOHメモリの再配置を指示できればいいのに。。。

C#でポインタとbyte配列の高速なやり取り

C#で、ポインタとバイト配列を相互にやり取りしたい事が多い。
例えば、画像の画素値を高速に得るためにBitmapData.Scan0を使いたいけど、byte[]に入れて色々処理したい時とか。
Marshal.Copyでいいじゃんと言われそうだけど、たとえばRGB画像のR画像だけ欲しい時、いちいち全部Marshal.Copyしてから扱うのは無駄に思える。
(実際は中の人がDMAを使ってるのか、すごく速い)

やり方は色々思いつくけど、どれが高効率かわからないので、実験してみた。
以下Marshalクラスを眺めたりして思いついた方法。IntPtrが指すRGB画像を色ごとにbyte[]にぶち込むのが目標とする。

  • managed
  • Marshal.Readbyte(…)を使う
  • Readbyte(ptr, ofs)でofsをインクリメント
  • アドレスをインクリメントして、Readbyte(new IntPtr(addr))する
  • Marshal.Copyで普通にやる
    • 全画像Copyして、配列で頑張る
    • 全画像Copyして、1色ずつ画像を完成させていく(Read画像とWrite画像が1つずつにできるので、キャッシュを期待)
    • Line毎にCopyしてみる
    • 画像を幾つかのパートに分けて、マルチスレッド化してLine毎にCopyしてみる
  • unmanaged
    • ptr.ToPointer()からfixed(byte* R_Image)にポインタインクリメントしながらコピー
    • ptr.ToPointer()からMarshal.AllockでコピーしてからMarshal.Copy
    • unmanagedmemorystreamからReadByteする

    結論から言うと、Marshal.ReadByteやUnmanagedMemoryStreamは遅かった。前述した通り、Marshal.Copyはとても速いので、これらの関数はこの用途には使えないね。ググってもほとんどMarshal.Copyしか出てこないのはこういう訳か。
    unsafeポインタ系はやっぱり速いけど、unsafeはあんまり使いたくないので、Line毎にMarshal.Copyして弄るっていう処理を複数スレッドでやることにする。

    以下、検証に使った関数たち
    続きを読む

    C#でGenericなJag配列の生成と多次元配列からの変換

    以前に、C#で配列の初期値つきの初期化について書きましたが、今回は更に多次元配列とかJag配列とかを簡単に作りたいっていう話。

    C#では多次元配列(a[2,3]みたいなやつ)も使えますが、僕はJag配列(a[2][3]みたいなやつ)の方が圧倒的に使用頻度が高いです。配列の配列っていう概念の方がわかりやすいし。
    できることとしては何が違うか知りませんが、引数の都合などでたまに多次元配列をJag配列化したいことがあります。
    最初は任意の次元の配列も再帰とかで書ける気がしたんだけど、実は書けないっぽい。

    ついでに、以前の記事のように初期値つきJag配列初期化についても考えたけど、こちらも再帰的に書けないきがしている。
    多次元の配列の初期化は、次元が増えるほど面倒なので、とりあえず妥協して、途中の次元まではArray型の配列型、最後の配列だけGenericに生成するようにして、初期化付きJag配列生成メソッドを書いてみた。

    public class k2kArray
    {
        public static T[][] ConvertToJag<T>(T[,] array)
        {
            int i, j;
            var ret = new T[array.GetLength(0)][];
            int l = array.GetLength(1);
     
            for (i = 0; i < ret.Length; ++i)
            {
                ret[i] = new T[l];
                for (j = 0; j < l; ++j) ret[i][j] = array[i, j];
            }
     
            return ret;
        }
     
        public static TElem[] Init<TElem>(TElem[] arr, TElem InitialValue) where TElem : struct { return Init<TElem>(arr, () => InitialValue); }
        public static TElem[] Init<TElem>(TElem[] arr, Func<TElem> InitElem) { return Init<TElem>(arr, (i) => InitElem()); }
        public static TElem[] Init<TElem>(TElem[] arr, Func<int, TElem> InitElem)
        {
            int i;
            for (i = 0; i < arr.Length; ++i) arr[i] = InitElem(i);
            return arr;
        }
     
        public static TElem[] CreateInit<TElem>(int Length, TElem InitialValue) where TElem : struct { return CreateInit<TElem>(Length, () => InitialValue); }
        public static TElem[] CreateInit<TElem>(int Length, Func<TElem> InitElem) { return CreateInit<TElem>(Length, (i) => InitElem()); }
        public static TElem[] CreateInit<TElem>(int Length, Func<int, TElem> InitElem) { return Init<TElem>(new TElem[Length], InitElem); }
     
        public static Array CreateJagArray<TElem>(Func<TElem> InitElem, params int[] Length)
        {
            return CreateJagArrayDelegate<TElem>(Length, Length.Length, InitElem)();
        }
     
        public static Func<Array> CreateJagArrayDelegate<TElem>(int[] Length, int Dimension, Func<TElem> InitElem)
        {
            if (Dimension == 0)
            {
                return () => CreateInit<TElem>(Length[Dimension], InitElem);
            }
            else
            {
                return () =>
                          CreateInit<Array>(Length[Dimension-1],
                                CreateJagArrayDelegate<TElem>(Length, Dimension - 1, InitElem)
                          );
            }
        }
     
    }

    なんか微妙。string[][]の初期化とかしたいんだけど、Array[]になっちゃう。。
    その後キャストするにしてもうまい方法が思いつかない。
    なんかいい方法ないかな。っていういつもの結論。

    C#で配列の初期値つきの初期化

    C#というか.NET Frameworkって配列に初期値を入れたい時に短い書き方がないよね。
    いちいちforループ書くのめんどくさいし。
    LINQあたりでうまく書けるのかもしれないけど、トリッキーなのもやだし、自分用ライブラリに初期化用メソッドを入れてみた。

    public class k2kArray
    {
    	public static TElem[] Init<TElem>(TElem[] arr, TElem InitialValue) where TElem : struct
    	{
    		return Init<TElem>(arr, () => InitialValue);
    	}
    	public static TElem[] Init<TElem>(TElem[] arr, Func<TElem> InitElem)
    	{
    		return Init<TElem>(arr, (i) => InitElem());
    	}
    	public static TElem[] Init<TElem>(TElem[] arr, Func<int, TElem> InitElem)
    	{
    		int i;
    		for (i = 0; i < arr.Length; ++i) arr[i] = InitElem(i);
    		return arr;
    	}
     
    	public static TElem[] CreateInit<TElem>(int Length, TElem InitialValue) where TElem : struct
    	{
    		return CreateInit<TElem>(Length, () => InitialValue);
    	}
    	public static TElem[] CreateInit<TElem>(int Length, Func<TElem> InitElem)
    	{
    		return CreateInit<TElem>(Length, (i) => InitElem());	
    	}
    	public static TElem[] CreateInit<TElem>(int Length, Func<int, TElem> InitElem)
    	{
    		return Init<TElem>(new TElem[Length], InitElem);
    	}
    }

    ついでに初期化された配列をnewする関数も。
    引数にデリゲートがある関数は参照型用。引数付きコンストラクタが呼びたい気分の時のために作った。
    ついでにIndexに依存する初期値を生成するラムダ式を引数に取れたら便利かと思ったのでそれも追加。
    使うときはこんな感じ。

    byte[] arr1 = k2kArray.CreateInit<byte>( 5, 0xFF );
    string[] arr2 = k2kArray.CreateInit<string>( 5, () => "初期値だよ" );
    string[] arr3 = k2kArray.CreateInit<string>( 5, (i) => string.Format("{0}番目", i) );

    見かけ的には、デリゲートよりnew()制約つけてnewするようにしたほうが良かったかも。余計なこともできるようにするか、シンプルにするか。
    ていうかデリゲートだと遅いかな。

    結局初期化式はあんまりスマートじゃなくなってしまった。
    なんかいい方法ないかな。

    ガーベッジコレクションまかせの画像処理

    画像処理書く時って、画像入れるメモリを確保したり開放したりするけど、ガーベジコレクションに慣れるとめんどくさい。

    OpenCVとか、色んなライブラリも、画像のメモリに関しては気にして書かなきゃいけなくて、C#でいうところのusingで囲得ないようなアルゴリズムも多くて、そのへんのデザインパターンがよくわからない僕としては難しいと思っちゃっています。

    速度とかわりとどうでもよかったり、テスト用のプログラムをサクっと書きたい時には、適当にManagedなbyte配列に画素値をぶっこんでごにょごにょして、適度にGC.Collect()しながら使うのが楽です。

    そのためのライブラリを作ってみたんだけど、メモリを放置できる快感を覚えてしまって、邪道と分かっていながら抜け出せない。。
    試しにつくって以来、結局いろんなフィルタとかを加えて、大きくなりつつある。
    そのうち公開します。

    1 2

    ホーム > プログラミング > .NET Framework

    カレンダー
    « 2018 年 11月 »
    S M T W T F S
            1 2 3
    4 5 6 7 8 9 10
    11 12 13 14 15 16 17
    18 19 20 21 22 23 24
    25 26 27 28 29 30  
    最近の反応
    メタ情報

    ページの上部に戻る