Shape (Triangle, Rectangle, Circle, Line) Detection in CSharp

From Emgu CV: OpenCV in .NET (C#, VB, C++ and more)
Jump to navigation Jump to search

This project is part of the Emgu.CV.Example solution

Shape Detection

In this tutorial, we demonstrate how to perform Hough Line and Circle detection using Emgu CV, as well as using the Contour class to detect Triangles and Rectangles in the image. The "pic3.png" file from the OpenCV sample folder is used here.

pic3.png from opencv

Source Code

Emgu CV 4.x

Click to view source code

        public Mat ProcessImage(Mat img)
        {
            using (UMat gray = new UMat())
            using (UMat cannyEdges = new UMat())
            using (Mat triangleRectangleImage = new Mat(img.Size, DepthType.Cv8U, 3)) //image to draw triangles and rectangles on
            using (Mat circleImage = new Mat(img.Size, DepthType.Cv8U, 3)) //image to draw circles on
            using (Mat lineImage = new Mat(img.Size, DepthType.Cv8U, 3)) //image to drtaw lines on
            {
                //Convert the image to grayscale and filter out the noise
                CvInvoke.CvtColor(img, gray, ColorConversion.Bgr2Gray);

                //Remove noise
                CvInvoke.GaussianBlur(gray, gray, new Size(3,3), 1);

                #region circle detection
                double cannyThreshold = 180.0;
                double circleAccumulatorThreshold = 120;
                CircleF[] circles = CvInvoke.HoughCircles(gray, HoughModes.Gradient, 2.0, 20.0, cannyThreshold,
                    circleAccumulatorThreshold, 5);
                #endregion

                #region Canny and edge detection
                double cannyThresholdLinking = 120.0;
                CvInvoke.Canny(gray, cannyEdges, cannyThreshold, cannyThresholdLinking);
                LineSegment2D[] lines = CvInvoke.HoughLinesP(
                    cannyEdges,
                    1, //Distance resolution in pixel-related units
                    Math.PI / 45.0, //Angle resolution measured in radians.
                    20, //threshold
                    30, //min Line width
                    10); //gap between lines
                #endregion

                #region Find triangles and rectangles
                List<Triangle2DF> triangleList = new List<Triangle2DF>();
                List<RotatedRect> boxList = new List<RotatedRect>(); //a box is a rotated rectangle
                using (VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint())
                {
                    CvInvoke.FindContours(cannyEdges, contours, null, RetrType.List,
                        ChainApproxMethod.ChainApproxSimple);
                    int count = contours.Size;
                    for (int i = 0; i < count; i++)
                    {
                        using (VectorOfPoint contour = contours[i])
                        using (VectorOfPoint approxContour = new VectorOfPoint())
                        {
                            CvInvoke.ApproxPolyDP(contour, approxContour, CvInvoke.ArcLength(contour, true) * 0.05,
                                true);
                            if (CvInvoke.ContourArea(approxContour, false) > 250
                            ) //only consider contours with area greater than 250
                            {
                                if (approxContour.Size == 3) //The contour has 3 vertices, it is a triangle
                                {
                                    Point[] pts = approxContour.ToArray();
                                    triangleList.Add(new Triangle2DF(
                                        pts[0],
                                        pts[1],
                                        pts[2]
                                    ));
                                }
                                else if (approxContour.Size == 4) //The contour has 4 vertices.
                                {
                                    #region determine if all the angles in the contour are within [80, 100] degree
                                    bool isRectangle = true;
                                    Point[] pts = approxContour.ToArray();
                                    LineSegment2D[] edges = PointCollection.PolyLine(pts, true);

                                    for (int j = 0; j < edges.Length; j++)
                                    {
                                        double angle = Math.Abs(
                                            edges[(j + 1) % edges.Length].GetExteriorAngleDegree(edges[j]));
                                        if (angle < 80 || angle > 100)
                                        {
                                            isRectangle = false;
                                            break;
                                        }
                                    }

                                    #endregion

                                    if (isRectangle) boxList.Add(CvInvoke.MinAreaRect(approxContour));
                                }
                            }
                        }
                    }
                }
                #endregion

                #region draw triangles and rectangles
                triangleRectangleImage.SetTo(new MCvScalar(0));
                foreach (Triangle2DF triangle in triangleList)
                {
                    CvInvoke.Polylines(triangleRectangleImage, Array.ConvertAll(triangle.GetVertices(), Point.Round),
                        true, new Bgr(Color.DarkBlue).MCvScalar, 2);
                }

                foreach (RotatedRect box in boxList)
                {
                    CvInvoke.Polylines(triangleRectangleImage, Array.ConvertAll(box.GetVertices(), Point.Round), true,
                        new Bgr(Color.DarkOrange).MCvScalar, 2);
                }

                //Drawing a light gray frame around the image
                CvInvoke.Rectangle(triangleRectangleImage,
                    new Rectangle(Point.Empty,
                        new Size(triangleRectangleImage.Width - 1, triangleRectangleImage.Height - 1)),
                    new MCvScalar(120, 120, 120));
                //Draw the labels
                CvInvoke.PutText(triangleRectangleImage, "Triangles and Rectangles", new Point(20, 20),
                    FontFace.HersheyDuplex, 0.5, new MCvScalar(120, 120, 120));
                #endregion

                #region draw circles
                circleImage.SetTo(new MCvScalar(0));
                foreach (CircleF circle in circles)
                    CvInvoke.Circle(circleImage, Point.Round(circle.Center), (int)circle.Radius,
                        new Bgr(Color.Brown).MCvScalar, 2);

                //Drawing a light gray frame around the image
                CvInvoke.Rectangle(circleImage,
                    new Rectangle(Point.Empty, new Size(circleImage.Width - 1, circleImage.Height - 1)),
                    new MCvScalar(120, 120, 120));
                //Draw the labels
                CvInvoke.PutText(circleImage, "Circles", new Point(20, 20), FontFace.HersheyDuplex, 0.5,
                    new MCvScalar(120, 120, 120));
                #endregion

                #region draw lines
                lineImage.SetTo(new MCvScalar(0));
                foreach (LineSegment2D line in lines)
                    CvInvoke.Line(lineImage, line.P1, line.P2, new Bgr(Color.Green).MCvScalar, 2);
                //Drawing a light gray frame around the image
                CvInvoke.Rectangle(lineImage,
                    new Rectangle(Point.Empty, new Size(lineImage.Width - 1, lineImage.Height - 1)),
                    new MCvScalar(120, 120, 120));
                //Draw the labels
                CvInvoke.PutText(lineImage, "Lines", new Point(20, 20), FontFace.HersheyDuplex, 0.5,
                    new MCvScalar(120, 120, 120));
                #endregion

                Mat result = new Mat();
                CvInvoke.VConcat(new Mat[] { img, triangleRectangleImage, circleImage, lineImage }, result);
                return result;
            }
        }

Emgu CV 3.x

Click to view source code

            StringBuilder msgBuilder = new StringBuilder("Performance: ");

            //Load the image from file and resize it for display
            Image<Bgr, Byte> img = 
               new Image<Bgr, byte>(fileNameTextBox.Text)
               .Resize(400, 400, Emgu.CV.CvEnum.Inter.Linear, true);

            //Convert the image to grayscale and filter out the noise
            UMat uimage = new UMat();
            CvInvoke.CvtColor(img, uimage, ColorConversion.Bgr2Gray);

            //use image pyr to remove noise
            UMat pyrDown = new UMat();
            CvInvoke.PyrDown(uimage, pyrDown);
            CvInvoke.PyrUp(pyrDown, uimage);
            
            //Image<Gray, Byte> gray = img.Convert<Gray, Byte>().PyrDown().PyrUp();

            #region circle detection
            Stopwatch watch = Stopwatch.StartNew();
            double cannyThreshold = 180.0;
            double circleAccumulatorThreshold = 120;
            CircleF[] circles = CvInvoke.HoughCircles(uimage, HoughType.Gradient, 2.0, 20.0, cannyThreshold, circleAccumulatorThreshold, 5);

            watch.Stop();
            msgBuilder.Append(String.Format("Hough circles - {0} ms; ", watch.ElapsedMilliseconds));
            #endregion

            #region Canny and edge detection
            watch.Reset(); watch.Start();
            double cannyThresholdLinking = 120.0;
            UMat cannyEdges = new UMat();
            CvInvoke.Canny(uimage, cannyEdges, cannyThreshold, cannyThresholdLinking);

            LineSegment2D[] lines = CvInvoke.HoughLinesP(
               cannyEdges, 
               1, //Distance resolution in pixel-related units
               Math.PI/45.0, //Angle resolution measured in radians.
               20, //threshold
               30, //min Line width
               10); //gap between lines

            watch.Stop();
            msgBuilder.Append(String.Format("Canny & Hough lines - {0} ms; ", watch.ElapsedMilliseconds));
            #endregion

            #region Find triangles and rectangles
            watch.Reset(); watch.Start();
            List<Triangle2DF> triangleList = new List<Triangle2DF>();
            List<RotatedRect> boxList = new List<RotatedRect>(); //a box is a rotated rectangle

            using (VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint())
            {
               CvInvoke.FindContours(cannyEdges, contours, null, RetrType.List, ChainApproxMethod.ChainApproxSimple );
               int count = contours.Size;
               for (int i = 0; i < count; i++)
               {
                  using (VectorOfPoint contour = contours[i])
                  using (VectorOfPoint approxContour = new VectorOfPoint())
                  {
                     CvInvoke.ApproxPolyDP(contour, approxContour, CvInvoke.ArcLength(contour, true) * 0.05, true);
                     if (CvInvoke.ContourArea(approxContour, false) > 250) //only consider contours with area greater than 250
                     {
                        if (approxContour.Size == 3) //The contour has 3 vertices, it is a triangle
                        {
                           Point[] pts = approxContour.ToArray();
                           triangleList.Add(new Triangle2DF(
                              pts[0],
                              pts[1],
                              pts[2]
                              ));
                        } else if (approxContour.Size == 4) //The contour has 4 vertices.
                        {
                           #region determine if all the angles in the contour are within [80, 100] degree
                           bool isRectangle = true;
                           Point[] pts = approxContour.ToArray();
                           LineSegment2D[] edges = PointCollection.PolyLine(pts, true);

                           for (int j = 0; j < edges.Length; j++)
                           {
                              double angle = Math.Abs(
                                 edges[(j + 1) % edges.Length].GetExteriorAngleDegree(edges[j]));
                              if (angle < 80 || angle > 100)
                              {
                                 isRectangle = false;
                                 break;
                              }
                           }
                           #endregion

                           if (isRectangle) boxList.Add(CvInvoke.MinAreaRect(approxContour));
                        }
                     }
                  }
               }
            }

            watch.Stop();
            msgBuilder.Append(String.Format("Triangles & Rectangles - {0} ms; ", watch.ElapsedMilliseconds));
            #endregion

            originalImageBox.Image = img;
            this.Text = msgBuilder.ToString();

            #region draw triangles and rectangles
            Image<Bgr, Byte> triangleRectangleImage = img.CopyBlank();
            foreach (Triangle2DF triangle in triangleList)
               triangleRectangleImage.Draw(triangle, new Bgr(Color.DarkBlue), 2);
            foreach (RotatedRect box in boxList)
               triangleRectangleImage.Draw(box, new Bgr(Color.DarkOrange), 2);
            triangleRectangleImageBox.Image = triangleRectangleImage;
            #endregion

            #region draw circles
            Image<Bgr, Byte> circleImage = img.CopyBlank();
            foreach (CircleF circle in circles)
               circleImage.Draw(circle, new Bgr(Color.Brown), 2);
            circleImageBox.Image = circleImage;
            #endregion

            #region draw lines
            Image<Bgr, Byte> lineImage = img.CopyBlank();
            foreach (LineSegment2D line in lines)
               lineImage.Draw(line, new Bgr(Color.Green), 2);
            lineImageBox.Image = lineImage;
            #endregion

Emgu CV 2.x

Click to view source code

//Load the image from file
Image<Bgr, Byte> img = new Image<Bgr, byte>(fileNameTextBox.Text).Resize(400, 400, true);

//Convert the image to grayscale and filter out the noise
Image<Gray, Byte> gray = img.Convert<Gray, Byte>().PyrDown().PyrUp();

Gray cannyThreshold = new Gray(180);
Gray cannyThresholdLinking = new Gray(120);
Gray circleAccumulatorThreshold = new Gray(120);

CircleF[] circles = gray.HoughCircles(
    cannyThreshold,
    circleAccumulatorThreshold,
    5.0, //Resolution of the accumulator used to detect centers of the circles
    10.0, //min distance 
    5, //min radius
    0 //max radius
    )[0]; //Get the circles from the first channel

Image<Gray, Byte> cannyEdges = gray.Canny(cannyThreshold, cannyThresholdLinking);
LineSegment2D[] lines = cannyEdges.HoughLinesBinary(
    1, //Distance resolution in pixel-related units
    Math.PI / 45.0, //Angle resolution measured in radians.
    20, //threshold
    30, //min Line width
    10 //gap between lines
    )[0]; //Get the lines from the first channel

#region Find triangles and rectangles
List<Triangle2DF> triangleList = new List<Triangle2DF>();
List<MCvBox2D> boxList = new List<MCvBox2D>();

using (MemStorage storage = new MemStorage()) //allocate storage for contour approximation
   for (Contour<Point> contours = cannyEdges.FindContours(); contours != null; contours = contours.HNext)
   {
      Contour<Point> currentContour = contours.ApproxPoly(contours.Perimeter * 0.05, storage);

      if (contours.Area > 250) //only consider contours with area greater than 250
      {
         if (currentContour.Total == 3) //The contour has 3 vertices, it is a triangle
         {
            Point[] pts = currentContour.ToArray();
            triangleList.Add(new Triangle2DF(
               pts[0],
               pts[1],
               pts[2]
               ));
         }
         else if (currentContour.Total == 4) //The contour has 4 vertices.
         {
            #region determine if all the angles in the contour are within the range of [80, 100] degree
            bool isRectangle = true;
            Point[] pts = currentContour.ToArray();
            LineSegment2D[] edges = PointCollection.PolyLine(pts, true);

            for (int i = 0; i < edges.Length; i++)
            {
               double angle = Math.Abs(
                  edges[(i + 1) % edges.Length].GetExteriorAngleDegree(edges[i]));
               if (angle < 80 || angle > 100)
               {
                  isRectangle = false;
                  break;
               }
            }
            #endregion

            if (isRectangle) boxList.Add(currentContour.GetMinAreaRect());
         }
      }
   }
#endregion

originalImageBox.Image = img;

#region draw triangles and rectangles
Image<Bgr, Byte> triangleRectangleImage = img.CopyBlank();
foreach (Triangle2DF triangle in triangleList)
   triangleRectangleImage.Draw(triangle, new Bgr(Color.DarkBlue), 2);
foreach (MCvBox2D box in boxList)
   triangleRectangleImage.Draw(box, new Bgr(Color.DarkOrange), 2);
triangleRectangleImageBox.Image = triangleRectangleImage;
#endregion

#region draw circles
Image<Bgr, Byte> circleImage = img.CopyBlank();
foreach (CircleF circle in circles)
   circleImage.Draw(circle, new Bgr(Color.Brown), 2);
circleImageBox.Image = circleImage;
#endregion

#region draw lines
Image<Bgr, Byte> lineImage = img.CopyBlank();
foreach (LineSegment2D line in lines)
   lineImage.Draw(line, new Bgr(Color.Green), 2);
lineImageBox.Image = lineImage;
#endregion

Result

Result of shape detection