角点检测

基本概念

1.兴趣点
在图像处理和计算机视觉领域, 兴趣点(interest points)也被称为关键点(key points)或者特征点(feature points)被大量用于解决物体识别、图像识别、图像匹配、视觉跟踪、三维重建等一系列问题。我们不再观察整幅图, 而是选择某些特殊的点, 然后对它们进行分析, 如果能检测到足够的这种点, 同时它们的区分度很高, 并且可以精确定位稳定的特征, 那么这个方法就具有实用价值。图像特征类型被分为如下三种:

  • 边缘
  • 角点(感兴趣关键点)
  • 斑点(Blob)(感兴趣区域)
    2.角点
    角点通常被定义为两条边的交点, 也被定义为在任意方向的一个微小变动都会引起灰度很大的变化的点。角点作为图像上的特征点, 包含有重要的信息, 在图像融合和目标跟踪及三维重建中有重要应用价值。另外, 角点
    的描述也可以有以下几种:
  • 一阶导数(即灰度的梯度)的局部最大所对应的像素点;
  • 两条及两条以上边缘的交点;
  • 图像中梯度值和梯度方向的变化速率都很高的点;
  • 角点处的一阶导数最大, 二阶导数为0, 它指示了物体边缘变化不连续的方向。
    3.角点检测
    现有的角点检测算法并不是十分健壮, 很多方法需要大量的训练集合冗余数据来防止或减少错误特征的出现。另外, 角点检测方法的一个重要衡量标准是其对多幅图像中相同或相似特征的检测能力, 并且能够应对光照变化、图像旋转等变化。目前的角点检测算法可归结为以下三类:
  • 基于灰度图像的角点检测
  • 基于二值图像的角点检测
  • 基于轮廓曲线的角点检测
    基于灰度图像的角点检测又可以分为基于梯度、基于模板和基于模板梯度组合三类方法。其中基于模板的方法主要考虑像素邻域点的灰度变化, 即图像亮度的变化, 将与邻点亮度对比足够大的点定义为角点, 常用的Harris角点检测算法就是基于模板的角点检测方法。

Harris角点检测—cornerHarris()

基本概念

harris角点检测是一种直接基于灰度图像的角点提取算法, 稳定性高, 但是由于采用了高斯滤波, 运算速度相对较慢, 角点信息有丢失和位置偏移的现象, 以及聚簇现象。OpenCV提供cornerHarris()函数来进行harris角点检测, 它是对每一个像素(x,y)在blockSize x blockSize邻域内, 计算2x2梯度的协方差矩阵M(x,y), 计算如下式子:

opencv Moravec角点 opencv找角点_角点检测

就可以找到局部最大值, 也就是找到了角点, 我们可以用阈值化筛选自己要的角点。
参考网址:

函数原型

void cornerHarris( InputArray src, OutputArray dst, int blockSize,
                                int ksize, double k,
                                int borderType = BORDER_DEFAULT );
  • src: 输入原图, 需为8位单通道或浮点类型图像
  • dst: Harris角点检测结果, 类型为CV_32FC1, 大小和原图一样
  • blockSize: 邻域大小, 更多信息查看cornerEigenValsAndVecs()
  • ksize: 表示Sobel()算子孔径大小
  • k: Harris检测常量参数, 通常情况取值为0.04~0.06
  • borderType: 图像像素的边界模式,一般采用默认值

应用实例

opencv Moravec角点 opencv找角点_角点_02

代码

Mat img = imread("D:\\1\\2.jpg");
imshow("src", img);
Mat result = img.clone();
Mat gray;
cvtColor(img, gray, CV_BGR2GRAY);

Mat dst;
Mat corner_img;
cornerHarris(gray, corner_img, 2, 3, 0.04);
imshow("corner", corner_img);
threshold(corner_img, dst, 0.005, 255, CV_THRESH_BINARY);
imshow("dst", dst);

int rowNumber = gray.rows;  //获取行数
int colNumber = gray.cols;  //获取每一行的元素
cout<<rowNumber<<endl;
cout<<colNumber<<endl;
cout<<dst.type()<<endl;


for(int i = 0; i<rowNumber; i++)
{
   for(int j = 0; j<colNumber; j++)
   {
       if(dst.at<float>(i, j) == 255)
       {
           circle(result, Point(j, i), 5, Scalar(0, 0, 255), 2, 8);
       }
   }
}

imshow("result", result);
waitKey(0);

运行结果

opencv Moravec角点 opencv找角点_opencv Moravec角点_03

opencv Moravec角点 opencv找角点_opencv Moravec角点_04

opencv Moravec角点 opencv找角点_opencv_05

opencv Moravec角点 opencv找角点_角点检测_06

Shi-Tomasi角点检测—goodFeaturesToTrack()

Shi-Tomasi算法是Harris算法的改进, Harris 算法最原始的定义是将矩阵 M 的行列式值与 M 的迹相减, 再将差值同预先给定的阈值进行比较。后来Shi和Tomasi提出改进的方法, 若两个特征值中较小的一个大于最小阈值,则会得到强角点。

函数原型

void goodFeaturesToTrack( InputArray image, OutputArray corners,
                                     int maxCorners, double qualityLevel, double minDistance,
                                     InputArray mask = noArray(), int blockSize = 3,
                                     bool useHarrisDetector = false, double k = 0.04 );
  • image: 输入图像, 需为8位或32位浮点类型单通道图像
  • corners: 检测到的角点的输出向量
  • maxCorners: 角点的最大数量
  • qualityLevel: 角点检测可接受的最小特征值, 实际用于过滤角点的最小特征是qualityLevel和图像中最大特征值得乘积, 所有qualityLevel一般不超过1(通常取0.10或0.01)
  • minDistance: 角点之间的最小距离, 此参数用于保证返回的角点之间的距离不小于minDistance个像素
  • mask: 可选参数, 表示感兴趣区域的掩码, 用于指定角点检测区域
  • blcokSize: 默认值3, 是计算导数自相关矩阵时指定的邻域范围
  • useHarrisDetector: 默认值false, 指示是否使用Harris角点检测
  • k: 默认值0.04, 用于设置Hessian自相关矩阵行列式的相对权重的权重系数

应用实例

opencv Moravec角点 opencv找角点_角点检测_07

代码

Mat img = imread("0.jpg");
imshow("src", img);
Mat result = img.clone();
Mat gray;
cvtColor(img, gray, CV_BGR2GRAY);

vector<Point2f> corners;
goodFeaturesToTrack(gray, corners, 100, 0.01, 10, Mat(), 3, false, 0.04);
cout<<"角点数量"<<corners.size()<<endl;

for(int i = 0; i<corners.size(); i++)
{
    circle(result, corners[i], 5, Scalar(0, 255, 0), 2, 8);
}

imshow("result", result);
waitKey(0);

运行结果

opencv Moravec角点 opencv找角点_ci_08

opencv Moravec角点 opencv找角点_ci_09

亚像素角点检测—cornerSubPix()

基本概念

如果我们进行图像处理的目的不是提取用于之别的特征点, 而是进行几何测量, 通常需要更高的精度, 而前面的角点检测方法都只能提供像素级坐标, 但是有时候需要更高的精度比如亚像素(浮点坐标)精度。亚像素级角点检测在摄像机标定、三维结构重建方面是一个基本的测量值。

函数原型

void cornerSubPix( InputArray image, InputOutputArray corners,
                                Size winSize, Size zeroZone,
                                TermCriteria criteria );
  • image: 输入图像
  • corners: 提供输入角点初始坐标和精确的输出坐标
  • winSize: 搜索窗口的一半尺寸, 若winSize=(5,5), 那么表示使用(5*2+1)x(5*2+1)=11x11大小的搜索窗口
  • zeroZone: 表示死区的一半尺寸, 值为(-1,-1)表示没有死区
  • criteria: TermCriteria类型的对象, 表示求角点的迭代过程的终止条件, 即角点位置的确定, criteria可以是最大迭代数目, 或者是设定的精度, 也可以是它们的组合

应用实例

opencv Moravec角点 opencv找角点_ci_10

代码

Mat img = imread("3.jpg");
imshow("src", img);
Mat result = img.clone();
Mat gray;
cvtColor(img, gray, CV_BGR2GRAY);

vector<Point2f> corners;
goodFeaturesToTrack(gray, corners, 100, 0.01, 10, Mat(), 3, false, 0.04);
cout<<"角点数量"<<corners.size()<<endl;

for(int i = 0; i<corners.size(); i++)
{
    cout<<"像素坐标:("<<corners[i].x<<", "<<corners[i].y<<")"<<endl;
    circle(result, corners[i], 5, Scalar(0, 255, 0), 2, 8);
}
imshow("result", result);

Size winSize = Size(5, 5);
Size zeroZone = Size(-1, -1);
//最大迭代数目或精度其中一个达到
TermCriteria criteria = TermCriteria(CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 40, 0.001);
cornerSubPix(gray, corners, winSize, zeroZone, criteria);

for(int j=0; j<corners.size(); j++)
{
    cout<<"亚像素坐标:("<<corners[j].x<<", "<<corners[j].y<<")"<<endl;
    circle(img, corners[j], 5, Scalar(0, 255, 0), -1, 8);
}
imshow("subPix", img);

waitKey(0);

运行结果

opencv Moravec角点 opencv找角点_角点_11

opencv Moravec角点 opencv找角点_角点检测_12

示例代码1

//--------------------------------------【程序说明】-------------------------------------------
//      程序说明:《OpenCV3编程入门》OpenCV2版书本配套示例程序86
//      程序描述:Harris角点检测
//      开发测试所用操作系统: Windows 7 64bit
//      开发测试所用IDE版本:Visual Studio 2010
//      开发测试所用OpenCV版本: 2.4.9
//      2014年06月 Created by @浅墨_毛星云
//      2014年11月 Revised by @浅墨_毛星云
//------------------------------------------------------------------------------------------------



//---------------------------------【头文件、命名空间包含部分】----------------------------
//      描述:包含程序所使用的头文件和命名空间
//------------------------------------------------------------------------------------------------
#include <opencv2/opencv.hpp>
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
using namespace cv;
using namespace std;


//-----------------------------------【宏定义部分】--------------------------------------------  
//  描述:定义一些辅助宏  
//------------------------------------------------------------------------------------------------  
#define WINDOW_NAME1 "【程序窗口1】"        //为窗口标题定义的宏  
#define WINDOW_NAME2 "【程序窗口2】"        //为窗口标题定义的宏  

//-----------------------------------【全局变量声明部分】--------------------------------------
//      描述:全局变量声明
//-----------------------------------------------------------------------------------------------
Mat g_srcImage, g_srcImage1,g_grayImage;
int thresh = 30; //当前阈值
int max_thresh = 175; //最大阈值


//-----------------------------------【全局函数声明部分】--------------------------------------
//      描述:全局函数声明
//-----------------------------------------------------------------------------------------------
void on_CornerHarris( int, void* );//回调函数
static void ShowHelpText();

//-----------------------------------【main( )函数】--------------------------------------------
//      描述:控制台应用程序的入口函数,我们的程序从这里开始执行
//-----------------------------------------------------------------------------------------------
int main( int argc, char** argv )
{
    //【0】改变console字体颜色
    system("color 3F");  

    //【0】显示帮助文字
    ShowHelpText();

    //【1】载入原始图并进行克隆保存
    g_srcImage = imread( "3.jpg", 1 );
    if(!g_srcImage.data ) { printf("读取图片错误,请确定目录下是否有imread函数指定的图片存在~! \n"); return false; }  
    imshow("原始图",g_srcImage);
    g_srcImage1=g_srcImage.clone( );

    //【2】存留一张灰度图
    cvtColor( g_srcImage1, g_grayImage, CV_BGR2GRAY );

    //【3】创建窗口和滚动条
    namedWindow( WINDOW_NAME1, CV_WINDOW_AUTOSIZE );
    createTrackbar( "阈值: ", WINDOW_NAME1, &thresh, max_thresh, on_CornerHarris );

    //【4】调用一次回调函数,进行初始化
    on_CornerHarris( 0, 0 );

    waitKey(0);
    return(0);
}

//-----------------------------------【on_HoughLines( )函数】--------------------------------
//      描述:回调函数
//----------------------------------------------------------------------------------------------

void on_CornerHarris( int, void* )
{
    //---------------------------【1】定义一些局部变量-----------------------------
    Mat dstImage;//目标图
    Mat normImage;//归一化后的图
    Mat scaledImage;//线性变换后的八位无符号整型的图

    //---------------------------【2】初始化---------------------------------------
    //置零当前需要显示的两幅图,即清除上一次调用此函数时他们的值
    dstImage = Mat::zeros( g_srcImage.size(), CV_32FC1 );
    g_srcImage1=g_srcImage.clone( );

    //---------------------------【3】正式检测-------------------------------------
    //进行角点检测
    cornerHarris( g_grayImage, dstImage, 2, 3, 0.04, BORDER_DEFAULT );

    // 归一化与转换
    normalize( dstImage, normImage, 0, 255, NORM_MINMAX, CV_32FC1, Mat() );
    convertScaleAbs( normImage, scaledImage );//将归一化后的图线性变换成8位无符号整型 

    //---------------------------【4】进行绘制-------------------------------------
    // 将检测到的,且符合阈值条件的角点绘制出来
    for( int j = 0; j < normImage.rows ; j++ )
    { for( int i = 0; i < normImage.cols; i++ )
    {
        if( (int) normImage.at<float>(j,i) > thresh+80 )
        {
            circle( g_srcImage1, Point( i, j ), 5,  Scalar(10,10,255), 2, 8, 0 );
            circle( scaledImage, Point( i, j ), 5,  Scalar(0,10,255), 2, 8, 0 );
        }
    }
    }
    //---------------------------【4】显示最终效果---------------------------------
    imshow( WINDOW_NAME1, g_srcImage1 );
    imshow( WINDOW_NAME2, scaledImage );

}

//-----------------------------------【ShowHelpText( )函数】----------------------------------
//      描述:输出一些帮助信息
//----------------------------------------------------------------------------------------------
static void ShowHelpText()
{
    //输出欢迎信息和OpenCV版本
    printf("\n\n\t\t\t非常感谢购买《OpenCV3编程入门》一书!\n");
    printf("\n\n\t\t\t此为本书OpenCV2版的第86个配套示例程序\n");
    printf("\n\n\t\t\t   当前使用的OpenCV版本为:" CV_VERSION );
    printf("\n\n  ----------------------------------------------------------------------------\n");
    //输出一些帮助信息
    printf("\n\n\n\t【欢迎来到Harris角点检测示例程序~】\n\n");  
    printf("\n\t请调整滚动条观察图像效果~\n\n");
}

实例代码2

//--------------------------------------【程序说明】-------------------------------------------
//      程序说明:《OpenCV3编程入门》OpenCV2版书本配套示例程序87
//      程序描述:Shi-Tomasi角点检测示例
//      开发测试所用操作系统: Windows 7 64bit
//      开发测试所用IDE版本:Visual Studio 2010
//      开发测试所用OpenCV版本: 2.4.9
//      2014年06月 Created by @浅墨_毛星云
//      2014年11月 Revised by @浅墨_毛星云
//------------------------------------------------------------------------------------------------



//---------------------------------【头文件、命名空间包含部分】----------------------------
//      描述:包含程序所使用的头文件和命名空间
//------------------------------------------------------------------------------------------------
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
using namespace cv;
using namespace std;

//-----------------------------------【宏定义部分】-------------------------------------------- 
//  描述:定义一些辅助宏 
//----------------------------------------------------------------------------------------------
#define WINDOW_NAME "【Shi-Tomasi角点检测】"        //为窗口标题定义的宏 



//-----------------------------------【全局变量声明部分】--------------------------------------
//          描述:全局变量声明
//-----------------------------------------------------------------------------------------------
Mat g_srcImage, g_grayImage;
int g_maxCornerNumber = 33;
int g_maxTrackbarNumber = 500;
RNG g_rng(12345);//初始化随机数生成器


//-----------------------------【on_GoodFeaturesToTrack( )函数】----------------------------
//          描述:响应滑动条移动消息的回调函数
//----------------------------------------------------------------------------------------------
void on_GoodFeaturesToTrack( int, void* )
{
    //【1】对变量小于等于1时的处理
    if( g_maxCornerNumber <= 1 ) { g_maxCornerNumber = 1; }

    //【2】Shi-Tomasi算法(goodFeaturesToTrack函数)的参数准备
    vector<Point2f> corners;
    double qualityLevel = 0.01;//角点检测可接受的最小特征值
    double minDistance = 10;//角点之间的最小距离
    int blockSize = 3;//计算导数自相关矩阵时指定的邻域范围
    double k = 0.04;//权重系数
    Mat copy = g_srcImage.clone();  //复制源图像到一个临时变量中,作为感兴趣区域

    //【3】进行Shi-Tomasi角点检测
    goodFeaturesToTrack( g_grayImage,//输入图像
        corners,//检测到的角点的输出向量
        g_maxCornerNumber,//角点的最大数量
        qualityLevel,//角点检测可接受的最小特征值
        minDistance,//角点之间的最小距离
        Mat(),//感兴趣区域
        blockSize,//计算导数自相关矩阵时指定的邻域范围
        false,//不使用Harris角点检测
        k );//权重系数


    //【4】输出文字信息
    cout<<"\t>此次检测到的角点数量为:"<<corners.size()<<endl;

    //【5】绘制检测到的角点
    int r = 4;
    for( int i = 0; i < corners.size(); i++ )
    { 
        //以随机的颜色绘制出角点
        circle( copy, corners[i], r, Scalar(g_rng.uniform(0,255), g_rng.uniform(0,255),
            g_rng.uniform(0,255)), -1, 8, 0 ); 
    }

    //【6】显示(更新)窗口
    imshow( WINDOW_NAME, copy );
}


//-----------------------------------【ShowHelpText( )函数】----------------------------------
//          描述:输出一些帮助信息
//----------------------------------------------------------------------------------------------
static void ShowHelpText( )
{
    //输出欢迎信息和OpenCV版本
    printf("\n\n\t\t\t非常感谢购买《OpenCV3编程入门》一书!\n");
    printf("\n\n\t\t\t此为本书OpenCV2版的第87个配套示例程序\n");
    printf("\n\n\t\t\t   当前使用的OpenCV版本为:" CV_VERSION );
    printf("\n\n  ----------------------------------------------------------------------------\n");
    //输出一些帮助信息
    printf("\n\n\n\t欢迎来到【Shi-Tomasi角点检测】示例程序\n"); 
    printf("\n\t请调整滑动条观察图像效果\n\n");

}


//--------------------------------------【main( )函数】-----------------------------------------
//          描述:控制台应用程序的入口函数,我们的程序从这里开始执行
//-----------------------------------------------------------------------------------------------
int main(  )
{
    //【0】改变console字体颜色
    system("color 2F"); 

    //【0】显示帮助文字
    ShowHelpText();

    //【1】载入源图像并将其转换为灰度图
    g_srcImage = imread("3.jpg", 1 );
    cvtColor( g_srcImage, g_grayImage, CV_BGR2GRAY );

    //【2】创建窗口和滑动条,并进行显示和回调函数初始化
    namedWindow( WINDOW_NAME, CV_WINDOW_AUTOSIZE );
    createTrackbar( "最大角点数", WINDOW_NAME, &g_maxCornerNumber, g_maxTrackbarNumber, on_GoodFeaturesToTrack );
    imshow( WINDOW_NAME, g_srcImage );
    on_GoodFeaturesToTrack( 0, 0 );

    waitKey(0);
    return(0);
}