1. 程式人生 > >SIFT特徵點檢測學習一(轉載)

SIFT特徵點檢測學習一(轉載)

    sift演算法在cv領域的重要性不言而喻,該作者的文章引用率在cv界是number1.本篇部落格只是本人把sift演算法知識點整理了下,以免忘記。本文比較早的一篇博文opencv原始碼解析之(3):特徵點檢查前言1 中有使用opencv自帶的sift做了個簡單的實驗,而這次主要是利用Rob Hess的sift原始碼來做實驗,其實現在的opencv版本中帶的sift演算法也是Rob Hess的,只是稍微包裝了下。

  首先網上有不少文章介紹了sift演算法,寫得都不錯,比如: 

     該部落格對sift演算法理論做了介紹,且有示意圖輔助理解,從該文中可以瞭解sift演算法的大概流程.

     這篇文章對sift演算法做了通俗易懂的解釋.

  這篇部落格有教你怎樣用c語言一步一步寫sift演算法。

  該文也對sift做了詳細的介紹,部落格的作者還對sift匹配做了講解。

  下面還是簡單看下sift演算法的理論,具體的內容可以參考上面的幾篇文章。

  一、Sift描述子形成的步驟

  1、 構造高斯差分空間影象。

  Sift特徵點的檢測時在DOG影象上進行的,DOG影象是將相鄰尺度空間影象相減得到的。且金字塔的每一層都要構造一個DOG空間影象。預設引數是金字塔4層,即4個octave,每一個octave中有5張不同尺度的圖片,不同octave的圖片尺寸大小不同,所以每一層中就會得到4幅DOG影象。

高斯金字塔的第1層第1副原影象是將原影象放大2倍且sigma(sigma=1.6)模糊,第2幅影象是k*sigma(k等於根號2)模糊,第3幅是k*k*sigma模糊,後面類推…

     高斯金字塔第2層第1幅圖是選擇金字塔上一層(這裡是第1層)中尺度空間引數為k*k*sigma的那幅圖(實際上是2倍的尺度空間)進行降取樣(尺寸大小為原來的1/4倍)得到,如果k不等於根號2,那麼取原圖的2*sigma降取樣得到。第2層第2幅圖是在本層第一幅圖尺度模糊係數增加k倍模糊後的影象,後面類似…

  示意圖如下所示:

  

     尺度不變當然是與圖片尺寸有關,即圖片的尺寸大小變化,但是其檢測結果不變。

  2、尋找極大極小值點。

  將每個畫素點與其所在的那幅影象鄰域的8個畫素,它所在的向量尺度空間上下2幅圖對應位置鄰域各9個點,總共26個點進行畫素值比較,如果該點是最大或者最小點,則改點就暫時列為特徵點。

  其鄰圖如下:

  

  3、精確定位極值點

  子畫素級極值點:

  由於上面找到的近似極值點落在畫素點的位置上,實際上我們在畫素點附近如果用空間曲面去擬合的話,很多情況下極值點都不是恰好在畫素點上,而是在附近。所以sift演算法提出的作者用泰勒展開找到了亞畫素級的特徵點。這種點更穩定,更具有代表性。

  消除對比度低的特徵點:

  對求出亮度比較低的那些點直接過濾點,程式中的閾值為0.03.

  消除邊界上的點:

  處理方法類似harrs角點,把平坦區域和直線邊界上的點去掉,即對於是邊界上的點但又不是直角上的點,sift演算法是不把這些點作為特徵點的。

  4、選取特徵點主方向

  在特徵點附近選取一個區域,該區域大小與圖影象的尺度有關,尺度越大,區域越大。並對該區域統計36個bin的方向直方圖,將直方圖中最大bin的那個方向作為該點的主方向,另外大於最大bin80%的方向也可以同時作為主方向。這樣的話,由於1個特徵點有可能有多個主方向,所以一個特徵點有可能有多個128維的描述子。如下圖所示:

  

     5、 構造特徵點描述運算元。

     以特徵點為中心,取領域內16*16大小的區域,並把這個區域分成4*4個大小為4*4的小區域,每個小區域內計算加權梯度直方圖,該權值分為2部分,其一是該點的梯度大小,其二是改點離特徵點的距離(二維高斯的關係),每個小區域直方圖分為8個bin,所以一個特徵點的維數=4*4*8=128維。示意圖如下(該圖取的領域為8*8個點,因此描述子向量的維數為32維):

  

   6、實驗部分

         下面來做下試驗,試驗sift程式碼採用Rob Hess的程式碼,opencv目前版本中的sift原始碼也是採用Rob Hess的。程式碼可以在他的主頁上下載:http://blogs.oregonstate.edu/hess/code/sift/

這裡我下載的是windows版本的,並採用Qt做了個簡單的介面。

         環境:WindowsXP+Opencv2.4.2+Qt4.8.2+QtCreator2.5.1,QtCreator內部採用的是vc的編譯器。

         執行軟體,單擊Open Image後選擇一張需要進行特徵點檢測的圖片,我這裡顯示的結果如下:  

    

         單擊Sift Detect按鈕後,檢測到的效果如下:

    

主要程式碼部分如下(附錄有工程code下載連結):

SiftDetect.h:

複製程式碼
#ifndef SIFTDETECT_H
#define SIFTDETECT_H

#include <QDialog>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
//#include <opencv/cxcore.h>
//#include <opencv/highgui.h>
//#include <opencv/imgproc.h>
#include <stdio.h>
#include <stdio.h>
#include "sift.h"
#include "imgfeatures.h"
#include "utils.h"


using namespace cv;

namespace Ui {
class SiftDetect;
}

class SiftDetect : public QDialog
{
    Q_OBJECT
    
public:
    explicit SiftDetect(QWidget *parent = 0);
    ~SiftDetect();
    
private slots:


    void on_openButton_clicked();

    void on_detectButton_clicked();

    void on_closeButton_clicked();

private:
    Ui::SiftDetect *ui;

    Mat src, dst;
    IplImage* img;
    struct  feature* features;
    int n;
    int display;
    int intvls;
    double sigma;
    double contr_thr;
    int curv_thr;
    int img_dbl;
    int descr_width;
    int descr_hist_bins;
};

#endif // SIFTDETECT_H
複製程式碼

SiftDetect.cpp:

複製程式碼
#include "siftdetect.h"
#include "ui_siftdetect.h"
#include <QtGui>
#include <QtCore>
#include "sift.h"
#include "imgfeatures.h"
#include "utils.h"
//#include <sift.c>

SiftDetect::SiftDetect(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::SiftDetect)
{
    ui->setupUi(this);

    n = 0;
    display = 1;
    intvls = SIFT_INTVLS;
    sigma = SIFT_SIGMA;
    contr_thr = SIFT_CONTR_THR;
    curv_thr = SIFT_CURV_THR;
    img_dbl = SIFT_IMG_DBL;
    descr_width = SIFT_DESCR_WIDTH;
    descr_hist_bins = SIFT_DESCR_HIST_BINS;
}

SiftDetect::~SiftDetect()
{
 //   cvReleaseImage( &img );//釋放記憶體退出程式後竟然報錯
    delete ui;
}



void SiftDetect::on_openButton_clicked()
{
    QString img_name = QFileDialog::getOpenFileName(this, "Open Image", "../sift_detect",
                                                    tr("Image Files(*.png *.jpeg *.jpg *.bmp)"));
  //  img = cvLoadImage( img_name.toAscii().data() );
    src = imread( img_name.toAscii().data() );
    imwrite( "../sift_detect/src.jpg", src );
    ui->textBrowser->clear();
    ui->textBrowser->setFixedSize( src.cols, src.rows );
    ui->textBrowser->append( "<img src=../sift_detect/src.jpg>" );

}

void SiftDetect::on_detectButton_clicked()
{
    //將Mat型的src轉換成IplImage*型的img,因為這裡是opencv新老版本混合程式設計的方法。
     img = &src.operator IplImage();
     n = _sift_features( img, &features, intvls, sigma, contr_thr, curv_thr, img_dbl, descr_width, descr_hist_bins );
     if( display )
         {
             draw_features( img, features, n );
             ui->textBrowser->clear();
             //將IplImage*型的img轉換成Mat型的dst,這也是opencv新老版本混合程式設計的一種方法。
             dst = Mat( img );
             imwrite( "../sift_detect/dst.jpg", dst );
             //cvSaveImage( "../sift_detect/dst.jpg", img );
             ui->textBrowser->append( "<img src=../sift_detect/dst.jpg>" );
         }
}

void SiftDetect::on_closeButton_clicked()
{
    close();
}
複製程式碼

  二、Sift特徵點匹配過程

  由步驟一我們已經獲得了圖片的特徵點向量集合。現在來看看特徵點匹配,特徵點匹配的一個應用就是物體的識別,比如說我有2張圖片A和B,圖片的內容相同,只是圖片的大小尺寸不同。假設A圖片尺寸比較大,且我們已經採用sift演算法對圖片A和B都進行了檢測,獲得了它們的特徵點集合,現在我們的問題是需要把A和B中相應的特徵點給對應連線起來。

  既然是匹配,當然每個特徵點向量要相似才能匹配到一起,這裡採用的是歐式距離來衡量其相似度。

  對B中的特徵點x,去尋找A中最相似的點y,最簡單的方法就是拿x與A中所有點進行相似度比較,距離最小的那個為匹配點。但是如果圖片中特徵點數目很多的話,這樣效率會很低。所以我們需要把A中特徵點向量集合用一種資料結構來描述,這種描述要有利於x在A中的搜尋,即減少時間複雜度。在sift匹配中,這種資料結構採用的是kd-tree。

  裡面講得比較詳細,且舉了例子,很容易理解,這裡就沒有必要重複了。

     同樣,採用Rob Hess的程式碼做了個sift匹配的實驗,開發環境與上面的一樣。

     開啟軟體後,單擊Open Image按鈕,依次開啟需要匹配的2張圖片,如下圖所示:

  

     單擊Sift Detect按鈕,則程式會單獨對這2幅圖片進行sift特徵點檢測,結果如下圖所示:

  

     單擊Sift Match按鈕,則會對這2幅圖的特徵點結果進行匹配,本次實驗的匹配圖如下所示:

  

實驗主要部分程式碼(附錄有工程code連結下載):

SiftMatch.h:

複製程式碼
#ifndef SIFTMATCH_H
#define SIFTMATCH_H

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

using namespace cv;

namespace Ui {
class SiftMatch;
}

class SiftMatch : public QDialog
{
    Q_OBJECT
    
public:
    explicit SiftMatch(QWidget *parent = 0);
    ~SiftMatch();
    
private slots:
    void on_openButton_clicked();

    void on_detectButton_clicked();

    void on_matchButton_clicked();

    void on_closeButton_clicked();

private:
    Ui::SiftMatch *ui;
    Mat src1, src2, src1_c, src2_c, dst;
    IplImage *img1, *img2, *img3, *stacked;
    Point pt1, pt2;
    double d0, d1;
    struct feature *feat1, *feat2, *feat;
    struct feature **nbrs;
    struct kd_node *kd_root;
    int open_image_number;
    int n1, n2, k, i, m;
};

#endif // SIFTMATCH_H
複製程式碼

SiftMatch.cpp:

複製程式碼
#include "siftmatch.h"
#include "ui_siftmatch.h"

#include <QtCore>
#include <QtGui>
#include "imgfeatures.h"
#include "kdtree.h"
#include "minpq.h"
#include "sift.h"
#include "utils.h"
#include "xform.h"

/* the maximum number of keypoint NN candidates to check during BBF search */
#define KDTREE_BBF_MAX_NN_CHKS 200

/* threshold on squared ratio of distances between NN and 2nd NN */
#define NN_SQ_DIST_RATIO_THR 0.49

SiftMatch::SiftMatch(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::SiftMatch)
{    
    open_image_number = 0;
    m = 0;

    ui->setupUi(this);   
}

SiftMatch::~SiftMatch()
{
    delete ui;
}

void SiftMatch::on_openButton_clicked()
{
    QString img_name = QFileDialog::getOpenFileName(this, "Open Image", "../sift_detect",
                                                    tr("Image Files(*.png *.jpeg *.jpg *.bmp)"));
    open_image_number++;
    //開啟第1張圖片
    if( 1 == open_image_number )
        {
            src1 = imread( img_name.toAscii().data() );
            img1 = cvLoadImage( img_name.toAscii().data() );
            //轉換成IplImage*型別,但是這樣轉換過的後面使用起來感覺還是不特別順利,說明並不是完全100%相容了。
      //      img1 = &src1.operator IplImage();
            imwrite( "../sift_match/src1.jpg", src1 );
            ui->textBrowser->setFixedSize( src1.cols, src1.rows );
            ui->textBrowser->append( "<img src=../sift_match/src1.jpg>" );
         }
    //開啟第2張圖片
    else if( 2 == open_image_number )
        {
            src2 = imread( img_name.toAscii().data() );
            img2 = cvLoadImage( img_name.toAscii().data() );
         //   img2 = &src2.operator IplImage();
            imwrite( "../sift_match/src2.jpg", src2 );
            ui->textBrowser->setFixedSize( src2.cols+src1.cols, src2.rows+src1.rows );
            ui->textBrowser->append( "<img src=../sift_match/src2.jpg>" );
        }
    else
        open_image_number = 0;

}

void SiftMatch::on_detectButton_clicked()
{
    //將2幅圖片合成1幅圖片
    //img1 = cvLoadImage();
    stacked = stack_imgs( img1, img2 );
    ui->textBrowser->clear();

    //顯示第1幅圖片上的特徵點
    n1 = sift_features( img1, &feat1 );
    draw_features( img1, feat1, n1 );
    src1_c = Mat(img1);
    imwrite("../sift_match/src1_c.jpg", src1_c);
    ui->textBrowser->append("<img src=../sift_match/src1_c.jpg>");

    //顯示第2幅圖片上的特徵點
    n2 = sift_features( img2, &feat2 );
    draw_features( img2, feat2, n2 );
    src2_c = Mat(img2);
    imwrite("../sift_match/src2_c.jpg", src2_c);
    ui->textBrowser->append("<img src=../sift_match/src2_c.jpg>");
}

void SiftMatch::on_matchButton_clicked()
{
    kd_root = kdtree_build( feat2, n2 );
    for( i = 0; i < n1; i++ )
        {
            feat = feat1+i;
            k = kdtree_bbf_knn( kd_root, feat, 2, &nbrs, KDTREE_BBF_MAX_NN_CHKS );
            if( k == 2 )
                {
                    d0 = descr_dist_sq( feat, nbrs[0] );
                    d1 = descr_dist_sq( feat, nbrs[1] );
                    if( d0 < d1 * NN_SQ_DIST_RATIO_THR )
                        {
                            pt1 = Point( cvRound( feat->x ), cvRound( feat->y ) );
                            pt2 = Point( cvRound( nbrs[0]->x ), cvRound( nbrs[0]->y ) );
                            pt2.y += img1->height;
                            cvLine( stacked, pt1, pt2, CV_RGB(255,0,255), 1, 8, 0 );
                            m++;
                            feat1[i].fwd_match = nbrs[0];
                        }
                }
             free( nbrs );
         }
    dst = Mat( stacked );
    imwrite( "../sift_match/dst.jpg", dst );
    ui->textBrowser->clear();
    ui->textBrowser->setFixedSize( dst.cols, dst.rows );
    ui->textBrowser->append("<img src=../sift_match/dst.jpg>");

}

void SiftMatch::on_closeButton_clicked()
{
    close();
}
複製程式碼

   總結:

   通過整理下sift演算法知識點,對sift演算法有了更全面的認識,另外感謝Rob Hess開源了sift演算法的程式碼,感覺寫好這個演算法確實不同意的。(另外,本文部落格中引用了上面提到的部落格中的圖片,在此宣告一下。)

         附錄一:

         Rob Hess sift的c程式碼在c++中的使用。

由於Rob Hess的程式碼是基於c的,如果在其它關於介面開發的c++程式中,比如Qt,MFC等。我這裡是Qt,連線時會報如下錯誤:

siftdetect.obj:-1: error: LNK2019: 無法解析的外部符號 "int __cdecl _sift_features(struct _IplImage *,struct feature * *,int,double,double,int,int,int,int)" ([email protected]@[email protected]@[email protected]@[email protected]),該符號在函式 "private: void __thiscall SiftDetect::on_detectButton_clicked(void)" ([email protected]@@AAEXXZ) 中被引用

         因此我們要找本質的原因,原因就是c語法和c++語法畢竟不同,所以難免有些相容性問題。看錯誤提示我們知道是在函式_sift_features時報的錯,該函式在Rob Hess的標頭檔案中是被定義的extern型別。而在c的編譯器中,extern的函式檔名編譯後會自動在前面加一杆,”_”;而c++語法中會有函式過載的現象,因此它不是像c那樣直接在前面加”_”,否則會衝突,因此c++編譯器會在其函式名後面加入一些像亂碼的東西(反正連結時是機器去尋找,只要能區分即可)。

         本次實驗的解決方法是在Rob Hess的sift.h等幾個標頭檔案檔案開始處加入語句:

#ifdef __cplusplus

extern "C" {

#endif     

該檔案的結處加入語句:

#ifdef __cplusplus

}

#endif

  這樣的話編譯器在編譯該檔案時會知道這是C語法,所以其中間檔案命名規則會相應改變,問題也就相應的解決了。

     另外,如果出現錯誤提示:

  utils.obj:-1: error: LNK2019: 無法解析的外部符號 _va_end,該符號在函式 _fatal_error 中被引用。

    則在utils.c程式碼中找到va_start( ap, format );和va_end( ap );並將其註釋起來即可。

  附錄二:

相關推薦

SIFT特徵檢測學習轉載

    sift演算法在cv領域的重要性不言而喻,該作者的文章引用率在cv界是number1.本篇部落格只是本人把sift演算法知識點整理了下,以免忘記。本文比較早的一篇博文opencv原始碼解析之(3):特徵點檢查前言1 中有使用opencv自帶的sift做了個簡單的實驗,而這次主要是利用Rob Hes

SIFT特徵演算法原始碼分析opencv

SIFT特徵點演算法原始碼分析 SIFT演算法在opencv中被實現為一個類: SIFT ,主要的操作都在這個類過載的"()"運算子中實現。下面介紹這個類,以及其中呼叫的一些關鍵的函式。 SIFT類的建構函式:初始化演算法引數 SIFT::SIFT(int nfeatu

opencv學習筆記二十九:SIFT特徵檢測與匹配

SIFT(Scale-invariant feature transform)是一種檢測區域性特徵的演算法,該演算法通過求一幅圖中的特徵點(interest points,or corner points)及其有關scale 和 orientation 的描述子得到特徵並進行

特徵檢測學習_1(sift演算法)

 sift演算法在cv領域的重要性不言而喻,該作者的文章引用率在cv界是number1.本篇部落格只是本人把sift演算法知識點整理了下,以免忘記。本文比較早的一篇博文opencv原始碼解析之(3):特徵點檢查前言1 中有使用opencv自帶的sift做了個簡單的實驗,而這次主要是利用Rob Hess的s

特徵檢測學習_2(surf演算法)

在上篇部落格特徵點檢測學習_1(sift演算法) 中簡單介紹了經典的sift演算法,sift演算法比較穩定,檢測到的特徵點也比較多,其最大的確定是計算複雜度較高。後面有不少學者對其進行了改進,其中比較出名的就是本文要介紹的surf演算法,surf的中文意思為快速魯棒特徵。本

特徵檢測學習(surf演算法)

  在上篇部落格特徵點檢測學習_1(sift演算法) 中簡單介紹了經典的sift演算法,sift演算法比較穩定,檢測到的特徵點也比較多,其最大的確定是計算複雜度較高。後面有不少學者對其進行了改進,其中比較出名的就是本文要介紹的surf演算法,surf的中文意思為快速魯

surf與sift特徵檢測程式碼實現

概述        在opencv的features2d中實現了SIFT和SURF演算法,可以用於影象特徵點的自動檢測。具體實現是採用SurfFeatureDetector/SiftFeatureDetector類的detect函式檢測SURF/SIFT特徵的關鍵點,並儲

SIFT特徵檢測特徵描述,特徵匹配理解

前面提到了Harris角點檢測,此方法一個很明顯的缺點是不能解決尺度變化不變性,因為Harris中檢測一個點是不是角點是看這個點所在patch和周圍各個方向patch是否有明顯變化,可是尺度變化後這個方法不適用,SIFT利用差分高斯金字塔解決了這個問題。SIFT特徵點檢測步驟

特徵檢測學習_3(Harris演算法)

/* int block_size: 鄰域大小,相鄰畫素的尺寸,就是視窗函式大小 W(x, y); int aperture_size: aperture size for the Sobel(); 因為我們用sobel函式來得到Ix, Iy; double k :

redis學習 key鍵,Python操作redis 鍵

lpad ren redis key lee 設置 amp res 列表 pex # -*- coding: utf-8 -*- import redis #這個redis 連接不能用,請根據自己的需要修改 r =redis.Redis(host="123.516.174

機器學習算法基礎概念學習總結轉載

原則 不清楚 tof 條件 cnblogs 偽代碼 相關關系 什麽 最近鄰   來源:lantian0802的專欄   blog.csdn.net/lantian0802/article/details/38333479      一、基礎概念        

Java學習計劃轉載

建立 轉載 java編程思想 背景 自己 告訴 清晰 入行 距離 第一部分 在搭建SSM的過程中,可能會經常接觸到一個叫maven的工具。這個工具也是你以後工作當中幾乎是必須要使用的工具,所以你在搭建SSM的過程中,也可以順便了解一下maven的知識。在你目前這個階段,你只

Tkprof工具詳解轉載

depth ber 官方 Go ble 不可 _id sys 避免 在數據庫生成的oracle trace文件中,可讀性是比較差的,此時可使用tkprof工具來格式化trace文件,tkprof是一個命令行工具,作用就是把原始的跟蹤trace文件作為輸入,然後格式化一個可讀

《安全運維工程師成長手冊》學習筆記轉載

tps 成長 要掌握 黑客 行業 安全行業 工作 屬於 接口 前言 (0)“識人面相” 明確自身位置、技術能力、以及希望達到的高度。物以類聚,人以群分;選對團隊,跟準人,才能在這”惡劣”的環境中生存下去。 這一部分的內容是從黃登老師的自身經歷出發,先介紹了他的職業生涯,

大資料學習路線轉載

學習路線文章 哎,都是淚!!! 一、大資料技術基礎 1、linux操作基礎 linux系統簡介與安裝 linux常用命令–檔案操作 linux常用命令–使用者管理與許可權 linux常用命令–系統管理 linux常用命令–免密登陸配置與網路管理 linux上常用軟體安裝 linux本地yum源配置及yum

python學習2轉載

一、流程控制之while迴圈 語法:while 條件:  迴圈體else:  else語句(當條件不成立的時候執行這裡 和break沒關係) 判斷條件是否成立。 如果成立執行迴圈體。然後再次判斷條件,。。。。。直到條件不成立的時候跳出迴圈 break :終止當前本層迴圈(直接跳到迴圈的末尾)

python學習3轉載

主要內容: 列表 和 元組和字典 列表 一、列表介紹 列表是一種能儲存大量資料的資料結構,是能裝物件的物件。由方括號 [] 括起來,能放任意型別的資料,資料之間用逗號隔開 列表儲存資料是有順序的 二、增刪改查 lis = [] 1、增加 (三種) lis.append()  &nb

基於python的tesseract學習初學者

tesseract是一個OCR庫,可以通過訓練識別出任何字型,也可以識別出任何unicode字元。 一、安裝(本文為win10開發環境) 下載地址:https://digi.bib.uni-mannheim.de/tesseract/ 執行安裝檔案,一路下一步就好。 安裝完成需將te

javascript基礎學習變數

var 用var申明一個變數: var a = 1; console.log(a) // 1 console.log(a) // undefined var a = 1; js的申明過程: var a; // undefined,只申明,不賦值。會有個預設值unde

vim 學習筆記 轉載

vim簡單使用教程 vim的學習曲線相當的大(參看各種文字編輯器的學習曲線),所以,如果你一開始看到的是一大堆VIM的命令分類,你一定會對這個編輯器失去興趣的。下面的文章翻譯自《Learn Vim Progressively》,我覺得這是給新手最好的VIM的升級