轮廓的查找、表达、绘制、特性及匹配(How to Use Contour? Find, Component, Construct, Features Match)

前言
    轮廓是构成任何一个形状的边界或外形线。前面讲了如何根据色彩及色彩的分布(直方图对比和模板匹配)来进行匹配,现在我们来看看如何利用物体的轮廓。包括以下内容:轮廓的查找、表达方式、组织方式、绘制、特性、匹配。

 查找轮廓
    首先我们面对的问题是如何在图像中找到轮廓,OpenCv(EmguCv)为我们做了很多工作,我们的任务只是调用现成的函数而已。Image<TColor,TDepth>类的FindContours方法可以很方便的查找轮廓,不过在查找之前,我们需要将彩色图像转换成灰度图像,然后再将灰度图像转换成二值图像。代码如下所示:

Image<Bgr, Byte> imageSource = new Image<Bgr, byte>(sourceImageFileName); //获取源图像
Image<Gray, Byte> imageGray = imageSource.Convert<Gray, Byte>(); //将源图像转换成灰度图像
int thresholdValue = tbThreshold.Value; //用于二值化的阀值
Image<Gray, Byte> imageThreshold = imageGray.ThresholdBinary(new Gray(thresholdValue), new Gray(255d)); //对灰度图像二值化
Contour<Point> contour=imageThreshold.FindContours();

 

轮廓的表达方式
    使用上面的代码可以得到图像的默认轮廓,但是轮廓在电脑中是如何表达的呢?在OpenCv(EmguCv)中提供了两类表达轮廓的方式:顶点的序列、Freeman链码。

1.顶点的序列
    用多个顶点(或各点间的线段)来表达轮廓。假设要表达一个从(0,0)到(2,2)的矩形,
(1)如果用点来表示,那么依次存储的可能是:(0,0),(1,0),(2,0),(2,1),(2,2),(1,2),(0,2),(0,1);
(2)如果用点间的线段来表达轮廓,那么依次存储的可能是:(0,0),(2,0),(2,2),(0,2)。
以下代码可以用来获取轮廓上的点:

for (int i = 0; i < contour.Total; i++)
sbContour.AppendFormat(
"{0},", contour[i]);

 

 2.Freeman链码
    Freeman链码需要一个起点,以及从起点出发的一系列位移。每个位移有8个方向,从0~7分别指向从正北开始的8个方向。假设要用Freeman链码表达从(0,0)到(2,2)的矩形,可能的表示方法是:起点(0,0),方向链2,2,4,4,6,6,0,0。
    EmguCv对Freeman链码的支持很少,我们需要做一系列的工作才能在.net中使用Freeman链码:
(1)获取Freeman链码

复制代码
//查找用Freeman链码表示的轮廓
Image<Gray,Byte> imageTemp=imageThreshold.Copy();
IntPtr storage
= CvInvoke.cvCreateMemStorage(0);
IntPtr ptrFirstChain
= IntPtr.Zero;
int total = CvInvoke.cvFindContours(imageTemp.Ptr, storage, ref ptrFirstChain, sizeof(MCvChain), mode, CHAIN_APPROX_METHOD.CV_CHAIN_CODE, new Point(0, 0));
复制代码


(2)遍历Freeman链码上的点

复制代码
//初始化Freeman链码读取
[DllImport("cv200.dll")]
public static extern void cvStartReadChainPoints(IntPtr ptrChain,IntPtr ptrReader);
//读取Freeman链码的点
[DllImport("cv200.dll")]
public static extern Point cvReadChainPoint(IntPtr ptrReader);
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet
= System.Runtime.InteropServices.CharSet.Ansi)]
//定义链码读取结构
public struct MCvChainPtReader
{
//seqReader
public MCvSeqReader seqReader;
/// char
public byte code;
/// POINT->tagPOINT
public Point pt;
/// char[16]
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 16)]
public string deltas;
}

//将链码指针转换成结构
MCvChain chain=(MCvChain)Marshal.PtrToStructure(ptrChain,typeof(MCvChain));
//定义存放链码上点的列表
List<Point> pointList = new List<Point>(chain.total);
//链码读取结构
MCvChainPtReader chainReader = new MCvChainPtReader();
IntPtr ptrReader
= Marshal.AllocHGlobal(sizeof(MCvSeqReader) + sizeof(byte) + sizeof(Point) + 16 * sizeof(byte));
Marshal.StructureToPtr(chainReader, ptrReader,
false);
//开始读取链码
cvStartReadChainPoints(ptrChain, ptrReader);
int i = 0;
while (ptrReader != IntPtr.Zero && i < chain.total)
{
//依次读取链码上的每个点
Point p = cvReadChainPoint(ptrReader);
if (ptrReader == IntPtr.Zero)
break;
else
{
pointList.Add(p);
sbChain.AppendFormat(
"{0},", p);
i
++;
}
}
imageResult.DrawPolyline(pointList.ToArray(),
true, new Bgr(lblExternalColor.BackColor), 2);
复制代码

  

    需要注意的是:cvReadChainPoint函数似乎永远不会满足循环终止的条件,即ptrReader永远不会被置为null,这跟《学习OpenCv》和参考上不一致;我们需要用chain.total来辅助终止循环,读取了所有的点之后就可以罢手了。

轮廓之间的组织方式
    在查找到轮廓之后,不同轮廓是怎么组织的呢?根据不同的选择,它们可能是:(1)列表;(2)双层结构;(3)树型结构。
    从纵向上来看,列表只有一层,双层结构有一或者两层,树型结构可能有一层或者多层。
    如果要遍历所有的轮廓,可以使用递归的方式,代码如下:

复制代码
//遍历轮廓,并生成遍历结果
private void TravelContour(Contour<Point> contour,ref int total,ref StringBuilder sbContour)
{
if (contour != null)
{
sbContour.Append(
"------------------------\r\n");
sbContour.AppendFormat(
"轮廓{0},右节点:{1},下级节点:{2},外接矩形:({3})\r\n", total, contour.HNext != null, contour.VNext != null, contour.BoundingRectangle);
sbContour.AppendFormat(
"包含{0}个点(面积:{1},周长:{2}):\r\n", contour.Total, contour.Area, contour.Perimeter);
for (int i = 0; i < contour.Total; i++)
sbContour.AppendFormat(
"{0},", contour[i]);
sbContour.Append(
"\r\n");
total
++;
if (contour.HNext != null)
TravelContour(contour.HNext,
ref total, ref sbContour);
if (contour.VNext != null)
TravelContour(contour.VNext,
ref total, ref sbContour);
}
}
复制代码

 

轮廓的绘制
    轮廓的绘制比较简单,用上面提到的方法取得轮廓的所有点,然后把这些点连接成一个多边形即可。
    当然,对于用顶点序列表示的轮廓,用Image<TColor,TDepth>.Draw方法或者cvDrawContours函数可以很方便的绘制出轮廓。我发现,如果将参数max_level设置成2,可以绘制出所有的轮廓。
    绘制轮廓的代码如下:

Image<Bgr, Byte> imageResult = imageThreshold.Convert<Bgr, Byte>(); //结果图像
int maxLevel = 0; //绘制的轮廓深度
int.TryParse(txtMaxLevel.Text, out maxLevel);
imageResult.Draw(contour,
new Bgr(lblExternalColor.BackColor), new Bgr(lblHoleColor.BackColor), maxLevel, 2);


轮廓的特性
    轮廓的特性有很多,下面一一介绍。

1.轮廓的多边形逼近
    轮廓的多边形逼近指的是:使用多边形来近似表示一个轮廓。
    多边形逼近的目的是为了减少轮廓的顶点数目。
    多边形逼近的结果依然是一个轮廓,只是这个轮廓相对要粗旷一些。
    可以使用Contour<Point>.ApproxPoly方法或者cvApproxyPoly函数来对轮廓进行多边形逼近,示例代码如下:

contour = firstContour.ApproxPoly(double.Parse(txtApproxParameter.Text), 2, new MemStorage());

  

2.轮廓的关键点
    轮廓的关键点是:轮廓上包含曲线信息比较多的点。关键点是轮廓顶点的子集。
    可以使用cvFindDominantPoints函数来获取轮廓上的关键点,该函数返回的结果一个包含 关键点在轮廓顶点中索引 的序列。再次强调:是索引,不是具体的点。如果要得到关键点的具体坐标,可以用索引到轮廓上去找。
    以下代码演示了如何获取轮廓上的关键点:

复制代码
//得到关键点信息
private void GetDominantPointsInfo(Contour<Point> contour, ref StringBuilder sbContour, ref Image<Bgr, Byte> imageResult, double parameter1, double parameter2, double parameter3, double parameter4, Bgr dominantPointColor)
{
if (contour.Total > 2)
{
MemStorage storage
= new MemStorage();
try
{
IntPtr ptrSeq
= cvFindDominantPoints(contour.Ptr, storage.Ptr, (int)CV_DOMINANT.CV_DOMINANT_IPAN, parameter1, parameter2, parameter3, parameter4);
Seq
<int> seq = new Seq<int>(ptrSeq, storage);
sbContour.AppendFormat(
"{0}个关键点:\r\n", seq.Total);
for (int i = 0; i < seq.Total; i++)
{
int idx = seq[i]; //关键点序列中存储的数据 是 关键点在轮廓中所处位置的索引
Point p = contour[idx]; //得到关键点的坐标
sbContour.AppendFormat("{0}({1},{2}),", idx, p.X, p.Y);
imageResult.Draw(
new CircleF(new PointF(p.X, p.Y), 3), dominantPointColor, -1);
}
sbContour.Append(
"\r\n");
}
catch (CvException ex)
{
sbContour.AppendFormat(
"在获取关键点时发生异常,错误描述:{0},错误源:{1},错误堆栈:{2}\r\n错误文件:{3},函数名:{4},行:{5},错误内部描述:{6}\r\n", ex.Message, ex.Source, ex.StackTrace, ex.FileName, ex.FunctionName, ex.Line, ex.ErrorStr);
}
catch (Exception e)
{
sbContour.AppendFormat(
"在获取关键点时发生异常,错误描述:{0},错误源:{1},错误堆栈:{2}\r\n", e.Message, e.Source, e.StackTrace);
}
finally
{
storage.Dispose();
}
}
}
复制代码


3.轮廓的周长和面积
    轮廓的周长可以用Contour<Point>.Perimeter属性或者cvArcLength函数来获取。
    轮廓的面积可以用Contour<Point>.Area属性或者cvContourArea函数来获取。

4.轮廓的边界框
    有三种常见的边界框:矩形、圆形、椭圆。
    (1)矩形:在图像处理系统中提供了一种叫Rectangle的矩形,不过它只能表达边垂直或水平的特例;OpenCv中还有一种叫Box的矩形,它跟数学上的矩形一致,只要4个角是直角即可。
    如果要获取轮廓的Rectangle,可以使用Contour<Point>.BoundingRectangle属性或者cvBoundingRect函数。
    如果要获取轮廓的Box,可以使用Contour<Point>.GetMinAreaRect方法或者cvMinAreaRect2函数。
    (2)圆形
    如果要获取轮廓的圆形边界框,可以使用cvMinEnclosingCircle函数。
    (3)椭圆
    如果要获取轮廓的椭圆边界框,可以使用cvFitEllipse2函数。
    下列代码演示了如何获取轮廓的各种边界框:

复制代码
//得到边界框信息
private void GetEdgeInfo(Contour<Point> contour, string edge, ref StringBuilder sbContour, ref Image<Bgr, Byte> imageResult, Bgr edgeColor)
{
if (edge == "Rect")
//矩形
imageResult.Draw(contour.BoundingRectangle, edgeColor, 2);
else if (edge == "MinAreaRect")
{
//最小矩形
MCvBox2D box = CvInvoke.cvMinAreaRect2(contour.Ptr, IntPtr.Zero);
PointF[] points
= box.GetVertices();
Point[] ps
= new Point[points.Length];
for (int i = 0; i < points.Length; i++)
ps[i]
= new Point((int)points[i].X, (int)points[i].Y);
imageResult.DrawPolyline(ps,
true, edgeColor, 2);
}
else if (edge == "Circle")
{
//圆形
PointF center;
float radius;
CvInvoke.cvMinEnclosingCircle(contour.Ptr,
out center, out radius);
imageResult.Draw(
new CircleF(center, radius), edgeColor, 2);
}
else
{
//椭圆
if (contour.Total >= 6)
{
MCvBox2D box
= CvInvoke.cvFitEllipse2(contour.Ptr);
imageResult.Draw(
new Ellipse(box), edgeColor, 2);
}
else
sbContour.Append(
"轮廓点数小于6,不能创建外围椭圆。\r\n");
}
}
复制代码

  

5.轮廓的矩
    我们可以使用Contour<Point>.GetMoments方法或者cvMoments函数方便的得到轮廓的矩集,然后再相应的方法或函数获取各种矩。
    特定的矩:MCvMoments.GetSpatialMoment方法、cvGetSpatialMoment函数
    中心矩:MCvMoments.GetCentralMoment方法、cvGetCentralMoment函数
    归一化中心矩:MCvMoments.GetNormalizedCentralMoment方法、cvGetNormalizedCentralMoment函数
    Hu矩:MCvMoments.GetHuMoment方法、McvHuMoments.hu1~hu7字段、cvGetHuMoments函数
    以下代码演示了如何获取轮廓的矩:

复制代码
//得到各种矩的信息
private void GetMomentsInfo(Contour<Point> contour, ref StringBuilder sbContour)
{
//
MCvMoments moments = contour.GetMoments();
//遍历各种情况下的矩、中心矩及归一化矩,必须满足条件:xOrder>=0; yOrder>=0; xOrder+yOrder<=3;
for (int xOrder = 0; xOrder <= 3; xOrder++)
{
for (int yOrder = 0; yOrder <= 3; yOrder++)
{
if (xOrder + yOrder <= 3)
{
double spatialMoment = moments.GetSpatialMoment(xOrder, yOrder);
double centralMoment = moments.GetCentralMoment(xOrder, yOrder);
double normalizedCentralMoment = moments.GetNormalizedCentralMoment(xOrder, yOrder);
sbContour.AppendFormat(
"矩(xOrder:{0},yOrder:{1}),矩:{2:F09},中心矩:{3:F09},归一化矩:{4:F09}\r\n", xOrder, yOrder, spatialMoment, centralMoment, normalizedCentralMoment);
}
}
}
//Hu矩
MCvHuMoments huMonents = moments.GetHuMoment();
sbContour.AppendFormat(
"Hu矩 h1:{0:F09},h2:{1:F09},h3:{2:F09},h4:{3:F09},h5:{4:F09},h6:{5:F09},h7:{6:F09}\r\n", huMonents.hu1, huMonents.hu2, huMonents.hu3, huMonents.hu4, huMonents.hu5, huMonents.hu6, huMonents.hu7);
}
复制代码


6.轮廓的轮廓树
    轮廓树用来描述某个特定轮廓的内部特征。注意:轮廓树跟轮廓是一一对应的关系;轮廓树不用于描述多个轮廓之间的层次关系。
    可以用函数cvCreateContourTree来构造轮廓树。

IntPtr ptrTree1 = CvInvoke.cvCreateContourTree(contour1.Ptr, new MemStorage().Ptr, thresholdOfCreate);

 

 7.轮廓的凸包和凸缺陷
    轮廓的凸包和凸缺陷用于描述物体的外形。凸包和凸缺陷很容易获得,不过我目前不知道它们到底怎么使用。
    如果要判断轮廓是否是凸的,可以用Contour<Point>.Convex属性和cvCheckContourConvexity函数。
    如果要获取轮廓的凸包,可以用Contour<Point>.GetConvexHull方法或者cvConvexHull2函数,返回的是包含顶点的序列。
    如果要获取轮廓的凸缺陷,可以用Contour<Point>.GetConvexityDefacts方法或者cvConvexityDefects函数。
    注意:EmguCv将缺陷的单词拼写错了,defect才是缺陷。
    以下代码演示了如何获取轮廓的凸包及凸缺陷:

复制代码
//得到凸包及缺陷信息
private void GetConvexInfo(Contour<Point> contour,ref StringBuilder sbContour,ref Image<Bgr,Byte> imageResult)
{
if (!contour.Convex) //判断轮廓是否为凸
{
//凸包
Seq<Point> convexHull = contour.GetConvexHull(ORIENTATION.CV_CLOCKWISE);
//缺陷
Seq<MCvConvexityDefect> defects = contour.GetConvexityDefacts(new MemStorage(), ORIENTATION.CV_CLOCKWISE);
//显示信息
sbContour.AppendFormat("轮廓的凸包有{0}个点,依次为:", convexHull.Total);
Point[] points
= new Point[convexHull.Total];
for (int i = 0; i < convexHull.Total; i++)
{
Point p
= convexHull[i];
points[i]
= p;
sbContour.AppendFormat(
"{0},", p);
}
sbContour.Append(
"\r\n");
imageResult.DrawPolyline(points,
true, new Bgr(lblConvexColor.BackColor), 2);
MCvConvexityDefect defect;
sbContour.AppendFormat(
"轮廓有{0}个缺陷,依次为:\r\n", defects.Total);
for (int i = 0; i < defects.Total; i++)
{
defect
= defects[i];
sbContour.AppendFormat(
"缺陷:{0},起点:{1},终点:{2},最深的点:{3},深度:{4}\r\n", i, defect.StartPoint, defect.EndPoint, defect.DepthPoint, defect.Depth);
}
}
else
sbContour.Append(
"轮廓是凸的,凸包和轮廓一样。\r\n");
}
复制代码

 

 8.轮廓的成对几何直方图
    成对几何直方图的资料比较少,我是这么理解的。
    (1)轮廓保存的是一系列的顶点,轮廓是由一系列线段组成的多边形。对于看起来光滑的轮廓(例如圆),只是线段条数比较多,线段长度比较短而已。实际上,电脑中显示的任何曲线都由线段组成。
    (2)每两条线段之间都有一定的关系,包括它们(或者它们的延长线)之间的夹角,两条线段的夹角范围是:(0,180)。
    (3)每两条线段上的点之间还有距离关系,包括最短(小)距离、最远(大)距离,以及平均距离。最大距离我用了一个偷懒的计算方法,我把轮廓外界矩形的对角线长度看作了最大距离。
    (4)成对几何直方图所用的统计数据包括了夹角和距离。
    可以用函数cvCalcPGH来计算轮廓的成对几何直方图,示例代码如下:

复制代码
//生成成对几何直方图
Rectangle rect1 = contour1.BoundingRectangle;
float maxDist1 = (float)Math.Sqrt(rect1.Width * rect1.Width + rect1.Height * rect1.Height); //轮廓的最大距离:这里使用轮廓矩形边界框的对角线长度
int[] bins1 = new int[] { 60, 20 };
RangeF[] ranges1
= new RangeF[] { new RangeF(0f, 180f), new RangeF(0f, maxDist1) }; //直方图第0维为角度,范围在(0,180),第2维为轮廓两条边缘线段的距离
DenseHistogram hist1 = new DenseHistogram(bins1, ranges1);
CvInvoke.cvCalcPGH(contour1.Ptr, hist1.Ptr);
复制代码

 

 

轮廓的匹配
    如果要比较两个物体,可供选择的特征很多。如果要判断某个人的性别,可以根据他(她)头发的长短来判断,这很直观,在长发男稀有的年代准确率也很高。也可以根据这个人尿尿的射程来判断,如果射程大于0.50米,则是男性。总之,方法很多,不一而足。
    我们在上文中得到了轮廓的这么多特征,它们也可以用于进行匹配。典型的轮廓匹配方法有:Hu矩匹配、轮廓树匹配、成对几何直方图匹配。
1.Hu矩匹配
    轮廓的Hu矩对包括缩放、旋转和镜像映射在内的变化具有不变性。Contour<Point>.MatchShapes方法和cvMatchShapes函数可以很方便的实现对2个轮廓间的匹配。
2.轮廓树匹配
    用树的形式比较两个轮廓。cvMatchContourTrees函数实现了轮廓树的对比。
3.成对几何直方图匹配
    在得到轮廓的成对几何直方图之后,可以使用直方图对比的方法来进行匹配。如果您和我一样忘记了直方图的对比方式,可以看看我写的另一篇文章《颜色直方图的计算、显示、处理、对比及反向投影(How to Use Histogram? Calculate, Show, Process, Compare and BackProject)》。

    各种轮廓匹配的示例代码如下:

复制代码
//开始匹配
private void btnStartMatch_Click(object sender, EventArgs e)
{
//准备轮廓(这里只比较最外围的轮廓)
Image<Bgr, Byte> image1 = new Image<Bgr, byte>((Bitmap)pbImage1.Image);
Image
<Bgr, Byte> image2 = new Image<Bgr, byte>((Bitmap)pbImage2.Image);
Image
<Gray, Byte> imageGray1 = image1.Convert<Gray, Byte>();
Image
<Gray, Byte> imageGray2 = image2.Convert<Gray, Byte>();
Image
<Gray, Byte> imageThreshold1 = imageGray1.ThresholdBinaryInv(new Gray(128d), new Gray(255d));
Image
<Gray, Byte> imageThreshold2 = imageGray2.ThresholdBinaryInv(new Gray(128d), new Gray(255d));
Contour
<Point> contour1 = imageThreshold1.FindContours(CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE, RETR_TYPE.CV_RETR_EXTERNAL);
Contour
<Point> contour2 = imageThreshold2.FindContours(CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE, RETR_TYPE.CV_RETR_EXTERNAL);
/*if (contour1.Perimeter / 50 > 2 && contour2.Perimeter / 50 > 2)
{
contour1 = contour1.ApproxPoly(contour1.Perimeter / 50, 2, new MemStorage()); //对轮廓进行多边形逼近(参数设为轮廓周长的1/50)
contour2 = contour2.ApproxPoly(contour2.Perimeter / 50, 2, new MemStorage());
}
*/
//进行匹配
string result = "";
if (rbHuMoments.Checked)
result
= MatchShapes(contour1, contour2); //Hu矩匹配
else if (rbContourTree.Checked)
result
= MatchContourTrees(contour1, contour2); //轮廓树匹配
else if (rbPGH.Checked)
result
= MatchPghHist(contour1, contour2); //成对几何直方图匹配
txtResult.Text += result;
}

//Hu矩匹配
private string MatchShapes(Contour<Point> contour1, Contour<Point> contour2)
{
//匹配方法
CONTOURS_MATCH_TYPE matchType = rbHuI1.Checked ? CONTOURS_MATCH_TYPE.CV_CONTOUR_MATCH_I1 : (rbHuI2.Checked ? CONTOURS_MATCH_TYPE.CV_CONTOURS_MATCH_I2 : CONTOURS_MATCH_TYPE.CV_CONTOURS_MATCH_I3);
Stopwatch sw
= new Stopwatch();
sw.Start();
//匹配
double matchValue = contour1.MatchShapes(contour2, matchType);
sw.Stop();
double time = sw.Elapsed.TotalMilliseconds;
return string.Format("Hu矩匹配({0:G}),结果:{1:F05},用时:{2:F05}毫秒\r\n", matchType, matchValue, time);
}

//轮廓树匹配
private string MatchContourTrees(Contour<Point> contour1, Contour<Point> contour2)
{
//生成轮廓树
double thresholdOfCreate = double.Parse(txtThresholdOfCreateContourTrees.Text); //生成轮廓树的阀值
IntPtr ptrTree1 = CvInvoke.cvCreateContourTree(contour1.Ptr, new MemStorage().Ptr, thresholdOfCreate);
IntPtr ptrTree2
= CvInvoke.cvCreateContourTree(contour2.Ptr, new MemStorage().Ptr, thresholdOfCreate);
//匹配
double thresholdOfMatch = double.Parse(txtThresholdOfMatchContourTrees.Text); //比较轮廓树的阀值
Stopwatch sw = new Stopwatch();
sw.Start();
double matchValue = CvInvoke.cvMatchContourTrees(ptrTree1, ptrTree2, MATCH_CONTOUR_TREE_METHOD.CONTOUR_TREES_MATCH_I1, thresholdOfMatch);
sw.Stop();
double time = sw.Elapsed.TotalMilliseconds;
return string.Format("轮廓树匹配(生成轮廓树的阀值:{0},比较轮廓树的阀值:{1}),结果:{2:F05},用时:{3:F05}毫秒\r\n", thresholdOfCreate, thresholdOfMatch, matchValue, time);
}

//成对几何直方图匹配
private string MatchPghHist(Contour<Point> contour1, Contour<Point> contour2)
{
//生成成对几何直方图
Rectangle rect1 = contour1.BoundingRectangle;
float maxDist1 = (float)Math.Sqrt(rect1.Width * rect1.Width + rect1.Height * rect1.Height); //轮廓的最大距离:这里使用轮廓矩形边界框的对角线长度
int[] bins1 = new int[] { 60, 20 };
RangeF[] ranges1
= new RangeF[] { new RangeF(0f, 180f), new RangeF(0f, maxDist1) }; //直方图第0维为角度,范围在(0,180),第2维为轮廓两条边缘线段的距离
DenseHistogram hist1 = new DenseHistogram(bins1, ranges1);
CvInvoke.cvCalcPGH(contour1.Ptr, hist1.Ptr);
Rectangle rect2
= contour2.BoundingRectangle;
float maxDist2 = (float)Math.Sqrt(rect2.Width * rect2.Width + rect2.Height * rect2.Height);
int[] bins2 = new int[] { 60, 20 };
RangeF[] ranges2
= new RangeF[] { new RangeF(0f, 180f), new RangeF(0f, maxDist2) };
DenseHistogram hist2
= new DenseHistogram(bins2, ranges2);
CvInvoke.cvCalcPGH(contour2.Ptr, hist2.Ptr);
//匹配
Stopwatch sw = new Stopwatch();
sw.Start();
double compareResult;
HISTOGRAM_COMP_METHOD compareMethod
= rbHistCorrel.Checked ? HISTOGRAM_COMP_METHOD.CV_COMP_CORREL : (rbHistChisqr.Checked ? HISTOGRAM_COMP_METHOD.CV_COMP_CHISQR : (rbHistIntersect.Checked ? HISTOGRAM_COMP_METHOD.CV_COMP_INTERSECT : HISTOGRAM_COMP_METHOD.CV_COMP_BHATTACHARYYA));
if (rbHistEmd.Checked)
{
//EMD
//将直方图转换成矩阵
Matrix<Single> matrix1 = FormProcessHist.ConvertDenseHistogramToMatrix(hist1);
Matrix
<Single> matrix2 = FormProcessHist.ConvertDenseHistogramToMatrix(hist2);
compareResult
= CvInvoke.cvCalcEMD2(matrix1.Ptr, matrix2.Ptr, DIST_TYPE.CV_DIST_L2, null, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
matrix1.Dispose();
matrix2.Dispose();
}
else
{
//直方图对比方式
hist1.Normalize(1d);
hist2.Normalize(1d);
compareResult
= CvInvoke.cvCompareHist(hist1.Ptr, hist2.Ptr, compareMethod);
}
sw.Stop();
double time = sw.Elapsed.TotalMilliseconds;
return string.Format("成对几何直方图匹配(匹配方式:{0}),结果:{1:F05},用时:{2:F05}毫秒\r\n", rbHistEmd.Checked ? "EMD" : compareMethod.ToString("G"), compareResult, time);
}

复制代码

 

  

    通过以上代码,可以计算出两个轮廓对比的值,但是这些值具体代表什么意义呢?实际上,我目前还不清楚,需要进行大量的试验才行。

 

---------------------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

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

 

namespace Chuanlin2012._01
{
    public partial class MainFrm : Form
    {
        public Image<Bgr, byte> src;
        public Image<Bgr, byte> subsrc;     

        public MainFrm()
        {
            InitializeComponent();
        }

 

        private void 打开ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            OpenFileDialog opflg = new OpenFileDialog();
            opflg.Filter = "所有文件|*.*";
            if (opflg.ShowDialog() == DialogResult.OK)
            {
                Image<Bgr, byte> rgbimg1 = new Image<Bgr, byte>(opflg.FileName);
                imageBox1.Image = rgbimg1;
                src = rgbimg1;
              
            }

        }

 

        private void 保存ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (imageBox1.Image != null)
            {
                SaveFileDialog sfdlg = new SaveFileDialog();
                sfdlg.Filter = "bmp文件|*.bmp";
                sfdlg.FilterIndex = 2;
                sfdlg.RestoreDirectory = true;

                if (sfdlg.ShowDialog() == DialogResult.OK)
                {
                    string fname = sfdlg.FileName;
                    Bitmap bmp = imageBox1.Image.Bitmap;
                    bmp.Save(fname, System.Drawing.Imaging.ImageFormat.Bmp);
                }
            }
            else
            {
                System.Windows.Forms.MessageBox.Show("请先打开图像!");
                return;
            }

 


        }

 

   private void 退出ToolStripMenuItem_Click_1(object sender, EventArgs e)
        {
            this.Close();

        }

 

        private void sobel算子ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (imageBox1.Image != null)
            {
                Image<Bgr, float> imgsoble = src.Sobel(0, 1, 3);
                imageBox1.Image = imgsoble;
            }
            else
            {
                System.Windows.Forms.MessageBox.Show("请先打开图像!");
                return;
            }
                
        }

 

                private void laplace算子ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (imageBox1.Image != null)
            {
                Image<Bgr, float> imglaplace = src.Laplace(3);
                imageBox1.Image = imglaplace;
            }
            else 
            {
                System.Windows.Forms.MessageBox.Show("请先打开图像!");
                return;
            }

        }


             private void canny算子ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (imageBox1.Image != null)
            {

                //CannyFrm cfrm = new CannyFrm();
                //cfrm.Show();
                double m = 90;
                double n = 255;
                Gray threshold1 = new Gray(m);
                Gray threshold2 = new Gray(n);
                Image<Gray, byte> imggray = src.Convert<Gray, byte>();
                Image<Gray, byte> imgcanny = imggray.Canny(threshold1, threshold2);
                
                imageBox1.Image = imgcanny;
               

            }
            else
                System.Windows.Forms.MessageBox.Show("请先输入图像!");
        }

 

        private void 二值化后绘制图像轮廓ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (imageBox1.Image != null)
            {
                Image<Gray, byte> imggray = src.Convert<Gray, byte>();
                Image<Gray, byte> img2b = imggray.ThresholdBinary(new Gray(60), new Gray(255));
                Contour<Point> contour = img2b.FindContours();        //找轮廓
                Image<Bgr, byte> imgbgr = img2b.Convert<Bgr, byte>();
                int maxLevel = 0;                                                           //绘制的轮廓深度
                imgbgr.Draw(contour, new Bgr(0, 0, 255), new Bgr(0, 0, 255), maxLevel, 1);//绘制图像轮廓
                imageBox1.Image = imgbgr;                            
            }
            else
                System.Windows.Forms.MessageBox.Show("请先输入图像!");
        }

 

 

        private void SmoothBlur算子ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (imageBox1.Image != null)
            {
                Image<Bgr, byte> imgsmoot = src.SmoothBlur(9, 9);
                imageBox1.Image = imgsmoot;
 
            }
        }

 

        private void button1_Click(object sender, EventArgs e)
        {
            OpenFileDialog opdlg = new OpenFileDialog();
            opdlg.Filter = "所有文件|*.*";
            if (opdlg.ShowDialog() == DialogResult.OK)
            {
                Image<Bgr, byte> imgrgb = new Image<Bgr, byte>(opdlg.FileName);
                imageBox2.Image = imgrgb;
                subsrc = imgrgb;
            }
        }

 

//六种模板匹配方法

        private void radioButton1_CheckedChanged(object sender, EventArgs e)
        {
            if (imageBox1.Image != null && imageBox2.Image != null)
            {
                double time;
                double totalTime = 0;
                Stopwatch sw = new Stopwatch();
                sw.Start();
 
                //输入图像            
                Image<Bgr, Byte> imageInput =src ;
                //模板图像            
                Image<Bgr, Byte> imageTemplate = subsrc;

              
                //匹配方式数组
                TM_TYPE tmType = TM_TYPE.CV_TM_CCOEFF;

                //输出图像(匹配结果)
                Image<Gray, Single> imageResult = imageInput.MatchTemplate(imageTemplate,tmType);             

                //归一化结果               
                //CvInvoke.cvNormalize(imageResult.Ptr, imageResult.Ptr, 1d, 0d, NORM_TYPE.CV_MINMAX, IntPtr.Zero);
                //imageBox1.Image = imageResult;
                
                //找到最匹配的点,以及该点的值                
                double bestValue;                
                Point bestPoint;                
                FindBestMatchPointAndValue(imageResult, tmType, out bestValue, out bestPoint);
                //在最匹配的点附近画一个跟模板一样大的矩形                
                Rectangle rect = new Rectangle(new Point(bestPoint.X - imageTemplate.Size.Width / 2, bestPoint.Y - imageTemplate.Size.Height / 2), imageTemplate.Size);               
                imageResult.Draw(rect, new Gray(bestValue), 2);
                imageBox1.Image = imageResult;               
                time = sw.Elapsed.TotalMilliseconds; 
                totalTime += time;
                sw.Stop();
                textBox1.Text = totalTime.ToString();
                sw.Reset();
         
            }
            else
                System.Windows.Forms.MessageBox.Show("请先输入图像!");

        }
      
         private void radioButton2_CheckedChanged(object sender, EventArgs e)
         {
             if (imageBox1.Image != null && imageBox2.Image != null)
             {
                 //输入图像            
                 Image<Bgr, Byte> imageInput = src;
                 //模板图像            
                 Image<Bgr, Byte> imageTemplate = subsrc;

                 TM_TYPE tmtype = TM_TYPE.CV_TM_CCORR;

                 Image<Gray, Single> imgResult = imageInput.MatchTemplate(imageTemplate, tmtype);

                 //找到最好的点和最好点的值
                 double bestValue;
                 Point bestPoint;
                 FindBestMatchPointAndValue(imgResult,tmtype,out  bestValue,out  bestPoint);

                 Rectangle rect = new Rectangle(new Point(bestPoint.X - imageTemplate.Width / 2, bestPoint.Y - imageTemplate.Height / 2), imageTemplate.Size);
                 imgResult.Draw(rect, new Gray(bestValue), 2);
                 imageBox1.Image = imgResult;

             }
             else
             {
                 System.Windows.Forms.MessageBox.Show("请先输入图像!");
             }
         }

 

         private void radioButton3_CheckedChanged(object sender, EventArgs e)
         {
               if(imageBox1.Image!=null && imageBox2.Image!=null)
               {
                   //输入图像            
                 Image<Bgr, Byte> imageInput = src;
                 //模板图像            
                 Image<Bgr, Byte> imageTemplate = subsrc;

                 TM_TYPE tmtype = TM_TYPE.CV_TM_SQDIFF;

                 Image<Gray, Single> imgResult = imageInput.MatchTemplate(imageTemplate, tmtype);

                 //找到最好的点和最好点的值
                 double bestValue;
                 Point bestPoint;
                 FindBestMatchPointAndValue(imgResult,tmtype,out  bestValue,out  bestPoint);

                 Rectangle rect = new Rectangle(new Point(bestPoint.X - imageTemplate.Width / 2, bestPoint.Y - imageTemplate.Height / 2), imageTemplate.Size);
                 imgResult.Draw(rect, new Gray(bestValue), 2);
                 imageBox1.Image = imgResult;
            

             }
             else
             {
                 System.Windows.Forms.MessageBox.Show("请先输入图像!");
             }
         }

 

         private void radioButton4_CheckedChanged(object sender, EventArgs e)
         {
             if (imageBox1.Image != null && imageBox2.Image != null)
             {
                 //输入图像            
                 Image<Bgr, Byte> imageInput = src;
                 //模板图像            
                 Image<Bgr, Byte> imageTemplate = subsrc;

                 TM_TYPE tmtype = TM_TYPE.CV_TM_CCOEFF_NORMED;

                 Image<Gray, Single> imgResult = imageInput.MatchTemplate(imageTemplate, tmtype);

                 //找到最好的点和最好点的值
                 double bestValue;
                 Point bestPoint;
                 FindBestMatchPointAndValue(imgResult, tmtype, out  bestValue, out  bestPoint);

                 Rectangle rect = new Rectangle(new Point(bestPoint.X - imageTemplate.Width / 2, bestPoint.Y - imageTemplate.Height / 2), imageTemplate.Size);
                 imgResult.Draw(rect, new Gray(bestValue), 2);
                 imageBox1.Image = imgResult;
                
             }
             else
             {
                 System.Windows.Forms.MessageBox.Show("请先输入图像!");
             }
         }

 

         private void radioButton5_CheckedChanged(object sender, EventArgs e)
         {
             if (imageBox1.Image != null && imageBox2.Image != null)
             {
                 //输入图像            
                 Image<Bgr, Byte> imageInput = src;
                 //模板图像            
                 Image<Bgr, Byte> imageTemplate = subsrc;

                 TM_TYPE tmtype = TM_TYPE.CV_TM_CCORR_NORMED;

                 Image<Gray, Single> imgResult = imageInput.MatchTemplate(imageTemplate, tmtype);

                 //找到最好的点和最好点的值
                 double bestValue;
                 Point bestPoint;
                 FindBestMatchPointAndValue(imgResult, tmtype, out  bestValue, out  bestPoint);

                 Rectangle rect = new Rectangle(new Point(bestPoint.X - imageTemplate.Width / 2, bestPoint.Y - imageTemplate.Height / 2), imageTemplate.Size);
                 imgResult.Draw(rect, new Gray(bestValue), 2);
                 imageBox1.Image = imgResult;

             }
             else
             {
                 System.Windows.Forms.MessageBox.Show("请先输入图像!");
             }
         }

         private void radioButton6_CheckedChanged(object sender, EventArgs e)
         {
             if (imageBox1.Image != null && imageBox2.Image != null)
             {
                 //输入图像            
                 Image<Bgr, Byte> imageInput = src;
                 //模板图像            
                 Image<Bgr, Byte> imageTemplate = subsrc;

                 TM_TYPE tmtype = TM_TYPE.CV_TM_SQDIFF_NORMED;

                 Image<Gray, Single> imgResult = imageInput.MatchTemplate(imageTemplate, tmtype);

                 //找到最好的点和最好点的值
                 double bestValue;
                 Point bestPoint;
                 FindBestMatchPointAndValue(imgResult, tmtype, out  bestValue, out  bestPoint);

                 Rectangle rect = new Rectangle(new Point(bestPoint.X - imageTemplate.Width / 2, bestPoint.Y - imageTemplate.Height / 2), imageTemplate.Size);
                 imgResult.Draw(rect, new Gray(bestValue), 2);
                
             }
             else
             {
                 System.Windows.Forms.MessageBox.Show("请先输入图像!");
             }
         }

         private void FindBestMatchPointAndValue(Image<Gray, Single> image, TM_TYPE tmType, out double bestValue, out Point bestPoint)
         {
             bestValue = 0d;
             bestPoint = new Point(0, 0);
             double[] minValues, maxValues;
             Point[] minLocations, maxLocations;
             image.MinMax(out minValues, out maxValues, out minLocations, out maxLocations);
             //对于平方差匹配和归一化平方差匹配,最小值表示最好的匹配;其他情况下,最大值表示最好的匹配            
             if (tmType == TM_TYPE.CV_TM_SQDIFF || tmType == TM_TYPE.CV_TM_SQDIFF_NORMED)
             {
                 bestValue = minValues[0];
                 bestPoint = minLocations[0];
             }
             else
             {
                 bestValue = maxValues[0];
                 bestPoint = maxLocations[0];
             }
         }

         private void 图像对比ToolStripMenuItem_Click(object sender, EventArgs e)
         {
             CompareFrm cpfrm = new CompareFrm();
             cpfrm.Show();
         }


     
    }
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/387653.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

itcast-ssh-crm实践

分析 BaseDao 文件上传 转载于:https://www.cnblogs.com/hellowq/p/10209761.html

分类器大牛们

David Lowe&#xff1a;Sift算法的发明者&#xff0c;天才。 Rob Hess&#xff1a;sift的源码OpenSift的作者&#xff0c;个人主页上有openSift的下载链接&#xff0c;Opencv中sift的实现&#xff0c;也是参考这个。 Koen van de Sande&#xff1a;作者给出了sift,densesift,co…

利用python脚本程序监控文件被修改

需求&#xff1a;利用python编写监控程序&#xff0c;监控一个文件目录&#xff0c;当目录下的文件发生改变时&#xff0c;实现有修改就发报警邮件 邮件使用QQ邮箱&#xff0c;需要开启smtp&#xff0c;使用手机发生短信&#xff0c;腾讯会给你发邮箱密码。如下所示&#xff1a…

Oracle RAC

环境如下&#xff1a; Linux操作系统&#xff1a;Centos 6.5 64bit &#xff08;这个版本的redhat 6内核等OS在安装grid最后执行root.sh时会出现crs-4124&#xff0c;是oracle11.2.0.1的bug&#xff09; VMware version&#xff1a;Workstation 8.0.3 build-703057 Oracle…

Activiti多人会签例子

Activiti中提供了多实例任务&#xff08;for-each&#xff09;将多实例应到到UserTask中可以实现会签功能。 Multi-instance (for each) Description A multi-instance activity is a way of defining repetition for a certain step in a business process. In programming …

MySQL-ProxySQL中间件(一)| ProxySQL基本概念

目录 MySQL-ProxySQL中间件&#xff08;一&#xff09;| ProxySQL基本概念&#xff1a; https://www.cnblogs.com/SQLServer2012/p/10972593.htmlMySQL-ProxySQL中间件&#xff08;二&#xff09;| Admin Schemas介绍&#xff1a;https://www.cnblogs.com/SQLServer2012/p/109…

标签td设置隐藏(hidden)

这样设置这个td就不会被其他的td给挤掉了! 还有一种方法就是把tr标签的solid设置为0px 这个方法把td标签的left,right,bottom,top的边框的solid全部设置为0px;转载于:https://www.cnblogs.com/tranquilityMan/p/10972811.html

Windows Server 2008 NFS

打开Windows Server 2008的Dos运行窗口&#xff08;不是powershell&#xff09;&#xff0c;然后键入&#xff1a; servermanagercmd.exe -install FS-NFS-Services 安装完毕之后&#xff0c;就要把NFS的存贮映射到Windows Server 2008上某个盘符以供使用&#xff0c;但为了…

金融反欺诈模型----项目实战--机器学习

机器学习&#xff1a;从源数据清洗到特征工程建立谈金融反欺诈模型训练 本文旨在通过一个完整的实战例子&#xff0c;演示从源数据清洗到特征工程建立&#xff0c;再到模型训练&#xff0c;以及模型验证和评估的一个机器学习的完整流程。由于初识机器学习&#xff0c;会比较多的…

Win7下如何挂载NFS共享目录

NFS是Unix中广泛使用的文件共享协议&#xff0c;在Linux下得到了传承&#xff0c;使用简单&#xff0c;读写性能强大。过去Windows与Linux共享文件夹需要使用Samba&#xff08;CIFS&#xff09;协议&#xff0c;虽然定制性更高&#xff0c;但设置和使用都比较繁琐。Windows 7加…

ECharts 点击非图表区域的点击事件不触发问题

1. 通过 myChart.getZr().on(click, fn) 监听整个图表的点击事件&#xff0c;注册回调 myChart.getZr().on(click, () > {//拿到index即可取出被点击数据的所有信息console.log(clickIndex) }) 2. 在 tooltip 的 formatter 函数中&#xff0c;每次调用都记录下需要的参数&am…

强大的django-debug-toolbar,django项目性能分析工具

强大的django-debug-toolbar,django项目性能分析工具 给大家介绍一个用于django中debug模式下查看网站性能等其他信息的插件django-debug-toolbar 首先安装 pip install django-debug-toolbar 接下来在自己django项目中的settings中添加配置 INSTALLED_APPS [debug_toolbar,]M…

个人作业——软件工程实践总结

一、请回望暑假时的第一次作业&#xff0c;你对于软件工程课程的想象 1&#xff09;对比开篇博客你对课程目标和期待&#xff0c;“希望通过实践锻炼&#xff0c;增强计算机专业的能力和就业竞争力”&#xff0c;对比目前的所学所练所得&#xff0c;在哪些方面达到了你的期待和…

利用jdk自带的运行监控工具JConsole观察分析Java程序的运行 Jtop

利用jdk自带的运行监控工具JConsole观察分析Java程序的运行 原文链接 一、JConsole是什么 从Java 5开始 引入了 JConsole。JConsole 是一个内置 Java 性能分析器&#xff0c;可以从命令行或在 GUI shell 中运行。您可以轻松地使用 JConsole&#xff08;或者&#xff0c;它更高端…

java内存溢出分析工具:jmap使用实战

java内存溢出分析工具&#xff1a;jmap使用实战 在一次解决系统tomcat老是内存撑到头&#xff0c;然后崩溃的问题时&#xff0c;使用到了jmap。 1 使用命令 在环境是linuxjdk1.5以上&#xff0c;这个工具是自带的&#xff0c;路径在JDK_HOME/bin/下 jmap -histo pid>a.log…

JDK内置工具使用

JDK内置工具使用 一、javah命令(C Header and Stub File Generator) 二、jps命令(Java Virtual Machine Process Status Tool) 三、jstack命令(Java Stack Trace) 四、jstat命令(Java Virtual Machine Statistics Monitoring Tool) 五、jmap命令(Java Memory Map) 六、jinfo命令…

Windows10系统下wsappx占用CPU资源过高?wsappx是什么?如何关闭wsappx进程?

在Windows10系统开机的时候&#xff0c;wsappx进程占用的CPU资源非常高&#xff0c;导致电脑运行速度缓慢&#xff0c;那么我们如何关闭wsappx进程&#xff0c;让电脑加快运行速度呢&#xff1f;下面就一起来看一下操作的方法吧。 【现象】 1、先来看一下电脑刚开机的时候&…

jvm02

java虚拟机内存管理 每个线程就是一个顺序的执行单元&#xff0c;线程共享区即多个线程共享同一块区域&#xff0c;线程独占区即每个线程都有自己的虚拟机栈&#xff0c;本地方法栈&#xff0c;程序计数器。 程序计数器是一个比较小的内存空间&#xff0c;可以看作是当前线程所…

搭建svn管理平台

安装svn服务器&#xff1a;yum -y install subversion创建svn的目录&#xff1a;mkdir -p /data/svn初始化svn目录&#xff1a;svnadmin create /data/svnconf下的三个目录介绍&#xff1a;authz&#xff1a;控制权限,创建用户。密码在passwd创建 passwd&#xff1a;密码文件&…

Oracle dataguard 正常切换和应急切换

Oracle dataguard 正常切换和应急切换oracle dataguard提供异地容灾方案,能有效的防止单点故障和提供高可用技术,这里介绍dataguard正常主备切换和应急切换&#xff08;应急切换模拟主库出现问题无法还原,备库脱离dataguard接管主库对外提供服务&#xff09;1&#xff09;Oracl…