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