现在的位置: 首页 > 综合 > 正文

C#实现图像轮廓的查找、表达、绘制、特性及匹配

2014年03月11日 ⁄ 综合 ⁄ 共 2995字 ⁄ 字号 评论关闭
前言

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

查找轮廓

    首先我们面对的问题是如何在图像中找到轮廓,OpenCv(EmguCv)为我们做了很多工作,我们的任务只是调用现成的函数而已。 Image<TColor,TDepth>类的FindContours方法可以很方便的查找轮廓,不过在查找之前,我们需要将彩色图像转换成灰度图像,然后再将灰度图像转换成二值图像。代码如下所示:
  1. Image<Bgr, Byte> imageSource = new Image<Bgr, byte>(sourceImageFileName); //获取源图像
  2. Image<Gray, Byte> imageGray = imageSource.Convert<Gray, Byte>(); //将源图像转换成灰度图像
  3. int thresholdValue = tbThreshold.Value; //用于二值化的阀值
  4. Image<Gray, Byte> imageThreshold = imageGray.ThresholdBinary(new Gray(thresholdValue), new Gray(255d)); //对灰度图像二值化
  5. 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)。
以下代码可以用来获取轮廓上的点:

  1. for (int i = 0; i < contour.Total; i++)
  2. 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链码表示的轮廓

  1. //查找用Freeman链码表示的轮廓
  2. Image<Gray,Byte> imageTemp=imageThreshold.Copy();
  3. IntPtr storage = CvInvoke.cvCreateMemStorage(0);
  4. IntPtr ptrFirstChain = IntPtr.Zero;
  5. 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链码上的点

  1. //初始化Freeman链码读取
  2. [DllImport("cv200.dll")]
  3. public static extern void cvStartReadChainPoints(IntPtr ptrChain,IntPtr ptrReader);
  4. //读取Freeman链码的点
  5. [DllImport("cv200.dll")]
  6. public static extern Point cvReadChainPoint(IntPtr ptrReader);
  7. [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet = System.Runtime.InteropServices.CharSet.Ansi)]
  8. //定义链码读取结构
  9. public struct MCvChainPtReader
  10. {
  11. //seqReader
  12. public MCvSeqReader seqReader;
  13. /// char
  14. public byte code;
  15. /// POINT->tagPOINT
  16. public Point pt;
  17. /// char[16]
  18. [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 16)]
  19. public string deltas;
  20. }

  21. //将链码指针转换成结构
  22. MCvChain chain=(MCvChain)Marshal.PtrToStructure(ptrChain,typeof(MCvChain));
  23. //定义存放链码上点的列表
  24. List<Point> pointList = new List<Point>(chain.total);
  25. //链码读取结构
  26. MCvChainPtReader chainReader = new MCvChainPtReader();
  27. IntPtr ptrReader = Marshal.AllocHGlobal(sizeof(MCvSeqReader) + sizeof(byte) + sizeof(Point) + 16 * sizeof(byte));
  28. Marshal.StructureToPtr(chainReader, ptrReader, false);
  29. //开始读取链码
  30. cvStartReadChainPoints(ptrChain, ptrReader);
  31. int i = 0;
  32. while (ptrReader != IntPtr.Zero && i < chain.total)
  33. {
  34. //依次读取链码上的每个点
  35. Point p = cvReadChainPoint(ptrReader);
  36. if (ptrReader == IntPtr.Zero)
  37. break;
  38. else
  39. {
  40. pointList.Add(p);
  41. sbChain.AppendFormat("{0},", p);
  42. i++;
  43. }
  44. }
  45. imageResult.DrawPolyline(pointList.ToArray(), true, new Bgr(lblExternalColor.BackColor), 2);

复制代码

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

抱歉!评论已关闭.