Visual Basic でOpenCV⑮ - 矩形の検出

Visual BasicOpenCVを使用し、画像に含まれる矩形を検出を行います。

矩形の検出

Visual BasicOpenCV⑭ - オブジェクト・コーナー検出2を拡張し、画像に含まれる矩形を検出するプログラムを紹介します。
フォームは先のプログラムに近いですが、MenuStrip コントロールの下にToolStrip コントロールを追加します。

TextBox へは指定するのは、検出する矩形のサイズです。指定形式は「横幅x 高さ」の形式ですが、プログラム内では面積で扱います。

Form1.vb

結果を表示するフォームに変更はありません。Form1.vb の変更は軽微ですので、先のプログラムと異なる部分を示します。

    :
Public Class Form1
    :
    Public Sub New()
        MyBase.New
        InitializeComponent()
        :
        PBox.Location = New Point(0, 0)
        tSTextBox.Text = "50 x 50"                                 ' 追加
        tSTextBox.TextBoxTextAlign = HorizontalAlignment.Center    ' 追加

        AllowDrop = True
        :
    End Sub

    ' adjust window size
    Private Sub AdjustWinSize(img As Image)
        PBox.Size = img.Size        'スクロール対応

        ClientSize = New Size(      ' ウィンドウサイズ調整
                         img.Width, img.Height + MenuStrip1.Height _
                            + ToolStrip1.Height + StatusStrip1.Height)

    End Sub

    :

    Private Sub ToolMenuEffect_Click(sender As Object, e As EventArgs) _
                                                Handles ToolMenuEffect.Click
        Try
            If PBox.Image Is Nothing Then Return         ' 読み込んでいるか

            Cursor = Cursors.WaitCursor
            Dim detects As Integer = mForm2.DoCvShow(ccvfunc, tSTextBox.Text)
            toolSSLbl.Text = "detects = " & detects.ToString()

        :
    End Sub
    :

コンストラクターでTextBox の値とTextBoxTextAlign を設定します。
AdjustWinSize メソッドは、ToolStrip コントロールを配置したので、その分をHeightへ加算するように変更します。
[処理]メニュー項目が選択されたときに制御の渡るToolMenuEffect_Click メソッド内からForm2 のDoCvShow メソッドを呼び出しますが、引数にTextBox の値を渡します。DoCvShow メソッドは検出した矩形の数を返しますので、その値をStatusStrip コントロールへ表示します。

実行

本プログラムは、画像に存在する矩形を検出します。以降に実行例を示します。

この例では、2 つの矩形を検出します。元画像を表示しているフォームのステータスバーに検出数が表示されます。結果表示のフォームにコーナーの検出場所と矩形が色を変えて表示されます。矩形として検出している部分は、赤い線で囲われます。この例を見ると三角形は矩形ではないため除外され、他の矩形は検出されます。この例では「50 x 50」を指定しているため、すべての矩形が条件を満足いています。

CCvFunc .vb

矩形検出を処理するCCv の派生クラスCCvFunc の一部を示します。

Public Function DoCvFunction(ByVal size As String) As (Drawing.Bitmap, Integer)
    Dim detects = 0
    Dim delimitter = {"X"c, "x"c} ' delimitter
    Dim resolutions = size.Split(delimitter)
    Dim width = Convert.ToInt32(resolutions(0))
    Dim height = Convert.ToInt32(resolutions(1))

    Using gray As New Mat()
        Cv2.CvtColor(mSrc, gray, ColorConversionCodes.RGB2GRAY)
        Cv2.Threshold(gray, gray, 128, 255, ThresholdTypes.Binary)

        Dim contours As Point()()
        Dim hierarchy As HierarchyIndex()
        Cv2.FindContours(gray, contours, hierarchy, RetrievalModes.Tree,
                                        ContourApproximationModes.ApproxTC89L1)

        mDst = mSrc.Clone()
        For i = 0 To contours.Length - 1
            Cv2.DrawContours(mDst, contours, i, Scalar.Green, 2)
        Next

        For i = 0 To contours.Length - 1
            Dim a As Double = Cv2.ContourArea(contours(i), False)
            If a > width * height Then  ' only an area of  width * height or more
                Dim approx As Point()   ' contour to a straight line
                approx = Cv2.ApproxPolyDP(contours(i), 0.01 *
                                        Cv2.ArcLength(contours(i), True), True)
                If approx.Length = 4 Then ' rectangle only
                    detects += 1
                    Dim tmpContours = New Point()() {approx}

                    Dim maxLevel = 0
                    Cv2.DrawContours(mDst, tmpContours, 0, Scalar.Red, 2,
                                        LineTypes.AntiAlias, hierarchy, maxLevel)
                End If
            End If
        Next
    End Using
    Dim bmp As Drawing.Bitmap = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mDst)
    Return (bmp, detects)
End Function

まず、Char() 型のdelimitter に区切り文字を与え、Split メソッドでサイズの値をwidth とheight へ格納します。Split メソッドを呼び出したときに、2 つの値が返されていないと例外が発生し、メッセージが表示されます。
輪郭検出に、Cv2.FindContours メソッドを使用しますが、この関数は入力画像に2 値画像を期待します。Cv2.CvtColor メソッドでカラー画像をグレイスケール画像に変換します。そして、Cv2.Threshold メソッドで閾値処理を行います。この2 値化した画像をCv2.FindContours メソッドに与え、輪郭を検出します。検出した輪郭は、引数のcontours へ格納されます。各輪郭は点のベクトルとして格納されます(Point()())。輪郭を求める際に、各種パラメーターを与えることができます。
次に、Cv2.ContourArea メソッドを使用し、領域の面積を求め、小さなものは排除します。ここでは、width × height 以下のものは排除します。この面積は自身の使用する画像に合わせて適切な値を与えてください。小さすぎると目的外のオブジェクトを検出し、大きすぎると目的のオブジェクトを検出できなくなる可能性があります。
次にCv2.ApproxPolyDP メソッドで輪郭を直線近似化します。Cv2.ApproxPolyDP メソッドは、指定された引数の精度で多角形曲線を近似します。近似した結果がapprox へ格納されます。このapprox を調べ、頂点が4 つのものだけを取り出せば、矩形を検出できます。ソースリストから分かるように、本プログラムは、複数の矩形を検出します。検出した矩形を、Cv2.DrawContours メソッドで描きます。

サイズ変更し実行

先の例では、すべての矩形を検出しています。ここでは、検出のサイズを大きくし、再度検出してみましょう。サイズを「50 x 500」へ変更後、再度実行します。

小さな矩形波対象外となり、大きな矩形のみを検出します。