Shape (Triangle, Rectangle, Circle, Line) Detection in CSharp
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.
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