SKProgramLab

Let's Enjoy Programming! ~画像処理/IoT/機械学習など~

C#/VB.NETで画像処理⑧<画像の輪郭抽出>

こんにちは、SKです。
f:id:SKProgramLab:20200310203357p:plain:w100

C#/VB.NETで画像処理シリーズの第8弾。
カメラ画像の輪郭抽出の処理を実装します。

輪郭抽出とは、二値化画像の白領域と黒領域の境界線(輪郭)を検出する処理のことを言います。
↓参考
オブジェクト輪郭検出 | OpenCV / findContours を使用して画像中のオブジェクトの輪郭を検出する方法

OpenCVSharpとは?

skprogramlab.hatenablog.com

(前回)画像を二値化する

skprogramlab.hatenablog.com

動画手順

youtu.be

①輪郭抽出ボタンを配置する

フォーム上にボタンを配置します。
このボタンを押すと、輪郭抽出フラグを立て、カメラ画像を二値化・輪郭抽出し、輪郭に赤い線を描画してPictureBoxに表示するようにします。 表示テキストは"Contours"に。 f:id:SKProgramLab:20200406213402p:plain

輪郭抽出フラグのbool変数isContoursを追加します。
f:id:SKProgramLab:20200406213459p:plain

②画像を二値化する

まずは前回記事同様、画像を二値化します。
ただ今回、PictureBoxには元のRGB画像に輪郭線のみ描画したカラー画像を表示するため、二値化の出力画像は別変数grayに格納します。

まずはこう。
f:id:SKProgramLab:20200406214128p:plain
局所的に使用するMat型変数は、.NETの最強のリソース開放システムである"Using"を使って定義するようにしましょう!

Usingについて

C# Tips −usingを使え、使えったら使え(^^)−

③輪郭を抽出する

輪郭抽出には、OpenCVFindContours関数を使用しますが、輪郭座標のみ抜き出す場合は、その派生関数であるFindContoursAsArrayを使うほうがシンプルに記述できます。これはOpenCVには無く、OpenCVSharp特有の便利関数ですね。

FindContoursAsArray関数

Cv2.FindContoursAsArray Method
・第1引数:入力画像(二値化)
・第2引数:輪郭抽出モード
・第3引数:輪郭近似モード

第2/3引数のモードについては、下記リンクが参考になります。 https://www.pynote.info/entry/opencv-findcontours
ここでは、RetrievalModes.Tree、ContourApproximationModes.ApproxSimpleを使います。

また、戻り値はPoint型の二次元配列になっているため、二重Forループで座標を抜き出し、Cv2.Line関数で輪郭に赤い線を描画しています。

private void Process(Mat img)
{
    if (isContours)
    {
        using (Mat gray = new Mat(img.Size(), MatType.CV_8UC1))
        {
            Cv2.CvtColor(img, gray, ColorConversionCodes.BGR2GRAY);
            Cv2.Threshold(gray, gray, 0, 255, ThresholdTypes.Otsu);
            Point[][] contours = Cv2.FindContoursAsArray(gray, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple);
            foreach (Point[] points in contours)
            {
                for (int i = 0; i < points.Length; i++)
                {
                    Cv2.Line(img, points[i], points[(i + 1) % points.Length], Scalar.Red, 2);
                }
            }
        }
    }
}

f:id:SKProgramLab:20200406220053p:plain
このあたりは記述方法にやや癖があるかもしれませんが、慣れて使いこなしていきましょう!

実行します。
起動時 f:id:SKProgramLab:20200406220559p:plain
Contours押下時
f:id:SKProgramLab:20200406220719p:plain

最後に、コード全文を載せておきます。
C#

using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using OpenCvSharp;
using OpenCvSharp.Extensions;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            Task.Run(() =>
            {
                using (VideoCapture v = new VideoCapture(0))
                using (Mat img = new Mat())
                {
                    while (true)
                    {
                        v.Read(img);
                        Process(img);
                        pictureBox1.Image = BitmapConverter.ToBitmap(img);
                    }
                }
            });
        }

        private bool isContours = false;
        
        private void Process(Mat img)
        {
            if (isContours)
            {
                using (Mat gray = new Mat(img.Size(), MatType.CV_8UC1))
                {
                    Cv2.CvtColor(img, gray, ColorConversionCodes.BGR2GRAY);
                    Cv2.Threshold(gray, gray, 0, 255, ThresholdTypes.Otsu);
                    Point[][] contours = Cv2.FindContoursAsArray(gray, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple);
                    foreach (Point[] points in contours)
                    {
                        for (int i = 0; i < points.Length; i++)
                        {
                            Cv2.Line(img, points[i], points[(i + 1) % points.Length], Scalar.Red, 2);
                        }
                    }
                }
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            isContours = !isContours;
        }
    }
}


VB.NET

Imports OpenCvSharp
Imports OpenCvSharp.Extensions

Public Class Form1

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Task.Run(Sub()
                     Using v As New VideoCapture(0)
                         Using img As New Mat
                             Do
                                 v.Read(img)
                                 Process(img)
                                 PictureBox1.Image = img.ToBitmap
                             Loop
                         End Using
                     End Using
                 End Sub)
    End Sub

    Private isContours As Boolean = False

    Private Sub Process(ByVal img As Mat)
        If isContours Then
            Using gray As New Mat(img.Size, MatType.CV_8UC1)
                Cv2.CvtColor(img, gray, ColorConversionCodes.BGR2GRAY)
                Cv2.Threshold(gray, gray, 0, 255, ThresholdTypes.Otsu)
                Dim contours As Point()() = Cv2.FindContoursAsArray(gray, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple)
                For Each points As Point() In contours
                    For i = 0 To points.Length - 1
                        Cv2.Line(img, points(i), points((i + 1) Mod points.Length), Scalar.Red, 2)
                    Next
                Next
            End Using
        End If
    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        isContours = Not isContours
    End Sub

End Class