"VideoInputSharp"をつくってみた

オープンソースプロジェクト第二弾。

https://github.com/shimat/videoinputsharp

videoInput.NET Framework向けラッパーです。videoInputというのは、DirectShowによるビデオキャプチャを容易にできる、C++向けライブラリです。Windows限定なのでラッパーはC++/CLIで実装しました。

OpenCVにもvideoInputは組み込まれているらしいのですが、どうもCvCaptureからだとフレームレートがいじれない様子。またそもそも、このラッパーは別にOpenCVを意識しているわけではなく、様々な用途に使うことができると思います。

出力としてはキャプチャした画像のピクセルデータのバイト配列(byte[])、もしくはそのポインタ(IntPtr)が得られます。何らかの方法で、そのデータをBitmapやIplImageなどの画像へ書き込む必要があります。


1つのカメラからキャプチャして、WindowsFormに表示するサンプルです。

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using VideoInputSharp;

class Program
{
    [STAThread]
    static void Main(string[] args)
    {
        const int DeviceID = 0;
        const int CaptureFps = 30;
        const int CaptureWidth = 640;
        const int CaptureHeight = 480;

        using (VideoInput vi = new VideoInput())
        {
            // initializes settings
            vi.SetIdealFramerate(DeviceID, CaptureFps);
            vi.SetupDevice(DeviceID, CaptureWidth, CaptureHeight);

            int width = vi.GetWidth(DeviceID);
            int height = vi.GetHeight(DeviceID);

            using (Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb))
            using (Form form = new Form() { Text = "VideoInputSharp sample", ClientSize = new Size(width, height) })
            using (PictureBox pb = new PictureBox() { Dock = DockStyle.Fill, Image = bitmap })
            {
                // allocates pixel data buffer
                byte[] buffer = new byte[vi.GetSize(DeviceID)];

                // to get the data from the device first check if the data is new
                if (vi.IsFrameNew(DeviceID))
                {
                    vi.GetPixels(DeviceID, buffer, false, true);
                }

                // shows window
                form.Controls.Add(pb);
                form.Show();

                // captures until the window is closed
                while (form.Created)
                {
                    if (vi.IsFrameNew(DeviceID))
                    {
                        vi.GetPixels(DeviceID, buffer, false, true);
                    }
                    WritePixels(bitmap, buffer);
                    pb.Refresh();
                    Application.DoEvents();
                }

                // stops capturing
                vi.StopDevice(DeviceID);
            }
        }
    }
    static void WritePixels(Bitmap bitmap, byte[] pixels)
    {
        BitmapData bd = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, bitmap.PixelFormat);
        Marshal.Copy(pixels, 0, bd.Scan0, pixels.Length);
        bitmap.UnlockBits(bd);
    }
}

SetIdealFramerateでフレームレートの設定、SetupDeviceでキャプチャの幅と高さの設定ができます。どちらも指定した通りの値に設定されない可能性があることに注意です。ですから、出力先のBitmapを作る際は希望の幅のCaptureWidthではなく、GetWidthにより実際に設定された幅の値を取得して使用するようにします。



OpenCvSharpと組み合わせるサンプル。3つのカメラからキャプチャします。

using System;
using OpenCvSharp;
using VideoInputSharp;

class CaptureByVideoInputSharp
{
    static void Main(string[] args)
    {
        const int DeviceID1 = 0;
        const int DeviceID2 = 1;
        const int DeviceID3 = 2;
        const int CaptureFps = 30;
        const int CaptureWidth = 640;
        const int CaptureHeight = 480;

        using (VideoInput vi = new VideoInput())
        {
            vi.SetIdealFramerate(DeviceID1, CaptureFps);
            vi.SetupDevice(DeviceID1, CaptureWidth, CaptureHeight);
            vi.SetupDevice(DeviceID2);
            vi.SetupDevice(DeviceID3);

            using (IplImage img1 = new IplImage(vi.GetWidth(DeviceID1), vi.GetHeight(DeviceID1), BitDepth.U8, 3))
            using (IplImage img2 = new IplImage(vi.GetWidth(DeviceID2), vi.GetHeight(DeviceID2), BitDepth.U8, 3))
            using (IplImage img3 = new IplImage(vi.GetWidth(DeviceID3), vi.GetHeight(DeviceID3), BitDepth.U8, 3))
            using (CvWindow window1 = new CvWindow("Camera 1"))
            using (CvWindow window2 = new CvWindow("Camera 2"))
            using (CvWindow window3 = new CvWindow("Camera 3"))
            {
                if (vi.IsFrameNew(DeviceID1))
                    vi.GetPixels(DeviceID1, img1.ImageData, false, true);
                if (vi.IsFrameNew(DeviceID2))
                    vi.GetPixels(DeviceID2, img2.ImageData, false, true);
                if (vi.IsFrameNew(DeviceID3))
                    vi.GetPixels(DeviceID3, img3.ImageData, false, true);

                while (true)
                {
                    if (vi.IsFrameNew(DeviceID1))
                        vi.GetPixels(DeviceID1, img1.ImageData, false, true);
                    if (vi.IsFrameNew(DeviceID2))
                        vi.GetPixels(DeviceID2, img2.ImageData, false, true);
                    if (vi.IsFrameNew(DeviceID3))
                        vi.GetPixels(DeviceID3, img3.ImageData, false, true);

                    window1.Image = img1;
                    window2.Image = img2;
                    window3.Image = img3;

                    if (Cv.WaitKey(1) > 0)
                        break;
                }

                vi.StopDevice(DeviceID1);
                vi.StopDevice(DeviceID2);
                vi.StopDevice(DeviceID3);
            }
        }
    }
}


IplImageで画像を受け取り、Bitmapに変換してWindowsForm(PictureBox)で表示するサンプルです。.NETのGUIを使い、かつキャプチャした画像に画像処理を加えたい場合に使えます。

IplImageからBitmapへの変換はToBitmapメソッドで行えますが、返り値にBitmapがくるものではなく、引数にBitmapを指定するものを使うと望ましいです。こうすると、ループ中でインスタンスを大量に生成されることなく、ピクセルデータのコピーのみが行われます。

const int DeviceID = 0;
const int CaptureFps = 30;
const int CaptureWidth = 640;
const int CaptureHeight = 480;

using (VideoInput vi = new VideoInput())
{
    vi.SetIdealFramerate(DeviceID, CaptureFps);
    vi.SetupDevice(DeviceID, CaptureWidth, CaptureHeight);

    int width = vi.GetWidth(DeviceID);
    int height = vi.GetHeight(DeviceID);

    using (IplImage img = new IplImage(width, height, BitDepth.U8, 3))
    using (Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb))
    using (Form form = new Form() { Text = "VideoInputSharp sample", ClientSize = new Size(width, height) })
    using (PictureBox pb = new PictureBox() { Dock = DockStyle.Fill, Image = bitmap })
    {
        if (vi.IsFrameNew(DeviceID))
        {
            vi.GetPixels(DeviceID, img.ImageData, false, true);
        }

        form.Controls.Add(pb);
        form.Show();

        while (form.Created)
        {
            if (vi.IsFrameNew(DeviceID))
            {
                vi.GetPixels(DeviceID, img.ImageData, false, true);
            }
            img.ToBitmap(bitmap);
            pb.Refresh();
            Application.DoEvents();
        }

        vi.StopDevice(DeviceID);
    }
}