catalinaの備忘録スマートフォン用の表示で見るcatalinaの備忘録ソフトウェアやハードウェアの備忘録。後で逆引きできるように。pythonHTTP接続までは簡単にできたので、一気にHoloLensとの連携までやってみました。ためした結果HTTPサーバまでできた時点で「あとはもう簡単だろう」と思っていたら、想像以上に手こずりました。一言で結論だけ書くと「英語カードだけで学習したDeepLearningの分類機では日本語カードを分類できなかった」です。工夫が必要そうですね。詳細HoloLensのカメラ映像からカード部分を抽出し、Chainerの分類器に入力する直前でファイルに保存するとこんなやつでした。テストに使ったのは適当に箱から引っ張り出してきたカードです。誤って分類したラベルのカードイラストはこんな感じです。うん、なんか似てる気がする。(似てない)DeepLearningの識別器はまだまだ改善の余地ありですね。それでも連休中に「私がやりたいこと」のうち「今まで一度もやったことないもの」の技術要素は詰め込み終わったので、あとは時間が空いたときにチマチマと進められると思います。以下、やってみたことの詳細です。(長文です)PythonでHTTPサーバを立ち上げる今回はシンプルなHTTPサーバとしてbottleを使うことにしました。現時点では小難しいことをするつもりはないので、こういう単純な仕組みで充分です。bottleは非常にシンプルにできていてREST-APIを軽く作って試すだけなら数時間でできました。bottleをインストールするpipで一発です。ファイルは一個だけなのでわざわざインストールしなくてもいい気はします。main()これでブラウザでlocalhostの8080番ポートの/helloにアクセスするとHelloWorldが表示されます。HoloLensからアクセスするとき(localhost以外からアクセスする)はrunのhost=のパラメータを"0.0.0.0"とかにしておくとよさげです。立ち上がらないうちのマシンだと、なぜかlocalhost:8080をlistenできませんでした。どこかで見覚えのある番号だなーと思って調べてみると、Jenkins氏が使っていました。上記pythonのコード中のポート番号を変えて対応しました。あと、アンチウイルス系のソフトに付随するファイアウォール系のソフトが動いているとリモートからアクセスできないなんてことがあるので、必要に応じて設定を変えておきます。PUTメソッドを実装するすごく簡単でした。)これが"localhost:8080/resource"に対するPUTのハンドラになります。識別結果を"ok,ファイルサイズ"の形でとりあえず返して目視確認します。shellから適当な画像ファイルを投げ込んでみます。学習に使ったデータファイルを投げます。PUTと-InFileオプションで指定します。89197すんなりいきました。このままchainerの分類器をくっつけてみることにします。PUTで受けたpngファイルをdecodeして画像分類器にかける前回のエントリ(http://catalina1344.hatenablog.jp/entry/2017/05/04/171318)でやったことをくっつけます。m)ポイントはhttp-PUTで取得したボディのデコードです。PUTのbodyに載っているデータは先のPowerShellのアクセス方法ではPNGでエンコードされたフォーマットです。オンメモリ上のPNGファイルイメージをデコードする必要があります。オンメモリ上のPNGファイルイメージはそのままではPIL.Image.openに渡すことができません。オンメモリ上のバイト列をio表現にしてあげてからImage.openに渡す必要があるみたいです。io表現はPython3からはio.BytesIOを使うようで、これはio.StringIO(文字列)と違ってエンコード・デコードが行われない生のバイト列を表現するものです。fromarrayとか使えないか試しましたが、これはデコードされたピクセルデータのバイト列を渡すためのものでした。つまるところio.BytesIOで表現されたバイト列をPIL.Image.openに渡せばデコードしてくれるというわけですね。あとは前回やったようにchainerのNNの入力形式に合わせてあげて、NNに突っ込んで結果を取り出すだけです。結果は"ok,分類ラベル"の形で返されます。HoloLensからPUTするまずHoloLensからPUTするための画像を作る必要があります。カメラ映像をそのまま送るのは負荷的に優しくないので、必要部分だけ切り取ります。そのままコードに落としこむ(重すぎて使えない)ダメでした。色変換とパースペクティブ変換が重すぎます。映像中から一枚のMTGのカードを抽出するのに1~2秒かかります。色変換で盛大に詰まったので、やったことだけをメモします。NV12=YUV420Pだと思っていたけど、チョット違うらしいOpenCVのMatで異なる大きさを持つ次元の取り扱いがわからないまずNV12はMS曰く、4:2:0-Planer形式だそうです。charとして取り扱っていましたが、chainerに渡すにはu,vチャネルも適切に処理する必要があります。マニュアルにはPlaner形式とあるので次のように推測していました。Yプレーンの後にUプレーンが来るUとVはYプレーンのサイズと比較してwidth,heigntがそれぞれ1/2Uプレーンの後にVプレーンが来るこう思って適当にコード書いて変換かけてみましたが、色味がおかしい。辿り着いたのがこのマニュアル。https://msdn.microsoft.com/ja-jp/library/windows/desktop/dd206750(v=vs.85).aspx#nv12NV12ってYだけPlaneになっていてUVはPackedらしいです。こんなの初めて見ました。上記ページにあるYV12かIMC2,4あたりを想定していただけにショックです。little-endianとか書いてますね。適当にバイト列を取り扱ってOpenCVのcvtColorに与えればいいかなと思っていましたが、これでは取り扱いに困ります。(PlanerとPackedの組み合わせで表現された画像をMatでどうやって表現すればいいのか。)というわけで、書きなぐったコードは重すぎるので捨てました(動作確認はとれたので理論が正しいことは確認できた)。YプレーンとUVプレーンに分けて処理するcv::Matでplanerとpackedを混ぜて扱う方法がわからないので、分けて処理します。pSourceBufferが画像データの先頭アドレスだとして、次のように2つのcv::Matで表すことができます。uvはpackedなのでchが2です。w*h);カード画像が含まれている矩形だけ処理するHoloLens自身のCPUはそれほど贅沢なもの載ってないので、計算量を減らすことにします。モノクロ画でカード検出はできているので、カードが含まれている矩形範囲を抽出します。rect);};これで以降の処理はカメラ映像の一部に対してのみ行うことになるので、負荷は一気に下がります。全てのカラーチャネル個別にパースペクティブ変換する前回の記事でやったChainerで学習したモデルは、アフィン変換・パースペクティブ変換などを含まない画像から学習されたモデルです。いった不変性に対するロバスト性は持っていないことは想定されるので、HoloLens上であらかじめパースペクティブ変換してしまいます。上記のバウンディングボックス計算で得られたROI中のカードの四隅の位置と、変換後のカードの画像(NNに入力するもの)をもとにパースペクティブ変換行列を作ります。そのパースペクティブ変換行列を使って、カメラで撮影された画像を平面に投影した画像をつくります。croped_size);これでカメラから撮影された画像中からカード領域を抽出し平面投影したものを生成できました。この記事の先頭に貼ってある映像、こんな感じの画像が抽出できます。(ただし、この段階では画像のように下半分が見切れたものではなくカード全体の画像になります)カラーチャネルを合成する自前で書くのもう面倒(さっきのカラー変換で力尽きた)なので、少々CPU負荷が高くても力技でいきます。カラーチャネルの合成をOpenCVの関数でやるためには、すべてのプレーンが同じ幅、高さを持っている必要があります。UVチャネルは幅、高さそれぞれ1/2なのでresizeで引き延ばしてしまいます。補間でノイズが載りそうですが今は無視します。);Y,U,Vのチャネルをマージしたいのですが、NV12カラーフォーマットではcv::mergeは使えません。cv::mergeはcv::mixChannelsという関数の特別な条件が成立したときに使えるものなので、mixChannelsでチャネルのマージ方法を細かく指定します。cv::mergeはシングルチャネル同士のmergeなので使えない。3);これでy_mat(Y-Plane)とfullscale_uvmat(UV-PackedPlane)の各チャネルを合成できました。cropしてNNに入力できる形に合わせるNNは223x223の画像の入力を期待しているので、ここまでにできたイメージ(224x312)から切り取ります。rgb_mat.step);多少カクつきますが、とりあえず動きました。オンメモリ上でpngエンコードするHTTP経由で送る前に、送るデータに意味付けをする必要があります。先のHTTPサーバはpngのファイルイメージを期待しているので、それに合わせます。HoloLens上のオンメモリの画像イメージからpngファイルイメージをオンメモリ上に生成します。OpenCVの関数でいうimencodeです。>(file_image);これでとりあえずfile_imageにpng形式のファイルイメージが格納できます。先頭3Byteが'png'になっていることが確認できます。HTTP-PUTするMS公式がUWPでhttpを取り扱うためのサンプルコードを出してくれているので、これを参考にします。https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/HttpClientまだcreate_task.thenに慣れませんが、とりあえずこれで先に立ち上げたWebサーバにpng画像を投げて、識別できるようになりました。task_continuation_context::use_current());感想と今後の展望これで私がやりたいことの土台は整いました。あとは識別率をあげるとか、演出をどうするとか仕組みを整えていくことになりそうです。次は3Dレンダリングに挑戦していきたいと思います。それでは今回はこれにて。23:29HoloLensからHTTPでChainerの画像分類器を叩くTweet広告を非表示にする関連記事2018-06-26C++のHTTPサーバ(boost::beast)を試す毎年恒例の夏バテの時期がやってきました。今...2017-07-20カードゲームARの実現可能性検証なんとなくですが実現可能性は高い気がしてきました。かたりぃ...2017-06-10古典的な手法を使ってMTGのカードを検出する古典的な方法で画像中からMTGのカードを取り出す実験をしてみま...2017-05-04ChainerでMTGのカードを分類する(識別フェーズ)黄金週間なので趣味プログラミングに没頭です。HoloLensでARをやってみました。»プロフィールid:Catalina1344ソフトウェアやハードウェアの備忘録。後で逆引きできるように。OpenMVSで3D再構築するOpenMVGを試してみる(環境構築+α)父の日ですね月別アーカイブ忍者AdMaxはてなブログをはじめよう!Catalina1344さんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。