平成 25 年度卒業研究論文 拡張現実 (AR) を用いた 3D シミュレーションアプリの開発 - Kinect で実現するナチュラルユーザーインターフェース - 近畿大学工学部 情報システム工学科情報メディアコース 1010960077 太田雄介 2014.02.10 提出
目次 1. 研究の背景と目的...1 1.1 Kinect の概要...2 1.1.1 Kinect の仕組み...2 1.1.2 Kinect の原理...2-3 1.1.3 Kinect for Windows と Xbox 360 Kinect...3 1.2 Kinect の実用例...3 2. 開発環境...3-4 2.1 Kinect for Windows SDK...4 2.2 WPF アプリケーション...4-5 2.2.1 GUI(.xaml)...5 2.2.2 ロジック (.xaml.cs)...5 3. KinectClosetSimulation システム外部設計...5 3.1 MainWindow.xaml...5 3.2 MainWindow.xaml.cs...6 4. KinectClosetSimulation システム内部設計...6 4.1 MainWindow.xaml...6 4.2 MainWindow.xaml.cs...7 4.2.1 MainWindow.xaml.cs >> MainWindow...7-9 4.2.2 MainWindow.xaml.cs >> WindowLoaded...9-11 4.2.3 MainWindow.xaml.cs >> WindowClosing...11 4.2.4 MainWindow.xaml.cs >> ColorFrameReady...11-14 4.2.5 MainWindow.xaml.cs >> SkeletonFrameReady...14-18 4.2.6 MainWindow.xaml.cs >> ChangeImageSource...18-19 5. 今後の課題...20 6. まとめ...20 7. 謝辞...20 8. 参考文献...21 9. 付録...22-29
1. 研究の背景と目的 Natural User Interface(NUI) は, 人間の五感や, 人間が自然に行う動作によって機械を操作する方法である.NUI は Wii リモコンに始まり,2010 年に米 Microsoft 社の Kinect がゲーム機 X box 用のセンサーとして発売され 2013 年 11 月 25 日にはさらに機能が追加された Kinect v2 Developer Preview が世界中に公開された ( 一般発売は 2014 年予定 ) [1] また, 同年 7 月 22 日には米 LeapMotion 社が指先によるジェスチャー入力を可能にする Leap Motion Controller [2] を発売しており 今日のモーションセンサ技術の発展は, 従来は不可能であったユーザー インターフェース (User Interface : UI) を可能にしている. コンピュータを操作する際のデータ入力の操作は, よりユーザに優しいものに移り変わってきている. 元々は全ての操作をキーボードからのコマンド入力 (Character-based User Interface : CUI) によって行ってきたものが, ビジュアルな画面上でマウスなどのポインティングデバイスによって基礎的な操作を行う (Graphical User Interface : GUI) ことができるまでに進化した. そして Kinect のように直感的な人間の動作で操作できるインターフェースは,Natural User Interface (NUI) と呼ばれ, 次世代の UI として注目されている. 本研究では Kinect の特徴を活かした拡張現実 (AR) 空間を構築し, これまで実現できなかった効果を生み出す視覚的シミュレーションアプリを開発することを目的とする. ユーザ要求 本研究で用いる Kinect は, 何も持たず, 何にも触れずに体の動きや声でコンピュータと情報のやり取りを行う NUI を実現することができるので, あらゆる分野 ( 後述 1.2) に応用され今後も更なる発展が期待されるデバイスの 1 つである. 従って Kinect を利用したアプリケーションのユーザとしては, 直感的で思い通りに操作が可能であることだけではなく, これまでには実現できなかった効果を生み出し それらによって新鮮な感覚を体験することも自然な要求である. AR アプリケーションが広く普及するためには, 用途によって様々に異なるユーザの要求に対し最適な機能を備えている必要がある. 例えば, 仮想世界を眼前の光景に融合するアプリケーションのユーザとしては, いつでも AR を利用することができる環境が整っており, 手軽な操作で実行可能であることが自然な要求である. 言い換えれば 現段階の Kinect のシステムでは, ユーザが いつでも利用することができる 手軽な操作で実行可能な 機能を実現することは難しい. 従って本研究では, ユーザが実際に使用したい時や場所に然るべき機能を備えたシステムが常備してあることが理想であり, それを想定した上で開発する必要がある. 制作内容 服屋には服の種類だけでなく, それぞれのサイズや色など数多くのバリエーションの服が並べられている. これらの服の中から客が納得して購入するまで試着を繰り返すのは, 多くの手間や時間がかかるだけでなく商品である服自体が汚れたり傷んでしまう可能性がある. そこで Kinect を用いてバーチャル試着を行うことができれば, 手軽に何度も購入予定の服を着るシミュレーションをすることができる. また, 試着室に入る必要もなくなるので, 同行者や店員と話しながら試着を楽しむことが出来る. 1
1.1 Kinect の概要 Kinect( キネクト ) とは,Microsoft 社が発売している Xbox360 用の新デバイスのことであり, ユーザーは Kinect の前で物理コントローラーを使わずにゲーム操作を行うことが出来る. この操作をを可能にしているのは,Kinect 本体に搭載されている RGB カメラ 近赤外線プロジェクタ 近赤外線カメラ ( 深度センサー ) 4 つのマイクアレイ チルトモーターであり, カラー情報やプレイヤーの身長 骨格 三次元的位置を認識, 追跡しモーションキャプチャーを合成することが出来る. また, マイクを搭載しているため, 音源位置特定 音声認識をすることも出来るので, 声によるゲーム操作も可能にしている. [3] 図 -1 Kinect カメラ 現在 Microsoft 社は,Kinect を Windows 上で動作させるための Kinect for Windows SDK を一般公 開しており,Windows(Windows7 以降 ) 上で Kinect を用いたアプリケーションの開発が可能と なっている. 1.1.1 Kinect の仕組み [4] Kinect の内部には以下が搭載されている. 近赤外線プロジェクタ 近赤外線カメラ( 深度カメラ ) ( 解像度最大 640 480) RGB カメラ ( 解像度最大 1280 960) マイクアレイ( 4) チルトモーター図 -2 Kinect の内部 [3] Kinect の下部にはマイクアレイがあり,4 本の個別マイクから構成される.1 本は近赤外線プロジェクターの左に, 他の 3 本はカラーカメラの右側に等間隔で配置されている. [3] 1.1.2 Kinect の原理 レーザー光源の位置から水平方向にずれた位置でカメラにより観察を行うと, レーザー光が 照射されている被写体までの距離 ( 奥行き ) z が変わると, カメラ画像内で物体に照射された 光点の位置が水平方向に距離 x ずれる. この依存関係 z = f(x) を利用することで,x を計測して z を求めることができる ( 光切断法 ). カメラ画像内のすべて物体の奥行き z を同時に求めるため には 2 次元のランダムドットパターン ΣPij を照射して, そのカメラ画像と元々のランダム ドットパターンの一部 Pij との相互相関を計算して, そのピークを求めることで, カメラ画像 内で Pij が照射された位置を特定できる. これにより, カメラ画像内の部分毎に水平方向のずれ x が決まるので, カメラ画像の奥行き z を求めることができる. この手法は照射されたランダム ドットパターンを観測して, そのある部分の光源を特定できるので Light Coding 技術と呼ばれ ている Kinect 内部にマイクが 4 つある理由は, それぞれのマイクで収集した音声を分析することで音 の位相から音源の位置を割り出すためである. 音源位置は左右に ±50 の範囲で検出可能であり, 2
複数のプレイヤーが Kinect の前にいたとき誰が発声しているのかを判別できる.( 上限 7 人 ) ま た, マイクにはエコー キャンセラ機能があり, 発する声とソフトウェア内の音源は分離する. 1.1.3 Kinect for Windows と Xbox 360 Kinect [4] Kinect for Windows SDK からは,Kinect for Windows センサーだけでなく Xbox 360 Kinect センサーによって開発することも出来る. しかし,Xbox 360 Kinect センサーを使用する場合には以下の点に注意しなければならない. 開発目的であれば,Xbox 360 Kinect センサーでも利用可能だが, 商用利用することは出来ない. Xbox 360 Kinect センサーでは近接モード (Near Mode) はサポートしない. Kinect for Windows センサーと Xbox 360 Kinect センサーは完全互換ではない. 1.2 Kinect の実用例 Kinect は様々な分野で活躍できる可能性を秘めている. 医療現場 : 手術中の執刀医が患者の情報をコンピュータ上で閲覧する際, 従来は衛生状態を保つために自らの操作が不可能であるため, 情報の即時提示が困難なことや, 集中力低下のリスクがあったが,Kinect を用いて片手または声だけで容易に操作をすれば, 衛生状態を保ちながら執刀医が直感的に操作をすることができるようになる. [5] リハビリテーション : 患者が Kinect を用いたリハビリテーションを行えば, 直感的な操作で 比較的簡単にできる上, 患者自身のモチベーションの維持が期待できる. [6] 2. 開発環境 [4] [8] ハードウェア要件 OS: Microsoft Windows 7,8 または Microsoft Windows Embedded Standard 7 Kinect を動作させる Kinect for Windows SDK は Windows7 以前の OS には対応していない. 本研究では Windows7 を使用する. CPU: デュアルコア 2.66GHz 以上 ( 推奨 ) Kinect はハードウェアですべての処理を行うわけではなく,PC 側でも処理を行うのである程度の スペックを求められる. 今回の研究ではデュアルコア 3.30GHz のプロセッサを用いる. メモリ : 2GB 以上の RAM ( 推奨は 4GB 以上 ) USB 2.0 ポートに接続された Kinect センサー グラフィック ボード : DirectX 9.0 以上をサポート ソフトウェア要件 3
インストール Kinect for Windows SDK1.5( ソフトウェア開発キット ) 32bit にも 64bit にも対応しているが, 本研究ではプログラミングに Visual Studio C# 2010 Express を使用するため 32bit 版を利用する. Microsoft Visual C# 2010 Express (32bit) 既述の NUI ライブラリに命令を出すための統合開発環境.Microsoft Visual C# 2010 Express は 32bit のみである. 2.1 Kinect for Windows SDK [8] 図 -3(SDK の仕組み ) Microsoft 社は Windows 上で Kinect の開発ができる SDK として,Kinect for SDK の β 版を 2011 年 6 月にリリースした.Kinect for Windows SDK によって,Kinect の全ての機能を利用することが出来るようになった.2012 年 2 月に正式版がリリースされた.Kinect for Windows SDK を使用することで,WPF アプリケーション,WinForms アプリケーション,XNA アプリケーションでプログラミングすることが出来る. 本研究では,WPF アプリケーションを用いて開発する. 2.2 WPF アプリケーション [9] WPF(Windows Presentation Foundation) は,XAML 言語 (Extensible Application Markup Language) と C# を始めとする, ロジックを記述するプログラミング言語を用いて, 見た目 ( デザイン ) に関する部分は XAML 言語, ロジックは C# で記述するという, アプリケーションの見た目に関する部分をロジックと完全に切り離して考えることが出来る GUI 開発ライブラリのことである.XAML コードと, それに付随する C#(C++, Visual Basic) の分離コードは, ビルド時に 1 つのクラスに合成され, コンパイルされる. 4
( 図 -4 画像参照元 : http://www.atmarkit.co.jp/ait/articles/1005/14/news105.html) 2.2.1 GUI(.xaml) XAML ファイルは, オブジェクトに対応づけるための XML 要素を記述する.NET Framework3.0 を基盤としたマークアップ言語であり, そこで定義されている表示要素をウィンドウに配置してレイアウトを指定する. また特定の入力要素の値を変数と結びつけたり ( データバインディング ), オブジェクトの相互関係を定義したり, イベント処理を設定することが出来る. 2.2.2 ロジック (.xaml.cs) XAML ファイルがオブジェクトに対応づけるための XML 要素を記述する為のファイルであるのに対し, ロジックを記述するファイルではクラスを作成することによってその指定した XML 要素に UI 要素を付け加えることが出来る. 3. KinectClosetSimulation システム外部設計 服画像を 3 種類用意し, 認識されたプレイヤーが右腕を, 肘を曲げずに高く上げると, プレイヤーの肩に合わせて服が描画され, 更にもう一度右腕を降ろした状態から肘を曲げずに高く上げると また違う服が描画される. 3.1 MainWindow.xaml XAML ファイルは, 開発するアプリケーションのレイアウトを主に担当するので, 各要素の配置, サイズを視覚的に確認しながら進めていくことが出来る. 図 -5 XAML ファイル 5
3.2 MainWindow.xaml.cs XAML ファイルでは出来ない細かい処理はロジック (.xaml.cs) ファイルで行う. 図 -6 実行結果 4. KinectClosetSimulation システム内部設計 4.1 MainWindow.xaml 既述のように,XAML ファイルは主にレイアウトを担当するが, Loaded="WindowLoaded" のようにイベント処理をするための準備もこのファイルで行う. <Window x:class="kinectclosetsimulation.mainwindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525" Loaded="WindowLoaded" Closing="WindowClosing" Left="0" Top="0"HorizontalAlignment="Left" IsManipulationEnabled="True" SizeToContent="WidthAndHeight" VerticalAlignment="Top"WindowStartupLocation="CenterScreen"> <Viewbox> <Grid> <Canvas Width="640" Height="480"> <Image Name="Image" Width="640" Height="480" /> <Image x:name="clothe" Source="Images\SpaceSuit2.png" RenderTransformOrigin="0.5, 0.5" Canvas.Left="100" Canvas.Top="100" Stretch="None"/> </Canvas> </Grid> </Viewbox> </Window> 表 1 MainWindow.xaml 6
4.2 MainWindow.xaml.cs 4.2.1 MainWindow.xaml.cs >> MainWindow KinectSensor private KinectSensor kinectsensor ; public class Kinectsensor (Kinect for Windows v1.5 ~) Represents a Kinect sensor. (Kinect の機能にアクセスするためのクラスやイベントを保持する ) Public Propaties 1 AudioSource Gets the audio source. ( 音声関連処理を行うクラスのインスタンスを取得する.) 2 ColorStream Gets the color stream for the Kinect. (RGB カメラ関連処理を行うクラスのインスタンスを取得する.) 3 CoodinateMapper (v1.6~) 1 Gets a CoordinateMapper object, which is used to convert points from one coordinate space (color, depth, skeleton) to another. ((RGB, 深度, 骨格 ) のうち 1 つの座標を 他のカメラ座標に変換する CoodinateMapper オブジェクトを取得する.) 4 DepthStream Gets the depth stream. ( 距離カメラ関連処理を行うクラスのインスタンスを取得する.) 5 DeviceConnectionId Gets the USB connector ID. ( デバイスの接続 ID を取得する.) 6 ElevationAngle Gets or sets the elevation angle. ( チルトモータの角度を設定もしくは取得する.) 7 IsRunning (bool) Gets a value indicating whether the sensor is streaming data. ( センサーが動作中か (true) それ以外か (false) を取得する.) 8 KinectSensors (static) Gets a collection of Kinect sensors. (PC に接続されている Kinect センサーの一覧を取得する.) 9 MaxElevationAngle Gets the maximum elevation angle (in degrees). ( チルトモータの最大角度を取得する ) 10 MinElevationAngle Gets the minimum elevation angle (in degrees). ( チルトモータの最小角度を取得する.) 11 SkeletonStream Gets the skeleton stream. ( プレイヤー ( スケルトン ) 関連の処理を行うクラスのインスタンスを取得する.) 12 Status Gets the status of the sensor. (Kinect の接続状態を取得する.) 表 2 KinectSensor class Public properties 7
Public Method 1 MapDepthFrameToColorFra me 2 MapDepthToColorImagePoin t Obsolete starting with version 1.6; replaced by the CoodinateMapper.MapDepthFrameToColorFrame method. In versions prior to 1.6, this method maps a depth frame to a color frame. ((Kinect for Windows)v1.6 以降は旧式となり,CoodinateMapper クラスに移された.v1.6 以前は, 深度フレーム (X, Y) に対応するカラーフレーム (X, Y) の座標を取得するメソッド.) Obsolete starting with version 1.6; replaced by the CoodinateMapper.MapDepthPointToColorImagePoint method. In versions prior to 1.6, this method maps a depth coordinate with depth value to a color coordinate. ((Kinect for Windows)v1.6 以降は旧式となり,CoodinateMapper クラスに移された.v1.6 以前は 深度値 (Z 座標 ) を合わせた深度座標に対応する RGB カメラ座標を取得するメソッド.) 3 MapDepthToSkeletonPoint Obsolete starting with version 1.6; replaced by the CoodinateMapper.MapDepthPointToSkeletonPoint method. In versions prior to 1.6, this method maps a coordinate (depthx, depthy) and depth value to a skeleton point. ((Kinect for Windows)v1.6 以降は旧式となり,CoodinateMapper クラスに移された.v1.6 以前は, 深度値 (Z 座標 ) を合わせた深度座標に対応するスケルトン座標を取得するメソッド.) 4 MapSkeletonPointToColor Obsolete starting with version 1.6; replaced by the CoodinateMapper.MapSkeletonPointToColorPoint method. In versions prior to 1.6, this method maps a skeleton point to a color point. ((Kinect for Windows)v1.6 以降は旧式となり,CoodinateMapper クラスに移された,v1.6 以前は, スケルトン座標に対応する RGB カメラの座標に変換する.) 5 MapSkeletonPointToDepth Obsolete starting with version 1.6; replaced by the CoodinateMapper.MapSkeletonPointToDepth method. In versions prior to 1.6, this method maps a skeleton point to a depth point. ((Kinect for Windows)v1.6 以降は旧式となり,CoodinateMapper クラスに移された.v1.6 以前は, スケルトン座標に対応する RGB カメラの座標に変換する.) 6 Start Starts streaming data out of the sensor. (Kinect の処理を開始する.) 7 Stop Stops streaming data out of the sensor. (Kinect の処理を停止する.) 表 3 KinectSensor class Public method 1 CoodinateMapper は Kinect for Windows v1.6 以前は実装されていない. 8
Public Events 1 AllFramesReady Event that fires when new frames are available for each of the sensor's active streams. ( 全てのストリームで, フレームが更新された時に起動するイベント ) 2 ColorFrameReady Event that fires when a new color frame is available in the ColorStream. ( カラーストリームで カラーフレームが更新された時に起動するイベン ト ) 3 DepthFrameReady Event that fires when a new depth frame is available in the DepthStream. ( 深度ストリームで 深度フレームが更新された時に起動するイベント ) 4 SkeletonFrameReady Event that fires when a new skeleton frame is available in the SkeletonStream. ( スケルトンストリームで スケルトンフレームが更新された時に起動す るイベント ) 表 4 KinectSensor class Public Events 4.2.2 MainWindow.xaml.cs >> WindowLoaded カラー 骨格ストリームの有効化, バッファの初期化, 新しいインスタンスの初期化, イベントの発生などの, 新しい服を描画するための準備を行う. WriteableBitmap 指定したパラメータを使用して,WriteableBitmapSource クラスの新しいインスタンスを初期化し, ターゲットに画像を書き込み 更新するための BitmapSource を提供する.WriteableBitmap オブジェクトは System.Windows.Media.Imaging 名前空間に含まれ, 画像のピクセルデータの頻繁な更新に対処するために作成されている.WriteableBitmap を作成するときは画像のプロパティ (RGB カメラの幅,RGB カメラの高さ,Bitmap の水平値,Bitmap の垂直値,Bitmap のピクセルフォーマット,Bitmap の Bitmap パレット ) を定義し,WriteableBitmap オブジェクトにメモリを一度割り当てれば, 後は必要に応じてピクセルデータが更新される. public partial class MainWindow : Window private KinectSensor kinectsensor ; private WriteableBitmap writeablebitmap ; // 新しいインスタンスを初期化 this.writeablebitmap = new WriteableBitmap(this.kinectSensor.ColorStream.FrameWidth, this.kinectsensor.colorstream.frameheight, 96.0, 96.0, pixelformats.bgr32, null); public class WriteableBitmap Provides a BitmapSource that can be written to and updated. ( 書き込み及び更新が可能な BitmapSource を提供する.) 9
Constructor 1 WriteableBitmap(BitmapSource) Initialize a new instance of the WriteableBitmap class using the given BitmapSource. ( 指定した BitmapSource を使用して,WriteableBitmap クラスの新しいインスタンスを初期化する.) BitmapSource 初期化に使用する BitmapSource 2 WriteableBitmap(Int32, Int32, Double, Double, pixelformat, BitmapPalette) Initialize a new instance of the WriteableBitmap class using the specified parameters. ( 指定したパラメータを使用して,WriteableBitmap クラスの新しいインスタンスを初期化する.) Int32 Bitmap の幅 Int32 Double Double pixelformat Bitmap の高さ Bitmap の水平ドット / インチ Bitmap の垂直ドット / インチ Bitmap の pixelformat BitmapPalette Bitmap の BitmapPalette 表 5 WriteableBitmap class WriteableBitmap Constuctors XAML ファイル内の Image オブジェクトの Source プロパティに, プロジェクトのソリューションエクスプローラ内にある images フォルダにまとめて入れておいた, ターゲット上に表示する画像ファイルへのパスを設定しておく. そして WriteableBitmap クラスの新しいインスタンスを初期化したら新たに画像を描画する. 10
MainWindow.xaml.cs this.writeablebitmap = new WriteableBitmap(this.kinectSensor.ColorStream.FrameWidth, this.kinectsensor.colorstream.frameheight, 96.0, 96.0, pixelformats.bgr32, null); //images フォルダ内の画像を描画 this.image.source = this.writeablebitmap; MainWindow.xaml <Image x:name="clothe" Source="Images\SpaceSuit2.png" RenderTransformOrigin="0.5, 0.5" Canvas.Left="100" Canvas.Top="100" Stretch="None"/> 4.2.3 MainWindow.xaml.cs >> WindowClosing Kinect の処理を終了するためのイベント. this.kinectsensor.stop(); 表 6 WindowClosing 4.2.4 MainWindow.xaml.cs >> ColorFrameReady RGB カメラでカラー画像を表示するためのイベント. ColorImageFrame using (ColorImageFrame colorimageframe = e.opencolorimageframe()) if (colorimageframe!= null) colorimageframe.copypixeldatato(this.colorpixels); this.writeablebitmap.writepixels(new Int32Rect(0, 0, this.writeablebitmap.pixelwidth, this.writeablebitmap.pixelheight),this.colorpixels, this.writeablebitmap.pixelwidth * sizeof(int), 0); public class ColorImageFrame It provides access to the dimensions, format and pixel data for a color frame. ( フレームごとの RGB カメラのピクセルデータとバイト長, フォーマットを取得する.) 11
Public Properties Format Gets the format for the color data, which includes the data type, frame rate, and the resolution. ( カラーフレームのフォーマット ( フレームレート 座標 ) を取得 設定 ) PixelDataLengt h Public Method CopyPixelDataTo ( byte[] ) Gets the total length of the pixel data buffer. ( ピクセルデータのバイト長を取得 ) 表 7 ColorImageFrame class ColorImageFrame Properties Copies a frame of color data to a pre-allocated array of bytes. The array size must be equal to the ColorImageFrame.PixelDataLength Property. ( フレームごとの RGB ピクセルデータを取得する.PixelDataLength Property で取得したバイト長分の長さが確保された byte の配列でなければならない.) 表 8 ColorImageFrame class ColorImageFrame Method ColorImageFrameReadyEventArgs private void ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e) using (ColorImageFrame colorimageframe = e.opencolorimageframe()) public class ColorImageFrameReadyEventArgs The event arguments provided in a KinectSensor.ColorFrameReady event when a frame of color data is ready. (RGB カメラのフレーム更新を行う時の ColorImageFrame に渡される引数を提供する.) Public Method 2 OpenColorImageFrame() Gets the most recent frame of color data. ( 新しいフレームの RGB カメラの情報を取得する.) 表 9 ColorImageFrameReadyEventArgs class ColorImageFrameReadyEventArgs Method 2 このメソッドで取得した ColorImageFrame は using でくくるか,Dispose を明示的に呼び出す必要がある. 12
MainWindow.xaml.cs private void ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e) // 新しい ColorImageFrame の RGB カメラの情報を取得する using (ColorImageFrame colorimageframe = e.opencolorimageframe()) if (colorimageframe!= null) // フレームごとの RGB ピクセルデータを取得する colorimageframe.copypixeldatato(this.colorpixels); する // 3 WriteableBitmap.WritePixels : ビットマップの指定した領域内のピクセルを更新 this.writeablebitmap.writepixels(new Int32Rect(0, 0, this.writeablebitmap.pixelwidth, this.writeablebitmap.pixelheight),this.colorpixels, this.writeablebitmap.pixelwidth * sizeof(int), 0); 3 WriteableBitmap.WritePixels Method WriteableBitmap.WritePixels( ); Int32Rect sourcerect, //1 Array Int32 Int32 pixels, //2 stride, //3 offset, //4 this.writeablebitmap.writepixels( new Int32Rect(0,0,this.writeableBitmap.PixelWidth, this.writeablebitmap.pixelheight), this.colorpixels, //private byte[] colorpixels; this.writeablebitmap.pixelwidth * sizeof(int), 0 ); 表 10 WriteableBitmap.WritePixels Method ビットマップの指定した領域内のピクセルを更新する.1 Int32Rect は 更新する WriteableBitmapSource の四角形であり, パラメータは,X 座標,Y 座標, 幅, 高さから成る. 座標は (0, 0), 幅と高さはビットマップの幅を取得する WriteableBitmap. PixelWidth, 及びビットマップの高さを取得する WriteableBitmap.PixelHeight から指定する.2 Array は ビットマップの更新に使用するピクセル配列であり, 表 11 のように KinectSensor から画像情報を受け取るために, 初期化されてフレームの PixelDataLength 分の長さが確保された byte 配列のバッファ colorpixels を指定する. 13
private KinectSensor kinectsensor; private byte[] colorpixels; if (null!= this.kinectsensor) // バッファの初期化 this.colorpixels = new byte[this.kinectsensor.colorstream.framepixeldatalength]; 表 12 colorpixels 3 stride は ピクセル 1 行分の byte 数 のことである.stride 値を求めるには, 画像の幅にピクセルあたりの byte 数を掛ける必要がある.4 offset は, 入力バッファの offset のことで, 基準点からの差 ( 距離 ) で表した値のことであるが, 本研究では画像のピクセル全てを置き換えるため, 常に 0 となる. 4.2.5 MainWindow.xaml.cs >> SkeletonFrameReady スケルトンフレームの更新イベントである.RGB カメラのフレーム更新イベントと同様に OpenSkeletonFrame() でフレームデータを取得し, 有効であればターゲットが指定された条件を満たし次第, 描画を行う. SkeletonFrame using (SkeletonFrame skeletonframe = e.openskeletonframe()) if (skeletonframe!= null) skeletons = new Skeleton[skeletonFrame.SkeletonArrayLength]; skeletonframe.copyskeletondatato(skeletons); public class SkeletonFrame Contains a per-frame buffer for skeleton data streamed out of a sensor. Also provides access to the floor clipping plane and the number of skeletons tracked. (KinectSensor が放出したスケルトンデータのフレームごとのバッファ, クリッピング平面, トラッキングしているターゲットの数 ) 14
Public Properties Floor ClipPlane FrameNumber SkeletonArrayLength Gets or sets the clip plane of the floor in the form of a four-component vector: (x, y, z, w). (3DCG をレンダリングするクリッピング平面の情報を取得 設定する ) Gets or sets the frame number. ( フレーム番号を取得 設定する ) Gets the total length of the skeleton data buffer for this SkeletonFrame. ( スケルトン配列の長さを取得する ) 表 13 SkeletonFrame class Public Properties Public Method CopySkeletonDataTo( skeletondata ) Copies skeleton data to an array of Skeletons, where each Skeleton contains a collection of the joints. ( ターゲットのスケルトン座標のデータを取得する.) 表 14 SkeleletonFrame class Public Properties SkeletonFrameReadyEventArgs private void SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e) Public class SkeletonFrameReadyEventArgs The event arguments provided in a KinectSensor.SkeletonFrameReady event when a frame of skeleton data is ready. ( スケルトンのフレーム更新を行う時の SkeletonFrameReady に渡される引数を提供する.) Public Method 3 OpenSkeletonFrame() Opens a SkeletonFrame object, which contains one frame of skeleton data. ( 新しいスケルトンフレームのスケルトンデータの情報を取得する.) 表 15 SkeleletonFrame class Public Method 3 このメソッドで取得した SkeletonFrame は using でくくるか,Dispose を明示的に呼び出す必要がある. 15
MainWindow.xaml.cs private void SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e) Skeleton[] skeletons = new Skeleton[0]; using (SkeletonFrame skeletonframe = e.openskeletonframe()) if (skeletonframe!= null) skeletons = new Skeleton[skeletonFrame.SkeletonArrayLength]; skeletonframe.copyskeletondatato(skeletons); if (skeletons.length!= 0) foreach (Skeleton skel in skeletons) if (skel.trackingstate == SkeletonTrackingState.Tracked) ColorImagePoint shoulderrightpoint = kinectsensor.mapskeletonpointtocolor(skel.joints[jointtype.shoulderright].position, ColorImageFormat.RgbResolution640x480Fps30); ColorImagePoint shoulderleftpoint = kinectsensor.mapskeletonpointtocolor(skel.joints[jointtype.shoulderleft].position, ColorImageFormat.RgbResolution640x480Fps30); float rightarmlift = skel.joints[jointtype.elbowright].position.y - skel.joints[jointtype.shoulderright].position.y; float leftarmlift = skel.joints[jointtype.elbowleft].position.y - skel.joints[jointtype.shoulderleft].position.y; if (weared) // 右腕が 下がって いる状態 if (rightarmlift < 0) CLOTHE.Visibility = Visibility.Visible; Canvas.SetLeft(CLOTHE, shoulderleftpoint.x - 65.0); Canvas.SetTop(CLOTHE, shoulderrightpoint.y - 50.0); isrightarmlifted = false; if (!isrightarmlifted) // 右腕が 上がって いる状態 if(rightarmlift > 0) ChangeImageSource(); // 服の画像 Source 変更 weared = true; isrightarmlifted = true; 16
スケルトンの座標を RGB カメラの座標に変換する if (skeletons.length!= 0) foreach (Skeleton skel in skeletons) if (skel.trackingstate == SkeletonTrackingState.Tracked) ColorImagePoint shoulderrightpoint = kinectsensor.mapskeletonpointtocolor(skel.joints[jointtype.shoulderright].position, ColorImageFormat.RgbResolution640x480Fps30); ColorImagePoint shoulderleftpoint = kinectsensor.mapskeletonpointtocolor(skel.joints[jointtype.shoulderleft].position, ColorImageFormat.RgbResolution640x480Fps30); 表 16 スケルトンの座標を RGB カメラの座標に変換する RGB カメラの解像度 フレームレート ColorImageFormat colorimageformat = ColorImageFormat.RgbResolution640x480Fps30; public enum ColorImageFormat (ColorImageFormat 列挙子 ) Image formats for a ColorImageFormat Each image format contains the data type, the resolution, and the frame rate. ( データタイプ ( 解像度 フレームレート ) を含む RGB カメラのフォーマット ) ColorImageFormat Members RawYuvResolution640x480Fps15 RgbResolution1280x960Fps12 RgbResolution640x480Fps30 YuvResolution640x480Fps15 Raw YUV data 1) whose resolution is 640 x 480 and frame rate is 15 frames per second. ( 解像度は 640 480 1 秒あたり 15 フレーム更新する YUV フォーマット ) RBG data whose resolution is 1280 x 960 and frame rate is 12 frames per second. ( 解像度は 1280 960 1 秒あたり 12 フレーム更新する RGB フォーマット ) RBG data whose resolution is 640 x 480 and frame rate is 30 frames per second. ( 解像度は 640 480 1 秒あたり 30 フレーム更新する RGB フォーマット ) YUV data whose resolution is 640 x 480 and frame rate is 15 frames per second. ( 解像度は 640 480 1 秒あたり 15 フレーム更新する YUV フォーマット ) Undefined The format is not defined. ( フォーマットが未定義 ) 参照元 :(http://msdn.microsoft.com/enus/library/microsoft.kinect.colorimageformat.aspx) 17
float rightarmlift = skel.joints[jointtype.elbowright].position.y - skel.joints[jointtype.shoulderright].position.y; if (weared) // 右腕が 下がって いる状態 if (rightarmlift < 0) CLOTHE.Visibility = Visibility.Visible; Canvas.SetLeft(CLOTHE, shoulderleftpoint.x - 65.0); Canvas.SetTop(CLOTHE, shoulderrightpoint.y - 50.0); isrightarmlifted = false; if (!isrightarmlifted) // 右腕が 上がって いる状態 if(rightarmlift > 0) ChangeImageSource(); // 服の画像 Source 変更 weared = true; isrightarmlifted = true; 表 17 服が描画される為の条件 既述のように,Kinect が認識したプレイヤーが右手を, 肘を伸ばした状態で高く上げると服が描画 変更される仕組みになっている. 変数 float rightarmlift で ( 右肘の Y 座標 )-( 右肩の Y 座標 ) を指定し,0 より大きな値になるか小さな値になるか, つまり右腕が上がっている状態か下がっている状態かで異なる処理を行う.weard( 現在服が描画されている状態 ) で右腕が下がっていると, 右肩から左肩の間に服が描画され, 右腕が上がると ChangeImageSource イベントを呼び出し, 新たな服が描画される. 4.2.5 MainWindow.xaml.cs >> ChangeImageSource 描画する服の変更を行うイベント. private void ChangeImageSource() ++imageindex; imageindex = imageindex % N_images; BitmapImage bitmapimage = new BitmapImage(new Uri(imgUriString[imageIndex], CLOTHE.Source = bitmapimage; UriKind.Relative)); 18
public class Bitmap Provides a specialized BitmapSource that is optimizedfor loading images using Extensible Application markup Language. ( 特別な BitmapSource イメージを提供する.Extensible Application markup Language を使用してイメージの読み込みのために最適化される.) BitmapImage Constructor BitmapImage() BitmapImage(Uri,) BitmapImage(Uri, RequestCachePolicy) Initializes a new instance of the BitmapImage class. (Bitmap クラスの新しいインスタンスを初期化する.) Initialize a new instance of the BitmapImage class by using the supplied Uri ( 指定された Uri を使用して Bitmap クラスの新しいインスタンスを初期化する.) Initializes a new instance of the BitmapImage class with an image whose source is a Uri, and is cached according to the provided RequestCachePolicy. ( 指定された Uri を使用して Bitmap クラスの新しいインスタンスを,RequestCachePolicy に従って初期化する.) 表 18 Bitmap class BitmapImage Constructor private void ChangeImageSource() ++imageindex; imageindex = imageindex % N_images; BitmapImage bitmapimage = new BitmapImage(new Uri(imgUriString[imageIndex], CLOTHE.Source = bitmapimage; UriKind.Relative)); 認識されたプレーヤーの右腕が上がると ChangeImageSource イベントが呼び出される. イベントが呼び出されると,imageIndex の値が一つ大きくなり, プレイヤーに服が描画されている場合でも, されていない場合でも, 新しい服が指定され, 描画される. 19
5. 今後の課題 ユーザー インターフェースが不十分 当初は, 腕が上がったことを認識すると服が表示されるのではなく, デフォルトで表示された画像を, ジェスチャー認識のスワイプを行うことで別の服に入れ替える予定であったが, プログラムが複雑で学習に時間を要するため, 時間の都合上, 実現することが出来なかった. 当初はターゲットの身長や体格, カメラからの距離によって服画像が拡大 縮小する予定で あった. Kinect に搭載されている近赤外線センサーを使用すれば, 深度 骨格情報を得ることが出来るため, ターゲットに合わせて服画像のサイズを自動的に変更することができたが, 時間の都合上, 実現することが出来なかった. 使い方を知らないユーザへの配慮の不足 既述のように, このアプリケーションは実際に一般のユーザが使用することを想定した上で取り組んでいたものであるにもかかわらず, 操作方法の説明がなく, 次にユーザがとるべきジェスチャーの誘導がない. 一般のユーザ目線で, わかりやすく, 面白いと感じるシステムを開発することを意識して取り組むべきであった. 6. まとめ Kinect を用いたモーションキャプチャで拡張現実 (AR) 空間を構築し, 実装させることができた. このバーチャル試着は, 着替えの手間なく試着をすることが出来るという便利さだけではなく, 試着室に入らずに気軽に服を変更することが出来るため同行者とともに楽しみながら試着を行うことが出来るので, その可能性は大いにあり, 研究を行う意義は十分にあると考えられる. その一方で, 研究の進捗に関しては多くの課題を残す結果となった. 特に, 新しいユーザー インターフェース をコンセプトとして, アイディアとそれを実現するための計画を立てた上で研究に臨んだにも関わらず, 学習と技術力の不足のために実現することが出来なかった. 卒業研究に約一年間取り組み, 学習や研究の成果を論文に書き起こして発表するまでの期間には, 本論には載せられないほどの些細なミスの見落としがあったために何週間も悩まされたこと, 残り時間の都合から研究内容を大幅に変更するために開発環境を変更し, 一から勉強し直す必要がありミーティングにおける進捗状況の発表が遅れてしまったことがあったが, 卒業後も研究は引き続き継続し, 発表の機会があれば是非積極的に参加していきたいと考えている. 7. 謝辞 最後に, 研究をはじめ, 本論文の作成にあたりいつでも熱心なご指導 ご指摘を賜りました徐丙鉄教授, 開発環境の構築にあたり指導をして頂いた工学部情報システム工学科映像応用システム研究室の田中一基教授, 映像応用システム研究室学生の皆様, そして日々のミーティングを通じて多くの知識やアドバイスを得る機会をくださった情報物理研究室学生の皆様に深く感謝申し上げます. 20
8. 参考文献 [1] 新型 Kinect for Windows v2 Developer Preview プログラミング入門 http://www.buildinsider.net/small/kinect2dp/01 [2]Leap Motion Mac & PC Motion Controller for Games, Design, & More https://www.leapmotion.com/ [3]United States Patent RANGE MAPPING USING SPECKLE DECORRELATION https://docs.google.com/viewer?url=patentimages.storage.googleapis.com/pdfs/us7433024.pdf [4]Jarret Webb / James Ashley 著 株式会社プロシステムエルオーシー訳初音玲監修 Kinect ソフトウェア開発講座 ( 翔泳社 ) [5] 手術室向け非接触型画像操作システム Opect( オペクト ) http://www.nichiigakkan.co.jp/service/medical/category/hospital/opect.html [6]Kinect の特徴を生かしたインタラクティブ リハビリシステム http://www.systemfriend.co.jp/node/434#.uged3jlxree [7] これまでとは違う!AR を本気でビジネスに生かす http://itpro.nikkeibp.co.jp/article/column/20130614/485301/?st=systeml [8] 中村薫, 斎藤俊太, 宮城英人 ( 株式会社ゲッシュ ) 著 Kinect for Windows SDK プログラミング C# 編 ( 秀和システム ) [9]Silverlight 入門 http:// anticlimactically/fdotnet/chushin/introsl_02/introsl_02_01.html [10] 第 1 回いよいよ WPF の時代 WPF の習得を始めよう http://www.atmarkit.co.jp/ait/articles/1005/14/news105.html 21
9. 付録 ( ソースコード ) MainWindow.xaml <Window x:class="kinectclosetsimulation.mainwindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525" Loaded="WindowLoaded" Closing="WindowClosing" Left="0" Top="0" HorizontalAlignment="Left" IsManipulationEnabled="True" SizeToContent="WidthAndHeight" VerticalAlignment="Top" WindowStartupLocation="CenterScreen"> <Viewbox> <Grid> <Canvas Width="640" Height="480"> <Image Name="Image" Width="640" Height="480" /> <Image x:name="clothe" Source="Images\SpaceSuit2.png" RenderTransformOrigin="0.5, 0.5" Canvas.Left="100" Canvas.Top="100" Stretch="None"/> </Canvas> </Grid> </Viewbox> </Window> MainWindow.xaml.cs using System; using System.Globalization; using System.IO; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Imaging; using Microsoft.Kinect; namespace KinectClosetSimulation public partial class MainWindow : Window 22
Boolean isrightarmlifted = false; int N_images = 3; string[] imguristring = "../Images/SchoolUnifo.png", "../Images/SpaceSuit2.png", "../Images/RogerUnifo2.png"; int imageindex = 1; Boolean weared = false; private KinectSensor kinectsensor; private WriteableBitmap writeablebitmap; private byte[] colorpixels; public MainWindow() InitializeComponent(); CLOTHE.Visibility = Visibility.Collapsed; private void WindowLoaded(object sender, RoutedEventArgs e) foreach (var potentialsensor in KinectSensor.KinectSensors) if (potentialsensor.status == KinectStatus.Connected) this.kinectsensor = potentialsensor; break; if (null!= this.kinectsensor) this.kinectsensor.colorstream.enable(colorimageformat.rgbresolution640x480fps30); this.kinectsensor.skeletonstream.enable(); this.colorpixels = new byte[this.kinectsensor.colorstream.framepixeldatalength]; 23
this.writeablebitmap = new WriteableBitmap(this.kinectSensor.ColorStream.FrameWidth, this.kinectsensor.colorstream.frameheight, 96.0, 96.0, PixelFormats.Bgr32, null); this.image.source = this.writeablebitmap; this.kinectsensor.colorframeready += this.colorframeready; this.kinectsensor.skeletonframeready += this.skeletonframeready; try this.kinectsensor.start(); catch (IOException) this.kinectsensor = null; private void WindowClosing(object sender, System.ComponentModel.CancelEventArgs e) if (this.kinectsensor!= null) this.kinectsensor.stop(); private void ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e) using (ColorImageFrame colorimageframe = e.opencolorimageframe()) 24
if (colorimageframe!= null) colorimageframe.copypixeldatato(this.colorpixels); this.writeablebitmap.writepixels(new Int32Rect(0, 0, this.writeablebitmap.pixelwidth, this.writeablebitmap.pixelheight), this.colorpixels, this.writeablebitmap.pixelwidth * sizeof(int), 0); private void SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e) Skeleton[] skeletons = new Skeleton[0]; using (SkeletonFrame skeletonframe = e.openskeletonframe()) if (skeletonframe!= null) skeletons = new Skeleton[skeletonFrame.SkeletonArrayLength]; skeletonframe.copyskeletondatato(skeletons); if (skeletons.length!= 0) foreach (Skeleton skel in skeletons) if (skel.trackingstate == SkeletonTrackingState.Tracked) ColorImagePoint shoulderrightpoint = kinectsensor.mapskeletonpointtocolor(skel.joints[jointtype.shoulderright].position, ColorImageFormat.RgbResolution640x480Fps30); ColorImagePoint shoulderleftpoint = 25
kinectsensor.mapskeletonpointtocolor(skel.joints[jointtype.shoulderleft].position, ColorImageFormat.RgbResolution640x480Fps30); float rightarmlift = skel.joints[jointtype.elbowright].position.y - skel.joints[jointtype.shoulderright].position.y; float leftarmlift = skel.joints[jointtype.elbowleft].position.y - skel.joints[jointtype.shoulderleft].position.y; if (weared) if (rightarmlift < 0) CLOTHE.Visibility = Visibility.Visible; Canvas.SetLeft(CLOTHE, shoulderleftpoint.x - 65.0); Canvas.SetTop(CLOTHE, shoulderrightpoint.y - 50.0); isrightarmlifted = false; if (!isrightarmlifted) if(rightarmlift > 0) ChangeImageSource(); weared = true; isrightarmlifted = true; 26
private void ChangeImageSource() ++imageindex; imageindex = imageindex % N_images; //messagetextbox.text = "imageindex=" + imageindex; BitmapImage bitmapimage = new BitmapImage(new Uri(imgUriString[imageIndex], UriKind.Relative)); CLOTHE.Source = bitmapimage; 27