top>プログラミング>キャプチャ


‖USBカメラ画像のキャプチャ

重要!!
これまでのサンプルにバグ&メモリーリークが発見されました。


ここでは,DirectXの機能の一つである,DirectShowを利用したUSBカメラ画像の表示,キャプチャプログラミングについて解説します.以下のプログラムをコンパイルする際にはDirectX SDKのインストールおよびVC++での設定が必要です.これらについてはプログラミングの準備をご覧ください

.

1. 全体の処理の流れ
2. ソースリスト
3. プロジェクトファイルのダウンロード

1. 全体の処理の流れ

USBカメラから画像情報を表示,キャプチャする処理の流れは以下のようになります.

@ グラフビルダー ( IGraphBuilder )
A デバイスフィルタ ( IBaseFilter )
B グラバフィルタ ( IBaseFilter )
C サンプルグラバ ( ISampleGrabber )
D メディアコントロール ( IMediaControl )

基本となるDirectShowオブジェクトは上の5つ.

USBカメラから入力された画像が“Aデバイスフィルタ”→“Bグラバフィルタ”→ディスプレイへと出力されます.また“Cサンプルグラバ”は“Bグラバフィルタ”からデータを取得し,BYTE型の画像データをコールバックとして外部出力します.この“コールバック”を参照することで,キャプチャ画像を取得することができます.(コールバックは裏で何回も呼ばれる処理で,スレッドと OnTimer を組み合わせたような動作をします.)

“Aデバイスフィルタ”と“Bグラバフィルタ”とディスプレイは“@グラフビルダー”によって接続され,“Dメディアコントロール”によって全体のスイッチが入り,USBカメラ画像が全体に流れます

Topへ


2. ソースリスト

ここでは,1で説明した大まかな処理の流れを細分化し,それぞれの処理をコード化していきます.

あっそうそう,DirectShowプログラムをやる前にはVC++の設定をしないとコンパイルが通りませんよ〜。

まずはじめに,DirectShowヘッダをインクルードします.


#include <dshowutil.cpp>
#include <atlbase.h>
#include <streams.h>
#include <qedit.h>

キーワードとなるのは5つのDirectShowオブジェクト.
グラフビルダー ( IGraphBuilder ) m_pGraph
デバイスフィルタ ( IBaseFilter ) pSrc
グラバフィルタ ( IBaseFilter ) pGrabber
サンプルグラバ ( ISampleGrabber ) m_pGrabberInterface
メディアコントロール ( IMediaControl ) pMediaControl

これらを生成,接続していくのが基本的なプログラムの流れです.

また,本プログラムでは,コールバック関数 CMySampleGrabberCB を ISampleGrabberCB から継承して実装しています.


MySampleGrabberCB.h


#include <dshow.h>

#include <qedit.h>

class CMySampleGrabberCB : public ISampleGrabberCB

{

public:

	void SetImage(unsigned char * pBuffer,int nBufferSize);

	CMySampleGrabberCB(int CameraNum);

	virtual ~CMySampleGrabberCB();



	STDMETHODIMP_(ULONG) AddRef() { return 2; }

	STDMETHODIMP_(ULONG) Release() { return 1; }

	STDMETHODIMP QueryInterface(REFIID riid, void ** ppv){

		if( riid == IID_ISampleGrabberCB || riid == IID_IUnknown ){

			*ppv = (void *) static_cast<ISampleGrabberCB*> ( this );

			return NOERROR;

		}

	return E_NOINTERFACE;

	}

	STDMETHODIMP SampleCB( double SampleTime, IMediaSample * pSample ){

		return S_OK;

	}

	STDMETHODIMP BufferCB( double dblSampleTime, BYTE * pBuffer, long lBufferSize ){

		int i;

		for(i = 0; i < lBufferSize; i++){

			m_Buffer[i] = pBuffer[i];

		}

		return S_OK;

	}



private:

	int m_iCameraNumber;

	unsigned char * m_Buffer; //コールバックで保存されるカメラ画像のバッファ

};

MySampleGrabberCB.cpp(9/4修正)


#include "stdafx.h"

#include "MySampleGrabberCB.h"



CMySampleGrabberCB::CMySampleGrabberCB(int CameraNum)

{

        m_iCameraNumber=CameraNum;

}



CMySampleGrabberCB::~CMySampleGrabberCB()

{
 //必要ありません
 /* 
      if(m_Buffer){

                delete[] m_Buffer;

                m_Buffer=NULL;

        }
 */
}



void CMySampleGrabberCB::SetImage(unsigned char * pBuffer,int nBufferSize)

{
 // newは必要ありません。
 //     m_Buffer=new unsigned char[nBufferSize];

        m_Buffer=pBuffer;

}

USBカメラ画像の表示,キャプチャ
@ COMの開始 CoInitialize(NULL);
A グラフビルダーの生成 CComPtr< IGraphBuilder > m_pGraph;
m_pGraph.CoCreateInstance(CLSID_FilterGraph);
B デバイスフィルタの生成 CComPtr< IBaseFilter > pSrc;
B−@ デバイス列挙子の生成 CComPtr< ICreateDevEnum > pDevEnum;
pDevEnum.CoCreateInstance(CLSID_SystemDeviceEnum)
B−A モニカ列挙子の生成 CComPtr< IEnumMoniker > pClassEnum;
pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pClassEnum, 0)
pClassEnum->Reset();
B−B モニカの取得 IMoniker * pMoniker = NULL;
ULONG cFetched;
pClassEnum->Next(1, &pMoniker, &cFetched);
B−C モニカをデバイスフィルタにバインド pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void **)pSrc);
pMoniker->Release();
B−D デバイスフィルタグラフビルダーに追加 m_pGraph->AddFilter(pSrc, L"Video Capture");
C サンプルグラバの生成とグラバフィルタとの接続 CComPtr< ISampleGrabber > m_pGrabberInterface;
m_pGrabberInterface.CoCreateInstance(CLSID_SampleGrabber);
CComQIPtr< IBaseFilter, &IID_IBaseFilter > pGrabber(m_pGrabberInterface);
C−A サンプルグラバのメディアタイプを設定 CMediaType m_VideoType;
SetMyMediaTypes(&m_VideoType);
m_pGrabberInterface->SetMediaType(&m_VideoType);
C−B グラバフィルタグラフビルダーに追加 m_pGraph->AddFilter(pGrabber,L"SampleGrabber");
D デバイスフィルタグラバフィルタの接続 IPin * pCapOut = GetOutPin(pSrc, 0);
IPin * pGrabIn = GetInPin(pGrabber, 0);
m_pGraph->Connect(pCapOut, pGrabIn);
E グラフビルダーとディスプレイの接続 IPin * pGrabOut = GetOutPin(pGrabber, 0);
m_pGraph->Render(pGrabOut);
F サンプルグラバの動作設定 m_pGrabberInterface->SetBufferSamples(FALSE);
m_pGrabberInterface->SetOneShot(FALSE);
F−A コールバックの生成と設定 CMySampleGrabberCB * m_MySampleGrabberCB = new CMySampleGrabberCB();
m_MySampleGrabberCB->SetImage(m_CamImage, m_Size);
F−B コールバックの起動 m_pGrabberInterface->SetCallback(m_MySampleGrabberCB, 1);
G メディアコントロールの接続 CComQIPtr< IMediaControl, &IID_IMediaControl > pMediaControl = m_pGraph;
G−A メディアコントロールの起動 pMediaControl->Run();

ここまででSDIによるサンプルウィンドウが表示されるはずです.実際にソースを書く際には,エラー処理をいれてください.特にG−ApMediaControl->Run(); が失敗する可能性があるので,成功するまで Run() を繰り返すのがよいでしょう

終了処理
@ メディアコントロールの呼び出し CComQIPtr<IMediaControl, &IID_IMediaControl > pMediaControl = m_pGraph;
@−A メディアコントロールの停止 pMediaControl->Stop();
A グラフビルダーの開放 m_pGraph.Release();
B コールバックの停止 m_pGrabberInterface->SetCallback(NULL, 1);
C サンプルグラバの開放 m_pGrabberInterface.Release();
D コールバックの消滅 delete m_pMySampleGrabberCB;
E COMの終了 CoUninitialize();

ここでも,@−ApMediaControl->Stop();が失敗する可能性があるので,Stop するまで繰り返し実行してください.

Topへ


3. プロジェクトファイルのダウンロード
(自分でCamera.hをカスタマイズしたい方用)

 

冒頭でも述べましたが,CCameraクラス(DirectX)を使用する際にはVC++上でのプロジェクト設定およびリンクファイルへのパス設定が必要になります.これらについてはプログラミングの準備をご覧ください.
また、以下のファイルはVC++.NET2002で作成しています。その他のバージョンでは少しいじらないと動かないかもしれません。


Camera.h と camera.cpp のみ CCameraSet.zip 4kB 2004/9/4
カメラ1台使用のサンプルプログラム CCameraSample.zip 31kB 2004/9/4
スナップ機能付サンプルプログラム
(VC++.NET DirectX9を利用)
CamSamp.zip 67kB 2004/9/4
カメラ2台用サンプルプログラム
(VC++.NET DirectX9を利用)
CameraSample2.zip 66kB 2005/12/1

CCameraの使い方1(ピクチャボックスへの表示)


HWND hPicture = NULL;
GetDlgItem(IDC_PICTBOX,&hPicture);
CCamera cam(1);
cam->InitGraphs(&hPicture);
      ・
      ・
      ・
cam->Close();
@HWNDのインスタンス作成
AピクチャボックスのとHWNDをつなぐ
Bカメラのインスタンス作成.引数はカメラ番号
C初期化&表示
 
 
 
Dカメラの終了

カメラを2台用いる場合は以下のようにする


CCamera cam1(1)
CCamera cam2(2)

また,ピクチャボックスへの表示を1枚1枚 OnTimer で表示する場合は InitGraphs(0) を, DirectShowのプレビューウィンドウを表示する場合は InitGraphs(1) を使用する.
注意! カメラのインスタンスを作ったら,必ずcam->Close()を実行すること!cam->Close()を実行しなければ,Windowsがシャットダウンすることがあります.(私の使っているノートPCは7割くらいの確率でWindows2000が落ちます.)
また,デバッグを終了するときも注意!
デバッグの中止ボタンを押すとクラスのデストラクタが実行されないので,cam->Close()を必ず実行できるところにおく必要があります.私の場合はcam->Close()をWM_CLOSEのOnClose()内においています.そして,デバッグを中止する際には必ず自分で作成したダイアログボックスの×ボタンを押して終了させています.(めんどくさいけどね)
この問題はじきに解決したいと考えています.
って言うか,落ちるのはThinkPadだけのような気が・・・

最後に,CCameraクラスの作成において,ベースとなるコードを書いてくれた増澤氏に感謝!

Topへ


@ プログラミングの準備
A ソケット(TCP/IP)
B スレッド
C USBカメラ画像のキャプチャ
D OpenGL
E Text to Speech