USBカメラから画像をキャプチャ

最近USBカメラが激安でおどろいて買ってしまいました。
リスナーさんから「オセロの盤面を認識したらどうだろう」と言われて、「えっ、つまんね」と思ったので作ってみました。

一次に参考にしたのはココ。
http://mobiquitous.com/programming/usbcamera.html
この先に、C#用のDirectX9UP DirectShow用のラップ用ライブラリが紹介されていますので、まずそれをダウンロードします。
http://www.codeproject.com/KB/directx/directshownet.aspx

ただ、VS2002時代のソースなのかターゲット?が.NET 1.1?のようで同梱のコンパイル済みDLLをそのまま参照してもうまくいかないようなので、まず自分でその.slnを開いて変換してビルドします。

で、自プロジェクトを作成してそのDLLを参照します。

必要なのは任意のタイミングでの一枚ずつの画像の撮影なので、それ用のプログラムを上記サイトからパクらせてもらいました。ただ、画像のキャプチャ完了のイベントがどんなタイミングで発生するのかすぐわからなかったので、突貫工事のためプレビューさせて発生させました。

ボタンのクリックでコールバックを登録します。その直後のプレビューのキャプチャで一枚撮影されていると思います。

        private void button1_Click(object sender, EventArgs e)
        {
            int size = videoInfoHeader.BmiHeader.ImageSize;

            frameArray = new byte[size + 64000];

            //bool capturedFlag = false;
            capturedFlag = false;

            //ビデオデータのサンプリングに利用するコールバック メソッドを指定する.
            //第一引数	 ISampleGrabberCB インターフェイスへのポインタ
            //			 nullを指定するとコールバックを解除
            //第二引数	0->ISampleGrabberCB.SampleCB メソッドを利用 	
            //			1->ISampleGrabberCB.BufferCB メソッドを利用
            result = sampleGrabber.SetCallback(this, 1);

        }

第2引数が 1 なので、BufferCBメソッドが呼ばれます。
ってことで、

public Form1()
        {
            InitializeComponent();
            InitDev();
        }
        private void InitDev()
        {

って感じで
InitDev()
SampleCB()
BufferCB()
OnCaptureDone()
の各メソッドに必要な順序で必要な処理を書いて行きます。
WndProc(ref Message m)
OnGraphNotify()
もあんまりよくわかっていないもののないと動かないようなのでそのままパクらせていただきました。

ということでできた Form1.cs が以下の通りです。これで動くはず;;
ってことでオセロのほうは輝度あたりで石の色、有無を判定する方向で続行。あと、
http://kotatuinu.cocolog-nifty.com/blog/2010/10/cusb-07bf.html
さんのほうで、C#OpenCVラッパー経由でOpenCVを使う方法が紹介されているようなので、OpenCVでエッジ検出とかやってエッジの顔出しくらいだったらやってもいいかな…。とりま以上です。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

using System.Collections;
using System.Drawing.Imaging;
using DShowNET;
using DShowNET.Device;

namespace CameraReversi1
{
    public partial class Form1 : Form , ISampleGrabberCB
    {
        private ArrayList captureDevices;
        DsDevice device = null;

        private IBaseFilter captureFilter; //ソースフィルタ
        object captureObject = null;

        private IGraphBuilder graphBuilder; //基本的なフィルタグラフマネージャ

        private IVideoWindow videoWindow; //オーナーウィンドウの位置やサイズなどの設定用のインタフェース.
        private IMediaControl mediaControl;//データのストリーミングの移動、ポーズ、停止などの処理用のインタフェース.
        private IMediaEventEx mediaEvent; //DirectShowイベント処理用のインタフェース

        private ICaptureGraphBuilder2 captureGraphBuilder;//ビデオキャプチャ&編集用のメソッドを備えたキャプチャグラフビルダ
        private ISampleGrabber sampleGrabber; //フィルタグラフ内を通る個々のデータ取得用のインタフェース. 
        private IBaseFilter grabFilter; //Grabber Filterのインタフェース.
        int result; //エラー判定用フラグ

        VideoInfoHeader videoInfoHeader; //ビデオイメージのフォーマットを記述する構造体
        Guid pinCategory;
        Guid mediaType;

        private byte[] frameArray;//キャプチャしたフレームデータ用の配列	
        private delegate void CaptureDone();//静止画キャプチャ終了時の処理を行うデリゲード
        private Bitmap capturedBitmap = null;//キャプチャしたフレームデータ用のビットマップ

        private bool capturedFlag = false;	//キャプチャの終了を示すフラグ

        
        private bool updatedFlag = false;   //ビットマップの更新を示すフラグ

        public const int WM_GRAPHNOTIFY = 0x00008001;	//DirectShowイベントの発生を示すWindows メッセージ.


        public Form1()
        {
            InitializeComponent();
            InitDev();
        }
        private void InitDev()
        {
            //PCに接続されているキャプチャデバイス(USBカメラなど)のリストを取得.
            if (!DsDev.GetDevicesOfCat(FilterCategory.VideoInputDevice, out captureDevices))
            {
                MessageBox.Show("ビデオキャプチャ可能なデバイスが見つかりません.");
                return;
            }
            //最初に見つかったデバイスを選択
            device = captureDevices[0] as DsDevice;
            Console.WriteLine(device.Name);

            //DicrectShow関連の初期化
            /*if (!InitVideo(device.Mon))
                return;
            */

            //キャプチャデバイス(device)とソースフィルタ(captureFilter)を対応付ける.
            Guid guidBF = typeof(IBaseFilter).GUID;
            device.Mon.BindToObject(null, null, ref guidBF, out captureObject);
            captureFilter = (IBaseFilter)captureObject;


            Type comType = null;
            object comObject = null;

            //graphBuilderを作成.
            comType = Type.GetTypeFromCLSID(Clsid.FilterGraph);
            comObject = Activator.CreateInstance(comType);
            graphBuilder = (IGraphBuilder)comObject;
            comObject = null;

            //各種インタフェースを取得.
            mediaControl = (IMediaControl)graphBuilder;
            videoWindow = (IVideoWindow)graphBuilder;
            mediaEvent = (IMediaEventEx)graphBuilder;

            //キャプチャグラフビルダ(captureGraphBuilder)を作成.
            comType = Type.GetTypeFromCLSID(Clsid.CaptureGraphBuilder2);
            comObject = Activator.CreateInstance(comType);
            captureGraphBuilder = (ICaptureGraphBuilder2)comObject;
            comObject = null;

            //サンプルグラバ(sampleGrabber)を作成
            comType = Type.GetTypeFromCLSID(Clsid.SampleGrabber);
            comObject = Activator.CreateInstance(comType);
            sampleGrabber = (ISampleGrabber)comObject;
            comObject = null;

            //フィルタと関連付ける.
            grabFilter = (IBaseFilter)sampleGrabber;

            //キャプチャするビデオデータのフォーマットを設定.
            AMMediaType amMediaType = new AMMediaType();
            amMediaType.majorType = MediaType.Video;
            amMediaType.subType = MediaSubType.RGB24;
            amMediaType.formatType = FormatType.VideoInfo;

            int result;

            result = sampleGrabber.SetMediaType(amMediaType);
            if (result < 0) Marshal.ThrowExceptionForHR(result);

            //captureGraphBuilder(キャプチャグラフビルダ)をgraphBuilder(フィルタグラフマネージャ)に追加.
            result = captureGraphBuilder.SetFiltergraph(graphBuilder);
            if (result < 0) Marshal.ThrowExceptionForHR(result);

            //captureFilter(ソースフィルタ)をgraphBuilder(フィルタグラフマネージャ)に追加.
            result = graphBuilder.AddFilter(captureFilter, "Video Capture Device");
            if (result < 0) Marshal.ThrowExceptionForHR(result);

            //grabFilter(変換フィルタ)をgraphBuilder(フィルタグラフマネージャ)に追加.
            result = graphBuilder.AddFilter(grabFilter, "Frame Grab Filter");
            if (result < 0) Marshal.ThrowExceptionForHR(result);

            //キャプチャフィルタをサンプルグラバーフィルタに接続する.
            pinCategory = PinCategory.Capture;
            mediaType = MediaType.Video;
            result = captureGraphBuilder.RenderStream(ref pinCategory, ref mediaType,
            captureFilter, null, grabFilter);
            if (result < 0) Marshal.ThrowExceptionForHR(result);

            //キャプチャフィルタをデフォルトのレンダラフィルタ(ディスプレイ上に出力)に接続する.(プレビュー)
            pinCategory = PinCategory.Preview;
            mediaType = MediaType.Video;
            result = captureGraphBuilder.RenderStream(ref pinCategory, ref mediaType,
            captureFilter, null, null);
            if (result < 0) Marshal.ThrowExceptionForHR(result);

            //フレームキャプチャの設定が完了したかを確認する.
            amMediaType = new AMMediaType();
            result = sampleGrabber.GetConnectedMediaType(amMediaType);
            if (result < 0) Marshal.ThrowExceptionForHR(result);
            if ((amMediaType.formatType != FormatType.VideoInfo) || (amMediaType.formatPtr == IntPtr.Zero))
                throw new NotSupportedException("キャプチャできないメディアフォーマットです.");

            //キャプチャするビデオデータのフォーマットから,videoInfoHeaderを作成する.
            videoInfoHeader =
            (VideoInfoHeader)Marshal.PtrToStructure(amMediaType.formatPtr, typeof(VideoInfoHeader));
            Marshal.FreeCoTaskMem(amMediaType.formatPtr);
            amMediaType.formatPtr = IntPtr.Zero;

            //フィルタ内を通るサンプルをバッファにコピーしないように指定する.
            result = sampleGrabber.SetBufferSamples(false);
            //サンプルを一つ(1フレーム)受け取ったらフィルタを停止するように指定する.
            if (result == 0) result = sampleGrabber.SetOneShot(false);
            //コールバック関数の利用を停止する.
            if (result == 0) result = sampleGrabber.SetCallback(null, 0);
            if (result < 0) Marshal.ThrowExceptionForHR(result);

            //プレビューを開始する.
            result = mediaControl.Run();

            if (result < 0)
                Marshal.ThrowExceptionForHR(result);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            int size = videoInfoHeader.BmiHeader.ImageSize;

            frameArray = new byte[size + 64000];

            //bool capturedFlag = false;
            capturedFlag = false;

            //ビデオデータのサンプリングに利用するコールバック メソッドを指定する.
            //第一引数	 ISampleGrabberCB インターフェイスへのポインタ
            //			 nullを指定するとコールバックを解除
            //第二引数	0->ISampleGrabberCB.SampleCB メソッドを利用 	
            //			1->ISampleGrabberCB.BufferCB メソッドを利用
            result = sampleGrabber.SetCallback(this, 1);

        }

        //フレームキャプチャ完了時に呼び出されるコールバック関数
        int ISampleGrabberCB.SampleCB(double SampleTime, IMediaSample pSample)
        {
            return 0;
        }

        //フレームキャプチャ完了時に呼び出されるコールバック関数
        int ISampleGrabberCB.BufferCB(double SampleTime, IntPtr pBuffer, int BufferLength)
        {
            //既にコールバックが呼び出されているか,frameArrayが初期化されていなければ終了.
            if (capturedFlag || (frameArray == null))
            {
                return 0;
            }

            capturedFlag = true;
            //メモリ内のサンプリングされたデータ(pBuffer)を配列(frameArray)にコピーする.
            if ((pBuffer != IntPtr.Zero) && (BufferLength > 1000) && (BufferLength <= frameArray.Length))
                Marshal.Copy(pBuffer, frameArray, 0, BufferLength);

            try
            {
                //CaptureDoneデリゲードを呼び出す.
                this.BeginInvoke(new CaptureDone(this.OnCaptureDone));
            }
            catch (Exception e)
            {
                MessageBox.Show(e.ToString());
            }
            return 0;
        }

        //Windows メッセージの処理
        protected override void WndProc(ref Message m)
        {
            if (m.Msg == WM_GRAPHNOTIFY)
            {
                if (mediaEvent == null)
                    this.OnGraphNotify();
                return;
            }
            base.WndProc(ref m);
        }

        //DirectShowイベントの処理
        public void OnGraphNotify()
        {
            DsEvCode code;
            int param1, param2, result = 0;

            while (result == 0)
            {
                result = mediaEvent.GetEvent(out code, out param1, out param2, 0);
                if (result < 0) break;
                result = mediaEvent.FreeEventParams(code, param1, param2);
            }

        }
        
        /*以下はデリゲード内の処理.
        void OnCaptureDone(){ }
        */
		void OnCaptureDone()
		{
			try
			{
				int result;
				if(sampleGrabber == null) return;

				//コールバック関数の利用を停止する
				result = sampleGrabber.SetCallback(null, 0);

				//フレームデータのサイズを取得
				int width = videoInfoHeader.BmiHeader.Width;
				int height = videoInfoHeader.BmiHeader.Height;

				//widthが4の倍数でない場合&widthとheightの値が適正でない場合は終了.
				if( ((width & 0x03) != 0) || (width < 32) || (width > 4096) || (height < 32) || (height> 4096) )
					return;

				//stride(1ライン分のデータサイズ(byte)=width* 3(RGB))を設定.
				int stride = width * 3;

				//配列frameArrayのアドレスを,メモリ空間内で固定する.
				GCHandle gcHandle = GCHandle.Alloc(frameArray, GCHandleType.Pinned);
				
				int addr = (int) gcHandle.AddrOfPinnedObject();
				addr += (height - 1) * stride;
				
				//frameArrayを格納したメモリアドレスから,ビットマップデータを作成.
				Bitmap bitmap = new Bitmap(width,height,-stride,PixelFormat.Format24bppRgb, (IntPtr) addr);
				capturedBitmap = new Bitmap(width,height,-stride,PixelFormat.Format24bppRgb, (IntPtr) addr);
				gcHandle.Free();
				frameArray = null;
				
				Image pre = null;

				//画面上にキャプチャした画像を表示する.
			
				//pre = parentForm.PictureBoxImage;
				//parentForm.PictureBoxImage =  bitmap;
                pre = pictureBox1.Image;
                pictureBox1.Image = bitmap;

				if(pre != null) pre.Dispose();
				
				//更新完了.
				updatedFlag = true;

				return;
			}
			catch(Exception e)
			{
				MessageBox.Show("フレームのキャプチャ(Grab)に失敗しました." + e.ToString());
			}
		}
    }
}