好久没发博客了,这一期间学了挺多东西的,只不过苦于学业压力没有时间整理,这回也是忙里偷闲整理出来一篇,希望喜欢!(明天就期中考咯!加油!)
标题是: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)。这些参数的设置对角点检测结果的质量和计算效率都有重要影响。
下面是这些参数的一些解释:
- block_size block_size:表示角点检测器在计算像素点的时候,需要考虑其周围的像素点的数量。在OpenCV中,block_size的默认值为3,表示角点检测器将考虑每个像素点周围3x3的像素点。因此,block_size的值越大,考虑的像素点数量就越多,角点检测器的计算也就越耗时。
- aperture_size:aperture_size是Sobel算子的大小,用于计算像素点的梯度。在OpenCV中,aperture_size的默认值为3,表示Sobel算子的大小为3x3。aperture_size的值越大,计算像素点梯度时考虑的像素点数量也就越多,可以提高角点检测的准确性,但同时也会增加计算时间。
- k: k是角点响应函数中的常数项,它控制了角点响应函数中的两个特征值之间的相对重要性。在Harris角点检测中,通常将k设置为0.04,这是一个经验值。
- 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
共勉!