none
画像の高速描画 RRS feed

  • 質問

  • 動画カメラから取得したフレームに一定の処理を行った後、ウィンドウに描画する処理を作成しています。DirectShowに対応していないカメラで、メーカーのSDKからフレームを取得しています。

    そこで、描画処理についてはやや古い記事などを参考にしてStretchDIBitsで描画処理を実装しました。

    それなりに動作はしているのですが、画像サイズが大きくなると(ハイビジョン程度)描画処理が遅くて駒落ちしてしまうような症状が見られます。
    最近はStretchDIBitsではなく何か別のスタンダードな描画方法などが有るのでしょうか?


    2014年11月12日 5:37

回答

  • 質問するときはもっと詳しい開発環境・ターゲット環境(IDE、OS、CPU、メモリ、グラフィックスカードなど)を書きましょう。それによって提案・助言できることも変わってきます。

    Windows Vista以降では、従来のGDIはほとんどハードウェアアクセラレートが効かず、ほぼCPUだけで描画することになってしまい、パフォーマンス不足に苦しむことになります。
    高速なグラフィックス描画を求めるのであれば、Direct3D/Direct2DやWPF、OpenGLなどを経由して、グラフィックスカードやCPU内蔵GPUなどのアクセラレータを利用します。これらのAPIにまったく対応していないような古いハードウェアでないかぎり、2D描画程度であればたいてい十分な速度が得られると思います。

    ターゲット環境がWindows Vista以降で、なおかつVisual C++を使って高速な2Dグラフィックス描画機能を実装したいのであれば、Direct2Dが最も適しています。
    カメラのフレームバッファがどういったフォーマットか分かりませんが、24bit RGB/BGRや8bitグレースケールなどであれば、まず同一の幅と高さを持つ32bit BGRAフォーマットの中間画像バッファ(DWORD型もしくはRGBQUAD構造体の配列など)を用意しておき、フレームバッファから画像データを(C++のループコードなどを使って)Direct2D用フォーマットに変換します。次にこれまた事前作成しておいた、同一の幅と高さを持つID2D1BitmapのCopyFromMemoryメソッドを使って画像データを流し込みます。あとはID2D1RenderTarget::DrawBitmapメソッドを使って描画を行ないます。
    Direct2Dの初期化と具体的な使い方は、Web検索するなりして自分で探してください。
    なおVisual Studio 2010/2012/2013 Proを使えるのであれば、MFCにDirect2Dのラッパークラスがあります。

    もしフレームバッファに複雑な画像処理を加えていて、それが描画のボトルネックになっている場合、Direct3D/OpenGLのプログラマブルシェーダーを活用して、画像処理をGPUアクセラレータに実行させることで、劇的に描画性能を向上させることが可能になる場合もありますが、プログラマブルシェーダーの修得にはそれなりの学習コストがかかります。
    • 回答の候補に設定 甕星 2014年11月13日 2:00
    • 回答としてマーク mr54 2014年11月14日 9:43
    2014年11月12日 10:51

すべての返信

  • 質問するときはもっと詳しい開発環境・ターゲット環境(IDE、OS、CPU、メモリ、グラフィックスカードなど)を書きましょう。それによって提案・助言できることも変わってきます。

    Windows Vista以降では、従来のGDIはほとんどハードウェアアクセラレートが効かず、ほぼCPUだけで描画することになってしまい、パフォーマンス不足に苦しむことになります。
    高速なグラフィックス描画を求めるのであれば、Direct3D/Direct2DやWPF、OpenGLなどを経由して、グラフィックスカードやCPU内蔵GPUなどのアクセラレータを利用します。これらのAPIにまったく対応していないような古いハードウェアでないかぎり、2D描画程度であればたいてい十分な速度が得られると思います。

    ターゲット環境がWindows Vista以降で、なおかつVisual C++を使って高速な2Dグラフィックス描画機能を実装したいのであれば、Direct2Dが最も適しています。
    カメラのフレームバッファがどういったフォーマットか分かりませんが、24bit RGB/BGRや8bitグレースケールなどであれば、まず同一の幅と高さを持つ32bit BGRAフォーマットの中間画像バッファ(DWORD型もしくはRGBQUAD構造体の配列など)を用意しておき、フレームバッファから画像データを(C++のループコードなどを使って)Direct2D用フォーマットに変換します。次にこれまた事前作成しておいた、同一の幅と高さを持つID2D1BitmapのCopyFromMemoryメソッドを使って画像データを流し込みます。あとはID2D1RenderTarget::DrawBitmapメソッドを使って描画を行ないます。
    Direct2Dの初期化と具体的な使い方は、Web検索するなりして自分で探してください。
    なおVisual Studio 2010/2012/2013 Proを使えるのであれば、MFCにDirect2Dのラッパークラスがあります。

    もしフレームバッファに複雑な画像処理を加えていて、それが描画のボトルネックになっている場合、Direct3D/OpenGLのプログラマブルシェーダーを活用して、画像処理をGPUアクセラレータに実行させることで、劇的に描画性能を向上させることが可能になる場合もありますが、プログラマブルシェーダーの修得にはそれなりの学習コストがかかります。
    • 回答の候補に設定 甕星 2014年11月13日 2:00
    • 回答としてマーク mr54 2014年11月14日 9:43
    2014年11月12日 10:51
  • sygh さんがおっしゃるように、Web 上でもそれなりに Direct2D のリソースは見つかりますので、Direct2D で試してみてはいかがでしょうか。 CopyFromMemory あたりが難しければ、ID2D1RenderTarget::CreateBitmap などを使えればより簡単にできると思います。

    [連載! とことん VC++] 第 10 回 ネイティブ VC++ におけるグラフィックス オーバービュー、および Direct2D の基本的な利用方法 https://code.msdn.microsoft.com/windowsdesktop/VisualC-7e652493
    これあたりが初心者向けです。

    Direct2D アプリケーションのパフォーマンス向上 (Windows)
    http://msdn.microsoft.com/ja-jp/library/windows/desktop/dd372260(v=vs.85).aspx#reuseResources
    処理のパフォーマンス改善に関してはこちらが参考になるかと思います。


    2014年11月13日 3:13
  • 補足していただくことは嬉しいのですが、「CopyFromMemoryの代わりにID2D1RenderTarget::CreateBitmap」という表現は誤解を与えると思われます。
    D2Dビットマップを作成してCopyFromMemoryを使うにはどのみちID2D1RenderTarget::CreateBitmapは必要になりますが、質問者さんはハイビジョン解像度での毎フレーム更新を行なっているので、ID2D1RenderTarget::CreateBitmapの際に画像データを流し込むだけ、という手法では質問者さんの要求は満たせません。要求するフレームレートや解像度にも依りますが、D2Dビットマップを毎フレーム確保し直すというのは概して大幅なパフォーマンスの低下を招きます。ビットマップの確保はアプリ起動時もしくは解像度変更時のみに限定するべきです。そういう意味を含めてCopyFromMemoryを使う手法を提案しています。
    • 編集済み sygh 2014年11月13日 5:57
    2014年11月13日 4:06
  • 他の回答者さんからD2Dのおすすめがありますので、
    自分は現行コードの見直しのポイントを提案してみます。
    ただし、自分も最終的にはDirect2Dの検討をおすすめする点は同じです。

    1.ストレッチの回数を減らせないか検討する。
     ・ストレッチはコストが高いです。

    2.ストレッチはメモリー上だけで行うようにし、
     デバイスには、等倍で転送するように手直ししてみる。
     ・デバイスに対してのアクセスはコストが高いです。
      メモリーデバイスコンテキスト上で操作したほうが早い場合が多いです。

    3.ストレッチ等の前処理部分と、デバイスへの転送部分を
     別スレッドにできないか検討してみる。
     ・効果がない場合もありえますが、まぁ検討してみてはどうでしょう。
     ・特にリアルタイムに連続して処理す場合は考えてみてください。

    4.DIBではなく、デバイス依存のBMPにできないか検討してみる。
     ・つまりBitBlt()で最終デバイスに転送するわけですね。

    以上思いつくままに並べてみました。

    2014年11月13日 5:01
  • >D2Dビットマップを毎フレーム確保し直すというのは概して大幅なパフォーマンスの低下を招きます。

    >そういう意味を含めてCopyFromMemoryを使う手法を提案しています。

    そうでしたか、確かにその通りかと思います。

    環境によっても変わると思いますが、毎回確保するのと Copy するのでどの程度のパフォーマンス差異が出るのか、個人的にはとても興味があります。


    2014年11月14日 0:32
  • sygh様、回答ありがとうございました。またこちらの環境について説明が足りず申し訳有りませんでした。開発環境はVisual Studio 2010 or 2013、ターゲットはWindows7以降でハードウェアは必要なスペックまで上げる事が出来ます。

    お教え頂いたようにまずはDirect2Dを使って実装してみようと思います。非常に参考になりました。

    2014年11月14日 9:43
  • 仲澤@失業者様

    ご回答ありがとうございます。

    ストレッチの回数ですが、フレーム毎にウィンドウサイズに合わせて描画しているため回数自体は減らせそうに有りません。またメモリデバイスコンテキスト上で操作する方法も試してましたが、残念ながらそれほど大きな改善はみられませんでした。

    3,4についてはまだ試せていませんが、検討してみたいと思います。ありがとうございました。

    2014年11月14日 9:48
  • qt6様

    参考URLのご提示ありがとうございました。また議論の中で今回の件ではCopyFromMemoryで流しこむ方が良いという点も非常に参考になりました。

    またCreateBitmapとの比較ができましたら報告させてください。

    2014年11月14日 9:52
  • qt6さん、ご返答ありがとうございます。確かに環境によって変わることもあると思います。GBクラスのビデオメモリと優秀なキャッシュ機構を備えたドライバーを擁するハードウェアであれば、私の考える小手先の最適化はかえって不要かもしれません。
    ただリアルタイムグラフィックスの分野では、フレームごとのリソース再確保は避けるべき(ヒープメモリの確保は概して遅く、また頻繁なメモリの確保と解放は断片化のおそれもあるため)、というのがセオリーですので、質問者のmr54さん同様に自分のほうでもきちんと検証してみようと思います。
    • 編集済み sygh 2014年11月14日 13:00
    2014年11月14日 12:50