第 8 章 檢測(cè)興趣點(diǎn)


本章包括以下內(nèi)容:

  • 檢測(cè)圖像中的角點(diǎn);
  • 快速檢測(cè)特征;
  • 尺度不變特征的檢測(cè);
  • 多尺度FAST 特征的檢測(cè)。


8.2 檢測(cè)圖像中的角點(diǎn)

Harris 特征檢測(cè)是檢測(cè)角點(diǎn)的經(jīng)典方法,本節(jié)將詳細(xì)探討這個(gè)方法。

OpenCV 中檢測(cè)Harris 角點(diǎn)的基本函數(shù)是cv::cornerHarris,它的使用方法非常簡(jiǎn)單。調(diào)用該函數(shù)時(shí)輸入一幅圖像,返回的結(jié)果是一個(gè)浮點(diǎn)數(shù)型圖像,其中每個(gè)像素表示角點(diǎn)強(qiáng)度。然后對(duì)輸出圖像閾值化,以獲得檢測(cè)角點(diǎn)的集合。代碼如下所示:

    // 檢測(cè)Harris 角點(diǎn)
    cv::Mat cornerStrength;
    cv::cornerHarris(image, // 輸入圖像
        cornerStrength, // 角點(diǎn)強(qiáng)度的圖像
        3, // 鄰域尺寸
        3, // 口徑尺寸
        0.01); // Harris 參數(shù)
        // 對(duì)角點(diǎn)強(qiáng)度閾值化
    cv::Mat harrisCorners;
    double threshold = 0.0001;
    cv::threshold(cornerStrength, harrisCorners,
        threshold, 255, cv::THRESH_BINARY_INV);

這是原始圖像。

church01.jpg

結(jié)果是一個(gè)二值分布圖像,如下圖所示。為了能更直觀地觀察圖像,此處進(jìn)行了反轉(zhuǎn)處理(即用cv::THRESH_BINARY_INV 代替cv::THRESH_BINARY,用黑色表示被檢測(cè)的角點(diǎn))。

result.jpg

在前面的函數(shù)中,我們發(fā)現(xiàn)興趣點(diǎn)檢測(cè)法需要使用幾個(gè)參數(shù),這可能會(huì)導(dǎo)致該方法很難調(diào)節(jié)。此外,得到的角點(diǎn)分布圖中包含很多聚集的角點(diǎn)像素,而不是我們想要檢測(cè)的具有明確定位的角點(diǎn)。因此,我們來定義一個(gè)檢測(cè)Harris 角點(diǎn)的類,以改進(jìn)角點(diǎn)檢測(cè)方法。

class HarrisDetector {

private:

    // 32 位浮點(diǎn)數(shù)型的角點(diǎn)強(qiáng)度圖像
    cv::Mat cornerStrength;
    // 32 位浮點(diǎn)數(shù)型的閾值化角點(diǎn)圖像
    cv::Mat cornerTh;
    // 局部最大值圖像(內(nèi)部)
    cv::Mat localMax;
    // 平滑導(dǎo)數(shù)的鄰域尺寸
    int neighborhood;
    // 梯度計(jì)算的口徑
    int aperture;
    // Harris 參數(shù)
    double k;
    // 閾值計(jì)算的最大強(qiáng)度
    double maxStrength;
    // 計(jì)算得到的閾值(內(nèi)部)
    double threshold;
    // 非最大值抑制的鄰域尺寸
    int nonMaxSize;
    // 非最大值抑制的內(nèi)核
    cv::Mat kernel;

public:

    HarrisDetector() : neighborhood(3), aperture(3),
        k(0.01), maxStrength(0.0),
        threshold(0.01), nonMaxSize(3) {
        // 創(chuàng)建用于非最大值抑制的內(nèi)核
        setLocalMaxWindowSize(nonMaxSize);
    }

檢測(cè)Harris 角點(diǎn)需要兩個(gè)步驟。首先是計(jì)算每個(gè)像素的Harris 值:

// 計(jì)算Harris 角點(diǎn)
void detect(const cv::Mat& image) {
    // 計(jì)算Harris
    cv::cornerHarris(image, cornerStrength,
        neighbourhood,// 鄰域尺寸
        aperture, // 口徑尺寸
        k); // Harris 參數(shù)

    // 計(jì)算內(nèi)部閾值
    cv::minMaxLoc(cornerStrength, 0, &maxStrength);

    // 檢測(cè)局部最大值
    cv::Mat dilated; // 臨時(shí)圖像
    cv::dilate(cornerStrength, dilated, cv::Mat());
    cv::compare(cornerStrength, dilated, localMax, cv::CMP_EQ);

然后,用指定的閾值獲得特征點(diǎn)。因?yàn)镠arris 值的可選范圍取決于選擇的參數(shù),所以閾值被作為質(zhì)量等級(jí),用最大Harris 值的一個(gè)比例值表示:

// 用Harris 值得到角點(diǎn)分布圖
cv::Mat getCornerMap(double qualityLevel) {

    cv::Mat cornerMap;

    // 對(duì)角點(diǎn)強(qiáng)度閾值化
    threshold = qualityLevel * maxStrength;
    cv::threshold(cornerStrength, cornerTh, threshold, 255,
        cv::THRESH_BINARY);

    // 轉(zhuǎn)換成8 位圖像
    cornerTh.convertTo(cornerMap, CV_8U);

    // 非最大值抑制
    cv::bitwise_and(cornerMap, localMax, cornerMap);

    return cornerMap;
}

這個(gè)方法將返回一個(gè)被檢測(cè)特征的二值角點(diǎn)分布圖。因?yàn)镠arris 特征的檢測(cè)過程分為兩個(gè)方法,所以我們可以用不同的閾值來測(cè)試檢測(cè)結(jié)果(直到獲得適當(dāng)數(shù)量的特征點(diǎn)),而不必重復(fù)進(jìn)行耗時(shí)的計(jì)算過程。當(dāng)然,你也可以從以std::vector 形式表示的cv::Point 實(shí)例中得到Harris特征:

// 用Harris 值得到特征點(diǎn)
void getCorners(std::vector<cv::Point>& points, double qualityLevel) {

    // 獲得角點(diǎn)分布圖
    cv::Mat cornerMap = getCornerMap(qualityLevel);
    // 獲得角點(diǎn)
    getCorners(points, cornerMap);
}

// 用角點(diǎn)分布圖得到特征點(diǎn)
void getCorners(std::vector<cv::Point>& points,
    const cv::Mat& cornerMap) {
    // 迭代遍歷像素,得到所有特征
    for (int y = 0; y < cornerMap.rows; y++) {

        const uchar* rowPtr = cornerMap.ptr<uchar>(y);

        for (int x = 0; x < cornerMap.cols; x++) {

            // 如果它是一個(gè)特征點(diǎn)
            if (rowPtr[x]) {

                points.push_back(cv::Point(x, y));
            }
        }
    }
}

這個(gè)類通過增加非最大值抑制步驟,也改進(jìn)了Harris 角點(diǎn)的檢測(cè)過程,下一節(jié)會(huì)詳細(xì)解釋這個(gè)步驟?,F(xiàn)在可以用cv::circle 函數(shù)畫出檢測(cè)到的特征點(diǎn),方法如下所示:

// 在特征點(diǎn)的位置畫圓形
void drawOnImage(cv::Mat& image,
    const std::vector<cv::Point>& points,
    cv::Scalar color = cv::Scalar(255, 255, 255),
    int radius = 3, int thickness = 1) {

    std::vector<cv::Point>::const_iterator it = points.begin();
    // 針對(duì)所有角點(diǎn)
    while (it != points.end()) {
        // 在每個(gè)角點(diǎn)位置畫一個(gè)圓
        cv::circle(image, *it, radius, color, thickness);
        ++it;
    }
}

使用這個(gè)類檢測(cè)Harris 特征點(diǎn)的方法如下所示:

// 創(chuàng)建Harris 檢測(cè)器實(shí)例
HarrisDetector harris;
// 計(jì)算Harris 值
harris.detect(image);
// 檢測(cè)Harris 角點(diǎn)
std::vector<cv::Point> pts;
harris.getCorners(pts,0.02);
// 畫出Harris 角點(diǎn)
harris.drawOnImage(image,pts);

結(jié)果如下圖所示。

result.jpg


8.3 快速檢測(cè)特征

本節(jié)將介紹另一種特征點(diǎn)算子,叫作FAST(Features from Accelerated Segment Test,加速分割測(cè)試獲得特征)。這種算子專門用來快速檢測(cè)興趣點(diǎn)——只需對(duì)比幾個(gè)像素,就可以判斷它是否為關(guān)鍵點(diǎn)。

OpenCV 有檢測(cè)特征點(diǎn)的公共接口。

    // 關(guān)鍵點(diǎn)的向量
    std::vector<cv::KeyPoint> keypoints;
    // FAST 特征檢測(cè)器,閾值為40
    cv::Ptr<cv::FastFeatureDetector> ptrFAST =
        cv::FastFeatureDetector::create(40);
    // 檢測(cè)關(guān)鍵點(diǎn)
    ptrFAST->detect(image, keypoints);

OpenCV 也提供了在圖像上畫關(guān)鍵點(diǎn)的通用函數(shù):

    cv::drawKeypoints(image, // 原始圖像
        keypoints, // 關(guān)鍵點(diǎn)的向量
        image, // 輸出圖像
        cv::Scalar(255, 255, 255), // 關(guān)鍵點(diǎn)的顏色
        cv::DrawMatchesFlags::DRAW_OVER_OUTIMG); // 畫圖標(biāo)志

選擇這個(gè)畫圖標(biāo)志后,輸入圖像上會(huì)畫出關(guān)鍵點(diǎn),輸出結(jié)果如下所示。

result.jpg

有一種比較有趣的做法,就是用一個(gè)負(fù)數(shù)作為關(guān)鍵點(diǎn)顏色。這樣一來,畫每個(gè)圓時(shí)會(huì)隨機(jī)選用不同的顏色。

FAST 對(duì)角點(diǎn)的定義基于候選特征點(diǎn)周圍的圖像強(qiáng)度值。以某個(gè)點(diǎn)為中心做一個(gè)圓,根據(jù)圓上的像素值判斷該
點(diǎn)是否為關(guān)鍵點(diǎn)。如果存在這樣一段圓弧,它的連續(xù)長(zhǎng)度超過周長(zhǎng)的3/4,并且它上面所有像素的強(qiáng)度值都與圓心的強(qiáng)度值明顯不同(全部更暗或更亮),那么就認(rèn)定這是一個(gè)關(guān)鍵點(diǎn)。

這種測(cè)試方法非常簡(jiǎn)單,計(jì)算速度也很快。而且在它的原始公式中,算法還用了一個(gè)技巧來進(jìn)一步提高處理速度。如果我們測(cè)試圓周上相隔90 度的四個(gè)點(diǎn)(例如取上、下、左、右四個(gè)位置),就很容易證明:為了滿足前面的條件,其中必須有三個(gè)點(diǎn)都比圓心更亮或都比圓心更暗。

如果不滿足該條件,就可以立即排除這個(gè)點(diǎn),不需要檢查圓周上的其他點(diǎn)。這種方法非常高效,因?yàn)樵趯?shí)際應(yīng)用中,圖像中大部分像素都可以用這種“四點(diǎn)比較法”排除。

從概念上講,用于檢查像素的圓的半徑應(yīng)該作為方法的一個(gè)參數(shù)。但是根據(jù)經(jīng)驗(yàn),半徑為3時(shí)可以得到好的結(jié)果和較高的計(jì)算效率。因此需要在圓周上檢查16 個(gè)像素。

image.png

這里用來預(yù)測(cè)試的像素是1、5、9 和13,至少需要9 個(gè)比圓心更暗(或更亮)的連續(xù)像素。這種設(shè)置通常稱為FAST-9 角點(diǎn)檢測(cè)器,也是OpenCV 默認(rèn)采用的方法。

一個(gè)點(diǎn)與圓心強(qiáng)度值的差距必須達(dá)到一個(gè)指定的值,才能被認(rèn)為是明顯更暗或更亮;這個(gè)值就是創(chuàng)建檢測(cè)器實(shí)例時(shí)指定的閾值參數(shù)。這個(gè)閾值越大,檢測(cè)到的角點(diǎn)數(shù)量就越少。

至于Harris 特征,通常最好在發(fā)現(xiàn)的角點(diǎn)上執(zhí)行非最大值抑制。因此,需要定義一個(gè)角點(diǎn)強(qiáng)度的衡量方法。有多種衡量方法可供選擇,下面介紹的是實(shí)際選用的方法——計(jì)算中心點(diǎn)像素與認(rèn)定的連續(xù)圓弧上的像素的差值,然后將這些差值的絕對(duì)值累加,就能得到角點(diǎn)強(qiáng)度??梢詮腸v::KeyPoint 實(shí)例的response 屬性獲取角點(diǎn)強(qiáng)度。

在事先明確興趣點(diǎn)數(shù)量的情況下,可以對(duì)檢測(cè)過程進(jìn)行動(dòng)態(tài)適配。簡(jiǎn)單的做法是采用范圍較大的閾值檢測(cè)出很多興趣點(diǎn),然后從中提取出n 個(gè)強(qiáng)度最大的。

if (numberOfPoints < keypoints.size())
    std::nth_element(keypoints.begin(),
        keypoints.begin() + numberOfPoints,
        keypoints.end(),
        [](cv::KeyPoint& a, cv::KeyPoint& b) {
            return a.response > b.response; });

函數(shù)中keypoints 的類型是std::vector,表示檢測(cè)到的興趣點(diǎn),numberOfPoints 是需要的興趣點(diǎn)數(shù)量。最后一個(gè)參數(shù)是lambda 比較器,用于提取最佳的興趣點(diǎn)。請(qǐng)注意,如果檢測(cè)到的興趣點(diǎn)太少(少于需要的數(shù)量),那就要采用更小的閾值,但是閾值太寬松又會(huì)加大計(jì)算量,所以需要權(quán)衡利弊,選取最佳的閾值。

檢測(cè)圖像特征點(diǎn)時(shí)還會(huì)遇到一種情況,就是興趣點(diǎn)的分布很不均勻。keypoints 通常會(huì)聚集在紋理較多的區(qū)域。有一種常用的處理方法,就是把圖像分割成網(wǎng)格狀,對(duì)每個(gè)小圖像進(jìn)行單獨(dú)檢測(cè)。以下代碼就是網(wǎng)格適配特征檢測(cè):

    // 關(guān)鍵點(diǎn)的向量
    std::vector<cv::KeyPoint> keypoints;
    int total(100); // requested number of keypoints
    int hstep(5), vstep(3); // a grid of 4 columns by 3 rows
    // hstep= vstep= 1; // try without grid
    int hsize(image.cols / hstep), vsize(image.rows / vstep);
    int subtotal(total / (hstep * vstep)); // number of keypoints per grid
    cv::Mat imageROI;
    std::vector<cv::KeyPoint> gridpoints;

    cv::Ptr<cv::FastFeatureDetector> ptrFAST = cv::FastFeatureDetector::create(40);
    // detection with low threshold
    ptrFAST->setThreshold(20);
    // non-max suppression
    ptrFAST->setNonmaxSuppression(true);

    // 檢測(cè)每個(gè)網(wǎng)格
    for (int i = 0; i < vstep; i++)
        for (int j = 0; j < hstep; j++) {
            // 在當(dāng)前網(wǎng)格創(chuàng)建ROI
            imageROI = image(cv::Rect(j * hsize, i * vsize, hsize, vsize));
            // 在網(wǎng)格中檢測(cè)關(guān)鍵點(diǎn)
            gridpoints.clear();
            ptrFAST->detect(imageROI, gridpoints);
            // 獲取強(qiáng)度最大的FAST 特征
            auto itEnd(gridpoints.end());
            if (gridpoints.size() > subtotal) {
                // 選取最強(qiáng)的特征
                std::nth_element(gridpoints.begin(),
                    gridpoints.begin() + subtotal,
                    gridpoints.end(),
                    [](cv::KeyPoint& a,
                        cv::KeyPoint& b) {
                            return a.response > b.response; });
                itEnd = gridpoints.begin() + subtotal;
            }
            // 加入全局特征容器
            for (auto it = gridpoints.begin(); it != itEnd; ++it) {
                // 轉(zhuǎn)換成圖像上的坐標(biāo)
                it->pt += cv::Point2f(j * hsize, i * vsize);
                keypoints.push_back(*it);
            }
        }

    cv::drawKeypoints(image, keypoints, image, cv::Scalar(255, 255, 255), cv::DrawMatchesFlags::DRAW_OVER_OUTIMG);

這里的關(guān)鍵在于,利用ROI 對(duì)每個(gè)網(wǎng)格的小圖像進(jìn)行關(guān)鍵點(diǎn)檢測(cè),這樣得到的關(guān)鍵點(diǎn)分布較為均勻,如下圖所示。

result.jpg


8.4 尺度不變特征的檢測(cè)

計(jì)算機(jī)視覺界引入了尺度不變特征的概念。它的理念是,不僅在任何尺度下拍攝的物體都能檢測(cè)到一致的關(guān)鍵點(diǎn),而且每個(gè)被檢測(cè)的特征點(diǎn)都對(duì)應(yīng)一個(gè)尺度因子。理想情況下,對(duì)于兩幅圖像中不同尺度的同一個(gè)物體點(diǎn),計(jì)算得到的兩個(gè)尺度因子之間的比率應(yīng)該等于圖像尺度的比率。

本節(jié)將介紹SURF 特征,它的全稱為加速穩(wěn)健特征(Speeded Up Robust Feature)。我們將會(huì)看到,它不僅是尺度不變特征,而且是具有較高計(jì)算效率的特征。

SURF 特征檢測(cè)屬于opencv_contrib 庫,這里將重點(diǎn)討論cv::xfeatures2d 模塊和它的cv::xfeatures2d::SurfFeatureDetector 類。和其他檢測(cè)器一樣,檢測(cè)興趣點(diǎn)之前要先創(chuàng)建檢測(cè)器實(shí)例,然后調(diào)用它的檢測(cè)方法:

    // 創(chuàng)建SURF 特征檢測(cè)器對(duì)象
    cv::Ptr<cv::xfeatures2d::SurfFeatureDetector> ptrSURF =
        cv::xfeatures2d::SurfFeatureDetector::create(2000.0);
    // 檢測(cè)關(guān)鍵點(diǎn)
    ptrSURF->detect(image, keypoints);

為了畫出這些特征,再次使用OpenCV的cv::drawKeypoints 函數(shù),但是要采用cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS 標(biāo)志以顯示相關(guān)的尺度因子:

// 畫出關(guān)鍵點(diǎn),包括尺度和方向信息
cv::drawKeypoints(image, // 原始圖像
    keypoints, // 關(guān)鍵點(diǎn)的向量
    featureImage, // 結(jié)果圖像
    cv::Scalar(255, 255, 255), // 點(diǎn)的顏色
    cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);

這里使用cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS 標(biāo)志得到了關(guān)鍵點(diǎn)的圓,并且圓的尺寸與每個(gè)特征計(jì)算得到的尺度成正比。為了使特征具有旋轉(zhuǎn)不變性,SURF 還讓每個(gè)特征關(guān)聯(lián)了一個(gè)方向,由每個(gè)圓內(nèi)的輻射線表示。

SURF 算法是SIFT 算法的加速版,而SIFT(Scale-Invariant Feature Transform,尺度不變特征轉(zhuǎn)換)是另一種著名的尺度不變特征檢測(cè)法。

SIFT 特征的檢測(cè)過程與SURF 非常相似:

// 構(gòu)建SIFT 特征檢測(cè)器實(shí)例
cv::Ptr<cv::xfeatures2d::SiftFeatureDetector> ptrSIFT =
cv::xfeatures2d::SiftFeatureDetector::create();
// 檢測(cè)關(guān)鍵點(diǎn)
ptrSIFT->detect(image, keypoints);

由于SIFT 基于浮點(diǎn)內(nèi)核計(jì)算特征點(diǎn),因此通常認(rèn)為SIFT 算法檢測(cè)在空間和尺度上能取得更加精確的定位?;谕瑯拥脑?,它的計(jì)算效率也更低,盡管相對(duì)效率取決于具體的實(shí)現(xiàn)方法。


8.5 多尺度FAST 特征的檢測(cè)

本節(jié)將介
紹BRISK(Binary Robust Invariant Scalable Keypoints,二元穩(wěn)健恒定可擴(kuò)展關(guān)鍵點(diǎn))檢測(cè)法,它基于上一節(jié)介紹的FAST 特征檢測(cè)法。本節(jié)還將討論另一種檢測(cè)方法ORB(Oriented FAST andRotated BRIEF,定向FAST 和旋轉(zhuǎn)BRIEF)。

根據(jù)上一節(jié)介紹的方法,首先創(chuàng)建檢測(cè)器實(shí)例,然后對(duì)一幅圖像調(diào)用detect 方法:

    // 構(gòu)造BRISK 特征檢測(cè)器對(duì)象
    cv::Ptr<cv::BRISK> ptrBRISK = cv::BRISK::create(60, 5);
    // 檢測(cè)關(guān)鍵點(diǎn)
    ptrBRISK->detect(image, keypoints);

下圖顯示了在多個(gè)尺度下檢測(cè)到的BRISK 關(guān)鍵點(diǎn)。

result.jpg

ORB 特征的檢測(cè)方法如下所示:

    // 構(gòu)造ORB 特征檢測(cè)器對(duì)象
    cv::Ptr<cv::ORB> ptrORB =
        cv::ORB::create(75, // 關(guān)鍵點(diǎn)的總數(shù)
            1.2, // 圖層之間的縮放因子
            8); // 金字塔的圖層數(shù)量
            // 檢測(cè)關(guān)鍵點(diǎn)
    ptrORB->detect(image, keypoints);

調(diào)用的結(jié)果如下所示。

result.jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容