☰
三种经典角点检测算法原理对比与OpenCV实现指南
2026/10/24 1:29:26 网站建设 项目流程

特征点提取是计算机视觉绕不开的基础活儿。不管你是做图像匹配、三维重建、全景拼接,还是视觉里程计,第一步几乎都是“从图像里找出一批有代表性的点”。早年我刚接触视觉算法的时候,看的很多教材一上来就是Moravec、Forstner和Harris这三种经典角点检测算法,后来在OpenCV里做实际项目,又反反复复跟它们打交道。今天这篇就把这三种算法放在一起,从数学原理讲到OpenCV落地实现,再把参数调优和踩坑经验一并交代清楚。

这篇文章适合刚入门视觉算法、想搞懂特征点来龙去脉的读者,也适合已经用过OpenCV但只停留在“调函数”层面、想补一补底层原理的朋友。看完之后,你能实现三种算法的OpenCV版本,也能根据实际场景判断该选哪一种。

1. 三种特征点算法的核心思路拆解

先说一个总体的认知:Moravec、Forstner和Harris本质上都是在回答同一个问题——图像中哪些像素位置是“角点”或者说“特征点”?但三者的回答方式完全不同,数学工具也一代比一代精巧。

1.1 特征点到底在找什么

在图像里,像素灰度变化可以从局部窗口来观察。假如你拿着一个小窗口在图像上游走,窗口内灰度变化很小,说明这块区域是平坦区域或者纹理极弱的区域;如果在某个方向上灰度变化剧烈,说明这里有边缘;如果在所有方向上游走,灰度都剧烈变化,那就是角点。

三种算法最终目标一致:把“在所有方向上灰度都剧烈变化”的位置找出来。区别在于,它们计算和评定这个“剧烈变化”的方式不一样。从工程角度理解,Moravec是“离散方向采样”,Forstner是“误差椭圆拟合”,Harris则是“自相关矩阵的泰勒展开”。后面逐一拆解。

1.2 Moravec算法:最朴素的离散方向判断

Moravec角点检测是三种里面最古老的思路,1977年提出。它的核心做法很简单:以当前像素为中心,取一个固定大小的窗口(比如3x3、5x5或者7x7),然后计算该窗口在水平、垂直、对角线和反对角线四个方向上移动一定距离后的灰度差平方和。

具体来说,对于图像中的每个像素点,窗口在四个方向上的灰度变化值分别是:

  • 水平方向:窗口向右或向左平移一个像素
  • 垂直方向:窗口向上或向下平移一个像素
  • 主对角线方向:窗口沿对角线方向平移
  • 反对角线方向:窗口沿反对角线平移

取这四个方向响应值中的最小值,作为该点的角点响应值。为什么取最小值而不是最大值或者平均值?因为角点的特性是“所有方向都剧烈变化”,如果一个方向变化很小,说明这个点可能位于边缘上——边缘在垂直于边缘的方向上变化剧烈,但沿边缘方向几乎没有变化。取最小值可以过滤掉边缘点,保留那些在任何方向移动都会引起灰度剧烈变化的点。

然后再设定一个阈值,把响应值大于阈值的局部极大值点作为最终角点。这里“局部极大值”的意思是在一个邻域内(比如3x3或5x5窗口内)该点的响应值大于周围所有邻居的响应值。

Moravec算法最大的缺陷在于只考虑了四个离散方向。如果窗口移动方向不在这些方向上,比如45度之外的其他角度,响应值的计算就不准确,导致检测结果对方向敏感。另外,窗口是二值化的矩形区域,与图像内容的贴合度也不够精细,响应函数容易受噪声干扰。

1.3 Forstner算法:误差椭圆视角的角点定位

Forstner算法是摄影测量领域非常经典的方法,后来被引入计算机视觉。它的思路比Moravec精细得多,核心是:在像素周围开一个窗口,对窗口内每个像素计算梯度,然后用这些梯度值构造一个协方差矩阵。

这个协方差矩阵在几何上对应一个误差椭圆。如果误差椭圆接近圆形,说明窗口内梯度在各个方向上都比较均匀,该像素可以视为角点;如果误差椭圆拉得很长,说明梯度主要集中在某个方向上,这个像素多半在边缘上;如果误差椭圆很小,梯度普遍微弱,那就是平坦区域。

具体计算时,Forstner算法对窗口内每个像素求得其在x方向上的梯度(也就是水平差分)和在y方向上的梯度(垂直差分),然后计算三个二阶矩值:x梯度的平方和、y梯度的平方和、以及x梯度乘以y梯度的和。这三个值组成一个2x2的对称矩阵。接下来用行列式除以迹来构造角点响应值。

  • 行列式反映误差椭圆的面积大小,面积大说明梯度能量强
  • 迹反映误差椭圆的主轴长度之和

两者的比值 q = 4 · det / tr² 是判断角点质量的关键参数,q越接近1,说明误差椭圆越圆,点越像角点。实际操作中通常要设定两个阈值:一个是q的阈值,另一个是响应值(det/tr)的阈值,两者同时满足才判定为角点。

Forstner算法对角点的定位精度比较高,因为它考虑的是窗口内梯度的统计特性,而不是像Moravec那样只看几个离散方向的灰度差。但计算量也明显增大,因为每个像素都要做窗口内的梯度统计。

1.4 Harris算法:自相关矩阵与尺度空间的思想雏形

Harris算法是1988年提出的,迄今为止依然是实践中最常用的角点检测算法之一。它本质上是Moravec思路的数学化升级版。Harris不再只考虑四个固定方向,而是用泰勒展开把灰度变化表示成关于位移的二次函数,然后分析这个函数的形态。

假设图像在位置 (x, y) 处的灰度为 I(x, y),考虑一个微小位移 (u, v),窗口内灰度变化可以写成加权平方和的形式。对 I 做一阶泰勒展开后,灰度变化近似为一个关于 (u, v) 的二次型。这个二次型的系数矩阵就是自相关矩阵 M,它是窗口内梯度的二阶矩矩阵,和Forstner的协方差矩阵在形式上非常接近。

Harris不直接分解矩阵的特征值,而是用行列式和迹来构造响应函数:

R = det(M) - k · tr(M)²

其中k是经验常数,通常在0.04到0.06之间。这个响应函数的设计逻辑在于:如果两个特征值都很大,行列式大、迹也大,但二者的组合关系让R保持较大的正值,对应角点;如果一个特征值大一个特征值小,R为负值,对应边缘;如果两个特征值都很小,R绝对值很小,对应平坦区域。

Harris算法的数学推导干净利落,而且窗口函数引入了高斯权重,离中心像素越远的梯度贡献越小,抗噪能力比Forstner更好。它是三种算法中综合性能最均衡的,OpenCV里的 cornerHarris 函数在底层实现时也遵循了这个逻辑。

2. 三种算法的OpenCV实现与代码落地

原理讲清楚以后,接下来看怎么在OpenCV里把三种算法都跑起来。我用的环境是OpenCV 4.5.4版本,配合C++接口,Python接口的调用方式大同小异,后面会给出对应说明。先写一个统一的测试框架,便于观察不同算法的检测效果。

2.1 OpenCV中的Moravec实现

Moravec算法没有直接封装在OpenCV标准库中,需要自己手动实现。代码如下:

void moravecCorners(const cv::Mat& src, std::vector<cv::Point2f>& corners, int windowSize, int threshold) { cv::Mat gray; if (src.channels() == 3) { cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); } else { gray = src.clone(); } int half = windowSize / 2; cv::Mat response = cv::Mat::zeros(gray.size(), CV_32FC1); // 四个方向的偏移 int dx[4] = {1, 0, 1, 1}; int dy[4] = {0, 1, 1, -1}; for (int y = half; y < gray.rows - half; y++) { for (int x = half; x < gray.cols - half; x++) { double minSum = DBL_MAX; for (int dir = 0; dir < 4; dir++) { double sum = 0.0; for (int wy = -half; wy <= half; wy++) { for (int wx = -half; wx <= half; wx++) { int px = x + wx; int py = y + wy; int qx = px + dx[dir]; int qy = py + dy[dir]; if (qx >= 0 && qx < gray.cols && qy >= 0 && qy < gray.rows) { double diff = (double)gray.at<uchar>(py, px) - (double)gray.at<uchar>(qy, qx); sum += diff * diff; } } } minSum = std::min(minSum, sum); } response.at<float>(y, x) = (float)minSum; } } // 非极大值抑制 + 阈值筛选 for (int y = half; y < gray.rows - half; y++) { for (int x = half; x < gray.cols - half; x++) { float val = response.at<float>(y, x); if (val < threshold) continue; bool isLocalMax = true; for (int wy = -half; wy <= half && isLocalMax; wy++) { for (int wx = -half; wx <= half && isLocalMax; wx++) { if (wx == 0 && wy == 0) continue; if (response.at<float>(y + wy, x + wx) >= val) { isLocalMax = false; } } } if (isLocalMax) { corners.push_back(cv::Point2f((float)x, (float)y)); } } } }

代码里有几个细节需要说明。首先是窗口遍历的边界问题,我在实现时直接把边界区域跳过,因为边界处无法完整覆盖窗口,计算出来的响应值无效。另外一个容易犯的错误是,非极大值抑制的邻域大小必须与窗口大小一致,否则会出现同一角点被重复检测到的情况。可以将邻近的多个点都判定为局部极大值,然后互相重叠。

2.2 OpenCV中的Forstner实现

Forstner算法的OpenCV实现同样需要手动撰写。核心步骤是计算每个像素的梯度二阶矩矩阵,再根据行列式和迹来判断角点。实现如下:

void forstnerCorners(const cv::Mat& src, std::vector<cv::Point2f>& corners, int windowSize, double qThreshold, double responseThreshold) { cv::Mat gray; if (src.channels() == 3) { cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); } else { gray = src.clone(); } gray.convertTo(gray, CV_32FC1); int half = windowSize / 2; // 使用Sobel算子计算梯度 cv::Mat gradX, gradY; cv::Sobel(gray, gradX, CV_32F, 1, 0, 3); cv::Sobel(gray, gradY, CV_32F, 0, 1, 3); cv::Mat response = cv::Mat::zeros(gray.size(), CV_32FC1); cv::Mat quality = cv::Mat::zeros(gray.size(), CV_32FC1); for (int y = half; y < gray.rows - half; y++) { for (int x = half; x < gray.cols - half; x++) { double m00 = 0, m01 = 0, m11 = 0; for (int wy = -half; wy <= half; wy++) { for (int wx = -half; wx <= half; wx++) { float gx = gradX.at<float>(y + wy, x + wx); float gy = gradY.at<float>(y + wy, x + wx); m00 += gx * gx; m01 += gx * gy; m11 += gy * gy; } } double det = m00 * m11 - m01 * m01; double trace = m00 + m11; if (trace < 1e-6) { response.at<float>(y, x) = 0; quality.at<float>(y, x) = 0; continue; } response.at<float>(y, x) = (float)(det / trace); quality.at<float>(y, x) = (float)(4.0 * det / (trace * trace)); } } // 阈值筛选和局部极大值 for (int y = half; y < gray.rows - half; y++) { for (int x = half; x < gray.cols - half; x++) { if (response.at<float>(y, x) < responseThreshold) continue; if (quality.at<float>(y, x) < qThreshold) continue; bool isLocalMax = true; for (int wy = -half; wy <= half && isLocalMax; wy++) { for (int wx = -half; wx <= half && isLocalMax; wx++) { if (wx == 0 && wy == 0) continue; if (response.at<float>(y + wy, x + wx) > response.at<float>(y, x)) { isLocalMax = false; } } } if (isLocalMax) { corners.push_back(cv::Point2f((float)x, (float)y)); } } } }

这里有一个值得注意的工程细节:Sobel核大小我选了3,这是最常用的选择。如果窗口尺寸很小(3x3),用更大的Sobel核反而会引入过多平滑,钝化角点;如果窗口尺寸很大(7x7或者更大),用5x5的Sobel核也无妨。另外注意梯度的数据类型,要用CV_32F,如果保留uchar类型,梯度值会被截断到0到255之间,负梯度直接丢失,后续计算全部失真。

2.3 OpenCV中的Harris实现

Harris是OpenCV标准库直接支持的一种算法,通过 cornerHarris 函数即可调用,也可以手动实现。先看直接调用的方式:

void harrisCorners(const cv::Mat& src, std::vector<cv::Point2f>& corners, int blockSize, int ksize, double k, double threshold) { cv::Mat gray; if (src.channels() == 3) { cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); } else { gray = src.clone(); } gray.convertTo(gray, CV_32FC1); cv::Mat response; cv::cornerHarris(gray, response, blockSize, ksize, k); // normalize到0~1之间方便阈值设定 cv::Mat responseNorm; cv::normalize(response, responseNorm, 0, 1, cv::NORM_MINMAX); int half = blockSize / 2; for (int y = half; y < gray.rows - half; y++) { for (int x = half; x < gray.cols - half; x++) { float val = responseNorm.at<float>(y, x); if (val < threshold) continue; bool isLocalMax = true; for (int wy = -half; wy <= half && isLocalMax; wy++) { for (int wx = -half; wx <= half && isLocalMax; wx++) { if (wx == 0 && wy == 0) continue; if (responseNorm.at<float>(y + wy, x + wx) > val) { isLocalMax = false; } } } if (isLocalMax) { corners.push_back(cv::Point2f((float)x, (float)y)); } } } }

如果要手动实现Harris,核心在于自相关矩阵的计算。与Forstner不同,Harris引入高斯加权,窗口内梯度对中心点的贡献不是等权重的。OpenCV内部在计算窗口内的二阶矩时会用一个高斯核去卷积,这比简单的矩形窗口求和更稳健。手动实现时,可以用 cv::GaussianBlur 分别对 x梯度平方、y梯度平方和x梯度乘y梯度三个通道做平滑,再构造M矩阵。

需要提醒的是,cornerHarris返回的响应值范围在不同图像上差异极大。如果不做归一化,直接用固定阈值截断,可能在一张图上效果很好,换一张图就一个点也检测不出来。所以我在代码里做了min-max归一化到0到1之间,这算是我个人的实践经验。当然归一化也有副作用,会把噪声放大,后面在阈值调参部分细说。

3. 实测对比:同一张图三种算法表现差异

理论终归要落回图像上。我用一张典型的室内场景图做了三种算法的实测对比。测试图像包含明显的墙角、桌面边缘、窗框纹理和平坦的墙面区域,各种类型的像素区域都覆盖到了,比较有代表性。

3.1 检测数量与分布

Moravec算法在默认参数下(窗口7x7,阈值按图像灰度差平方总和设定),检测到的角点数量较少,且大多集中在对比度强烈的区域,比如墙角周围。平坦区域几乎没有点,边缘区域偶尔有误检,但分布总体比较疏散。

Forstner算法在同样的测试图上,检测到的候选点明显更多,尤其是在纹理稍微丰富的区域也能提取出点来。原因在于Forstner的响应函数是det/tr,对弱纹理仍然能产生响应,只是响应的幅度普遍较低,需要靠阈值来框定范围。

Harris算法检测出的角点数量和质量介于二者之间,但分布更均匀。高斯窗口的作用使得响应在角点周围呈现出比较平滑的峰值形态,非极大值抑制后的点位置比较精确。

如果用三种算法在同一张图上分别提取200个特征点来观察,肉眼可见Harris的角点分布更合理,不会出现像Moravec那样在某个边缘上扎堆的问题。

3.2 定位精度对比

这里说的定位精度,是指检测出的角点坐标与真实角点位置的偏差。Moravec因为是离散方向采样,响应函数的峰值往往出现在角点周围几个像素的区域内,定位偏差在1到2个像素左右,而且在45度角边缘附近容易偏移。

Forstner由于利用了窗口内所有梯度信息,定位精度最高,偏差可以控制在0.5像素以内。这个特性在摄影测量领域尤其被看重,因为后续的空中三角测量和点位坐标解算对精度极其敏感。

Harris的定位精度介于两者之间,大约1个像素的偏差。但在实际视觉SLAM、图像拼接这类任务中,Harris配合亚像素精化(cornerSubPix)已经完全够用。

3.3 计算效率对比

单从计算量看,Moravec最轻量,因为它每个像素只需要计算四个方向的灰度差平方和,不涉及到梯度和矩阵运算。Forstner和Harris计算量明显更大,二者复杂度相当,都需要计算梯度、窗口内累加二阶矩。

不过实际用OpenCV跑的时候,由于库函数底层优化做得好,Harris的速度比Forstner手工实现快很多。我自己测试过:一张800x600的图像,Moravec大约需要25毫秒,Harris用OpenCV自带函数大约8毫秒,Forstner手工实现大约35毫秒。注意这个数字不含阈值筛选和极大值抑制,因为极大值抑制本身也是O(n)的扫描。

有人可能会问:既然Forstner定位精度更高,为什么在实际视觉系统里用的不如Harris多?原因很简单——Forstner对参数太敏感,窗口尺寸和两个阈值动一下,结果就千差万别,工程调参成本高。而Harris的OpenCV实现稳定,参数含义直观,k值基本可以固定,只需要调一个阈值,所以工程实践中Harris更流行。

4. Harris参数调优与工程化经验

用Harris算法做实际项目时,参数调起来有不少门道。很多新手拿到cornerHarris就默认用blockSize=3、ksize=3、k=0.04,然后就抱怨检测结果不理想。问题往往就出在这些默认参数未必适合特定图像。

4.1 blockSize和ksize的选择逻辑

blockSize是计算自相关矩阵时窗口的大小。它决定了一个角点周围多大范围被用于统计梯度分布。blockSize越大,统计信息越多,抗噪能力越强,但同时也越容易把两个相邻角点合并成一个响应峰,导致小尺度的角点丢失。blockSize越小,角点定位越精细,但对噪声敏感。

ksize是Sobel算子的孔径尺寸。它控制梯度计算时的平滑程度。ksize越大,梯度响应越平滑,角点检测越稳定,但也会让边缘响应变宽。经验上ksize取值不能大于blockSize,否则梯度计算本身就已经覆盖了超过统计窗口的范围,逻辑上就不对了。

实际调参时我习惯先固定blockSize,然后在3到7之间搜索ksize。对于高分辨率图像,可以适当把两个参数都调大,因为同样大小的角点在更大尺度图像上占据的像素也更多。

4.2 k值的争议

k作为经验常数,控制角点响应中迹项所占的权重。k越大,对角点的要求越苛刻,检测出的点越“尖”;k越小,越容易把边缘误判为角点。OpenCV官方通常推荐0.04到0.06之间。

但这只是一个经验范围,实际场景中不一定最优。有一种改进思路是用 det(M) / (tr(M) + eps) 来替代原始响应函数,消除对k的依赖,这就是所谓的Noble角点响应,在某些专利算法里能见到。如果项目允许,我会自己做实验对比,到底用原始Harris响应还是Noble响应效果更好,不能想当然。

4.3 阈值选取的坑

cornerHarris返回的响应值在不同图像上数值范围差异巨大。纹理丰富的图像响应整体偏高,平滑图像响应整体偏低。如果固定阈值,很难找到一套通用的数值。

我的建议是分两步走。第一步对响应图做归一化,范围映射到0到1;第二步以归一化后的响应值从高到低排序,按数量倒序截取前N个点作为候选角点。这样就能保证“无论图像如何,均匀分布的点数量可以预设”,这在视觉SLAM初始化特征点数量控制时非常好用。

不过归一化对离群值很敏感,万一图像里有个极高响应的点,会把其他点的响应全部压缩到0附近。解决方案是截断式归一化,比如把响应值超过99百分位的都截断到99百分位的值,再做归一化。这个技巧在自拍图匹配、室内外场景切换时格外有用。

4.4 非极大值抑制的误区

非极大值抑制是角点检测最后一步必不可少的环节。没有这一步,角点周围的一团像素都会被认为是角点,后续的描述子计算和匹配会带来大量冗余。

但很多人在实现时只用3x3窗口做极大值抑制,这在角点密集区域会出现问题。我在做棋盘格标定角点提取时发现,当窗口距离很近时,3x3的抑制范围不够,多个相邻点都成了局部极大值,结果一个棋盘格角点区域检测出4到5个点。加大抑制窗口到7x7或者9x9,情况立刻好了。

非极大值抑制窗口大小的选择,应该与角点最小间距预估匹配。如果图像里的角点天然就很密集,可以尝试在响应图上做距离变换,用自适应抑制半径。

5. 常见问题与排查实录

在实际使用中,不管是调OpenCV的函数还是跑自己写的代码,总有一些反复出现的坑。这里集中整理一下,方便排查。

5.1 问题:检测出的角点全部聚集在图像边缘

这个现象经常出现在自己实现Moravec和Forstner时。排查方向首要是看边界处理。传统写法里遍历循环只从 half 开始到 rows - half 结束,但如果缩小窗口尺寸或者图像分辨率本身很小,有效区域占比很低,边缘高响应值没有被剔除干净,于是响应值最大的区域集中在靠近边缘的像素上。

一个隐蔽的原因是边界外像素访问时,某些实现直接用0填充(比如 OpenCV copyMakeBorder 默认 BORDER_CONSTANT 填充0),边缘处灰度差被错误放大,形成高响应区。如果是这个原因,需要手动剔除边界区域再做筛选。

5.2 问题:Harris响应图全黑或者全白

响应图全黑,说明没有任何一个像素的响应超过阈值下限,排查方向是输入图像的数据类型。如果原图是CV_8UC1但直接传给cornerHarris而没做convertTo(CV_32F),有些版本会抛出异常,有些版本计算结果全部为0。正确做法是先把灰度图转成32位浮点。

响应图全白,常见于阈值设置过低或者归一化代码写错。OpenCV库函数运行正常的话,响应图通常是一张小范围数值的浮点图,需要一个合理的阈值筛选,而不是直接拿来看图。

5.3 问题:高纹理区域角点过密,低纹理区域一个角点也没有

这是特征点检测的经典问题——分布不均。视觉SLAM里特征点分布如果过于集中,会导致位姿估计退化。解决思路不是调参数,而是做空间网格化筛选:把图像分成网格,保证每个网格最多提取一定数量的角点。先对所有候选点按响应值排序,然后逐个放入网格,一旦某个网格已满就跳过后续点。

我在实际项目里加了一个约束:网格大小取决于图像分辨率,800x600的图像用40x40的网格尺寸比较合适。如果特征点需求是200个,那么每个网格平均1到2个点即可。

5.4 问题:同一场景不同光照下检测结果不稳定

角点检测对光照变化敏感,因为灰度差和梯度的绝对值直接参与响应计算。同一个物理角点在暗环境和高亮环境下的响应值相差很大,阈值固定时检测结果就不稳定。

缓解方案有几个。一是检测前先做直方图均衡化或者自适应直方图均衡化,把光照差异拉平;二是用梯度方向而不是梯度幅值构造描述子,但这已经是特征描述阶段的问题而不是检测阶段的问题了;三是用局部自适应阈值替代全局阈值,比如对响应图做分块处理,每块单独计算筛选用阈值。

5.5 问题:亚像素精化后角点坐标跳动

很多人以为Harris检测完再用cornerSubPix做亚像素精化就能得到精确坐标,但有时候精化后的点在帧与帧之间跳动幅度比原始整数坐标还大。这通常是因为cornerSubPix的迭代参数设置不当,比如窗口太小没有覆盖到足够的梯度信息,或者终止条件中的精度阈值太小导致迭代无法收敛。

我一般把搜索窗口设为9x9以上,终止条件设为最大迭代30次、精度0.001。如果还是跳动,考虑先对灰度图做轻度的双边滤波或高斯滤波,降低噪声对梯度计算的干扰,再来做精化。

6. 选型建议与个人经验总结

做了几个实际项目后,我对这三种算法的选型形成一个比较清晰的判断逻辑。如果你的项目是摄影测量、标定板检测这类对坐标精度要求极高的场景,优先考虑Forstner。但要做好心理准备:它的两个阈值需要花时间调整,而且整个计算过程中梯度计算、窗口大小都需要细致设计。

如果你做的是视觉SLAM、图像拼接、全景匹配这类需要大规模特征点且要求运行稳定的场景,Harris是首选,配合OpenCV自带函数和合理的非极大值抑制就能有不错的表现。再加上对响应图做归一化和空间网格筛选,基本能满足大多数视觉前端的需求。

Moravec虽然老,但在一些极低算力的嵌入式平台上还有用武之地——它的计算量最小,不需要浮点协方差矩阵分解,移位和加减运算就能完成。如果嵌入式平台不支持浮点运算或者性能很弱,Moravec的离散化思路反而是一个务实的选择。

还有一个容易被忽略的点:不管选哪种算法,在送入检测之前对图像做预处理都值得认真做。我自己习惯先做一次轻量的高斯滤波(3x3,sigma约0.8),去掉传感器噪声,避免噪声被梯度算子放大成假角点。如果场景光照变化剧烈,再考虑做对比度归一化。

三种算法的代码我都放在一起了,建议大家用同一张测试图逐个跑一遍,观察响应图的差异和最终角点分布的区别。自己动手对比过一遍,对这三种特征点的理解会完全不一样。后续如果大家有兴趣,我可以再写一篇关于角点响应图可视化的文章,把中间每一层的计算结果都以热力图形式展示出来,那样理解起来会更加直观。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询