opencv学习(1)-- 角点检测

1.1 引言
角点检测是计算机视觉中获取图像特征的一种方式,也可以称为特征点检测。广泛地应用于图像对齐、图像匹配、目标识别、三维建模等领域。
角点通常定义为两条边的交点,更严格地说,角点的局部邻域应该具有两个不同区域的不同方向的边界。就是图像梯度在两个或多个方向上有变化,并且角点要有足够的可重复性和显著性。比如角点一定不会出现在直线上。如下图所示
1.2 Harris角点检测
1.2.1 角点检测算法思想
角点检测算法的具体思想是使用一个固定窗口在图像上进行任意方向上的滑动,比较滑动前后窗口区域的像素灰度值变化,如果在任意方向多存在较大的灰度变化,可认为该窗口中存在角点。
该公式表征的是窗口通过平移之后,滑窗内相应位置的像素值对应相减,然后与 卷积
(u,v)是窗口的平移量,
通常的窗口函数有以下两种
泰勒展开:
矩阵表示
角点响应:
,其中k是常量,一般取值为0.04~0.06,这个参数仅仅是这个函数的一个系数,它的存在只是调节函数的形状而已。
-
特征值都比较大时,即窗口中含有角点
-
特征值一个较大,一个较小,窗口中含有边缘
-
特征值都比较小,窗口处在平坦区域
具体的数学演算参考:参考1,参考2;
1.2.2 相关API
·void cornerHarris( InputArray src, OutputArray dst, int blockSize, int ksize, double k; int borderType=BORDER_DEFAULT)
参数 | 含义 | |
作用 | 获取角点图像 | |
输入 | src | 数据类型为 float32 的输入图像。(灰度图像,输入单通道图) |
dst | 输出图像 | |
blockSize | 角点检测中要考虑的领域大小。也就是计算协方差矩阵时的窗口大小 | |
ksize | Sobel求导中使用的窗口大小 | |
k | 计算角点响应时的参数大小 | |
borderType | 边界的类型 | |
返回值 | void | 无 |
1.2.3 算法步骤
- 获取灰度图像;
- 使用sobel等梯度算子计算每个像素点x/y向的梯度
- 计算每个像素点的梯度平方
;
- 计算梯度在每个像素点的和;
- 构建M矩阵并计算角点响应;
- 设置阈值找出可能点并进行非极大值抑制,得到角点;
1.2.4 代码示例
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
Mat src, gray;
int thresh = 145;
int threshMax = 255;
const char* outputWin = "HarrisCorner";
void HarrisCallback(int,void*)
{
Mat HarrisDst, normDst,normScaleDst;
HarrisDst = Mat::zeros(src.size(),CV_32FC1);
int blockSize = 2;
int ksize = 3;
double k = 0.04;
cornerHarris(gray, HarrisDst,2,3,0.04);
normalize(HarrisDst, normDst,0,255,NORM_MINMAX,CV_32FC1,Mat());
//取绝对值
convertScaleAbs(normDst, normScaleDst);
//根据阈值选择角点
Mat resImg = src.clone();
for (int i = 0; i < resImg.rows; i++)
{
uchar* currentRow = normScaleDst.ptr(i);
for (int j = 0; j < resImg.cols; j++)
{
int value = (int)*currentRow;
if(value>thresh)
{
circle(resImg,Point(j,i),2,Scalar(0,0,255),2);
}
else;
currentRow++;
}
}
imshow(outputWin, resImg);
}
int main(int argc, char**argv)
{
src = imread("D:/testimg/CSet12/House.png");
if (src.empty())
{
cout << "the srcImg could not get" << endl;
return -1;
}
namedWindow("input",WINDOW_AUTOSIZE);
imshow("input",src);
namedWindow(outputWin, WINDOW_AUTOSIZE);
cvtColor(src,gray,COLOR_BGR2GRAY);
createTrackbar("Threshold",outputWin,&thresh,threshMax, HarrisCallback);
HarrisCallback(0,0);
waitKey(0);
return 0;
}
1.3 Shi-Tomasi角点检测
1.3.1 算法思想
跟Harris角点检测的理论基本一致,只是在使用矩阵特征值计算角度响应的时候有所不同,其其角度响应公式如下:
1.3.2 相关API
·void goodFeaturesToTrack( InputArray image, OutputArray corners, int maxCorners,double qualityLevel,double minDistance,InputArray mask=noArray(), int blockSize=3,bool usrHarrisDetector=false,duoble harrisk=0.04)
参数 | 含义 | |
作用 | 获取图像角点 | |
输入 | image | 8位或32位浮点型输入图像,单通道 |
corners | 检测出的角点 | |
maxCorners | 角点数目最大值,如果实际检测的角点超过此值,则只返回前maxCorners个强角点 | |
qualityLevel | 角点的品质因子,表示最小可接受的向量值 | |
minDistance | 两个角点之间最小的距离 | |
mask | 指定感兴趣区,如不需在整幅图上寻找角点,则用此参数指定ROI | |
blockSize | 角点检测中要考虑的领域大小。也就是计算协方差矩阵时的窗口大小 | |
usrHarrisDetector | 指示是否使用Harris角点检测,如不指定,则计算shi-tomasi角点 | |
harrisk | Harris角点检测需要的k值 | |
返回值 | void | 无 |
1.3.3 代码示例
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
Mat src, gray;
int cornerNum = 20;
int cornerMax = 200;
const char* outputWin = "ShiTomasi";
void ShiTomasiCallback(int,void*)
{
vector<Point2f> cornerVec;
goodFeaturesToTrack(gray, cornerVec, cornerNum,0.01,8,Mat(),3,false,0.04);
//根据阈值选择角点
Mat resImg = src.clone();
for (int i = 0; i < cornerVec.size(); i++)
{
circle(resImg, cornerVec[i], 2, Scalar(0, 0, 255), 2);
}
imshow(outputWin, resImg);
}
int main(int argc, char**argv)
{
src = imread("D:/testimg/CSet12/House.png");
if (src.empty())
{
cout << "the srcImg could not get" << endl;
return -1;
}
namedWindow("input",WINDOW_AUTOSIZE);
imshow("input",src);
namedWindow(outputWin, WINDOW_AUTOSIZE);
cvtColor(src,gray,COLOR_BGR2GRAY);
createTrackbar("Threshold",outputWin,&cornerNum, cornerMax, ShiTomasiCallback);
ShiTomasiCallback(0, 0);
waitKey(0);
return 0;
}
1.4 自定义角点检测器
1.4.1 概述
自定义角点检测是基于Harris和Shi-Tomasi角点检测,首先通过计算矩阵M得到两个特征值而获取角点响应值。然后自己设置阈值实现计算出阈值得到有效响应值的角点位置。
1.4.2 相关API
·void cornerEigenValsAndVecs( InputArray src, OutputArray dst, int blockSize=3,int ksize,int borderType=BORDER_DEFAULT)
EigenVals--
,
Vecs
参数 | 含义 | |
作用 | 通过计算自相关矩阵M求出其特征值 | |
输入 | src | 数据类型为 float32 的输入图像。(灰度图像,输入单通道图) |
dst | 输出图像(宽度为输入图像的6倍) | |
blockSize | 角点检测中要考虑的领域大小。也就是计算协方差矩阵时的窗口大小 | |
ksize | 梯度算子的核尺寸大小 | |
borderType | 边界的类型 | |
返回值 | void | 无 |
·void cornerMinEigenVal( InputArray src, OutputArray dst, int blockSize=3,int ksize=3,int borderType=BORDER_DEFAULT)
参数 | 含义 | |
作用 | 通过计算自相关矩阵M获取最小特征值,用于Shi-Tomasi | |
输入 | src | 数据类型为 float32 的输入图像。(灰度图像,输入单通道图) |
dst | 输出图像(大小同输入图像) | |
blockSize | 角点检测中要考虑的领域大小。也就是计算协方差矩阵时的窗口大小 | |
ksize | 梯度算子的核尺寸大小 | |
borderType | 边界的类型 | |
返回值 | void | 无 |
1.4.3 代码示例
* 自定义Harris检测
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
using namespace std;
Mat src, gray;
double RMin,RMax;
int qualityLevel = 30;
int countMax = 100;
Mat cornerEigenR;
const char* outputWin = "ShiTomasi";
void ThresholdCallback(int,void*)
{
if (qualityLevel < 10)
{
qualityLevel = 10;
}
else;
//根据阈值选择角点
Mat resImg = src.clone();
float vTemp = RMin + (((double)qualityLevel)/countMax)*(RMax-RMin);
for (int i = 0; i < src.rows; i++)
{
for (int j = 0; j < src.cols; j++)
{
float value = cornerEigenR.at<float>(i,j);
if (value > vTemp)
{
circle(resImg,Point(j,i),2,Scalar(0,0,255),2,8,0);
}
}
}
imshow(outputWin, resImg);
}
int main(int argc, char**argv)
{
src = imread("D:/testimg/CSet12/House.png");
if (src.empty())
{
cout << "the srcImg could not get" << endl;
return -1;
}
namedWindow("input",WINDOW_AUTOSIZE);
imshow("input",src);
namedWindow(outputWin, WINDOW_AUTOSIZE);
cvtColor(src,gray,COLOR_BGR2GRAY);
//计算特征值
int blockSize = 3;
int ksize = 3;
Mat cornerEigen;
cornerEigen = Mat::zeros(src.size(),CV_32FC(6));
cornerEigenValsAndVecs(gray, cornerEigen,blockSize,ksize);
//计算角点响应R=λ1*λ2-k(λ1+λ2)^2
double k = 0.04;
cornerEigenR = Mat::zeros(src.size(),CV_32FC1);
for (int i = 0; i < cornerEigen.rows; i++)
{
for (int j = 0; j < cornerEigen.cols; j++)
{
double lambda1 = cornerEigen.at<Vec6f>(i,j)[0];
double lambda2 = cornerEigen.at<Vec6f>(i, j)[1];
cornerEigenR.at<float>(i, j) = lambda1*lambda2 - k*pow((lambda1+ lambda2),2);
}
}
minMaxLoc(cornerEigenR,&RMin,&RMax,0,0,Mat());
createTrackbar("Threshold",outputWin,&qualityLevel, countMax, ThresholdCallback);
ThresholdCallback(0, 0);
waitKey(0);
return 0;
}
* 自定义Shi-Tomasi检测
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
using namespace std;
Mat src, gray;
double RMin,RMax;
int qualityLevel = 30;
int countMax = 100;
Mat cornerMin;
const char* outputWin = "ShiTomasi";
void ThresholdCallback(int,void*)
{
if (qualityLevel <5)
{
qualityLevel = 5;
}
else;
//根据阈值选择角点
Mat resImg = src.clone();
float vTemp = RMin + (((double)qualityLevel)/countMax)*(RMax-RMin);
for (int i = 0; i < src.rows; i++)
{
for (int j = 0; j < src.cols; j++)
{
float value = cornerMin.at<float>(i,j);
if (value > vTemp)
{
circle(resImg,Point(j,i),2,Scalar(0,0,255),2,8,0);
}
}
}
imshow(outputWin,resImg);
}
int main(int argc, char**argv)
{
src = imread("D:/testimg/CSet12/House.png");
if (src.empty())
{
cout << "the srcImg could not get" << endl;
return -1;
}
namedWindow("input",WINDOW_AUTOSIZE);
imshow("input",src);
namedWindow(outputWin, WINDOW_AUTOSIZE);
cvtColor(src,gray,COLOR_BGR2GRAY);
//计算特征值
int blockSize = 3;
int ksize = 3;
//计算最小特征值
cornerMin = Mat::zeros(src.size(), CV_32FC1);
cornerMinEigenVal(gray, cornerMin, blockSize, ksize,4);
//获取这个图的特征值最大最小值
minMaxLoc(cornerMin,&RMin,&RMax,0,0,Mat());
createTrackbar("quality",outputWin,&qualityLevel, countMax, ThresholdCallback);
ThresholdCallback(0, 0);
waitKey(0);
return 0;
}
1.5 亚像素级角点检测
1.5.1 概述
通过进行亚像素级别的角点检测可以提高角点的检测精准度,因为角点的位置不会是一个准确的像素点。可以通过插值算法、基于图像矩计算、曲线拟合(高斯曲面、多项式、椭圆曲面)
1.5.2 相关API
·void cornerSubPix( InputArray image, InputOutputArray corners, Size winSize,Size zeroZone,TermCirteria criteria)
参数 | 含义 | |
作用 | 对检测到的角点作进一步的优化计算,使角点的精度达到亚像素级别 | |
输入 | image | 输入图像 |
corners | 角点(输入检测到的角点-优化后的角点输出) | |
winSize | 计算亚像素角点时考虑的区域的大小 | |
zeroZone | 类似于winSize,但是总是具有较小的范围,通常忽略(即Size(-1, -1)) | |
criteria | 计算亚像素时停止迭代的标准,可选的值有TermCriteria::MAX_ITER 、TermCriteria::EPS(可以是两者其一,或两者均选),前者表示迭代次数达到了最大次数时停止,后者表示角点位置变化的最小值已经达到最小时停止迭代。二者均使用cv::TermCriteria()构造函数进行指定。 | |
返回值 | void | 无 |
1.5.3 代码示例
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
using namespace std;
Mat src, gray;
int cornerNum = 30;
int cornerMax = 80;
const char* outputWin = "SubPixel Corner";
void SubpixelCallback(int,void*)
{
if (cornerNum <5)
{
cornerNum = 5;
}
else;
//利用Shi-Tomasi获取角点
vector<Point2f> cornerVec;
int blockSize = 3;
int ksize = 3;
double qulitylevel = 0.01;
double mindistance = 8;
goodFeaturesToTrack(gray, cornerVec, cornerNum, qulitylevel, mindistance,Mat(),3,false,0.04);
printf("corners num = %d\n", cornerVec.size());
Mat resImg = src.clone();
for (int i = 0; i < cornerVec.size(); i++)
{
circle(resImg, cornerVec[i], 2, Scalar(0, 0, 255), 2, 8, 0);
}
//拟合找到亚像素位置
Size winSize = Size(5, 5);
Size zerozone = Size(-1,-1);
//迭代求解停止条件
TermCriteria tc = TermCriteria(TermCriteria::EPS+ TermCriteria::MAX_ITER,40,0.001);
cornerSubPix(gray,cornerVec,winSize,zerozone,tc);
for (int i = 0; i < cornerVec.size(); i++)
{
printf("%d Point: x: %f, y: %f\n", i,cornerVec[i].x, cornerVec[i].y);
}
imshow(outputWin,resImg);
}
int main(int argc, char**argv)
{
src = imread("D:/testimg/CSet12/House.png");
if (src.empty())
{
cout << "the srcImg could not get" << endl;
return -1;
}
namedWindow("input",WINDOW_AUTOSIZE);
imshow("input",src);
namedWindow(outputWin, WINDOW_AUTOSIZE);
cvtColor(src,gray,COLOR_BGR2GRAY);
createTrackbar("cornerNum",outputWin,&cornerNum, cornerMax, SubpixelCallback);
SubpixelCallback(0, 0);
waitKey(0);
return 0;
}
亚像素检测到的角点位置
参考:




更多推荐
所有评论(0)