好久没发博客了,这一期间学了挺多东西的,只不过苦于学业压力没有时间整理,这回也是忙里偷闲整理出来一篇,希望喜欢!(明天就期中考咯!加油!)

        标题是:Harris角点实时监测,“实时”其实就是笔者在基于对图片进行Harris角点检测的基础上,由于好奇调用摄像头的效果,所以自己改写了一下。虽然最后效果不是非常好,但是“重在参与”对吧!

        接下来笔者将以最详细的注释剖析下面Harris角点检测的最经典的代码。

        下面将全代码分为几个步骤:

        1.打开摄像头

        2.读取每一帧,将每一帧转化为灰度图(cvtColor)

        3.角点检测(Harris)

        4.归一化处理

        5.对转化为灰度图的图像进行检测


        有了思路,那代码也就很简单了!

1.打开摄像头

打开摄像头这一步非常简单↓:

//1.打开默认摄像头
	VideoCapture cap(0);
	if (!cap.isOpened()) {
		cerr << "Error: failed to open camera" << endl;
		return -1;
	}

2.读取每一帧,将每一帧转化为灰度图(cvtColor)

while(true){
//读取每一帧
Mat frame;
cap >> frame;
		
//将每一帧转化为灰度图,减少信息量,变为单通道
Mat frameBray;
cvtColor(frame, frameBray, COLOR_BGR2GRAY);

//后续...

}

注解:这里就有人问道,为什么进行角点检测要把它转化为灰度图啊(cvtColor)?

答案很简单:

        在Harris角点检测中,我们使用了图像中像素值的梯度信息来检测角点。而灰度图像只包含单个通道的像素值,因此更容易提取像素值的梯度信息。

        如果我们使用彩色图像进行角点检测,则需要在每个像素位置计算三个通道的像素值梯度信息,这样就会增加计算量并降低计算效率。此外,彩色图像中的颜色信息并不会对角点检测产生影响,因此使用灰度图像进行角点检测是更加合理和高效的选择。

        因此,在进行Harris角点检测时,通常会首先将图像转换为灰度图像,然后在灰度图像上执行角点检测算法。

3.角点检测(Harris)

        Harris角点检测,这里我们会用到OpenCv提供的cornerHarris()函数:

void cornerHarris(
InputArray src, 
OutputArray dst, 
int blockSize, 
int ksize, 
double k,
int borderType = BORDER_DEFAULT);
  • src输入图像,必须为单通道、8位或32位浮点数类型的图像;
  • dst输出图像,和 src 的大小和类型相同,用于存储角点响应函数值;
  • blockSize指定窗口的大小,用于计算每个像素的角点响应函数值;
  • ksize指定 Sobel 算子的大小,用于计算图像梯度;
  • k指定一个权重因子,用于计算角点响应函数值;
  • borderType指定图像边界的处理方式。

它通过计算每个像素的角点响应函数值来检测图像中的角点,返回的响应函数值越大,说明该像素越可能是角点。

程序如下:

int block_size = 3;
int soble = 3;
double k = 0.04;
double threshold1 = 0.5;

//角点检测
Mat dst;
cornerHarris(frameBray, dst, block_size, soble, k);

如何使用我们都知道,但是为什么参数是这样呢?下面作出解释:

        在Harris角点检测中,有几个参数需要指定,包括窗口大小(block_size)、Sobel算子大小(aperture_size)、k值和角点响应函数的阈值(threshold)。这些参数的设置对角点检测结果的质量和计算效率都有重要影响。

下面是这些参数的一些解释:

  1. block_size block_size:表示角点检测器在计算像素点的时候,需要考虑其周围的像素点的数量。在OpenCV中,block_size的默认值为3,表示角点检测器将考虑每个像素点周围3x3的像素点。因此,block_size的值越大,考虑的像素点数量就越多,角点检测器的计算也就越耗时。
  2. aperture_size:aperture_size是Sobel算子的大小,用于计算像素点的梯度。在OpenCV中,aperture_size的默认值为3,表示Sobel算子的大小为3x3。aperture_size的值越大,计算像素点梯度时考虑的像素点数量也就越多,可以提高角点检测的准确性,但同时也会增加计算时间。
  3. k: k是角点响应函数中的常数项,它控制了角点响应函数中的两个特征值之间的相对重要性。在Harris角点检测中,通常将k设置为0.04,这是一个经验值。
  4. threshold threshold:是角点响应函数的阈值,用于确定哪些像素点是角点。在进行非极大值抑制时,只有角点响应函数大于threshold的像素点才会被视为角点。threshold的值越大,检测出的角点数量就越少,反之则会检测出更多的角点。

        值得注意的是,输出图像的数据类型为浮点数类型,需要进行归一化处理后才能用于显示或保存。

4.归一化处理

        归一化处理要用到的函数有:normalize()convertScaleAbs()

        这里的重点,我们应该讨论的是为什么我们在使用Harris函数后,需要使用到这两个函数,这是大部分小白入门时所困扰的,也困扰了我挺久的,下面作出解释:

        在Harris角点检测中,检测到的角点响应函数值是一个浮点数,通常情况下在0到很大的范围内变化,而我们希望将响应函数值映射到0到255的范围内进行显示。

        这时候就需要对响应函数值进行归一化处理,即将响应函数值除以它们中的最大值,然后将结果乘以255,这样就可以将响应函数值映射到0到255的范围内进行显示了。

        这时又出现了一个问题:为什么将响应函数值除以它们中的最大值,然后将结果乘以255,这样就可以将响应函数值映射到0到255的范围呢?在这里举个例子:

       例如,假设响应函数值的最大值max_val,某个像素的响应函数值为val,则该像素在归一化后的响应函数值val/max_val,乘以255后,该像素在显示时的灰度值val/max_val*255。这样,我们就可以将响应函数值映射到0到255的范围内进行显示了。

程序:

//归一化处理
		Mat dst_norm;
		Mat dst_norm_scale;
		normalize(dst, dst_norm, 0,255,NORM_MINMAX, CV_32FC1, Mat());
		convertScaleAbs(dst_norm, dst_norm_scale);//dst_norm_scale是要输出的图像

5.对转化为灰度图的图像进行检测

//对转化为灰度图的图像进行检测
		for (int i = 0; i < dst_norm.rows; i++)
		{
			for (int j = 0; j < dst_norm.cols; j++)
			{
				if ((int)dst_norm.at<float>(i, j) > threshold1 * 255)
				{
					circle(dst_norm_scale, Point(i, j), 5, Scalar(0), 2, 8, 0);
				}
			}
		}

        最后这一部分代码需要解释的应该是(int)dst_norm.at<float>(i, j) > threshold1 * 255:

   (int) dst_norm.at<float>(i,j) > threshold*255 这行代码中,首先使用了类型转换符 (int),将 dst_norm 图像中 (i,j) 位置处的响应函数值转换为整型。

         然后,通过比较转换后的整型值和 threshold*255 的大小,来判断该像素点是否为角点。其中,threshold 是一个阈值参数,用于决定哪些像素点是角点,因此 threshold*255 表示响应函数值的上限阈值。只有当该像素点的响应函数值大于上限阈值时,才被认为是角点,进而进行后续的非极大值抑制等操作。

        需要注意的是,dst_norm 图像中的像素值都是浮点数类型,因此需要先将其转换为整型才能进行比较操作。同时,由于 dst_norm 图像是通过归一化处理后的响应函数图像,其像素值范围已经被映射到了 0 到 255 之间,因此需要将 threshold 乘以 255,以便与转换后的整型像素值进行比较。                      

5.完整代码

Important!!!

 其中threshold1的值可以自行更改,从而来改变对于角点检测的敏感度。

我是懒得调了,毕竟效果不是很好哈哈,大家可根据需要自行修改,比如创造一个三通道的彩色图,在彩色图上画circle,这样观察的效果就更好。

Mat dst_color = src.clone();
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;



int main()
{
	// 打开默认摄像头
	VideoCapture cap(0);
	if (!cap.isOpened()) {
		cerr << "Error: failed to open camera" << endl;
		return -1;
	}

	//namedWindow("实时监测", 600*480);
	
	
	int block_size = 3;
	int soble = 3;
	double k = 0.04;
	double threshold1 = 0.5;

	//角点检测
	while (true)
	{
		//读取每一帧
		Mat frame;
		cap >> frame;
		
		//将每一帧转化为灰度图,减少信息量,变为单通道
		Mat frameBray;
		cvtColor(frame, frameBray, COLOR_BGR2GRAY);
		//角点检测
		Mat dst;
		cornerHarris(frameBray, dst, block_size, soble, k);
		//归一化处理(原因是检测到的角点为浮点数,在很大的区间内变化,需要将它映射到0~255的范围内,value*maxValue/255)->将Harris响应函数的矩阵转化为8为无符号的整形数组0~255
		Mat dst_norm;
		Mat dst_norm_scale;
		normalize(dst, dst_norm, 0,255,NORM_MINMAX, CV_32FC1, Mat());//得到浮点类型的响应矩阵
		convertScaleAbs(dst_norm, dst_norm_scale);//dst_norm_scale是要输出的图像
		//对转化为灰度图的图像进行检测
		for (int i = 0; i < dst_norm.rows; i++)
		{
			for (int j = 0; j < dst_norm.cols; j++)
			{
				if ((int)dst_norm.at<float>(i, j) > threshold1 * 255)
				{
					circle(dst_norm_scale, Point(i, j), 5, Scalar(0), 2, 8, 0);
				}
			}
		}
		Mat threP;
		imshow("实时监测", dst_norm_scale);
		threshold(dst_norm_scale, threP, 60, 255, THRESH_BINARY);
		imshow("实时二值化监测", threP);
		waitKey(20);
	}
	
	// 释放资源
	cap.release();
	destroyAllWindows();

	return 0;

}

2023年4月17日01:03:52

共勉!