前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >角点检测集锦(Harris,Shi-Tomasi,亚像素级角点检测)原理及相关实现

角点检测集锦(Harris,Shi-Tomasi,亚像素级角点检测)原理及相关实现

作者头像
种花家的奋斗兔
发布2020-11-13 10:28:18
1K0
发布2020-11-13 10:28:18
举报

什么是角点?一个像素所在的区域一般情况下可以分为以下三种情况,平坦,边,角点三种情况,如下图最右边。 在角点处,沿任意方向运动都会引起像素颜色的明显变化等价于:在角点附近,图像梯度具有至少两个主方向。 如何实现角点检测? 角点检测最原始的想法就是取某个像素的一个邻域窗口,当这个窗口在各个方向上进行小范围移动时,观察窗口内平均的像素灰度值的变化。对于上述图像三种情况下像素灰度值的变化是不同的。 其中u,v,分别为窗口在水平,竖直方向的偏移。 转换为矩阵的形式如上,二乘二的矩阵分别是图像在该像素点x方向,y方向的梯度的乘积。我们的目的是寻找这样的像素点,它使得我们的u,v无论怎样取值,E(u,v)都是变化比较大的,这个像素点就是我们要找的角点。 对应的物理意义如下: 其中,λ1和λ2是其特征值。 转换一下: 其中当R<0时,是边缘点,R>0时,是角点,R在0附近时,则表示平滑区域。这里的α是在0.04到0.06之间,是一个经验值常数。 扩展:Shi-Tomasi角点检测Shi-Tomasi 算法是Harris 算法的改进。Harris 算法最原始的定义是将矩阵 M 的行列式值与 M 的迹相减,再将差值同预先给定的阈值进行比较。后来Shi 和Tomasi 提出改进的方法,若两个特征值中较小的一个大于最小阈值,则会得到强角点。 即Shi-Tomasi使用的角点响应函数为: R = min(λ1, λ2)

扩展:亚像素级角点检测:当我们想要进行几何测量或者标定的时候势必要比目标识别需要更高的精度的特征点。而上面的goodFeaturesToTrack()只能得到整数的坐标值,这时候我们就需要亚像素级的角点检测来得到实数坐标值来满足精度需求。 亚像素级角点检测的位置摄像机标定,跟踪并重建摄像机的轨迹或者重建被跟踪目标的三维结构时,是一个基本的测量值。下面是将角点位置精确到亚像素精度的过程: 个向量与其正交的向量的点积为0,角点满足上图所示情况。其中(a)点p附近的图像是均匀的,其梯度为0;(b)边缘的梯度与沿边缘方向的q-p向量正交。在图中两种情况下,p点梯度与q-p向量的点积均为0。 上图中,我们假设起始角点q在实际亚像素角点的附近。检测所有的q-p向量。若点p位于一个均匀区域,则点p的梯度为0。若q-p向量的方向与边缘的方向一致,则此边缘上p点处的梯度与q-p向量正交,在这两种情况下,p点处的梯度与q-p向量的点积为0.我们可以在p点周围找到很多组梯度以及相关的向量q-p,令其点集为0,然后可以通过求解方程组,方程组的解即为角点q的亚像素级精度的位置,即精确的角点位置。

实现:注意:首先需要先将原图像转换为灰度图 调用库函数中的Harris角点检测createTrackbar("Threshold: ", source_window, &thresh, max_thresh, cornerHarris_demo); cornerHarris_demo(0, 0); 关于CornerHarris_demo函数的设计: Mat dst, dst_norm, dst_norm_scaled; dst = Mat::zeros(src.size(), CV_32FC1); int blockSize = 2; int apertureSize = 3; double k = 0.04;//传入参数k一般在0.4-0.6之间 cornerHarris(src_gray, dst, blockSize, apertureSize, k, BORDER_DEFAULT); normalize(dst, dst_norm, 0, 255, NORM_MINMAX, CV_32FC1, Mat()); convertScaleAbs(dst_norm, dst_norm_scaled); for (int j = 0; j < dst_norm.rows; j++) { for (int i = 0; i < dst_norm.cols; i++) { if ((int)dst_norm.at<float>(j, i) > thresh) { circle(dst_norm_scaled, Point(i, j), 5, Scalar(0), 2, 8, 0); } } } namedWindow(corners_window, WINDOW_AUTOSIZE); imshow(corners_window, dst_norm_scaled); 即,调用 cornerHarris函数,得到中间的矩阵M,同时使用最大值最小值归一化,对结果进行归一化,遍历矩阵,对于大于设定阈值的点,认为是角点,然后绘制图像。 调用函数进行Shi-Tomasi角点检测。使用 goodFeaturesToTrack函数进行检测,同时设置滑动条进行参数调节。 设计自己的Harris_Corner函数首先,定义图像块大小,核大小,以及相关Mat矩阵 int blockSize = 3; int apertureSize = 3;//孔径大小(核大小 myHarris_dst = Mat::zeros(src_gray.size(), CV_32FC(6)); Mc = Mat::zeros(src_gray.size(), CV_32FC1); 然后,通过 cornerEigenValsAndVecs函数计算出图像的特征值和特征向量,遍历图像,两个特征值分别通过以下方式获取 然后,计算R,此处设置的经验值常数为0.4,(一般在0.4-0.6之间) 计算方法如下: Mc.at<float>(j, i) = lambda_1 * lambda_2 - 0.04f * pow((lambda_1 + lambda_2), 2); 从而得到M矩阵。 使用 minMaxLoc函数计算M矩阵中的最大值和最小值,方便阈值和滑动条的设置。 此处注意,在设计滑动条的时候,对于小于1的数(即0)做了处理,使得小于1的数设置为1,即防止滑动条拖动到了最左端导致程序崩溃。 if (myHarris_qualityLevel < 1) { myHarris_qualityLevel = 1; } 遍历图像,判断为角点的条件设置为M矩阵的最小值+最大值最小值差占比。(滑动条设置方式。)然后绘制圆。 注意:这里绘制圆的时候,绘制点和图像遍历是的x,y是相反的!!! 如果设置错了会导致结果非常不理想。 设计自己的Shi-Tomasi函数。首先计算最小的特征值,使用 cornerMinEigenVal函数,同样使用minMacloc函数计算得到矩阵的最大值和最小值,此时,判断的时候,直接使用存入的目标Mat矩阵中的数值进行比较即可。比较方式同上。 亚像素级角点检测:使用 cornerSubPix函数计算角点的精确位置。 Size winSize = Size(5, 5); Size zeroZone = Size(-1, -1); TermCriteria criteria = TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 40, 0.001); cornerSubPix(src_gray, corners, winSize, zeroZone, criteria); for (size_t i = 0; i < corners.size(); i++) {//标定的精确位置。 cout << " -- Refined Corner [" << i << "] (" << corners[i].x << "," << corners[i].y << ")" << endl; } 输出点效果如下: 左图是输出的角点位置,右图为标记的角点。

  1. 什么是角点?

一个像素所在的区域一般情况下可以分为以下三种情况,平坦,边,角点三种情况,如下图最右边。 在角点处,沿任意方向运动都会引起像素颜色的明显变化等价于:在角点附近,图像梯度具有至少两个主方向。

  1. 如何实现角点检测?
  2. 角点检测最原始的想法就是取某个像素的一个邻域窗口,当这个窗口在各个方向上进行小范围移动时,观察窗口内平均的像素灰度值的变化。对于上述图像三种情况下像素灰度值的变化是不同的。

其中u,v,分别为窗口在水平,竖直方向的偏移。 转换为矩阵的形式如上,二乘二的矩阵分别是图像在该像素点x方向,y方向的梯度的乘积。我们的目的是寻找这样的像素点,它使得我们的u,v无论怎样取值,E(u,v)都是变化比较大的,这个像素点就是我们要找的角点。

  1. 对应的物理意义如下:

其中,λ1和λ2是其特征值。 转换一下: 其中当R<0时,是边缘点,R>0时,是角点,R在0附近时,则表示平滑区域。这里的α是在0.04到0.06之间,是一个经验值常数。

  1. 扩展:Shi-Tomasi角点检测

Shi-Tomasi 算法是Harris 算法的改进。Harris 算法最原始的定义是将矩阵 M 的行列式值与 M 的迹相减,再将差值同预先给定的阈值进行比较。后来Shi 和Tomasi 提出改进的方法,若两个特征值中较小的一个大于最小阈值,则会得到强角点 即Shi-Tomasi使用的角点响应函数为: R = min(λ1, λ2)

  1. 扩展:亚像素级角点检测:

当我们想要进行几何测量或者标定的时候势必要比目标识别需要更高的精度的特征点。而上面的goodFeaturesToTrack()只能得到整数的坐标值,这时候我们就需要亚像素级的角点检测来得到实数坐标值来满足精度需求。 亚像素级角点检测的位置摄像机标定,跟踪并重建摄像机的轨迹或者重建被跟踪目标的三维结构时,是一个基本的测量值。下面是将角点位置精确到亚像素精度的过程: 个向量与其正交的向量的点积为0,角点满足上图所示情况。其中(a)点p附近的图像是均匀的,其梯度为0;(b)边缘的梯度与沿边缘方向的q-p向量正交。在图中两种情况下,p点梯度与q-p向量的点积均为0。 上图中,我们假设起始角点q在实际亚像素角点的附近。检测所有的q-p向量。若点p位于一个均匀区域,则点p的梯度为0。若q-p向量的方向与边缘的方向一致,则此边缘上p点处的梯度与q-p向量正交,在这两种情况下,p点处的梯度与q-p向量的点积为0.我们可以在p点周围找到很多组梯度以及相关的向量q-p,令其点集为0,然后可以通过求解方程组,方程组的解即为角点q的亚像素级精度的位置,即精确的角点位置。

  1. 实现:

注意:首先需要先将原图像转换为灰度图

  1. 调用库函数中的Harris角点检测

createTrackbar("Threshold: ", source_window, &thresh, max_thresh, cornerHarris_demo); cornerHarris_demo(0, 0); 关于CornerHarris_demo函数的设计: Mat dst, dst_norm, dst_norm_scaled; dst = Mat::zeros(src.size(), CV_32FC1); int blockSize = 2; int apertureSize = 3; double k = 0.04;//传入参数k一般在0.4-0.6之间 cornerHarris(src_gray, dst, blockSize, apertureSize, k, BORDER_DEFAULT); normalize(dst, dst_norm, 0, 255, NORM_MINMAX, CV_32FC1, Mat()); convertScaleAbs(dst_norm, dst_norm_scaled); for (int j = 0; j < dst_norm.rows; j++) { for (int i = 0; i < dst_norm.cols; i++) { if ((int)dst_norm.at<float>(j, i) > thresh) { circle(dst_norm_scaled, Point(i, j), 5, Scalar(0), 2, 8, 0); } } } namedWindow(corners_window, WINDOW_AUTOSIZE); imshow(corners_window, dst_norm_scaled); 即,调用 cornerHarris函数,得到中间的矩阵M,同时使用最大值最小值归一化,对结果进行归一化,遍历矩阵,对于大于设定阈值的点,认为是角点,然后绘制图像。

  1. 调用函数进行Shi-Tomasi角点检测。

使用 goodFeaturesToTrack函数进行检测,同时设置滑动条进行参数调节。

  1. 设计自己的Harris_Corner函数

首先,定义图像块大小,核大小,以及相关Mat矩阵 int blockSize = 3; int apertureSize = 3;//孔径大小(核大小 myHarris_dst = Mat::zeros(src_gray.size(), CV_32FC(6)); Mc = Mat::zeros(src_gray.size(), CV_32FC1); 然后,通过 cornerEigenValsAndVecs函数计算出图像的特征值和特征向量,遍历图像,两个特征值分别通过以下方式获取 然后,计算R,此处设置的经验值常数为0.4,(一般在0.4-0.6之间) 计算方法如下: Mc.at<float>(j, i) = lambda_1 * lambda_2 - 0.04f * pow((lambda_1 + lambda_2), 2); 从而得到M矩阵。 使用 minMaxLoc函数计算M矩阵中的最大值和最小值,方便阈值和滑动条的设置。 此处注意,在设计滑动条的时候,对于小于1的数(即0)做了处理,使得小于1的数设置为1,即防止滑动条拖动到了最左端导致程序崩溃。 if (myHarris_qualityLevel < 1) { myHarris_qualityLevel = 1; } 遍历图像,判断为角点的条件设置为M矩阵的最小值+最大值最小值差占比。(滑动条设置方式。)然后绘制圆。 注意:这里绘制圆的时候,绘制点和图像遍历是的x,y是相反的!!! 如果设置错了会导致结果非常不理想。

  1. 设计自己的Shi-Tomasi函数。

首先计算最小的特征值,使用 cornerMinEigenVal函数,同样使用minMacloc函数计算得到矩阵的最大值和最小值,此时,判断的时候,直接使用存入的目标Mat矩阵中的数值进行比较即可。比较方式同上。

  1. 亚像素级角点检测:

使用 cornerSubPix函数计算角点的精确位置。 Size winSize = Size(5, 5); Size zeroZone = Size(-1, -1); TermCriteria criteria = TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 40, 0.001); cornerSubPix(src_gray, corners, winSize, zeroZone, criteria); for (size_t i = 0; i < corners.size(); i++) {//标定的精确位置。 cout << " -- Refined Corner [" << i << "] (" << corners[i].x << "," << corners[i].y << ")" << endl; } 输出点效果如下: 左图是输出的角点位置,右图为标记的角点。

代码实现:

Harris

代码语言:javascript
复制
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
using namespace cv;
using namespace std;
Mat src, src_gray;
int thresh = 200;
int max_thresh = 255;
const char* source_window = "Source image";
const char* corners_window = "Corners detected";
void cornerHarris_demo( int, void* );
int main( int, char** argv )
{
  src = imread( argv[1], IMREAD_COLOR );
  cvtColor( src, src_gray, COLOR_BGR2GRAY );
  namedWindow( source_window, WINDOW_AUTOSIZE );
  createTrackbar( "Threshold: ", source_window, &thresh, max_thresh, cornerHarris_demo );
  imshow( source_window, src );
  cornerHarris_demo( 0, 0 );
  waitKey(0);
  return(0);
}
void cornerHarris_demo( int, void* )
{
  Mat dst, dst_norm, dst_norm_scaled;
  dst = Mat::zeros( src.size(), CV_32FC1 );
  int blockSize = 2;
  int apertureSize = 3;
  double k = 0.04;
  cornerHarris( src_gray, dst, blockSize, apertureSize, k, BORDER_DEFAULT );
  normalize( dst, dst_norm, 0, 255, NORM_MINMAX, CV_32FC1, Mat() );
  convertScaleAbs( dst_norm, dst_norm_scaled );
  for( int j = 0; j < dst_norm.rows ; j++ )
     { for( int i = 0; i < dst_norm.cols; i++ )
          {
            if( (int) dst_norm.at<float>(j,i) > thresh )
              {
               circle( dst_norm_scaled, Point( i, j ), 5,  Scalar(0), 2, 8, 0 );
              }
          }
     }
  namedWindow( corners_window, WINDOW_AUTOSIZE );
  imshow( corners_window, dst_norm_scaled );
}

Shi-Tomasi

代码语言:javascript
复制
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
using namespace cv;
using namespace std;
Mat src, src_gray;
int maxCorners = 23;
int maxTrackbar = 100;
RNG rng(12345);
const char* source_window = "Image";
void goodFeaturesToTrack_Demo( int, void* );
int main( int, char** argv )
{
  src = imread( argv[1], IMREAD_COLOR );
  cvtColor( src, src_gray, COLOR_BGR2GRAY );
  namedWindow( source_window, WINDOW_AUTOSIZE );
  createTrackbar( "Max  corners:", source_window, &maxCorners, maxTrackbar, goodFeaturesToTrack_Demo );
  imshow( source_window, src );
  goodFeaturesToTrack_Demo( 0, 0 );
  waitKey(0);
  return(0);
}
void goodFeaturesToTrack_Demo( int, void* )
{
  if( maxCorners < 1 ) { maxCorners = 1; }
  vector<Point2f> corners;
  double qualityLevel = 0.01;
  double minDistance = 10;
  int blockSize = 3, gradiantSize = 3;
  bool useHarrisDetector = false;
  double k = 0.04;
  Mat copy;
  copy = src.clone();
  goodFeaturesToTrack( src_gray,
               corners,
               maxCorners,
               qualityLevel,
               minDistance,
               Mat(),
               blockSize,
               gradiantSize,
               useHarrisDetector,
               k );
  cout<<"** Number of corners detected: "<<corners.size()<<endl;
  int r = 4;
  for( size_t i = 0; i < corners.size(); i++ )
     { circle( copy, corners[i], r, Scalar(rng.uniform(0,255), rng.uniform(0,255), rng.uniform(0,255)), -1, 8, 0 ); }
  namedWindow( source_window, WINDOW_AUTOSIZE );
  imshow( source_window, copy );
}

自定义Harris

代码语言:javascript
复制
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
using namespace cv;
using namespace std;
Mat src, src_gray;
Mat myHarris_dst; Mat myHarris_copy; Mat Mc;
Mat myShiTomasi_dst; Mat myShiTomasi_copy;
int myShiTomasi_qualityLevel = 50;
int myHarris_qualityLevel = 50;
int max_qualityLevel = 100;
double myHarris_minVal; double myHarris_maxVal;
double myShiTomasi_minVal; double myShiTomasi_maxVal;
RNG rng(12345);
const char* myHarris_window = "My Harris corner detector";
const char* myShiTomasi_window = "My Shi Tomasi corner detector";
void myShiTomasi_function( int, void* );
void myHarris_function( int, void* );
int main( int, char** argv )
{
  src = imread( argv[1], IMREAD_COLOR );
  cvtColor( src, src_gray, COLOR_BGR2GRAY );
  int blockSize = 3; int apertureSize = 3;
  myHarris_dst = Mat::zeros( src_gray.size(), CV_32FC(6) );
  Mc = Mat::zeros( src_gray.size(), CV_32FC1 );
  cornerEigenValsAndVecs( src_gray, myHarris_dst, blockSize, apertureSize, BORDER_DEFAULT );
  /* calculate Mc */
  for( int j = 0; j < src_gray.rows; j++ )
     { for( int i = 0; i < src_gray.cols; i++ )
          {
            float lambda_1 = myHarris_dst.at<Vec6f>(j, i)[0];
            float lambda_2 = myHarris_dst.at<Vec6f>(j, i)[1];
            Mc.at<float>(j,i) = lambda_1*lambda_2 - 0.04f*pow( ( lambda_1 + lambda_2 ), 2 );
          }
     }
  minMaxLoc( Mc, &myHarris_minVal, &myHarris_maxVal, 0, 0, Mat() );
  /* Create Window and Trackbar */
  namedWindow( myHarris_window, WINDOW_AUTOSIZE );
  createTrackbar( " Quality Level:", myHarris_window, &myHarris_qualityLevel, max_qualityLevel, myHarris_function );
  myHarris_function( 0, 0 );
  myShiTomasi_dst = Mat::zeros( src_gray.size(), CV_32FC1 );
  cornerMinEigenVal( src_gray, myShiTomasi_dst, blockSize, apertureSize, BORDER_DEFAULT );
  minMaxLoc( myShiTomasi_dst, &myShiTomasi_minVal, &myShiTomasi_maxVal, 0, 0, Mat() );
  /* Create Window and Trackbar */
  namedWindow( myShiTomasi_window, WINDOW_AUTOSIZE );
  createTrackbar( " Quality Level:", myShiTomasi_window, &myShiTomasi_qualityLevel, max_qualityLevel, myShiTomasi_function );
  myShiTomasi_function( 0, 0 );
  waitKey(0);
  return(0);
}
void myShiTomasi_function( int, void* )
{
  myShiTomasi_copy = src.clone();
  if( myShiTomasi_qualityLevel < 1 ) { myShiTomasi_qualityLevel = 1; }
  for( int j = 0; j < src_gray.rows; j++ )
     { for( int i = 0; i < src_gray.cols; i++ )
          {
            if( myShiTomasi_dst.at<float>(j,i) > myShiTomasi_minVal + ( myShiTomasi_maxVal - myShiTomasi_minVal )*myShiTomasi_qualityLevel/max_qualityLevel )
              { circle( myShiTomasi_copy, Point(i,j), 4, Scalar( rng.uniform(0,255), rng.uniform(0,255), rng.uniform(0,255) ), -1, 8, 0 ); }
          }
     }
  imshow( myShiTomasi_window, myShiTomasi_copy );
}
void myHarris_function( int, void* )
{
  myHarris_copy = src.clone();
  if( myHarris_qualityLevel < 1 ) { myHarris_qualityLevel = 1; }
  for( int j = 0; j < src_gray.rows; j++ )
     { for( int i = 0; i < src_gray.cols; i++ )
          {
            if( Mc.at<float>(j,i) > myHarris_minVal + ( myHarris_maxVal - myHarris_minVal )*myHarris_qualityLevel/max_qualityLevel )
              { circle( myHarris_copy, Point(i,j), 4, Scalar( rng.uniform(0,255), rng.uniform(0,255), rng.uniform(0,255) ), -1, 8, 0 ); }
          }
     }
  imshow( myHarris_window, myHarris_copy );
}

亚像素级角点检测

代码语言:javascript
复制
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
using namespace cv;
using namespace std;
Mat src, src_gray;
int maxCorners = 10;
int maxTrackbar = 25;
RNG rng(12345);
const char* source_window = "Image";
void goodFeaturesToTrack_Demo( int, void* );
int main( int, char** argv )
{
  src = imread( argv[1], IMREAD_COLOR );
  cvtColor( src, src_gray, COLOR_BGR2GRAY );
  namedWindow( source_window, WINDOW_AUTOSIZE );
  createTrackbar( "Max  corners:", source_window, &maxCorners, maxTrackbar, goodFeaturesToTrack_Demo );
  imshow( source_window, src );
  goodFeaturesToTrack_Demo( 0, 0 );
  waitKey(0);
  return(0);
}
void goodFeaturesToTrack_Demo( int, void* )
{
  if( maxCorners < 1 ) { maxCorners = 1; }
  vector<Point2f> corners;
  double qualityLevel = 0.01;
  double minDistance = 10;
  int blockSize = 3, gradiantSize = 3;
  bool useHarrisDetector = false;
  double k = 0.04;
  Mat copy;
  copy = src.clone();
  goodFeaturesToTrack( src_gray,
               corners,
               maxCorners,
               qualityLevel,
               minDistance,
               Mat(),
               blockSize,
               gradiantSize,
               useHarrisDetector,
               k );
  cout<<"** Number of corners detected: "<<corners.size()<<endl;
  int r = 4;
  for( size_t i = 0; i < corners.size(); i++ )
     { circle( copy, corners[i], r, Scalar(rng.uniform(0,255), rng.uniform(0,255), rng.uniform(0,255)), -1, 8, 0 ); }
  namedWindow( source_window, WINDOW_AUTOSIZE );
  imshow( source_window, copy );
  Size winSize = Size( 5, 5 );
  Size zeroZone = Size( -1, -1 );
  TermCriteria criteria = TermCriteria( TermCriteria::EPS + TermCriteria::COUNT, 40, 0.001 );
  cornerSubPix( src_gray, corners, winSize, zeroZone, criteria );
  for( size_t i = 0; i < corners.size(); i++ )
     { cout<<" -- Refined Corner ["<<i<<"]  ("<<corners[i].x<<","<<corners[i].y<<")"<<endl; }
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019/11/17 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Shi-Tomasi
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档