C#で三角形や四角形を判別する

C#でOpenCVSharpを使って、いくつかの図形の中から三角形や四角形を判別します。

目次

  1. 図形を判別する手順
  2. 判別する画像
  3. 判別アプリ
    1. XAML
    2. コード
  4. 実行してみた
  5. 主なメソッド
    1. Cv2.ApproxPolyDP

図形を判別する手順

下記の手順で図形を判別してみます。

  1. 輪郭を抽出する

  2. 抽出した輪郭を直線に近似する

  3. 近似した直線の数を数える

輪郭を直線に近似して数を数えるわけですから、円などには通じなさそうですね。実際にどうなるか試してみましたので、下記をお読みください。

判別する画像

こういう画像を準備しました。

元画像

判別アプリ

判別用のアプリは、左側に画像を、右側に図形選択用のボタンと近似した直線数を表示するテキストボックスを持つ、シンプルなものです。

XAML

<Window x:Class="cv2_test.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:cv2_test"
        mc:Ignorable="d"
        Title="MainWindow" Height="360" Width="630" Loaded="Window_Loaded">
    <Grid>
        <StackPanel Orientation="Horizontal">
            <Image Name="imgSrc" Stretch="Fill" Width="400" Height="300" Margin="5" />
            <StackPanel Orientation="Vertical">
                <Button Name="btnPrev" Content="Prev" Margin="10" Click="btnPrev_Click" />
                <Button Name="btnNext" Content="Next" Margin="10" Click="btnNext_Click" />
                <TextBlock Name="txtDescription" Width="200" />
            </StackPanel>
        </StackPanel>
    </Grid>
</Window>

StackPanelでImageとButtonとTextBlockを並べただけですね。

コード

NuGetを使ってOpenCVSharp4を参照に加えておいてください。手順は以前の記事を参照してください。

using OpenCvSharp;
using OpenCvSharp.WpfExtensions;
using System.Windows;

namespace cv2_test
{
    public partial class MainWindow : System.Windows.Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        Mat src_mat = new Mat();
        OpenCvSharp.Point[][] contours;
        HierarchyIndex[] hindex;
        int contour_index = 0;

        private void ShowApproxPoly(int idx)
        {
            Mat dst_mat = src_mat.Clone();

            // 輪郭を直線に近似
            OpenCvSharp.Point[][] approx = new OpenCvSharp.Point[1][];
            approx[0] = Cv2.ApproxPolyDP(contours[idx], 2.0, true);
            Cv2.DrawContours(dst_mat, approx, 0, new Scalar(0, 0, 255), 1);
            var dst_bitmap = BitmapSourceConverter.ToBitmapSource(dst_mat);
            imgSrc.Source = dst_bitmap;
            txtDescription.Text = "近似した直線の数: " + approx[0].Length.ToString();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            src_mat = new Mat("test.png", ImreadModes.Color);
            var src_bitmap = BitmapSourceConverter.ToBitmapSource(src_mat);
            imgSrc.Source = src_bitmap;

            var gray_mat = new Mat();
            Cv2.CvtColor(src_mat, gray_mat, ColorConversionCodes.BGR2GRAY);
            var bw_mat = new Mat();
            Cv2.Threshold(gray_mat, bw_mat, 192, 255, ThresholdTypes.BinaryInv);
            Cv2.FindContours(bw_mat, out contours, out hindex, RetrievalModes.External, ContourApproximationModes.ApproxNone);

            ShowApproxPoly(contour_index);
        }

        private void btnPrev_Click(object sender, RoutedEventArgs e)
        {
            if (contour_index > 0)
            {
                contour_index--;
            } else if(contour_index == 0)
            {
                contour_index = contours.Length - 1;
            }
            ShowApproxPoly(contour_index);
        }

        private void btnNext_Click(object sender, RoutedEventArgs e)
        {
            if (contour_index < contours.Length - 1)
            {
                contour_index++;
            } else if (contour_index == contours.Length - 1)
            {
                contour_index = 0;
            }
            ShowApproxPoly(contour_index);
        }
    }
}

Window_Loadedイベントで、画像の読み込みから輪郭の抽出までを行います。

図形毎の輪郭はPoint型の配列のジャグ配列になりますので、輪郭毎にインデックスが付きます。

そのインデックスを引数にしてShowApproxPoly自作関数を呼び出すと、その関数内で輪郭を直線に近似して、近似した直線の数を数えて、結果を描画します。

描画する際に、輪郭ではなくて近似した直線を赤色の線で描画します。

実行してみた

まず、正三角形を互い違いに2枚重ねたようにするとできる星形です。

12画

そして、ヒトデのようないわゆる星形です。

10画

四角形です。

4画

三角形です。

3画

円はどうなるでしょう。

円

円の方は0にはなりませんでした。表示されている図をよく見てみるとわかるのですが、なんとか直線に近似しようとしています。それで、8本の直線に近似してしまったようです。

このアプリでの直線近似の結果は下図のようになります。

全体の結果

円形以外は、まあまあ判別できそうですね。

主なメソッド

MatとBitmapSourceの変換については、こちらの記事を参考にしてください。

輪郭の抽出については、別の記事がありますのでそちらを参照してください。

Cv2.ApproxPolyDP

line = Cv2.ApproxPolyDP(curve, epsilon, closed);

変数

内容

curve

IEnumerable<Point>

近似したい輪郭。

epsilon

Double

近似直線の精度。オリジナルの曲線と近似直線との間で許容できる最大の距離。

closed

Boolean

閉じているかどうか。

line

Point[]

近似した直線。

Douglas-Peuckerというアルゴリズムで、輪郭や曲線を直線に近似します。

curve引数にCv2.FindContoursで取得した輪郭(のうちの一つ)をそのまま入れることができます。

epsilonは、図の解像度や図形に合わせた数値を入れます。近似される曲線と近似結果の曲線がどの程度ずれても良いかを指定する引数です。大きすぎず小さすぎず、適度な数値を指定する必要があります。

近似した結果で得られる直線は、Pointの配列で返されます。この配列の長さをカウントすると、いくつの直線に近似されたかがわかります。

公開日

広告