图像处理中的点检测

2015-11-02 19:44:46 samkieth 阅读数 13683

角点及角点检测

角点是图像的一种重要局部特征,它决定了图像中目标的形状,所以在图像匹配、
目标描述与识别以及运动估计、目标跟踪等领域,角点提取具有十分重要的意义。在计
算机视觉和图像处理中,对于角点的定义有着不同的表述,其具体定义和描述主要有如
下几种: 
①角点是一阶导数的局部最大值所对应的像素点; 
②角点是指两条以上的边缘的交点; 
③角点指示了物体边缘变化不连续的方向; 
④角点处的一阶导数最大,而且高阶导数为零; 
⑤角点处不仅梯度的数值大,而且梯度方向的变化率也很大。也就是说,角点指示
了图像在二维空间内灰度变化剧烈的位置,是和周围的邻点有着明显差异的像素点。

 
第一类定义不能准确地定位角点,当检测到边缘时其一阶导数均局部最大,虽然能
较好的区分边缘和灰度平滑区域,但没有考虑角点处的曲率,故不能区分边缘与角点;
第二类定义不够准确,不易于数学表达,定义太抽象;第三类考虑到了角点处曲率的变
化与边缘处的不同,为更准确的角点定义奠定了基础;第四类定义不够准确,不能很好
的区分角点、边缘、平滑区域,不易于数学描述;第五类定义既考虑了梯度的局部最大
(角点也是边缘点的一部分),又考虑了梯度方向的变化率(只有当梯度方向剧烈变化时才
指示为角点)。
从上述描述可知第五类定义利用灰度信息,计算曲率和梯度能很好的区分角点、边缘与平滑区域,且易于数学表达。由于在实际工作中不需要提取边缘,因此得到了广泛的应用。
 

角点检测算法可归纳为3类:基于灰度图像的角点检测、基于二值图像的角点检测、基于轮廓曲线的角点检测。

基于灰度图像的角点检测又可分为基于梯度、基于模板和基于模板梯度组合3类方法,其中基于模板的方法主要考虑像素领域点的灰度变化,即图像亮度的变化,将与邻点亮度对比足够大的点定义为角点。常见的基于模板的角点检测算法有Kitchen-Rosenfeld角点检测算法,Harris角点检测算法、KLT角点检测算法及SUSAN角点检测算法。和其他角点检测算法相比,SUSAN角点检测算法具有算法简单、位置准确、抗噪声能力强等特点。



角点检测算法

1.FAST算法

          1.1定义

Rosten等人将FAST角点定义为:若某像素点与其周围领域内足够多的像素点处于不同的区域,则该像素点可能为角点。也就是某些属性与众不同,考虑灰度图像,即若该点的灰度值比其周围领域内足够多的像素点的灰度值大或者小,则该点可能为角点。

其中I(x)为圆周上任意一点的灰度,I(p)为圆心的灰度,Ed为灰度值差得阈值,如果N大于给定阈值,一般为周围圆圈点的四分之三,则认为p是一个特征点。
为了获得更快的结果,还采用了额外的加速办法。如果测试了候选点周围每隔90度角的4个点,应该至少有3个和候选点的灰度值差足够大,否则则不用再计算其他点,直接认为该候选点不是特征点。候选点周围的圆的选取半径是一个很重要的参数,这里我为了简单高效,采用半径为3,共有16个周边像素需要比较。为了提高比较的效率,通常只使用N个周边像素来比较,也就是大家经常说的FAST-N。我看很多文献推荐FAST-9,作者的主页上有FAST-9FAST-10FAST-11FAST-12,大家使用比较多的是FAST-9FAST-12。上个图说明的更形象一些。


      1.2fast算法实现步骤

  1. 从图片中选取一个像素$P$,下面我们将判断它是否是一个特征点。我们首先把它的亮度值设为$I_p$。
  2. 设定一个合适的阈值$t$。
  3. 考虑以该像素点为中心的一个半径等于3像素的离散化的Bresenham圆,这个圆的边界上有16个像素(如图1所示)。
  4. 现在,如果在这个大小为16个像素的圆上有$n$个连续的像素点,它们的像素值要么都比$I_p + t$大,要么都比$I_p - t$小,那么它就是一个角点。(如图1中的白色虚线所示)。$n$的值可以设置为12或者9,实验证明选择9可能会有更好的效果。

上面的算法中,对于图像中的每一个点,我们都要去遍历其邻域圆上的16个点的像素,效率较低。我们下面提出了一种高效的测试(high-speed test)来快速排除一大部分非角点的像素。该方法仅仅检查在位置1,9,5和13四个位置的像素,首先检测位置1和位置9,如果它们都比阈值暗或比阈值亮,再检测位置5和位置13。如果$P$是一个角点,那么上述四个像素点中至少有3个应该必须都大于$I_p+t$或者小于$I_p-t$,因为若是一个角点,超过四分之三圆的部分应该满足判断条件。

如果不满足,那么$p$不可能是一个角点。对于所有点做上面这一部分初步的检测后,符合条件的将成为候选的角点,我们再对候选的角点,做完整的测试,即检测圆上的所有点。

上面的算法效率实际上是很高的,但是有点一些缺点:

  1. 当$n<12$时不能拒绝许多的候选点;
  2. 检测出来的角点不是最优的,这是因为它的效率取决于问题的排序与角点的分布;
  3. 对于角点分析的结果被丢弃了;
  4. 多个特征点容易挤在一起。

1.3采用非极大值抑制

从邻近的位置选取了多个特征点是另一个问题,我们可以使用Non-Maximal Suppression来解决。

  1. 为每一个检测到的特征点计算它的响应大小(score function)$V$。这里$V$定义为点$p$和它周围16个像素点的绝对偏差的和。
  2. 考虑两个相邻的特征点,并比较它们的$V$值。
  3. $V$值较低的点将会被删除。

1.4fast算法在opencv实现

在OpenCV中进行FAST特征提取的函数为FAST。它一共有4个参数,第一个参数是输入的图像,第二个是返回的特征点,第三个是定义的阈值,第四个决定是否使用非极大值抑制。


void FAST(InputArray image,vector<KeyPoint>& keypoints,int threshold,boolnonmaxSuppression=true )
 
C++:void FASTX(InputArray image,vector<KeyPoint>& keypoints,int threshold,boolnonmaxSuppression, int type)
另外还有一个接口为FASTX,它提供了第五个参数type用来指定FAST检测中像素邻域圆的参数:TYPE_9_16TYPE_7_12TYPE_5_8

OpenCV里对FAST的使用也非常简单,先声明一组特征点,构建FAST特征检测,接下来调用detect函数检测图像中的特征点,最后把特征点绘制到图片上。上代码说的清楚些。
#include <opencv2/core/core.hpp>  
#include <opencv2/features2d/features2d.hpp>  
#include <opencv2/highgui/highgui.hpp>  
  
#include <vector>  
  
using namespace cv;  
  
void main()  
{  
    Mat image;  
    image = imread("church01.jpg");  
    // vector of keyPoints  
    std::vector<KeyPoint> keyPoints;  
    // construction of the fast feature detector object  
    FastFeatureDetector fast(40);   // 检测的阈值为40  
    // feature point detection  
    fast.detect(image,keyPoints);  
    drawKeypoints(image, keyPoints, image, Scalar::all(255), DrawMatchesFlags::DRAW_OVER_OUTIMG);  
    imshow("FAST feature", image);  
    cvWaitKey(0);  
} 
       

      1.5fast算法总结

FAST算法比其他已知的角点检测算法要快很多倍,但是当图片中的噪点较多时,它的健壮性并不好,而且算法的效果还依赖于一个阈值$t$。

 

而且FAST不产生多尺度特征而且FAST特征点没有方向信息,这样就会失去旋转不变性。




2012-07-07 14:42:21 jia20003 阅读数 109368

图像处理之霍夫变换(直线检测算法)

霍夫变换是图像变换中的经典手段之一,主要用来从图像中分离出具有某种相同特征的几何

形状(如,直线,圆等)。霍夫变换寻找直线与圆的方法相比与其它方法可以更好的减少噪

声干扰。经典的霍夫变换常用来检测直线,圆,椭圆等。

 

霍夫变换算法思想:

以直线检测为例,每个像素坐标点经过变换都变成都直线特质有贡献的统一度量,一个简单

的例子如下:一条直线在图像中是一系列离散点的集合,通过一个直线的离散极坐标公式,

可以表达出直线的离散点几何等式如下:

X *cos(theta) + y * sin(theta)  = r 其中角度theta指r与X轴之间的夹角,r为到直线几何垂

直距离。任何在直线上点,x, y都可以表达,其中 r, theta是常量。该公式图形表示如下:

然而在实现的图像处理领域,图像的像素坐标P(x, y)是已知的,而r, theta则是我们要寻找

的变量。如果我们能绘制每个(r, theta)值根据像素点坐标P(x, y)值的话,那么就从图像笛卡

尔坐标系统转换到极坐标霍夫空间系统,这种从点到曲线的变换称为直线的霍夫变换。变换

通过量化霍夫参数空间为有限个值间隔等分或者累加格子。当霍夫变换算法开始,每个像素

坐标点P(x, y)被转换到(r, theta)的曲线点上面,累加到对应的格子数据点,当一个波峰出现

时候,说明有直线存在。同样的原理,我们可以用来检测圆,只是对于圆的参数方程变为如

下等式:

(x –a ) ^2 + (y-b) ^ 2 = r^2其中(a, b)为圆的中心点坐标,r圆的半径。这样霍夫的参数空间就

变成一个三维参数空间。给定圆半径转为二维霍夫参数空间,变换相对简单,也比较常用。

 

编程思路解析:

1.      读取一幅带处理二值图像,最好背景为黑色。

2.      取得源像素数据

3.      根据直线的霍夫变换公式完成霍夫变换,预览霍夫空间结果

4.       寻找最大霍夫值,设置阈值,反变换到图像RGB值空间(程序难点之一)

5.      越界处理,显示霍夫变换处理以后的图像

 

关键代码解析:

直线的变换角度为[0 ~ PI]之间,设置等份为500为PI/500,同时根据参数直线参数方程的取值

范围为[-r, r]有如下霍夫参数定义:

 // prepare for hough transform
 int centerX = width / 2;
 int centerY = height / 2;
 double hough_interval = PI_VALUE/(double)hough_space;
	    
 int max = Math.max(width, height);
 int max_length = (int)(Math.sqrt(2.0D) * max);
 hough_1d = new int[2 * hough_space * max_length];

实现从像素RGB空间到霍夫空间变换的代码为:

// start hough transform now....
int[][] image_2d = convert1Dto2D(inPixels);
for (int row = 0; row < height; row++) {
	for (int col = 0; col < width; col++) {
    	int p = image_2d[row][col] & 0xff;
    	if(p == 0) continue; // which means background color
    	
    	// since we does not know the theta angle and r value, 
    	// we have to calculate all hough space for each pixel point
    	// then we got the max possible theta and r pair.
    	// r = x * cos(theta) + y * sin(theta)
    	for(int cell=0; cell < hough_space; cell++ ) {
    		max = (int)((col - centerX) * Math.cos(cell * hough_interval) + (row - centerY) * Math.sin(cell * hough_interval));
    		max += max_length; // start from zero, not (-max_length)
    		if (max < 0 || (max >= 2 * max_length)) {// make sure r did not out of scope[0, 2*max_lenght]
                continue;
            }
    		hough_2d[cell][max] +=1;
    	}
    }
}

寻找最大霍夫值计算霍夫阈值的代码如下:

// find the max hough value
int max_hough = 0;
for(int i=0; i<hough_space; i++) {
	for(int j=0; j<2*max_length; j++) {
		hough_1d[(i + j * hough_space)] = hough_2d[i][j];
		if(hough_2d[i][j] > max_hough) {
			max_hough = hough_2d[i][j];
		}
	}
}
System.out.println("MAX HOUGH VALUE = " + max_hough);

// transfer back to image pixels space from hough parameter space
int hough_threshold = (int)(threshold * max_hough);

从霍夫空间反变换回像素数据空间代码如下:

	    // transfer back to image pixels space from hough parameter space
	    int hough_threshold = (int)(threshold * max_hough);
	    for(int row = 0; row < hough_space; row++) {
	    	for(int col = 0; col < 2*max_length; col++) {
	    		if(hough_2d[row][col] < hough_threshold) // discard it
	    			continue;
	    		int hough_value = hough_2d[row][col];
	    		boolean isLine = true;
	    		for(int i=-1; i<2; i++) {
	    			for(int j=-1; j<2; j++) {
	    				if(i != 0 || j != 0) {
    		              int yf = row + i;
    		              int xf = col + j;
    		              if(xf < 0) continue;
    		              if(xf < 2*max_length) {
    		            	  if (yf < 0) {
    		            		  yf += hough_space;
    		            	  }
    		                  if (yf >= hough_space) {
    		                	  yf -= hough_space;
    		                  }
    		                  if(hough_2d[yf][xf] <= hough_value) {
    		                	  continue;
    		                  }
    		                  isLine = false;
    		                  break;
    		              }
	    				}
	    			}
	    		}
	    		if(!isLine) continue;
	    		
	    		// transform back to pixel data now...
	            double dy = Math.sin(row * hough_interval);
	            double dx = Math.cos(row * hough_interval);
	            if ((row <= hough_space / 4) || (row >= 3 * hough_space / 4)) {
	                for (int subrow = 0; subrow < height; ++subrow) {
	                  int subcol = (int)((col - max_length - ((subrow - centerY) * dy)) / dx) + centerX;
	                  if ((subcol < width) && (subcol >= 0)) {
	                	  image_2d[subrow][subcol] = -16776961;
	                  }
	                }
	              } else {
	                for (int subcol = 0; subcol < width; ++subcol) {
	                  int subrow = (int)((col - max_length - ((subcol - centerX) * dx)) / dy) + centerY;
	                  if ((subrow < height) && (subrow >= 0)) {
	                	  image_2d[subrow][subcol] = -16776961;
	                  }
	                }
	              }
	    	}
	    }
霍夫变换源图如下:

霍夫变换以后,在霍夫空间显示如下:(白色表示已经找到直线信号)


最终反变换回到像素空间效果如下:


一个更好的运行监测直线的结果(输入为二值图像):


完整的霍夫变换源代码如下:

package com.gloomyfish.image.transform;

import java.awt.image.BufferedImage;

import com.process.blur.study.AbstractBufferedImageOp;

public class HoughLineFilter extends AbstractBufferedImageOp {
	public final static double PI_VALUE = Math.PI;
	private int hough_space = 500;
	private int[] hough_1d;
	private int[][] hough_2d;
	private int width;
	private int height;
	
	private float threshold;
	private float scale;
	private float offset;
	
	public HoughLineFilter() {
		// default hough transform parameters
		//	scale = 1.0f;
		//	offset = 0.0f;
		threshold = 0.5f;
		scale = 1.0f;
		offset = 0.0f;
	}
	
	public void setHoughSpace(int space) {
		this.hough_space = space;
	}
	
	public float getThreshold() {
		return threshold;
	}

	public void setThreshold(float threshold) {
		this.threshold = threshold;
	}

	public float getScale() {
		return scale;
	}

	public void setScale(float scale) {
		this.scale = scale;
	}

	public float getOffset() {
		return offset;
	}

	public void setOffset(float offset) {
		this.offset = offset;
	}

	@Override
	public BufferedImage filter(BufferedImage src, BufferedImage dest) {
		width = src.getWidth();
        height = src.getHeight();

        if ( dest == null )
            dest = createCompatibleDestImage( src, null );

        int[] inPixels = new int[width*height];
        int[] outPixels = new int[width*height];
        getRGB( src, 0, 0, width, height, inPixels );
        houghTransform(inPixels, outPixels);
        setRGB( dest, 0, 0, width, height, outPixels );
        return dest;
	}

	private void houghTransform(int[] inPixels, int[] outPixels) {
        // prepare for hough transform
	    int centerX = width / 2;
	    int centerY = height / 2;
	    double hough_interval = PI_VALUE/(double)hough_space;
	    
	    int max = Math.max(width, height);
	    int max_length = (int)(Math.sqrt(2.0D) * max);
	    hough_1d = new int[2 * hough_space * max_length];
	    
	    // define temp hough 2D array and initialize the hough 2D
	    hough_2d = new int[hough_space][2*max_length];
	    for(int i=0; i<hough_space; i++) {
	    	for(int j=0; j<2*max_length; j++) {
	    		hough_2d[i][j] = 0;
	    	}
	    }
	    
	    // start hough transform now....
	    int[][] image_2d = convert1Dto2D(inPixels);
	    for (int row = 0; row < height; row++) {
	    	for (int col = 0; col < width; col++) {
	        	int p = image_2d[row][col] & 0xff;
	        	if(p == 0) continue; // which means background color
	        	
	        	// since we does not know the theta angle and r value, 
	        	// we have to calculate all hough space for each pixel point
	        	// then we got the max possible theta and r pair.
	        	// r = x * cos(theta) + y * sin(theta)
	        	for(int cell=0; cell < hough_space; cell++ ) {
	        		max = (int)((col - centerX) * Math.cos(cell * hough_interval) + (row - centerY) * Math.sin(cell * hough_interval));
	        		max += max_length; // start from zero, not (-max_length)
	        		if (max < 0 || (max >= 2 * max_length)) {// make sure r did not out of scope[0, 2*max_lenght]
	                    continue;
	                }
	        		hough_2d[cell][max] +=1;
	        	}
	        }
	    }
	    
		// find the max hough value
		int max_hough = 0;
		for(int i=0; i<hough_space; i++) {
			for(int j=0; j<2*max_length; j++) {
				hough_1d[(i + j * hough_space)] = hough_2d[i][j];
				if(hough_2d[i][j] > max_hough) {
					max_hough = hough_2d[i][j];
				}
			}
		}
		System.out.println("MAX HOUGH VALUE = " + max_hough);
		
		// transfer back to image pixels space from hough parameter space
		int hough_threshold = (int)(threshold * max_hough);
	    for(int row = 0; row < hough_space; row++) {
	    	for(int col = 0; col < 2*max_length; col++) {
	    		if(hough_2d[row][col] < hough_threshold) // discard it
	    			continue;
	    		int hough_value = hough_2d[row][col];
	    		boolean isLine = true;
	    		for(int i=-1; i<2; i++) {
	    			for(int j=-1; j<2; j++) {
	    				if(i != 0 || j != 0) {
    		              int yf = row + i;
    		              int xf = col + j;
    		              if(xf < 0) continue;
    		              if(xf < 2*max_length) {
    		            	  if (yf < 0) {
    		            		  yf += hough_space;
    		            	  }
    		                  if (yf >= hough_space) {
    		                	  yf -= hough_space;
    		                  }
    		                  if(hough_2d[yf][xf] <= hough_value) {
    		                	  continue;
    		                  }
    		                  isLine = false;
    		                  break;
    		              }
	    				}
	    			}
	    		}
	    		if(!isLine) continue;
	    		
	    		// transform back to pixel data now...
	            double dy = Math.sin(row * hough_interval);
	            double dx = Math.cos(row * hough_interval);
	            if ((row <= hough_space / 4) || (row >= 3 * hough_space / 4)) {
	                for (int subrow = 0; subrow < height; ++subrow) {
	                  int subcol = (int)((col - max_length - ((subrow - centerY) * dy)) / dx) + centerX;
	                  if ((subcol < width) && (subcol >= 0)) {
	                	  image_2d[subrow][subcol] = -16776961;
	                  }
	                }
	              } else {
	                for (int subcol = 0; subcol < width; ++subcol) {
	                  int subrow = (int)((col - max_length - ((subcol - centerX) * dx)) / dy) + centerY;
	                  if ((subrow < height) && (subrow >= 0)) {
	                	  image_2d[subrow][subcol] = -16776961;
	                  }
	                }
	              }
	    	}
	    }
	    
	    // convert to hough 1D and return result
	    for (int i = 0; i < this.hough_1d.length; i++)
	    {
	      int value = clamp((int)(scale * this.hough_1d[i] + offset)); // scale always equals 1
	      this.hough_1d[i] = (0xFF000000 | value + (value << 16) + (value << 8));
	    }
	    
	    // convert to image 1D and return
	    for (int row = 0; row < height; row++) {
	    	for (int col = 0; col < width; col++) {
	        	outPixels[(col + row * width)] = image_2d[row][col];
	        }
	    }
	}
	
	public BufferedImage getHoughImage() {
		BufferedImage houghImage = new BufferedImage(hough_2d[0].length, hough_space, BufferedImage.TYPE_4BYTE_ABGR);
		setRGB(houghImage, 0, 0, hough_2d[0].length, hough_space, hough_1d);
		return houghImage;
	}
	
	public static int clamp(int value) {
	      if (value < 0)
	    	  value = 0;
	      else if (value > 255) {
	    	  value = 255;
	      }
	      return value;
	}
	
	private int[][] convert1Dto2D(int[] pixels) {
		int[][] image_2d = new int[height][width];
		int index = 0;
		for(int row = 0; row < height; row++) {
			for(int col = 0; col < width; col++) {
				index = row * width + col;
				image_2d[row][col] = pixels[index];
			}
		}
		return image_2d;
	}

}
转载文章请务必注明出自本博客!!

学习图像处理,点击视频教程《数字图像处理-基础入门》




2016-12-14 11:31:09 jia20003 阅读数 11536

图像处理之角点检测与亚像素角点定位

 

角点是图像中亮度变化最强地方反映了图像的本质特征,提取图像中的角点可以有效提高图像处理速度与精准度。所以对于整张图像来说特别重要,角点检测与提取的越准确图像处理与分析结果就越接近真实。同时角点检测对真实环境下的对象识别、对象匹配都起到决定性作用。Harris角点检测是图像处理中角点提取的经典算法之一,应用范围广发,在经典的SIFT特征提取算法中Harris角点检测起到关键作用。通常对角点检测算法都有如下要求:


1. 基于灰度图像、能够自动调整运行稳定,检测出角点的数目。

2. 对噪声不敏感、有一定的噪声抑制,有较强的角点角点检测能力。

3. 准确性够高,能够正确发现角点位置

4. 算法尽可能的快与运行时间短


Harris角点检测基本上满足了上述四点要求,所以被广发应用,除了Harris角点检测,另外一种常见的角点检测算法-Shi-Tomasi角点检测也得到了广发应用,OpenCV中对这两种算法均有实现API可以调用。关于Harris角点检测原理可以看我之前写的博文:

http://blog.csdn.net/jia20003/article/details/16908661

关于Shi-Tomasi角点检测,与Harris角点检测唯一不同就是在计算角点响应值R上面。


然后根据输入的阈值T大于该阈值的R对应像素点即为图像中角点位置坐标。此刻坐标往往都是整数出现,而在真实的世界中坐标多数时候都不是整数,假设我们计算出来的角点位置P(34, 189)而实际上准确角点位置是P(34.278, 189.706)这样带小数的位置,而这样的准确位置寻找过程就叫做子像素定位或者亚像素定位。这一步在SURF与SIFT算法中都有应用而且非常重要。常见的亚像素级别精准定位方法有三类:

1. 基于插值方法

2. 基于几何矩寻找方法

3. 拟合方法 - 比较常用

拟合方法中根据使用的公式不同可以分为高斯曲面拟合与多项式拟合等等。以高斯拟合为例


这样就求出了亚像素的位置。使用亚像素位置进行计算得到结果将更加准确,对图像特征提取、匹配结果效果显著。OpenCV中已经对角点检测实现了亚像素级别的API可以调用。

代码演示

OpenCV亚像素角点检测例子:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

Mat src, gray_src;
int max_corners = 10;
int max_trackbar = 30;
const char* output_title = "subpxiel-result";
void GoodFeature2Track_Demo(int, void*);
int main(int argc, char** argv) {
	src = imread("D:/vcprojects/images/home.jpg");
	if (src.empty()) {
		printf("could not load image...\n");
		return -1;
	}
	cvtColor(src, gray_src, COLOR_BGR2GRAY);
	namedWindow("input", CV_WINDOW_AUTOSIZE);
	namedWindow(output_title, CV_WINDOW_AUTOSIZE);
	imshow("input", src);

	createTrackbar("Corners:", output_title, &max_corners, max_trackbar, GoodFeature2Track_Demo);
	GoodFeature2Track_Demo(0, 0);

	waitKey(0);
	return 0;
}

void GoodFeature2Track_Demo(int, void*) {
	if (max_corners < 1) {
		max_corners = 1;
	}
	vector<Point2f> corners;
	double qualityLevel = 0.01;
	double minDistance = 10;
	int blockSize = 3;
	double k = 0.04;
	goodFeaturesToTrack(gray_src, corners, max_corners, qualityLevel, minDistance, Mat(), blockSize, false, k);
	cout << "number of corners : " << corners.size() << endl;
	Mat copy = src.clone();
	for (size_t t = 0; t < corners.size(); t++) {
		circle(copy, corners[t], 4, Scalar(255, 0, 0), 2, 8, 0);
	}
	imshow(output_title, copy);

	// locate corner point on sub pixel level
	Size winSize = Size(5, 5);
	Size zerozone = Size(-1, -1);
	TermCriteria criteria = TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 40, 0.001);
	cornerSubPix(gray_src, corners, winSize, zerozone, criteria);
	for (size_t t = 0; t < corners.size(); t++) {
		cout << (t+1) << ".point[x, y]=" << corners[t].x << "," << corners[t].y << endl;
	}

	return;
}

原图如下:


运行结果:


转载请注明来自【jia20003】的博客!


2019-09-10 10:23:03 u014410989 阅读数 497


特征就是有意义的图像区域,该区域具有独特性或易于识别性。涉及角点、边、斑点、脊向等概念。
OpenCV中最常使用的特征检测和提取算法有:
Harris、SIFT、SURF、FAST、BRIEF、ORB等。

引言

计算机视觉中,我们经常要匹配两幅图像。匹配的的方式就是通过比较两幅图像中的公共特征,比如边,角,以及图像块(blob)等,来对两幅图像进行匹配。

相对于边,角更适合描述图像特征,比如下面的图像中,大概有6种特征,我们用A、B、C、D、E、F来描述,其中A, B是平的区域,在图像中很难精确定位,C,D是边,比A,B好些,但是图像中的边也很多,定位到某个边也比较困难,相比来说E,F的角更适合描述当前的图像的特征,也更好检测,因为你不论怎么移动图像,这些角的特征都和图像其它部分不同。所以在计算机视觉中,我们通常用角来描述图像特征。

image
E,F中的角我们通常称作角点(corner points),他们具有以下特征:

–局部窗口沿各方向移动,均产生明显变化的点

–图像局部曲率突变的点

斑点通常是指与周围有着颜色和灰度差别的区域。在实际地图中,往往存在着大量这样的斑点,如一颗树是一个斑点,一块草地是一个斑点,一栋房子也可以是一个斑点。由于斑点代表的是一个区域,相比单纯的角点,它的稳定性要好,抗噪声能力要强,所以它在图像配准上扮演了很重要的角色。

各种图像特征的演进关系

特征检测
Moravec
Harris
FAST
SIFT
SURF
BRIEF
ORB

1. Moravec角点检测

Moravec角点检测用一个二值窗口在图像的某个像素点的所有方向上进行移动,并计算移动后和移动前像素强度变化的平均值,得到的***最小***的值定为该点的角点响应值。用公式表示,即为:

在这里插入图片描述

x,y是待检测点的图像坐标。
u,v是待检测点邻域图像块的移动方向。
w是可调节的权重。
在这里插入图片描述一般计算这个像素点水平、垂直、对角线、反对角线4个方向上的灰度差平方和。
在这里插入图片描述

2. Harris

参考博客:
1、Harris 角点检测(Harris Corner Detection)(OpenCV实现)
https://blog.csdn.net/u014485485/article/details/79056666

2、Harris Corner Detector 原理及编程实现
https://blog.csdn.net/tanhongguang1/article/details/8898927

Harris角点检测是Chris Harris和Mike Stephens在1988年提出的。主要用于运动图像的追踪。
Harris角点检测来自于Moravec检点检测(1977,Moravec),并对它进行了改进和更强的数学建模。
OpenCV 函数:cornerHarris

与Moravec角点相比,Harris角点的优点:
Moravec角点有一些缺点,导致它的检测不精确,Harris改良了这些缺点。
1.Moravec响应是各向异性的,因为只考虑了45度倍数方向上的响应。
Harris通过对shift region进行泰勒展开,覆盖所有方向的移动。
2.Moravec响应容易受到噪声的干扰,因为窗口是二值的,且为方形。
Harris改用具有平滑效果的高斯圆形窗口。
3.Moravec响应对边缘、角点的判决过于简单,因为响应只考虑E的最小值。
Harris重新设计了一种衡量边缘、角点的方式——利用E的方差。
在这里插入图片描述

Harris角点的缺点:
由上述分析也可以看出,Harris角点没有尺度不变性,因为框的设计并没有考虑这一点。所以当图像尺寸变化时,框的大小不相应调整,就会出现错误的分类。
在一个尺度下是Harris角点, 在在另一个尺度下可能就不是Harris角点了.
在这里插入图片描述

3.SIFT

Harris角点具有平移不变形和旋转不变形,但是没有尺度不变形。
SIFT对于平移、旋转、尺度缩放、亮度变化都保持不变性。

SIFT,即尺度不变特征变换(Scale-invariant feature transform,SIFT),是用于图像处理领域的一种描述。这种描述具有尺度不变性,可在图像中检测出关键点,是一种局部特征描述子。
SIFT由David Lowe在1999年提出,在2004年加以完善 [1-2] 。SIFT在数字图像的特征描述方面当之无愧可称之为最红最火的一种,许多人对SIFT进行了改进,诞生了SIFT的一系列变种。SIFT已经申请了专利。

The SIFT algorithm is patented in the United States and cannot be used in commercial products without a license from the University of British Columbia.

下载opencv,如果使用3.0及以上版本,一定要使用opencv-contrib-python,因为在opencv-python中移除了SIFT,SURF等算法。移除的理由是这些算法受专利保护。最简单的方法为在命令行中运行

pip install opencv-contrib-python

参考资料:
1、David G. Lowe, “Distinctive image features from scale-invariant keypoints,” International Journal of Computer Vision, 60, 2 (2004), pp. 91-110. [PDF][CODE][project homepage][author’s homepage]

2、原理讲解(中文)
[SIFT算法详解及应用,讲解全面形象,百度文库下载]
[SIFT算法详解及应用,讲解全面形象,CSDN下载]

[SIFT算法的Matlab实现(blog) ]
zddhub, SIFT算法详解: http://blog.csdn.net/zddblog/article/details/7521424
Rachel Zhang, SIFT特征提取分析: http://blog.csdn.net/abcjennifer/article/details/7639681
JiePro, SIFT算法:特征描述子: http://www.cnblogs.com/JiePro/p/sift_4.html

3、代码
作者提供的matlab演示代码,sift关键函数没有开源代码:https://www.cs.ubc.ca/~lowe/keypoints/siftDemoV4.zip
Rob Hess, OpenSIFT源码,C语言版本: https://github.com/robwhess/opensift
SIFT算法的Matlab实现(github下载)matlab语言版本 :https://github.com/sun11/sw-sift

4. SURF

近来不断有人改进SIFT,其中最著名的有 SURF(计算量小,运算速度快,提取的特征点几乎与SIFT相同)和 CSIFT(彩色尺度特征不变变换,顾名思义,可以解决基于彩色图像的SIFT问题)。

SIFT特征和SURF特征比较: https://blog.csdn.net/blateyang/article/details/76512398
在这里插入图片描述

5. FAST

为了提高特征点检测速度,Edward Rosten和Tom Drummond在2006年发表的“Machine learning for high-speed corner detection”文章中提出了一种FAST特征,并在2010年对这篇论文作了小幅度的修改后重新发表。
FAST的全称为Features From Accelerated Segment Test。(FAST主要用于角点检测)

在这里插入图片描述1.从图片中选取一个像素P,下面我们将判断它是否是一个特征点。我们首先把它的亮度值设为Ip。
2.设定一个合适的阈值t。
3.考虑以该像素点为中心的一个半径等于3像素的离散化的Bresenham圆,这个圆的边界上有16个像素(如图1所示)。
4.现在,如果在这个大小为16个像素的圆上有n个连续的像素点,它们的像素值要么都比Ip+t大,要么都比Ip−t小,那么它就是一个角点。(如图1中的白色虚线所示)。n的值可以设置为12或者9,实验证明选择9可能会有更好的效果。
上面的算法中,对于图像中的每一个点,我们都要去遍历其邻域圆上的16个点的像素,效率较低。我们下面提出了一种高效的测试(high-speed test)来快速排除一大部分非角点的像素。该方法仅仅检查在位置1,9,5和13四个位置的像素,首先检测位置1和位置9,如果它们都比阈值暗或比阈值亮,再检测位置5和位置13。如果P是一个角点,那么上述四个像素点中至少有3个应该必须都大于Ip+t或者小于Ip−t,因为若是一个角点,超过四分之三圆的部分应该满足判断条件。如果不满足,那么p不可能是一个角点。对于所有点做上面这一部分初步的检测后,符合条件的将成为候选的角点,我们再对候选的角点,做完整的测试,即检测圆上的所有点。

参考:
Features From Accelerated Segment Test:
https://www.cnblogs.com/ronny/p/4078710.html?utm_source=tuicool

6. BRIEF

EPFL的Calonder在ECCV2010上提出了一种可以快速计算且表达方式为二进制编码的描述子。

SIFT特征采用了128维的特征描述子,由于描述子用的浮点数,所以它将会占用512 bytes的空间。
如果一幅图像中有1000个特征点,那么SIFT或SURF特征描述子将占用大量的内存空间,对于那些资源紧张的应用,尤其是嵌入式的应用,这样的特征描述子显然是不可行的。
而且,越占有越大的空间,意味着越长的匹配时间。
但是实际上SFIT或SURF的特征描述子中,并不是所有维都在匹配中有着实质性的作用。

BRIEF提供了一种计算二值串的捷径,而并不需要去计算一个类似于SIFT的特征描述子。它需要先平滑图像,然后在特征点周围选择一个Patch,在这个Patch内通过一种选定的方法来挑选出来nd个点对。然后对于每一个点对(p,q),我们来比较这两个点的亮度值,如果I§>I(q),则这个点对生成了二值串中一个的值为1,如果I§<I(q),则对应在二值串中的值为-1,否则为0。所有nd个点对,都进行比较之间,我们就生成了一个nd长的二进制串。

一旦维数选定了,我们就可以用汉明距离来匹配这些描述子了。

值得注意的是,对于BRIEF,它仅仅是一种特征描述符,它不提供提取特征点的方法。所以,如果你必须使一种特征点定位的方法,如FAST、SIFT、SURF等。总体来说,BRIEF是一个效率很高的提取特征描述子的方法,同时,当图像发生很大的平面内的旋转,它有着很好的识别率。

参考资料
SIFT、SURF、Harris、BRIEF、FAST、DAISY、FAST等描述符介绍:https://blog.csdn.net/qq_29828623/article/details/52403562
在这里插入图片描述
参考资料:
特征匹配,sift,surf,orb,brisk,brief: https://blog.csdn.net/sinat_31337047/article/details/52760780

7. ORB

ORB是Oriented FAST and Rotated BRIEF的简称。ORB将在本文中详细描述。Ethan Rublee and Vincent Rabaud and Kurt Konolige and Gary Bradski,《ORB: an efficient alternative to SIFT or SURF》, ICCV 2011。OpenCV2.3中已经实现。

主要贡献在于:
1、对于FAST算法,增加了快速准确的方向指向功能;
2、高运算效率的具有指向功能的BRIEF特征;
3、具有指向功能的BRIEF特征的方差及相关性分析;
4、基于旋转不变且去关联性的BRIEF特征的方法,用于减少近邻取样(点取样)应用中。

计算速度: ORB>>SURF>>SIFT(各差一个量级)
旋转鲁棒性: SURF>ORB~SIFT(表示差不多)
模糊鲁棒性: SURF>ORB~SIFT
尺度变换鲁棒性: SURF>SIFT>ORB(ORB并不具备尺度变换性)

所以结论就是,如果对计算实时性要求非常高,可选用ORB算法,但基本要保证正对拍摄;如果对实行性要求稍高,可以选择SURF;基本不用SIFT。

参考资料:
SURF SIFT ORB三种特征检测算法比较: https://blog.csdn.net/zilanpotou182/article/details/66478915
ORB 一种特征匹配替代方法:对比SIFT或SURF : https://blog.csdn.net/u012525173/article/details/70332181

8. LBP

LBP(Local Binary Pattern),局部二值模式是一种描述图像局部纹理的特征算子,该算子是由T.Ojala等人于1994年首次提出的,后经过发展改进可应用于图像特征分析,该算子具有旋转不变性与灰度不变性(不怕光照变化)等显著优点。

LBP特征描述的是一种灰度范围内的图像处理操作技术。LBP特征是高效的图像特征分析方法,经过改进与发展已经应用于多个领域之中,特别是人脸识别、表情识别、行人检测领域已经取得了成功。

在这里插入图片描述
参考资料:https://blog.csdn.net/yuanlulu/article/details/82148429

9. HOG

求取前先灰度化然后Gamma校正,降低图像局部的阴影和光照变化所造成的影响,同时可以抑制噪音的干扰。

HOG主要捕获轮廓信息。统计每个cell的梯度直方图,然后若干个cell直方图组成一个block的descriptor。所有block组成整幅图片的descriptor。

这里注意,同一个block之间的cell之间像素不重叠,但是不同的block之间回有像素重叠。其实每个block更像是一个滑窗,滑窗的步长一般小于block边长。
滑窗和block关系参考下图(图中的cell是8X8,每4个cell组成一个block,水平和垂直上的滑窗步长都是8)
由于大滑动窗口之间是由很多重合的,所以opnecv里实现HOG的时候使用了很高明的缓存技术加速计算过程。

在这里插入图片描述
特点

对图像几何的和光学的形变都能保持很好的不变性,适合检测行人这种居于有一定刚性的物体,可以容许行人有一些细微的肢体动作。

缺点也有,计算量大,无法处理遮挡。
一个检测行人的效果图:
在这里插入图片描述

10. HAAR

人脸检测最为经典的算法Haar-like特征+Adaboost。这是最为常用的物体检测的方法(最初用于人脸检测),也是用的最多的方法。

训练过程: 输入图像->图像预处理->提取特征->训练分类器(二分类)->得到训练好的模型;

测试过程:输入图像->图像预处理->提取特征->导入模型->二分类(是不是所要检测的物体)。

Haar-like特征是很简单的,无非就是那么几种,如两矩形特征、三矩形特征、对角特征。后来,还加入了边缘特征、线特征、中心环绕特征等。使用积分图可以加速计算特征。最后,使用集成的方法Adaboost进行训练。

在这里插入图片描述

  • HAAR特征只是使用特征模板,计算图像颜色的深浅模式,模板以不同的大小和位置在子图像中滑动,计算出特征值来,交给boost分类器。其实HAAR与LBP还是有点像的,都是灰度差。
  • HAAR计算的模式也决定它对旋转没有很好的适应性

参考资料:https://blog.csdn.net/yuanlulu/article/details/82148429

11. Feature Detection Methods List:

https://blog.csdn.net/vonzhoufz/article/details/46594369

  1. Canny Edge Detect,
    A Computational Approach to Edge Detection, 1986. The Canny edge detector is an edge detection operator that uses a multi-stage algorithm to detect a wide range of edges in images.

  2. Harris,
    A combined corner and edge detector, 1988. considering the differential of the corner score with respect to direction directly.

  3. GFTT,
    Good Features to Track,1994, Determines strong corners on an image.

  4. Matas-2000,
    Robust Detection of Lines Using the Progressive Probabilistic Hough Transform. 霍夫变换检测直线.

  5. SIFT,
    Distinctive Image Features from Scale-Invariant Keypoints,2004, invariant to image translation, scaling, and rotation, partially invariant to illumination changes and robust to local geometric distortion. 128-dim(512B).

  6. SURF,
    Speeded Up Robust Features,2006,受SIFT启发,比SIFT快,健壮. 64-dim(256B).

  7. FAST ,
    Machine Learning for High-speed Corner Detection, 2006,wiki. Very fast, not robust to high level noise.

  8. ORB,
    ORB: an efficient alternative to SIFT or SURF,2011,基于FAST和BRIEF,比SIFT快两个数量级,可作为SIFT的替代(a fusion of FAST keypoint detector and BRIEF descriptor). 32B binary descriptor.

  9. BRISK,
    BRISK: Binary Robust Invariant Scalable Keypoints, 2011 . 64B binary descriptor.

  10. STAR,
    Censure: Center surround extremas for realtime feature detection and matching,2008,引用次数不高.scale-invariant center-surround detector (CENSURE) that claims to outperform other detectors and is capable of real-time implementation.

  11. MSER,
    Robust Wide Baseline Stereo from Maximally Stable Extremal Regions, 2002, 斑点检测(blob detection).

2016-09-03 15:58:29 NNNNNNNNNNNNY 阅读数 13057

关于“轮廓检测”和“边缘检测”这两个自己也弄的不是特别清楚,可能确实比较相似吧。下面简单说一下自己的看法。
区别:
边缘检测主要是通过一些手段检测数字图像中明暗变化剧烈(即梯度变化比较大)像素点,偏向于图像中像素点的变化。如canny边缘检测,结果通常保存在和源图片一样尺寸和类型的边缘图中。
轮廓检测指检测图像中的对象边界,更偏向于关注上层语义对象。如OpenCV中的findContours()函数, 它会得到每一个轮廓并以点向量方式存储,除此也得到一个图像的拓扑信息,即一个轮廓的后一个轮廓、前一个轮廓、父轮廓和内嵌轮廓的索引编号。
联系:
我们在做图像的轮廓检测时通常可以先检测边缘,再将检测到的边缘进行进一步处理,得到图像的轮廓。

参考:
1. http://wangmurong.org.cn/2015/10/21/edge-detection-segmentation-contour-detection/
2. Opencv官方例程中的contours2.cpp
3. 《OpenCV3编程入门》 浅墨_毛星云