none
DirectShowのGetCurrentBufferで取得した静止画バッファから指定された領域を別バッファにコピーするには? RRS feed

  • 質問

  • いつもお世話になっております。

    現在、Windows XP + VS.NET Team System 2008 Development Edoton をつかって DirectShowでキャプチャデバイスの動画を表示しながら、特定のタイミングでスナップショットを取り、さらにそのスナップショットから特定の部分だけを抜き出すプログラムを作成しています(SDKで開発しています)。

    キャプチャデバイスの動画の(オリジナルサイズの)スナップショットを取ることはできました。
    (試しに取得したバッファをビットマップファイルで保存してみたところ保存できました)

    今度は、そのバッファから必要な範囲だけを別のバッファに取り出したいと考えているのですが・・・どの様にすればいいのか皆目見当もつかず^^;

    取得したオリジナルのビットマップも別用途で利用する事を前提で、指定された範囲(RECT 構造体で指定?)の部分だけを別のバッファにコピーする方法がありましたらご教授いただけないでしょうか?

    できるだけ、一時ファイルを作成せずバッファだけで処理ができる事を望んでいます・・・

    よろしくお願いいたします。

    2010年10月29日 11:51

回答

  • 電柱一家さん

    レスありがとうございます。

    > ビットマップの場合、横のサイズは必ず4の倍数にしないと駄目なので

    そうだったんですね!!
    ありがとうございます、参考にさせていただきます。

    >別に無理に HeapAlloc/VirtualAlloc を使わなくても、malloc/free もしくは new/delete でいいんじゃないかと。

    どちらも試して見ているのですが、トリミングをする前にまずテストで、そのまま複製してみようと思ったのですが・・・

    BYTE *Buf1 = NULL;
    size_t Buf1Size;
    BYTE *Buf2 = NULL;

    ・・・・Buf1にGetCurrentBufferでスクリーンショットを取得・Buf1Sizeにはサイズを格納

    Buf2 = (BYTE *)VirtualAlloc(NULL, l_Buf1Size, MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    for(int loop = 0; loop < Buf1Size; loop++)
    {
         Buf2[loop] = Buf1[loop];
    }

    ・・・

    以上のようにやってみたところ、「Buf2[loop] = Buf1[loop];」のところで「ハンドルされていない例外が発生しました」と出てしまいました。
    このやり方はまずいということなのでしょうか・・・(これの応用で、トリミングする範囲のピクセル情報だった場合のみコピーするような作りにしようと思っていたのですが・・・)?

    • 回答としてマーク どらちん 2010年11月1日 11:24
    2010年11月1日 9:04
  • 感覚としてはそのような感じになります。

    流れとして以下のようになります。

      ・前提

        trimming先のBufferをvoid* destBuf、trimming元のBufferをvoid* srcBufとします。

        trimming先のBuffer SizeをSIZE_T destBufSizeとします。

     

      1.trimming先のBuffer Sizeを計算します。

        すでにご提示されているので省略します。

     

      2.trimming先のBufferを確保します。

        destBuf = VirtualAlloc(NULL,destBufSize,MEM_COMMIT | MEM_RESERVE,PAGE_READWRITE);

        MEM_COMMITしないと、MemoryにAccessできません。

     

        destBuf = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,destBufSize);

        HEAP_ZERO_MEMORYはなくてもよいですが、指定しないとMemoryの中身は0で初期化されなくなります。

     

        destBuf = malloc(destBufSize);

     

        サーバ上で常時起動させるアプリケーションとのことですので、綿密に作る必要がありますね。

        どの手段を利用するかは、要件が分かる方と相談されることをお勧めします。

     

      3.trimmingする位置を計算する

        まず、行から求めます。上記に記述したとおり、row byte、Top downかBottom upに注意しましょう。

        次にLeftとRightからCopyするByte数を計算します。(ここではcopySizeとします。)

        行単位でCopyします。

        CopyMemory(destBuf,srcBuf,copySize);

        TopとBottomから行数を求め、上記の処理を繰り返します。

     

      4.destBufを解放する

        VirtualFree(destBuf,0,MEM_RELEASE);

     

    Bitmap(DIB)の構造については、以下に説明があります。

    あまりお詳しくない場合は、熟読されることをお勧めします。

    [Bitmap Header Types]

      http://msdn.microsoft.com/en-us/library/dd183386(v=VS.85).aspx

     

    [Device-Independent Bitmaps]

      http://msdn.microsoft.com/en-us/library/dd183562(VS.85).aspx

    • 回答としてマーク どらちん 2010年11月1日 11:24
    2010年11月1日 9:53

すべての返信

  • >取得したオリジナルのビットマップ

    HBITMAPになっていますか?

     

    1.CreateCompatibleDCでHDCを作成し、オリジナルのビットマップをSelectObjectする

    2.CreateCompatibleDCでHDCを作成し、CreateDIBSectionでHBITMAPを作成する。それをSelectObjectする。

      このときのSizeはtrimmingするSizeにする。

    3.BitBltで1→2へBitmapのtrimmingする部分を転送する

     

    DIBSectionなので、2.のBufferに直接Accessすることも可能です。

     

     

    補足です。

    HBITMAPになっていない場合、1.で空のHBITMAPをCreateDIBSectionで作成し、BufferにDataを直接Copyしてもよいです。

    しかしそれをやるぐらいなら、bppとBitmapのrow byte、Top downかBottom upかに注意しながら、HeapAlloc/VirtualAllocで作成したBufferにtrimming部分を計算してCopyしていったほうが早いでしょう。

    • 編集済み kozz 2010年10月29日 14:20 bps->bpp(bits per pixel)
    2010年10月29日 12:14
  • kozz さん

    レスありがとうございます。

    >HBITMAPになっていますか?

    なっていないようです。
    テストでファイルに保存する際、bitmap系の各種ヘッダを追加して保存しているので、単純に中身(表現正しいですかね?)だけのようです。

    VirtualAllocで作成したバッファにCopyをするというのは
    1.作成するBufferのサイズを取得(縦×横×ColoerBit / 8)
         void *l_pTrim = NULL;
         long l_TrimSize = width * height * g_pVideoInfoHeader->bmiHeader.biBitCount / 8;
              ※g_pVideoInfoHeaderは、VIDEOINFOHEADERクラスです。

    2.VirtualAllocでバッファ領域を確保(エラー処理は省略してます)
        l_pBuffer2 = VirtualAlloc(NULL, l_lBuffer2Size, MEM_RESERVE, PAGE_READWRITE);

    3.ループを使って、必要範囲に含まれていた場合コピーする(ループの中や条件は相当省略)
        for(・・・;・・・;・・・)
        {
            if(指定範囲内の領域だった場合)
            {
                l_pTrim[xx] = orgBuf[yy];
            }
        }

    4.必要な処理が終わったら解放
    VirtualFree(l_pTrim, 0, MEM_RELEASE);

    こんな感覚でいいのでしょうか?
    HeapAlloc/VirtualAllocを使ったことがなく、MSDNを見てはいるのですが・・・使い方に自信が持てなくて^^;
    サーバ上で常時起動させるアプリケーションになる予定なので、慎重に作らないとなんです・・・>_<

    お手数ではございますが、ご教授可能な範囲で教えていただけると幸いです。

    2010年11月1日 4:23
  • VirtualAllocで作成したバッファにCopyをするというのは
    1.作成するBufferのサイズを取得(縦×横×ColoerBit / 8)

    ビットマップの場合、横のサイズは必ず4の倍数にしないと駄目なので、バッファサイズは以下のような計算になるかと思います。

    const int pitch = ((横 * ColorBit + 31) >> 3) & ~3;
    const int size = pitch * 縦;
    
    
    HeapAlloc/VirtualAllocを使ったことがなく、MSDNを見てはいるのですが・・・使い方に自信が持てなくて^^;

    別に無理に HeapAlloc/VirtualAlloc を使わなくても、malloc/free もしくは new/delete でいいんじゃないかと。


    電柱一家
    2010年11月1日 7:22
  • 電柱一家さん

    レスありがとうございます。

    > ビットマップの場合、横のサイズは必ず4の倍数にしないと駄目なので

    そうだったんですね!!
    ありがとうございます、参考にさせていただきます。

    >別に無理に HeapAlloc/VirtualAlloc を使わなくても、malloc/free もしくは new/delete でいいんじゃないかと。

    どちらも試して見ているのですが、トリミングをする前にまずテストで、そのまま複製してみようと思ったのですが・・・

    BYTE *Buf1 = NULL;
    size_t Buf1Size;
    BYTE *Buf2 = NULL;

    ・・・・Buf1にGetCurrentBufferでスクリーンショットを取得・Buf1Sizeにはサイズを格納

    Buf2 = (BYTE *)VirtualAlloc(NULL, l_Buf1Size, MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    for(int loop = 0; loop < Buf1Size; loop++)
    {
         Buf2[loop] = Buf1[loop];
    }

    ・・・

    以上のようにやってみたところ、「Buf2[loop] = Buf1[loop];」のところで「ハンドルされていない例外が発生しました」と出てしまいました。
    このやり方はまずいということなのでしょうか・・・(これの応用で、トリミングする範囲のピクセル情報だった場合のみコピーするような作りにしようと思っていたのですが・・・)?

    • 回答としてマーク どらちん 2010年11月1日 11:24
    2010年11月1日 9:04
  • 感覚としてはそのような感じになります。

    流れとして以下のようになります。

      ・前提

        trimming先のBufferをvoid* destBuf、trimming元のBufferをvoid* srcBufとします。

        trimming先のBuffer SizeをSIZE_T destBufSizeとします。

     

      1.trimming先のBuffer Sizeを計算します。

        すでにご提示されているので省略します。

     

      2.trimming先のBufferを確保します。

        destBuf = VirtualAlloc(NULL,destBufSize,MEM_COMMIT | MEM_RESERVE,PAGE_READWRITE);

        MEM_COMMITしないと、MemoryにAccessできません。

     

        destBuf = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,destBufSize);

        HEAP_ZERO_MEMORYはなくてもよいですが、指定しないとMemoryの中身は0で初期化されなくなります。

     

        destBuf = malloc(destBufSize);

     

        サーバ上で常時起動させるアプリケーションとのことですので、綿密に作る必要がありますね。

        どの手段を利用するかは、要件が分かる方と相談されることをお勧めします。

     

      3.trimmingする位置を計算する

        まず、行から求めます。上記に記述したとおり、row byte、Top downかBottom upに注意しましょう。

        次にLeftとRightからCopyするByte数を計算します。(ここではcopySizeとします。)

        行単位でCopyします。

        CopyMemory(destBuf,srcBuf,copySize);

        TopとBottomから行数を求め、上記の処理を繰り返します。

     

      4.destBufを解放する

        VirtualFree(destBuf,0,MEM_RELEASE);

     

    Bitmap(DIB)の構造については、以下に説明があります。

    あまりお詳しくない場合は、熟読されることをお勧めします。

    [Bitmap Header Types]

      http://msdn.microsoft.com/en-us/library/dd183386(v=VS.85).aspx

     

    [Device-Independent Bitmaps]

      http://msdn.microsoft.com/en-us/library/dd183562(VS.85).aspx

    • 回答としてマーク どらちん 2010年11月1日 11:24
    2010年11月1日 9:53
  • kozzさん、電柱一家さん

    ありがとうございました!!
    おかげさまで大まかなところはできました!

    こんな感じで作成しました。

    //使っているGlobal変数だけ表示w
    ISampleGrabber *g_pSampleGrabber = NULL;
    AM_MEDIA_TYPE g_am_media_type;
    VIDEOINFOHEADER *g_pVideoInfoHeader = NULL;
    long g_lOriginalWidth;            //オリジナル画像の幅
    long g_lOriginalHeight;            //オリジナル画像の高さ


    //その時点のスクリーンショットを取得する関数
    BOOL DoCapture(HWND hWnd, RECT TrimRegion)
    {
     BYTE *l_pBuffer = NULL;            //静止画として取得したときの静止画のバッファ
     long l_lBufferSize;             //↑のサイズ
     BYTE *l_pBuffer2 = NULL;           //↑から指定した範囲をピックアップした画像を確保するバッファ
     long l_lBuffer2Size;            //↑のサイズ
     long l_SizeByPixcel;            //1ピクセル単位のバイト数
     long l_lLoop1, l_lLoop2;           //ループ用変数

     //バッファの準備
     l_lBufferSize = g_am_media_type.lSampleSize;
     l_pBuffer = (BYTE *)VirtualAlloc(NULL, l_lBufferSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
     if(l_pBuffer == NULL)
     {
      ShowDefMsgBox(L"静止画取得するためのバッファの確保に失敗しました。");
      return FALSE;
     }

     //現在表示されている映像を静止画として取得
     if(FAILED(g_pSampleGrabber->GetCurrentBuffer(&l_lBufferSize, (long *)l_pBuffer)))
     {
      ShowDefMsgBox(L"静止画取得に失敗しました。");
      if(l_pBuffer != NULL)
      {
       free(l_pBuffer);
       l_pBuffer = NULL;
      }
      return FALSE;
     }

     //1ピクセルあたりのサイズを取得
     l_SizeByPixcel = g_pVideoInfoHeader->bmiHeader.biBitCount / 8;
     //指定範囲用のバッファサイズとバッファの格納(縦×横×1PixcelあたりのByte数)]
     l_lBuffer2Size = (TrimRegion.bottom - TrimRegion.top) * ((((TrimRegion.right - TrimRegion.left) * g_pVideoInfoHeader->bmiHeader.biBitCount + 31) >> 3) & ~3);
     l_pBuffer2 = (BYTE *)VirtualAlloc(NULL, l_lBuffer2Size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
     if(l_pBuffer2 == NULL)
     {
      ShowDefMsgBox(L"トリミング範囲取得用バッファの確保に失敗しました。");
      if(l_pBuffer != NULL)
      {
       free(l_pBuffer);
       l_pBuffer = NULL;
      }
      return FALSE;
     }

     l_lLoop2 = 0;
     for(l_lLoop1 = (g_lOriginalWidth * (g_lOriginalHeight - TrimRegion.bottom) + TrimRegion.left) * l_SizeByPixcel; l_lLoop1 < (g_lOriginalWidth * (g_lOriginalHeight - TrimRegion.top)) * l_SizeByPixcel; l_lLoop1 += g_lOriginalWidth * l_SizeByPixcel)
     {
      CopyMemory(&l_pBuffer2[l_lLoop2],&l_pBuffer[l_lLoop1], (TrimRegion.right - TrimRegion.left) * l_SizeByPixcel);
      l_lLoop2 += (((TrimRegion.right - TrimRegion.left) * g_pVideoInfoHeader->bmiHeader.biBitCount + 31) >> 3) & ~3;
     }

    //取得したバッファはここで処理・・・


    //バッファの解放
     if(l_pBuffer != NULL)
     {
      VirtualFree(l_pBuffer2, 0, MEM_RELEASE);
      l_pBuffer2 = NULL;
     }
     if(l_pBuffer != NULL)
     {
      VirtualFree(l_pBuffer, 0, MEM_RELEASE);
      l_pBuffer = NULL;
     }


     return TRUE;
    }

    こんな感じで行けました!!
    お二人のアドバイスで、先に進めそうです☆
    本当にありがとうございました。

    追伸:結局取得した画像をピクチャーコントロールに表示させなければならなくなり、HBITMAPに変換することになり、初めからBitBltを使えばよかったのかなぁ・・・と、ややへこんでいます(笑)。


    2010年11月1日 11:24
  • うまくいったようで何よりです。

    一点だけまずいところがありますよ。

    > free(l_pBuffer);
    これはmallocで確保したMemoryを解放するための関数ですので、VirtualFreeに変えましょう。

    (いろいろと試行錯誤した跡なのかな)

     

    >HBITMAPに変換することになり

    あら、そうでしたか。

    それならCreateDIBSectionでHBITMAPを作ってから、trimmingしたほうが簡単ですね。

    2010年11月1日 11:53
  • >kozzさん

    ご丁寧にご確認までいただき、感謝します。

    >> free(l_pBuffer);
    >これはmallocで確保したMemoryを解放するための関数ですので、VirtualFreeに変えましょう。

    >(いろいろと試行錯誤した跡なのかな)

    おっしゃるとおりで、転記ミスです^^;
    実際のソースはVirtualFreeになっています。

    >それならCreateDIBSectionでHBITMAPを作ってから、trimmingしたほうが簡単ですね。

    そうなんです>_<
    でもせっかくなので、変換した後のものをHBITMAPにSetObjectして、ピクチャコントロールに表示させることにします(笑)

    本当にありがとうございました!!

    ここからがまた大変なのですが(キャプチャしたものをOCRして・・・・とまだまだ続きます)、わからないことがありましたら改めて投稿させていただくかもしれませんが、機会があればよろしくお願いいたします☆

     

    2010年11月2日 2:54