前言 轮廓是构成任何一个形状的边界或外形线。前面讲了如何根据色彩及色彩的分布(直方图对比和模板匹配)来进行匹配,现在我们来看看如何利用物体的轮廓。包括以下内容:轮廓的查找、表达方式、组织方式、绘制、特性、匹配。查找轮廓 首先我们面对的问题是如何在图像中找到轮廓,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); 复制代码 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链码表示的轮廓 //查找用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链码上的点 //初始化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{//seqReaderpublic MCvSeqReader seqReader;/// charpublic byte code;/// POINT->tagPOINTpublic 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来辅助终止循环,读取了所有的点之后就可以罢手了。
转贴自:http://www.pin5i.com/showtopic-26712.html