Bootstrap

【opencv】第10章 角点检测

第10章 角点检测

10.1 Harris角点检测

10.1.1 兴趣点与角点

在图像处理和与计算机视觉领域,兴趣点(interest points),也被称作关键点 (key points)、特 征 点(feature points)。它被大量用于解决物体识别、图像识别、 图像匹配、视觉跟踪、三维重建等一系列的问题。我们不再观察整幅图,而是选 择某些特殊的点,然后对它们进行局部有的放矢地分析。如果能检测到足够多的 这种点,同时它们的区分度很高,并且可以精确定位稳定的特征,那么这个方法 就具有实用价值。

图像特征类型可以被分为如下三种:

  • 边 缘
  • 角点(感兴趣关键点)
  • 斑点(Blobs)(感兴趣区域)

其中,角点是个很特殊的存在。如果某一点在任意方向的一个微小变动都会 引起灰度很大的变化,那么我们就把它称之为角点。角点作为图像上的特征点, 包含有重要的信息,在图像融合和目标跟踪及三维重建中有重要的应用价值。它 们在图像中可以轻易地定位,同时,在人造物体场景,比如门、窗、桌等处也随 处可见。因为角点位于两条边缘的交点处,代表了两个边缘变化的方向上的点, 所以它们是可以精确定位的二维特征,甚至可以达到亚像素的精度。又由于其图 像梯度有很高的变化,这种变化是可以用来帮助检测角点的。需要注意的是,角 点与位于相同强度区域上的点不同,与物体轮廓上的点也不同,因为轮廓点难以 在相同的其他物体上精确定位。

另外,关于角点的具体描述可以有如下几种:

  • 一阶导数(即灰度的梯度)的局部最大所对应的像素点;
  • 两条及两条以上边缘的交点;
  • 图像中梯度值和梯度方向的变化速率都很高的点;
  • 角点处的一阶导数最大,二阶导数为零,它指示了物体边缘变化不连续的 方向。

10.1.2 角点检测

现有的角点检测算法并不是都十分的健壮。很多方法都要求有大量的训练集 和冗余数据来防止或减少错误特征的出现。另外,角点检测方法的一个很重要的 评价标准是其对多幅图像中相同或相似特征的检测能力,并且能够应对光照变化、 图像旋转等图像变化。
在当前的图像处理领域,角点检测算法可归纳为以下三类。

  • 基于灰度图像的角点检测
  • 基于二值图像的角点检测
  • 基于轮廓曲线的角点检测

而基于灰度图像的角点检测又可分为基于梯度、基于模板和基于模板梯度组 合三类方法。其中基于模板的方法主要考虑像素领域点的灰度变化,即图像亮度 的变化,将与邻点亮度对比足够大的点定义为角点。常见的基于模板的角点检测 算法有Kitchen-Rosenfeld 角点检测算法,Harris 角点检测算法、KLT 角点检测算 法 及SUSAN 角点检测算法。
接下来,让我们一起来了解harris角点检测。

10.1.3 harris角 点 检 测

harris 角点检测是一种直接基于灰度图像的角点提取算法,稳定性高,尤其对 L 型角点检测精度高。但由于采用了高斯滤波,运算速度相对较慢,角点信息有 丢失和位置偏移的现象,而且角点提取有聚簇现象。

10.1.4 实 现Harris 角点检测:cornerHarris(函数

cornerHarris 函数用于在OpenCV 中 运 行Harris 角点检测算子来进行角点检 测。和cornerMinEigenVal() 以 及cornerEigenValsAndVecs() 函数类似,cornerHarris 函数对于每一个像素(x,y) 在 blockSize×blockSize 邻域内,计算2x2 梯度的协 方差矩阵M(x,y), 接着它计算如下式子:
d s t ( x , y ) = d e t M K ) − k ⋅ ( t r M × ) 2 dst(x,y)=detMK)-k·(trM×)² dst(x,y)=detMK)k(trM×)2
就可以找出输出图中的局部最大值,即找出了角点。 其函数原型和参数解析如下。

void cornerHarris(InputArray src, OutputArray dst, int blockSize, int ksize, double k, intborderType = BORDER_DEFAULT)
  • 第 一 个参数,InputArray类 型 的src, 输入图像,即源图像,填Mat 类的对 象即可,且须为单通道8位或者浮点型图像。
  • 第二个参数,OutputArray 类 型 的dst, 函数调用后的运算结果存在这里, 即这个参数用于存放Harris 角点检测的输出结果,和源图片有一样的尺寸 和类型。
  • 第三个参数,int 类 型 的 blockSize, 表示邻域的大小,更多详细信息在 cornerEigen ValsAndVecs()中有讲到。
  • 第四个参数,int类 型 的ksize,表示 Sobel()算子的孔径大小。
  • 第五个参数,double 类型的k,Harris 参数。
  • 第六个参数,int类 型 的borderType, 图像像素的边界模式。注意它有默认 值 BORDER_DEFAULT 。 更详细的解释,参考borderInterpolate() 函数。

讲解完这个函数,我们看一个Harris 角点检测示例程序,其中还用到了之前 讲到的 threshold 函数。

void Test65() {
    Mat srcImage = imread("school.jpg", 0);
    imshow("srcImage", srcImage);

    //进行Harris角点检测找出角点

    Mat cornerStrength;
    cornerHarris(srcImage, cornerStrength, 2, 3, 0.01);

    //二值化

    Mat harrisCorner;
    threshold(cornerStrength, harrisCorner, 0.00001, 255, THRESH_BINARY);
    imshow("harrisCorner", harrisCorner);

    waitKey(0);

}

在这里插入图片描述

在这里插入图片描述

10.1.5 综 合 示 例 :harris 角点检测与绘制

本次综合示例为调节滚动条来控制阈值,以控制的 harris 检测角点的数量。 一共有三个图片窗口,分别为显示原始图的窗口、包含滚动条的彩色效果图窗口, 以及灰度图效果图窗口。

namespace test66 {
    Mat g_srcImage, g_srcImage1, g_grayImage;
    int thresh = 30;
    int max_thresh = 175;

    void on_CornerHarris(int, void*) {
        Mat dstImage, normImage, scaledImage;

        dstImage = Mat::zeros(g_srcImage.size(), CV_32FC1);
        g_srcImage1 = g_srcImage.clone();

        //角点检测

        cornerHarris(g_grayImage, dstImage, 2, 3, 0.04, BORDER_DEFAULT);

        //归一化与转换

        normalize(dstImage, normImage, 0, 255, NORM_MINMAX, CV_32FC1, Mat());
        convertScaleAbs(normImage, scaledImage); //转换为8为无符号整型

        for (int j = 0; j < normImage.rows; ++j) {
            for (int i = 0; i < normImage.cols; ++i) {
                if ((int)normImage.at<float>(j, i) > thresh + 80) {
                    circle(g_srcImage1, Point(i, j), 5, Scalar(10, 10, 255), 2, 8, 0);
                    circle(scaledImage, Point(i, j), 5, Scalar(0 ,10, 255), 2, 8, 0);
                }
            }
        }
        imshow("srcImage", g_srcImage);
        imshow("scaledImage", scaledImage);

    }

    void Test() {
        g_srcImage = imread("school.jpg");
        imshow("srcImage", g_srcImage);

        g_srcImage1 = g_srcImage.clone();
        cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);

        namedWindow("srcImage");
        createTrackbar("value", "srcImage", &thresh, max_thresh, on_CornerHarris);

        waitKey(0);
    }


}

void Test66() {
    test66::Test();
}

在这里插入图片描述
在这里插入图片描述

10.2 Shi-Tomasi角点检测

10.2.1 Shi-Tomasi角点检测概述

除 了 利 用Harris 进行角点检测之外,我们通常还可以利用Shi-Tomasi 方法进 行角点检测。Shi-Tomasi 算 法 是Harris 算法的改进,此算法最原始的定义是将矩 阵M 的行列式值与M 的迹相减,再将差值同预先给定的阈值进行比较。后来Shi 和Tomasi 提出改进了方法,若两个特征值中较小的一个大于最小阈值,则会得到 强角点。

由 于Shi-Tomasi 算子是1994年在文章《Good Features to Track》中被提出的, OpenCV 实现此算法的函数名便定义为goodFeaturesToTrack。下面我们来看看此 函数的用法。

10.2.2 确定图像强角点:goodFeaturesToTrackO 函数

goodFeaturesToTrack() 函 数 结 合 了Shi-Tomasi 算子,用于确定图像的强角点 。

void goodFeaturesToTrack(
    InputArray image,
    OutputArray corners, int maxCorners,
    double qualityLevel,
    double minDistance,
    InputArray mask = noArray(), int blockSize = 3,
    bool useHarrisDetector = false,
    double k = 0.04)
  • 第一个参数,InputArray类型的image, 输入图像,须为8位或浮点型32 位单通道图像。
  • 第二个参数,OutputArray类型的corners,检测到的角点的输出向量。
  • 第三个参数,int类型的maxCorners, 角点的最大数量。
  • 第四个参数,double类型的qualityLevel,角点检测可接受的最小特征值。 其实实际用于过滤角点的最小特征值是qualityLevel 与图像中最大特征值 的乘积。所以qualityLevel 通常不会超过1(常用的值为0.10或者0.01)。 而检测完所有的角点后,还要进一步剔除掉一些距离较近的角点。
  • 第五个参数,double类型的minDistance,角点之间的最小距离,此参数用 于保证返回的角点之间的距离不小于minDistance 个像素。
  • 第六个参数,InputArray类型的mask, 可选参数,表示感兴趣区域,有默 认值noArray()。若此参数非空(需为CV_8UC1 类型,且和第一个参数image 有相同的尺寸),便用于指定角点检测区域。
  • 第七个参数,int类型的blockSize,有默认值3,是计算导数自相关矩阵时 指定的邻域范围。
  • 第八个参数,bool 类型的useHarrisDetector,默认值 false,指示是否使用 Harris 角点检测。
  • 第九个参数,double类型的k, 有默认值0.04,为用于设置Hessian自相关 矩阵行列式的相对权重的权重系数。
    另外值得一提的是,goodFeaturesToTrack 函数可用来初始化一个基于点的对 象跟踪操作。

10.2.3 综 合 示 例 :Shi-Tomasi 角点检测

下面是一个详细注释的以goodFeaturesToTrack 函数为核心, 进 行Shi-Tomasi 角点检测的示例程序。

namespace test67 {
    Mat g_srcIMage, g_grayImage;
    int g_maxCornerNumber = 33;
    int g_maxTrackerbarNumber = 500;
    RNG g_rng(12345);

    void onGoodFeaturesToTrack(int, void*) {
        if (g_maxCornerNumber <= 1) {
            g_maxCornerNumber = 1;
        }

        std::vector<Point2f>corners;
        double qualityLevel = 0.01;
        double minDistance = 10;
        int blockSize = 3;
        double k = 0.04;

        Mat copy = g_srcImage.clone();

        goodFeaturesToTrack(g_grayImage, corners, g_maxCornerNumber, qualityLevel, minDistance, Mat(), blockSize, false, k);

        std::cout << "total corners:" << corners.size() << std::endl;

        //绘制角点

        int r = 4;
        for (int i = 0; i < corners.size(); ++i) {
            circle(copy, corners[i], r, Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255)), -1, 8, 0);
            imshow("srcImage", copy);
        }

        Size winSize = Size(5, 5);
        Size zeroZone = Size(-1, -1);

        TermCriteria criteria = TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 40, 0.001);

        //计算出亚像素角点位置

        cornerSubPix(g_grayImage, corners, winSize, zeroZone, criteria);

        //输出角点信息

        for (int i = 0; i < corners.size(); ++i) {
            std::cout << " \t" << "pos[" << i << "]" << " = (" << corners[i].x << "," << corners[i].y << ")" << std::endl;
        }
    }

    void Test() {
        g_srcImage = imread("school.jpg");
        cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);

        namedWindow("srcImage");
        createTrackbar("value", "srcImage", &g_maxCornerNumber, g_maxTrackerbarNumber, onGoodFeaturesToTrack);
        imshow("srcImage", g_srcImage);

        waitKey(0);

    }
}

void Test67() {
    test67::Test();
}

在这里插入图片描述

在这里插入图片描述

10.3 亚像素级角点检测

10.3.1 背景概述

若我们进行图像处理的目的不是提取用于识别的特征点而是进行几何测量, 这通常需要更高的精度,而函数 goodFeaturesToTrack) 只能提供简单的像素的坐 标值,也就是说,有时候会需要实数坐标值而不是整数坐标值。
亚像素级角点检测的位置在摄像机标定、跟踪并重建摄像机的轨迹,或者重 建被跟踪目标的三维结构时,是一个基本的测量值。
下面我们将讨论如何将所求得的角点位置精确到亚像素级精度。 一个向量和 与其正交的向量的点积为0,角点则满足如图10.11所示情况。
在这里插入图片描述

其中,(a) 点 p 附近的图像是均匀的,其梯度为0;(b) 边缘的梯度与沿 边缘方向的q-p 向量正交。在图中的两种情况下,p 点 梯 度 与q-p 向量的点积 均为0。

图10. 11中,我们假设起始角点q 在实际亚像素级角点的附近。检测所有的 q-p 向量。若点p 位于一个均匀的区域,则点p 处的梯度为0。若q-p 向量的方向 与边缘的方向一致,则此边缘上p 点处的梯度与q-p 向量正交,在这两种情况下, p 点处的梯度与qp 向量的点积为0。我们可以在p 点周围找到很多组梯度以及相 关的向量q-p, 令其点集为0,然后可以通过求解方程组,方程组的解即为角点q 的亚像素级精度的位置,也就是精确的角点位置。
OpenCV 为我们提供了cornerSubPix() 函数,用于发现亚像素精度的角点位 置。

10.3.2 寻找亚像素角点:cornerSubPix(函数

cornerSubPix 函数用于寻找亚像素角点位置(不是整数类型的位置,而是更 精确的浮点类型位置)。

void cornerSubPix(
    InputArray image,
    InputOutputArray corners,
    Size winSize,
    Size zeroZone,
    TermCriteria criteria)
  • 第 一 个参数,InputArray 类型的image, 输入图像,即源图像。
  • 第二个参数,InputOutputArray类 型 的corners,提供输入角点的初始坐标和 精确的输出坐标。
  • 第三个参数,Size类型的winSize, 搜索窗口的一半尺寸。若winSize=Size (5,5),那么就表示使用(52+1)×(52+1)=11×11大小的搜索窗口。
  • 第四个参数,Size 类 型 的zeroZone, 表示死区的一半尺寸。而死区为不对 搜索区的中央位置做求和运算的区域,用来避免自相关矩阵出现的某些可 能的奇异性。值为(-1,-1)表示没有死区。
  • 第五个参数,TermCriteria类型的criteria,求角点的迭代过程的终止条件。 即角点位置的确定,要么迭代数大于某个设定值,或者是精确懂达到某个 设定值。criteria 可以是最大迭代数目,或者是设定的精确度,也可以是它 们的组合。

10.3.3 综合示例:亚像素级角点检测

这个程序在上一小节Shi-Tomasi 角点检测示例程序的基础上,在 on_GoodFeaturesToTrack (回调函数中加上以下进行亚像素角点检测的代码,就成 为了亚像素级角点检测的示例程序,需添加的代码如下。

namespace test67 {
    Mat g_srcIMage, g_grayImage;
    int g_maxCornerNumber = 33;
    int g_maxTrackerbarNumber = 500;
    RNG g_rng(12345);

    void onGoodFeaturesToTrack(int, void*) {
        if (g_maxCornerNumber <= 1) {
            g_maxCornerNumber = 1;
        }

        std::vector<Point2f>corners;
        double qualityLevel = 0.01;
        double minDistance = 10;
        int blockSize = 3;
        double k = 0.04;

        Mat copy = g_srcImage.clone();

        goodFeaturesToTrack(g_grayImage, corners, g_maxCornerNumber, qualityLevel, minDistance, Mat(), blockSize, false, k);

        std::cout << "total corners:" << corners.size() << std::endl;

        //绘制角点

        int r = 4;
        for (int i = 0; i < corners.size(); ++i) {
            circle(copy, corners[i], r, Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255)), -1, 8, 0);
            imshow("srcImage", copy);
        }

        Size winSize = Size(5, 5);
        Size zeroZone = Size(-1, -1);

        TermCriteria criteria = TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 40, 0.001);

        //计算出亚像素角点位置

        cornerSubPix(g_grayImage, corners, winSize, zeroZone, criteria);

        //输出角点信息

        for (int i = 0; i < corners.size(); ++i) {
            std::cout << " \t" << "pos[" << i << "]" << " = (" << corners[i].x << "," << corners[i].y << ")" << std::endl;
        }
    }

    void Test() {
        g_srcImage = imread("school.jpg");
        cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);

        namedWindow("srcImage");
        createTrackbar("value", "srcImage", &g_maxCornerNumber, g_maxTrackerbarNumber, onGoodFeaturesToTrack);
        imshow("srcImage", g_srcImage);

        waitKey(0);

    }
}

void Test67() {
    test67::Test();
}

在这里插入图片描述

10.4 本章小结

本章我们讲解了Harris 角点检测和Shi-Tomasi 角点检测,以及一种亚像素角 点检测方法。当然,也可以自己制作角点检测的函数,需要用到cornerMinEigen Val 函数和minMaxLoc 函数。最后的特征点选取,判断条件要根据自己的情况编辑。 如果对特征点,角点的精度要求更高,可以用cornerSubPix 函数将角点定位到子 像 素 。

本章核心函数清单

函数名称说明对应讲解章节
cornerHarris运行Harris角点检测算子来进行角点检测10.1.4
goodFeaturesToTrack结合Shi-Tomasi算子确定图像的强角点 10.2.2
cornerSubPix寻找亚像素角点位置10.3.2

本章示例程序清单

示例程序序号程序说明对应章节
85实现Harris角点检测:cornerHarris()函数的使用10.1.4
86harris角点检测与绘制10.1.5
87Shi-Tomasi角点检测10.2.3
88亚像素级角点检测10.3.3
;