查找轮廓
![[转载]轮廓的查找、表达、绘制、特性及匹配 [转载]轮廓的查找、表达、绘制、特性及匹配](https://i-blog.csdnimg.cn/blog_migrate/a4c26d1e5885305701be709a3d33442f.gif)
![[转载]轮廓的查找、表达、绘制、特性及匹配 [转载]轮廓的查找、表达、绘制、特性及匹配](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
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();
轮廓的表达方式
1.顶点的序列
(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)。
以下代码可以用来获取轮廓上的点:
sbContour.AppendFormat( " {0}, " , contour[i]);
(1)获取Freeman链码
![[转载]轮廓的查找、表达、绘制、特性及匹配 [转载]轮廓的查找、表达、绘制、特性及匹配](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![[转载]轮廓的查找、表达、绘制、特性及匹配 [转载]轮廓的查找、表达、绘制、特性及匹配](https://i-blog.csdnimg.cn/blog_migrate/a4c26d1e5885305701be709a3d33442f.gif)
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链码上的点
![[转载]轮廓的查找、表达、绘制、特性及匹配 [转载]轮廓的查找、表达、绘制、特性及匹配](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![[转载]轮廓的查找、表达、绘制、特性及匹配 [转载]轮廓的查找、表达、绘制、特性及匹配](https://i-blog.csdnimg.cn/blog_migrate/a4c26d1e5885305701be709a3d33442f.gif)
[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 );
轮廓之间的组织方式
![[转载]轮廓的查找、表达、绘制、特性及匹配 [转载]轮廓的查找、表达、绘制、特性及匹配](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![[转载]轮廓的查找、表达、绘制、特性及匹配 [转载]轮廓的查找、表达、绘制、特性及匹配](https://i-blog.csdnimg.cn/blog_migrate/a4c26d1e5885305701be709a3d33442f.gif)
private void TravelContour(Contour < Point > contour, ref int total, ref StringBuilder sbContour)
{
if (contour != null )
{
sbContour.Append( " ------------------------rn " );
sbContour.AppendFormat( " 轮廓{0},右节点:{1},下级节点:{2},外接矩形:({3})rn " , total, contour.HNext != null , contour.VNext != null , contour.BoundingRectangle);
sbContour.AppendFormat( " 包含{0}个点(面积:{1},周长:{2}):rn " , contour.Total, contour.Area, contour.Perimeter);
for ( int i = 0 ; i < contour.Total; i ++ )
sbContour.AppendFormat( " {0}, " , contour[i]);
sbContour.Append( " rn " );
total ++ ;
if (contour.HNext != null )
TravelContour(contour.HNext, ref total, ref sbContour);
if (contour.VNext != null )
TravelContour(contour.VNext, ref total, ref sbContour);
}
}
轮廓的绘制
![[转载]轮廓的查找、表达、绘制、特性及匹配 [转载]轮廓的查找、表达、绘制、特性及匹配](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![[转载]轮廓的查找、表达、绘制、特性及匹配 [转载]轮廓的查找、表达、绘制、特性及匹配](https://i-blog.csdnimg.cn/blog_migrate/a4c26d1e5885305701be709a3d33442f.gif)
int maxLevel = 0 ; // 绘制的轮廓深度
int .TryParse(txtMaxLevel.Text, out maxLevel);
imageResult.Draw(contour, new Bgr(lblExternalColor.BackColor), new Bgr(lblHoleColor.BackColor), maxLevel, 2 );
轮廓的特性
1.轮廓的多边形逼近
2.轮廓的关键点
![[转载]轮廓的查找、表达、绘制、特性及匹配 [转载]轮廓的查找、表达、绘制、特性及匹配](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![[转载]轮廓的查找、表达、绘制、特性及匹配 [转载]轮廓的查找、表达、绘制、特性及匹配](https://i-blog.csdnimg.cn/blog_migrate/a4c26d1e5885305701be709a3d33442f.gif)
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}个关键点:rn " , 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( " rn " );
}
catch (CvException ex)
{
sbContour.AppendFormat( " 在获取关键点时发生异常,错误描述:{0},错误源:{1},错误堆栈:{2}rn错误文件:{3},函数名:{4},行:{5},错误内部描述:{6}rn " , ex.Message, ex.Source, ex.StackTrace, ex.FileName, ex.FunctionName, ex.Line, ex.ErrorStr);
}
catch (Exception e)
{
sbContour.AppendFormat( " 在获取关键点时发生异常,错误描述:{0},错误源:{1},错误堆栈:{2}rn " , e.Message, e.Source, e.StackTrace);
}
finally
{
storage.Dispose();
}
}
}
3.轮廓的周长和面积
4.轮廓的边界框
![[转载]轮廓的查找、表达、绘制、特性及匹配 [转载]轮廓的查找、表达、绘制、特性及匹配](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![[转载]轮廓的查找、表达、绘制、特性及匹配 [转载]轮廓的查找、表达、绘制、特性及匹配](https://i-blog.csdnimg.cn/blog_migrate/a4c26d1e5885305701be709a3d33442f.gif)
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,不能创建外围椭圆。rn " );
}
}
5.轮廓的矩
![[转载]轮廓的查找、表达、绘制、特性及匹配 [转载]轮廓的查找、表达、绘制、特性及匹配](https://i-blog.csdnimg.cn/blog_migrate/a4c26d1e5885305701be709a3d33442f.gif)
![[转载]轮廓的查找、表达、绘制、特性及匹配 [转载]轮廓的查找、表达、绘制、特性及匹配](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
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.GetNormalizedCentralMome
sbContour.AppendFormat( " 矩(xOrder:{0},yOrder:{1}),矩:{2:F09},中心矩:{3:F09},归一化矩:{4:F09}rn " , 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}rn " , huMonents.hu1, huMonents.hu2, huMonents.hu3, huMonents.hu4, huMonents.hu5, huMonents.hu6, huMonents.hu7);
}
6.轮廓的轮廓树
![[转载]轮廓的查找、表达、绘制、特性及匹配 [转载]轮廓的查找、表达、绘制、特性及匹配](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![[转载]轮廓的查找、表达、绘制、特性及匹配 [转载]轮廓的查找、表达、绘制、特性及匹配](https://i-blog.csdnimg.cn/blog_migrate/a4c26d1e5885305701be709a3d33442f.gif)
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( " rn " );
imageResult.DrawPolyline(points, true , new Bgr(lblConvexColor.BackColor), 2 );
MCvConvexityDefect defect;
sbContour.AppendFormat( " 轮廓有{0}个缺陷,依次为:rn " , defects.Total);
for ( int i = 0 ; i < defects.Total; i ++ )
{
defect = defects[i];
sbContour.AppendFormat( " 缺陷:{0},起点:{1},终点:{2},最深的点:{3},深度:{4}rn " , i, defect.StartPoint, defect.EndPoint, defect.DepthPoint, defect.Depth);
}
}
else
sbContour.Append( " 轮廓是凸的,凸包和轮廓一样。rn " );
}
![[转载]轮廓的查找、表达、绘制、特性及匹配 [转载]轮廓的查找、表达、绘制、特性及匹配](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![[转载]轮廓的查找、表达、绘制、特性及匹配 [转载]轮廓的查找、表达、绘制、特性及匹配](https://i-blog.csdnimg.cn/blog_migrate/a4c26d1e5885305701be709a3d33442f.gif)
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);
轮廓的匹配
1.Hu矩匹配
2.轮廓树匹配
3.成对几何直方图匹配
![[转载]轮廓的查找、表达、绘制、特性及匹配 [转载]轮廓的查找、表达、绘制、特性及匹配](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![[转载]轮廓的查找、表达、绘制、特性及匹配 [转载]轮廓的查找、表达、绘制、特性及匹配](https://i-blog.csdnimg.cn/blog_migrate/a4c26d1e5885305701be709a3d33442f.gif)
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);
// 进行匹配
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}毫秒rn " , matchType, matchValue, time);
}
// 轮廓树匹配
private string MatchContourTrees(Contour < Point > contour1, Contour < Point > contour2)
{
// 生成轮廓树
double thresholdOfCreate = double .Parse(txtThresholdOfCreateCont
IntPtr ptrTree1 = CvInvoke.cvCreateContourTree(contour1.Ptr, new MemStorage().Ptr, thresholdOfCreate);
IntPtr ptrTree2 = CvInvoke.cvCreateContourTree(contour2.Ptr, new MemStorage().Ptr, thresholdOfCreate);
// 匹配
double thresholdOfMatch = double .Parse(txtThresholdOfMatchConto
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}毫秒rn " , 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.ConvertDenseHistogramToM
Matrix < Single > matrix2 = FormProcessHist.ConvertDenseHistogramToM
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}毫秒rn " , rbHistEmd.Checked ? " EMD " : compareMethod.ToString( " G " ), compareResult, time);
}