catalinaの備忘録

ソフトウェアやハードウェアの備忘録。後で逆引きできるように。

Windowsストアアプリを作ってみる

Windowsストアアプリを作ってみました。かたりぃなです。 HoloLensが目標ではあるのですが、Windowsストアアプリの作り方もわからないまま買うのはただのギャンブルなので、まずはかんたんなアプリを作ってみて、慣れてからHololens用に移る算段です。

アプリを作る動機

MagicTheGatheringというカードゲームの遊び方の一つにプレインチェイスというのがあります。 これは次元カードというものを使うのですが、悲しいことに英語版しか発売されていません。 頑張れば読めなくはないですが、わからない単語が出てきたりするとスマホでネットを調べることになり、ゲームが中断されてしまいます。というわけで ゲームが中断されてしまうと興ざめです。

何を作ったの?

できることは次のとおりです。

  • MTGのプレインチェイスの次元カードを一覧で見れる(日本語/英語ともに)
  • カードの詳細をすぐに見れる

アプリとしてはとてもシンプルです。

技術的には

  • アプリが起動されたら自動的にMTG-WikiAPIを叩いて、次元カードの一覧を拾ってくる
  • 次元カード一覧をリストで表示できる
  • ユーザーはカード一覧から詳細を見たいものを選択できる
  • 選択されたカードの詳細情報をMTG-WikiAPIでとってくる
  • カードの詳細を表示できる
  • 詳細ページを見終わったらリスト画面に戻れる
  • リストアップされたカード中から検索できる
  • 一度見たMTG-Wikiの情報はアプリのローカルストレージにキャッシュする

です。 とても簡単そうですが、「まずは作ってみる」目標としては丁度いいレベルだと思います。 言語はc++です(HoloLensでDirectX叩きたいので、その準備)。 では開発者アカウントの取得から順にポイントを順に整理していきます。

Microsoft開発者アカウントを取得してVisualStudioに設定する

既にとってあるので省略します。Microsoftの開発者向けサイトから適当に登録します。 登録するためにはMicrosoftアカウントが必要です。クレジットカードを登録して千円くらい支払えば完了です。 この登録でMicrosoftアカウントが開発者アカウントとして登録されるみたいです。

登録後にVisualStudioを起動するとMicrosoftアカウント設定しましょうとか出るので、開発者登録したMicrosoftアカウントを設定するだけです。

Windows Phoneの開発者向け機能のロック解除

まず開発用マシンとWindows PhoneをUSB接続しておきます。今回使用するのはFreetelのKATANA2です。

ドライバインストールが終わってエクスプローラ起動してストレージが見える状態になってればOKでした。 WindowsPhoneが認識されたら「Windows Phone Developer Registration」というツールを起動します。 Windows10SDKに含まれているので、インストール済みならCortanaさんに聞くだけで場所を教えてくれます。 ロック解除が終わると「Windows Phone Developer Registration」はこんな画面になります。 f:id:Catalina1344:20170124211021p:plain

Windows Phone側での設定

これはWindows Phone側の作業です。

開発者向け機能がアンロックされたので、次のぺージを参考にしつつ設定していきます。 Enable your device for development 某Androナントカのときも似たようなことしてた気がします

適当なプロジェクトをビルド・デプロイする

再びPC側での作業です。Visual StudioのテンプレートでUWPの適当なプロジェクトを選択してARM向けビルドするだけです。 Release設定じゃなきゃダメかもなーと思っていましたが、Debugプロジェクトもデプロイできました。

VisualStudioの画面としてはここを設定します。 f:id:Catalina1344:20170124211332p:plain

ちなみに、デプロイ時にデバイスがアクティブである必要があります。(ロック画面や画面が消灯している状態ではダメ) まあ失敗したらエラーメッセージ出るので、その都度直せばいいかと。

デバッガの起動が重い

シンボル情報の読み込みに時間かかりまくります。単純なUWPアプリをデバイスに書き込んでデバッガ起動するまで数分くらいです。 初回だけなので、我慢しましょう。我慢できないときはCtrl+F5でデバッガ接続なしでデプロイです。

ここまで一日かからずにできました。簡単ですね。 やっとプログラミングです。 プログラミングは年明けから始めたので、一か月弱でここまでできたぞと。

プログラミングで躓いたポイント

たくさんあってもう忘れかけていますが、せっかく新鮮な体験をしたので思い出せる限り記録につづりたいと思います。

アプリのマニフェスト

なぜかカメラが起動できないとか、なぜかWebアクセスできないといった時に真っ先に疑ったほうがよいところです。 プロジェクト中のPackage.appmanifestを開いて適切なアクセス権限を設定しましょう。

C++/cxのハット(^)記号

Windowsランタイム側で寿命管理してくれる(マネージド)なポインタのようです。 感覚的にはstd::shared_ptrみたいなもんかと思ってます。インスタンス化するときはnewではなくref newで。std::make_sharedと同じノリですね。 自前のc++クラスをマネージドにしたいとき(データバインディング対象にしたいとか)はref class class_name sealedでクラス定義を書くようです。

文字列処理

Platform::Stringは基本的な機能しか提供していないので、状況によっては自前で編集する必要があります。 とっても面倒ですね。 新しいAPI覚えるの面倒なのでC++の世界に持ってきて解決することにします。 UWP(Platform::String^)の文字列はstd::wstringにそのまま変換できます。

ただ、C++の世界そのままとはいえ文字列がwstringです。 単純な文字列リテラルとの比較をする場合でもwstring同志でやる必要あります。 Lつければこのあたりの面倒みてくれるらしいです。ラクですね。

// テキストボックスの文字列をc++の文字列にする
std::wstring    str(textbox->Text->Data() );

// C++の世界でやりたいことをやる
// たとえば検索
auto pos = str.find(L"Search"); // Lつけるのを忘れずに

// uwpに戻す
this->textBox->Text = ref new Platform::String(str.c_str() );

create_tasks

バイル端末もマルチコア時代なので、非同期処理を書きましょう。ユーザースレッドを止めないのが基本です。 毎回スレッド作るのってコスト高い気がしますが、ランタイムがスレッドプールを持っていて必要に応じて割り当ててくれるらしいので、気にせずcreate_taskしましょう。 ちなみにこのライブラリはMicrosoft PPL(並列パターンライブラリ)というらしいです。

非同期の例といえばWebAPIですね。とりあえず適当なURIを叩いてみましょう。

 create_task(client->GetAsync(request_uri)).then([](HttpResponseMessage ^ response) {
        response->EnsureSuccessStatusCode();
        return response->Content->ReadAsStringAsync();
    }).then([title, this](Platform::String ^ response_string) {
    });

サンプルコードコピペすれば動くのですが、ここが一番手こずったポイントです。一週間くらい悩みました。 細かく分割して整理します。分けて考えるは基本ですね。

create_taskによってタスク生成する

文法としてはこの部分です

create_task(client->GetAsync(request_uri))

この例ではhttpclientのGetASyncが返す関数オブジェクトを渡すことになりますが、ここには関数ポインタなら何でも渡せます。ラムダでもいいわけです。 作ったタスクは即時実行されるわけではなく、スレッドプールから割り当てられたスレッドを後で実行されます。(UIスレッドを止めない)

ただ、実行が終ったあとで何かしたいですよね?UIの更新だったり、ファイル保存だったり。 このままでは後続の処理に困ります。そこで登場するのがthenです。

.then

新しい言語仕様か?と思ってしまいますが、そんなことはありませんでした。create_taskで生成されたtaskクラスのメソッドthenです。 thenにも関数オブジェクトを渡すことができます。 こいつに渡す関数が先ほど登場した「後から実行したい何か」です。 thenが返すのもtaskなので、こうやって非同期操作を数珠繋ぎにしていくのがスタイルのようです。

ラムダ

c++にもラムダが実装されたので、ここではラムダをthenに渡しています。 昔ながらの非同期操作だとイベントハンドラごとに関数をわけて記述するので全体が見通せなくなりがちでした。 こうやってラムダで記述すれば全体が見通せるのでスッキリしますね

ちなみにMicrosoftのサンプルコードではこういう記述を見かけることがあります。

[](HttpResponseMessage ^ response) -> typename {}

ポインタからの何かを参照しているのかと勘違いしていましたが、どうやらこれはc++の機能の戻り値の型を後置する記法らしいです。

戻り値の型を後置する関数宣言構文 - cpprefjp C++日本語リファレンス

今回は直接関係ないので省略しますが、テンプレートを使うと戻り値の型を調べるのが大変になってくるので、auto宣言しておいてこういう記述を使うと幸せになれそうです。

非同期操作中に変数の寿命が尽きることがある

そもそも設計が間違っているのかもしれませんが、これに悩まされました。 非同期操作の関数にマネージドポインタを渡すと、変数の参照カウントが増えるので非同期操作が終わるまで安心して使えて、解放も自動的に行えるのですが、 taskを数珠繋ぎにしてアダプティブなクラスをかぶせていくようなスタイルだと、途中で変数の寿命が尽きてしまうことがあります。 実際に問題があったコードで絆創膏的な対処しかしていませんが、こういうのです。

         readtask.then([this](Streams::IRandomAccessStream ^ stream)
            {
                return BitmapDecoder::CreateAsync(stream);

            }).then([this](BitmapDecoder ^ decoder) -> IAsyncOperation<SoftwareBitmap^>^
            {
                return decoder->GetSoftwareBitmapAsync();

            }).then([this](SoftwareBitmap^ bitmap) -> IAsyncOperation<OcrResult^>^
            {
                this->tmp_bmp = bitmap;    // ここで保持しておかないと、OCR実行中にbitmapが消失してエラーになってしまう。
                                        // どうしてこうなるか理由は不明。
                return this->ocr->RecognizeAsync(bitmap);

            }).then([this](OcrResult^ result)
            {
                // 空白文字を除去する
                std::wstring    str(result->Text->Data() );
                std::wstring::size_type pos;
                do {
                    std::wstring target(L" ");
                    pos = str.find(target);
                    if (pos != std::wstring::npos) {
                        str.erase(pos, target.length());
                    }
                } while (pos != std::wstring::npos);

                // 文字認識結果を取り出す
                this->textBox->Text = ref new Platform::String(str.c_str() );
            });

寿命が尽きるということまでは解っているので、また気が向いたときに追ってみます。

コンパイル時データバインディング

まずデータバインディングとはUIとアプリケーションロジックの分離(コードビハインドというらしい)をスマートに行うものです。 WPFの頃からデータバインディング自体はありましたが、いつの頃からかコンパイル時データバインディングが実装されたようです。 リストビューを作る参考にさせてもらいました。

UWPアプリでコンパイル時バインド(x:Bind)を使ってListViewに値を表示する - Qiita

上記はC#での例なので、C++/cxで同じことをやろうとしたところで少し手こずりました。 ポイントは

  • ネイティブ型はデータバインディングバインディングソースに(というかプロパティとして見せること自体が)できない
  • MVVMのVMとして見せるクラスも同様
  • プロパティのgetter/setterはUWPランタイムのクラスなら書かなくてもいい

というわけで単純に文字列をlistviewで表示するコードはこうなりました。

まずXAML

<ListView x:Name="listView" ItemsSource="{x:Bind Path=card_list}">
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="local:data_element">
            <StackPanel>
                <TextBlock Text="{x:Bind card_name}"></TextBlock>
            </StackPanel>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

次にプロパティを宣言するクラス

 // viewクラスから見せるプロパティ
    public:
        property Windows::Foundation::Collections::IVector< data_element^> ^ card_list;

最後にプロパティをインスタンス化するところ

        // OnNavigatedToあたりでリストをインスタンス化する
    card_list = ref new Platform::Collections::Vector< data_element^>();

あとはcard_listに対してよくある操作(要素の追加/削除/更新)をすれば、自動的にUIに反映されます。

プラットフォーム固有の型を覚えるのが面倒

型情報はC++使いとしてはとても大事ですが、プラットフォーム固有の型とか覚えるの(というかマニュアル調べるの)面倒です。 できるだけautoで済ませましょう。 forはどうするかと考えてしまいますが、コレクションに対する操作なら範囲指定forで充分です。 状況によっては型情報(Platform::Object型を返してくるとか)が必要になりますが、そういうとき以外はautoでいきます。

たとえばアプリのローカルストレージに保存してあるcompositeを読む処理はこうなります。 型がわからなくなりそうですが、このくらいならVisualStudioのIntelisenceも動きますし、マウスカーソルでポイントすると型情報を見せてくれます。autoさん(というかVisualStudio)すごい。

 auto localSettings = ApplicationData::Current->LocalSettings;
    auto composite = safe_cast<ApplicationDataCompositeValue^>(localSettings->Values->Lookup("cards"));

    if (composite) {
        // 二回目以降の起動の場合、既にあるカードリストを取り込む
        for (auto value : composite) {
            auto cardname = safe_cast<String ^>(value->Value);
            auto element = ref new data_element(cardname);
            card_list->Append( element );
        }
    }

まとめ

長くなりましたが、書初めプログラムとしては非常に楽しいものでしたし勉強になりました。 次はDirectX11を叩いてみて、納得がいくところまでできたらHoloLens購入したいと思います。

WindowsストアアプリでOpenCVを使う

Hololensが楽しそうなのでアプリ作れないか試行錯誤しています。かたりぃなです。 開発者向けの販売とはいえ購入には踏ん切りがつかないので、何かつくれるようになったら購入したいと思っています。 というわけでWindowsストアアプリ向けに画像処理のアプリを作る基盤づくりです。 今回はOpenCVWindowsストアアプリ向け(UWP)にポーティングしてみます。

とりあえず動かしたい

手っ取り早くHololensエミュレータOpenCV動かすにはこちらがお勧め。 HololensのCPU向けにビルドされたパッケージ一式がすでにNuGetにありました。 OpenCV for Hololens (UWP/C++) - NuGet Must Haves

c++/cxのUWPプロジェクトを作って適当なopencvAPIを叩いてみたところ動作はしました。 ストアアプリ認定キットも通るので問題なく使えそうです。 このNuGetパッケージのcv::getBuildInformation()を参考にすればOpenCVソースコードからビルドするのに使えるかもしれませんね。 ただ配布されているパッケージはRelease版のライブラリのみなので、デバッグに難ありです。

公式で何かやってるんじゃない?

MSもOpenCVをforkしていました。 Easily build OpenCV-powered apps for Windows Store! - MS Open Tech ブラウザからパっと見た限り特別何か手を入れてる様子は見えません。 git cloneして中身を調べてみた内容をメモします。

既に試した人がいるので、参考にさせてもらいました。

Windows 10ユニバーサルアプリ(Universal Windows Application)でOpenCVを使う(その2) - embeddedなブログ

とりあえず中身みてみる

windowsでもpowershellが入ってコマンドライン操作も昔より楽になりました。 コマンド体系が独特ですが、まあ慣れの問題でしょう。 とりあえず中身を見てみます。

git clone https://github.com/Microsoft/opencv
git branch -a
  master
  remotes/origin/HEAD -> origin/master
  remotes/origin/contrib-enable
  remotes/origin/highgui
  remotes/origin/master
  remotes/origin/test
  remotes/origin/test-attach
  remotes/origin/test-vs2015
  remotes/origin/test_gtest_upd
  remotes/origin/update_py_script
  remotes/origin/videoio-old-sample
  remotes/origin/vs2015-samples
  remotes/origin/vs2015-samples-ARM
  remotes/origin/winrt

vs-2015-samplesってのが気になりますね。

git checkout -b vs2015-samples remotes/origin/vs2015-samples

このブランチのREADME.mdを見ると、OpenCVをUWP向けにビルドする手順が記載されています。 Short wayとLong wayがありますが、まずはShort wayを試してみます。 準備済みのソリューションファイルを開いてビルドするだけです。 何が違うのかちょっと気になったので、簡単にですがOpenCV本家と比較してみました。

  1. UWP環境でビルドするコンパイルオプションの追加(/ZWなど)
  2. vcxprojファイルにWindowsストアアプリ用の設定を追記

まず1ですが、OpenCVをデフォルト設定でビルドすると、従来のWindowsアプリ向けのDLLとインポートライブラリが生成されます。 これはWindowsストアアプリで配布できないらしく、Windowsストアアプリ用にはUWPとしてDLLを生成する必要があります。 このためのコンパイルオプションが/ZWらしいです。 方法: ユニバーサル Windows アプリで既存の C++ コードを使用する

次に2ですが、単純にOpenCV本家のビルドオプションに/ZWを付けるだけではダメでした。 UWPのvcxprojファイルをテキストエディタで開いてみるとに次の内容が記載されています。

  • ApplicationType
  • WindowsTargetPlatformVersion
  • WindowsTargetPlatformMinVersion

など。 アプリ配布のターゲットを示すもののようで、単純にOpenCVのビルドプロジェクトを/ZWを付けてcmakeした場合、このあたりの記述が行われません。 このためコンパイルエラーになってしまいます。 MS曰く適切に記述しなさいとのこと。 ユニバーサル Windows プラットフォームへの移植 (C++)

OpenCVをUWP(Windowsストアアプリ向け)にビルドする

ソリューションファイルのパスはMicrosoft/OpenCVのルートディレクトリから見て

  • vs2015\WS\10.0\ARM
  • vs2015\WS\10.0\x64
  • vs2015\WS\10.0\x86

の3つが各CPUアーキテクチャ向けのものです。 いずれのディレクトリにもOpenCV.slnがあるのでこれをビルドすればOKです。 READMEに書いてあるように環境変数OCV2015_ROOTにopencvのルートディレクトリを設定してからビルドです。 この環境変数がないとOpenCVが参照しているライブラリ(zlibとか)を探せないのでエラーになります。 Windowsストアアプリとして組み込むにはRelease版が必要ですが、開発中はデバッグ版も欲しいので、Debug,Release両方をビルドするのを忘れずに。

ビルドで生成されるもの

ビルドが終わるとソリューションファイルの配置されているディレクトリに次のものが生成されます。

  • /include
  • /lib/Debug
  • /lib/Release
  • /bin/Debug
  • /bin/Release

それぞれの役割は名前から想像できるとおり、 includeはインクルードファイル群の置き場です。 libはDLLをアプリケーションが利用するためのインポートライブラリ一式です。 binはDLL本体です。

生成されたOpenCVWindowsストアアプリで使う

ここからはストアアプリのVisualStudioプロジェクトで設定をしていきます。 ソリューションエクスプローラからプロジェクトのプロパティを開き、「C/C++ -> 追加のインクルードディレクトリ」で設定します。 libは同様に「リンカー -> 追加のライブラリディレクトリ」で設定します。 リンクするライブラリファイルはリンカの入力で指定しても良いのですが、一個一個記載していくとミスの元になるのでスクリプト化しました。 プロジェクト作るたびに設定するのも面倒ですし。 PowerShell書くのは初めてですがなかなか楽しめました。

gist.github.com

こいつを実行すると、生成されたインポートライブラリを取り込むためのC++ソースコードが生成できます。 各アーキテクチャ(x86/x64/ARM)と各ビルド(Debug/Release)libディレクトリにあるライブラリを全てリンクするようなC++ソースコードが生成できます。 こんな感じ。

// genereted powershell script from gen_opencl_linkheader.ps1
#include "pch.h"
#ifdef _DEBUG
#ifdef _M_X64
#pragma comment(lib, "C:\\myproject\\ms-opencv\\opencv\\vs2015\\WS\\10.0\\x64\\lib\\Debug\\opencv_calib3d300d.lib" )
#pragma comment(lib, "C:\\myproject\\ms-opencv\\opencv\\vs2015\\WS\\10.0\\x64\\lib\\Debug\\opencv_core300d.lib" )
#pragma comment(lib, "C:\\myproject\\ms-opencv\\opencv\\vs2015\\WS\\10.0\\x64\\lib\\Debug\\opencv_features2d300d.lib" )
// 略

これでdebug版/release版の切り替え、ターゲットCPUごとにリンクライブラリを設定しなおさずに済みます。

最後にOpenCVのDLLをVisualStudioプロジェクトに組み込んで、コンテンツとしてファイルを含めるよう設定します。 (ソリューションエクスプローラにDLLを追加してからそのDLLを右クリック、プロパティ->全般->コンテンツ->YES)

Windowsアプリ認定キットを通す

VisualStudioのプロジェクトを右クリックして、「ストア -> アプリパッケージの作成」でパッケージを作成します。 配布者名・予約済みアプリ名をプロジェクトに関連付けて、アプリパッケージのターゲットアーキテクチャを指定してパッケージ化すれば完了です。 アプリ認定キットで少しトラブったので忘れないようにメモします。

OpenCVのDLLがストアアプリとして利用不可のAPIを呼び出している

Debugビルドではストアアプリの要件を満たせないのが原因です。パッケージに含めたDLLがリリース版ビルドされているか確認しましょう。

OpenCV3.1のデフォルト設定では、生成されたDLLのファイル名でdebug/releaseどちらかを確認できます。 名前付けのルールは"モジュール名"+"バージョン番号(310とか300とか)"+"d"+".dll"になっています。 デバッグ版の場合はdが付加されて、リリース版では付加されません。 つまりバージョン番号と拡張子の間に"d"が付いているDLLを含めたパッケージをアプリ認定キットに通すとこのエラーで引っかかります。

イメージが既定です

ちょっと日本語が不自由なエラーメッセージですが、アプリ提出するならアイコンとかをデフォルト(=既定)のままじゃダメだということです。 内容をよく読むと、アプリのリソース(画像とかアイコンとか)がデフォルト設定のままだからダメだと言っているようです。 試しにアイコンを差し替えるとエラーメッセージが減ったので一通り設定してあげればよさそうです。

感想

色々と詰まりましたが、これでアプリ開発の環境は整いました。 次からは色々と試してみたいと思います。 Windows上でもPowerShellのおかげで手順の自動化ができるので、もっといろいろ試してみたいところです。 では今回はこれくらいで。

ChainerのモデルデータをOpenCVで使うための準備

できれば一通りの目論見が達成してから投稿したかったのですが、まとまった時間をとれそうにないのでいったん投稿です。かたりぃなです。

何をしたいの?

ChainerなどのDeep-Learningフレームワークを使えば色々な学習モデルを構築できそうです。 次のステップとして、その学習したモデルを使ってアプリだったりサービスだったりに組み込むことを考えています。 このとき、サーバがあればPythonをインストールしてChainer走らせて完成になるわけですが、できればクライアント側で処理したいところです。 クライアント環境にPythonを導入してもいい(そういうアプリの配布方法もある)のですが、私自身Pythonはあんまり得意ではないので、妙なところで転びそうです。 できれば慣れたC++でやりたい。C++でchainerの学習済みモデルを取り込みたい

というわけで、今回のゴールは「C++でchainerの学習済みモデルを取り込む」です。 まずは「実現はできそう」というところは見えてきたので、ゴール手前までやります。

どうやるの?

方法ですが、まずOpenCVのdnnで学習済みモデルを取り込めないか考えました。 結論としては「CaffeとTorchはOpencv.dnnの関数を使えば済むけどchainerはインポータを書く必要がある」です。 インポータを書くためにはChainerの出力フォーマットを知る必要があります。chainerは現バージョンでは2種類提供されていて

でした。 numpyを使ってしまうとまたPythonの環境に戻されてしまうので、c++からOpenCVのhdfモジュールを使ってchainerが出力したhdf5を読み込む実験をしてみます。

使うライブラリ一覧

  • OpenCV3.1 dev
    • contrib
      • hdf5モジュール
      • dnnモジュール
  • libhdf

ビルドツール

ライブラリの簡単な説明

OpenCVは有名な画像処理ライブラリです。少し前まではltseezという会社がメンテナンスしていたのですが、Intelに買収されたらしい。 政治的な話はおいといて、構成です。 一般的な機能(枯れた技術)を含むものはopencv本家のリポジトリにあります。 これに対して最新技術であったり、特許なんかの理由で本家に含められないものはopencv-contribリポジトリに含められています。 というわけで、両方それぞれダウンロードしてきます。

libhdfは階層型のデータ構造を扱うためのフォーマットらしく、データサイエンスの分野でよく使われるらしいです。 このあたりにライブラリやツールがあるのでダウンロードしておきます。 The HDF Group - Information, Support, and Software

OpenCVはNuGetでインストールじゃだめなの?

NuGetだとうまくいきませんでした。 NuGetで提供されているものをためそうとしましたが、少し古いVisualStudioでビルドされているようで2015ではそのまま使うことはできなさそうです。(ディレクトリを覗いてみると2012,13用っぽいディレクトリができているのがわかります) このあたりのパッケージ定義をいじってツールセットもそれにあったものを選択してあげればいけそうな気はしますが、そうしてまで使うのは本末転倒(バージョン管理でラクできない)ので手元で全部バージョン管理します。 またNuGetでもOpenCV3.1まではリリースされているのですが、contribがどうなってるのかちょっと不安です。

cmakeの注意点

今回はver3.7を使います。 起動後にopencvやhdfのディレクトリを指定してconfigureしたときにコンパイラのバージョン問い合わせダイアログが表示されます。 ここでターゲット(x86/x64, visualstudioのバージョン)が固定されてしまいます。変更方法はわかりませんでした。 x86/64の切り替えだけなら以前はのcmakeで生成されたソリューションをvisual studioで開いてから切り替えられるようになっていた気がしたのですが。 まあ実験用としてはどちらかに固定しておけば問題ないです。 名前がわかりにくく表示されているので間違えないようにしましょう。visual studio 2015の64bitなら「VisualStudio 14 2015 Win64」です。

hdfライブラリのビルド

hdf5はさきほどのライブラリをビルドします。 ビルドツールはCmakeで。 cmakeの基本的な使い方はほかの親切なサイトに譲るとして、ここではトラブった内容と解決方法を書き留めます。

hdfライブラリのビルド後のイベントが失敗する

setlocalうんたらとかいうスクリプトがビルド後のイベントに設定されているのですが、インストールプロジェクトでインストールを試みるとここで失敗します。 原因としてはデフォルト設定ではインストール先が"c:\programfiles\HDF~"になっているためで、ここにアクセスするには管理者権限が必要です。

解決策をスマートな順に列挙します。

  1. cmakeのinstallprefix変数に適当なディレクトリを設定する
  2. visual studioを管理者モードで起動する

最後のはやっつけ仕事的ですね。 installprefix指定したところにincludeとlib,binと一通り吐き出してくれるので、これが正攻法だと思います。 configureしたらgenerateを忘れずに。

OpenCV

最後にOpenCVのビルドです。

  • opencv-contribを取りんでconfigure
  • hdfのパスを指定する
  • with-msmfをenable
  • with-python(2,3ともに外す)

msmfはMicrosoft Media Foundationの略です。レガシーになりつつあるDirect Showではなく今後はこちらが推奨とのこと。

with-pythonを外した理由は、chainerをインストールしたときにhdfライブラリがくっついていたらしく、有効にしたままだとこっちを参照してしまいました。 依存ライブラリのパスを変えてあげればいいのかもしれませんが、依存関係で面倒ごとにならないように最初から切っておきます。

OpenCVPythonから叩くことは当面は無いので。2.7系3.5系それぞれ外すのを忘れずに。

OpenCV-contrib

windows環境だとopencvのビルドで多数の警告が出ることがあります。 警告はW4819(文字コードunicode形式で保存してください)ですが、とりあえずvisual studioの警告抑制で4819を指定しておきます。

opencv,hdfを使うプロジェクトを作る

visual studioを起動して適当なプロジェクトを作ります。 win32コンソールでいいんじゃないでしょうか。

ライブラリの参照パスの設定

参照プロジェクトとしてopencvを指定すればいいんですが、もういい加減面倒になってきました。dllをコピーして済ませます。 ライブラリディレクトリとインクルードディレクトリの指定をして、インポートライブラリもすぐ使うものだけ指定します。 libhdfとopencv_coreがあればいいかと。

hdfを読む実験(途中)

libhdfは私が欲しい機能と違う気がしてきたので、コードの途中までです。(投げやり) ちなみにこのコードのままではcv::Matの型やサイズを指定していないので実行時エラーになります。

 const std::string  hdf5_file_path("testmodel.hdf");
    auto hdf5_inst = cv::hdf::open(hdf5_file_path);

    const std::string layer_name = "predictor/conv1";
    if (hdf5_inst->hlexists(layer_name) ) {
        auto mat = cv::Mat();
        hdf5_inst->dsread(mat, "predictor/conv1");
    }

何が違う?

このhdfライブラリはhlexistsで目的のノードが存在していることを確認してからdsreadすれば、目的のデータは読めそうです。 しかしこういったインターフェースは私が欲しいものではありません。

私が欲しい機能は 「データ構造が変わってもプログラムを変更せずに動作確認したい」 です。

対するhdfの機能は「データの階層構造は固定されたものである」という大前提があります。 これは「chainerで層を増やして学習したものをOpenCVで試したい」といったときにプログラムを書き換える手間が発生します。

hdfそのままだと扱いづらい。どうしよう?

hdfの構造なんてそういうものと言われればそれまでかもしれませんが、ちょっと納得いきません。 私が欲しいのはboost::ptreeみたいなパーサです。 libhdfのツールにdumphdfというのがあってxml出力できるようなので試してみましたが、xmlにするとファイルサイズが大変なことになってしまったのでちょっと工夫が必要そうです。 (以前作った3層のCNNで10MByte超)

感想と今後の展望

xmlとhdfのハイブリッド構成ができればいいのかもしれません。 xml側にネットの構造だけ記述されていて、実際のwとbはhdfから読んでくるとか。 色々工夫する余地はありそうです。 では今回はこれくらいで。

Chainerでcifar-10画像分類を試してみる

やっとchainerでCNNを動作させるところまで辿り着きました。かたりぃなです。

何がやりたいのか?

ARというかHololensのMRで現実世界のオブジェクトを識別して追加の情報をユーザーに提示できれば楽しいだろうなと思っています。 HololensのAPIを軽く見たところ、深度マップなどの面倒は見てくれるようですが、画像からの物体検出などは見当たりませんでした。 画像処理といえばOpenCVなんかが有名で、最近はdnnモジュールが追加されているのでこいつを使って公開されている学習済みモデルを取り込めば一般物体認識はできそうですが、学習済みモデルを持ってくるだけでは私のやりたいことに届かなさそうです。 手作業で前処理や特徴抽出を記述していくのもアリかもしれませんが、そういう試行錯誤に時間を費やすのは勿体ないと思います。 なら自前でモデルを作ってDeepLearningのフレームワークを使って学習させよう、そしてAR/MRでなんか面白いことやりたいなといったところです。 過去にChainer動かしてみたりもしてますし、ゲーム目的とはいえGPUもよさげなのが手に入りました。 さっそくやってみます。 今回のゴールはcifarデータセットをCNNで処理して一般物体認識を行うことです。

cifarデータセットとは

10種類のラベル付けされた画像のデータセットです。一般物体認識における分類問題と呼ばれるものです。 画像の情報として

  • RGBの3チャンネル
  • 32x32 pixel です。 これができれば私がやりたいことの実現に向けた足掛かりになりそうです。

データセットの中身を確認する

cifarデータセットpython-typeでダウンロードしてそこからデータを取り出します。 pythonはあまり慣れていないので調べることが多いです。 ネットの情報ではcPickleを使おうという情報が散見されますが、どうやらインストールしたpythonのバージョンにはcPickleが無いらしく、仕方ないのでpickleを使います。 シリアライズとデシリアライズを面倒見てくれるモジュールらしいです。 手元の環境ではエンコードタイプの問題でややこしいことになってしまいました。

# python3ではcPickleがなくなったらしいので、pickleを使う
import pickle;

# metadataがラベルデータ
# それ以外がデータセット
# データは32x32,3ch(RGB)の形式

# ただのバイト列としてエンコーディングされたものとして扱う
f = open('data_batch_1', 'rb')
train_data = pickle.load(f, encoding="bytes")

len(train_data)
# result : 4
train_data.keys()
# result : dict_keys([b'filenames', b'data', b'labels', b'batch_label'])
type(train_data[b'data'])
# result : <class 'numpy.ndarray'>
train_data[b'data'].shape
# result : (10000, 3072)
type(train_data[b'labels'])
# result : <class 'list'>
train_data[b'labels'][:10]
# result : [6, 9, 9, 4, 1, 1, 2, 7, 8, 3]
sample_image = train_data[b'data'][:100].reshape((10, 10, 3, 32, 32)).transpose((0, 3, 1, 4, 2)).reshape((320, 320, 3)) # 先頭100個をタイル状に並べ替える
Image.fromarray(sample_image).save('sample.png')
# result : outputfile "sample.png"

これでデータ構造はわかりました。何か目に見えるものがあると達成感ありますね。

CNNとは

畳み込み(2次元カーネル)を使ったニューラルネットワークです。 今回は動かすことが目的なので、細かいパラメータ調整はしていません。

こちらのページを参考にさせていただきました。

Chainerのtrainerを使ってCIFAR-10の分類に挑戦したかった - Qiita

詰まったポイント

ネットで調べてみましたが、少し前のバージョンのchainerのコードが多く見受けられました。 functionsetとforwardで順方向の計算グラフを定義されていたりしましたが、functionsetは非推奨となったようで、NN全体をchainとして定義するのが今の主流のようです。 手作業で書こうとしましたが、ゼロから書くのはまだ無理でした。 上記ページのコードを引用して動作確認しました。 pickleのところとか、学習結果の出力を少し試してみた程度の変更です。

全体のコード。

giste82cb557c1939de2f1bb45089637ea59

cpuのみで動かすと重くて全然進みませんが、GPUなら数十分で終わりました。これなら色々と試行錯誤できそうです。

今後の展望

以前に試したARの原理の延長で、物体ごとに異なるオブジェクトを表示してみたいですね。

ARの原理実験 - catalinaの備忘録

ただ、カードゲームなんかで全ての種類のカードを分類するのはちょっときつそうなので、適当な分類ラベルを選択して限定的に実装してみたいと思います。 またDeepLearningを使えば検出精度の向上も期待できるかもしれません。 DeepLearningで何ができるか、色々と試行錯誤してみましょう。 週末のまとまった時間でキリのいいところまでできたのでひと段落です。

では今回はこれくらいで。

UWPで音声認識APIを試してみる

画像の分類問題に少し飽きてきました。かたりぃなです。
機械学習を使った画像の分類方法を色々と試していますが、どれも私のやりたいことと少しずれている感じがしていて少し煮詰まった感があります。

音声認識に挑戦(ライブラリを叩くだけ)

画像や映像よりも先行しているイメージのある音声の識別ってどういう理論なのだろうと思いつつPRMLの後ろのほうの章をじっくり読んでいますが、まだまだ修行が足りないらしくよくわかりません。
時系列のデータ扱うのって色々面倒だなーと思い始めています。
そういえばWindowsの左下で「何でも聞いてください」と言ってる人がいますね。マイクアイコン付いてますし、試してみましょう。
Hololensでこれが使えれば遊びの幅が広がりそうなので購入するための言い訳に使えます。

Microsoft音声認識APIを試す

まずは自分のプログラムで試す前にCortanaさんに話しかけてみます。
思った以上にお利口さんで、動画サイト見ながらマイクに音声を入れてもしっかり識別してくれます。
ユーザー見えのふるまいとしてCortanaさんがやってくれることは次のようなことでした。

  • 音声を認識して文字列にする
  • 文字列をもとにアプリやブラウザを起動する

試した単語は次の4つです。最後のは意地悪テストの域に入るので、まあ充分実用的な精度で識別できています。
発音の識別だけでなく形態素解析までやってくれてるんでしょうか。なんかすごい。

  • 「ぺいんと」

  候補「ペイント」のアプリを表示後にペイントを起動

  • 「はいぱーぶいまねーじゃー」

  候補「Hyper v マネージャ」を表示後にブラウザで検索結果を表示

  • 「とらんぷし」

  候補「トランプ氏」を表示後にブラウザで「トランプ氏」の検索結果を表示

  • 「じぇんきんすし」

  候補「jenkinsし」を表示後にアプリ候補「天気」を表示

HyperVマネージャを起動してくれなかったのはちょっと残念です。Cortanaさんのすぐ隣にいるのに。
ジェンキン寿司は古いネタなのかCortanaさんには通じませんでした。

公式ドキュメントはこちら
https://msdn.microsoft.com/en-us/library/windows/apps/windows.media.speechrecognition.aspx

簡単に試せそうなものを先人が示してくれているのでコピペして動かします。
UWPで音声認識 - かずきのBlog@hatena

まとめ

10行未満のコードで簡単に音声認識ができました。
これなら気軽にアプリに組み込むことができそうです。
Hololensのような端末で何か作ることを考えたときに、こういったユーザーインターフェイスは従来のそれと比較して重要度が相対的に高くなると思います。
どこまで細かいことができるのかはまた別途試してみたいところです。
では今回はこれくらいで。

HoloLensの開発環境を試してみた

やっとうちのメインマシンがWindows10Proになったので試してみました。かたりぃなです。

とはいっても実機は無いのでエミュレータです。

 

実験環境

実験環境はこんな感じです。HyperVを有効化しておくことを忘れずに。

HyperVとは?

いわゆるMicrosoftの仮想化技術です。私自身使うのは初めてです。

今回使うHyperVはクライアントHyperVと呼ばれているもので、VMWareとかVirtualBoxと同系列になります。Windows上で他OSを動かすのに使います。

 

必要なツールをインストールする

公式サイトみながらやればいいんですが、英語読むの面倒な人はこのあたりを参考にすればいけると思います。

HoloLens Emulatorアプリ開発入門(1):HoloLensのAR世界を疑似体験できる、エミュレーターの基礎知識とインストール、基本的な使い方 (3/3) - @IT

 

おおまかな手順としては

  1. HyperV仮想化の有効化
  2. VisualStudioのインストール
  3. Hololensエミュレータのインストール

です。

VisualStudioのインストール時にUWP関係のものにチェックを付けるのをお忘れなく。

 

自作プログラムを準備

プログラムはXAMLのhelloworldでもいいのですが、私がやりたいことを試すためにDirectX11のテンプレートから始めることにします。

言語はC++cxでやってみます。とはいってもまだコードは書きません。サンプル動かしてみてエミュレータ上で動くアプリがどんなものかを見るだけ。

公式発表ではDirectX11 laterとのことですが、DirectX12はマニュアル見た限りすごく低レベルなAPIに思えたので、11でいってみます。

https://developer.microsoft.com/en-us/windows/holographic/install_the_tools

プロジェクト作成から実行までの手順はVisualStudioに慣れている人ならいつもの手順ですね

  1. 新規プロジェクトを選択
  2. DirectX11のテンプレートをダウンロード(立方体が回ってるやつ)
  3. ビルド
  4. 実行

まあ普通にWindows上でアプリが起動するかと思います。

HoloLens上で実行してみる

以前にMicrosoft SurfaceRT向けに何か作ろうかなと試したことがありました。手順はその時と同じです。ツールバーから実行環境を指定するだけです。

f:id:Catalina1344:20161106204757p:plain

なんか未来ちっくな名前の人がいますね。これです。

f:id:Catalina1344:20161106204807p:plain

 

これでHololensEmulator上へのアプリのデプロイとデバッグが開始されます。

 

エミュレータの設定は?

どうやらこれで自動的にHoloLensの仮想マシンがHyperV上に生成されるらしく、HyperVマネージャを起動して確認するとそれっぽい名前の仮想マシンが作られていることがわかります。

手元の環境ではデフォルト設定でも軽快にエミュレータは動作したので、しばらくはこれで色々やってみることにします。

 

トラブルシューティング

今回詰まったポイントは2つでした。

VisualStudioのHoloLensデバッグセッションが開始できない

HoloLensエミュレータは起動しているのにデバッグセッションが開始できないとVisualStudioに言われることがあります。

どうやらエミュレータ上にアプリをデプロイするにはアカウント設定を終える必要があるっぽいので、手持ちのMicrosoftアカウントを設定します。

英語キーボード設定になっているので記号入力で詰まってしまいますが、試しにタイプしながらやればすんなりいきました。

HoloLensエミュレータの操作に慣れる

慣れてきたので思い出しましたが、FPSゲームっぽい操作感覚です。

  • 左クリックとドラッグ=頭の向きを変える
  • 右クリック=視点カーソル?地点の選択
  • WSAD=移動

です。

スペースキーを押すと空間マップが表示されます。手元のカメラではこんなの作れない(kinnectがあればもうちょっと色々できるのかな?)ので、サンプルとして準備されている空間マップを切り替えてみると楽しめると思います。

 

まとめ

HoloLensエミュレータを動かしてみただけでもそれなりに楽しめました。

次回はもう一歩進んで何か面白いことができればなと思います。

Microsoft hololensへの期待

久しぶりのブログ記事です。かたりぃなです。

MicrosoftHMD(ヘッドマウントディスプレイ)のデベロッパー向けエディションが日本で発売されるという話を耳にして歓喜しています。

Microsoft HoloLens の日本での提供について | News Center Japan

APIリファレンスも一通り流し読みしたので、私自身の見解を簡単に整理したいと思います。読み間違い・勘違いも含まれている可能性がありますので、詳細は公式サイトをあたってください。

hololensとは?

公式サイト:Microsoft HoloLens | Official Site

 AR(拡張現実)とVR(仮想現実)のさらに進んだ概念としてのMR(複合現実)を実現するためのデバイスです。

利用側からみた特徴としては

  • Windows10搭載
  • 透過型ディスプレイ
  • スピーカ、マイク、カメラなどの現実世界と情報をやり取りするためのデバイスを内蔵

といったところでしょうか。

アプリ開発側からみた特徴としては

  • Intel32bitCPUと、それに付随するDSPか何のでリアルタイム演算
  • Windows10のストアアプリとして配信可能(UWPアプリ)
  • 3DグラフィックのレンダリングはDirectX11世代
  • Unity対応
  • 開発用エミュレータを動作させるにはHyperVが必要

さっそくエミュレータを試そうとしみましたが、残念ながらうちのOSはWindows10-Homeのため、HyperVを動かせないので環境作るためにしばらく保留状態です。

またWindows8以降から標準搭載されているWindowsストアでのアプリを配布ですが、Windows8発売当初は開発者向けには「ストアアプリ」と銘打ってVisualStudio用のテンプレートの配布などが行われていましたが、今は複数のデバイスにパッケージを共通化して配布するための仕組みとして「UWP(ユニバーサルウインドウズプラットフォーム)」が標準となっています。開発言語としてはC#,C++/cxなどが利用できるようです。

C++/cxって何ぞ?と思って今色々試している最中ですが、利用目的から解釈すると「ストアアプリで配布するアプリを作るためにMicrosoftがマネージド拡張を施したc++」です。

開発環境はこれくらいにして、ハードウェア的な課題も既に見えていて次のようなものがあります

  • 視野角が狭い
  • バッテリー駆動時間(2~3h)

特に視野角についてはMicrosoftも認識しているようで、今後改善していくようです。

 

VRとAR

hololensの仕組みの詳細に入る前に、世間一般を賑わせているVRとの違いについて。Oculusとか最近だとPSVRなんかが有名で、視界全体を覆うHMDを使うことで仮想空間への没入感を高めてユーザー体験を向上させようというアプローチです。

VRは仮想世界を主とし、そこに対していかにしてユーザー体験を向上させるかといったアプローチですが、ARは現実世界ありきで、そこに対して付加価値を提供していくかといったアプローチになります。

MRはさらに進んだ概念で現実世界と仮想世界の融合を目指すもので、どちらかといえばARの延長上(ARを包含するといったほうが正しいのかな)にある概念です。

 

hololensの原理を考える

原理はあちこちの記事で述べられているので関連しそうな技術要素だけ簡単に整理します。

現実世界に立体像を表示するという研究や成果は既にあって、例えば日本では

空中ディスプレイ エアリアルイメージングパネル

なんかがあります。

ここで案内されている展示場所に行けば実体験できるので、直接行けるなら実体験したほうが早いと思います。実際に触ると感動とともに課題なんかも見えやすいので。

実際に試させてもらいましたが、空中に結像されたメニューをそのまま選択する方式はユーザーにフィードバックが無いためちょっと難しいと感じました。

こういった技術を応用すれば現実世界で目的の場所に結像はできるわけですが、次はその位置を確定する必要があります。このあたりはおそらく

  • kinectと同様の深度センサを使って奥行を検出
  • 空間マッピングのためのアルゴリズム(ptam?dtam?slam?)
  • hololens自身の位置推定に加速度センサとジャイロセンサを使う

あたりかなと思っています。ptamとかよくわかってませんが。

機能を個別にみていくとhololensは基本的な機能は揃っているわけで、あとは拡張現実感を出すためのノウハウが必要になると思います。

加速度と角速度を使った姿勢推定ではカルマンフィルタや相補フィルタが有名です。

拡張現実感を出すためには現実と仮想の世界の間での誤差が小さいほど良い結果になりますが、この誤差を小さくする(外れ値とかノイズ対策?)機能としてアンカー(詳細は開発者マニュアルを参照)を置いてオブジェクトを固定するという解決ができそうです。

 

何ができるの?

応用分野の話になるので少しそれますが、こういうのとか楽しそう。海外のほうが先行してますね。

「遊戯王」のデュエルシーンが現実に!マイクロソフト製ARメガネ「HoloLens」でリアル再現した動画が話題に - Character JAPAN

 

以前PCのUSBカメラでやってみたもの

ARの原理実験 - catalinaの備忘録

 

カードゲーム好きなのでよく遊んでいますが、これをそのまま実現することはやっぱり困難があると思います。

既に分かっている課題は

  • カード種別の認識をどうやるの?
  • モデルデータをだれが作るの?

あたりです。

データが無いのは諦める(え?)として、カード種別は非常に膨大で、発売元が存続する限りは増えるので、画像分類問題のラベル(種類)が増えても有限時間内で種別を判断できるアルゴリズムがほしいなと思っています。(それが今まさに読んでるPRMLだったりするわけですが。)

 

まとめ

horolensは夢を現実にしてくれる気がします。そんな夢を買うために$3000という金額が必要になるわけですが。

本職は技術屋なので、自分への投資と思って買うのもアリかなと思いつつも悩みます。

まだ正式な発売まで時間はありますし、開発者向けのあとの一般向け販売ではもうちょっと手を出しやすいお値段になるとは思うので、情報を集めて整理しつつ考えましょう。

では今回はこのあたりで。