catalinaの備忘録

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

HololensでMediaCaptureを使ってカメラプレビューを取得する

HoloLensで入力画像を処理する前準備をしました。かたりぃなです。

今回やること

今回の記事でやることをざっと列挙します。

  • UWPでMediaCaptureを叩いてプレビューフレームを取得する
  • OpenCVのMat形式で扱えるようにする
  • とりあえず画像処理する

です。 画像処理の結果を使って3Dレンダリングまでやりたかったのですが、ちょっと詰まってしまっているのでいったんここまで記事にしておきます。 シミュレータではカメラが使えないのでほとんどが実機デバッグになります。買ってよかったHoloLens。

カメラから映像を取得する

HoloLensでMediaCaptureを使ってプレビューフレームを取得するコードはMicrosoft公式がサンプルコードを出してくれているのでそのまま使います。 中身ざっと眺めたところ、CreateAsyncしてGetLatestFrameするだけで使えるみたいです。

https://github.com/Microsoft/Windows-universal-samples/blob/master/Samples/HolographicFaceTracking/cpp/Content/VideoFrameProcessor.cpp

早速コード。 アプリのメインクラスの定義にメンバ変数を作っておきます。 mediacaptureを制御するクラスと、プレビューフレームを処理するワーカースレッドです。

    std::shared_ptr<HolographicFaceTracker::VideoFrameProcessor>    m_videoProcessor;
    std::thread                                                     m_worker_thread;

CreateAsyncでインスタンス化したものを保持しておき、std::threadでぐるぐる回します。 取得した映像フレームを処理するVideoProcessingという関数を作りました。ここで画像処理をします。

    task<void> videoInitTask = HolographicFaceTracker::VideoFrameProcessor::CreateAsync()
    .then([this](std::shared_ptr<HolographicFaceTracker::VideoFrameProcessor> videoProcessor)
    {
        m_videoProcessor = std::move(videoProcessor);
        m_worker_thread = std::thread([this]{
            while (1) {
                if (m_videoProcessor) {
                    auto frame = this->m_videoProcessor->GetLatestFrame();
                    if (frame) {
                        if (Windows::Media::Capture::Frames::VideoMediaFrame^ videoMediaFrame = frame->VideoMediaFrame) {
                            VideoProcessing(videoMediaFrame);
                        }
                    }
                }
            }
        });
    });

OpenCVに渡す(前準備)

映像フレームをOpenCVで処理します。 その前にOpenCVをUWP向けにビルドしておいたライブラリをプロジェクトに組み込みます。 MSがUWP向けに準備してくれているOpenCVはこちら。 GitHub - Microsoft/opencv: Open Source Computer Vision Library

試しに組み込んだときの記事はこちら。 WindowsストアアプリでOpenCVを使う - catalinaの備忘録

というわけで同様にして組み込みます。

先ほどのサンプルコード見るとそのまま画素にアクセスできるらしいので真似します。

 auto buffer = frame->SoftwareBitmap->LockBuffer(Windows::Graphics::Imaging::BitmapBufferAccessMode::Read);
    IMemoryBufferReference^ bufferRef = buffer->CreateReference();

    Microsoft::WRL::ComPtr<Windows::Foundation::IMemoryBufferByteAccess> memoryBufferByteAccess;
    if (SUCCEEDED(reinterpret_cast<IInspectable*>(bufferRef)->QueryInterface(IID_PPV_ARGS(&memoryBufferByteAccess))))
    {
        BYTE* pSourceBuffer = nullptr;
        UINT32 sourceCapacity = 0;
        if (SUCCEEDED(memoryBufferByteAccess->GetBuffer(&pSourceBuffer, &sourceCapacity)) && pSourceBuffer)
        {
            // pSourceBuffer使ってごにょごにょする
            // YUV420Pとして扱えばいい
        }
    }

ここで注意として、今回のコードではbitmapはNv12フォーマットです。 NV12とは。。。 YUV Video Subtypes (Windows) とのことで、いわゆるYUV420P形式です。

プレビューフレームの画素のバイト列をOpenCVのcv::Matにする。

簡単に。 OpenCVのMatとして取り扱います。YUV420Pなので最初のプレーンを参照すれば輝度のみ取れるので、グレースケール画像として取り扱えます。

auto cv_preview_image = cv::Mat(cv::Size(frame->SoftwareBitmap->PixelWidth, frame->SoftwareBitmap->PixelHeight), CV_8UC1, pSourceBuffer);

これでOKです。 cv::Matのコンストラクタは色々ありますが、この形式のものを使います。 第一引数はcv::Sizeであらわされる画像の幅と高さ。 第二引数はピクセルフォーマット(厳密にはmatの各要素のtype)で、pSourceBufferが画素列の先頭アドレスです。

デバッガで目視確認する

ピクセルの数列見ても楽しくないので、画を見たいです。 MS公式がVisualStudioのプラグインとしてcv::Matのイメージビューワを提供してくれてるので使います。 Image Watch - Visual Studio Marketplace

適当にフィルタ処理してみるとそれっぽくできてるのが確認できました。 というわけで画像処理の入り口まで辿り着きました。

課題・今後の展望

HoloLens実機でのデバッグはそれなりに手間がかかります。デバッガで少し見て解る問題ならいいんですが、ちょっと複雑なことをやろうとすると、厳しいです。

特に問題になるのは、リアルタイム処理とVisualStudioのデバッガが相性悪すぎといったところです。 映像をリアルタイムに処理した結果をデバッガのログに出していても、それを見るために頭を動かしてしまうと状況が変わってしまいます。 こればっかりはどうしようもないので、HoloLens側でデバッグ情報をレンダリングする仕組みを作っておいたほうが後々幸せになれそうな気がします。

とりあえずビルボードでテキストをレンダリングする仕組みくらい作っておいて損はなさそうです。

では今回はこれにて。