OpenCVSharp「インペイント」を試す。その3

C# コンピュータ
C#
前回からの変更点として、フィルターをONにした状態でコンボボックスのフィルターの種類を変更するとフィルターが切り替わるようにしました。水平(垂直)は単色でしたが始点と終点の2色を使うようにしてみました。
その2
OpenCVSharp「インペイント」を試す。その2
前回作成したプログラムでは紫色に塗りつぶされた部分をインペイント処理を施しましが、GIMPで紫色に潰す作業が意外と面倒なので透明部分をインペイント処理をするように変更してみました。実行環境構築プロジェクトの作成mkdir プロジェクト名cd...

ファイル名:Form1.cs
namespace InpaintSample3;

using System.Diagnostics;
using OpenCvSharp;
using OpenCvSharp.Extensions;
public partial class Form1 : Form
{
    Mat? view = null;

    public class Line_xy
    {
        public int SY {get; set;} = 0;
        public int SX {get; set;} = 0;
        public int EY {get; set;} = 0;
        public int EX {get; set;} = 0;
    };

    // 垂直(2色)
    static Mat verticalFilterTwoColors(Mat src, Mat mask)
    {
        Mat result = src.Clone();

        var line_xy = new List<Line_xy>();

        // 横方向へループ
        for (int x = 0; x < src.Width; x++) {
            int start_y = -1;
            // 縦方向へループ
            for (int y = 0; y < src.Height; y++) {
                byte b = mask.At<byte>(y, x);

                if (b == 255) {
                    // ON(WHITE)
                    if (start_y == -1) {
                        start_y = y; // 開始座標をセット
                    }
                } else {
                    // OFF(BLACK)
                    if (start_y > -1) {
                        var o = new Line_xy { SY = start_y, SX = x, EY = y-1, EX = x};
                        line_xy.Add(o);
                        start_y = -1;
                    }
                }
            } // for y
            if (start_y > -1) {
                var o = new Line_xy { SY = start_y, SX = x, EY = result.Height-1, EX = x};
                line_xy.Add(o);
            }
        }// for x

        foreach(var line in line_xy) {
            if (line.SY > 0) {
                var c = result.At<Vec4b>(line.SY-1, line.SX);
                Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));

                if (line.EY < (result.Height-1))
                {
                    int h = Math.Abs(line.EY - line.SY) / 2;
                    var c2 = result.At<Vec4b>(line.EY+1, line.EX);
                    Cv2.Line(result, line.SX, (line.SY+h), line.EX, line.EY, new Scalar(c2[0], c2[1], c2[2], 255));
                }

            } else if (line.EY < (result.Height-1)) {
                var c = result.At<Vec4b>(line.EY+1, line.EX);
                Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));
            }
        }
        return result;
    }
    // 垂直(単色・上から下へ)
    static Mat verticalFilterTop(Mat src, Mat mask)
    {
        Mat result = src.Clone();

        var line_xy = new List<Line_xy>();

        // 横方向へループ
        for (int x = 0; x < src.Width; x++) {
            int start_y = -1;
            // 縦方向へループ
            for (int y = 0; y < src.Height; y++) {
                byte b = mask.At<byte>(y, x);

                if (b == 255) {
                    // ON(WHITE)
                    if (start_y == -1) {
                        start_y = y; // 開始座標をセット
                    }
                } else {
                    // OFF(BLACK)
                    if (start_y > -1) {
                        var o = new Line_xy { SY = start_y, SX = x, EY = y-1, EX = x};
                        line_xy.Add(o);
                        start_y = -1;
                    }
                }
            } // for y
            if (start_y > -1) {
                var o = new Line_xy { SY = start_y, SX = x, EY = result.Height-1, EX = x};
                line_xy.Add(o);
            }
        }// for x

        foreach(var line in line_xy) {
            if (line.SY > 0) {
                var c = result.At<Vec4b>(line.SY-1, line.SX);
                Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));

            } else if (line.EY < (result.Height-1)) {
                var c = result.At<Vec4b>(line.EY+1, line.EX);
                Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));
            }
        }
        return result;
    }
    // 垂直(単色・下から上へ)
    static Mat verticalFilterBottom(Mat src, Mat mask)
    {
        Mat result = src.Clone();

        var line_xy = new List<Line_xy>();

        // 横方向へループ
        for (int x = 0; x < src.Width; x++) {
            int start_y = -1;
            // 縦方向へループ
            for (int y = src.Height-1; y >= 0; y--) {
                byte b = mask.At<byte>(y, x);

                if (b == 255) {
                    // ON(WHITE)
                    if (start_y == -1) {
                        start_y = y; // 開始座標をセット
                    }
                } else {
                    // OFF(BLACK)
                    if (start_y > -1) {
                        var o = new Line_xy { SY = start_y, SX = x, EY = y-1, EX = x};
                        line_xy.Add(o);
                        start_y = -1;
                    }
                }
            } // for y
            if (start_y > -1) {
                var o = new Line_xy { SY = start_y, SX = x, EY = result.Height-1, EX = x};
                line_xy.Add(o);
            }
        }// for x

        foreach(var line in line_xy) {
            if (line.SY < result.Height-1) {
                var c = result.At<Vec4b>(line.SY + 1, line.SX);
                Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));

            } else if (line.EY > 0) {
                var c = result.At<Vec4b>(line.EY - 1, line.EX);
                Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));
            }
        }
        return result;
    }
    // 水平(2色)
    static Mat horizontalFilterTwoColors(Mat src, Mat mask)
    {
        Mat result = src.Clone();

        var line_xy = new List<Line_xy>();

        // 縦方向へループ
        for (int y = 0; y < src.Height; y++) {
            int start_x = -1;
            // 横方向へループ
            for (int x = 0; x < src.Width; x++) {
                byte b = mask.At<byte>(y, x);

                if (b == 255) {
                    // ON(WHITE)
                    if (start_x == -1) {
                        start_x = x; // 開始座標をセット
                    }
                } else {
                    // OFF(BLACK)
                    if (start_x > -1) {
                        var o = new Line_xy { SY = y, SX = start_x, EY = y, EX = x-1};
                        line_xy.Add(o);
                        start_x = -1;
                    }
                }
            } // for x
            if (start_x > -1) {
                var o = new Line_xy { SY = y, SX = start_x, EY = y, EX = result.Width-1};
                line_xy.Add(o);
            }
        }// for y

        foreach(var line in line_xy) {
            if (line.SX > 0) {
                var c = result.At<Vec4b>(line.SY, line.SX-1);
                Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));
                if (line.EX < (result.Width-1))
                {
                    int w = Math.Abs(line.EX - line.SX) / 2;
                    var c2 = result.At<Vec4b>(line.EY, line.EX+1);
                    Cv2.Line(result, (line.SX+w), line.SY, line.EX, line.EY, new Scalar(c2[0], c2[1], c2[2], 255));
                }
            } else if (line.EX < (result.Width-1)) {
                var c = result.At<Vec4b>(line.EY, line.EX+1);
                Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));
            }
        }
        return result;
    }

    // 水平(単色・左から右へ)
    static Mat horizontalFilterLeft(Mat src, Mat mask)
    {
        Mat result = src.Clone();

        var line_xy = new List<Line_xy>();

        // 縦方向へループ
        for (int y = 0; y < src.Height; y++) {
            int start_x = -1;
            // 横方向へループ
            for (int x = 0; x < src.Width; x++) {
                byte b = mask.At<byte>(y, x);

                if (b == 255) {
                    // ON(WHITE)
                    if (start_x == -1) {
                        start_x = x; // 開始座標をセット
                    }
                } else {
                    // OFF(BLACK)
                    if (start_x > -1) {
                        var o = new Line_xy { SY = y, SX = start_x, EY = y, EX = x-1};
                        line_xy.Add(o);
                        start_x = -1;
                    }
                }
            } // for x
            if (start_x > -1) {
                var o = new Line_xy { SY = y, SX = start_x, EY = y, EX = result.Width-1};
                line_xy.Add(o);
            }
        }// for y

        foreach(var line in line_xy) {
            if (line.SX > 0) {
                var c = result.At<Vec4b>(line.SY, line.SX-1);
                Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));
            } else if (line.EX < (result.Width-1)) {
                var c = result.At<Vec4b>(line.EY, line.EX+1);
                Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));
            }
        }
        return result;
    }
    // 水平(単色・右から左へ)
    static Mat horizontalFilterRight(Mat src, Mat mask)
    {
        Mat result = src.Clone();

        var line_xy = new List<Line_xy>();

        // 縦方向へループ
        for (int y = 0; y < src.Height; y++) {
            int start_x = -1;
            // 横方向へループ
            for (int x = src.Width-1; x >= 0; x--) {
                byte b = mask.At<byte>(y, x);

                if (b == 255) {
                    // ON(WHITE)
                    if (start_x == -1) {
                        start_x = x; // 開始座標をセット
                    }
                } else {
                    // OFF(BLACK)
                    if (start_x > -1) {
                        var o = new Line_xy { SY = y, SX = start_x, EY = y, EX = x-1};
                        line_xy.Add(o);
                        start_x = -1;
                    }
                }
            } // for x
            if (start_x > -1) {
                var o = new Line_xy { SY = y, SX = start_x, EY = y, EX = result.Width-1};
                line_xy.Add(o);
            }
        }// for y

        foreach(var line in line_xy) {
            if (line.SX < result.Width-1) {
                var c = result.At<Vec4b>(line.SY, line.SX+1);
                Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));
            } else if (line.EX > 0) {
                var c = result.At<Vec4b>(line.EY, line.EX+1);
                Cv2.Line(result, line.SX, line.SY, line.EX, line.EY, new Scalar(c[0], c[1], c[2], 255));
            }
        }
        return result;
    }

    static public Bitmap Inpainters(int mode, ref Mat view)
    {
        using var mask = new Mat();
        Cv2.CvtColor(view, mask, ColorConversionCodes.RGB2GRAY);

        for (int y = 0; y < view.Height; y++) {
            for (int x = 0; x < view.Width; x++) {
                var pic = view.At<Vec4b>(y, x);
                if (pic[3] == 255) {
                    mask.Set(y, x, 0);
                } else {
                    mask.Set(y, x, 255);
                }
            }
        }

        switch (mode) {
            case 0:
                using (var tmp = new Mat())
                using (var result = new Mat()) {
                    Cv2.CvtColor(view, tmp, ColorConversionCodes.RGBA2RGB);
                    Cv2.Inpaint(tmp, mask, result, 0.0, InpaintMethod.Telea);
                    return BitmapConverter.ToBitmap(result);
                }
            case 1:
                using (var result = verticalFilterTwoColors(view, mask)) {
                    return BitmapConverter.ToBitmap(result);
                }
            case 2:
                using (var result = verticalFilterTop(view, mask)) {
                    return BitmapConverter.ToBitmap(result);
                }
            case 3:
                using (var result = verticalFilterBottom(view, mask)) {
                    return BitmapConverter.ToBitmap(result);
                }
            case 4:
                using (var result = horizontalFilterTwoColors(view, mask)) {
                    return BitmapConverter.ToBitmap(result);
                }
            case 5:
                using (var result = horizontalFilterLeft(view, mask)) {
                    return BitmapConverter.ToBitmap(result);
                }
            case 6:
                using (var result = horizontalFilterRight(view, mask)) {
                    return BitmapConverter.ToBitmap(result);
                }
       }
        return new Bitmap(1, 1);
    }
    public Form1()
    {
        InitializeComponent();

        Text = "インペイント";
        Size = new System.Drawing.Size(850, 800);

        var fnt = new Font("MS UI Gothic", 24);
        var vb = new Bitmap(800, 600);
        using (var g = Graphics.FromImage(vb)) {
            g.FillRectangle(Brushes.White, 0, 0, vb.Width, vb.Height);
            g.DrawString("こちらにD&D Or Ctrl+Cコピー Ctrl+V貼り付け", fnt,  Brushes.Green, 0.0f, 0.0f);
        }

        var ud = new SplitContainer {
            Dock = DockStyle.Fill,
            Orientation = Orientation.Horizontal,   // 上下
            Panel1MinSize = 120,
        };
        var picboxV = new PictureBox {
            SizeMode = PictureBoxSizeMode.Zoom,
            Dock = DockStyle.Fill,
            
            AllowDrop = true,
            Image = vb,
        };
        var execBtn = new RadioButton {
            Text = "フィルターOFF",
            Appearance = Appearance.Button,
            AutoCheck = false,
            Size = new System.Drawing.Size(180, 50),
        };
        var flowPanel = new FlowLayoutPanel { Dock = DockStyle.Fill, };

        var filterSelect = new List<string>{
                    "インペイント",
                    "垂直(2色)","垂直(上)","垂直(下)",
                    "水平(2色)","水平(左)","水平(右)",};
        var filterSelectCmd = new ComboBox {
            DataSource = filterSelect.ToArray<string>(),
        };

        picboxV.DragEnter += (sender, e) => {
            if (e.Data == null) return;
            if(!e.Data.GetDataPresent(DataFormats.FileDrop)) return;
            e.Effect = DragDropEffects.Copy;
        };
        picboxV.DragDrop += (sender, e) => {
            if (e.Data == null) return;
            var fd = e.Data.GetData(DataFormats.FileDrop);
            if (fd == null) return;
            string? path = ((string[])fd)[0];
            if (view is not null) view.Dispose();
            view = Cv2.ImRead(path, ImreadModes.Unchanged);
            if (view.Channels() != 4) {
                MessageBox.Show("RGBA以外は非対応", "メッセージ");
                view.Dispose();
                view = null;
                return;
            }
            if (picboxV.Image is not null) picboxV.Image.Dispose();
            picboxV.Image = BitmapConverter.ToBitmap(view);
            execBtn.Checked = false;
        };
        execBtn.Click += async (sender, e) => {
            if (view is null) return;
            if (execBtn.Checked == true) {
                picboxV.Image?.Dispose();
                picboxV.Image = BitmapConverter.ToBitmap(view);
                execBtn.Text = "フィルターOff";
                execBtn.Checked = false;
            } else {
                int mode = filterSelectCmd.SelectedIndex;
                var backCur = this.Cursor;
                this.Cursor = Cursors.WaitCursor;
                var bitmap = await Task.Run<Bitmap>(()=>Inpainters(mode, ref view));
                this.Cursor = backCur;
                picboxV.Image?.Dispose();
                picboxV.Image = bitmap;
                execBtn.Text = "フィルターOn";
                execBtn.Checked = true;
            }            
        };
        filterSelectCmd.SelectedIndexChanged += async (sender, e) =>
        {
            if (view is null) return;
            if (execBtn.Checked == false) return;

            int mode = filterSelectCmd.SelectedIndex;
            var backCur = this.Cursor;
            this.Cursor = Cursors.WaitCursor;
            var bitmap = await Task.Run<Bitmap>(()=>Inpainters(mode, ref view));
            this.Cursor = backCur;
            picboxV.Image?.Dispose();
            picboxV.Image = bitmap;
        };
        KeyPreview = true;
        KeyDown += (sender, e) => {
            if (e.KeyData == (Keys.Control | Keys.C)) {
                // コピー                
                if (picboxV.Image is null) return;
                var ms = new MemoryStream();
                picboxV.Image.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
                Clipboard.SetData("PNG", ms);            
            } else if (e.KeyData == (Keys.Control | Keys.V)) {
                // 貼り付け
                var obj = Clipboard.GetData("PNG");
                if (obj == null) return;
                var ms = (MemoryStream)obj;
                if (ms is null) return;
                if (view is not null) view.Dispose();
                using(var b = new Bitmap(ms)) {
                    if (b.PixelFormat != System.Drawing.Imaging.PixelFormat.Format32bppArgb) {
                        MessageBox.Show("RGBA以外は非対応", "メッセージ");
                        return;
                    }
                    view = BitmapConverter.ToMat(b);
                }
                if (picboxV.Image is not null) picboxV.Image.Dispose();
                picboxV.Image = BitmapConverter.ToBitmap(view);
                execBtn.Checked = false;
            }
        };
        ud.Panel1.Controls.Add(picboxV);
        flowPanel.Controls.AddRange(new Control[]{
            execBtn,filterSelectCmd,});
        ud.Panel2.Controls.Add(flowPanel);
        Controls.Add(ud);
    }
}

元画像、画像中央の白い部分が対象領域

インペイント

水平(2色)

水平(左)

水平(右)

 

コメント