前言:
在图像中角点是一个重要的局部特征,它决定了图像中关键区域的形状,体现了图像中重要的特征信息,所以在目标识别、图像匹配、图像重构方面角点具有十分重要的意义。 图像中角点的数量远比总像素数小,如果通过角点就能完成一些功能的话,将极大地提高处理效率。 对角点的定义一般分为以下三种:图像边界曲线上具有极大曲率值的点;图像中梯度值和梯度变化率都很高的点;图像边界方向变化不连续的点。 定义不同,角点的提取方法也不尽相同。目前,角点检测方法主要有2大类:
1)基于图像边缘轮廓特征的方法。此方法需要经过图像预分割、轮廓链码提取和角点检测,如基于边界曲率的角点检测,基于边界小波变换的角点检测以及基于边界链码的角点检测。
2)基于图像灰度信息的方法。此方法主要通过计算曲率及梯度进行角点检测,通过计算边缘的曲率来判断角点的存在性。典型代表有Harris算法、Susan算法、Moravec算法等。
一、Harris算法介绍
1.1 Harris算法原理
Harris 是 Harris 和 Stephens 在 1988 年提出,专门针对 Moravec 算子的改进版。Harris 算子,又称 Plessey算子,它基于与 Moravec 相同的角点定义,即定义在各个方向上灰度值变化的点。与 Moravec 算子的不同之处在于局部自相关测量结果的估计方式,Moravec 算子只对八方向离散的移动方向计算灰度变化,而 Harris算子允许获得所有方向上的自相关变化( 也就是灰度,它用一个自相关函数来计算灰度信号在二维方向上具有明显变化的像素点位置,构造一个与相关函数相关的矩阵M,而通过比较矩阵M的特征值的大小可以轻松地提取到相应的角点。
在图像中搜索有价值的特征点时,使用角点是一种不错的方法。角点是很容易在图像中定位的局部特征,并且大量存在于人造物体中(例如墙壁、门、窗户、桌子等产生的角点)。角点的价值在于它是两条边缘线的接合点,是一种二维特征,可以被精确的定位。与此相反的是位于均匀区域或物体轮廓上的点以及在同一物体的不同图像上很难重复精确定位的点。Harris特征检测就是检测角点的经典方法。接下来将详细探讨这个方法。
如果在各个方向上移动这个小窗口,窗口内的灰度发生了较大的变化,那么说明窗口内存在角点。如果在各个方向移动,灰度几乎不变,说明是平坦区域;如果只沿着某一个方向移动,灰度几乎不变,说明是直线;如果沿各个方向移动,灰度均发生变化,说明是角点。
设图像窗口平移,产生的灰度变化为,则:
由得:
由于:
于是对于局部微小的移动量,可以近似得到下面的表达:
为二维矩阵,且:
其中:
其中,表示 x 方向的梯度,表示 y 方向的梯度,为高斯函数。矩阵的特征值是自相关函数的一阶曲率。特征值的大小与特征点的性质息息相关。即当两个特征值都比较小时,则此点可能位于平坦区,不为角点或边界点; 当两个特征值一个较大、而另一个却相对较小时,则此点位于边界上,属于边界
点; 当两个特征值均相对较大时,则此点沿任意方向的曲率都较大,为需要提取的角点。
一般地,为了避免计算矩阵的特征值,常通过下式来计算特征点的响应函数,亦称兴趣值:
其中, 为矩阵 的行列式,为矩阵的迹,即 特征值之和。为经验值常数,一般在 0. 04 ~ 0.06 范围内取值。为了计算方便,文中采用如下角点响应函数计算兴趣值从而判定出角点,即:
其中,ε 表示任意小的正数。显然采用这中方式计算可以避免 选择的随机性。
1.2 Harris算法步骤
(1)利用差分算子对图像进行滤波,并计算图像中每个像素点的 、以及;
(2) 对 、以及进行高斯平滑,以去除噪声。在此过程中可通过归一化将模板参数和置 1;
(3) 计算原图像每个对应点的兴趣值 ,并进行局部非极大值抑制;
(4) 设定阈值,如果兴趣值大于这个阈值并且在某领域内是局部最大值,则认为该点是需要提取的角点。
1.3 Harris算法的不足
虽然 Harris 算法相较于其他众多基于灰度的角点提取算法具有明显的优势,但它仍然存在一些不足:
在经典的 Harris 角点检测中,当对角点的兴趣值进行非极大值抑制来确定局部极大值的时候,角点的提取效果几乎完全由设定的阈值大小决定。而阈值的大小也与所提取的角点数量息息相关,一般情况下,阈值越大提取的角点越少,极易造成正确角点的丢失; 阈值越小提取的角点数越多,也会带来很多伪角点。因此,在运用 Harris 算法进行角点检测时,阈值这个经验值的选取和设定对角点提取具有很大的影响。
二、代码实现
#include
#include
#include
using namespace cv;
using namespace std;
/************************************************/
void ConvertBGR_img2GRAY_img(const Mat &BGR_img, Mat &Gray_img)
{
//这个函数实现将彩色图转化成灰度图,利用常用公式:Gray = R*0.299 + G*0.587 + B*0.114进行自编程实现
//第一个参数BGR_img表示输入的彩色RGB图像的引用;
//第二个参数Gray_img表示转换后输出的灰度图像的引用;
if (BGR_img.empty() || BGR_img.channels() != 3)
{
return;
}
Gray_img = Mat::zeros(BGR_img.size(), CV_8UC1); //创建一张与输入图像同大小的单通道灰度图像
uchar *pointImage = BGR_img.data; //取出存储图像像素的数组的指针
uchar *pointImageGray = Gray_img.data;
int height = BGR_img.rows;
int width = BGR_img.cols;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
*(pointImageGray + i*Gray_img.step[0] + j*Gray_img.step[1]) = (uchar)(0.114*(pointImage + i*BGR_img.step[0] + j*BGR_img.step[1])[0] + 0.587*(pointImage + i*BGR_img.step[0] + j*BGR_img.step[1])[1] + 0.299*(pointImage + i*BGR_img.step[0] + j*BGR_img.step[1])[2]);
}
}
}
/**********************************************/
/*******************************************************/
//非极大值抑制和阈值检测
void LocalMaxValue(Mat &resultData, Mat &ResultImage, int kSize)
{
int r = kSize / 2;
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) >15000)
{
circle(ResultImage, Point(j, i), 5, Scalar(0, 0, 255), 2, 8, 0);
}
}
}
}
}
/*********************************************************/
// 主函数
int main()
{
const Mat src_img = imread("test11.png");
if (src_img.empty())
{
printf("could not load image...\n");
return -1;
}
namedWindow("原图:", CV_WINDOW_AUTOSIZE);
imshow("原图:", src_img);
// 将彩色图转化为灰度图,调用OpenCV提供的cvtColor接口
Mat gray_img;
ConvertBGR_img2GRAY_img(src_img, gray_img);
namedWindow("灰度图", CV_WINDOW_AUTOSIZE);
imshow("灰度图", gray_img);
// 第一步:用sobel算子计算度图像的水平和垂直方向上的差分
Mat image_con_sobel_Ix, image_con_sobel_Iy;
//image_con_sobel_Ix = sobel(gray_img,1,0, 3, BORDER_DEFAULT);
//image_con_sobel_Iy = sobel(gray_img,0,1, 3, BORDER_DEFAULT);
Sobel(gray_img, image_con_sobel_Ix, CV_64F, 1, 0, 3);
Sobel(gray_img, image_con_sobel_Iy, CV_64F, 0, 1, 3);
//convertScaleAbs(image_con_sobel_Ix, image_con_sobel_Ix);
//convertScaleAbs(image_con_sobel_Iy, image_con_sobel_Iy);
// 第二步:计算Ixx,Iyy,Ixy
Mat image_con_sobel_Ixx, image_con_sobel_Iyy, image_con_sobel_Ixy;
multiply(image_con_sobel_Ix, image_con_sobel_Ix, image_con_sobel_Ixx, 1.0, CV_64FC1);
multiply(image_con_sobel_Iy, image_con_sobel_Iy, image_con_sobel_Iyy, 1.0, CV_64FC1);
multiply(image_con_sobel_Ix, image_con_sobel_Iy, image_con_sobel_Ixy, 1.0, CV_64FC1);
//第三步:对Ixx,Iyy,Ixy进行高斯平滑
Mat blurimage_Ixx, blurimage_Iyy, blurimage_Ixy;
GaussianBlur(image_con_sobel_Ixx, blurimage_Ixx, Size(3, 3), 5);
GaussianBlur(image_con_sobel_Iyy, blurimage_Iyy, Size(3, 3), 5);
GaussianBlur(image_con_sobel_Ixy, blurimage_Ixy, Size(3, 3), 5);
//第四步:计算响应函数,为避免K值的随机性,我采用另外一种响应函数表达式
Mat Response_data;
Response_data = (blurimage_Ixx.mul(blurimage_Iyy) - blurimage_Ixy.mul(blurimage_Ixy)) / (blurimage_Ixx + blurimage_Iyy);
//第五步:非极大值抑制和阀值检测
Mat ResultImage = gray_img.clone();
LocalMaxValue(Response_data, ResultImage, 3);
namedWindow("结果", CV_WINDOW_AUTOSIZE);
imshow("结果", ResultImage);
waitKey(0);
return 0;
}
运行程序,结果如下:
单独观察:
三、使用API实现Harris角点检测
在OpenCV中提供了cornerHarris() 函数来对图像实现Harris角点检测。其函数原型和参数解析:
其中:
代码实现:
#include
#include
#include
using namespace cv;
using namespace std;
string out_title = "Harris角点检测";
int thresh = 100;
int max_count = 255;
Mat src_img, gray_img;
void Harris_callback(int, void*);
int main()
{
src_img = imread("test11.png");
if (src_img.empty())
{
printf("could not load image...\n");
return -1;
}
namedWindow("原图:", CV_WINDOW_AUTOSIZE);
imshow("原图:", src_img);
namedWindow(out_title, CV_WINDOW_AUTOSIZE);
cvtColor(src_img, gray_img, CV_BGR2GRAY);
createTrackbar("阈值", out_title, &thresh, max_count, Harris_callback);
Harris_callback(0, 0);
waitKey(0);
return 0;
}
void Harris_callback(int, void*)
{
Mat dst_img, norm_dst_img, scale_dst_img;
dst_img = Mat::zeros(gray_img.size(), CV_32FC1);
int blockSize = 2;
int ksize = 3;
double k = 0.04;
cornerHarris(gray_img, dst_img, blockSize, ksize, k, BORDER_DEFAULT); //调用API
normalize(dst_img, norm_dst_img, 0, 255, NORM_MINMAX, CV_32FC1, Mat()); // 调用归一化函数进行归一化处理
convertScaleAbs(norm_dst_img, scale_dst_img); //将图像转化为8位图
Mat result_img = src_img.clone();
for (int r = 0; r < result_img.rows; ++r)
{
uchar *ptr_row = scale_dst_img.ptr(r);
for (int c = 0; c < result_img.cols; ++c)
{
int value = int(ptr_row[c]);
if (value > thresh)
{
circle(result_img, Point(c, r), 2, Scalar(0, 0, 255), 2, 8, 0);
}
}
}
imshow(out_title, result_img);
}
运行程序,结构如下:
拖动TrackBar以获得不同阈值条件下的结果: