OpenCV DirectShow - eiichiromomma/CVMLAB GitHub Wiki

(OpenCV) DirectShow

キャプチャ部分のみDirectShowを使って動画像処理は使い慣れたOpenCVで行なう方法(かなり強引)

動機

OpenCVではcvcam.libがDirectShowに対応しているのだが、コールバック中に大量のデータを扱うとコケる不安定さがあった。

都合の良いことにDirectShow(というかWindowsか)は基本的にBGRのビットマップデータを受け渡しするため、同じくBGRで扱うOpenCVとは相性が良い。

画像処理はOpenCVのライブラリに慣れている。

キャプチャはDirectShowに任せてデータだけOpenCVがかっさらうプログラムにしてみた。

動作環境

ここではDV Camera(Sony製DCR-VX1000)とデジタルハイビジョンビデオプレーヤー(Victor製CU-VH1)をインターフェース|IFC-ILCB3 に接続して動作確認を行なった。

DirectShowのライブラリについてはPlatform SDKの項を参照のこと。

参考文献、ソース

ソースはlist604.cppをベースにしている。

サンプルソース

変更点

基本的にDirectShowの部分は参考ソースから殆どいじっていない。 DirectShowのウィンドウ処理が不要なので画像構造体や関連する関数を削ってある。

仕様

処理は

  • 差分処理
  • 現在の画像と差分画像を表示
  • 背景画像は起動時に取得したフレームとし'b'キーで更新可能
  • 'c'キーで現在の画像、差分画像、背景画像をjpg形式で保存
  • 'q'で終了

となっている。

ソース

    // 書籍「はじめての動画処理プログラミング」土井 滋貴 著 CQ出版
    // http://www.cqpub.co.jp/hanbai/books/43/43001.htm
    // で公開されている
    // list604.cpp  DirectShowを使ったキャプチャ ver1.1
    // を利用
    //
    // DirectShow対応デバイスからキャプチャしたビットマップをOpenCVで横取り 
    // 当然Windows専用
    // 変更者 Eiichiro Momma 2007/7/4
    
    #include <windows.h>
    #include <dshow.h>
    #include <stdio.h>
    #include <conio.h>
    #include <qedit.h>      // SampleGrabber用
    
    #include <cv.h>
    #include <highgui.h>
    
    void main()
    {
      /*ここからDirectShow用の処理 設定をいじらないならブラックボックス扱いで良い*/
      HRESULT hr;
      CoInitialize(NULL);              // COMの初期化
      // ---- キャプチャフィルタの準備 ----
      // キャプチャデバイスを探す
      ICreateDevEnum * pDevEnum = NULL;
      IEnumMoniker * pClassEnum = NULL;
      IBaseFilter  *pbf = NULL;
      IMoniker * pMoniker = NULL;
      ULONG cFetched;
      // デバイス列挙子を作成
      CoCreateInstance( CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC,
         IID_ICreateDevEnum, (void ** ) &pDevEnum);
      // ビデオキャプチャデバイス列挙子を作成
      pDevEnum -> CreateClassEnumerator( CLSID_VideoInputDeviceCategory, 
                        &pClassEnum, 0);
      if ( pClassEnum == NULL ){
        printf("ビデオキャプチャデバイスは存在しません¥n");
        pDevEnum -> Release();
        CoUninitialize();
        return ;
      }
      // 最初に見つかったビデオキャプチャデバイスのオブジェクトの
      // インタフェースを得る
      pClassEnum -> Next(1, &pMoniker, &cFetched);
      pMoniker -> BindToObject( 0, 0, IID_IBaseFilter, (void**)&pbf );
      pMoniker -> Release();
      pDevEnum -> Release();
      pClassEnum -> Release();
      // ---- フィルタグラフの準備 ----
      IGraphBuilder * pGraph = NULL;
      IMediaControl * pMC = NULL;
      // フィルタグラフを作り、インターフェースを得る
      CoCreateInstance( CLSID_FilterGraph, NULL, CLSCTX_INPROC,
                IID_IGraphBuilder, (void **) &pGraph);
      pGraph -> QueryInterface( IID_IMediaControl, (LPVOID *) &pMC );
      // キャプチャフィルタをフィルタグラフに追加
      pGraph -> AddFilter( pbf, L"Video Capture");
      // ---- グラバフィルタの準備 ----
      ISampleGrabber  *pGrab = NULL;        // これらは後で解放すること。
      IBaseFilter     *pF  = NULL;
      AM_MEDIA_TYPE   amt;
      // グラバフィルタを作る
      CoCreateInstance( CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, 
                          IID_IBaseFilter, (LPVOID *)&pF);
      pF -> QueryInterface( IID_ISampleGrabber, (void **)&pGrab );
      // グラバフィルタの挿入場所の特定のための設定
      ZeroMemory(&amt, sizeof(AM_MEDIA_TYPE));
      amt.majortype  = MEDIATYPE_Video;
      amt.subtype    = MEDIASUBTYPE_RGB24;
      amt.formattype = FORMAT_VideoInfo; 
      pGrab -> SetMediaType( &amt );
      // グラバフィルタをフィルタグラフに追加
      pGraph -> AddFilter(pF, L"SamGra");
      // ---- キャプチャグラフの準備 ----
      ICaptureGraphBuilder2 * pCapture = NULL;
      // キャプチャグラフを作る   
      CoCreateInstance( CLSID_CaptureGraphBuilder2 , NULL, CLSCTX_INPROC,
                  IID_ICaptureGraphBuilder2, (void **) &pCapture );
      // フィルタグラフをキャプチャグラフに組み込む
      pCapture -> SetFiltergraph( pGraph );
      // キャプチャグラフの設定、グラバをレンダリング出力に設定
      pCapture -> RenderStream ( &PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video,
                                    pbf, NULL, pF);
      // ビットマップ情報の取得  
      pGrab -> GetConnectedMediaType( &amt ); 
      // ビデオ ヘッダーへのポインタを獲得する。
      printf( "amt.lSampleSize = %d (byte)¥n", amt.lSampleSize );
      VIDEOINFOHEADER *pVideoHeader = (VIDEOINFOHEADER*)amt.pbFormat;
      // ビデオ ヘッダーには、ビットマップ情報が含まれる。
      // ビットマップ情報を BITMAPINFO 構造体にコピーする。
      BITMAPINFO BitmapInfo;
      ZeroMemory( &BitmapInfo, sizeof(BitmapInfo) );
      CopyMemory( &BitmapInfo.bmiHeader, &(pVideoHeader->bmiHeader), sizeof(BITMAPINFOHEADER));
      printf( "width = %d , height %d , color %d ¥n",
            BitmapInfo.bmiHeader.biWidth, 
            BitmapInfo.bmiHeader.biHeight, 
            BitmapInfo.bmiHeader.biBitCount );
      printf( "キャプチャを開始します、どれかキーを押して下さい¥n" ); 
      getch();
      /*ここまでDirectShow用の処理 設定をいじらないならブラックボックス扱いで良い*/
    
      /*ここからOpenCVの処理*/
      IplImage *frame = 0;
      IplImage *dstImage = 0;
      IplImage *baseImage = 0;
      //カラー24bitという前提
      frame = cvCreateImage(cvSize(BitmapInfo.bmiHeader.biWidth, BitmapInfo.bmiHeader.biHeight),IPL_DEPTH_8U,3);
      dstImage = cvCreateImage(cvSize(BitmapInfo.bmiHeader.biWidth, BitmapInfo.bmiHeader.biHeight),IPL_DEPTH_8U,3);
      baseImage = cvCreateImage(cvSize(BitmapInfo.bmiHeader.biWidth, BitmapInfo.bmiHeader.biHeight),IPL_DEPTH_8U,3);
      cvNamedWindow("OpenCV",CV_WINDOW_AUTOSIZE);
      cvNamedWindow("Dst Image",CV_WINDOW_AUTOSIZE);
      /*ここまでOpenCVの処理*/
    
      // ---- キャプチャ開始 ----
      pMC -> Run();            // レンダリング開始
      pGrab -> SetBufferSamples(TRUE);  // グラブ開始
      int nn = 0;              // グラブ回数のカウント、意味はない
      int input=0;
      int fCount=1;
      char fname[2048];
      while(1){
        hr = pGrab -> GetCurrentBuffer((long *)&(BitmapInfo.bmiHeader.biSizeImage),  // グラブ
                         (long *)(frame->imageData) ); //<-ここで横取り
        cvFlip(frame,frame,0); //上下反転しているのでcvFlipで戻す
        if(nn == 1){ //初期背景画像
          cvCopyImage(frame,baseImage);
        }
        cvSub(frame,baseImage,dstImage); //差分画像の作成
        cvShowImage("Dst Image",dstImage); //差分の表示
        cvShowImage("OpenCV",frame); //現在画像の表示
        nn++;
        printf("グラブ hr = %x, n = %d¥n", hr, nn );
        input = cvWaitKey(100); //早過ぎると処理が間に合わない?
        switch(input){
          case 'q':
            goto EXIT_WHILE;
            break;
          case 'b':
            //背景の更新
            cvCopyImage(frame,baseImage);
            break;
          case 'c':
            //キャプチャ
            sprintf(fname,"frame%00d.jpg)",fCount);
            cvSaveImage(fname,frame);
            sprintf(fname,"sub%00d.jpg)",fCount);
            cvSaveImage(fname,dstImage);
            sprintf(fname,"base%00d.jpg)",fCount);
            cvSaveImage(fname,baseImage);
            fCount++;
            break;
        }
      }
    EXIT_WHILE:
      /* ここからDirectShowの終了処理 */
      // インターフェースのリリース
      pbf -> Release();              // キャプチャフィルタ用
      pMC -> Release();              // フィルタグラフ用
      pGraph -> Release();
      pCapture -> Release();            // キャプチャグラフ用
      pF -> Release();              // サンプルグラバ用            
      pGrab -> Release();
      // COMのリリース
      CoUninitialize();
      /*ここまでDirectShowの終了処理*/
    
      //OpenCVの後片付け
      cvDestroyWindow("OpenCV");
      cvDestroyWindow("Dst Image");
      cvReleaseImage(&frame);
      cvReleaseImage(&baseImage);
      cvReleaseImage(&dstImage);
    }

一見大変そうだがOpenCVで処理をしている部分以外は殆どブラックボックス化できる。 wrapperでも仕込めばもっとスマートに実装できそうだが、ちゃんと動いているので特に予定なし。

⚠️ **GitHub.com Fallback** ⚠️