1、moravec角点
2、harris角点
3、
本节内容是以moravec角点的检测原理为基础的,建议先去浏览。
(1)先从moravec角点算法的兴趣值计算公式说起:
(2)回顾泰勒公式
(3)将(1)中的公式①按二维下的泰勒展开,取近似一阶,有:
③
:对x求偏导
(4)将 ③代入①,以泰勒展开的一阶近似代替f(x+u,y+v),有:
(5)从上面的推导中,可以看出,该式是二次型,因此有:
就是说,等式E(u,v)可化为对特征值*平方项的求和。(特征值、指矩阵的特征值,该矩阵下面也称矩阵M)
而我们的目的是通过E(u,v)来判断某个像素点是否角点,那么问题转化为通过特征值(或者fx fy)来判断像素点是否角点
事实上,某像素点的E(u,v)的特征值、与fx、fy以及该像素点是否角点的关系如下:(参考自:https://blog.csdn.net/linqianbi/article/details/78930239)
有上面的实验数据可见,当偏导(对于图像来说应该是梯度)fx、fy的值都大时,该点有可能为角点,其对应的特征值、也为较大
(6)进一步简化问题:上面提到将问题转化为通过特征值(或者fx fy)来判断像素点是否角点,那么现在需要一个函数来联立这两个特征值、,以简化问题
于是又提出了R的计算:
当R为较大的整数时,该点很有可能为角点。
#include
#include
#include
#include
#include
using namespace std;
using namespace cv;
//计算harris角点
void CornerHarris(Mat &srcImage, Mat &result,
int blockSize, int kSize, double k)
{
Mat src;
srcImage.copyTo(src);//浅复制
result.create(src.size(), CV_32F);
int depth = src.depth();
//检测掩膜尺寸
double scale = (double)(1 << ((kSize > 0 ? kSize : 3)
- 1))*blockSize;
if (depth == CV_8U)
scale *= 255.;
scale = 1. / scale;
//sobel
Mat dx, dy;
Sobel(src, dx, CV_32F, 1, 0, kSize, scale, 0);
Sobel(src, dy, CV_32F, 0, 1, kSize, scale, 0);
Size size = src.size();
Mat cov(size, CV_32FC3);
int i, j;
//求水平与竖直梯度
for (i = 0; i < size.height; i++){
//covData指针指向cov矩阵对应行的开头
float *covData = (float*)(cov.data + i*cov.step);
const float *dxData = (const float*)(dx.data +
i*dx.step);
const float *dyData = (const float*)(dy.data +
i*dy.step);
for (j = 0; j < size.width; j++){
float dx_ = dxData[j];
float dy_ = dyData[j];
covData[3 * j] = dx_*dx_;
covData[3 * j + 1] = dx_*dy_;
covData[3 * j + 2] = dy_*dy_;
}
}
//对图像进行盒滤波
boxFilter(cov, cov, cov.depth(),
Size(blockSize, blockSize), Point(-1, -1), false);
/*观察一下cov矩阵的内容*/
//Mat cov_result;
//normalize(cov, cov_result, 0, 255, NORM_MINMAX,
// CV_32FC1, Mat());
//convertScaleAbs(cov_result, cov_result);
//imshow("cov", cov_result);
//判断图像连续性
if (cov.isContinuous() && result.isContinuous()){
size.width *= size.height;
size.height = 1;
}
else{
size = result.size();
}
//计算响应函数
for (i = 0; i < size.height; i++){
//获取图像矩阵指针
float *resultData = (float*)(result.data + i*result.step);
const float *covData = (const float*)(cov.data + i*cov.step);
for (j = 0; j < size.width; j++){
//角点响应生成
float a = covData[3 * j];
float b = covData[3 * j + 1];
float c = covData[3 * j + 2];
resultData[j] = a*c - b*b - k*(a + c)*(a + c);
}
}
}
void main()
{
Mat srcImg = imread("F:\\opencv_re_learn\\flash.jpg");
if (!srcImg.data){
cout << "failed to read" << endl;
system("pause");
return;
}
imshow("src", srcImg);
Mat srcGray, result;
cvtColor(srcImg, srcGray, CV_BGR2GRAY);
result = Mat::zeros(srcImg.size(), CV_32FC1);
//角点检测参数
int blockSize = 2;
int apertureSize = 3;
double k = 0.04;
//角点检测
CornerHarris(srcGray, result, blockSize, apertureSize, k);
//矩阵归一化
normalize(result, result, 0, 255, NORM_MINMAX,
CV_32FC1, Mat());
convertScaleAbs(result, result);
//绘制角点检测结果
for (int j = 0; j < result.rows; j++){
for (int i = 0; i < result.cols; i++){
if ((int)result.at(j, i) > 130){
circle(srcImg, Point(i, j), 5,
Scalar(0,0,255), 2, 8, 0);
}
}
}
imshow("result", srcImg);
waitKey(0);
}
测试效果:可以看到,测试效果比moravec角点好了不少,但是仔细看可以发现,在找到的角点附近有重复的圆,这是源代码缺少了非极大值抑制这一部分,下面给出优化的代码。
一、8邻域内非极大值抑制【这个不能处理相隔两个像素距离的角点同时被检测到的问题,处理方案见二】
#include
#include
#include
#include
#include
using namespace std;
using namespace cv;
//计算harris角点
void CornerHarris(Mat &srcImage, Mat &result,
int blockSize, int kSize, double k)
{
Mat src;
srcImage.copyTo(src);//浅复制
result.create(src.size(), CV_32F);
int depth = src.depth();
//检测掩膜尺寸
double scale = (double)(1 << ((kSize > 0 ? kSize : 3)
- 1))*blockSize;
if (depth == CV_8U)
scale *= 255.;
scale = 1. / scale;
//sobel
Mat dx, dy;
Sobel(src, dx, CV_32F, 1, 0, kSize, scale, 0);
Sobel(src, dy, CV_32F, 0, 1, kSize, scale, 0);
Size size = src.size();
Mat cov(size, CV_32FC3);
int i, j;
//求水平与竖直梯度
for (i = 0; i < size.height; i++){
//covData指针指向cov矩阵对应行的开头
float *covData = (float*)(cov.data + i*cov.step);
const float *dxData = (const float*)(dx.data +
i*dx.step);
const float *dyData = (const float*)(dy.data +
i*dy.step);
for (j = 0; j < size.width; j++){
float dx_ = dxData[j];
float dy_ = dyData[j];
covData[3 * j] = dx_*dx_;
covData[3 * j + 1] = dx_*dy_;
covData[3 * j + 2] = dy_*dy_;
}
}
//对图像进行盒滤波
boxFilter(cov, cov, cov.depth(),
Size(blockSize, blockSize), Point(-1, -1), false);
/*观察一下cov矩阵的内容*/
//Mat cov_result;
//normalize(cov, cov_result, 0, 255, NORM_MINMAX,
// CV_32FC1, Mat());
//convertScaleAbs(cov_result, cov_result);
//imshow("cov", cov_result);
//判断图像连续性
if (cov.isContinuous() && result.isContinuous()){
size.width *= size.height;
size.height = 1;
}
else{
size = result.size();
}
//计算响应函数
for (i = 0; i < size.height; i++){
//获取图像矩阵指针
float *resultData = (float*)(result.data + i*result.step);
const float *covData = (const float*)(cov.data + i*cov.step);
for (j = 0; j < size.width; j++){
//角点响应生成
float a = covData[3 * j];
float b = covData[3 * j + 1];
float c = covData[3 * j + 2];
resultData[j] = a*c - b*b - k*(a + c)*(a + c);
}
}
}
//非极大值抑制
void LocalMaxValue(Mat &resultData, Mat &srcGray, Mat &ResultImage, int kSize)
{
int r = kSize / 2;
ResultImage = srcGray.clone();
for (int i = r; i < ResultImage.rows - r; i++)
{
for (int j = r; j < ResultImage.cols - r; j++)
{
if (resultData.at(i, j) > resultData.at(i - 1, j - 1) &&
resultData.at(i, j) > resultData.at(i - 1, j) &&
resultData.at(i, j) > resultData.at(i - 1, j + 1) &&
resultData.at(i, j) > resultData.at(i, j - 1) &&
resultData.at(i, j) > resultData.at(i, j + 1) &&
resultData.at(i, j) > resultData.at(i + 1, j - 1) &&
resultData.at(i, j) > resultData.at(i + 1, j) &&
resultData.at(i, j) > resultData.at(i + 1, j + 1))
{
if ((int)resultData.at(i, j) > 130)
{
circle(srcGray, Point(j, i), 5, Scalar(0, 0, 255), 2, 8, 0);
}
}
}
}
}
void main()
{
Mat srcImg = imread("F:\\opencv_re_learn\\flash.jpg");
if (!srcImg.data){
cout << "failed to read" << endl;
system("pause");
return;
}
imshow("src", srcImg);
Mat srcGray, result;
cvtColor(srcImg, srcGray, CV_BGR2GRAY);
result = Mat::zeros(srcImg.size(), CV_32FC1);
//角点检测参数
int blockSize = 2;
int apertureSize = 3;
double k = 0.04;
//角点检测
CornerHarris(srcGray, result, blockSize, apertureSize, k);
//矩阵归一化
normalize(result, result, 0, 255, NORM_MINMAX,
CV_32FC1, Mat());
convertScaleAbs(result, result);
//绘制角点检测结果
LocalMaxValue(result, srcImg, srcImg, 3);
imshow("result", srcImg);
waitKey(0);
}
实现效果:
二、以当前像素为中心点的指定大小窗口的非极大值抑制【处理速度会随窗口增大下降】
【这里的非极大值抑制函数与上面不同】
#include
#include
#include
#include
#include
using namespace std;
using namespace cv;
//计算harris角点
void CornerHarris(Mat &srcImage, Mat &result,
int blockSize, int kSize, double k)
{
Mat src;
srcImage.copyTo(src);//浅复制
result.create(src.size(), CV_32F);
int depth = src.depth();
//检测掩膜尺寸
double scale = (double)(1 << ((kSize > 0 ? kSize : 3)
- 1))*blockSize;
if (depth == CV_8U)
scale *= 255.;
scale = 1. / scale;
//sobel
Mat dx, dy;
Sobel(src, dx, CV_32F, 1, 0, kSize, scale, 0);
Sobel(src, dy, CV_32F, 0, 1, kSize, scale, 0);
Size size = src.size();
Mat cov(size, CV_32FC3);
int i, j;
//求水平与竖直梯度
for (i = 0; i < size.height; i++){
//covData指针指向cov矩阵对应行的开头
float *covData = (float*)(cov.data + i*cov.step);
const float *dxData = (const float*)(dx.data +
i*dx.step);
const float *dyData = (const float*)(dy.data +
i*dy.step);
for (j = 0; j < size.width; j++){
float dx_ = dxData[j];
float dy_ = dyData[j];
covData[3 * j] = dx_*dx_;
covData[3 * j + 1] = dx_*dy_;
covData[3 * j + 2] = dy_*dy_;
}
}
//对图像进行盒滤波
boxFilter(cov, cov, cov.depth(),
Size(blockSize, blockSize), Point(-1, -1), false);
/*观察一下cov矩阵的内容*/
//Mat cov_result;
//normalize(cov, cov_result, 0, 255, NORM_MINMAX,
// CV_32FC1, Mat());
//convertScaleAbs(cov_result, cov_result);
//imshow("cov", cov_result);
//判断图像连续性
if (cov.isContinuous() && result.isContinuous()){
size.width *= size.height;
size.height = 1;
}
else{
size = result.size();
}
//计算响应函数
for (i = 0; i < size.height; i++){
//获取图像矩阵指针
float *resultData = (float*)(result.data + i*result.step);
const float *covData = (const float*)(cov.data + i*cov.step);
for (j = 0; j < size.width; j++){
//角点响应生成
float a = covData[3 * j];
float b = covData[3 * j + 1];
float c = covData[3 * j + 2];
resultData[j] = a*c - b*b - k*(a + c)*(a + c);
}
}
}
//非极大值抑制
void LocalMaxValue(Mat &resultData, Mat &srcGray, Mat &ResultImage, int kSize)
{
if (kSize % 2 != 0){
int r = kSize / 2;
ResultImage = srcGray.clone();
for (int i = r; i < ResultImage.rows - r; i++)
{
for (int j = r; j < ResultImage.cols - r; j++)
{
int count = 0;
for (int k = r; k >= 0; k--){
for (int m = r; m >= 0; m--){
if (resultData.at(i, j) > resultData.at(i - k, j - m) &&
resultData.at(i, j) > resultData.at(i - k, j + m) &&
resultData.at(i, j) > resultData.at(i + k, j - m) &&
resultData.at(i, j) > resultData.at(i + k, j + m)){
if (m == 0 || k == 0){
count += 2;
}
else{
count += 4;
}
}
}
}
/* if (resultData.at(i, j) > resultData.at(i - 1, j - 1) &&
resultData.at(i, j) > resultData.at(i - 1, j) &&
resultData.at(i, j) > resultData.at(i - 1, j + 1) &&
resultData.at(i, j) > resultData.at(i, j - 1) &&
resultData.at(i, j) > resultData.at(i, j + 1) &&
resultData.at(i, j) > resultData.at(i + 1, j - 1) &&
resultData.at(i, j) > resultData.at(i + 1, j) &&
resultData.at(i, j) > resultData.at(i + 1, j + 1))*///判断当前像素的计算结果是8邻域内最大的
if (count >= (kSize*kSize - 1))
{
if ((int)resultData.at(i, j) > 110)
{
circle(srcGray, Point(j, i), 5, Scalar(0, 0, 255), 2, 8, 0);
}
}
}
}
}
else{
cout << "Ksize must be odd number" << endl;
}
}
void main()
{
Mat srcImg = imread("F:\\opencv_re_learn\\flash.jpg");
if (!srcImg.data){
cout << "failed to read" << endl;
system("pause");
return;
}
imshow("src", srcImg);
Mat srcGray, result;
cvtColor(srcImg, srcGray, CV_BGR2GRAY);
result = Mat::zeros(srcImg.size(), CV_32FC1);
//角点检测参数
int blockSize = 3;
int apertureSize = 5;
double k = 0.04;
//角点检测
CornerHarris(srcGray, result, blockSize, apertureSize, k);
//矩阵归一化
normalize(result, result, 0, 255, NORM_MINMAX,
CV_32FC1, Mat());
convertScaleAbs(result, result);
//绘制角点检测结果
//for (int j = 0; j < result.rows; j++){
// for (int i = 0; i < result.cols; i++){
// if ((int)result.at(j, i) > 110){
// circle(srcImg, Point(i, j), 5,
// Scalar(0,0,255), 2, 8, 0);
// }
// }
//}
LocalMaxValue(result, srcImg, srcImg, 15);//只能为奇数
imshow("result", srcImg);
waitKey(0);
}
结果:【左侧为不使用非极大值抑制的效果】【右侧为使用非极大值抑制的效果,指定窗口15*15(只能为奇数)】