Opencv2.4学习::角点检测(二)harris角点

角点检测(二)


1、moravec角点

2、harris角点

3、


harris角点

本节内容是以moravec角点的检测原理为基础的,建议先去浏览。

原理部分

(1)先从moravec角点算法的兴趣值计算公式说起:

                   ①

(2)回顾泰勒公式

formula           ②

(3)将(1)中的公式①按二维下的泰勒展开,取近似一阶,有:

f(x+u,y+v)\approx f(x,y)+uf_{x}+vf_{y}                                                        ③

f_{x}指对x的偏导:对x求偏导

(4)将 ③代入①,以泰勒展开的一阶近似代替f(x+u,y+v),有:

E(u,v)=\sum _{x,y}^{} \cdot w(x,y)[f(x,y)+uf_{x}+vf_{y}-f(x,y)]^{2}

=\sum _{x,y}^{} \cdot w(x,y)[uf_{x}+vf_{y}]^{2}

=\begin{pmatrix}u & v \end{pmatrix}\sum _{x,y}^{} \cdot w(x,y)\begin{bmatrix} f_{x}f_{x}&f_{x}f_{y} \\ f_{x}f_{y}&f_{y}f_{y} \end{bmatrix} \binom{u}{v}

(5)从上面的推导中,可以看出,该式是二次型,因此有:

就是说,等式E(u,v)可化为对特征值*平方项的求和。(特征值\lambda1\lambda2指矩阵\sum _{x,y}^{} \cdot w(x,y)\begin{bmatrix} f_{x}f_{x}&f_{x}f_{y} \\ f_{x}f_{y}&f_{y}f_{y} \end{bmatrix}的特征值,该矩阵下面也称矩阵M)

而我们的目的是通过E(u,v)来判断某个像素点是否角点,那么问题转化为通过特征值(或者fx fy)来判断像素点是否角点

事实上,某像素点的E(u,v)的特征值\lambda1\lambda2与fx、fy以及该像素点是否角点的关系如下:(参考自:https://blog.csdn.net/linqianbi/article/details/78930239

有上面的实验数据可见,当偏导(对于图像来说应该是梯度)fx、fy的值都大时,该点有可能为角点,其对应的特征值\lambda1\lambda2也为较大

 (6)进一步简化问题:上面提到将问题转化为通过特征值(或者fx fy)来判断像素点是否角点,那么现在需要一个函数来联立这两个特征值\lambda1\lambda2,以简化问题

于是又提出了R的计算:

当R为较大的整数时,该点很有可能为角点。

基本步骤:

  •   1.利用Soble计算出XY方向的梯度值
  •   2.计算出Ix^2,Iy^2,Ix*Iy
  •   3.利用高斯函数对Ix^2,Iy^2,Ix*Iy进行滤波
  •   4.计算局部特征结果矩阵M的特征值和响应函数C(i,j)=Det(M)-k(trace(M))^2   (0.04<=k<=0.06)
  •   5.将计算出响应函数的值C进行非极大值抑制,滤除一些不是角点的点,同时要满足大于设定的阈值

测试代码:

#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
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<uchar>(j, i) > 130){
				circle(srcImg, Point(i, j), 5,
					Scalar(0,0,255), 2, 8, 0);
			}
		}
	}
	imshow("result", srcImg);
	waitKey(0);
}

测试效果:可以看到,测试效果比moravec角点好了不少,但是仔细看可以发现,在找到的角点附近有重复的圆,这是源代码缺少了非极大值抑制这一部分,下面给出优化的代码。

 

代码优化:【增加了非极大值抑制函数】

一、8邻域内非极大值抑制【这个不能处理相隔两个像素距离的角点同时被检测到的问题,处理方案见二】

#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
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<uchar>(i, j) > resultData.at<uchar>(i - 1, j - 1) &&
				resultData.at<uchar>(i, j) > resultData.at<uchar>(i - 1, j) &&
				resultData.at<uchar>(i, j) > resultData.at<uchar>(i - 1, j + 1) &&
				resultData.at<uchar>(i, j) > resultData.at<uchar>(i, j - 1) &&
				resultData.at<uchar>(i, j) > resultData.at<uchar>(i, j + 1) &&
				resultData.at<uchar>(i, j) > resultData.at<uchar>(i + 1, j - 1) &&
				resultData.at<uchar>(i, j) > resultData.at<uchar>(i + 1, j) &&
				resultData.at<uchar>(i, j) > resultData.at<uchar>(i + 1, j + 1))
			{
				if ((int)resultData.at<uchar>(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<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
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<uchar>(i, j) > resultData.at<uchar>(i - k, j - m) &&
							resultData.at<uchar>(i, j) > resultData.at<uchar>(i - k, j + m) &&
							resultData.at<uchar>(i, j) > resultData.at<uchar>(i + k, j - m) &&
							resultData.at<uchar>(i, j) > resultData.at<uchar>(i + k, j + m)){
							if (m == 0 || k == 0){
								count += 2;
							}
							else{
								count += 4;
							}
						}
					}
				}
				/*			if (resultData.at<uchar>(i, j) > resultData.at<uchar>(i - 1, j - 1) &&
								resultData.at<uchar>(i, j) > resultData.at<uchar>(i - 1, j) &&
								resultData.at<uchar>(i, j) > resultData.at<uchar>(i - 1, j + 1) &&
								resultData.at<uchar>(i, j) > resultData.at<uchar>(i, j - 1) &&
								resultData.at<uchar>(i, j) > resultData.at<uchar>(i, j + 1) &&
								resultData.at<uchar>(i, j) > resultData.at<uchar>(i + 1, j - 1) &&
								resultData.at<uchar>(i, j) > resultData.at<uchar>(i + 1, j) &&
								resultData.at<uchar>(i, j) > resultData.at<uchar>(i + 1, j + 1))*///判断当前像素的计算结果是8邻域内最大的
				if (count >= (kSize*kSize - 1))
				{
					if ((int)resultData.at<uchar>(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<uchar>(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(只能为奇数)】 

猜你喜欢

转载自blog.csdn.net/dieju8330/article/details/83623012