一、相关原理
Moravec是最早的角点检测算法之一,它将角点定义为具有低“自相关性”的点。针对图像的每个像素,算法将像素周边的一个邻域作为一个patch,并检测这个patch和周围其他patch的相关性。这种相关性通过两个patch间的SSD(平方差之和)来衡量,SSD值越小则相似性越高。
一幅图像可分为区域、边缘和角点三个部分。如图1所示,如果像素位于平滑图像区域内,周围的patch都会非常相似。如果像素在边缘上,则周围的patch在与边缘正交的方向上会有很大差异,在与边缘平行的方向上则较为相似。而如果像素是各个方向上都有变化的特征点,则周围所有的patch都不会很相似。Moravec计算每个像素patch和周围patch的SSD最小值作为强度值,取局部强度最大的点作为角点。
图1
首先,计算每个像素点的兴趣值,即以该像素点为中心,取一个w*w(如:5x5)的方形窗口,计算0度、45度、90度、135度四个方向灰度差的平方和,取其中的最小值作为该像素点的兴趣值。
(1.1)
如图2所示,以3x3窗口为例,黑色窗口为I(x,y),红色窗口为I(x+u,y+v),其中四种移位(u,v)= (1,0), (1,1), (0,1), (-1, 1)。w(x,y)为方形二值窗口,若像素点在窗口内,则取值为1,否则为0。
图2
然后,使用非极大值抑制找到局部最大值(此时局部窗口大小可另行设定),将局部最大值与设定的阈值比较,最终确定角点。
此外,度量图像块A与图像块B之间的差异性(或相似性)有以下几种方法:
1)SAD(Sum of Absolute Difference),即两个小块的差的绝对值之和:
(1.2)
2)SSD(Sum of Squared Distance),即两个小块之间差的平方和:
(1.3)
3)NCC(Normalized Cross Correlation,归一化互相关),这种方式比前两种方法要复杂,它计算两个小块的相关性,所以相关性接近0表示不相似,而接近1表示相似。前两种距离形式恰好相反,距离越小越相似。
(1.4)
二、基本步骤
输入:源单通道图像、窗口大小、阈值
输出:角点
算法具体步骤:
1)对于每个像素点,计算E(u,v)的值。如果是3*3,(u,v)的值为4种情况,(1,0),(1,1),(0,1),(-1,1);如果是5*5的窗口,(u,v)的取值为8种情况,(1,0),(1,1),(0,1),(-1,1),(-1,0),(-1,-1),(0,-1),(1,-1)。
2)计算E(u,v)的最小值minValue;
3)对于每个位置minValue进行判断,是不是大于设定的阈值,如果大于设定的阈值,就进行非局部极大值抑制,判断是不是局部极大值。如果是局部极大值则为角点,否者不是角点。
算法结束。
图3 算法流程图
注:为什么要进行非局部极大值抑制?因为在一个窗口内,中心点附近的值也可能大于阈值,导致角点聚集现象。所以要在这个窗口内排除这些值,确定局部的最大值,而进行非局部极大值抑制。
三、缺点分析
1、对边缘点的反应比较强烈
强度值的计算并不是各向同性的(各向异性),只有离散的8个角度方向被考虑。moravec算法对角点的定义是:窗口在各个方向的移动,窗口内的灰度值都会产生较大的变化。而其实这里的“各个方向”,最多也就只有8个方向。所以,如果边缘的方向是这8个方向以外的方向,那么,就会被认为是角点。
图4
2、不具备旋转不变性
因为moravec算法只计算最多八个方向的灰度值差的平方和,所以可能会出现原来的一条边缘在这8个方向之上而旋转之后却不在了,也有可能是相反的情况。这种旋转不变性进一步抑制了moravec算法的实用性。
3、窗口是方形且是二元的
在moravec中,使用的滑动窗口是正方形的、以及窗内的各个像素权重同质性(应该是中心像素权重大,距离中心越远,权重越小),并且窗口值是二元的,在窗口内,窗口值是1,在窗口外,窗口值是0。为了达到精确估计局部灰度值的变化程度,圆形的窗口才是理想的。圆形的窗口,使得中心点到窗口的每一个边缘点的欧式距离基本是相等的,以及赋予离中心更近的点更大的权重。
4、离散点(噪声点)与角点有相同的角点性(cornerness),因此Moravec算子对噪声敏感,但是通过增大滑动窗口的大小可以对噪声起到一定的抑制作用,同时增加了计算量。另一方面,通过设定一个阈值T来对cornerness map进行二值化,小于阈值T的cornerness map设置为0,从而对离散点的局部极大值进行抑制。
5、对于图像的一些边界区域像素,没有计算E(u,v)值。
四、代码分析
注意在实际使用时还要进行非局部极大值抑制,本代码未实现。
/**********************************************************************************
*函数 Mat MoravecCorners(cv::Mat SrcImage, int kSize, int threshold)
*输入:
*SrcImage : 单通道图像
*kSize : 窗口尺寸
*threshold : 阈值
*输出
*MorImage : 提取到角点的图像
***************************************************************************************/
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace std;
using namespace cv;
Mat MoravecCorners(cv::Mat SrcImage, int kSize, int threshold)
{
Mat MorImage = SrcImage.clone();
// 获取初始化参数信息
int r = kSize / 2;
const int nRows = SrcImage.rows;
const int nCols = SrcImage.cols;
int nConut = 0;
CvPoint *pPoint = new CvPoint[nRows*nCols];
// 图像遍历
for (int y = r; y < SrcImage.rows - r; y++)//行(y轴--v轴)
{
for (int x = r; x<SrcImage.cols - r; x++)//列(x轴--u轴)
{
int wV1, wV2, wV3, wV4;
wV1 = wV2 = wV3 = wV4 = 0;
// 计算水平(0度)方向窗内兴趣值
for (int k = -r; k < r; k++)//k=-2,-1,0,1
wV1 += (SrcImage.at<uchar>(y, x + k) - SrcImage.at<uchar>(y, x + k + 1))*
(SrcImage.at<uchar>(y, x + k) - SrcImage.at<uchar>(y, x + k + 1));
// 计算垂直(90度)方向窗内兴趣值
for (int k = -r; k < r; k++)
wV2 += (SrcImage.at<uchar>(y + k, x) - SrcImage.at<uchar>(y + k + 1, x))*
(SrcImage.at<uchar>(y + k, x) - SrcImage.at<uchar>(y + k + 1, x));
// 计算45度方向窗内兴趣值
for (int k = -r; k < r; k++)
wV3 += (SrcImage.at<uchar>(y + k, x + k) - SrcImage.at<uchar>(y + k + 1, x + k + 1))*
(SrcImage.at<uchar>(y + k, x + k) - SrcImage.at<uchar>(y + k + 1, x + k + 1));
// 计算135度方向窗内兴趣值
for (int k = -r; k < r; k++)
wV4 += (SrcImage.at<uchar>(y + k, x - k) - SrcImage.at<uchar>(y + k + 1, x - k - 1))*
(SrcImage.at<uchar>(y + k, x - k) - SrcImage.at<uchar>(y + k + 1, x - k - 1));
// 取其中的最小值作为该像素点的最终兴趣值
int value = min(min(wV1, wV2), min(wV3, wV4));
//若兴趣值大于阈值,则将点的坐标存入数组中
if (value > threshold)
{
pPoint[nConut] = cvPoint(x, y);
nConut++;
}
}
}
//绘制兴趣点
cout << "kSize :5*5" << endl;
cout << "threshold : 5000" << endl;
cout << "corners :" << nConut << endl;
for (int i = 0; i < nConut; i++)
cv::circle(MorImage, pPoint[i], 3, cv::Scalar(255, 0, 0));
return MorImage;
}
int main()
{
cv::Mat SrcImage = imread("3.jpg");
if (!SrcImage.data)
return -1;
cv::Mat srcImage;
cvtColor(SrcImage, srcImage,CV_RGB2GRAY);
//窗口设置为5*5,阈值设置为5000
double time0 = static_cast<double>(getTickCount());
cv::Mat MorImage = MoravecCorners(srcImage, 5, 5000);
time0 = ((double)getTickCount() - time0) / getTickFrequency();
cout <<"runtime :"<< time0 << "s" << endl;
cv::imshow("MorMat", MorImage);
cv::waitKey(0);
return 0;
}
输入:lena.jpg,窗口大小5*5,阈值5000
输出:角点检测图(由于没有实现非局部极大值抑制,所以出现角点粘连现象)
图5 实验结果图