|
|
![]() 【Kinect Sensorを利用して拡張現実センサを作る試み】 |
||
![]() |
【リンクフリー】 私設研究所ネオテックラボ Neo-Tech-Lab.co.uk 【記載者】 【私設研究所Neo-Tech-Lab】 上田 智章 |
作成日 2011/01/08 更新 2011/04/03 最終更新 2011/05/06 |
ここにチェックボックス型外部コンテンツ・メニューが入ります。 | ||
|
|
|
【はじめに】本来見ることができない物理量を3次元的に可視化する拡張現実センサを実現する手段のひとつとして、Microsoft社のXBOX 360用モーションキャプチャーKinect Sensorに着目し、あれこれ試作を繰り返しています。このページでは、高周波磁界分布や超音波を3次元的に可視化する拡張現実センサを例に挙げてKinect Sensorの使い方を説明します。 【ご注意事項】2014年4月下旬に、OpenNIはサイトが閉鎖されました。恐らくは、イスラエルのPrimeSense社が米国アップル社に買収されたことに伴う経営方針の転換によるものと思われます。以降はKinectのソフトウェア開発はMicrosoft社のKinect for Windows SDKをご使用になられることを強く推奨いたします。 |
![]() 【図1】試作中の高周波磁界分布可視化拡張現実センサ 【写真2】試作中のワイヤレス高周波磁界・超音波センサ ![]() 【図】JavaScript音声合成エンジン『μ-iVoice』の表示例 |
【目次】 ⇒ 【English Version】【Kinect for Windows SDK Version1導入編】●2012年2月1日、遂にMicrosoft社が正式にKinect for Windows SDKのVersion1の配布を開始しました。どうやら、PC接続専用センサのKinect for Windows向けらしいのですが、筆者が確認したところ、XBOX360用センサをPCで動作させることができました。 Colorイメージ、Depth情報、Skelton(骨格)情報の取得だけでなく、音源方向の検出、音声の.wavファイル形式での録音、音声認識エンジンSpeechの実装、センサの上下角の遠隔操作などもXBOX360用センサで問題なく行うことができました。 英語ですが、各機能の使い方の解説ドキュメントが付いています。目的別に非常に丁寧な文章で解説されています。 ●骨格抽出、残像表示までの簡単なサンプルプログラムを目的別にプロジェクトファイルごと公開しています。 KINECT導入編を参考にしてください。/2012/02/16/ 【OpenNI編】●【Windows7でKinectを使えるようにするには?】●【手順1:ダウンロード】 ●【手順2:デバイスドライバのインストール】 ●【手順3:OpenNIのインストール】 ●【手順4:NITEのインストール】 ●【手順5:Sensorのインストール】 ●【手順6:環境変数の修正】 ●【手順7:OpenNI用xmlファイルの置き換え】 ●【手順8:NITE用xmlファイルの置き換え】 ●【動作確認】 ●【Kinect Sensorの使い方】 ● 【以下の項目書きかけです。】 ●【Kinect Sensorについて】 ●イスラエルのPrime Sense社のミドルウェアNITEとオープンソースOpenNI 【ARを支える他の技術】■『Kinectで拡張現実センサを構成する試み』【C#】■『Google翻訳の音声合成(Text-To-Speech: TTS)API』【JavaScript】 ■『Webで使える音声合成API μ-iVoice』【JavaScript】 変形DDS(Direct Digital Synthesizer)アルゴリズムを用いた音声合成/シンセサイザAPI(Version0.07)です。Google TTSと違ってブラウザ上で音声合成を行いますので文字数の制約はありません。簡易なスクリプトで音声に抑揚(音階、幅、強弱)をつけることができます。iframeでブログなどに埋め込めます。現状ひらがなだけですが、Version0.08で漢字への対応、自動抑揚付与などの機能追加、音質向上等の改良を予定。加えてローカルからオリジナル音声データの組み込みを可能にします。 現状、声域の狭い初音ミク風味(あまり似ていない)の音声データのみですが、今後、オリジナル公開音声データベースを構築する予定です。 |
【WebGLのページ】JavaScriptでリアルタイム3次元グラフィックスが楽しめる時代の到来までもうすぐです。現在、Google Chrome12、Mozilla FireFox5、及び開発版のSafari、Operaで使うことができます。 WebGLは、JavaScriptエンジンv8とDirectXを利用して高速化したOpenGLを融合して構成されたものです。 2011年5月に指摘されたセキュリティー問題はありますが、処理速度の高速性から利便性も高いので、将来的には普及するのではないでしょうか? ●WebGLとは? ●事前準備:利用できるウェブブラウザは? ●事前準備:FireFox5でWebGLが動作しない場合には? ●WebGLのサンプル ●WebGLのデモ ●クロスドメインテクスチャーを将来も利用できる方法 |
【OpenNI導入編】 |
|
かなり古い内容です。 現状では、KINECTを使うベストな方法は、Microsoft社のKINECT for Windows SDK Version1を使う方法です。 パソコン依存性の強いOpenNIはお薦めいたしません。 が、インストール方法を探しておられる方もまだおられるようなので、メモとして残しておきます。 記載日:2012年03年08日 【備考】 イスラエルのPrimeSense社が米アップル社に買収され、経営方針が変更されたようで、OpenNIサイトは2014年4月に閉鎖されています。もう過去の遺物の事は忘れましょう。 |
|
■Microsoft社XBOX用モーションキャプチャーセンサKinect |
【Windows7でKinectを使えるようにするには?】4月中旬に私設研究所Neo-Tech-Lab.comのノートパソコンを殆どWindows7に更新したのだが、困った事が発生してしまった。OpenNIを導入しようとするのだが失敗してしまい、唯一3月までに導入していたPCだけが動作する状態なのだ。それもそのはず、4月に入ってOpenNIがバージョンアップしているではないか。そこで試行錯誤の末、現状十分とは言えないがKinectが動作する導入条件を何とか見つけることができたので記載する。 【手順1:ダウンロード】以下の3つのダウンロードを行う。 ■2011年6月8日修正記載。Windows7(OS64bit版)で動作を確認。1-1.Kinect用デバイスドライバー(PrimeSensor Modules for OpenNI)をここ(github Avin2)からダウンロードする。少し見辛いが、青い字で8f199caと書かれたリンクをクリックする。 他のリンクをクリックすると違うファイルになるので注意の事。 avin2 pushed to master at avin2/SensorKinect April 16, 2011 [Tree: 8f199ca] ダウンローダーの負荷の関係でマウスカーソルがぐるぐる回っている間はダウンロードできない。30秒以上待たされる事も珍しくないので、気長に待とう。ダウンローダーのダウンロード準備が整うとDownloadsボタンの上にマウスカーソルを持っていくと青いボタンに変わる。 青いDownloadsボタンをクリックした後で表示されるダイアログボックスの『Download.zip』ボタンでzip圧縮ファイル『avin2-SensorKinect-8f199ca.zip』をダウンロード。(解凍すると17.6MB) ![]() ![]() ![]() 1-2.OpneNI.orgのダウンロードページから『OpenNI Binaries』の最新暫定(Latest Unstable)版の『OpenNI Unstable Build for Windows x86 (32-bit) v1.1.0.41 Development Edition』をダウンロードする。 たとえ、あなたのPCがWindows7の64bit版OSであったとしても32bit版をダウンロードする必要がある。現状、上記のavin2からダウンロードしたファイルに同梱のSensorモジュールがエラーを発生するからだ。 インストーラ『OpenNI-Win32-1.1.0.41-Dev.msi』 アップロード日付:2011年4月18日 ファイルサイズ:14 MB ![]() 1-3.OpneNI.orgのダウンロードページから『OpenNI Compliant Middleware Binaries』の最新暫定(Latest Unstable)版の『PrimeSense NITE Unstable Build for Windows x86 (32-bit) v1.3.1.5 Development Edition』をダウンロードする。 たとえ、あなたのPCがWindows7の64bit版OSであったとしても32bit版をダウンロードする必要がある。現状、上記のavin2からダウンロードしたファイルに同梱のSensorモジュールがエラーを発生するからだ。 インストーラ『NITE-Win32-1.3.1.5-Dev.msi』 アップロード日付:2011年4月18日 ファイルサイズ:37 MB ![]() 1-4. ステップ1-1でダウンロードしたzipファイルは解凍する。(中身のフォルダavin2-SensorKinect-8f199caごとデスクトップにでもコピーする) 【手順2:デバイスドライバのインストール】2-1.Kinect Sensorを電源ONの状態でUSBケーブルをPCに接続。直後にWindows7がデバイスインストールを試みるが確実に失敗するはず。(これで動作は正常。)2-2.『コンピュータ』⇒『システムのプロパティ』⇒『デバイスマネージャー』で状態を確認すると、ほかのデバイス欄にXbox NUI Motorと表示されているはずだ。 2-3.Windows7の64bitOSの場合は、ダウンロードした『avin2-SensorKinect-8f199ca.zip』を解凍すると、『avin2-SensorKinect-8f199ca』-『Platform』-『Win32』-『Driver』フォルダ内に『dpinst-amd64.exe』があるので実行する。これはWindows7にKinect Sensorデバイスの自動認識をさせるための設定プログラムだ。 ちなみにWindows7の32bitOSの場合、あるいはVistaの場合には、『dpinst-x86.exe』を使用すれば良い。 2-4.実行すると、Kinect Audio, Kinect Camera, Kinect Motorが認識される。 (以前のバージョンではAudioは対応されていなかった。) ![]() ![]() ![]() ![]() 【手順3:OpenNIのインストール】3-1.インストーラ『OpenNI-Win32-1.1.0.41-Dev.msi』を使い、デフォルト設定のままでOpenNIをインストールする。残念なことに64bit版では動作させることができなかった。(これは、手順5でインストールするPrimeSensorのインストールプログラムがインストール済みOpenNIを検証する際に、32bit版を仮定しているためにエラーメッセージが出てしまうためだ。ver.1.1.0.39以上のバージョンのOpenNIをインストールするようにエラーが出力される。自分で修正・コンパイルするのは面倒なのでトライしていない。)『C:\Program Files (x86)』に『OpenNI』フォルダが作成される。 ![]() ![]() 【手順4:NITEのインストール】4-1.インストーラ『NITE-Win32-1.3.1.5-Dev.msi』を使い、デフォルト設定のままでPrimeSense社のNITEをインストールする。これも32bit版である。途中、PrimeSenseのライセンス・キーの入力が求められるが、キーは『0KOIk2JeIBYClPWVnMoRKn5cdY4=』である。 (ダウンロードのときに直前のページに記述されている。) 『C:\Program Files (x86)』に『PrimeSense』フォルダが作成され、その下に『NITE』フォルダが作成される。 ![]() ![]() ![]() ![]() 【手順5:Sensorのインストール】5-1.OpenNIを使用するためのPrimeSensorモジュールをインストールする。『avin2-SensorKinect-8f199ca』-『Bin』フォルダに格納されたインストーラ『SensorKinect-Win-OpenSource32-5.0.1.msi』を使う。 『C:\Program Files (x86)\PrimeSense』フォルダの下に『Sensor』フォルダが作成される。 ![]() 【手順6:環境変数の修正】6-1.インストーラのせいだろうと思うが、環境変数OPEN_NI_BINの示すフォルダ名がBinでなければならないところがbinになる不具合があるので修正する。『システムの詳細設定』から『システムのプロパティ』を表示し、『環境変数(N)』ボタンをクリック。表示されるシステム環境変数(S)からOPEN_NI_BINを探し、選択して『編集(I)』ボタンをクリック。修正を行って、OKボタンで入力確定する。 ![]() ![]() ![]() ![]() 【手順7:OpenNI用xmlファイルの置き換え】7-1.デスクトップにメモ帳で下に示すようなテキストファイルを作り、SamplesConfig.xmlと名前を付けて一旦保存し、『C:\Program Files (x86)\OpenNI\Data』フォルダにコピー(上書き)する。既存ファイルを修正せずに上書きするのは、私の所有するWindows7機はどれもセキュリティー設定せいで『C:\Program Files (x86)\』フォルダ内のメモ帳による変更が制限されているためだ。ちなみに私のPCはノートパソコンだったので画面解像度の関係で『xRes="640" yRes="480" FPS="30"』と書いたが、デスクトップなら『xRes="1280" yRes="1024" FPS="15"』でも動作できるようだ。英文掲示板でそのような記述を見かけた。(動作検証は行っていません。未確認です。) 『SamplesConfig.xml』<OpenNI> <Licenses> <License vendor="PrimeSense" key="0KOIk2JeIBYClPWVnMoRKn5cdY4="/> </Licenses> <Log writeToConsole="false" writeToFile="false"> <!-- 0 - Verbose, 1 - Info, 2 - Warning, 3 - Error (default) --> <LogLevel value="3"/> <Masks> <Mask name="ALL" on="true"/> </Masks> <Dumps> </Dumps> </Log> <ProductionNodes> <!-- Normal Image --> <Node type="Image" name="Image1" stopOnError="false"> <Configuration> <Mirror on="true"/> </Configuration> </Node> <!-- HighRes Image --> <!-- <Node type="Image" name="Image1" stopOnError="false"> <Configuration> <MapOutputMode xRes="640" yRes="480" FPS="30"/> <Mirror on="true"/> </Configuration> </Node> --> <!-- Normal IR --> <!-- <Node type="IR" name="IR1"> <Configuration> <MapOutputMode xRes="640" yRes="480" FPS="30"/> <Mirror on="true"/> </Configuration> </Node> --> <!-- HighRes IR --> <!-- <Node type="IR" name="IR1"> <Configuration> <MapOutputMode xRes="640" yRes="480" FPS="30"/> <Mirror on="true"/> </Configuration> </Node> --> <Node type="Depth" name="Depth1"> <Configuration> <Mirror on="true"/> </Configuration> </Node> <!-- <Node type="Audio" name="Audio1"> </Node> --> </ProductionNodes> </OpenNI> 【手順8:NITE用xmlファイルの置き換え】8-1.上記と同様に、デスクトップにメモ帳で下に示すような3つのテキストファイルを作り、Sample-Scene.xml、Sample-Tracking.xml、Sample-User.xmlと名前を付けて一旦保存し、『C:\Program Files (x86)\PrimeSense\NITE\Data』フォルダにコピー(上書き)する。『Sample-Scene.xml』<OpenNI> <Licenses> <License vendor="PrimeSense" key="0KOIk2JeIBYClPWVnMoRKn5cdY4="/> </Licenses> <Log writeToConsole="true" writeToFile="false"> <!-- 0 - Verbose, 1 - Info, 2 - Warning, 3 - Error (default) --> <LogLevel value="3"/> <Masks> <Mask name="ALL" on="false"/> </Masks> <Dumps> </Dumps> </Log> <ProductionNodes> <Node type="Depth"> <Configuration> <Mirror on="true"/> </Configuration> </Node> <Node type="Scene" /> </ProductionNodes> </OpenNI> 『Sample-Tracking.xml』<OpenNI> <Licenses> <License vendor="PrimeSense" key="0KOIk2JeIBYClPWVnMoRKn5cdY4="/> </Licenses> <Log writeToConsole="true" writeToFile="false"> <!-- 0 - Verbose, 1 - Info, 2 - Warning, 3 - Error (default) --> <LogLevel value="3"/> <Masks> <Mask name="ALL" on="false"/> </Masks> <Dumps> </Dumps> </Log> <ProductionNodes> <Node type="Depth"> <Configuration> <Mirror on="true"/> </Configuration> </Node> <Node type="Gesture" /> <Node type="Hands" /> </ProductionNodes> </OpenNI> 『Sample-User.xml』<OpenNI> <Licenses> <License vendor="PrimeSense" key="0KOIk2JeIBYClPWVnMoRKn5cdY4="/> </Licenses> <Log writeToConsole="true" writeToFile="false"> <!-- 0 - Verbose, 1 - Info, 2 - Warning, 3 - Error (default) --> <LogLevel value="3"/> <Masks> <Mask name="ALL" on="false"/> </Masks> <Dumps> </Dumps> </Log> <ProductionNodes> <Node type="Depth"> <Configuration> <Mirror on="true"/> </Configuration> </Node> <Node type="User" /> </ProductionNodes> </OpenNI> 【動作確認】以上でKinectのデモプログラムが動作するはずだ。『c:\Program Files (x86)\OpenNI\Samples\Bin\Release\』フォルダにあるNIUserTracker.exeの実行例を示す。 ユーザー認識の後で、両腕をあげてガッツポーズを取ると骨格が表示される。 ![]() 【OpenNI.Net.dllが激変しているではないか?!】ここまで、やってOpenNIやNITEのデモプログラムは全部動作するようになったはずだが、2011年3月までのOpenNI.net.dllの仕様を知っている人にはショックが待ち構えている。OpenNI.net.dll⇒OpenNI.Net.dllと1文字大文字に変わっている。 あれっと思って、以前のプログラムをコンパイルしようとすると、名前空間『xn』が見当たらないと言う。 『じゃあ、参照取りなおすか。』と思ってしてみると、名前空間はOpenNI.Netに変更されている。 コンパイルしてみると一杯エラーが出てくる?? これは!!......\(-o-)/ 仕様が大変更されているではないか......orz......<(-_-;)> やられた.....定数とか参照とれてないし。また一から調べるとするか......(T_T) まぁ、確かにOpenNIサイトに骨格データが分かれているとか兆候はあったなぁ..... なので、以前に作ったサンプルプログラムを修正しました。【2011年5月15日午後14:14】 |
【注意】 以下のコードは、2011年4月版のOpenNI.Net.dllを使った場合のコードです2011年1月のC# Wapperから見て、かなり構造的な変更が何か所もあったようです。どうやらプロパティーでもメソッドでもなく、Public変数になっている部分とか、型の変更とか... 一から調べなおすのは面倒だったので、コンパイルしてエラー箇所を機嫌が良くなるように修正するという後ろ向きな修正方法で、以前に作成したサンプルプログラムを動くように修正しました。 取り敢えず、handsGeneratorはまだ復旧できていませんが、RGBカメラとかDepthバッファとかは以前のように動かすことができました。handsGenerator関連は外してもらっても問題ありません。 下にサンプルプログラムのソースを掲載しておきます。 【サンプルプログラムのプロジェクトファイル一式(zip形式) 】 |
【Kinect Sensorの使い方】C# WapperのOpenNI.net.dllを使えば、Visual C#でKinect Sensorを使うことができます。以下に最もポピュラーなRGBカメラの読出/表示、デプスバッファの読出/表示、ユーザー切り出し、骨格情報の読出しのサンプルプログラムを示します。スクリーンショットのデプスバッファのカラー表示画面で、最も手前にあるはずの左手の横に白い部分がありますが、これはデプスバッファの数値として0が返されてくる部分なのです。対象がカメラに近いほどこの白い領域がずれて目立ちます。Prime Sense社の3次元座標検出方法に強く関係していると考えられますが、詳細は不明です。 3次元座標の取得処理時には赤色レーザーが発光しますので、もしかするとレーザー光線をスキャンして2つのカメラを使った視差を求める方法かもしれません。この場合、片側のカメラに光点がなければ3次元座標(深さz値)を求めることができないので、値が0になるのかもしれません。 ![]() 【図xx】RGBカメラ、デプスバッファのカラー表示、ユーザー切り出し、骨格情報読出し表示の例 ![]() 【図xx】RGBカメラ、デプスバッファのモノクロ表示、ユーザー切り出し、骨格情報読出し表示の例 ![]() 【図xx】近くほど白い領域がずれて目立つ。Prime Sense社の3次元座標検出方法に関係していると考えられる。 |
【Program.cs】Kinect SensorをC#で利用する場合のサンプル・プログラムusing System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; using System.Drawing; namespace KinectSimpleViewer.net { static class Program { /// |
【Form1.cs】Kinect SensorをC#で利用する場合のサンプル・プログラムusing System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Text; using System.Windows.Forms; using OpenNI; using System.Threading; using System.Drawing.Imaging; using System.Drawing.Drawing2D; namespace KinectSimpleViewer { public partial class Form1 : Form { private readonly string SAMPLE_XML_FILE = @"../../Data/SamplesConfig.xml"; private Context context; private DepthGenerator depth; private ImageGenerator image; private UserGenerator userGenerator; private HandsGenerator handsGenerator; private SkeletonCapability skeletonCapbility; private PoseDetectionCapability poseDetectionCapability; private string calibPose; private Thread readerThread; private bool shouldRun; private Bitmap bitmap; private Bitmap bitmap2; private int[] histogram; private Point3D handsPos; private Dictionary<int, Dictionary<SkeletonJoint, SkeletonJointPosition>> joints; private static Int32 nJoints = 16; //【実際には15個しか戻されていない】 private SkeletonJointPosition[] SkeletonJointData = new SkeletonJointPosition[nJoints]; private bool shouldDrawPixels = true; //【画像を更新する】 private bool shouldDrawBackground = true; //【背景を描画する】 private bool shouldPrintID = true; //【UserIDを表示する】 private bool shouldPrintState = true; //【検出状態を表示する】 private bool shouldDrawSkeleton = true; //【骨格を表示する】 private Color[] colors = { Color.Red, Color.Blue, Color.ForestGreen, Color.Yellow, Color.Orange, Color.Purple, Color.White }; private Color[] anticolors = { Color.Green, Color.Orange, Color.Red, Color.Purple, Color.Blue, Color.Yellow, Color.Black }; private int ncolors = 6; //【カラーインデックスデータ】A/Dコンバータの計測値に対応する球体表示色を与える配列 public static Int16 nColors = 256; public static int[] ColorLookupTable = new int[] { 0x00FFFFFF, 0x00FBFBFB, 0x00F7F7F7, 0x00F3F3F3, 0x00EFEFEF, 0x00EBEBEB, 0x00E7E7E7, 0x00E4E4E4, 0x00E0E0E0, 0x00DCDCDC, 0x00D8D8D8, 0x00D4D4D4, 0x00D0D0D0, 0x00CCCCCC, 0x00C8C8C8, 0x00C4C4C4, 0x00C0C0C0, 0x00BCBCBC, 0x00B8B8B8, 0x00B4B4B4, 0x00B1B1B1, 0x00ADADAD, 0x00A9A9A9, 0x00A5A5A5, 0x00A1A1A1, 0x009D9D9D, 0x00999999, 0x00959595, 0x00919191, 0x008D8D8D, 0x00898989, 0x00858585, 0x00828282, 0x007E7E7E, 0x007A7A7A, 0x00767676, 0x00727272, 0x006E6E6E, 0x006A6A6A, 0x00666666, 0x00626262, 0x005E5E5E, 0x005A5A5A, 0x00565656, 0x00525252, 0x004F4F4F, 0x004B4B4B, 0x00474747, 0x00434343, 0x003F3F3F, 0x003B3B3B, 0x000000FF, 0x000005FF, 0x00000AFF, 0x00000FFF, 0x000014FF, 0x000019FF, 0x00001EFF, 0x000023FF, 0x000028FF, 0x00002DFF, 0x000032FF, 0x000037FF, 0x00003CFF, 0x000041FF, 0x000046FF, 0x00004BFF, 0x000050FF, 0x000055FF, 0x00005AFF, 0x00005FFF, 0x000064FF, 0x000069FF, 0x00006EFF, 0x000073FF, 0x000078FF, 0x00007DFF, 0x000082FF, 0x000087FF, 0x00008CFF, 0x000091FF, 0x000096FF, 0x00009BFF, 0x0000A0FF, 0x0000A5FF, 0x0000AAFF, 0x0000AFFF, 0x0000B4FF, 0x0000B9FF, 0x0000BEFF, 0x0000C3FF, 0x0000C8FF, 0x0000CDFF, 0x0000D2FF, 0x0000D7FF, 0x0000DCFF, 0x0000E1FF, 0x0000E6FF, 0x0000EBFF, 0x0000F0FF, 0x0000F5FF, 0x0000FAFF, 0x0000FFFF, 0x0000FFFA, 0x0000FFF5, 0x0000FFF0, 0x0000FFEB, 0x0000FFE6, 0x0000FFE1, 0x0000FFDC, 0x0000FFD7, 0x0000FFD2, 0x0000FFCD, 0x0000FFC8, 0x0000FFC3, 0x0000FFBE, 0x0000FFB9, 0x0000FFB4, 0x0000FFAF, 0x0000FFAA, 0x0000FFA5, 0x0000FFA0, 0x0000FF9B, 0x0000FF96, 0x0000FF91, 0x0000FF8C, 0x0000FF87, 0x0000FF82, 0x0000FF7D, 0x0000FF78, 0x0000FF73, 0x0000FF6E, 0x0000FF69, 0x0000FF64, 0x0000FF5F, 0x0000FF5A, 0x0000FF55, 0x0000FF50, 0x0000FF4B, 0x0000FF46, 0x0000FF41, 0x0000FF3C, 0x0000FF37, 0x0000FF32, 0x0000FF2D, 0x0000FF28, 0x0000FF23, 0x0000FF1E, 0x0000FF19, 0x0000FF14, 0x0000FF0F, 0x0000FF0A, 0x0000FF05, 0x0000FF00, 0x0005FF00, 0x000AFF00, 0x000FFF00, 0x0014FF00, 0x0019FF00, 0x001EFF00, 0x0023FF00, 0x0028FF00, 0x002DFF00, 0x0032FF00, 0x0037FF00, 0x003CFF00, 0x0041FF00, 0x0046FF00, 0x004BFF00, 0x0050FF00, 0x0055FF00, 0x005AFF00, 0x005FFF00, 0x0064FF00, 0x0069FF00, 0x006EFF00, 0x0073FF00, 0x0078FF00, 0x007DFF00, 0x0082FF00, 0x0087FF00, 0x008CFF00, 0x0091FF00, 0x0096FF00, 0x009BFF00, 0x00A0FF00, 0x00A5FF00, 0x00AAFF00, 0x00AFFF00, 0x00B4FF00, 0x00B9FF00, 0x00BEFF00, 0x00C3FF00, 0x00C8FF00, 0x00CDFF00, 0x00D2FF00, 0x00D7FF00, 0x00DCFF00, 0x00E1FF00, 0x00E6FF00, 0x00EBFF00, 0x00F0FF00, 0x00F5FF00, 0x00FAFF00, 0x00FFFF00, 0x00FFFA00, 0x00FFF500, 0x00FFF000, 0x00FFEB00, 0x00FFE600, 0x00FFE100, 0x00FFDC00, 0x00FFD700, 0x00FFD200, 0x00FFCD00, 0x00FFC800, 0x00FFC300, 0x00FFBE00, 0x00FFB900, 0x00FFB400, 0x00FFAF00, 0x00FFAA00, 0x00FFA500, 0x00FFA000, 0x00FF9B00, 0x00FF9600, 0x00FF9100, 0x00FF8C00, 0x00FF8700, 0x00FF8200, 0x00FF7D00, 0x00FF7800, 0x00FF7300, 0x00FF6E00, 0x00FF6900, 0x00FF6400, 0x00FF5F00, 0x00FF5A00, 0x00FF5500, 0x00FF5000, 0x00FF4B00, 0x00FF4600, 0x00FF4100, 0x00FF3C00, 0x00FF3700, 0x00FF3200, 0x00FF2D00, 0x00FF2800, 0x00FF2300, 0x00FF1E00, 0x00FF1900, 0x00FF1400, 0x00FF0F00, 0x00FF0A00, 0x00FF0500, 0x00FF0000 }; public Form1() { InitializeComponent(); this.context = new Context(SAMPLE_XML_FILE); this.depth = context.FindExistingNode(NodeType.Depth) as DepthGenerator; if (this.depth == null) { throw new Exception("Viewer must have a depth node!"); } this.image = context.FindExistingNode(NodeType.Image) as ImageGenerator; if (this.image == null) { throw new Exception("Viewer must have a image node!"); } this.userGenerator = new UserGenerator(this.context); this.userGenerator.NewUser += userGenerator_NewUser; this.userGenerator.LostUser += userGenerator_LostUser; this.userGenerator.StartGenerating(); this.skeletonCapbility = this.userGenerator.SkeletonCapability; this.calibPose = this.skeletonCapbility.CalibrationPose; this.skeletonCapbility.CalibrationEnd += skeletonCapbility_CalibrationEnd; this.skeletonCapbility.SetSkeletonProfile(SkeletonProfile.All); this.joints = new Dictionary<int, Dictionary<SkeletonJoint, SkeletonJointPosition>>(); this.handsGenerator = new HandsGenerator(this.context); this.handsGenerator.StartGenerating(); handsPos.X = 0; handsPos.Y = 0; handsPos.Z = 0; this.handsGenerator.SetSmoothing(1.3F); this.poseDetectionCapability = this.userGenerator.PoseDetectionCapability; this.poseDetectionCapability.PoseDetected += poseDetectionCapability_PoseDetected; this.histogram = new int[this.depth.DeviceMaxDepth]; //【MaxDepth:10000】 // MapOutputMode mapMode = this.depth.GetMapOutputMode(); MapOutputMode mapMode = this.image.MapOutputMode; this.bitmap = new Bitmap((int)mapMode.XRes, (int)mapMode.YRes/*, System.Drawing.Imaging.PixelFormat.Format24bppRgb*/); this.bitmap2 = new Bitmap((int)mapMode.XRes, (int)mapMode.YRes/*, System.Drawing.Imaging.PixelFormat.Format24bppRgb*/); this.context.GlobalMirror = false; //【実写・非鏡像表示】 this.depth.AlternativeViewpointCapability.SetViewpoint(image); //【骨格位置を実写画像に一致させる】 this.shouldRun = true; this.readerThread = new Thread(ReaderThread); this.readerThread.Start(); } void skeletonCapbility_CalibrationEnd(object sender, CalibrationEndEventArgs e) { if (e.Success) { this.skeletonCapbility.StartTracking(e.ID); this.joints.Add(e.ID, new Dictionary<SkeletonJoint, SkeletonJointPosition>()); } else { this.poseDetectionCapability.StartPoseDetection(calibPose, e.ID); } } void poseDetectionCapability_PoseDetected(object sender, PoseDetectedEventArgs e) { this.poseDetectionCapability.StopPoseDetection(e.ID); this.skeletonCapbility.RequestCalibration(e.ID, true); } void userGenerator_NewUser(object sender, NewUserEventArgs e) { this.poseDetectionCapability.StartPoseDetection(this.calibPose, e.ID); } void userGenerator_LostUser(object sender, UserLostEventArgs e) { this.joints.Remove(e.ID); } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); lock (this) { e.Graphics.DrawImage(this.bitmap, this.panel1.Location.X, this.panel1.Location.Y, this.panel1.Size.Width, this.panel1.Size.Height); e.Graphics.DrawImage(this.bitmap2, this.panel2.Location.X, this.panel2.Location.Y, this.panel2.Size.Width, this.panel2.Size.Height); } } protected override void OnPaintBackground(PaintEventArgs pevent) { //Don't allow the background to paint } protected override void OnClosing(CancelEventArgs e) { this.shouldRun = false; this.readerThread.Join(); base.OnClosing(e); } protected override void OnKeyPress(KeyPressEventArgs e) { switch (e.KeyChar) { case (char)27: Close(); // exit(1); break; case 'b': this.shouldDrawBackground = !this.shouldDrawBackground; break; case 'x': this.shouldDrawPixels = !this.shouldDrawPixels; break; case 's': this.shouldDrawSkeleton = !this.shouldDrawSkeleton; break; case 'i': this.shouldPrintID = !this.shouldPrintID; break; case 'l': this.shouldPrintState = !this.shouldPrintState; break; case '1': // g_nViewState = DISPLAY_MODE_OVERLAY; depth.AlternativeViewpointCapability.SetViewpoint(image); break; case '2': // g_nViewState = DISPLAY_MODE_DEPTH; depth.AlternativeViewpointCapability.ResetViewpoint(); break; case '3': // g_nViewState = DISPLAY_MODE_IMAGE; depth.AlternativeViewpointCapability.ResetViewpoint(); break; case 'm': context.GlobalMirror = !context.GlobalMirror; break; } base.OnKeyPress(e); } private unsafe void CalcHist(DepthMetaData depthMD) { // reset for (int i = 0; i < this.histogram.Length; ++i) { this.histogram[i] = 0; } ushort* pDepth = (ushort*)depthMD.DepthMapPtr.ToPointer(); int points = 0; for (int y = 0; y < depthMD.YRes; ++y) { for (int x = 0; x < depthMD.XRes; ++x, ++pDepth) { ushort depthVal = *pDepth; if (depthVal != 0) { this.histogram[depthVal]++; points++; } } } for (int i = 1; i < this.histogram.Length; i++) { this.histogram[i] += this.histogram[i - 1]; } if (points > 0) { for (int i = 1; i < this.histogram.Length; i++) { this.histogram[i] = (int)(256 * (1.0f - (this.histogram[i] / (float)points))); } } } private void GetJoint(int user, SkeletonJoint joint, int k) { SkeletonJointPosition pos = this.skeletonCapbility.GetSkeletonJointPosition(user, joint); if (pos.Position.Z == 0) { pos.Confidence = 0; } else { pos.Position = this.depth.ConvertRealWorldToProjective(pos.Position); } this.joints[user][joint] = pos; SkeletonJointData[k] = pos; //pos.fConfidenceも含めて保存される } private void GetJoints(int user) { GetJoint(user, SkeletonJoint.Head, 0); //【頭】 GetJoint(user, SkeletonJoint.Neck, 1); //【首】 GetJoint(user, SkeletonJoint.LeftShoulder, 2); //【左肩】 GetJoint(user, SkeletonJoint.LeftElbow, 3); //【左ひじ】 GetJoint(user, SkeletonJoint.LeftHand, 4); //【左手】 GetJoint(user, SkeletonJoint.RightShoulder, 5); //【右肩】 GetJoint(user, SkeletonJoint.RightElbow, 6); //【右ひじ】 GetJoint(user, SkeletonJoint.RightHand, 7); //【右手】 GetJoint(user, SkeletonJoint.Torso, 8); //【胴】 GetJoint(user, SkeletonJoint.LeftHip, 9); //【左尻】 GetJoint(user, SkeletonJoint.LeftKnee, 10); //【左膝】 GetJoint(user, SkeletonJoint.LeftFoot, 11); //【左足首】 GetJoint(user, SkeletonJoint.RightHip, 12); //【右尻】 GetJoint(user, SkeletonJoint.RightKnee, 13); //【右膝】 GetJoint(user, SkeletonJoint.RightFoot, 14); //【右足首】 if (SkeletonJointData[9].Confidence != 0 && SkeletonJointData[12].Confidence != 0) //【下腹部】 { Point3D pos = new Point3D(); SkeletonJointData[15] = SkeletonJointData[9]; pos.X = 0.5f * (SkeletonJointData[9].Position.X + SkeletonJointData[12].Position.X); pos.Y = 0.5f * (SkeletonJointData[9].Position.Y + SkeletonJointData[12].Position.Y); pos.Z = 0.5f * (SkeletonJointData[9].Position.Z + SkeletonJointData[12].Position.Z); SkeletonJointData[15].Position = pos; } } private void DrawLine(Graphics g, Color color, Dictionary<SkeletonJoint, SkeletonJointPosition> dict, SkeletonJoint j1, SkeletonJoint j2) { Point3D pos1 = dict[j1].Position; Point3D pos2 = dict[j2].Position; if (dict[j1].Confidence == 0 || dict[j2].Confidence == 0) return; if ((int)pos1.X < 0 || (int)pos1.X > 640 || (int)pos1.Y < 0 || (int)pos1.Y > 480) return; if ((int)pos2.X < 0 || (int)pos2.X > 640 || (int)pos2.Y < 0 || (int)pos2.Y > 480) return; //【線幅3に設定】 g.DrawLine(new Pen(color, 3.0f), new Point((int)pos1.X, (int)pos1.Y), new Point((int)pos2.X, (int)pos2.Y)); } private void DrawSkeleton(Graphics g, Color color, int user) { GetJoints(user); Dictionary<SkeletonJoint, SkeletonJointPosition> dict = this.joints[user]; DrawLine(g, color, dict, SkeletonJoint.Head, SkeletonJoint.Neck); DrawLine(g, color, dict, SkeletonJoint.LeftShoulder, SkeletonJoint.Torso); DrawLine(g, color, dict, SkeletonJoint.RightShoulder, SkeletonJoint.Torso); DrawLine(g, color, dict, SkeletonJoint.Neck, SkeletonJoint.LeftShoulder); DrawLine(g, color, dict, SkeletonJoint.LeftShoulder, SkeletonJoint.LeftElbow); DrawLine(g, color, dict, SkeletonJoint.LeftElbow, SkeletonJoint.LeftHand); DrawLine(g, color, dict, SkeletonJoint.Neck, SkeletonJoint.RightShoulder); DrawLine(g, color, dict, SkeletonJoint.RightShoulder, SkeletonJoint.RightElbow); DrawLine(g, color, dict, SkeletonJoint.RightElbow, SkeletonJoint.RightHand); DrawLine(g, color, dict, SkeletonJoint.LeftHip, SkeletonJoint.Torso); DrawLine(g, color, dict, SkeletonJoint.RightHip, SkeletonJoint.Torso); DrawLine(g, color, dict, SkeletonJoint.LeftHip, SkeletonJoint.RightHip); DrawLine(g, color, dict, SkeletonJoint.LeftHip, SkeletonJoint.LeftKnee); DrawLine(g, color, dict, SkeletonJoint.LeftKnee, SkeletonJoint.LeftFoot); DrawLine(g, color, dict, SkeletonJoint.RightHip, SkeletonJoint.RightKnee); DrawLine(g, color, dict, SkeletonJoint.RightKnee, SkeletonJoint.RightFoot); for (var i = 0; i < nJoints; i++) { if (SkeletonJointData[i].Confidence != 0 && SkeletonJointData[i].Position.X >= 0 && SkeletonJointData[i].Position.X < 640 && SkeletonJointData[i].Position.Y >= 0 && SkeletonJointData[i].Position.Y < 480) { //【関節を円で表示】 SolidBrush newbrush = new SolidBrush(Color.Red); g.FillEllipse(newbrush, SkeletonJointData[i].Position.X - 5f, SkeletonJointData[i].Position.Y - 5f, 10f, 10f); } } } private unsafe void ReaderThread() { DepthMetaData depthMD = new DepthMetaData(); ImageMetaData imageMD = new ImageMetaData(); while (this.shouldRun) { try { this.context.WaitOneUpdateAll(this.depth); } catch (Exception) { } this.depth.GetMetaData(depthMD); this.image.GetMetaData(imageMD); CalcHist(depthMD); lock (this) { Rectangle rect = new Rectangle(0, 0, this.bitmap.Width, this.bitmap.Height); BitmapData data = this.bitmap.LockBits(rect, ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb); BitmapData data2 = this.bitmap2.LockBits(rect, ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb); if (this.shouldDrawPixels) { //【image情報】RGBカメラ画像を左側パネルに表示する byte* pImage = (byte*)this.image.ImageMapPtr.ToPointer(); //【RGB24bitBitMap】 for (int y = 0; y < imageMD.YRes; ++y) { byte* pDest = (byte*)data.Scan0.ToPointer() + y * data.Stride; for (int x = 0; x < imageMD.XRes; ++x, pImage += 3, pDest += 3) { pDest[0] = *(pImage + 2); //【青】 pDest[1] = *(pImage + 1); //【緑】 pDest[2] = *(pImage); //【赤】 } } //【depth情報】デプスバッファ情報を右側パネルに表示する ushort* pDepth = (ushort*)this.depth.DepthMapPtr.ToPointer(); //【depth16bit】 ushort* pLabels = (ushort*)this.userGenerator.GetUserPixels(0).LabelMapPtr.ToPointer(); for (int y = 0; y < depthMD.YRes; ++y) { byte* pDest = (byte*)data2.Scan0.ToPointer() + y * data.Stride; for (int x = 0; x < depthMD.XRes; ++x, ++pDepth, ++pLabels, pDest += 3) { pDest[0] = pDest[1] = pDest[2] = 0; ushort label = *pLabels; if (this.shouldDrawBackground || *pLabels != 0) { if (checkBox1.Checked) { //depthに応じて着色表示 byte dL = (byte)(255 - (int)((float)(*pDepth) * 0.0255)); if (*pDepth == 0) { dL = 0; } Color depthColor = Color.FromArgb(ColorLookupTable[dL]); pDest[0] = (byte)depthColor.B; pDest[1] = (byte)depthColor.G; pDest[2] = (byte)depthColor.R; } else { //depth bufferにuser表示 Color labelColor = Color.White; if (label != 0) { labelColor = colors[label % ncolors]; } byte pixel = (byte)this.histogram[*pDepth]; pDest[0] = (byte)(pixel * (labelColor.B / 256.0)); pDest[1] = (byte)(pixel * (labelColor.G / 256.0)); pDest[2] = (byte)(pixel * (labelColor.R / 256.0)); } } } } } this.bitmap.UnlockBits(data); this.bitmap2.UnlockBits(data2); //【Kinect Sensorの検出状態と骨格情報の表示】 Graphics g = Graphics.FromImage(this.bitmap); // this.handsGenerator. string hand = "Hand"; if (handsPos.Z != 0) { g.DrawString(hand, new Font("Arial", 6), new SolidBrush(anticolors[0]), handsPos.X, handsPos.Y); label5.Text = "x = " + handsPos.X.ToString(); label6.Text = "y = " + handsPos.Y.ToString(); label7.Text = "z = " + handsPos.Z.ToString(); } int[] users = this.userGenerator.GetUsers(); foreach (int user in users) //【各ユーザーに関して実施】 { if (this.shouldPrintID) { Point3D com = this.userGenerator.GetCoM(user); com = this.depth.ConvertRealWorldToProjective(com); string label = ""; if (!this.shouldPrintState) { label += user; } else if (this.skeletonCapbility.IsTracking(user)) { label += user + " - Tracking"; } else if (this.skeletonCapbility.IsCalibrating(user)) { label += user + " - Calibrating..."; } else { label += user + " - Looking for pose"; // this.handsGenerator.StartTracking(ref handsPos); } g.DrawString(label, new Font("Arial", 6), new SolidBrush(anticolors[user % ncolors]), com.X, com.Y); } // if (this.shouldDrawSkeleton && this.skeletonCapbility.IsTracking(user)) if (this.skeletonCapbility.IsTracking(user)) DrawSkeleton(g, colors[0], user); //【赤で表示】 } g.Dispose(); } this.Invalidate(); } } } } |
![]() 【図xx】Form1のデザイン |
【Form1.Designer.cs】Kinect SensorをC#で利用する場合のサンプル・プログラムnamespace KinectSimpleViewer { partial class Form1 { /// |
【Kinectセンサについて】2011/04/03 追記■Microsoft社XBOX用モーションキャプチャーセンサKinect ![]() ■Visual C#でKinectアプリケーション開発が行えます。 |
【注】Kinect(キネクト)センサは、Microsoft社のXBOX360用のモーションキャプチャーセンサ(1万5千円弱)で単独で購入することができます。640×480画素のRGBカメラのほか、赤外線カメラも搭載しており、毎秒30フレームの速度で深さ情報(depth buffer)を得る事ができます。USBインターフェースなのでWindowsパソコンに接続可能です。2011年1月6日にC#で使える暫定版OpenNI.net.dllがオープンソースで公開されています。OpenNI.net.dllを使えば上の写真のように基本的な骨格情報(関節の3次元座標)をリアルタイムに得られます。下の動画は磁界・超音波センサに応用してみた事例です。トランジスタ技術2011年2月号の拡張現実センサの進化形に該当します。(詳細は別ページで記載の予定。現在、導入メモ等と一部ソースを記載。[2011/02/05 - 2011/03/31]) |
【各種拡張現実センサの開発】
どちらかと言うと、専用センサを開発するのがメインで、拡張現実センサの機能を付加しているという程度です。 |
|
|
|