随笔 - 54  文章 - 0  评论 - 45  阅读 - 45万 

    角点 (corners) 的定义有两个版本:两条边缘的交点,或  邻域内具有两个主方向的特征点

    从人眼来看,角点是图像亮度发生剧烈变化的点 或 边缘曲线上曲率为极大值的点例如,下图 E 和 F 便是典型的角点

        

 

1  检测思路

    在图像中定义一个局部小窗口,然后沿各个方向移动时,会出现 a) b) c) 三种情况,分别对应平坦区、边缘和角点:

     a)  窗口内的图像强度,在窗口向各个方向移动时,都没有发生变化,则窗口内都是 “平坦区”,不存在角点

     b)  窗口内的图像强度,在窗口向某一个 (些) 方向移动时,发生较大变化;而在另一些方向不发生变化,那么,窗口内可能存在 “边缘

     c)  窗口内的图像强度,在窗口向各个方向移动时,都发生了较大的变化,则认为窗口内存在 “角点

        

                 a)  flat region              b)  edge                     c)  corner

 

2  Harris 角点

2.1  泰勒展开

    图像在点 (x,y) 处的灰度值为 I(x,y),当在 x 方向上平移 Δu,且 y 方向上平移 Δv 时,图像灰度值的变化为

 E(Δu,Δv)=x,yw(x,y)window function[I(x+Δu,y+Δv)shifted intensityI(x,y)intensity]2

    I(x,y) 的偏导数分别记为 IxIy,则上式用二元一阶泰勒级数近似展开

    x,y[I(x+Δu,y+Δv)I(x,y)]2x,y[I(x,y)+ΔuIx+ΔvIyI(x,y)]2=x,y[Δu2Ix2+2ΔuΔvIxIy+Δv2Iy2]

    写成矩阵形式

    E(Δu,Δv)[ΔuΔv](x,yw(x,y)[Ix2IxIyIxIyIy2])[ΔuΔv]

    则有

    E(Δu,Δv)[ΔuΔv]M[ΔuΔv],    假定  M=x,yw(x,y)[Ix2IxIyIxIyIy2]

2.2  判别方法  

    定义角点响应值 R=det(M)k(trace(M))2=λ1λ2k(λ1+λ2)2根据响应值的大小,判断小窗口内是否包含角点:

     1) “平坦区”:|R| 小的区域,即 λ1λ2 都小;

     2)  “边缘”: R <0 的区域,即 λ1>>λ2 或反之;

     3)  “角点”: R 大的区域,即 λ1λ2 都大且近似相等    

    为了便于直观理解,绘制成 λ1λ2 平面如下图:

       

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 选取 λ1λ2 中的最小值,作为新的角点响应值 R

  R=min(λ1,λ2)

  则相应的 λ1λ2 平面为:

  

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();
}  

   检测结果:将求得的角点响应值R,输出 txt 文件,与 cornerHarris() 输出的 R 进行比较,结果几乎完全相同 (只有几处小数点后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 章

    Harris 角点

    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

 

posted on   飞鸢逐浪  阅读(2291)  评论(0编辑  收藏  举报
编辑推荐:
· 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训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示