Hololensでワールド座標系とOpenCVの画像座標系の変換を確認
やったこと(概要)
Hololensでスクリーンショットを撮って,OpenCVで画像処理,その結果を空間に反映ということは,今後よくあるのではないかと思います. ここで重要なのが座標系の変換です.Hololensの空間であるUnityのワールド座標系とOpenCVの画像座標系を適切に変換する必要があります.
Unityはこうした座標系変換を行うAPIを提供しています.今回はそれらのAPIが適切に機能するかを検証・確認してみました.
※OpenCVの画像座標としていますが,一般的な画像座標と等価です.
(何事もおそらくできるだろう という発想が危険です!)
さらに進化した画像処理ライブラリの定番 OpenCV 3基本プログラミング
- 作者: 北山洋幸
- 出版社/メーカー: カットシステム
- 発売日: 2016/04
- メディア: 単行本
- この商品を含むブログ (1件) を見る
詳解 OpenCV ―コンピュータビジョンライブラリを使った画像処理・認識
- 作者: Gary Bradski,Adrian Kaehler,松田晃一
- 出版社/メーカー: オライリージャパン
- 発売日: 2009/08/24
- メディア: 単行本(ソフトカバー)
- 購入: 17人 クリック: 272回
- この商品を含むブログ (35件) を見る
- 作者: 小枝正直,上田悦子,中村恭之
- 出版社/メーカー: 講談社
- 発売日: 2014/07/18
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
方法
AirTapしたときに,次の動作を行うプログラムを作成します.
- スクリーンショット撮影.
- HololensのCursorのワールド座標を取得し,OpenCV画像座標に変換.
- OpenCVでスクリーンショットに点を描画.
- その描画座標を再度,ワールド座標に変換し,空間にCubeを設置.
つまり,ワールド座標→画像座標→ワールド座標の変換を行い,スクリーンショットの点とワールド座標のCubeが同じ場所に設置されればOKです.
コード
今回作成したコードはこちらにあります.
なお,HololensToolkit for UnityとOpenCVForUnity(有償)が必要です.
ポイント
変換API
ワールド座標系から画像座標系へ
画像座標系すなわちビューポート座標系の変換にはCamera.WorldToViewportPointを使います.この関数は正規化された値が返ってくること,Y軸の向きが逆(上方向)であることに注意しましょう. 従って正規化された値を画像サイズで乗算し,Y軸の向きを反転させる必要があります.
OpenCVForUnity.Point worldPointToMatPoint(Vector3 worldPosition, Mat image) { Vector3 viewportPoint = Camera.main.WorldToViewportPoint(worldPosition); OpenCVForUnity.Point drawPoint = new OpenCVForUnity.Point(viewportPoint.x * image.width(), (1.0 - viewportPoint.y) * image.height()); return drawPoint; }
画像座標系からワールド座標系へ
画像座標系からワールド座標系への変換には,まずビューポート座標系は正規化された値であるため,画像座標を正規化し,Camera.ViewportPointToRayでRayを取得し,ワールド座標系を取得します. また,ビューポート座標を求めるときはY軸の向きを反転させる必要があります.
bool matPointToWorldPoint(OpenCVForUnity.Point point, Mat image, out Vector3 worldPoint) { // 画像座標を正規化,Y軸の向きを反転し,ビューポート座標を求める. float viewportPointX = (float)(point.x / (double)image.width()); float viewportPointY = (float)(1.0 - point.y / (double)image.height()); Vector3 viewportPoint = new Vector3(viewportPointX, viewportPointY); // Rayを使って,Z軸も含んだワールド座標を求める. Ray ray = Camera.main.ViewportPointToRay(viewportPoint); RaycastHit hitInfo; if (Physics.Raycast(ray, out hitInfo)) { worldPoint = hitInfo.point; return true; } worldPoint = new Vector3(); return false; }
結果
う~ん,微妙にずれてるけど仕方ないのかぁ~.
その他
※ 生活感ありありの画像で申し訳ありませんm_m
Hololensで初音ミクに踊ってもらった. MMDモデルとモーションの取り込み
作ったもの
Hololensでベッドの上で初音ミクに「Perfect Human」を踊ってもらいました. HololensアプリケーションにMMDのモデルとモーションを取り込んで動いています.
【Hololens】初音ミクにPerfectHumanをベッドの上で踊ってもらった
使用したもの
Hololens Tookkit for Unityに加え, 以下のものを活用させていただきました.
モデル
モーション
MMDファイルのUnityへの取り込み
MMD for Unity
開発キット
- Unity 5.5 0p2
ポイント
MMD for Unityのインポートと修正
インポート
UnityでMMDモデルを利用するにはMMD4Mecanimを利用する方法もあるようですが,現行バージョンではUWPアプリケーションが非対応になっているようです. そこで,代わりにMikuMikuDance for Unityを利用します.公式サイトからzipファイルをダウンロードし,展開したディレクトリをそのままUnityのProjectにインポートします.
修正
インポートしたMMD for Unityに修正を加えます.2017年3月現在やや古い状態なので動くようにします・(ここではとりあえず動作することを目指して,機械的に修正を加えていきますが適切かどうかはまた別な話です.)
1 APIのアップデート
取り込んだMMD for Unityのディレクトリを右クリックしてRun API Updateをクリックします.
2 コードの修正
いくつかの行がエラーになるはずなので修正方法をピックアップします.
まず,以下のコードをコメントアウト.
// AnimationUtility.SetAnimationType(clip, animation_type);
GameObject.collider, GameObject.rigidbodyのようにcolliderやrigidbodyをプロパティとして参照している箇所でエラーが出ています.そこをGetComponent<>()メソッドを使って取得するようにします.
// 変更前 parent.rigidbody parent.collider // 変更後 parent.GetComponent<Rigidbody>(); parent.GetComponent<Collider>(); // ・・・みたいに
MMDモデルとモーションの取り込み
MMD for Unityの公式サイトのドキュメントに従ってモデルとモーションを取り込みます.
MikuMikuDance for Unity - Howto
モデルの取り込み時にAnimationTypeをGeneric Mecanimを選択します.
アニメーションの設定
MMD for Unityでモーションのアニメーションまで作ったらあとは登録します. AnimationControllerを作ってprefabにanimationに設定していきます.
RigidbodyやSpatialMappingの設定
- モデルにRigidbodyやColliderを設定.
- 空間認識のためにSpatialMappingを設定.
ただ,起動直後に重力を有効にしてしまうと空間認識が完了しておらず,モデルが奈落の底に落ちてしまうため,空間認識が完了してから重力を有効にします.
まとめ
MMDのモデルとモーションを取り込んでダンスするHololensアプリケーションを作りました.
参考文献
HololensでPhotoCapture画像をワールド座標の照準オブジェをもとにトリミング
作ったもの
前回,突貫ながらHololensのQRコードリーダを開発しました.
今回は,QRコードリーダに正方形の照準を加えました.
前回作ったQRコードリーダはAirTapするとHololensの視野全体をキャプチャし, QRコードの検出と解析を行うものでした.
視野角全体をキャプチャするため,画像のほとんどの部分は不要です. 特に,視野角にQRコードが2つ入っていると対応できなくなっていました.
そこで,Hololensに正方形の照準を設けて,キャプチャ下画像のうち照準内のみを切り出して処理するようにしました.
仕組み
MainCamera直下に照準としてQuad(以下QrSight)を設置します.
PhotoCaputureを行なったときに,QrSightの4頂点のワールド座標をキャプチャ画像の座標系に変換し,囲まれた範囲を切り出します.
ソースコード
特に今回は Assets/Scripts/PhotoInput.csを実装しました.
ポイント
QrSightの設置
MainCameraの直下にQuadオブジェクトを設置します.枠線のみの透過pngの画像をテクスチャとして割り当てます. これを照準として利用します.
- popsition: (x, y, z = 0,0,0)
- scale: (x, y, z = 0.2,0.2,0.2)
QrSightの4頂点の座標
AirTapで画像キャプチャされたときにQrSightの4頂点の座標を求め,座標変換を行います.
まずは,QrSightの座標(中央座標)からscaleを手掛かりに頂点をそれぞれ求めます. 今回はこのようにQuadの各頂点の位置を求めましたが,もっと良いやり方があればご教授願いです.
そして,Camera.main.WorldToScreenPointメソッドで ワールド座標系から投影座標系に変換を行います.なおこの時にRayCastした座標を使うべきか否かは悩みどころで悩んでます.
var position = QrSight.transform.position; var direction = QrSight.transform.forward; var scale = QrSight.transform.localScale; // ワールド座標系でのQR照準の座標を求めます. var leftTop = new Vector3( position.x - scale.x / 2, position.y + scale.y / 2, position.z); var rightTop = new Vector3( position.x + scale.x / 2, position.y + scale.y / 2, position.z); var rightBottom = new Vector3( position.x + scale.x / 2, position.y - scale.y / 2, position.z); var leftBottom = new Vector3( position.x - scale.x / 2, position.y - scale.y / 2, position.z); #if false /* Rayを使うかどうかが悩みどころ;;*/ RaycastHit leftTopHit, rightTopHit, leftBottomHit, rightBottomHit; Physics.Raycast(leftTop, direction, out leftTopHit); Physics.Raycast(rightTop, direction, out rightTopHit); Physics.Raycast(leftBottom, direction, out leftBottomHit); Physics.Raycast(rightBottom, direction, out rightBottomHit); // ワールド座標系を投影座標系に変換 var leftTopScreen = Camera.main.WorldToScreenPoint(leftTopHit.point); var rightTopScreen = Camera.main.WorldToScreenPoint(rightTopHit.point); var leftBottomScreen = Camera.main.WorldToScreenPoint(leftBottomHit.point); var rightBottomScreen = Camera.main.WorldToScreenPoint(rightBottomHit.point); #else // ワールド座標系を投影座標系に変換 var leftTopScreen = Camera.main.WorldToScreenPoint(leftTop); var rightTopScreen = Camera.main.WorldToScreenPoint(rightTop); var leftBottomScreen = Camera.main.WorldToScreenPoint(leftBottom); var rightBottomScreen = Camera.main.WorldToScreenPoint(rightBottom); #endif
これで投影座標系に変換できたため画像上のピクセルが求まったかと思いきやそうではなくて,キャプチャ画像の座標に変換する必要がありそうでした. 次のように,各頂点の座標をいったん正規化してCameraParametersの解像度を使って変換します.
// 投影座標系を,PhotoCaptureが撮影する画像上での座標に変換 int leftSide = (int)(leftTopScreen.x / (float)Camera.main.pixelWidth * cameraParameters.cameraResolutionWidth); int rightSide = (int)(rightTopScreen.x / (float)Camera.main.pixelWidth * cameraParameters.cameraResolutionWidth); int bottomSide = (int)(leftBottomScreen.y / (float)Camera.main.pixelHeight * cameraParameters.cameraResolutionHeight); int topSide = (int)(leftTopScreen.y / (float)Camera.main.pixelHeight * cameraParameters.cameraResolutionHeight);
これで,キャプチャ画像座標系でのQrSightの頂点座標が求まりました.ただ,ズレも目立つので調整する必要ありそうです(;‘∀’)
画像の変換・トリミング
キャプチャ画像座標系でのQrSightの頂点座標をもとに,画像を抜き出します. ここはOpenCVを使えばよいと思いますが今回はゴリゴリ書いてみました.
byte[] dst = new byte[src.Count]; for (int y = 0; y < cameraParameters.cameraResolutionHeight; ++y) { for (int x = 0; x < cameraParameters.cameraResolutionWidth; ++x) { int px = (y * cameraParameters.cameraResolutionWidth + x) * stride; if (x >= leftSide && x <= rightSide && y >= bottomSide && y <= topSide) { for (int i = 0; i < stride; ++i) { dst[px + i] = src[px + i]; } } else { } } } // *一部,ブログ記事用に本体コードに変更を加えています.
まとめ
- 前回開発したHololens Qrコードリーダに照準を付けてみました.
- ワールド座標系をHololensのPhotoCaptureによるキャプチャ画像の座標系に変換しました.(間違ってるかも)
- 変換した座標をもとにキャプチャ画像をトリミングしました.
HololensでQRコードリーダを作ってみた.
作ったもの
AirTapするとQRコードを読み込んで,その内容を空間に表示するHololensアプリケーションです.
仕組み
QRコードの検出とデコードにZXing.Netを利用しました.
AirTapするとHololensのカメラの画像をZXingに入力して,デコードを行います. デコードで得られたQRコードの内容のカーソルの座標に表示します. カーソルの座標を3次元で得るためにSpatialMappingはあらかじめ行っています.
(まぁ,できあいの物をつなげただけと言われればそれまでです.)
ソースコード
今回開発したものはこちらで公開しています.
ポイント
WebCamとSpatialMappingを許可
HolotoolKit-Unityをインポート後,WebCamとSpatialMappingを許可します.
HololensのWebCamは2017年2月23日現在,UnityEditor上でのデバッグには非対応のようです. 実際,UnityEditor上でWebCamを使用すると開発PCに接続しているWebCamの映像が入力されました. (これはこれでデバッグとして使えるかもしれませんが.)
インポートするZXingについて
HololensはUWPアプリケーションであるため,ZXingに含まれるzxing.winmdをプラグインとしてインポートします. インポート後,WSAPlayerでのみ使用するように設定されていることを確認します.
UnityEditorは非対応のため,プリプロセッサで分岐します.
public string Decode(byte[] src, int width, int height) { #if !UNITY_EDITOR Debug.Log("qr decoding..."); ZXing.IBarcodeReader reader = new ZXing.BarcodeReader(); var res = reader.Decode(src, width, height, ZXing.BitmapFormat.BGRA32); if (res == null) { return null; } return res.Text; #else return "editor debugging..."; #endif }
.Net Framework,.Net Core, UWP, Xamarinなど最近いろいろありますが,この辺りを知っている人は難なくできそうでうですが,自分にとってはつまづきポイントだったため,記述しておきます;;
WebCamで撮影
Hololensで見ている景色を撮影する方法は Locatable camera in Unityで解説されています.
また,CameraParameterのhologramOpacityプロパティの値を0にすることでホログラムが映らなくなるはずなので,今回は0にします.
c.hologramOpacity = 0;
該当コード: HololensQrCodeReader/PhotoInput.cs at master · hiroyky/HololensQrCodeReader · GitHub
QRコードのデコード
QRコードの検出とデコードはもっぱらZXingのライブラリ任せです. byte配列にして渡しています.
該当コード: HololensQrCodeReader/QrDecoder.cs at master · hiroyky/HololensQrCodeReader · GitHub
QRコードの内容表示
QRコードを無事デコードできたら,その内容をTextMeshで表示します。 TextMeshの表示位置はカーソルの位置です。
まとめ
突貫で簡易ながらHololensでQRコードリーダを作ってみました.
Hololensを購入しました.
Hololensを購入しました. 操作に慣れるところからです笑
Hololensがよやく届きました(^▽^)/ pic.twitter.com/8zeqG939O3
— ひよこやま (@hiroyky) 2017年1月19日
MQTTでServer(Broker)も設置してRaspberry Pi 3とLinux機を通信させてみた。
概要
先日、RaspberryPi3にGroveの温湿度センサを取り付けて温湿度をREST APIとして提供する装置を作ってみました。
REST APIでも良いのですが、今回はMQTTというプロトコルを使って温度・湿度を送受信してみました。 MQTTについてはMQTT as a Service sango MQTTについて詳しく知る(外部)が参考になると思います。
目標
- Raspberry PIをPublisherとし、温湿度を送信すする。
- Linux上に構築したMQTT Server(Broker)を構築する。
- 別のLinuxマシンが温湿度を受信する。
RaspberryPiが送信した温湿度を、MQTT Serverを経由して別のLinuxマシンが受信します。
Raspberry Piではじめるおうちハック ~ラズパイとIoTでつくる未来の住まい~
- 作者: 大和田茂,川上和義,小菅昌克,.
- 出版社/メーカー: マイナビ出版
- 発売日: 2016/10/31
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
やってみた
MQTT Serverのセットアップ
まっ更なUbuntuにmosquittoをインストール。各OSごとの導入方法はDownloads | Mosquittoで紹介されています。 Ubuntu16.04では標準のパッケージリストにすでに含まれているようです。最新のパッケージが欲しい場合はリポジトリを追加する必要がありそうですが、このまま行きます。
$ sudo apt-get install mosquitto $ sudo service mosquitto status sudo: unable to resolve host mqtt-broker ● mosquitto.service - LSB: mosquitto MQTT v3.1 message broker Loaded: loaded (/etc/init.d/mosquitto; bad; vendor preset: enabled) Active: active (running) since Mon 2017-01-02 17:39:05 UTC; 19h ago ・・・
MQTTのおためし
MQTTのSubscriverをセットアップ
こちらもまっ更なUbuntuにインストール。mosquitto_clients
をインストールします。
$ sudo apt-get install mosquitto-clients
mosquitto_sub
で起動します。とりあえずトピック名はsample
でいきます。
$ mosquitto_sub -d -t sample -h <mosuqittoサーバのアドレス> Client mosqsub/2193-mqtt-test2 sending CONNECT Client mosqsub/2193-mqtt-test2 received CONNACK Client mosqsub/2193-mqtt-test2 sending SUBSCRIBE (Mid: 1, Topic: sample, QoS: 0) Client mosqsub/2193-mqtt-test2 received SUBACK Subscribed (mid: 1): 0 Client mosqsub/2193-mqtt-test2 received PUBLISH (d0, q0, r0, m0, 'sample', ... (5 bytes))
MQTTのPublisherをセットアップ
温湿度センサのRaspberryPi3にインストール。mosquitto_clients
をインストールします。
$ sudo apt-get install mosquitto-clients
テキトーなメッセージを送ってみます。
$ mosquitto_pub -d -t sample -m "Hello" -h <mosquittoサーバのアドレス>
Subscriberの方にメッセージのHello
が標準出力されました。
MQTTのPublisherをPythonで実装
pythonインターフェイスの導入
ありがたいことにmosquittoのpythonインターフェイスが用意されているので導入します。
$ sudo pip install paho-mqtt
まぁこんな感じかな。grovepiからセンサの値を取得して、そのままMQTTで送信。 コード全体はこちらのリポジトリ このスクリプトをcronあたりで定期実行すればとりあえずOKと。
#!/usr/bin/python2.7 # -*- coding:utf-8 -*- import paho.mqtt.publish import grovepi def parse_args(): ・・・ def sensing(): (temperature, humidity) = grovepi.dht(8, 0) return (temperature, humidity) def publish(hostname, data): print data paho.mqtt.publish.multiple([data], hostname=hostname) if __name__ == '__main__': args = parse_args() (temperature, humidity) = sensing() data = { 'topic': args.topic, 'payload':str({'temperature':temperature, 'humidity':humidity}) } publish(args.hostname, data)
参考文献
Ubuntuでnvidiaドライバをインストールしたらログインループにorz..
概要
Ubuntu 16.04 LTSにnvidiaドライバをインストールしたらGUIでのログインができなくなってしまいました。 ログイン画面が表示されてパスワードを入力してログインを試みても、一瞬画面が黒くなってまた戻ってしまいます。
似た事例
同じ現象に悩まされた人はいるようです。そして解決方法もマチマチな感じ。
ちなみにこちらは動画で症状と解決方法を提案しています。ただ、自分はこの方法を試しても改善しませんでした。しかしながら、コストは大きくなのでとりあえず試してみるのがいいと思います。
How to fix ubuntu login loop [ quick tutorial ]
解決方法
自分の成功した解決策は次のaskubuntuに投稿された1つ目の投稿です。
ctrl + alt + f1を押してCUIでログイン。
$ sudo ubuntu-drivers list $ sudo ubuntu-drivers autoinstall $ sudo reboot
これでとりえあず良くなりました。
機種
;; (なんかNAVERまとめ みたいなエントリになってしまった。。。)
CUDA C プロフェッショナル プログラミング (impress top gear)
- 作者: John Cheng,Max Grossman,Ty McKercher,森野慎也,株式会社クイープ
- 出版社/メーカー: インプレス
- 発売日: 2015/09/24
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (2件) を見る
GPU並列図形処理入門 ~CUDA・OpenGLの導入と活用 (Software Design plus)
- 作者: 乾正知
- 出版社/メーカー: 技術評論社
- 発売日: 2014/02/18
- メディア: 大型本
- この商品を含むブログ (1件) を見る
- 作者: 岡田賢治,小山田耕二
- 出版社/メーカー: 秀和システム
- 発売日: 2010/03/25
- メディア: 単行本
- 購入: 1人 クリック: 53回
- この商品を含むブログ (7件) を見る