版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。
EmguCV是一个基于OpenCV的开源免费的跨平台计算机视觉库,它向C#和VB.NET开发者提供了OpenCV库的大部分功能。
教程VB.net版本请访问:EmguCV学习笔记 VB.Net 目录-CSDN博客
教程C#版本请访问:EmguCV学习笔记 C# 目录-CSDN博客
笔者的博客网址:https://blog.csdn.net/uruseibest
教程配套文件及相关说明以及如何获得pdf教程和代码,请移步:EmguCV学习笔记
学习VB.Net知识,请移步: vb.net 教程 目录_vb中如何用datagridview-CSDN博客
学习C#知识,请移步:C# 教程 目录_c#教程目录-CSDN博客
6.3 轮廓外接多边形
6.3.1 最大外接矩形BoundingRectangle
CvInvoke.BoundingRectangle方法用于获得轮廓的最大外接矩形。该方法其中一个声明:
public static Rectangle BoundingRectangle(
IInputArray points
)
参数说明:
- points:要计算最大外接矩形的轮廓。这是一个VectorOfPoint类型。对应使用FindContours方法获得的contours所包含的成员。
返回值:
返回一个Rectangle对象,表示计算得到的最大矩形的位置和大小。
代码参看6.3.2节【minAreaRect】中的示例。
6.3.2 最小外接矩形minAreaRect
在Emgu.CV中,CvInvoke.MinAreaRect函数用于计算轮廓的最小外接矩形。它的用法如下:
public static RotatedRect MinAreaRect(
IInputArray points
)
参数说明:
- points:要计算最大外接矩形的轮廓。这是一个VectorOfPoint类型。对应使用FindContours方法获得的contours所包含的成员。
返回值:
返回一个RotatedRect对象,表示计算得到的最小外接矩形。通过RotatedRect的GetVertices方法返回四个顶点,然后可使用CvInvoke.Polylines绘制。
【代码位置:frmChapter6】Button8_Click
private void Button8_Click(object sender, EventArgs e)
{
Mat msrc = new Mat("C:\\learnEmgucv\\shape2.jpg", ImreadModes.Color);
//转灰度
Mat mgray = new Mat();
CvInvoke.CvtColor(msrc, mgray, ColorConversion.Bgr2Gray);
//二值化
Mat mmid = new Mat();
CvInvoke.Threshold(mgray, mmid, 150, 255, ThresholdType.BinaryInv);
ImageBox1.Image = mmid;
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
VectorOfRect hierarchy = new VectorOfRect();
//获得轮廓
CvInvoke.FindContours(mmid, contours, hierarchy, RetrType.List, ChainApproxMethod.ChainApproxSimple);
//绘制最大外接矩形的图像
Mat mmax = new Mat();
mmax = msrc.Clone();
//绘制最小外接矩形的图像
Mat mmin = new Mat();
mmin = msrc.Clone();
for(int i = 0;i< contours.Size;i++)
{
//获得最大外接矩形
Rectangle rectmax = CvInvoke.BoundingRectangle(contours[i]);
CvInvoke.Rectangle(mmax, rectmax, new MCvScalar(255), 2);
//获得最小外接矩形
RotatedRect rectmin = CvInvoke.MinAreaRect(contours[i]);
PointF[] pf = rectmin.GetVertices();
//实现数组PointF到数组Point的转换
Point[] pf1= Array.ConvertAll(pf, new Converter<PointF, Point>(PointFToPoint));
//不能按照矩形绘制,只能绘制多边形
CvInvoke.Polylines(mmin, pf1, true, new MCvScalar(255, 0, 0), 2);
}
ImageBox2.Image = mmax;
ImageBox3.Image = mmin;
}
运行后如下图所示:
图6-9 绘制轮廓的最大外接矩形和最小外接矩形
【代码位置:frmChapter6】Button9_Click
//外接多边形:最大外接矩形 和 最小外接矩形
//用Image来代替mat完成最后的绘制
private void Button9_Click(object sender, EventArgs e)
{
Mat m1 = new Mat("C:\\learnEmgucv\\shape2.jpg", ImreadModes.Grayscale);
ImageBox1.Image = m1;
Mat mid1 = new Mat();
CvInvoke.Threshold(m1, mid1, 150, 255, ThresholdType.BinaryInv);
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
VectorOfRect hierarchy = new VectorOfRect();
CvInvoke.FindContours(mid1, contours, hierarchy, RetrType.List, ChainApproxMethod.ChainApproxSimple);
Image<Bgr, byte> m2 = new Image<Bgr, byte>("C:\\learnEmgucv\\shape2.jpg");
//最大外接矩形
Image<Bgr, byte> mmax = new Image<Bgr, byte>(m2.Width, m2.Height);
mmax = m2.Clone();
//最小外接矩形
Image<Bgr, byte> mmin = new Image<Bgr, byte>(m2.Width, m2.Height);
mmin = m2.Clone();
for (int i = 0; i < contours.Size;i++)
{
//最大外接矩形
Rectangle rectmax = CvInvoke.BoundingRectangle(contours[i]);
mmax.Draw(rectmax, new Bgr(0, 255, 0), 2);
//或者以下代码:
//CvInvoke.Rectangle(mmax, rectmax, new MCvScalar(255), 2);
RotatedRect rectmin = CvInvoke.MinAreaRect(contours[i]);
mmin.Draw(rectmin, new Bgr(0, 255, 0), 2);
//或者以下代码:
//PointF[] pf = rectmin.GetVertices();
//Point[] pf1 = Array.ConvertAll(pf, new Converter<PointF, Point>(PointFToPoint));
//CvInvoke.Polylines(mmin, pf1, true, new MCvScalar(255, 0, 0), 2);
}
ImageBox2.Image = mmax;
ImageBox3.Image = mmin;
}
运行后如下图所示:
图6-10 绘制轮廓的最大外接矩形和最小外接矩形
6.3.3 多边形逼近ApproxPolyDP
CvInvoke.ApproxPolyDP方法用于对轮廓进行多边形逼近。声明如下:
public static void ApproxPolyDP(
IInputArray curve,
IOutputArray approxCurve,
double epsilon,
bool closed
)
参数说明:
- curve:要进行逼近的轮廓。这是一个VectorOfPoint类型。对应使用FindContours方法获得的contours所包含的成员。
- approxCurve:输出逼近后的多边形曲线。
- epsilon:逼近精度,即逼近后的多边形曲线与原轮廓的最大距离。
- closed:表示逼近后的多边形曲线是否是闭合的。如果为True,表示闭合;如果为False,表示开放。
【代码位置:frmChapter6】Button10_Click
//外接多边形
private void Button10_Click(object sender, EventArgs e)
{
Mat msrc = new Mat("C:\\learnEmgucv\\shape2.jpg", ImreadModes.Grayscale);
ImageBox1.Image = msrc;
Mat mid1 = new Mat();
CvInvoke.Threshold(msrc, mid1, 150, 255, ThresholdType.BinaryInv);
ImageBox2.Image = mid1;
//查找轮廓
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
VectorOfRect hierarchy = new VectorOfRect();
CvInvoke.FindContours(mid1, contours, hierarchy, RetrType.List, ChainApproxMethod.ChainApproxSimple);
Mat mpoly = new Mat(msrc.Size, DepthType.Cv8U, 3);
mpoly.SetTo(new MCvScalar(255, 255, 255));
for(int i = 0;i< contours.Size;i++)
{
VectorOfPoint result = new VectorOfPoint();
//获得逼近后的多边形曲线
CvInvoke.ApproxPolyDP(contours[i], result, 4, true);
Point[] p = result.ToArray();
//绘制多边形
CvInvoke.Polylines(mpoly, p, true, new MCvScalar(255, 0, 0), 2);
//输出多边形的边数
CvInvoke.PutText(mpoly, p.Length.ToString(), new Point(p[p.Length - 1].X - 20, p[p.Length - 1].Y), FontFace.HersheyComplex, 1, new MCvScalar(0, 0, 255), 1);
}
ImageBox3.Image = mpoly;
}
运行后如下图所示:
图6-11 绘制轮廓外接多边形
6.3.4 最小外接圆MinEnclosingCircle
CvInvoke.MinEnclosingCircle方法用于计算轮廓的最小外接圆。它的声明之一:
public static CircleF MinEnclosingCircle(
IInputArray points
)
示例参看6.3.6节 【FitEllipse】代码。
6.3.5 最小外接三角形MinEnclosingTriangle
CvInvoke.MinEnclosingTriangle方法用于计算轮廓的最小外接三角形。它的声明如下:
public static double MinEnclosingTriangle(
IInputArray points,
IOutputArray triangles
)
示例参看6.3.6节 【FitEllipse】代码。
6.3.6 拟合椭圆FitEllipse
CvInvoke.FitEllipse方法用于拟合给定点集的椭圆。它的声明如下:
public static RotatedRect FitEllipse(
IInputArray points
)
【代码位置:frmChapter6】Button11_Click
//最小外接三角形、最小外接圆、拟合椭圆
private void Button11_Click(object sender, EventArgs e)
{
Mat m1 = new Mat("C:\\learnEmgucv\\shape2.jpg", ImreadModes.Grayscale);
//ImageBox1.Image = m1;
Mat mid1 = new Mat();
CvInvoke.Threshold(m1, mid1, 150, 255, ThresholdType.BinaryInv);
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
VectorOfRect hierarchy = new VectorOfRect();
CvInvoke.FindContours(mid1, contours, hierarchy, RetrType.List, ChainApproxMethod.ChainApproxSimple);
Mat m2 = new Mat("C:\\learnEmgucv\\shape2.jpg", ImreadModes.AnyColor);
//外接三角形
Mat mtriangle = new Mat();
mtriangle = m1.Clone();
//外接圆形
Mat mcircle = new Mat();
mcircle = m1.Clone();
//拟合椭圆
Mat mEllipse = new Mat();
mEllipse = m1.Clone();
VectorOfPoint triangle = new VectorOfPoint();
for (int i = 0; i < contours.Size; i++)
{
//获得外接三角形
Double trianglearea = CvInvoke.MinEnclosingTriangle(contours[i], triangle);
//使用封闭多边形来绘制三角形,也可以用line来绘制
CvInvoke.Polylines(mtriangle, triangle, true, new MCvScalar(0), 2);
CircleF cf = CvInvoke.MinEnclosingCircle(contours[i]);
//获得外接圆形
CvInvoke.Circle(mcircle, new Point((int)(cf.Center.X), (int)(cf.Center.Y)), (int)cf.Radius, new MCvScalar(0), 2);
//获得拟合椭圆
RotatedRect rr = CvInvoke.FitEllipse(contours[i]);
CvInvoke.Ellipse(mEllipse, rr, new MCvScalar(0), 2);
}
ImageBox1.Image = mtriangle;
ImageBox2.Image = mcircle;
ImageBox3.Image = mEllipse;
}
运行后如下图所示:
图6-12 轮廓的最小外接三角形、最小外接圆、拟合椭圆
6.3.7 拟合直线FitLine
拟合直线是指利用图像中的点集,找到一条最匹配点集的直线,使得该直线能够尽可能地覆盖所有的点。通过拟合直线,可以得到图像中点的分布情况,从而可以进行轮廓提取、边缘检测等操作。例如,在目标检测中,可以通过拟合直线来定位目标对象的位置和方向;在图像处理中,可以通过拟合直线来进行图像的分割、去除噪声、边缘检测等。
在Emgu.CV中,CvInvoke.FitLine函数用于拟合给定点集的直线,它的声明如下:
public static void FitLine(
IInputArray points,
IOutputArray line,
DistType distType,
double param,
double reps,
double aeps
)
参数说明:
- input:输入的点集。
- line:输出的直线,这是一个VectorOfFloat类型,包含四个元素,其中line(0),line(1)是一个方向向量;line(2),line(3)是直线上一个点。可以通过此输出获得直线的斜率和截距:
斜率 = line (1) / line (0)
截距 = line (3) – 斜率 * line (2)
- distType:距离类型,可以是DistType.L1或者DistType.L2。
- param:距离类型的参数,和所选的距离类型有关,值可以设置为0,FitLine本身会自动选择最优化的值。
- reps:拟合直线的径向精度,默认为0.01。
- aeps:拟合直线的角度精度,默认为0.01。
【代码位置:frmChapter6】Button12_Click
//FitLine
private void Button12_Click(object sender, EventArgs e)
{
Mat m = new Mat(500, 500, DepthType.Cv8U, 3);
Random rand = new Random();
Point[] pts = new Point[20];
//设置随机点
for (int i = 0; i < 20; i++)
{
int x = rand.Next(0, 500);
int y = rand.Next(0, 500);
pts[i] = new Point(x, y);
CvInvoke.Circle(m, pts[i], 3, new MCvScalar(0, 0, 255), -1);
}
VectorOfPoint vop = new VectorOfPoint(pts);
VectorOfFloat vof = new VectorOfFloat();
CvInvoke.FitLine(vop, vof, DistType.L2, 0, 0.01, 0.01);
//计算斜率
Double k = vof[1] / vof[0];
//计算截距
Double b = vof[3] - k * vof[2];
//获得直线起点
Point startpoint = new Point(0, (int)(b));
//获得直线终点
Point endpoint = new Point(m.Width - 1, (int)(k * (m.Width - 1) + b));
CvInvoke.Line(m, startpoint, endpoint, new MCvScalar(255, 0, 0), 2);
ImageBox1.Image = m;
}
运行后如下图所示:
图6-13 获得拟合直线
6.3.8 最短距离pointPolygonTest
CvInvoke.PointPolygonTest方法用于计算给定点到多边形的最短距离,或判断点是否在多边形内部。该方法声明如下:
public static double PointPolygonTest(
IInputArray contour,
PointF pt,
bool measureDist
)
参数说明:
- contour:轮廓。这是一个VectorOfPoint类型。对应使用FindContours方法获得的contours所包含的成员。
- pt:要计算的点的坐标。
- measureDist:如果设置为true,则计算最短距离;如果设置为false,则仅判断点是否在多边形内部。
返回值:
如果measureDist设置为true,则返回给定点到多边形的最短距离,正值表示点在多边形外部,负值表示点在多边形内部。
如果measureDist设置为false,则返回一个整数值:1表示点在多边形内部,0表示点在多边形边界上,-1表示点在多边形外部。
【代码位置:frmChapter6】Button13_Click
//PointPolygonTest 轮廓外一点到轮廓的距离
private void Button13_Click(object sender, EventArgs e)
{
Mat m = new Mat(400, 400, DepthType.Cv8U, 1);
m.SetTo(new MCvScalar(0));
//绘制一个矩形轮廓和一个圆形轮廓
CvInvoke.Rectangle(m, new Rectangle(50, 50, 100, 100), new MCvScalar(255), -1);
CvInvoke.Circle(m, new Point(300, 300), 100, new MCvScalar(255), -1);
Mat m1 = new Mat();
m1 = m.Clone();
Point p1 = new Point(240, 100);
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
VectorOfRect hierarchy = new VectorOfRect();
CvInvoke.FindContours(m1, contours, hierarchy, RetrType.List, ChainApproxMethod.ChainApproxSimple);
List<Double> dis = new List<Double>();
for (int i = 0; i < contours.Size; i++)
{
Double dis1 = CvInvoke.PointPolygonTest(contours[i], p1, true);
dis.Add(dis1);
//只看在轮廓的内外
//点在轮廓上,返回0,不计算距离返回0
//点在轮廓内,返回正数,不计算距离返回1
//点在轮廓外,返回负数,不计算距离返回-1
//Double dis2 = CvInvoke.PointPolygonTest(contours[i], p1, false);
//Console.WriteLine(dis2);
}
CvInvoke.Circle(m1, p1, 2, new MCvScalar(255), -1);
ImageBox1.Image = m1;
//使用获得的距离绘制圆形
Mat m3 = new Mat(400, 400, DepthType.Cv8U, 3);
m3.SetTo(new MCvScalar(0));
CvInvoke.Rectangle(m3, new Rectangle(50, 50, 100, 100), new MCvScalar(255, 255, 255), -1);
CvInvoke.Circle(m3, new Point(300, 300), 100, new MCvScalar(255, 255, 255), -1);
CvInvoke.Circle(m3, p1, 2, new MCvScalar(0, 0, 255), -1);
for (int i = 0; i < dis.Count; i++)
CvInvoke.Circle(m3, p1, (int)Math.Abs(dis[i]), new MCvScalar(0, 255, 0), 2);
ImageBox2.Image = m3;
}
运行后如下图所示:
图6-14 点到轮廓的外接圆
上述代码只能获得最小距离,如果要得到轮廓外一点到轮廓最小距离的轮廓上一点的坐标,可以采用以下方法:
1、图像M1上绘制图形A
2、获得图形A的轮廓A(M1上只有一个轮廓)
3、获得点A到图形A的最小距离d
4、图像M2上使用点A为圆心,最小距离d为半径画圆A
5、M2和M1做异或,得到圆A
6、获得圆A的轮廓B(M2上只有一个圆形的轮廓)
7、获得轮廓B上的每个点到轮廓A的距离,判断是否符合条件:理论上等于0就表示该点在轮廓A上,但是通常不会直接判断=0而是给定一个范围。
那么符合条件的点即为需要的坐标点。
实现代码如下:
【代码位置:frmChapter6】Button14_Click
//获得点到轮廓的最短距离
//以点为圆心,最短距离为半径画圆
//获得圆的轮廓上的点,这里可能会出现误差
//求出每个点到轮廓的最短距离
//如果为0,那么这个点在轮廓上,
//但是通常而言,会有偏差
//感觉此方法误差比较大
private void Button14_Click(object sender, EventArgs e)
{
Mat m = new Mat(400, 400, DepthType.Cv8U, 1);
m.SetTo(new MCvScalar(0));
CvInvoke.Rectangle(m, new Rectangle(50, 50, 100, 100), new MCvScalar(255), -1);
ImageBox1.Image = m;
Point p1 = new Point(240, 100);
Mat m1 = new Mat();
m1 = m.Clone();
//求出点到形状的距离
VectorOfVectorOfPoint contours1 = new VectorOfVectorOfPoint();
CvInvoke.FindContours(m1, contours1, null, RetrType.External, ChainApproxMethod.ChainApproxSimple);
//这里只有一个形状轮廓,所以直接使用contours[0]
Double dis1 = CvInvoke.PointPolygonTest(contours1[0], p1, true);
//获得最短距离后画圆
CvInvoke.Circle(m1, p1, (int)Math.Abs(dis1), new MCvScalar(255), 1);
ImageBox2.Image = m1;
//m和m1异或,这里获得到刚才生成的圆形
Mat m2 = new Mat();
CvInvoke.BitwiseXor(m, m1, m2);
VectorOfVectorOfPoint contours2 = new VectorOfVectorOfPoint();
CvInvoke.FindContours(m2, contours2, null, RetrType.External, ChainApproxMethod.ChainApproxSimple);
//这里只有一个形状轮廓
VectorOfPoint cirpoints = contours2[0];
//保存形状上符合距离条件的点
List<Point> shapes = new List<Point>();
for(int i = 0;i< cirpoints.Size;i++)
{
Point cirp = new Point(cirpoints[i].X, cirpoints[i].Y);
//不用计算距离,只需要获得是否在轮廓上
Double dis = CvInvoke.PointPolygonTest(contours1[0], cirp, true);
//小于2视为符合条件
if (Math.Abs(dis) <= 1)
shapes.Add(cirp);
}
Mat m3 = new Mat();
m3 = m.Clone();
CvInvoke.CvtColor(m3, m3, ColorConversion.Gray2Bgr);
//绘制点
CvInvoke.Circle(m3, p1, 2, new MCvScalar(0, 0, 255), -1);
//以点为圆心绘制圆,半径为最短距离
CvInvoke.Circle(m3, p1, (int)Math.Abs(dis1), new MCvScalar(0, 255, 0), 1);
//画出线段
foreach (Point p in shapes)
CvInvoke.Line(m3, p1, p, new MCvScalar(255, 0, 0), 1);
ImageBox3.Image = m3;
}
运行后如下图所示:
图6-15 轮廓与轮廓外一点最小距离的坐标点
通过运行结果,可以看出此方法获得的坐标点太多,误差比较大,为此在下面的代码中进行改进,采用平均值来获得坐标点。
【代码位置:frmChapter6】Button15_Click
//获得点到轮廓的最短距离
//对上面代码上的改进,增加了平均值
private void Button15_Click(object sender, EventArgs e)
{
Mat m = new Mat(400, 400, DepthType.Cv8U, 1);
m.SetTo(new MCvScalar(0));
//CvInvoke.Circle(m, new Point(300, 300), 100, new MCvScalar(255), -1);
CvInvoke.Rectangle(m, new Rectangle(50, 50, 100, 100), new MCvScalar(255), -1);
ImageBox1.Image = m;
Point p1 = new Point(240, 100);
Mat m1 = new Mat();
m1 = m.Clone();
//求出点到形状的距离
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
CvInvoke.FindContours(m1, contours, null, RetrType.External, ChainApproxMethod.ChainApproxSimple);
//这里只有一个形状轮廓
Double dis1 = CvInvoke.PointPolygonTest(contours[0], p1, true);
CvInvoke.Circle(m1, p1, (int)Math.Abs(dis1), new MCvScalar(255), 1);
ImageBox2.Image = m1;
Mat m2 = new Mat();
CvInvoke.BitwiseXor(m, m1, m2);
//ImageBox3.Image = m2;
VectorOfVectorOfPoint contours1 = new VectorOfVectorOfPoint();
CvInvoke.FindContours(m2, contours1, null, RetrType.External, ChainApproxMethod.ChainApproxSimple);
//这里只有一个形状轮廓
VectorOfPoint cirpoints = contours1[0];
List<Point> shapes = new List<Point>();
for (int i = 0; i < cirpoints.Size; i++)
{
Point cirp = new Point(cirpoints[i].X, cirpoints[i].Y);
//不用计算距离,只需要获得是否在轮廓上
Double dis = CvInvoke.PointPolygonTest(contours[0], cirp, true);
//距离是否在设置范围,这里是3,多少都没所谓,后面要计算平均值
if (Math.Abs(dis) <= 3)
shapes.Add(cirp);
}
Mat m3 = new Mat();
m3 = m.Clone();
CvInvoke.CvtColor(m3, m3, ColorConversion.Gray2Bgr);
CvInvoke.Circle(m3, p1, (int)Math.Abs(dis1), new MCvScalar(0, 255, 0), 1);
CvInvoke.Circle(m3, p1, 2, new MCvScalar(0, 0, 255), -1);
Double totalx = 0;
Double totaly = 0;
Double avgx;
Double avgy;
//平均值计算点坐标
for (int i = 0; i < shapes.Count; i++)
{
totalx += shapes[i].X;
totaly += shapes[i].Y;
}
avgx = totalx / shapes.Count;
avgy = totaly / shapes.Count;
CvInvoke.Line(m3, p1, new Point((int)(avgx), (int)(avgy)), new MCvScalar(255, 0, 0), 1);
ImageBox3.Image = m3;
}
运行后如下图所示:
图6-16 轮廓与轮廓外一点最小距离的坐标点