1. 程式人生 > >OpenCV2學習筆記(十):特徵點檢測之Harris法

OpenCV2學習筆記(十):特徵點檢測之Harris法

在計算機視覺中,特徵點的概念被大量用於解決物體識別、影象匹配、視覺跟蹤、三維重建等問題,比如影象中物體的角點,它們是在影象中可被輕易而精確地定位的二維特徵。顧名思義,特徵點檢測的思想是無需觀察整幅影象,而是通過選擇某些特殊點,然後對它們執行區域性分析。如果能檢測到足夠多的這種點,同時它們的區分度很高,並且可以精確定位穩定的特徵,那麼這個方法就很有效。這裡主要使用Harris特徵檢測器檢測影象角點。開發平臺為Qt5.3.2+OpenCV2.4.9。

在此之前,先給出OpenCV中cv::cornerHarris函式的呼叫方式:

    cv::cornerHarris(image, // 輸入影象
                     cornerStrength, //
輸出為表示角點強度的32位浮點影象 3, // 導數平滑的相鄰畫素的尺寸 3, // 梯度計算的濾波器孔徑大小 0.01); // Harris引數

描述Harris運算元的經典論文可參考:

The article by C.Harris, M.J. Stephens, A Combined Corner and Edge Detector, Alvey Vision Conference, pp.147-152, 1988

The article by J. Shi and C. Tomasi, Good features to track, Int. Conference on Computer Vision and Pattern Recognition, pp. 593-600, 1994

The article by K. Mikolajczyk and C. Schmid, Scale and Affine invariant interest point detectors, International Journal of Computer Vision, vol 60, no 1, pp. 63-86, 2004

在第一篇論文中提到,Harris角點檢測最直觀的解釋是在任意兩個相互垂直的方向上,都有較大變化的點。為了定義一幅影象中的角點,Harris觀察一個假定的特徵點周圍小視窗內的方向性強度平均變化。

這裡寫圖片描述

如圖所示,假設一個小視窗在影象上移動,在平滑區域如左圖所示,視窗在各個方向上均沒有變化。對於中間圖,小視窗在邊緣的方向上移動時也沒有變化。而知道小視窗移動到右圖的角點處,視窗在各個方向上均有明顯的變化。Harris角點檢測正是利用了這個直觀的物理現象,通過視窗在各個方向上的變化程度,決定是否存在著角點。這裡我們考慮偏移量(u,v),則將影象視窗平移(u,v)產生的E(u,v)可表示為:

這裡寫圖片描述

由以下公式可得到E(u,v):

這裡寫圖片描述

這裡寫圖片描述

對於區域性微小的移動,E(u,v)可近似表達為:

這裡寫圖片描述

其中M的詳細表達式為:

這裡寫圖片描述

E(u,v)的橢圓形式如下:

這裡寫圖片描述

E(u,v)是一個協方差矩陣,表現的是所有方向上強度的變化率。該定義涉及影象的一階導數,這通常是Sobel運算元的計算結果。而在OpenCV中cv::cornerHarris函式的第四個引數對應的正是用於計算Sobel濾波器的孔徑(aperture)。協方差的兩個特徵值給出了最大平均強度變化以及垂直方向上的平均強度變化,如果這兩個特徵值均較低,就認為當前是同質區域;如果其中一個特徵值較高,另外一個較低,則認為當前位於邊緣上;最後,若兩個特徵值均較高,則判定當前位於角點處。

因此,定義角點響應函式R,其中k為函式cv::cornerHarris中的最後一個引數;之後,對R進行閾值處理,設定若R大於閾值threshold,則提取出區域性極大值:

這裡寫圖片描述

下面記錄一下harris角點檢測的幾種方案。

一、基本的Harris角點檢測實現

#include <QCoreApplication>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    // Harris法的簡單實現
    cv::Mat image = cv::imread("c:/031.jpg", 0);
    cv::Mat cornerStrength;
    cv::cornerHarris(image,
                     cornerStrength, // 輸出為表示角點強度的32位浮點影象
                     3, // 導數平滑的相鄰畫素的尺寸
                     3, // 梯度計算的濾波器孔徑大小
                     0.01); // Harris的相關引數

    // 要在視窗輸出,需要轉化為CV_8U格式,閾值化即可
    // 角點強度的閾值
    cv::Mat harrisCorner;
    double threshold = 0.0001;
    cv::threshold(cornerStrength,
                  harrisCorner,
                  threshold,
                  255,
                  cv::THRESH_BINARY_INV); // 輸出為翻轉的二值影象

    cv::namedWindow("Original Image");
    cv::imshow("Original Image", image);
    cv::namedWindow("Harris Corner");
    cv::imshow("Harris Corner", harrisCorner);

    return a.exec();
}

得到的結果為二值影象,可以看到影象中角點的位置包含許多圓圈,這與精確定位特徵點的目標相悖:

這裡寫圖片描述

二、改進的Harris角點檢測實現(Shi-Tomasi演算法)

這裡通過封裝自定義類來改進角點檢測的效果。定義一個類HarrisDetector(其中已封裝了Harris引數和相關函式):

#ifndef HARRISDETECTOR_H
#define HARRISDETECTOR_H
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

class HarrisDetector
{
private:
    // 表示影象角點強度的32位浮點影象
    cv::Mat cornerStrength;
    // 將輸出影象閾值化後的32位浮點影象
    cv::Mat cornerThreshold;
    // 區域性極大值影象
    cv::Mat localMax;

    // 以下三個為cornerHarris函式的必要引數
    // 導數平滑的相鄰畫素的尺寸
    int neighbourhoodPixelSize;
    // 濾波器的孔徑大小
    int aperture;
    // Harris引數
    double k;

    // 閾值計算的最大強度
    double maxStrength;
    // 計算得到的閾值
    double threshold;
    // 非極大值抑制的相鄰畫素的尺寸
    int noneighbourhoodPixelSize;
    // 非極大值抑制的核
    cv::Mat kernel;

public:
    // 初始化引數
    HarrisDetector():neighbourhoodPixelSize(3),
                     aperture(3),
                     k(0.01),
                     maxStrength(0.0),
                     threshold(0.01),
                     noneighbourhoodPixelSize(3)
    {
        setLocalMaxWindowSize(noneighbourhoodPixelSize);
    }

    // 建立非極大值抑制的核
    void setLocalMaxWindowSize(int size);

    // 計算Harris角點
    void detect(const cv::Mat &image);

    // 由Harris的值獲取角點圖
    cv::Mat getCornerMap(double qualityLevel);

    // 由Harris的值獲取特徵點
    void getCorners(std::vector<cv::Point> &points, double qualityLevel);

    // 由角點圖獲取特徵點
    void getCorners(std::vector<cv::Point> &points, const cv::Mat& cornerMap);

    // 在特徵點的位置繪製圖
    void drawOnImage(cv::Mat &image,
                     const std::vector<cv::Point> &points,
                     cv::Scalar color = cv::Scalar(255, 255, 255),
                     int radius = 4, int thickness = 2);
};


#endif // HARRISDETECTOR_H

接著,在harrisdetector.cpp中定義各個函式和初始化的引數:

#include "harrisdetector.h"

// 建立非極大值抑制的核
void HarrisDetector::setLocalMaxWindowSize(int size)
{
    noneighbourhoodPixelSize = size;
    kernel.create(noneighbourhoodPixelSize, noneighbourhoodPixelSize, CV_8U);
}

// 計算Harris角點
void HarrisDetector::detect(const cv::Mat &image)
{
    // Harris計算
    cv::cornerHarris(image,cornerStrength,
                     neighbourhoodPixelSize,
                     aperture,
                     k);
    // 內部閾值計算
    double minStrength; // 未使用
    cv::minMaxLoc(cornerStrength,
                  &minStrength,
                  &maxStrength);
    // 區域性極大值檢測
    cv::Mat dilate; // 臨時影象
    cv::dilate(cornerStrength, dilate, cv::Mat());
    cv::compare(cornerStrength, dilate, localMax, cv::CMP_EQ);
}

// 由Harris的值獲取角點圖
cv::Mat HarrisDetector::getCornerMap(double qualityLevel)
{
    cv::Mat cornerMap;
    // 對角點影象進行閾值化
    threshold = qualityLevel * maxStrength;
    cv::threshold(cornerStrength, cornerThreshold,
                  threshold,255,cv::THRESH_BINARY);
    // 轉換為8點陣圖像
    cornerThreshold.convertTo(cornerMap, CV_8U);
    // 非極大值抑制
    cv::bitwise_and(cornerMap, localMax, cornerMap);
    return cornerMap;
}

// 由Harris的值獲取特徵點
void HarrisDetector::getCorners(std::vector<cv::Point> &points, double qualityLevel)
{
    // 得到角點圖
    cv::Mat cornerMap = getCornerMap(qualityLevel);
    getCorners(points, cornerMap);
}

// 由角點圖獲取特徵點
void HarrisDetector::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++ )
        {
            // 如果是特徵點
            if (rowPtr[x])
            {
                points.push_back(cv::Point(x,y));
            }
        }
    }
}

// 在特徵點的位置繪製圖
void HarrisDetector::drawOnImage(cv::Mat &image,
                 const std::vector<cv::Point> &points,
                 cv::Scalar color,
                 int radius, int thickness)
{
    std::vector<cv::Point>::const_iterator it = points.begin();
    // 對於所有角點,繪製白色圓圈
    while(it != points.end())
    {
        cv::circle(image, *it, radius, color, thickness);
        ++ it;
    }
}

最後,使用該類的步驟如下,直接修改main函式:

#include <QCoreApplication>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <QDebug>
#include "harrisdetector.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    cv::Mat image = cv::imread("c:/031.jpg", 0);
    // 建立Harris物件
    HarrisDetector harris;
    // 計算Harris的值
    harris.detect(image);
    // 檢測Harris的角點
    std::vector<cv::Point>pts;
    harris.getCorners(pts, 0.1);
    // 繪製角點圖
    harris.drawOnImage(image, pts);
    cv::namedWindow("Harris Corners Final");

    return a.exec();
}

生成的影象:

這裡寫圖片描述

這裡為了改進特徵點檢測結果,添加了額外的非極大值抑制步驟,目的是移除彼此相鄰的Harris角點。這就要求Harris角點不只需要得分高於給定閾值,它還必須是區域性極大值。在檢測中使用了一個技巧,即將Harris得分的影象進行膨脹:

cv::dilate(cornerStrength, dilate, cv::Mat());

這是由於膨脹運算替換每個畫素值為相鄰範圍內的最大值,因此只有區域性極大值的點才會保留原樣,並通過以下函式進行測試:

cv::compare(cornerStrength, dilate, localMax, cv::CMP_EQ);

其中,localMax矩陣僅在區域性極大值的位置為真,因此又可以在getCornerMap函式中用它來抑制所有非極大值的特徵(基於cv::bitwise_and函式)。

三、引入適合跟蹤的優質特徵的Harris檢測實現

在浮點處理器的幫助下,為了避免特徵值分解而引入的數學上的簡化變得微不足道,因此Harris檢測可以基於計算而得的特徵值。原則上這個修改不會顯著影響檢測的結果,但是能夠避免使用任意的k引數。

以上第二中方法引入了局部極大值的條件,改善了部分效果。然而,特徵點傾向於影象中不均勻分佈、普遍集中在紋理豐富的部分。這裡記錄一種新的解決方案:

該方案利用兩個特徵點之間的最小距離,從Harris得分最高的點開始,僅接受離開有效特徵點距離大於特定值的那些點。在OpenCV中提供cv::goodFeaturesToTrack實現這一方法,它檢測到的特徵能用於視覺跟蹤應用中的優質特徵集合。其呼叫方式如下:

    // 計算適合跟蹤的優質特徵
    std::vector<cv::Point2f> corners;
    cv::goodFeaturesToTrack(image,corners,
        250,    // 返回的最大特徵點的數目
        0.01,   // 質量等級
        8); // 兩點之間的最小允許距離

除了質量等級閾值、特徵點之間的最小允許距離,該函式還需要指定返回的最大特徵點數目,這是因為特徵點是按照強度進行排序的。以下給出該方法的實現程式碼,直接在main函式中新增:

    // 輸入影象
    cv::Mat image= cv::imread("c:/031.jpg",0);

    // 計算適合跟蹤的優質特徵
    std::vector<cv::Point2f> corners;
    cv::goodFeaturesToTrack(image,corners,
        250,    // 返回的最大特徵點的數目
        0.01,   // 質量等級
        8); // 兩點之間的最小允許距離

    // 遍歷所有特徵點並畫圓圈
    std::vector<cv::Point2f>::const_iterator it= corners.begin();
    while (it!=corners.end())
    {
        cv::circle(image, *it, 3, cv::Scalar(255,255,255), 2);
        ++it;
    }

    // 顯示輸出結果
    cv::namedWindow("Good Features to Track");
    cv::imshow("Good Features to Track",image);

返回生成的結果:

這裡寫圖片描述

可以看到,該方法顯著改進了特徵點的分佈情況,但是這樣也增加了檢測的複雜度,因為要求特徵點要安裝Harris的得分進行排序。該函式也可以指定一個可選的引數,使得按照經典的焦點分數定義進行計算。

其中,cv::goodFeaturesToTrack函式擁有一個封裝類cv::GoodFeatureToTrackDetector,它繼承自cv::FeaturesDetector類。其用法與以上的Harris類相類似:

    // 特徵點向量
    std::vector<cv::KeyPoint> keypoints;
    cv::goodFeaturesToTrackDetector gftt(
        250,    // 返回的最大特徵點的數目
        0.01,   // 質量等級
        8); // 兩點之間的最小允許距離
    // 使用FeatureDetector的函式進行檢測
    gftt.detect(image, keypoints);

結果與先前得到的結果是一樣的,因為它們呼叫的是同一個函式。

關於Harris的理論研究有待進一步研究……

相關推薦

OpenCV2學習筆記特徵檢測Harris

在計算機視覺中,特徵點的概念被大量用於解決物體識別、影象匹配、視覺跟蹤、三維重建等問題,比如影象中物體的角點,它們是在影象中可被輕易而精確地定位的二維特徵。顧名思義,特徵點檢測的思想是無需觀察整幅影象,而是通過選擇某些特殊點,然後對它們執行區域性分析。如果能檢測

OpenCV2學習筆記十三基於SURF特徵的影象匹配

SURF演算法是著名的尺度不變特徵檢測器SIFT(Scale-Invariant Features Transform)的高效變種,它為每個檢測到的特徵定義了位置和尺度,其中尺度的值可用於定義圍繞特徵點的視窗大小,使得每個特徵點都與眾不同。這裡便是使用SURF演

javaweb學習筆記XML

目錄   1 xml入門 2 xml語法 3 xml顯示 4 XML解析方式及工具 5 xml約束 1 xml入門 Extensible Markup Language(可擴充套件標記語言),XML 的設計宗旨是傳輸資料,而不是顯示資料。XML 標籤

學習筆記使用支援向量機區分僵屍網路DGA家族

1.資料蒐集和資料清洗       ·1000個cryptolocker域名       ·1000個post-tovar-goz域名       ·alexa前1000域名   &n

機器學習筆記TensorFlow實戰二深層神經網路

1 - 深度學習與深層神經網路 深度學習的精確定義為:“一類通過多層非線性變換對高複雜性資料建模演算法的集合” 因此,多層神經網路有著2個非常重要的特性 多層 非線性 1.1 - 線性模型的侷限性 線上性模型中,模型的輸出為輸入的加權和,假設一

python 學習筆記 資料庫連線池

#!/usr/bin/python # -*- coding: utf-8 -*- from __future__ import print_function import Queue import pymysql import logging LOG =

各種音視訊編解碼學習詳解 編解碼學習筆記Ogg系列

 最近在研究音視訊編解碼這一塊兒,看到@bitbit大神寫的【各種音視訊編解碼學習詳解】這篇文章,非常感謝,佩服的五體投地。奈何大神這邊文章太長,在這裡我把它分解成很多小的篇幅,方便閱讀。大神部落格傳送門:https://www.cnblogs.com/skyofbitbit/p/3651

TensorFlow學習筆記 CIFAR-10

1. CIFAR-10 Cifar-10 是由 Hinton 的兩個大弟子 Alex Krizhevsky、Ilya Sutskever 收集的一個用於普適物體識別的資料集。Cifar 是加拿大政府牽頭投資的一個先進科學專案研究所。Hinton、Bengio

tensorflow學習筆記sess.run()

session.run([fetch1, fetch2]) 執行sess.run()時,tensorflow是否計算了整個圖 我們在編寫程式碼的時候,總是要先定義好整個圖,然後才呼叫sess

統計學習方法 學習筆記決策樹

人生 最優決策 如何解決 極大似然函數 速度 結點 then 計算 結果 這一個學習筆記將要了解決策樹,在研一上機器學習這門課的時候,老師在講到這一節的時候,舉了一個例子我現在還能記得:你們坐在這裏上課,就像這個決策樹一樣,在你人生中的每一個重要結點,你都做出了選擇,經過多

shell編程學習筆記Shell中的for循環

str don clas 循環 實現 code 結束 最簡 命令 shell編程中可以實現for循環遍歷 先來寫一個最簡單的吧,循環輸出從1到10,腳本內容為: #! /bin/sh for i in {1..10} do echo $i done

WPF and Silverlight 學習筆記WPF佈局管理StackPanel、WrapPanel、DockPanel

一、StackPanel StackPanel是以堆疊的方式顯示其中的控制元件 1、可以使用Orientation屬性更改堆疊的順序 Orientation="Vertical"       預設,由上到下顯示各控制元件。控制元件在未定義的前提下,寬度為StackPa

Java學習筆記53.面向物件方法詳解

  方法是類或物件的行為特徵的抽象,方法是類或物件最重要的組成成分。但從功能上看,方法完全類似於傳統結構化程式設計裡的函式。值得指出的是,Java裡的方法不能獨立存在,所有的方法必須定義在類裡。方法在邏輯上要麼屬於類,要麼屬於物件。 1.方法的所屬性   永遠不要把方法當成

OpenCV學習筆記使用CascadeClassifier檢測人臉

#include "stdafx.h" #include <opencv2/opencv.hpp> class FaceRecognition { private: cv::Mat m_mImg; char* face_cascade_name = "D:\\OpenCV\\opencv\

OpenCV學習筆記11libfacedetection人臉檢測的配置與使用

1. 前言 libfacedetection庫是深圳大學的於仕琪老師釋出的開源人臉檢測庫,相比於OpenCV自帶的CascadeClassifier人臉檢測,無論在速度上還是精度上,都有巨大的優勢,是目前已知開源庫中最好用的一款。 本文通過學習libface

OpenCV2學習筆記利用Cmake高速查找OpenCV函數源代碼

one 生成 img log 分享 lan 學習筆記 全部 modules 在使用OpenCV時,在對一個函數的調用不是非常了解的情況下,通常希望查到該函數的官方聲明。而假設想進一步研究OpenCV的函數,則必須深入到源碼。在VS中我們能夠選中想要查

OpenCV2學習筆記Kalman濾波演算法

在視訊跟蹤處理中,預測目標運動軌跡是一項基本任務。目標運動狀態估計的目的有三個:一是對目標過去的狀態進行平滑;二是對目標現在的運動狀態進行濾波;三是對目標未來的運動狀態進行預測。物體的運動狀態一般包括目標位置、速度、加速度等。著名的Kalman濾波技術就是其中一

Cris 的Python筆記面向物件三大特徵繼承

文章目錄 1、繼承的特性 2、Python 的多繼承(瞭解) 1、繼承的特性 # 通過繼承可以使得子類很好的複用父類的程式碼,減少冗餘程式碼,同時更加符合現實邏輯(程式設計就是對現實世界的抽象)

PyTorch 學習筆記transforms的二二個方法

本文擷取自《PyTorch 模型訓練實用教程》,獲取全文pdf請點選:https://github.com/tensor-yu/PyTorch_Tutorial 文章目錄 一、 裁剪——Crop 1.隨機裁剪:transforms.Random

Matlab影象處理學習筆記surf特徵

本文主要演示如何使用matlab自帶的Computer Vision System Toolbox這個工具箱進行suft特徵點的檢測、匹配及顯示。這個工具箱是matlab2012b及之後才有的一個工具