harris角点检测是一种特征提取的方法,而特征提取正是计算机视觉的一种重要手段。尽管它看起来很复杂,其实也是基于数学原理和简单的图像处理来实现的。
本文以前能够参看笔者写的几篇图像处理的文章,将会有助于更深刻了解harris角点检测的实现。ios
为了判断图像的角点,能够利用卷积窗口滑动的思想,让以该点为中心的窗口在附近滑动。以下图是全部描述角点文章的初始图例,它表征的正是这一特性:当滑动窗口在全部方向移动时,窗口内的像素灰度出现了较大的变化,就多是角点。
算法
根据上述的算法思想,能够构建数学模型,图像窗口平移[u,v]产生灰度变化E(u,v)为:
其中w(x,y)是一种加权函数,几乎全部的应用都把它设为高斯函数。由上述公式,进行推导以下:
最后获得的公式(6),在几何意义上表征的是一个椭圆。椭圆的长短轴分别沿着矩阵M的两个特征向量的方向,而两个与之对应的特征值分别是半长轴和半短轴的长度的平方的倒数。
那么根据矩阵M的两个特征值λ1和λ2,能够将图像上的像素点分类成直线、平面与角点:当λ1和λ2 都比较大,且近似相等时,能够认为是角点。以下图所示:
函数
而上述表达不太方便使用,又定义了一个角点响应函数R,经过R的大小来判断像素是否为角点:
式中,detM为矩阵M的行列式,traceM为矩阵M的直迹。α为常常常数,取值范围为0.04~0.06。对于R公式,有推导以下:
能够知道,角点响应值R仍然表征了矩阵M两个特征值λ1和λ2,一样能够进行上述分类:当R为大数值正数的时候,表示为角点。以下图所示:
优化
在OpenCV中,已经提供了Harris角点检测函数cornerHarris()。为了更好地理解Harris角点提取的原理,这里参考了网上代码,本身实现了其算法,不过也调用了OpenCV中一些基本函数。
根据上述原理,Harris图像角点检测算法的关键是计算M矩阵,M矩阵是图像I(x,y)的偏导数矩阵,也就是要先求出图像的梯度。spa
1.计算图像I(x,y)在X,Y方向的梯度。在这里是经过卷积函数filter2D实现的,具体原理能够看(1)中提到的相关文章。3d
Mat gray; imgSrc.convertTo(gray, CV_64F); Mat xKernel = (Mat_<double>(1, 3) << -1, 0, 1); Mat yKernel = xKernel.t(); Mat Ix, Iy; filter2D(gray, Ix, CV_64F, xKernel); filter2D(gray, Iy, CV_64F, yKernel);
2.计算图像两个方向梯度的乘积。code
Mat Ix2, Iy2, Ixy; Ix2 = Ix.mul(Ix); Iy2 = Iy.mul(Iy); Ixy = Ix.mul(Iy);
3.对Ix二、Iy2和Ixy进行高斯滤波,生成矩阵M的元素A、B和C。htm
Mat gaussKernel = getGaussianKernel(7, 1); filter2D(Ix2, Ix2, CV_64F, gaussKernel); filter2D(Iy2, Iy2, CV_64F, gaussKernel); filter2D(Ixy, Ixy, CV_64F, gaussKernel);
4.根据公式计算每一个像素的Harris响应值R,获得图像对应的响应值矩阵。blog
Mat cornerStrength(gray.size(), gray.type()); for (int i = 0; i < gray.rows; i++) { for (int j = 0; j < gray.cols; j++) { double det_m = Ix2.at<double>(i, j) * Iy2.at<double>(i, j) - Ixy.at<double>(i, j) * Ixy.at<double>(i, j); double trace_m = Ix2.at<double>(i, j) + Iy2.at<double>(i, j); cornerStrength.at<double>(i, j) = det_m - alpha * trace_m *trace_m; } }
5.在3×3的邻域内进行非最大值抑制,找到局部最大值点,即为图像中的角点。在这里非最大值抑制是经过图像膨胀的实现的。比较膨胀先后的响应值矩阵,获得局部最大值。
//在3×3的邻域内进行非最大值抑制,找到局部最大值点,即为图像中的角点 double maxStrength; minMaxLoc(cornerStrength, NULL, &maxStrength, NULL, NULL); Mat dilated; Mat localMax; dilate(cornerStrength, dilated, Mat()); //膨胀 compare(cornerStrength, dilated, localMax, CMP_EQ); //比较保留最大值的点 //获得角点的位置 Mat cornerMap; double qualityLevel = 0.01; double thresh = qualityLevel * maxStrength; cornerMap = cornerStrength > thresh; //小于阈值t的R置为零。 bitwise_and(cornerMap, localMax, cornerMap); //位与运算,有0则为0, 全为1则为1 imgDst = cornerMap.clone();
合并以上步骤,传入参数,最终的实现代码:
#include <iostream> #include <algorithm> #include <opencv2\opencv.hpp> using namespace cv; using namespace std; void detectHarrisCorners(const Mat& imgSrc, Mat& imgDst, double alpha) { // Mat gray; imgSrc.convertTo(gray, CV_64F); //计算图像I(x,y)在X,Y方向的梯度 Mat xKernel = (Mat_<double>(1, 3) << -1, 0, 1); Mat yKernel = xKernel.t(); Mat Ix, Iy; filter2D(gray, Ix, CV_64F, xKernel); filter2D(gray, Iy, CV_64F, yKernel); //计算图像两个方向梯度的乘积。 Mat Ix2, Iy2, Ixy; Ix2 = Ix.mul(Ix); Iy2 = Iy.mul(Iy); Ixy = Ix.mul(Iy); //对Ix二、Iy2和Ixy进行高斯滤波,生成矩阵M的元素A、B和C。 Mat gaussKernel = getGaussianKernel(7, 1); filter2D(Ix2, Ix2, CV_64F, gaussKernel); filter2D(Iy2, Iy2, CV_64F, gaussKernel); filter2D(Ixy, Ixy, CV_64F, gaussKernel); //根据公式计算每一个像素的Harris响应值R,获得图像对应的响应值矩阵。 Mat cornerStrength(gray.size(), gray.type()); for (int i = 0; i < gray.rows; i++) { for (int j = 0; j < gray.cols; j++) { double det_m = Ix2.at<double>(i, j) * Iy2.at<double>(i, j) - Ixy.at<double>(i, j) * Ixy.at<double>(i, j); double trace_m = Ix2.at<double>(i, j) + Iy2.at<double>(i, j); cornerStrength.at<double>(i, j) = det_m - alpha * trace_m *trace_m; } } //在3×3的邻域内进行非最大值抑制,找到局部最大值点,即为图像中的角点 double maxStrength; minMaxLoc(cornerStrength, NULL, &maxStrength, NULL, NULL); Mat dilated; Mat localMax; dilate(cornerStrength, dilated, Mat()); //膨胀 compare(cornerStrength, dilated, localMax, CMP_EQ); //比较保留最大值的点 //获得角点的位置 Mat cornerMap; double qualityLevel = 0.01; double thresh = qualityLevel * maxStrength; cornerMap = cornerStrength > thresh; //小于阈值t的R置为零。 bitwise_and(cornerMap, localMax, cornerMap); //位与运算,有0则为0, 全为1则为1 imgDst = cornerMap.clone(); } //在角点位置绘制标记 void drawCornerOnImage(Mat& image, const Mat&binary) { Mat_<uchar>::const_iterator it = binary.begin<uchar>(); Mat_<uchar>::const_iterator itd = binary.end<uchar>(); for (int i = 0; it != itd; it++, i++) { if (*it) circle(image, Point(i%image.cols, i / image.cols), 3, Scalar(0, 255, 0), 1); } } int main() { //从文件中读取成灰度图像 const char* imagename = "D:\\Data\\imgDemo\\whdx.jpg"; Mat img = imread(imagename, IMREAD_GRAYSCALE); if (img.empty()) { fprintf(stderr, "Can not load image %s\n", imagename); return -1; } // Mat imgDst; double alpha = 0.05; detectHarrisCorners(img, imgDst, alpha); //在角点位置绘制标记 drawCornerOnImage(img, imgDst); // imshow("Harris角点检测", img); waitKey(); return 0; }
其运行结果为: