OpenCVSharp「平均化フィルタ」を試す2。「filter2d」

C# コンピュータ
C#

OpenCVのサンプルはpythonで書かれていることが多いのですが、C#(OpenCVSharp)で扱う場合、変数の型がC#の何を使えばよいかが問題になります。画像などのオブジェクトはOpenCVSharp用にMatオブジェクトがあるので、それを使えば良いのですが、filter2dの引数にカーネルを指定する必要があります。pythonの場合numpy.ndarray型をセットしていましたが、C#の場合double[,]でdobuleの2次元配列を作成しInputArray.Create()でfilter2d用の引数に変換しています。この辺りの変換のルールを把握できれば、pyhonで書かれたOpenCVのサンプルをC#に変換することが出来ます。

実行環境構築

プロジェクトの作成

mkdir BlurSample2
cd BlurSample2
dotnet new winforms
dotnet add package OpenCvSharp4.Windows
dotnet add package OpenCvSharp4.Extensions
code .

ソースプログラム

namespace BlurSample2;

using OpenCvSharp;
using OpenCvSharp.Extensions;

public partial class Form1 : Form
{
    Bitmap? bmp = null;

    static double[,] kernel3x3 = {
        { 1.0/9.0,  1.0/9.0, 1.0/9.0},
        { 1.0/9.0,  1.0/9.0, 1.0/9.0},
        { 1.0/9.0,  1.0/9.0, 1.0/9.0},
    };
    static double[,] kernel5x5 = {
        { 1.0/25.0,  1.0/25.0, 1.0/25.0, 1.0/25.0, 1.0/25.0},
        { 1.0/25.0,  1.0/25.0, 1.0/25.0, 1.0/25.0, 1.0/25.0},
        { 1.0/25.0,  1.0/25.0, 1.0/25.0, 1.0/25.0, 1.0/25.0},
        { 1.0/25.0,  1.0/25.0, 1.0/25.0, 1.0/25.0, 1.0/25.0},
        { 1.0/25.0,  1.0/25.0, 1.0/25.0, 1.0/25.0, 1.0/25.0},
    };
    static double[,] kernel7x7 = {
        { 1.0/49.0,  1.0/49.0, 1.0/49.0, 1.0/49.0, 1.0/49.0, 1.0/49.0, 1.0/49.0},
        { 1.0/49.0,  1.0/49.0, 1.0/49.0, 1.0/49.0, 1.0/49.0, 1.0/49.0, 1.0/49.0},
        { 1.0/49.0,  1.0/49.0, 1.0/49.0, 1.0/49.0, 1.0/49.0, 1.0/49.0, 1.0/49.0},
        { 1.0/49.0,  1.0/49.0, 1.0/49.0, 1.0/49.0, 1.0/49.0, 1.0/49.0, 1.0/49.0},
        { 1.0/49.0,  1.0/49.0, 1.0/49.0, 1.0/49.0, 1.0/49.0, 1.0/49.0, 1.0/49.0},
        { 1.0/49.0,  1.0/49.0, 1.0/49.0, 1.0/49.0, 1.0/49.0, 1.0/49.0, 1.0/49.0},
        { 1.0/49.0,  1.0/49.0, 1.0/49.0, 1.0/49.0, 1.0/49.0, 1.0/49.0, 1.0/49.0},
    };
    static double[,] kernel9x9 = {
        { 1.0/81.0,  1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0},
        { 1.0/81.0,  1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0},
        { 1.0/81.0,  1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0},
        { 1.0/81.0,  1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0},
        { 1.0/81.0,  1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0},
        { 1.0/81.0,  1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0},
        { 1.0/81.0,  1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0},
        { 1.0/81.0,  1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0},
        { 1.0/81.0,  1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0, 1.0/81.0},
    };
    
    // フィルター(平均化フィルター)
    static Bitmap Filter(Bitmap b, int sz) {

        using var mat = BitmapConverter.ToMat(b);
        using var result = new Mat();
        switch (sz) {
            case 3:
            Cv2.Filter2D(mat, result, -1, InputArray.Create(kernel3x3));
                break;            
            case 5:
            Cv2.Filter2D(mat, result, -1, InputArray.Create(kernel5x5));
                break;            
            case 7:
            Cv2.Filter2D(mat, result, -1, InputArray.Create(kernel7x7));
                break;            
            case 9:
            Cv2.Filter2D(mat, result, -1, InputArray.Create(kernel9x9));
                break;            
        }
        return BitmapConverter.ToBitmap(result);
    }

    public Form1()
    {
        InitializeComponent();

        Text = "平均化フィルター";

        var sc = new SplitContainer {
            Dock = DockStyle.Fill,
            Orientation = Orientation.Horizontal,   // 上下
            Panel1MinSize = 100,
        };
        var picbox = new PictureBox {
            Dock = DockStyle.Fill,
            AllowDrop = true,
            SizeMode = PictureBoxSizeMode.Zoom,
            Image = bmp,
        };
        picbox.DragEnter += (o, e) => {
            if (e.Data == null) return;
            if(!e.Data.GetDataPresent(DataFormats.FileDrop)) return;
            e.Effect = DragDropEffects.Copy;
        };
        var toClip = new Button {
            Text = "クリップボードへ",
            Location = new System.Drawing.Point(10, 10),
            Size = new System.Drawing.Size(180, 50),
            Enabled = false,
        };
        var filterExec = new RadioButton {
            Text = "フィルターOFF",
            Appearance = Appearance.Button,
            AutoCheck = false,
            Location = new System.Drawing.Point(200, 10),
            Size = new System.Drawing.Size(180, 50),
            Enabled = false,
        };
        var kernelSizelabel = new Label {
            Text = "カーネルサイズ",
            Location = new System.Drawing.Point(400, 10),
            Size = new System.Drawing.Size(180, 50),
        };
        var kernelSize = new ComboBox {
            ItemHeight = 20,
            Location = new System.Drawing.Point(600, 10),
            Size = new System.Drawing.Size(180, 50),
        };
        kernelSize.Items.AddRange(new string[] {"3x3", "5x5", "7x7", "9x9"});
        kernelSize.SelectedIndex = 0;

        picbox.DragDrop += async (sender, e) => {
            // ファイルドロップ
            if (e.Data == null) return;
            if(!e.Data.GetDataPresent(DataFormats.FileDrop)) return;
            string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
            if (files.Length < 1) return;

            // 読み込み
            if (picbox.Image != null) picbox.Image.Dispose();
            await Task.Run(()=>{
                using var fs = new System.IO.FileStream(
                    files[0], System.IO.FileMode.Open,  System.IO.FileAccess.Read);
                bmp = (Bitmap)System.Drawing.Image.FromStream(fs);
            });
            if (bmp != null) {
                picbox.Image = (Bitmap)bmp.Clone();
                toClip.Enabled = true;
                filterExec.Enabled = true;
            }
        };
        toClip.Click += (sender, e) => {
            toClip.Enabled = false;
            Clipboard.SetImage(picbox.Image);
            toClip.Enabled = true;
        };
        filterExec.Click += async (sender, e) => {
            filterExec.Enabled = false;

            filterExec.Checked = !filterExec.Checked;
            if (picbox.Image != null) picbox.Image.Dispose();
            
            if (filterExec.Checked) {
                // フィルター
                int sz = 0;
                switch(kernelSize.SelectedIndex) {
                    case 0:
                        sz = 3;
                        break;
                    case 1:
                        sz = 5;
                        break;
                    case 2:
                        sz = 7;
                        break;
                    case 3:
                        sz = 9;
                        break;
                }
                if (bmp != null) {
                    Bitmap? result = null;
                    await Task.Run(()=>{
                        result = Filter(bmp, sz);
                    });
                    if (result != null) {
                        picbox.Image = result;
                        filterExec.Text = "フィルターON";
                    }
                }
            } else {
                if (bmp != null ) {
                    picbox.Image = (Bitmap)bmp.Clone();
                    filterExec.Text = "フィルターOFF";
                }
            }


            filterExec.Enabled = true;
        };
        sc.Panel1.Controls.Add(picbox);
        sc.Panel2.Controls.Add(toClip);
        sc.Panel2.Controls.Add(filterExec);
        sc.Panel2.Controls.Add(kernelSizelabel);
        sc.Panel2.Controls.Add(kernelSize);
        Controls.Add(sc);

        this.FormClosed += (sender, o) => {
            if (bmp != null) bmp.Dispose();
        };
    }
}

実行

dotnet run

感想

filter2dをで平均値フィルタをするためにカーネルを記述しています。はじめカーネルの配列に1/9と設定していたのですが、フィルタを実行すると真っ黒の画像になりました。doubleを指定しているので自動的にキャストをしてくれるだろうと期待しましたが、真っ黒になったところを見るとint同士の割り算を実行し、その答えをdobuleにキャストしたと思われます。1/9=(double)0=>0.0d

コメント