OpenCV-特征提取与检测(01、Harris角点检测)

版权声明:本文由 Micheal 超 博客 创作,未经博主允许不得转载。 https://blog.csdn.net/qq_42887760/article/details/86619752
  • Harris角点检测算子是于1988年由CHris Harris & Mike Stephens提出来的。在具体展开之前,不得不提一下Moravec早在1981就提出来的Moravec角点检测算子。
  • 角点的作用:
    角点是图像的很重要的局部特征,它决定图像中目标的形状。常用于三维场景重建,运动估计,目标跟踪,目标识别,图像配准等。

Moravec角点检测算子

(可以参考:https://blog.csdn.net/f290131665/article/details/80064479
1. Moravec基本原理
Moravec是一种基于灰度方差的角点检测算子,该算子计算图像中某个像素点沿着水平垂直对角线反对角线四个方向的灰度方差,其中的最小值选为该像素点的兴趣值,再通过局部非极大值抑制来检测是否为角点。
2. Moravec计算过程
Moravec角点检测算子的思想其实特别简单,在图像上取一个 k k * k k 的 “滑动窗口” (比如 5x5),计算水平、垂直、对角线、反对角线四个方向的相邻像素灰度差的平方和,不断的移动这个窗口并检测窗口中的像素变化情况E。像素变化情况E可简单分为以下三种:
A) 如果在窗口中的图像是平坦的,那么E的变化不大。
B) 如果在窗口中的图像是一条边,那么在沿这条边滑动时E变化不大,而在沿垂直于这条边的方向滑动窗口时,E的变化会很大。
C) 如果在窗口中的图像是一个角点时,窗口沿任何方向移动E的值都会发生很大变化。
在这里插入图片描述
3. 上图就是对Moravec算子的形象描述。用数学语言来表示的话就是:
在这里插入图片描述
在这里插入图片描述
其中( u u , v v )就表示四个移动方向水平(1,0)、对角线(1,1)、垂直(0,1)、反对角线(-1,1),E就是像素的变化值。Moravec算子对四个方向进行加权求和来确定变化的大小,然和设定阈值,来确定到底是边还是角点。
4. Moravec优点和缺点
优点:计算速度快。
缺点:
1. 由于窗口是方形并且二元的,因此对图像噪声敏感。
2. 有方向上的局限性。只有离散的8个45度角方向被考虑。
3. 对边缘的相应太简单

Harris角点检测算子

Harris角点检测算子实质上就是对Moravec算子的改良和优化。

  • 1.对Moravec算子中提到的公式,做进一步解释
    在这里插入图片描述
    1) 对于 x y (x,y) 平移 u v (u,v) 个单位后强度的变换有下式, I ( x + u , y + v ) I(x+u,y+v) 是平移后的强度, I ( x , y ) I(x,y) 是原图像像素。对于括号里面的值,如果是强度恒定的区域,那么它就接近于零,反之如果强度变化剧烈那么其值将非常大,所以我们期望 E ( u , v ) E(u,v) 很大。
    2) 其中 w w 是窗函数,窗口函数是一个矩形窗口或高斯窗口,它给在其中的像素加权。它可以是加权函数,也可以是高斯函数。
    在这里插入图片描述

  • 2.我们必须使边角检测的函数 E ( u , v ) E(u,v) 最大化,这意味着,我们必须最大限度地利用第二个参数。将泰勒展开式应用于上述方程并使用一些数学步骤,我们的最终方程式为:
    在这里插入图片描述
    这里 I x Ix I y Iy 分别是在x和y方向上的导数(使用cv2.Sobel()可以得到)。

  • 3.然后是主要部分。在此之后,他们创建了一个分数,基本上是一个等式,它将决定一个窗口是否可以包含边角。
    在这里插入图片描述
    所以这些特征值的值决定了一个区域是角落,边缘还是平面。

    1. 当 | R | 很小,也就是介于 λ λ 1 λ λ 2 之间,该区域是平面.
    2. 当 R<0 ,也就是当 λ λ 1>> λ λ 2 或相反时,该区域是边缘.
    3. 当 R 很大,即当 λ λ 1 λ λ 2 都很大并且R介于 λ λ 1 ~ λ λ 2 之间, 该区域是边角.

可以如下图表示:
在这里插入图片描述

  • 算法详细步骤
    第一步:计算图像X方向与Y方向的一阶高斯偏导数 I x Ix I y Iy
    第二步:根据第一步结果得到 I x Ix 2 , I y Iy 2 I x I y Ix*Iy
    第三步:高斯模糊第二步三个值得到 S x x , S y y , S x y Sxx, Syy, Sxy
    第四部:定义每个像素的Harris矩阵,计算出矩阵的两个特质值
    第五步:计算出每个像素的R值
    第六步:使用3X3或者5X5的窗口,实现非最大值压制
    第七步:根据角点检测结果计算,最提取到的关键点以绿色标记,显示在原图上。

  • 对于2中的具体推导步骤可以参考:https://www.cnblogs.com/klitech/p/5779600.html
    在这里插入图片描述

API参数说明

在这里插入图片描述
void cornerHarris ( // harris角点检测
InputArray src, // 彩色图与灰度图都可以,最好是灰度图
OutputArray dst, // 输出图像,计算出来的值可能正负,可能有小数,所以输入图像深度最好为 CV_32F,输出图形深度与输入一致
int blockSize, // -blockSize – 计算 λ1 λ2 时候的矩阵大小
int ksize, // -Ksize 窗口大小
double k, // -K表示计算角度响应时候的参数大小 默认在0.04~0.06
int borderType = BORDER_DEFAULT
);

• img - 输入图像,应该是 灰度 和 float32 类型
• blockSize - 角点检测中要考虑的领域大小。即 计算 λ λ 1 λ λ 2 时候的矩阵大小
• ksize - Sobel 求导中使用的窗口大小
• k - Harris 角点检测方程中的自由参数,参数默认取值在[0,04,0.06]

程序代码

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;
Mat src, gray_src;
int thresh = 130;
int max_count = 255;
const char* output_title = "HarrisCornerDetection Result";
void Harris_Demo(int, void*);

int main(int argc, char** argv) {

	src = imread("E:/Experiment/OpenCV/Pictures/cornerHarrisTest.jpg");
	if (src.empty()) {
		printf("could not load image...\n");
		return -1;
	}
	namedWindow("input image", CV_WINDOW_AUTOSIZE);
	imshow("input image", src);

	namedWindow(output_title, CV_WINDOW_AUTOSIZE);
	cvtColor(src, gray_src, COLOR_BGR2GRAY);
	createTrackbar("Threshold:", output_title, &thresh, max_count, Harris_Demo);
	Harris_Demo(0, 0);

	waitKey(0);
	return 0;
}

void Harris_Demo(int, void*) {
	Mat dst, norm_dst, normScaleDst;
	dst = Mat::zeros(gray_src.size(), CV_32FC1);

	int blockSize = 2;
	int ksize = 3;
	double k = 0.04;
	cornerHarris(gray_src, dst, blockSize, ksize, k, BORDER_DEFAULT);// 角点检测
	//printf("dst depth=%d, type=%d\n", dst.depth(), dst.type()); // dst depth=5, type=5  CV_32F
	normalize(dst, norm_dst, 0, 255, NORM_MINMAX, CV_32FC1, Mat()); // 检测出的元素值范围不确定,而且有正有负,归一化一下
	//printf("norm depth=%d, type=%d\n", norm_dst.depth(), norm_dst.type()); // norm depth=5, type=5  CV_32F
	convertScaleAbs(norm_dst, normScaleDst); // 将元素值取绝对值,且将输出图像深度变为 CV_8U    公式:dst(I)=abs(src(I)*scale + shift)
	//printf("scaleDst depth=%d, type=%d\n", normScaleDst.depth(), normScaleDst.type()); // scaleDst depth=0, type=0  CV_8U

	Mat resultImg = src.clone();// 深拷贝
	for (int row = 0; row < resultImg.rows; row++) {
		uchar* currentRow = normScaleDst.ptr(row);
		for (int col = 0; col < resultImg.cols; col++) {
			int value = (int)*currentRow;
			if (value > thresh) {// 过滤,角点响应值 大于 阈值才显示
				circle(resultImg, Point(col, row), 2, Scalar(0, 0, 255), 2, 8, 0);
			}
			currentRow++;// scaleDst的深度是CV_8U , 所以用 ++ 指针步长没有问题
		}
	}

	imshow(output_title, resultImg);
}

运行截图

在这里插入图片描述

参考博客

  1. https://blog.csdn.net/yudingjun0611/article/details/7991601
  2. https://blog.csdn.net/f290131665/article/details/80064479
  3. https://blog.csdn.net/ssw_1990/article/details/70155210
  4. https://blog.csdn.net/u014403318/article/details/80562785
  5. https://www.cnblogs.com/klitech/p/5779600.html
  6. https://www.aliyun.com/jiaocheng/1343014.html
  7. https://blog.csdn.net/huanghuangjin/article/details/81261258

猜你喜欢

转载自blog.csdn.net/qq_42887760/article/details/86619752