OpenCV中常用的角点检测为Harris角点和ShiTomasi角点。

以OpenCV源代码文件 .\opencv\sources\samples\cpp\tutorial_code\TrackingMotion\cornerDetector_Demo.cpp为例,主要分析其中的这两种角点检测源代码。角点检测数学原理请参考我之前转载的一篇博客 http://www.cnblogs.com/riddick/p/7645904.html,分析的很详细,不再赘述。本文主要分析其源代码:

1. Harris角点检测  

  根据数学上的推导,可以根据图像中某一像素点邻域内构建的协方差矩阵获取特征值和特征向量,根据特征值建立特征表达式,如下:                

(αβ) - k(α+β)^

  可以根据上式的值得大小来判断该像素点是平坦区域内点、边界点还是角点。下面说一下怎么在原图像中建立协方差矩阵并求取特征值α和β和特征向量t1, t2。

  该例程代码中调用cornerEigenValsAndVecs()函数计算特征值和特征向量。函数原型如下:

void cv::cornerEigenValsAndVecs( InputArray _src, OutputArray _dst, int blockSize, int ksize, int borderType )

  src为输入灰度图像,dst为输出(6通道 CV_32FC(6),依次保存的是α, t1,  β, t2),blockSize为邻域大小,ksize为sobel求取微分时的窗口大小。  

  该函数内部调用cornerEigenValsVecs()函数,原型如下: 

static void  cornerEigenValsVecs( const Mat& src, Mat& eigenv, int block_size,int aperture_size, int op_type, double k=.,int borderType=BORDER_DEFAULT )

  主要介绍一下op_type这个参数,该参数是一个枚举值,有三个值可以选择(MINEIGENVAL, HARRIS, EIGENVALSVECS)

  ①MINEIGENVAL用于ShiTomasi角点检测中获取两个特征值中较小的那个值,用以获取强角点,随后介绍;

  ②HARRIS在cornerHarris()函数中用到,用于直接利用协方差矩阵获取特征表达式值的大小,k值在此时会被设置,通常为0.04,其他情况下设置为0;

  ③EIGENVALSVECS就是本例程中设置的,求取两个特征值和特征向量。

  在cornerEigenValsVecs()函数中,先利用sobel算子求水平方向和竖直方向的微分,窗口大小为前述,如下代码:         

Mat Dx, Dy;
if( aperture_size > )
{
Sobel( src, Dx, CV_32F, , , aperture_size, scale, , borderType );
Sobel( src, Dy, CV_32F, , , aperture_size, scale, , borderType );
}
else
{
Scharr( src, Dx, CV_32F, , , scale, , borderType );
Scharr( src, Dy, CV_32F, , , scale, , borderType );

  然后初始化协方差矩阵cov(三通道,依次保存dx*dx, dx*dy, dy*dy),如下:

for( ; j < size.width; j++ )
{
float dx = dxdata[j];
float dy = dydata[j]; cov_data[j*] = dx*dx;
cov_data[j*+] = dx*dy;
cov_data[j*+] = dy*dy;

  接下来对协方差矩阵进行在前述设定窗口内进行均值(盒式)滤波:

    boxFilter(cov, cov, cov.depth(), Size(block_size, block_size),
Point(-,-), false, borderType ); if( op_type == MINEIGENVAL )
calcMinEigenVal( cov, eigenv );
else if( op_type == HARRIS )
calcHarris( cov, eigenv, k );
else if( op_type == EIGENVALSVECS )
calcEigenValsVecs( cov, eigenv );

  然后就是利用滤波后的协方差矩阵求取特征值和特征向量了,根据设定不同的op_type调用不同的函数计算,本例程中为调用最后一个calcEigenValsVecs()函数,该函数如下:

static void calcEigenValsVecs( const Mat& _cov, Mat& _dst )
{
Size size = _cov.size();
if( _cov.isContinuous() && _dst.isContinuous() )
{
size.width *= size.height;
size.height = ;
} for( int i = ; i < size.height; i++ )
{
const float* cov = _cov.ptr<float>(i);
float* dst = _dst.ptr<float>(i);
//调用该函数计算2x2协方差矩阵的特征值和特征向量
eigen2x2(cov, dst, size.width);
}
}

  该函数中调用eigen2x2()函数计算每个像素点处协方差矩阵的2个特征值和2个特征向量,协方差矩阵为如下形式,数据都保存在cov的三个通道中:

   eigen2x2()函数如下:2x2矩阵特征值和特征向量的计算,有线性代数基础的都学过,就不再赘述

static void eigen2x2( const float* cov, float* dst, int n )
{
for( int j = ; j < n; j++ )
{
double a = cov[j*];
double b = cov[j*+];
double c = cov[j*+]; double u = (a + c)*0.5;
double v = std::sqrt((a - c)*(a - c)*0.25 + b*b);
     
     //计算两个特征值l1,l2
double l1 = u + v;
double l2 = u - v;
     //计算特征值l1对应的特征向量
double x = b;
double y = l1 - a;
double e = fabs(x); if( e + fabs(y) < 1e- )
{
y = b;
x = l1 - c;
e = fabs(x);
if( e + fabs(y) < 1e- )
{
e = ./(e + fabs(y) + FLT_EPSILON);
x *= e, y *= e;
}
} double d = ./std::sqrt(x*x + y*y + DBL_EPSILON);
//保存特征值l1及其对应的特征向量
dst[*j] = (float)l1;
dst[*j + ] = (float)(x*d);
dst[*j + ] = (float)(y*d);
//计算特征值l2对应的特征向量
x = b;
y = l2 - a;
e = fabs(x); if( e + fabs(y) < 1e- )
{
y = b;
x = l2 - c;
e = fabs(x);
if( e + fabs(y) < 1e- )
{
e = ./(e + fabs(y) + FLT_EPSILON);
x *= e, y *= e;
}
} d = ./std::sqrt(x*x + y*y + DBL_EPSILON);
//保存特征值l2及其对应的特征向量
dst[*j + ] = (float)l2;
dst[*j + ] = (float)(x*d);
dst[*j + ] = (float)(y*d);
}
}

  求得2个特征值α、β和2个特征向量之后,就是要利用特征值构建特征表达式,通过表达式的值(  (αβ) - k(α+β)^2   )来区分角点,k的值通常设置为0.04:

/* calculate Mc */
for( int j = ; j < src_gray.rows; j++ )
{ for( int i = ; i < src_gray.cols; i++ )
{
float lambda_1 = myHarris_dst.at<Vec6f>(j, i)[];
float lambda_2 = myHarris_dst.at<Vec6f>(j, i)[];
Mc.at<float>(j,i) = lambda_1*lambda_2 - 0.04f*pow( ( lambda_1 + lambda_2 ), );
}
}

  代码中利用 minMaxLoc( Mc, &myHarris_minVal, &myHarris_maxVal, , , Mat() ); 函数获取特征表达式的最大值min和最小值max,通过选取不同的阈值min<=thresh<=max,来指定大于阈值thresh的表达式值对应的点为检测出的角点。并利用circle()函数显示出来。

circle( myHarris_copy, Point(i,j), , Scalar( rng.uniform(,), rng.uniform(,), rng.uniform(,) ), -, ,  ); 

至此,Harris角点检测完成!

 2. ShiTomasi角点检测

  ShiTomasi角点提取是获取harris角点中的强角点,怎么获取强角点呢,那就是只选取两个特征值中较小的那个特征值构建特征表达式,如果较小的特征值都能够满足设定的阈值条件,那么该角点就视为强角点。

  调用  cornerMinEigenVal( src_gray, myShiTomasi_dst, blockSize, apertureSize, BORDER_DEFAULT ); 函数来获取较小的特征值,其实该函数内部依然调用上面所述的函数 cornerEigenValsVecs( src, dst, blockSize, ksize, MINEIGENVAL, , borderType ); ,然后将op_type设置为MINEIGENVAL枚举值,进而调用 static void calcMinEigenVal( const Mat& _cov, Mat& _dst ) 函数计算较小的特征值。该函数代码如下:

static void calcMinEigenVal( const Mat& _cov, Mat& _dst )
{
int i, j;
Size size = _cov.size();
#if CV_TRY_AVX
bool haveAvx = CV_CPU_HAS_SUPPORT_AVX;
#endif
#if CV_SIMD128
bool haveSimd = hasSIMD128();
#endif if( _cov.isContinuous() && _dst.isContinuous() )
{
size.width *= size.height;
size.height = ;
} for( i = ; i < size.height; i++ )
{
const float* cov = _cov.ptr<float>(i);
float* dst = _dst.ptr<float>(i);
#if CV_TRY_AVX
if( haveAvx )
j = calcMinEigenValLine_AVX(cov, dst, size.width);
else
#endif // CV_TRY_AVX
j = ; #if CV_SIMD128
if( haveSimd )
{
v_float32x4 half = v_setall_f32(0.5f);
for( ; j <= size.width - v_float32x4::nlanes; j += v_float32x4::nlanes )
{
v_float32x4 v_a, v_b, v_c, v_t;
v_load_deinterleave(cov + j*, v_a, v_b, v_c);
v_a *= half;
v_c *= half;
v_t = v_a - v_c;
v_t = v_muladd(v_b, v_b, (v_t * v_t));
v_store(dst + j, (v_a + v_c) - v_sqrt(v_t));
}
}
#endif // CV_SIMD128 for( ; j < size.width; j++ )
{
float a = cov[j*]*0.5f;
float b = cov[j*+];
float c = cov[j*+]*0.5f; //求根公式计算较小的根,即为较小的特征值
dst[j] = (float)((a + c) - std::sqrt((a - c)*(a - c) + b*b));
}
}
}

  所有像素点处较小的特征值求出后,直接将该特征值作为特征表达式的值。利用 minMaxLoc( Mc, &myHarris_minVal, &myHarris_maxVal, , , Mat() ); 函数选取最小的min和最大的max,通过调整阈值thresh来设定大于阈值thresh的为显示出来的强角点。

  至此,ShiTomasi角点检测完成!

  #自己写了一个简单的Harris和ShiTomasi角点检测的代码,如下,仅供参考:

 #include <opencv2\opencv.hpp>
#include <iostream>
#include <string> using namespace std; #define HARRIS cv::RNG rng(); void calEigen2x2(cv::Mat cov,cv::Mat &eigenValue)
{
int height = cov.rows;
int width = cov.cols; float *pCov = (float*)cov.data;
float *pEigenValue = (float*)eigenValue.data;
for (int i = ; i < height; i++)
{
for (int j = ; j < width; j++)
{
double a = pCov[(i*width + j) * + ];
double b = pCov[(i*width + j) * + ];
double c = pCov[(i*width + j) * + ]; double tmp1 = (a + c) / .;
double tmp2 = sqrtf(b*b + (a - c)*(a - c) / .); double alpha = tmp1 - tmp2;
double beta = tmp1 + tmp2; pEigenValue[(i*width + j) * + ] =(float) alpha;
pEigenValue[(i*width + j) * + ] =(float) beta;
}
}
} void myCalEigenValues(cv::Mat srcImg, cv::Mat &eigenValue, int covWin, int sobelWin)
{
//求微分
cv::Mat sobelx, sobely;
cv::Sobel(srcImg, sobelx, CV_32FC1, , , sobelWin, . / (*), , );
cv::Sobel(srcImg, sobely, CV_32FC1, , , sobelWin, . / (*), , ); cv::Mat cov = cv::Mat::zeros(srcImg.size(), CV_32FC3);
int height = srcImg.rows;
int width = srcImg.cols;
float *pSobelX = (float*)sobelx.data;
float *pSobelY = (float*)sobely.data;
float *pCov = (float*)cov.data;
for (int i = ; i < height; i++)
{
for (int j = ; j < width; j++)
{
float dx = pSobelX[i*width + j];
float dy = pSobelY[i*width + j]; pCov[(i*width + j) * + ] = dx*dx;
pCov[(i*width + j) * + ] = dx*dy;
pCov[(i*width + j) * + ] = dy*dy;
}
} cv::boxFilter(cov, cov, cov.depth(), cv::Size(covWin, covWin), cv::Point(-, -), false, ); calEigen2x2(cov, eigenValue);
} void main()
{
string imgPath = "data/srcImg/0.png";
cv::Mat srcImg = cv::imread(imgPath, );
cv::Mat grayImg;
cv::cvtColor(srcImg, grayImg, CV_BGR2GRAY);
int height = srcImg.rows;
int width = srcImg.cols; cv::Mat eigenValue = cv::Mat::zeros(grayImg.size(), CV_32FC2);
int covWin = , sobelWin = ;
myCalEigenValues(grayImg, eigenValue, covWin, sobelWin); cv::Mat Mc = cv::Mat::zeros(grayImg.size(), CV_32FC1);
#ifndef HARRIS
//计算特征表达式值
for (int i = ; i<height; i++)
{
for (int j = ; j < width; j++)
{
float alpha = eigenValue.at<float>(i, j*+);
float beta = eigenValue.at<float>(i, j*+); Mc.at<float>(i, j) = alpha*beta - 0.04f*pow((alpha + beta), );
}
}
#else //ShiTomasi
for (int i = ; i<height; i++)
{
for (int j = ; j < width; j++)
{
float alpha = eigenValue.at<float>(i, j * + );
float beta = eigenValue.at<float>(i, j * + ); float minEigenValue = (alpha > beta) ? (beta) : (alpha); Mc.at<float>(i, j) = minEigenValue;
}
}
#endif double minVal, maxVal;
cv::minMaxLoc(Mc, &minVal, &maxVal, , , cv::Mat()); double thresh = (maxVal + minVal) / .;
for (int i = ; i < height; i++)
{
for (int j = ; j < width; j++)
{
double value = (double)Mc.at<float>(i, j);
if (value > thresh)
{
cv::circle(srcImg, cv::Point(j, i), , cv::Scalar(rng.uniform(, ), rng.uniform(, ), rng.uniform(, )), -, , );
}
}
} cv::namedWindow("show", );
cv::imshow("show", srcImg);
cv::waitKey();
}

3. cornerHarris()函数详解

  前面讲述cornerEigenValsVecs()这个函数是提到op_type这个枚举类型,有三个枚举值可以设置。其中MINEIGENVAL 和  EIGENVALSVECS都在前面介绍过。而 HARRIS则在cornerHarris()函数中使用,这是一个公开的OpenCV API函数,函数原型如下:

void cv::cornerHarris( InputArray _src, OutputArray _dst, int blockSize, int ksize, double k, int borderType )

  k值为上述特征表达式中的常数项。该函数内部其实还是调用cornerEigenValsVecs()函数,只不过调用时将op_type设置为枚举值HARRIS。意思就是提取HARRIS角点,然后调用内部的 static void calcHarris( const Mat& _cov, Mat& _dst, double k ) 函数。只不过还内部函数不再计算特征值和特征向量,而是直接计算特征表达式的值。而特征表达式用下式表示:

  其中矩阵M就是前面说的协方差矩阵,det(M)为M的行列式,Tr(M)为M的迹。在程序中代码如下:

for( ; j < size.width; j++ )
{
float a = cov[j*];
float b = cov[j*+];
float c = cov[j*+];         //求特征表达式的值
dst[j] = (float)(a*c - b*b - k*(a + c)*(a + c));
}

  通常算出特征表达式的值后将其归一化到(0-255),然后可以直接设置阈值Thresh,特征表达式的值>Thresh对应的点视为角点。具体可以参见OpenCV例程源代码:.\opencv\sources\samples\cpp\tutorial_code\TrackingMotion\cornerHarris_Demo.cpp

 


OpenCV角点检测源代码分析(Harris和ShiTomasi角点)的更多相关文章

  1. Harris角点检测原理分析

    看到一篇从数学意义上讲解Harris角点检测很透彻的文章,转载自:http://blog.csdn.net/newthinker_wei/article/details/45603583 主要参考了: ...

  2. OpenCV亚像素角点cornerSubPixel()源代码分析

    上一篇博客中讲到了goodFeatureToTrack()这个API函数能够获取图像中的强角点.但是获取的角点坐标是整数,但是通常情况下,角点的真实位置并不一定在整数像素位置,因此为了获取更为精确的角 ...

  3. 角点检测和匹配——Harris算子

    一.基本概念 角点corner:可以将角点看做两个边缘的交叉处,在两个方向上都有较大的变化.具体可由下图中分辨出来: 兴趣点interest point:兴趣点是图像中能够较鲁棒的检测出来的点,它不仅 ...

  4. 【Computer Vision】角点检测和匹配——Harris算子

    一.基本概念 角点corner:可以将角点看做两个边缘的交叉处,在两个方向上都有较大的变化.具体可由下图中分辨出来: 兴趣点interest point:兴趣点是图像中能够较鲁棒的检测出来的点,它不仅 ...

  5. OpenCV——Harris、Shi Tomas、自定义、亚像素角点检测

    #include <opencv2/opencv.hpp> #include <iostream> using namespace cv; using namespace st ...

  6. goodFeaturesToTrack——Shi-Tomasi角点检测

    J.Shi和C.Tomasi在1994年在其论文"Good Features to Track"中,提出了一种对Harris角点检测算子的改进算法--Shi-Tomasi角点检测算 ...

  7. OpenCV角点检测goodFeaturesToTrack()源代码分析

    上面一篇博客分析了HARRIS和ShiTomasi角点检测的源代码.而为了提取更准确的角点,OpenCV中提供了goodFeaturesToTrack()这个API函数,来获取更加准确的角点位置.这篇 ...

  8. OpenCV计算机视觉学习(13)——图像特征点检测(Harris角点检测,sift算法)

    如果需要处理的原图及代码,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice 前言 ...

  9. opencv-角点检测之Harris角点检测

    转自:https://blog.csdn.net/poem_qianmo/article/details/29356187 先看看程序运行截图:   一.引言:关于兴趣点(interest point ...

随机推荐

  1. JavaScript八张思维导图—操作符

    JS基本概念 JS操作符 JS基本语句 JS数组用法 Date用法 JS字符串用法 JS编程风格 JS编程实践 不知不觉做前端已经五年多了,无论是从最初的jQuery还是现在火热的Angular,Vu ...

  2. PHP pathinfo() 函数

    PHP pathinfo() 函数 完整的 PHP Filesystem 参考手册 定义和用法 pathinfo() 函数以数组或字符串的形式返回关于文件路径的信息. 返回的数组元素如下: [dirn ...

  3. 把VueThink整合到已有ThinkPHP 5.0项目中

     享 关键字: VueThink ThinkPHP5.0 Vue2.x TP5 管理后台扩展 VueThink初认识 VueThink,是一个很不错的技术框架,由广州洪睿科技的技术团队2016年研发( ...

  4. vi命令加行号查找替换等命令

    一.加行号           : set nu二.vi查找:    当你用vi打开一个文件后,因为文件太长,如何才能找到你所要查找的关键字呢?在vi里可没有菜单-〉查找,              ...

  5. 数据库连接池(c3p0)

    (一)问题的提出: 在使用开发基于数据库的web程序时,传统的数据库使用模式按照以下步骤: 在程序中建立数据库连接 进行sql操作 断开数据库连接 但是,这种模式存在着移动的问题: 传统连接模式每次向 ...

  6. python 导入模块错误

    问题: 导入一些模块或者运行第三方软件的时候,会出现一下类似错误: ImportError: No module named future.utils 问题原因: 没有安装第三方库 future,这个 ...

  7. [Git] git log命令

    这是git的新系列,不常用的命令和其参数比较容易记不住,干脆将常用的记录下来,日后查查方便也是好的,一篇文章一个git命令,长短根据命令有所不同. git log命令主要用于查看提交历史,同时根据添加 ...

  8. 阿里java开发手册中命名规约解读之DO/BO/DTO/VO/AO

    前言 在阅读<阿里巴巴Java开发手册>时,看到命名规则中有这样一条 虽然知道这些是根据Java对象的角色所分配名称的后缀,但是没有弄清楚分别是什么意思,日常开发中也没有使用到. 网上查找 ...

  9. 转-WebService到底是什么?

    原文链接:WebService到底是什么? 一.序言 大家或多或少都听过WebService(Web服务),有一段时间很多计算机期刊.书籍和网站都大肆的提及和宣传WebService技术,其中不乏很多 ...

  10. tomcat部署最佳实践(一)

    Tomcat部署最佳实践 标签: linux 笔者Q:972581034 交流群:605799367.有任何疑问可与笔者或加群交流 tomcat是玩web软件必会技能之一,今天我给大家介绍一下tomc ...