【OpenCV图像处理】二十五、角点检测

博客参考 朱伟 等编著的《OpenCV图像处理编程实例》

======================================================================================

在很多应用场景中,图像像素区域的兴趣点区域对于目标检测、目标跟踪有着重要的意义。当兴趣点周围存在长方形区域时,最容易形成角点。

对于兴趣点检测,角点反映的是图像中局部最大值或最小值的孤立点,可以理解为区域邻域的小方块,存在于不同方向的主边缘处。窗口向任意方向的移动都会导致图像灰度的明显变化,形成的点集称为角点。

1. moravec角点

moravec角点是Moravec在1981年提出的角点检测算子,是最早的角点检测算法之一,常常应用于立体匹配。moravec角点的原理是通过滑动窗口像素变化来实现角点检测,首先计算窗口像素的兴趣值,也就是以当前像素为中心像素点,取一个正方形窗口ωxω(如ω=3),计算其0°,45°,90°和135°四个方向灰度差的平方和,取其中的最小值作为该兴趣点的兴趣值。

公式如下所示:

moravec角点检测器对每一个兴趣中心点进行滑窗遍历,计算其相关8-邻域方向的特征关系,窗口的变化可以取3x3,5x5或7x7,像素点位置变化可取

(u,v)={(-1,-1),(-1,0),(-1,-1),(0,-1),(0,1),(1,-1,),(1,0,),(1,-1)}。moravec角点检测与其他类型角点检测相比存在两个缺点

(1).非均匀性响应。窗口特性决定了在进行角点检测时,很容易受到邻近特性的影响,一般在实验操作前,先进行平滑操作。moravec角点检测算子对斜边缘的响应很强,这是因为只考虑了45°的方向变化,而没有在全部方向上考虑

(2).噪声响应。由于窗口函数是一个二值函数,不管像素离中心点的距离是多少,均赋予一样的权重,致使其对应的噪声也有很强的响应。moravec焦点检测对噪声十分敏感,一般在进行角点检测前,先对图像兴趣区域采用较大的窗口或先进行平滑操作。

moravec角点检测具体程序如下所示:

 

//moravec角点检测
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>

using namespace cv;
using namespace std;

//moravec角点检测函数
Mat MoravecCorners(Mat srcImage, int kSize, int threshlod);

int main()
{
	Mat srcImage = imread("2345.jpg", 0);
	if (!srcImage.data)
	{
		cout << "读入图片错误!" << endl;
		system("pause");
		return -1;
	}
	Mat resMorMat = MoravecCorners(srcImage, 5, 10000);
	imshow("原图像", srcImage);
	imshow("标记角点图像", resMorMat);

	waitKey();
	return 0;
}

Mat MoravecCorners(Mat srcImage, int kSize, int threshlod)
{
	Mat resMorMat = srcImage.clone();
	//获取初始化参数信息
	int r = kSize / 2;
	const int rowsNum = srcImage.rows;
	const int colsNum = srcImage.cols;
	int nCount = 0;
	CvPoint *pPoint = new CvPoint[rowsNum*colsNum];
	//图像遍历
	for (int i = r; i < srcImage.rows - r; i++)
	{
		for (int j = r; j < srcImage.cols - r; j++)
		{
			int wV1, wV2, wV3, wV4;
			wV1 = wV2 = wV3 = wV4 = 0;
			//计算水平方向窗内的兴趣值
			for (int k = -r; k < r; k++)
			{
				wV1 += (srcImage.at<uchar>(i, j + k) -
					srcImage.at<uchar>(i, j + k + 1))*(srcImage.at<uchar>
					(i, j + k) - srcImage.at<uchar>(i, j + k + 1));
			}
			//计算垂直方向窗内的兴趣值
			for (int k = -r; k < r; k++)
			{
				wV2 += (srcImage.at<uchar>(i + k, j) -
					srcImage.at<uchar>(i + k + 1, j))*(srcImage.at<uchar>
					(i + k, j) - srcImage.at<uchar>(i + k + 1, j));
			}
			//计算45°方向窗内的兴趣值
			for (int k = -r; k < r; k++)
			{
				wV3 += (srcImage.at<uchar>(i + k, j + k) -
					srcImage.at<uchar>(i + k + 1, j))*(srcImage.at<uchar>
					(i + k, j + k) - srcImage.at<uchar>(i + k + 1, j + k + 1));
			}
			//计算135°方向窗内的兴趣值
			for (int k = -r; k < r; k++)
			{
				wV4 += (srcImage.at<uchar>(i + k, j - k) -
					srcImage.at<uchar>(i + k + 1, j - k - 1))*(srcImage.at<uchar>
					(i + k, j - k) - srcImage.at<uchar>(i + k + 1, j - k - 1));
			}
			//取其中的最小值作为这个像素点的最终兴趣值
			int value = min(min(wV1, wV2), min(wV3, wV4));
			//若兴趣值大于阈值,则将点的坐标存入数组中
			if (value>threshlod)
			{
				pPoint[nCount] = cvPoint(j, 1);
				nCount++;
			}
		}
	}
	//绘制兴趣点
	for (int i = 0; i < nCount; i++)
	{
		circle(resMorMat, pPoint[i], 5, Scalar(255, 0, 0));
	}
	return resMorMat;
}


2.harris角点

 

harris角点在moravec角点的基础上进行了改进,对于比moravec角点的连续平方求和,它引入了局部变化因子,利用高斯权重函数特性进行角点检测。

对于图像f(x,y),任取窗口块W,进行评议Δx、Δy,考虑道具部特性变化,计算图像平移后窗口变化值之差的平方和如下:

考虑到响应特性,角点不会受到光圈参数的影响,对于Sw(Δi,Δj)中的高斯响应部分,利用泰勒展开,对于平移后的图像,可变换成下式:

将上式中f(i,j) - f(i-Δi,j-Δj)的近似值代入到Sw(i,j)中可以得到:

最后进行矩阵变换,可以得到:

其中a = fx·fx,b = c =fx·fy,d = fy·fy,对于局部微小移动量[u,v],窗口移动导致的图像灰度变化,实际可理解为矩阵变换中实对称矩阵M满足:

其中M为2x2矩阵,由图像导数可以得到参数对应于a,b,c,d。对于局部结构矩阵M代表的邻域,将实对称矩阵对角化处理后,对应项可以理解为旋转因子,经过对角化处理以后,根据两个正交方向的变化分量计算其相应的特征值λ1和λ2。如果两个热正值均较大而且数值相当,则图像窗口在所有方向上的移动都将产生明显的灰度变化,可判断其将形成角点。如果仅有一个特征值较高且远大于另一个特征值,则得到相应的边缘,其他情况可以得到稳定的区域。基于上述特征值特性,harris角点理论提出了相应的角点响应函数,如下所示:

其中k为常量因子,通常情况下取值为0.04~0.06,对图像窗口内数据进行求和加权,实际上可以更好的刻画窗口中心特性。harris角点在实践复杂度上更高于moravec角点,对噪声也十分敏感,也存在非均匀响应。尽管如此,harris角点检测器还是目前应用最广泛的检点检测器之一,它的检测率高,而且可以得到重复相应。

harris角点检测算法的实现步骤如下所示:

(1).利用水平与数值差分算子对图像进行卷积操作,计算得到相应的fx,fy,根据实对称矩阵M的组成,计算对应矩阵元素的值。

(2).利用高斯函数对矩阵M进行平滑操作,得到新的M矩阵,步骤1和2可以改变书序,也可以先对图像进行高斯滤波,再求相应方向上的梯度大小。

(3).对每一像素和给定的邻域窗口,计算局部特征结果矩阵M的特征值和相应函数C(i,j) = det(M) -k(trace(M))^2

(4).选取相应函数C的阈值,根据非极大值抑制原理,同事满足阈值及某邻域内的局部极大值为候选角点。

harris角点检测的具体代码如下所示:

 

#include <iostream>
#include <stdlib.h>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>

using namespace cv;
using namespace std;

//计算Harris角点的函数
void CornerHarris(const Mat& srcImage, Mat &result,
	int blocksize, int kSize, double k);

int main()
{
	Mat srcImage = imread("2345.jpg");
	if (!srcImage.data)
	{
		cout << "读入图片错误!" << endl;
		system("pause");
		return -1;
	}
	imshow("srcImage", srcImage);
	Mat grayImage, result;
	cvtColor(srcImage, grayImage, CV_BGR2GRAY);
	result = Mat::zeros(srcImage.size(), CV_32FC1);
	//角点检测参数
	int blocksize = 2;
	int aperatureSize = 3;
	double k = 0.04;
	//进行角点检测
	CornerHarris(grayImage, result, blocksize, aperatureSize, k);

	//矩阵进行归一化
	normalize(result, result, 0, 255, NORM_MINMAX,CV_32FC1,Mat());
	convertScaleAbs(result, result);
	//绘制角点检测结果
	for (int j = 0; j < result xss=removed>(j);
      for (int i = 0; i < result.cols; i++)
      {
       int a = ptr_rst[i];
       if (ptr_rst[i]<threshold)
       {
            circle(srcImage, Point(i, j), 5, Scalar(0,0,255), 2, 8, 0);
       }
      }    
	imshow("结果图", result);
	waitKey();
	return 0;
}

void CornerHarris(const Mat& srcImage, Mat &result, int blocksize, int kSize, double k)
{
	Mat src;
	srcImage.copyTo(src);
	result.create(srcImage.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 (int i = 0; i < size.height; i++)
	{
		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 (int j = 0; j < size.width; j++)
		{
			float dx_ = dxData[j];
			float dy_ = dyData[j];
			covData[3 * j] = dx_*dx_;
			covData[3 * j + 1] = dy_*dy_;
			covData[3 * j + 2] = dx_*dy_;
		}
	}
	//对图像进行boxfilter操作
	boxFilter(cov, cov, cov.depth(), Size(blocksize, blocksize),
		Point(-1, -1), false);	//最后一个参数false表示 滤波不进行归一化
	//判断图像的连续性
	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);
		}
	}
}

 

 

 

 

 

3.Shi-Tomasi角点

在1994年发表的论文“Good Features to Track”一文中基于harris原理提出了改进算法,采用和harris不同的角点响应函数,已知

harris角点响应函数定义为:

其中λ1和λ2分别为M矩阵的两个特征值,Shi-Tomasi角点检测则选取了不同的角点响应函数:

Shi-Tomasi角点检测划分相关一对一配对,依据图像的角点特征、图像灰度和位置信息,采用最大互相关函数进行相似度计算,最终实现角点检测。

在OpenCV中利用函数goodFeaturesToTrack()函数实现该角点的检测算法,下面首先简单介绍一下这个函数

函数原型为:

 

void goodFeaturesToTrack( InputArray image, OutputArray corners, int maxCorners, double qualityLevel, double minDistance,
                                     InputArray mask=noArray(), int blockSize=3,bool useHarrisDetector=false, double k=0.04 );

第一个参数image为单通道的图像,可以是8bit或32bit浮点型数据

 

第二个参数corners为检测到的角点输出矩阵

第三个参数maxCorners表示检测到的角点输出的最大数目

第四个参数qualityLevel表示可允许接受的角点最差质量,这个参数使用时将该数值乘以最佳角点的质量数值来删除角点

第五个参数minDistance表示返回角点间的最小的欧式距离,用于限定近邻像素被检测出角点的可能性

第六个参数mask表示可选参数为兴趣区域,若输入图像非空,它将被限定到检测到角点区域

第七个参数blockSize表示像素邻域中计算协方差矩阵的窗口的尺寸

第八个参数useHarrisDetector表示应用harris检测算法来确定,有默认参数false

第九个参数k表示一个经验参数,有默认值0.04

应用这个函数进行Shi-Tomasi角点检测的程序如下所示:

 

//实现Shi-Tomasi角点检测
#include <stdlib.h>
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>

using namespace cv;
using namespace std;

int main()
{
	Mat srcImage = imread("2345.jpg");
	if (!srcImage.data)
	{
		cout << "读入图片有误!" << endl;
		system("pause");
		return -1;
	}
	Mat srcGray;
	cvtColor(srcImage, srcGray, CV_BGR2GRAY);
	//设置角点检测参数
	vector<Point2f>vecCorners;
	//可允许接受的角点最差质量
	double qualityLevel = 0.01;
	//角点间最小的欧氏距离
	double minDistance = 10;
	//像素邻域中计算协方差矩阵窗口的尺寸
	int blockSize = 3;
	bool useHarrisDetector = false;
	double k = 0.04;
	//检测到的角点输出的最大数目
	int maxCorners = 50;
	int maxTrackbar = 100;
	Mat result = srcImage.clone();
	//调用函数进行Shi-Tomasi角点检测
	goodFeaturesToTrack(srcGray, vecCorners, maxCorners, qualityLevel,
		minDistance, Mat(), blockSize, useHarrisDetector, k);
	cout << "Corners:" << vecCorners.size() << endl;
	//绘制检测角点
	for (size_t i = 0; i < vecCorners.size(); i++)
	{
		circle(result, vecCorners[i], 4, Scalar(0, 0, 255), 2);
	}
	imshow("原图像", srcImage);
	imshow("结果图像", result);
	waitKey();
	return 0;
}

执行后结果如下所示:

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值