蓬莱の備忘録

VRとか画像処理とか

【Direct2D】メモリ上からBitmapへとコピーする

はじめに

カメラでキャプチャしたイメージに何らかの画像処理を施して,その結果をリアルタイムでウィンドウに描画するプログラムを作りたいと思います.
基本的に扱うのは2D画像のみなので,Direct3Dと相互運用性のある高速かつ高精細な2Dグラフィックスを提供するAPIとしてDirect2Dを使用します.

大まかな流れとしては,
1. ID2D1RenderTarget::CreateBitmap() メソッドでID2D1Bitmapを作成する.
2. ID2D1HwndRenderTarget::BeginDraw() メソッドでレンダーターゲットの描画操作を開始.
3. カメラでキャプチャしたイメージ(もしくは画像処理を施したイメージ)を作成したID2D1Bitmapに反映させる.
4. ID2D1HwndRenderTarget::EndDraw() メソッドでレンダーターゲットの描画操作を終了.
5. 以後,2~4までを繰り返す.
となります.

しかしながら,毎フレーム更新の度にID2D1RenderTarget::CreateBitmapを行い,画像データを流し込むのは(要求するフレームレートや解像度にもよるが)大幅なパフォーマンスの低下を招きます.基本的にリアルタイムグラフィックスの分野においては,フレームごとのリソース再確保は避けるべきとのことらしいです.
そのため,事前に作成しておいたID2D1Bitmapを再利用し,同一の幅と高さを持つ画像データをCopyFromMemory メソッドを用いてメモリ上でやり取りを行いたいと思います.

開発環境

OS Windows10
CPU Intel Core i5-6600K
GPU GeForce 750 Ti

CopyFromMemoryの使い方

Microsoft社のドキュメントを見てみると,以下のような構文となっています.
ID2D1Bitmap::CopyFromMemory メソッド (Windows)
第2パラメータでコピーするデータを指定,第3パラメータでコピーするデータのストライド(ピッチ)を指定します.

virtual HRESULT CopyFromMemory(
  [in, optional]  const D2D1_RECT_U *dstRect,
  [in]            const void *srcData,
                  UINT32 pitch
) = 0;

dstRectはnullptrを指定,pitchについては”ピクセル幅×ピクセル当たりのバイト数+メモリパディング”という数式を使用して計算できます.
しかし,肝心のsrcDataについてはどのようなデータを用意すればいいのか,ドキュメントの記述では理解できませんでしたが,同じくMicrosoft社のフォーラムに同様の疑問と対処が記載されていました.
How to use ID2D1Bitmap::CopyFromMemory
これによると,事前にID2D1BitmapをID2D1RenderTarget::CreateBitmap() メソッドで作成する際に,指定するパラメータの一つにpixelFormatがあります.
DXGI_FORMAT_B8G8R8A8_UNORMやDXGI_FORMAT_R8G8B8A8_UNORMのように,DXGI_FORMATでピクセル形式を指定します.srcDataの画像データも,この形式に準じるようです.

m_pRenderTarget->CreateBitmap(
         D2D1::SizeU(width, height), 
         D2D1::BitmapProperties(D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE)), 
         &pBitmap);

サンプルコードのようにDXGI_FORMAT_B8G8R8A8_UNORMで指定した場合,データ配列は32bitの符号なし整数の配列となります.青色成分が最初の8bit,緑色成分が次の8bit,赤色成分がその次の8bit,アルファが最後の8bitです.

OpenCVのcv::Mat型画像データをメモリ上に配置

というわけで,画像処理ライブラリのOpenCVを扱うことが多いので,cv::Mat型からID2D1Bitmapへのコピーを行っていきます.
サンプルコードについては適宜置き換えて下さい.

cv::Mat image;  // カメラからキャプチャしたイメージを格納
byte* memory = new byte[image.rows*image.cols*4];  // CopyFromMemory() メソッドの第2パラメータで指定
・・・・・
for (int row = 0; row < image.rows; row++) {
	cv::Vec3b *src = image.ptr<cv::Vec3b>(row);
	for (int col = 0; col < image.cols; col++) {
		cv::Vec3b bgr = src[col];
		int pointBGR = col * 4 + row * image.rows * 4;
		memory[pointBGR + 0] = bgr[0];
		memory[pointBGR + 1] = bgr[1];
		memory[pointBGR + 2] = bgr[2];
	}
}
・・・・・
/* メモリ上のピクセルデータをBitmapにコピー */
pBitmap->CopyFromMemory(nullptr, memory, image.rows*image.cols*4);
/* Bitmapの描画 */
m_pRenderTarget->DrawBitmap(pBitmap, //the bitmap to draw [a portion of],
	D2D1::RectF(0.0f, 0.0f, image.rows, image.cols), //destination rectangle,
	1.0f, //alpha blending multiplier,
	D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, //interpolation mode,
	D2D1::RectF(0.0f, 0.0f, image.rows, image.cols)); //source rectangle

おわりに

当初の目的は達成できましたが,如何せんDirect2Dの理解が足りていない...
次は,Webカメラでキャプチャしたイメージと,OpenCVで画像処理を施したイメージを同時に表示するサンプルプログラムを試作してみたいと思います.ソースコードについては,載せる予定ではあります.