Thursday, September 28, 2017

[C#] OpenCvSharp Detect Object in Image by Contour | OpenCvSharp 使用輪廓偵測圖片中的物體

snip_20170928110929

This time I will try to detect objects from image by OpenCvSharp, let me introduce the flow first.
這次我要用OpenCvSharp嘗試偵測圖片中的物體,讓我先介紹流程。

  1. Load image (I load image as gray scale here). | 讀取圖片(我直接讀成灰階)
  2. Blur the image (I choose Gaussian Blur). | 模糊化圖片(我用高斯模糊)
  3. Get edge (use canny) | 取得邊緣(使用Canny)
  4. Find the Contour. | 找出輪廓
  5. Get right contour and draw on image. | 取得正確的輪廓並畫到圖片上上



The source image is a picture with random item I shot on my desk.
圖片來源是我拍了張桌上隨機物體的照片。
var gray = new Mat(file, ImreadModes.GrayScale);
1


Then blur the image first , so we can get better result in next step get edge, you can adjust the parameter to get best result for your picture.
然後先模糊化圖片,這樣在下一步取得邊緣時可以取得更好的成果,你可以針對你的照片來調整參數取得最佳結果。
var blur = new Mat();
Cv2.GaussianBlur(gray, blur, new Size(9,9), 0);
snip_20170928114455


Next step is using Canny get edge, you can tune the threshold value to get better result.
下一步是用Canny取得邊緣,你可以調整閥值來取得更好的結果。
var canny = new Mat();
Cv2.Canny(blur,canny,20,135);
snip_20170928114524


After get edge, we can use it to get Contour, if draw contour on image, it should look like this.
取得邊緣後,我們可以利用它來取得輪廓,如果將輪廓畫到圖片上,他會長得像這樣。
Point[][] contours; 
HierarchyIndex[] hierarchyIndexes; 
Cv2.FindContours(canny,out contours, out hierarchyIndexes, RetrievalModes.External, ContourApproximationModes.ApproxSimple);
Cv2.DrawContours(image,contours,-1,Scalar.GreenYellow,thickness:1);
snip_20170928132108


Then we can use contour data to draw object's rectangle, if we direct use it without filter, sometimes it will look like this.
然後我們就可以用輪廓的資料來畫出物件的外框,如果我們不先處理就直接使用,有時候會變成這樣。
snip_20170928132300


To avoid overlaping, we need do some extra work to filter it before draw, after tuning, we finally get what we want.
為了避免重疊,我們需要多做一些工來在繪製前過濾資料,經過調整後,我們終於得到了我們想要的結果。
snip_20170928134842


The full code is here.
完整程式碼如下。
using System;
using System.Collections.Generic;
using OpenCvSharp;

namespace OpenCvTest3
{
    class Program
    {
        static void Main(string[] args)
        {
            //get image
            var file = "1.jpg";
            var gray = new Mat(file, ImreadModes.GrayScale);
            var org = new Mat(file);

            //blur first
            var blur = new Mat();
            Cv2.GaussianBlur(gray, blur, new Size(9,9), 0);

            #region get canny
            var canny = new Mat();
            Cv2.Canny(blur,canny,20,135);
            #endregion

            #region find contours
            Point[][] contours; 
            HierarchyIndex[] hierarchyIndexes; 
            Cv2.FindContours(
                canny,
                out contours,
                out hierarchyIndexes,
                mode: RetrievalModes.External,
                method: ContourApproximationModes.ApproxSimple);
            #endregion

            Console.WriteLine($"contours={contours.Length}");

            //get rect info from contour 
            var rectList = new List();
            var rectDraw = new List();
            foreach (var c in contours)
            {
                //skip too small obj
                if(c.Length>80)
                    rectList.Add(Cv2.BoundingRect(c));
            }

            for (int i = 0; i < rectList.Count; i++)
            {
                //only draw bigger one if fully overlap
                if(CheckInBound(rectList[i],rectList)) rectDraw.Add(rectList[i]);
            }

            //draw rectangle on image
            var image = org.Clone();
            foreach (var rect in rectDraw)
            {
                Cv2.Rectangle(image, new Point(rect.X, rect.Y), new Point(rect.X + rect.Width, rect.Y + rect.Height), Scalar.Red, 2);
            }

            //draw Contours if you want 
            //Cv2.DrawContours(image,contours,-1,Scalar.Yellow,thickness:1);

            using (new Window("image", image))
            using (new Window("gray blur", blur))
            using (new Window("canny", canny))

            {
                Cv2.WaitKey();
            }
        }

        //A stupid way to avoid fully overlap.
        private static bool CheckInBound(Rect rect, List list)
        {
            foreach (var r in list)
            {
                if (rect.X > r.X && rect.Y > r.Y &&
                   (rect.X + rect.Width) < (r.X + r.Width) &&
                   (rect.Y + rect.Height) < (r.Y + r.Height))
                    return false;
            }
            return true;
        }
    }
}

Here's another test result.
這是另一個測試結果。
snip_20170928134819

Hope you enjoy it.

No comments:

Post a Comment