`
king_c
  • 浏览: 213267 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

[转载]图像特征检测综述

    博客分类:
  • c#
阅读更多

图像特征检测综述(Image Feature Detection)

作者:Wuya 来源:博客园 发布时间:2010-03-03 22:15 阅读:4388 次 原文链接 [收藏]

作者:王先荣
前言
图像特征提取是计算机视觉和图像处理中的一个概念。它指的是使用计算机提取图像信息,决定每个图像的点是否属于一个图像特征。本文主要探讨如何提取图像中的“角点”这一特征,及其相关的内容。而诸如直方图、边缘、区域等内容在前文中有所提及,请查看相关文章。OpenCv(EmguCv)中实现了多种角点特征的提取方法,包括:Harris角点、ShiTomasi角点、亚像素级角点、SURF角点、Star关键点、FAST关键点、Lepetit关键点等等,本文将逐一介绍如何检测这些角点。在此之前将会先介绍跟角点检测密切相关的一些变换,包括Sobel算子、拉普拉斯算子、Canny算子、霍夫变换。另外,还会介绍一种广泛使用而OpenCv中并未实现的SIFT角点检测,以及最近在OpenCv中实现的MSER区域检测。所要讲述的内容会很多,我这里尽量写一些需要注意的地方及实现代码,而参考手册及书本中有的内容将一笔带过或者不会提及。

Sobel算子
Sobel算子用多项式计算来拟合导数计算,可以用OpenCv中的cvSobel函数或者EmguCv中的Image<TColor,TDepth>.Sobel方法来进行计算。需要注意的是,xorder和yorder中必须且只能有一个为非零值,即只能计算x方向或者y反向的导数;如果将方形滤波器的宽度设置为特殊值CV_SCHARR(-1),将使用Scharr滤波器代替Sobel滤波器。
使用Sobel滤波器的示例代码如下:

Sobel算子
<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ --> //Sobel算子 private string SobelFeatureDetect() { //获取参数 int xOrder = int.Parse((string)cmbSobelXOrder.SelectedItem); int yOrder = int.Parse((string)cmbSobelYOrder.SelectedItem); int apertureSize = int.Parse((string)cmbSobelApertureSize.SelectedItem); if ((xOrder == 0 && yOrder == 0) || (xOrder != 0 && yOrder != 0)) return "Sobel算子,参数错误:xOrder和yOrder中必须且只能有一个非零。\r\n"; //计算 Stopwatch sw = new Stopwatch(); sw.Start(); Image<Gray, Single> imageDest = imageSourceGrayscale.Sobel(xOrder, yOrder, apertureSize); sw.Stop(); //显示 pbResult.Image = imageDest.Bitmap; //释放资源 imageDest.Dispose(); //返回 return string.Format("·Sobel算子,用时{0:F05}毫秒,参数(x方向求导阶数:{1},y方向求导阶数:{2},方形滤波器宽度:{3})\r\n", sw.Elapsed.TotalMilliseconds, xOrder, yOrder, apertureSize); }


拉普拉斯算子
拉普拉斯算子可以用作边缘检测;可以用OpenCv中的cvLaplace函数或者EmguCv中的Image<TColor,TDepth>.Laplace方法来进行拉普拉斯变换。需要注意的是:OpenCv的文档有点小错误,apertureSize参数值不能为CV_SCHARR(-1)。
使用拉普拉斯变换的示例代码如下:

拉普拉斯算子
<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ --> //拉普拉斯变换 private string LaplaceFeatureDetect() { //获取参数 int apertureSize = int.Parse((string)cmbLaplaceApertureSize.SelectedItem); //计算 Stopwatch sw = new Stopwatch(); sw.Start(); Image<Gray, Single> imageDest = imageSourceGrayscale.Laplace(apertureSize); sw.Stop(); //显示 pbResult.Image = imageDest.Bitmap; //释放资源 imageDest.Dispose(); //返回 return string.Format("·拉普拉斯变换,用时{0:F05}毫秒,参数(方形滤波器宽度:{1})\r\n", sw.Elapsed.TotalMilliseconds, apertureSize); }


Canny算子
Canny算子也可以用作边缘检测;可以用OpenCv中的cvCanny函数或者EmguCv中的Image<TColor,TDepth>.Canny方法来进行Canny边缘检测。所不同的是,Image<TColor,TDepth>.Canny方法可以用于检测彩色图像的边缘,但是它只能使用apertureSize参数的默认值3;
而cvCanny只能处理灰度图像,不过可以自定义apertureSize。cvCanny和Canny的方法参数名有点点不同,下面是参数对照表。
Image<TColor,TDepth>.Canny CvInvoke.cvCanny
thresh lowThresh
threshLinking highThresh
3 apertureSize
值得注意的是,apertureSize只能取3,5或者7,这可以在cvcanny.cpp第87行看到:

<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ --> aperture_size &= INT_MAX; if( (aperture_size & 1) == 0 || aperture_size < 3 || aperture_size > 7 ) CV_ERROR( CV_StsBadFlag, "" );


使用Canny算子的示例代码如下:

Canny算子
<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ --> //Canny算子 private string CannyFeatureDetect() { //获取参数 double lowThresh = double.Parse(txtCannyLowThresh.Text); double highThresh = double.Parse(txtCannyHighThresh.Text); int apertureSize = int.Parse((string)cmbCannyApertureSize.SelectedItem); //计算 Stopwatch sw = new Stopwatch(); sw.Start(); Image<Gray, Byte> imageDest = null; Image<Bgr, Byte> imageDest2 = null; if (rbCannyUseCvCanny.Checked) { imageDest = new Image<Gray, byte>(imageSourceGrayscale.Size); CvInvoke.cvCanny(imageSourceGrayscale.Ptr, imageDest.Ptr, lowThresh, highThresh, apertureSize); } else imageDest2 = imageSource.Canny(new Bgr(lowThresh, lowThresh, lowThresh), new Bgr(highThresh, highThresh, highThresh)); sw.Stop(); //显示 pbResult.Image = rbCannyUseCvCanny.Checked ? imageDest.Bitmap : imageDest2.Bitmap; //释放资源 if (imageDest != null) imageDest.Dispose(); if (imageDest2 != null) imageDest2.Dispose(); //返回 return string.Format("·Canny算子,用时{0:F05}毫秒,参数(方式:{1},阀值下限:{2},阀值上限:{3},方形滤波器宽度:{4})\r\n", sw.Elapsed.TotalMilliseconds, rbCannyUseCvCanny.Checked ? "cvCanny" : "Image<TColor, TDepth>.Canny", lowThresh, highThresh, apertureSize); }

 

另外,在http://www.china-vision.net/blog/user2/15975/archives/2007/804.html有一种自动获取Canny算子高低阀值的方法,作者提供了用C语言实现的代码。我将其改写成了C#版本,代码如下:

计算图像的自适应Canny算子阀值
<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ --> /// <summary> /// 计算图像的自适应Canny算子阀值 /// </summary> /// <param name="imageSrc">源图像,只能是256级灰度图像</param> /// <param name="apertureSize">方形滤波器的宽度</param> /// <param name="lowThresh">阀值下限</param> /// <param name="highThresh">阀值上限</param> unsafe void AdaptiveFindCannyThreshold(Image<Gray, Byte> imageSrc, int apertureSize, out double lowThresh, out double highThresh) { //计算源图像x方向和y方向的1阶Sobel算子 Size size = imageSrc.Size; Image<Gray, Int16> imageDx = new Image<Gray, short>(size); Image<Gray, Int16> imageDy = new Image<Gray, short>(size); CvInvoke.cvSobel(imageSrc.Ptr, imageDx.Ptr, 1, 0, apertureSize); CvInvoke.cvSobel(imageSrc.Ptr, imageDy.Ptr, 0, 1, apertureSize); Image<Gray, Single> image = new Image<Gray, float>(size); int i, j; DenseHistogram hist = null; int hist_size = 255; float[] range_0 = new float[] { 0, 256 }; double PercentOfPixelsNotEdges = 0.7; //计算边缘的强度,并保存于图像中 float maxv = 0; float temp; byte* imageDataDx = (byte*)imageDx.MIplImage.imageData.ToPointer(); byte* imageDataDy = (byte*)imageDy.MIplImage.imageData.ToPointer(); byte* imageData = (byte*)image.MIplImage.imageData.ToPointer(); int widthStepDx = imageDx.MIplImage.widthStep; int widthStepDy = widthStepDx; int widthStep = image.MIplImage.widthStep; for (i = 0; i < size.Height; i++) { short* _dx = (short*)(imageDataDx + widthStepDx * i); short* _dy = (short*)(imageDataDy + widthStepDy * i); float* _image = (float*)(imageData + widthStep * i); for (j = 0; j < size.Width; j++) { temp = (float)(Math.Abs(*(_dx + j)) + Math.Abs(*(_dy + j))); *(_image + j) = temp; if (maxv < temp) maxv = temp; } } //计算直方图 range_0[1] = maxv; hist_size = hist_size > maxv ? (int)maxv : hist_size; hist = new DenseHistogram(hist_size, new RangeF(range_0[0], range_0[1])); hist.Calculate<Single>(new Image<Gray, Single>[] { image }, false, null); int total = (int)(size.Height * size.Width * PercentOfPixelsNotEdges); double sum = 0; int icount = hist.BinDimension[0].Size; for (i = 0; i < icount; i++) { sum += hist[i]; if (sum > total) break; } //计算阀值 highThresh = (i + 1) * maxv / hist_size; lowThresh = highThresh * 0.4; //释放资源 imageDx.Dispose(); imageDy.Dispose(); image.Dispose(); hist.Dispose(); }


霍夫变换
霍夫变换是一种在图像中寻找直线、圆及其他简单形状的方法,在OpenCv中实现了霍夫线变换和霍夫圆变换。值得注意的地方有以下几点:(1)HoughLines2需要先计算Canny边缘,然后再检测直线;(2)HoughLines2计算结果的获取随获取方式的不同而不同;(3)HoughCircles检测结果似乎不正确。
使用霍夫变换的示例代码如下所示:

霍夫变换
<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ --> //霍夫线变换 private string HoughLinesFeatureDetect() { //获取参数 HOUGH_TYPE method = rbHoughLinesSHT.Checked ? HOUGH_TYPE.CV_HOUGH_STANDARD : (rbHoughLinesPPHT.Checked ? HOUGH_TYPE.CV_HOUGH_PROBABILISTIC : HOUGH_TYPE.CV_HOUGH_MULTI_SCALE); double rho = double.Parse(txtHoughLinesRho.Text); double theta = double.Parse(txtHoughLinesTheta.Text); int threshold = int.Parse(txtHoughLinesThreshold.Text); double param1 = double.Parse(txtHoughLinesParam1.Text); double param2 = double.Parse(txtHoughLinesParam2.Text); MemStorage storage = new MemStorage(); int linesCount = 0; StringBuilder sbResult = new StringBuilder(); //计算,先运行Canny边缘检测(参数来自Canny算子属性页),然后再用计算霍夫线变换 double lowThresh = double.Parse(txtCannyLowThresh.Text); double highThresh = double.Parse(txtCannyHighThresh.Text); int apertureSize = int.Parse((string)cmbCannyApertureSize.SelectedItem); Image<Gray, Byte> imageCanny = new Image<Gray, byte>(imageSourceGrayscale.Size); CvInvoke.cvCanny(imageSourceGrayscale.Ptr, imageCanny.Ptr, lowThresh, highThresh, apertureSize); Stopwatch sw = new Stopwatch(); sw.Start(); IntPtr ptrLines = CvInvoke.cvHoughLines2(imageCanny.Ptr, storage.Ptr, method, rho, theta, threshold, param1, param2); Seq<LineSegment2D> linesSeq = null; Seq<PointF> linesSeq2 = null; if (method == HOUGH_TYPE.CV_HOUGH_PROBABILISTIC) linesSeq = new Seq<LineSegment2D>(ptrLines, storage); else linesSeq2 = new Seq<PointF>(ptrLines, storage); sw.Stop(); //显示 Image<Bgr, Byte> imageResult = imageSourceGrayscale.Convert<Bgr, Byte>(); if (linesSeq != null) { linesCount = linesSeq.Total; foreach (LineSegment2D line in linesSeq) { imageResult.Draw(line, new Bgr(255d, 0d, 0d), 4); sbResult.AppendFormat("{0}-{1},", line.P1, line.P2); } } else { linesCount = linesSeq2.Total; foreach (PointF line in linesSeq2) { float r = line.X; float t = line.Y; double a = Math.Cos(t), b = Math.Sin(t); double x0 = a * r, y0 = b * r; int x1 = (int)(x0 + 1000 * (-b)); int y1 = (int)(y0 + 1000 * (a)); int x2 = (int)(x0 - 1000 * (-b)); int y2 = (int)(y0 - 1000 * (a)); Point pt1 = new Point(x1, y1); Point pt2 = new Point(x2, y2); imageResult.Draw(new LineSegment2D(pt1, pt2), new Bgr(255d, 0d, 0d), 4); sbResult.AppendFormat("{0}-{1},", pt1, pt2); } } pbResult.Image = imageResult.Bitmap; //释放资源 imageCanny.Dispose(); imageResult.Dispose(); storage.Dispose(); //返回 return string.Format("·霍夫线变换,用时{0:F05}毫秒,参数(变换方式:{1},距离精度:{2},弧度精度:{3},阀值:{4},参数1:{5},参数2:{6}),找到{7}条直线\r\n{8}", sw.Elapsed.TotalMilliseconds, method.ToString("G"), rho, theta, threshold, param1, param2, linesCount, linesCount != 0 ? (sbResult.ToString() + "\r\n") : ""); } //霍夫圆变换 private string HoughCirclesFeatureDetect() { //获取参数 double dp = double.Parse(txtHoughCirclesDp.Text); double minDist = double.Parse(txtHoughCirclesMinDist.Text); double param1 = double.Parse(txtHoughCirclesParam1.Text); double param2 = double.Parse(txtHoughCirclesParam2.Text); int minRadius = int.Parse(txtHoughCirclesMinRadius.Text); int maxRadius = int.Parse(txtHoughCirclesMaxRadius.Text); StringBuilder sbResult = new StringBuilder(); //计算 Stopwatch sw = new Stopwatch(); sw.Start(); CircleF[][] circles = imageSourceGrayscale.HoughCircles(new Gray(param1), new Gray(param2), dp, minDist, minRadius, maxRadius); sw.Stop(); //显示 Image<Bgr, Byte> imageResult = imageSourceGrayscale.Convert<Bgr, Byte>(); int circlesCount = 0; foreach (CircleF[] cs in circles) { foreach (CircleF circle in cs) { imageResult.Draw(circle, new Bgr(255d, 0d, 0d), 4); sbResult.AppendFormat("圆心{0}半径{1},", circle.Center, circle.Radius); circlesCount++; } } pbResult.Image = imageResult.Bitmap; //释放资源 imageResult.Dispose(); //返回 return string.Format("·霍夫圆变换,用时{0:F05}毫秒,参数(累加器图像的最小分辨率:{1},不同圆之间的最小距离:{2},边缘阀值:{3},累加器阀值:{4},最小圆半径:{5},最大圆半径:{6}),找到{7}个圆\r\n{8}", sw.Elapsed.TotalMilliseconds, dp, minDist, param1, param2, minRadius, maxRadius, circlesCount, sbResult.Length > 0 ? (sbResult.ToString() + "\r\n") : ""); }

 

Harris角点
cvCornerHarris函数检测的结果实际上是一幅包含Harris角点的浮点型单通道图像,可以使用类似下面的代码来计算包含Harris角点的图像:

Harris角点
<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ --> //Harris角点 private string CornerHarrisFeatureDetect() { //获取参数 int blockSize = int.Parse(txtCornerHarrisBlockSize.Text); int apertureSize = int.Parse(txtCornerHarrisApertureSize.Text); double k = double.Parse(txtCornerHarrisK.Text); //计算 Image<Gray, Single> imageDest = <span sty
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics