0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C#のフォームにUSBカメラからOpenCvSharpで動画を取り込む

Posted at

 Windowsのカメラアプリが、PC環境の問題なのか長時間起動していると画面が灰色に表示され、OSを再起動しないとプレビューを表示できなくなる現象があった。USBカメラに対応したフリーソフトを探してもいいが、折角の機会なのでC#/OpenCvSharpで最低限必要な機能を作ってみた(プレビューを表示してボタンでキャプチャするだけの本当に最低限の機能だけ)。

 OpenCvSharpはNuGetから4.10.0.20240616をインストールしている。

GUI

 PictureBox1およびButton1を配置する。
 PictureBox1のAnchorはTop, Bottom, Left, Rightとし、Button1と合わせていい感じの場所に配置しておく。

コード

 フォームのコンストラクタ内に以下のコードを貼り付ける。

Progress<object?> pictureBoxRefresh =
    new Progress<object?>(_ => pictureBox1.Refresh());

_ = Task.Run(() =>
{
    using OpenCvSharp.VideoCapture cap = new(0)
    {
        FrameWidth = 1920,
        FrameHeight = 1080,
    };
    using OpenCvSharp.Mat frameBuff = new();
    using OpenCvSharp.Mat resizeDst1 = new();
    using OpenCvSharp.Mat resizeDst2 = new();

    var resizeBuff = resizeDst1;
    OpenCvSharp.Size dstSize = default;

    var isSrcCapture = false;
    button1.Click += (_, _)
     => isSrcCapture = true;

    var isDstSizeChange = true;
    pictureBox1.SizeChanged += (_, _)
     => isDstSizeChange = true;

    while (true)
    {
        if (!cap.Read(frameBuff))
        { continue; }

        if (isSrcCapture)
        {
            isSrcCapture = false;
            saveImage(frameBuff);
        }

        if (isDstSizeChange)
        {
            isDstSizeChange = false;
            dstSize = calcNewSize(frameBuff.Size(), pictureBox1.Size);

            if (dstSize.Width == 0)
            { continue; }

            resizeBuff = resizeBuff != resizeDst1 ? resizeDst1 : resizeDst2;

            OpenCvSharp.Cv2.Resize(frameBuff, resizeBuff, dstSize);

            using var dispose = pictureBox1.Image;
            pictureBox1.Image = new Bitmap(
                resizeBuff.Width, resizeBuff.Height, (int)resizeBuff.Step(),
                System.Drawing.Imaging.PixelFormat.Format24bppRgb, resizeBuff.Data);
        }

        if (dstSize.Width == 0)
        { continue; }

        OpenCvSharp.Cv2.Resize(frameBuff, resizeBuff, dstSize);

        pictureBoxRefresh.Report(null);
    }

    ; static OpenCvSharp.Size calcNewSize(OpenCvSharp.Size srcSize, Size dstSize)
    {
        var scale = double.Min(
            (double)dstSize.Width / srcSize.Width,
            (double)dstSize.Height / srcSize.Height);
        var w = (int)(scale * srcSize.Width) & ~3;
        var h = (int)((double)w / srcSize.Width * srcSize.Height);

        return new(w, h);
    }

    ; static void saveImage(OpenCvSharp.Mat frame)
    {
        var path = Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.MyPictures),
            @$"Camera Roll\{DateTime.Now:yyyyMMddTHHmmss}.jpg");

        var dir = Path.GetDirectoryName(path) ?? throw new Exception();
        if (!Directory.Exists(dir))
        {
            Directory.CreateDirectory(dir);
        }

        using Bitmap bmp =
            OpenCvSharp.Extensions.BitmapConverter.ToBitmap(frame);

        bmp.Save(path);
    }
});

雑記

 PictureBoxの大きさをFormの大きさに追従させているので、サイズ変化時のGUIの再描画周りでメモリアクセスの競合を避けるためにややこしいコードになっている(自分でPictureBox.Sizeを書き換えたほうが簡単に処理できるかもしれない)。

 Bitmapを作成する際はOpenCVのMatの生データへのポインタを渡している。Bitmap側の制限で横方向の生データが4バイトの整数倍である必要がある(OpenCVのMatでパディングができない)ため、リサイズの解像度を4の整数倍に切り捨てている。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?