角点 (corners) 的定义有两个版本:两条边缘的交点,或 邻域内具有两个主方向的特征点
从人眼来看,角点是图像亮度发生剧烈变化的点 或 边缘曲线上曲率为极大值的点。例如,下图 E 和 F 便是典型的角点
1 检测思路
在图像中定义一个局部小窗口,然后沿各个方向移动时,会出现 a) b) c) 三种情况,分别对应平坦区、边缘和角点:
a) 窗口内的图像强度,在窗口向各个方向移动时,都没有发生变化,则窗口内都是 “平坦区”,不存在角点
b) 窗口内的图像强度,在窗口向某一个 (些) 方向移动时,发生较大变化;而在另一些方向不发生变化,那么,窗口内可能存在 “边缘”
c) 窗口内的图像强度,在窗口向各个方向移动时,都发生了较大的变化,则认为窗口内存在 “角点”
a) flat region b) edge c) corner
2 Harris 角点
2.1 泰勒展开
图像在点 处的灰度值为 ,当在 方向上平移 ,且 方向上平移 时,图像灰度值的变化为
的偏导数分别记为 和 ,则上式用二元一阶泰勒级数近似展开
写成矩阵形式
则有
, 假定
2.2 判别方法
定义角点响应值 ,根据响应值的大小,判断小窗口内是否包含角点:
1) “平坦区”:|R| 小的区域,即 和 都小;
2) “边缘”: R <0 的区域,即 或反之;
3) “角点”: R 大的区域,即 和 都大且近似相等
为了便于直观理解,绘制成 平面如下图:
2.3 cornerHarris()
OpenCV 中 Harris 角点检测的函数为:
1 2 3 4 5 6 7 8 | void cornerHarris ( InputArray src, // 输入图像 (单通道,8位或浮点型) OutputArray dst, // 输出图像 (类型 CV_32FC1,大小同 src) int blockSize, // 邻域大小 int ksize, // Sobel 算子的孔径大小 double k, // 经验参数,取值范围 0.04 ~ 0.06 int borderType = BORDER_DEFAULT // 边界模式 ) |
2.4 代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | #include "opencv2/highgui.hpp" #include "opencv2/imgproc.hpp" using namespace cv; // Harris corner parameters int kThresh = 150; int kBlockSize = 2; int kApertureSize = 3; double k = 0.04; int main() { // read image Mat src, src_gray; src = imread( "building.jpg" ); if (src.empty()) return -1; cvtColor(src, src_gray, COLOR_BGR2GRAY); Mat dst, dst_norm, dst_norm_scaled; // Harris corner detect cornerHarris(src_gray, dst, kBlockSize, kApertureSize, k); normalize(dst, dst_norm, 0, 255, NORM_MINMAX, CV_32FC1); convertScaleAbs(dst_norm, dst_norm_scaled); // draw detected corners for ( int j=0; j < dst_norm.rows; j++) { for ( int i=0; i<dst_norm.cols;i++) { if (( int )dst_norm.at< float >(j,i) > kThresh) { circle(src, Point(i, j), 2, Scalar(0,255,0)); } } } imshow( "harris corner" , src); waitKey(); } |
检测结果:
3 Shi-Tomasi 角点
Shi-Tomasi 角点是 Harris 角点的改进,在多数情况下,其检测效果要优于 Harris。二者的区别在于,Shi-Tomasi 选取 和 中的最小值,作为新的角点响应值
则相应的 平面为:
3.1 goodFeaturesToTrack()
OpenCV 中 Shi-Tomasi 角点检测函数为:
1 2 3 4 5 6 7 8 9 10 11 | void goodFeaturesToTrack ( InputArray image, // 输入图像 (单通道,8位或浮点型32位) OutputArray corners, // 检测到的角点 int maxCorners, // 最多允许返回的角点数量 double qualityLevel, // double minDistance, // 角点间的最小欧拉距离 InputArray mask = noArray(), // int blockSize = 3, // bool useHarrisDetector = false , // double k = 0.04 // ) |
3.2 代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #include "opencv2/highgui.hpp" #include "opencv2/imgproc.hpp" using namespace cv; using namespace std; int kMaxCorners = 1000; double kQualityLevel = 0.1; double kMinDistance = 1; int main() { // read image Mat src, src_gray; src = imread( "building.jpg" ); if (src.empty()) return -1; cvtColor(src, src_gray, COLOR_BGR2GRAY); // Shi-Tomasi corner detect vector<Point2f> corners; goodFeaturesToTrack(src_gray, corners, kMaxCorners, kQualityLevel, kMinDistance); // draw and show detected corners for ( size_t i = 0; i < corners.size(); i++) { circle(src, corners[i], 2.5, Scalar(0, 255, 0)); } imshow( "Shi-Tomasi corner" , src); waitKey(); } |
检测结果:
4 角点检测的实现
分析 cornerHarris() 源码,复现计算步骤:Sobel 算子求解 dx 和 dy -> 矩阵 M -> boxFilter -> 每个像素的角点响应值 R,对应 C++ 代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | #include <iostream> #include "opencv2/highgui.hpp" #include "opencv2/imgproc.hpp" using namespace cv; using namespace std; int kApertureSize = 3; int kBlockSize = 2; double k = 0.04; int kThresh = 150; int main() { // read image Mat src, src_gray; src = imread( "chessboard.png" ); if (src.empty()) return -1; cvtColor(src, src_gray, COLOR_BGR2GRAY); // determine scale double scale = ( double )(1 << (kApertureSize - 1)) * kBlockSize; scale *= 255.0; scale = 1.0 / scale; // 1) dx, dy Mat Dx, Dy; Sobel(src_gray, Dx, CV_32F, 1, 0, kApertureSize, scale); Sobel(src_gray, Dy, CV_32F, 0, 1, kApertureSize, scale); // 2) cov matrix Size size = src_gray.size(); Mat cov(size, CV_32FC3); for ( int i = 0; i < size.height; i++) { float * cov_data = cov.ptr< float >(i); const float * dxdata = Dx.ptr< float >(i); const float * dydata = Dy.ptr< float >(i); for ( int j=0; j < size.width; j++) { float dx = dxdata[j]; float dy = dydata[j]; cov_data[j * 3] = dx * dx; cov_data[j * 3 + 1] = dx * dy; cov_data[j * 3 + 2] = dy * dy; } } // 3) box filter boxFilter(cov, cov, cov.depth(), Size(kBlockSize, kBlockSize), Point(-1,-1), false ); // 4) R Mat dst(size,CV_32FC1); Size size_cov = cov.size(); for ( int i = 0; i < size_cov.height; i++) { const float * ptr_cov = cov.ptr< float >(i); float * ptr_dst = dst.ptr< float >(i); for ( int j=0; j < size_cov.width; j++) { float a = ptr_cov[j * 3]; float b = ptr_cov[j * 3 + 1]; float c = ptr_cov[j * 3 + 2]; ptr_dst[j] = ( float )(a * c - b * b - k * (a + c) * (a + c)); } } #if HARRIS_OPENCV // compare with cornerHarris() cornerHarris(src_gray, dst, kBlockSize, kApertureSize, k); #endif // 5) normalization Mat dst_norm, dst_norm_scaled; normalize(dst, dst_norm, 0, 255, NORM_MINMAX, CV_32FC1); convertScaleAbs(dst_norm, dst_norm_scaled); // 6) drawing corners for ( int j = 0; j < dst_norm.rows; j++) { for ( int i = 0; i < dst_norm.cols; i++) { if (( int )dst_norm.at< float >(j, i) > 150) { circle(src, Point(i, j), 2, Scalar(0, 255, 0)); } } } imshow( "Harris corner" , src); waitKey(); } |
检测结果:将求得的角点响应值,输出 txt 文件,与 cornerHarris() 输出的 进行比较,结果几乎完全相同 (只有几处小数点后7位的值不同)
5 亚像素角点
5.1 cornerSubpix()
亚像素角点提取的函数 cornerSubPix(),常用于相机标定中,定义如下:
1 2 3 4 5 6 7 | void cornerSubPix( InputArray image, // 输入图象(单通道,8位或浮点型) InputOutputArray corners, // 亚像素精度的角点坐标 Size winSize, // 搜索窗口尺寸的 1/2 Size zeroZone, // TermCriteria criteria // 迭代终止准则 ) |
5.2 代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | #include <iostream> #include "opencv2/highgui.hpp" #include "opencv2/imgproc.hpp" using namespace cv; using namespace std; int kMaxCorners = 40; double kQualityLevel = 0.01; double kMinDistance = 50; int main() { // read image Mat src, src_gray; src = imread( "chessboard.png" ); if (src.empty()) return -1; cvtColor(src, src_gray, COLOR_BGR2GRAY); // Shi-Tomasi corner detect vector<Point2f> corners; goodFeaturesToTrack(src_gray, corners, kMaxCorners, kQualityLevel, kMinDistance); // draw and show detected corners for ( size_t i = 0; i < corners.size(); i++) { circle(src, corners[i], 3, CV_RGB(0, 255, 0)); } imshow( "Shi-Tomasi corner" , src); TermCriteria criteria = TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 40, 0.001); // find corner positions in subpixel cornerSubPix(src_gray, corners, Size(5, 5), Size(-1, -1), criteria); for ( size_t i = 0; i < corners.size(); i++) { cout << "Corner[" << i << "]: (" << corners[i].x << "," << corners[i].y << ")" << endl; } waitKey(); } |
输入棋盘格5行8列,对应7x4个角点,图像的分辨率为 600*387,则所有角点的理论坐标如下表:
角点的图象坐标值输出如下:
参考
《图像局部不变性特征与描述》 第 3 章
http://www.cse.psu.edu/~rtc12/CSE486/
OpenCV Tutorials / feature2d module / Harris corner detector
OpenCV-Python Tutorials / Feature Detection and Description / Shi-Tomasi Corner Detector & Good Features to Track
OpenCV Tutorials / feature2d module / Detecting corners location in subpixels
原文链接: http://www.cnblogs.com/xinxue/
专注于机器视觉、OpenCV、C++ 编程
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人