OpenCVSharp「直線検出」を試す。

C# コンピュータ
C#

HoughLinesP()を使って直線検出をしてみます。

実行環境構築

プロジェクトの作成

mkdir プロジェクト名
cd プロジェクト名
dotnet new winforms
dotnet add package OpenCvSharp4.Windows
dotnet add package OpenCvSharp4.Extensions
code .

ソースプログラム

namespace HougLinesPSample;

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

    public Form1()
    {
        InitializeComponent();

        Text = "直線検出";
        Size = new System.Drawing.Size(850, 800);

        var fnt = new Font("MS UI Gothic", 12);
        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", 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,
            //Location = new System.Drawing.Point(10, 10),
            Size = new System.Drawing.Size(180, 50),
        };
        var toClip = new Button {
            Text = "コピー",
            //Location = new System.Drawing.Point(200, 10),
            Size = new System.Drawing.Size(120, 50),
        };
        var fromClip = new Button {
            Text = "貼り付け",
            //Location = new System.Drawing.Point(330, 10),
            Size = new System.Drawing.Size(120, 50),
        };
        var threadholdTypesList = new string[] {"バイナリ", "バイナリ反転", "大津"};
        var thresholdTypesCmb = new ComboBox {
            Size = new System.Drawing.Size(100, 50),
            DataSource = threadholdTypesList,
        };
        int threadholdValue = 127;
        var threadholdValueLabel = new Label {
            Text = "2値化閾値:" + threadholdValue,
        };
        var threadholdValueTrack = new TrackBar {
             Minimum = 0,
             Maximum = 255,
             Value = threadholdValue,
        };
        threadholdValueTrack.ValueChanged += (sender, e) => {
            threadholdValue = threadholdValueTrack.Value;
            threadholdValueLabel.Text = "2値化閾値:" + threadholdValue;
        };
        int houghThreadholdValue = 80;
        var houghThreadholdValueLabel = new Label {
            Text = "ハフ閾値:" + houghThreadholdValue,
        };
        var houghThreadholdValueTrack = new TrackBar {
             Minimum = 0,
             Maximum = 255,
             Value = houghThreadholdValue,
        };
        houghThreadholdValueTrack.ValueChanged += (sender, e) => {
            houghThreadholdValue = houghThreadholdValueTrack.Value;
            houghThreadholdValueLabel.Text = "ハフ閾値:" + houghThreadholdValue;
        };

        int minLineLengthValue = 80;
        var minLineLengthValueLabel = new Label {
            Text = "線長最小:" + minLineLengthValue,
        };
        var minLineLengthValueTrack = new TrackBar {
             Minimum = 0,
             Maximum = 255,
             Value = minLineLengthValue,
        };
        minLineLengthValueTrack.ValueChanged += (sender, e) => {
            minLineLengthValue = minLineLengthValueTrack.Value;
            minLineLengthValueLabel.Text = "最小線長:" + minLineLengthValue;
        };

        int maxLineGapValue = 5;
        var maxLineGapValueLabel = new Label {
            Text = "最大ギャップ:" + maxLineGapValue,
        };
        var maxLineGapValueTrack = new TrackBar {
             Minimum = 0,
             Maximum = 255,
             Value = maxLineGapValue,
        };
        maxLineGapValueTrack.ValueChanged += (sender, e) => {
            maxLineGapValue = maxLineGapValueTrack.Value;
            maxLineGapValueLabel.Text = "最大ギャップ:" + maxLineGapValue;
        };

        var flowPanel = new FlowLayoutPanel {
            Dock = DockStyle.Fill,
        };

        picboxV.DragEnter += (sender, e) => {
            if (e.Data == null) return;
            if(!e.Data.GetDataPresent(DataFormats.FileDrop)) return;
            e.Effect = DragDropEffects.Copy;
        };
        picboxV.DragDrop += (sender, e) => {
            var ed = e.Data;
            if (ed == null) return;
            //if(!ed.GetDataPresent(DataFormats.FileDrop)) return;
            var f = ed.GetData(DataFormats.FileDrop);
            if(f == null) return;
            string path = ((string[])f)[0];
            if (view is not null) view.Dispose();
            view = Cv2.ImRead(path);
            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) {
                if (picboxV.Image is not null) picboxV.Image.Dispose();
                picboxV.Image = BitmapConverter.ToBitmap(view);

                execBtn.Text = "フィルターOff";
                execBtn.Checked = false;
            } else {
                int thresholdTypeIndex = thresholdTypesCmb.SelectedIndex;
                await Task.Run(()=>{
                    
                    using (var gray = new Mat())
                    using (var result = (Mat)view.Clone())
                    {
                        Cv2.CvtColor(result, gray, ColorConversionCodes.RGB2GRAY);
                        switch (thresholdTypeIndex) {
                            case 1:
                                Cv2.Threshold(gray, gray, threadholdValue, 255, ThresholdTypes.BinaryInv);
                                break;
                            case 2:
                                Cv2.Threshold(gray, gray, threadholdValue, 255, ThresholdTypes.Otsu);
                                break;
                            default:
                                Cv2.Threshold(gray, gray, threadholdValue, 255, ThresholdTypes.Binary);
                                break;
                        }
                        var lines = Cv2.HoughLinesP(gray, 1, Math.PI/360, houghThreadholdValue, (double)minLineLengthValue, (double)maxLineGapValue);
                        foreach(var line in lines) {
                            Cv2.Line(result, line.P1.X, line.P1.Y, line.P2.X, line.P2.Y, new Scalar(255, 0, 255), 3);
                        }

                        if (picboxV.Image is not null) picboxV.Image.Dispose();
                        picboxV.Image = BitmapConverter.ToBitmap(result);
                    }
                });

                execBtn.Text = "フィルターOn";
                execBtn.Checked = true;
            }            
        };
        toClip.Click += (sender, e) => {
            if (picboxV.Image is null) return;
            var ms = new MemoryStream();
            picboxV.Image.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
            Clipboard.SetData("PNG", ms);            
        };
        fromClip.Click += (sender, e) => {
            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)) {
                view = BitmapConverter.ToMat(b);
                if (view.Channels() == 4)
                    Cv2.CvtColor(view, view, ColorConversionCodes.RGBA2RGB);
            }
            if (picboxV.Image is not null) picboxV.Image.Dispose();
            picboxV.Image = BitmapConverter.ToBitmap(view);
            execBtn.Checked = false;
        };

        ud.Panel1.Controls.Add(picboxV);
        //ud.Panel2.Controls.AddRange(new Control[]{execBtn,toClip,fromClip});
        flowPanel.Controls.AddRange(new Control[]{
            execBtn,toClip,fromClip,thresholdTypesCmb,threadholdValueLabel,threadholdValueTrack,
            houghThreadholdValueLabel, houghThreadholdValueTrack,
            minLineLengthValueLabel, minLineLengthValueTrack,
            maxLineGapValueLabel, maxLineGapValueTrack});
        flowPanel.SetFlowBreak(fromClip, true);
        flowPanel.SetFlowBreak(threadholdValueTrack, true);
        ud.Panel2.Controls.Add(flowPanel);

        Controls.Add(ud);
    }
}

実行

dotnet run


枠が直線として認識されたためピンク色に変化しています。

コメント