天天看點

【C#】用EmguCV 繪制各種輪廓線

一般在做影像處理時,為提升效率,常會将影像轉為二值影像後再進行處理。

在EmguCV内有許多找輪廓線的方法,但是随着版本更新,不同版本的函數

不見得會一樣,每次都要重新查詢實在很麻煩,那不如把他們記下來。

版本概要:

EmguCV版本:3.2.0.2682

編譯器版本: Visual Studio 2017 Community

方案平台: x64 (許多導緻程式無法執行的原因是因為沒有改執行平台!)

正文開始。

首先我們用小畫家畫了一張圖來作為範本—一朵雲。

因為形狀奇特,非常适合用來說明。

【C#】用EmguCV 繪制各種輪廓線

1. BoundingBox: 可以框住全部範圍的矩形。

這是沒有經過旋轉地矩形,有經過旋轉的矩形在後面讨論。

using System;
using System.Windows.Forms;
using System.Drawing;

using Emgu.CV;
using Emgu.CV.Structure;
using Emgu.CV.CvEnum;
using Emgu.CV.Util;


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

        private void Form1_Load(object sender, EventArgs e)
        {

            Image<Gray, byte> I = new Image<Gray, byte>(@"D:\Test\1.jpg");
            Image<Bgr, byte> DrawI = I.Convert<Bgr, byte>();

            Image<Gray, byte> CannyImage = I.Clone();
            CvInvoke.Canny(I, CannyImage, , , , true);

            MyCV.BoundingBox(CannyImage, DrawI);
            pictureBox1.Image = DrawI.Bitmap;
        }
    }

    public class MyCV
    {
        public static void BoundingBox(Image<Gray, byte> src, Image<Bgr, byte> draw)
        {
            using (VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint())
            {
                CvInvoke.FindContours(src, contours, null, RetrType.External,
                                      ChainApproxMethod.ChainApproxSimple);

                int count = contours.Size;
                for (int i = ; i < count; i++)
                {
                    using (VectorOfPoint contour = contours[i])
                    {
                        Rectangle BoundingBox = CvInvoke.BoundingRectangle(contour);
                        CvInvoke.Rectangle(draw, BoundingBox, new MCvScalar(, , , ), );
                    }
                }
            }
        }
    }
}
           
【C#】用EmguCV 繪制各種輪廓線

注:後面的程式碼僅寫出操作的函數,省略主視窗及名稱空間,請自行代換主視窗的程式碼。

在這邊常有看到一些範例程式會建議使用ApproxPolyDP這個方法,取得近似的形狀,

經過測試,若是在一些精度需求不高的情況下可以這麼做,但就這個雲形的例子而言不建議這樣做。

下面是采用ApproxPolyDP函數的程式碼與結果。

public static void ApproxBoundingBox(Image<Gray, byte> src, Image<Bgr, byte> draw)
{
    using (VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint())
    {
        CvInvoke.FindContours(src, contours, null, RetrType.External,
                              ChainApproxMethod.ChainApproxSimple);

        int count = contours.Size;
        for (int i = ; i < count; i++)
        {
            using (VectorOfPoint contour = contours[i])
            using (VectorOfPoint approxContour = new VectorOfPoint())
            {
                CvInvoke.ApproxPolyDP(contour, approxContour, CvInvoke.ArcLength(contour, true) * , true);
                Rectangle BoundingBox = CvInvoke.BoundingRectangle(approxContour);
                CvInvoke.Rectangle(draw, BoundingBox, new MCvScalar(, , , ), );
            }
        }
    }
}
           
【C#】用EmguCV 繪制各種輪廓線

可以看到有許多卷卷的地方,都被近似掉了,以至于框選出來的範圍會失真。

但,若目标是長方形或三角形這種比較規則的形狀,使用近似的方法可以提升執行的效率。

其實若是直接把輪廓線畫出來就可以看得更清楚,近似後許多細節會消失。

以下是程式碼與執行結果。

public static void DrawContour(Image<Gray, byte> src, Image<Bgr, byte> draw)
{
    using (VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint())
    {
        CvInvoke.FindContours(src, contours, null, RetrType.External,
                                ChainApproxMethod.ChainApproxSimple);

        int count = contours.Size;
        for (int i = ; i < count; i++)
        {
            using (VectorOfPoint contour = contours[i])
            using (VectorOfPoint approxContour = new VectorOfPoint())
            {
                // 原始輪廓線
                CvInvoke.DrawContours(draw, contours, i, new MCvScalar(, , , ), );

                // 近似後輪廓線
                CvInvoke.ApproxPolyDP(contour, approxContour, 
                                      CvInvoke.ArcLength(contour, true) * , true);
                Point[] pts = approxContour.ToArray();
                for(int j=; j<pts.Length; j++)
                {
                    Point p1 = new Point(pts[j].X, pts[j].Y);
                    Point p2;

                    if (j == pts.Length - )
                        p2 = new Point(pts[].X, pts[].Y);
                    else
                        p2 = new Point(pts[j+].X, pts[j+].Y);

                    CvInvoke.Line(draw, p1, p2, new MCvScalar(, , , ), );
                }
            }
        }
    }
}
           
【C#】用EmguCV 繪制各種輪廓線

2. ConvexHull: 可以框住區塊的最小多邊形。

public static void ConvexHull(Image<Gray, byte> src, Image<Bgr, byte> draw)
{
    using (VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint())
    {
        CvInvoke.FindContours(src, contours, null, RetrType.External,
                                ChainApproxMethod.ChainApproxSimple);

        int count = contours.Size;
        for (int i = ; i < count; i++)
        {
            using (VectorOfPoint contour = contours[i])
            {
                PointF[] temp = Array.ConvertAll(contour.ToArray(),
                                                new Converter<Point, PointF>(Point2PointF));
                PointF[] pts = CvInvoke.ConvexHull(temp, true);

                for (int j = ; j < pts.Length; j++)
                {
                    Point p1 = new Point((int)pts[j].X, (int)pts[j].Y);
                    Point p2;

                    if (j == pts.Length - )
                        p2 = new Point((int)pts[].X, (int)pts[].Y);
                    else
                        p2 = new Point((int)pts[j + ].X, (int)pts[j + ].Y);

                    CvInvoke.Line(draw, p1, p2, new MCvScalar(, , , ), );
                }
            }
        }
    }
}

private static PointF Point2PointF(Point P)
{
    PointF PF = new PointF
    {
        X = P.X,
        Y = P.Y
    };
    return PF;
}
           
【C#】用EmguCV 繪制各種輪廓線

3. MinAreaBoundingBox: 可框住區域的最小矩形。

這是可旋轉的矩形,意即找到面積最小,又可以框住該區域的矩形。

public static void MinAreaBoundingBox(Image<Gray, byte> src, Image<Bgr, byte> draw)
{
    using (VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint())
    {
        CvInvoke.FindContours(src, contours, null, RetrType.External,
                                ChainApproxMethod.ChainApproxSimple);

        int count = contours.Size;
        for (int i = ; i < count; i++)
        {
            using (VectorOfPoint contour = contours[i])
            {
                RotatedRect BoundingBox = CvInvoke.MinAreaRect(contour);
                CvInvoke.Polylines(draw, Array.ConvertAll(BoundingBox.GetVertices(), Point.Round), 
                                   true, new Bgr(Color.DeepPink).MCvScalar, );
            }
        }
    }
}
           
【C#】用EmguCV 繪制各種輪廓線

4. MinAreaCircle:可框住區域的最小圓形。

public static void MinAreaCircle(Image<Gray, byte> src, Image<Bgr, byte> draw)
{
    using (VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint())
    {
        CvInvoke.FindContours(src, contours, null, RetrType.External,
                                ChainApproxMethod.ChainApproxSimple);

        int count = contours.Size;
        for (int i = ; i < count; i++)
        {
            using (VectorOfPoint contour = contours[i])
            {
                CircleF circle = CvInvoke.MinEnclosingCircle(contour);       
                CvInvoke.Circle(draw, new Point((int)circle.Center.X, (int) circle.Center.Y),
                                (int)circle.Radius, new MCvScalar(, , , ), );
            }
        }
    }
}
           
【C#】用EmguCV 繪制各種輪廓線

在EmguCV内一種輪廓線就一種畫法。

真的是要足夠熟練才能夠駕馭這些函數唉!

像是可憐的小夏已經陷在這些函數内好幾天了,真是頭昏眼花,臨表泣涕,不知所雲。

翻譯自:dotblogs.com.tw 夏恩的程式筆記