SiftGPU在Ubuntu和Windows下的編譯與使用

Sift特徵應該是使用最多的局部特徵了,可是相比其餘的一些特徵描述符,計算sift特徵描述符的時間較長。Changchang Wu使用GPU加速,實現了GPU版的sift特徵提取SiftGPU。 SiftGPU應該是在Windows環境下完成的,其在Windows下的配置較爲簡單。php

本文首先解釋了,在Ubuntu下SiftGPU的編譯,並簡單的實現了一個類,封裝SiftGPU的特徵提取和匹配。在最後簡單的介紹了下,SiftGPU在Windows下的使用。c++

Ubuntu下的安裝與使用

  • 安裝依賴庫
sudo apt-get install libgl1-mesa-dev libglu1-mesa-dev freeglut3-dev
  • 編譯glew 下載地址 glew
make
sudo make install

安裝位置爲/usr/lib64git

編譯SiftGPU

從Git上下載SiftGPU的源代碼,下載的原始代碼在編譯的時候須要修改兩個部分,能夠從原做者處clone,也能夠clone我修改後的代碼github

具體編譯的過程以下:windows

  • 在執行make 編譯,若是遇到fatal error: IL/il.h: No such file or directory,使用下面的命令安裝dev image library. sudo apt-get install libdevil-dev函數

  • 原始的代碼在編譯的時候有一處錯誤,編譯不過。 error: declaration of ‘operator new’ as non-function SIFTGPU_EXPORT void* operator new (size_t size); 須要在頭文件src/SiftGPU/SiftGPU.h中添加一句測試

#include <stddef.h>
  • 原始代碼編譯生成的庫,在使用的時候會出現錯誤:freeglut ERROR: Function <glutDestroyWindow> called without first calling 'glutInit'. 修改 src/SiftGPU/LiteWindow.h中的
virtual ~LiteWindow()   {  if(glut_id > 0) glutDestroyWindow(glut_id);  }

修改成ui

virtual ~LiteWindow()   
{  
    if(glut_id > 0) 
    {
      int argc = 0;
      char** argv;
      glutInit(&argc, argv); 
      glutDestroyWindow(glut_id); 
    } 
}
  • 編譯生成的庫在/bin/libsiftgpu.so,能夠使用ldd bin/libsiftgpu.so測試生成的庫連接是否正確。

使用

首先配置下CMakeLists.txt以下:url

cmake_minimum_required(VERSION 2.8.3)

project(test_siftgpu)

set(CMAKE_VERBOSE_MAKEFILE on)

set(OpenCV_DIR "/usr/local/opencv3.4.4/share/OpenCV")
find_package(OpenCV REQUIRED)

find_package(OpenGL REQUIRED)

find_package(GLUT REQUIRED)

#find_package(Glew REQUIRED)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

# set siftgpu
include_directories("/home/liqiang/Downloads/SiftGPU/src/SiftGPU")
include_directories(${OpenGL_INCLUDE_DIR})

link_directories(/usr/lib64) # GLEW

set(SIFTGPU_LIBS "/home/liqiang/Downloads/SiftGPU/bin/libsiftgpu.so")

add_executable(testSiftGPU main.cc)

target_link_libraries(testSiftGPU ${OpenCV_LIBS} ${SIFTGPU_LIBS} ${GLEW_LIBRARIES} ${GLUT_LIBRARIES} ${OPENGL_LIBRARIES})

就是設置lincludlib的位置,手動指定GLEW的位置link_directories(/usr/lib64) # GLEW和SiftGPU的庫和頭文件的位置include_directories("/home/liqiang/Downloads/SiftGPU/src/SiftGPU")set(SIFTGPU_LIBS "/home/liqiang/Downloads/SiftGPU/bin/libsiftgpu.so").es5

配置好CMakeLists.txt後,就能夠編譯下面的代碼進行特徵的提取和匹配了。

int main()
{
    // Read image  
    auto detector = cv::xfeatures2d::SIFT::create();

    Mat des;
    vector<KeyPoint> kpts;

    string file1 = "/home/liqiang/Documents/shared/8.jpg";
    auto t = getTickCount();
    auto img = imread(file1);
    detector->detectAndCompute(img,noArray(),kpts,des);
    auto end = static_cast<double>(getTickCount() - t) / getTickFrequency();
    cout << "OpenCV get sift consume:" << end << endl;
    cout << "count:" << kpts.size() << endl;


    // Declare sift and initlize
    SiftGPU sift;
    char* myargv[4] = {"-fo","-1","-v","1"};
    sift.ParseParam(4,myargv);

    // Check hardware is support siftGPU
    int support = sift.CreateContextGL();
    if(support != SiftGPU::SIFTGPU_FULL_SUPPORTED){
        cerr << "SiftGPU is not supported!" << endl;
        return 2;
    }

    auto img1 = imread("/home/liqiang/Documents/shared/3.jpg");
    auto img2 = imread("/home/liqiang/Documents/shared/4.jpg");
    auto img3 = imread("/home/liqiang/Documents/shared/5.jpg");
    auto img4 = imread("/home/liqiang/Documents/shared/6.jpg");
    auto img5 = imread("/home/liqiang/Documents/shared/7.jpg");

    auto f = [&sift](Mat &img,vector<float> &des,vector<SiftGPU::SiftKeypoint> &kpts){
        
        auto t = getTickCount();
        sift.RunSIFT(img.cols,img.rows,img.data,GL_RGB,GL_UNSIGNED_BYTE);
        auto num1 = sift.GetFeatureNum();
        
        des.resize(128 * num1);
        kpts.resize(num1);
        sift.GetFeatureVector(&kpts[0],&des[0]);
        cout << "=======================================" << endl;
        cout << "width x height : " << img.cols << "x" << img.rows << endl;
        cout << "Features count:" << num1 << endl;
        cout << "Extract features,consume:" << static_cast<double>(getTickCount() - t) / getTickFrequency() << endl;
    };


    vector<float> des1,des2,des3,des4,des5;
    vector<SiftGPU::SiftKeypoint> kpts1,kpts2,kpts3,kpts4,kpts5;

    f(img1,des1,kpts1);
    f(img2,des2,kpts2);
    f(img3,des3,kpts3);
    f(img4,des4,kpts4);
    f(img5,des5,kpts5);


    SiftMatchGPU matcher;
    matcher.VerifyContextGL();

    matcher.SetDescriptors(0,kpts1.size(),&des1[0]);
    matcher.SetDescriptors(1,kpts2.size(),&des2[0]);

    int (*match_buf)[2] = new int[kpts1.size()][2];
    t = getTickCount();
    int num_match = matcher.GetSiftMatch(kpts1.size(), match_buf);
    cout << "Match keypoints count:" << num_match << endl;
    end = static_cast<double>(getTickCount() - t) / getTickFrequency();

    cout << "Match,consume:" << end << endl;
}

SiftGPU進行特徵提取能夠分爲三步

  • 實例化SiftGPU,並設置其參數
char* myargv[4] = {"-fo","-1","-v","1"};
    sift.ParseParam(4,myargv);

關於SiftGPU的具體的參數說明,能夠參考其/SiftGPU/doc/manual.pdf使用手冊。

  • 調用RunSift函數進行特徵提取,該函數有多種重載。 經常使用的有兩個:

    • 直接傳入圖像的路徑RunSift(const char *imgpaht)
    • 傳入圖像的數據RunSift(int width,int height,const void *data,unsigned int gl_format,unsigned int gl_type) 上述代碼中使用OpenCV讀取圖像,而後利用再調用RunSift提取特徵。
  • 調用GetFeatureVector取得提取到的特徵描述。

上面代碼中,將上述三步封裝在了一個Lambda表達式中,而後調用改表達式連續的提取了多張圖片的sift特徵。其運行結果以下:

使用測試的幾張圖象尺寸相同,內容上的變化也不是很大。 上述結果能夠看到,使用OpenCV提取特徵耗費的時間爲:48ms,使用SiftGPU提取第一張圖像的特徵耗費的時間是:56ms,對比OpenCV甚至有點差距。 可是,SiftGPU在提取後幾張圖像的效率提高就比較明顯了,只有十幾毫秒。

在最後使用SiftGPU對提取的特徵進行了匹配,也是很快的。

封裝

對SiftGPU簡單的封裝了下,方便使用。代碼以下:

class GpuFeatureDetector{

    enum InitStatus{
        INIT_OK,
        INIT_IS_NOT_SUPPORT,
        INIT_VERIFY_FAILED
    };

public:
    GpuFeatureDetector() = default;
    ~GpuFeatureDetector() {
        if(m_siftGpuDetector) delete m_siftGpuDetector;
        if(m_siftGpuMatcher)  delete m_siftGpuMatcher;
    }
    InitStatus create(){
        m_siftGpuDetector = new SiftGPU();

        char* myargv[4] = {"-fo","-1","-v","1"};
        m_siftGpuDetector->ParseParam(4,myargv);
        // Set edge threshold, dog threshold

        if(m_siftGpuDetector->CreateContextGL() != SiftGPU::SIFTGPU_FULL_SUPPORTED){
            cerr << "SiftGPU is not supported!" << endl;
            return InitStatus::INIT_IS_NOT_SUPPORT;
        }

        m_siftGpuMatcher = new SiftMatchGPU();
        m_siftGpuMatcher->VerifyContextGL();

        m_maxMatch = 4096;

        return INIT_OK;
    }

    void detectAndCompute(const Mat &img,Mat &descriptors,vector<KeyPoint> &kpts){

        assert(img.channels() == 3); // RGB

        m_siftGpuDetector->RunSIFT(img.cols,img.rows,img.data,GL_RGB,GL_UNSIGNED_BYTE);
        auto num1 = m_siftGpuDetector->GetFeatureNum();

        vector<float> des(128 * num1);
        vector<SiftGPU::SiftKeypoint> keypoints(num1);
        m_siftGpuDetector->GetFeatureVector(&keypoints[0],&des[0]);

        // Trans to Mat
        Mat m(des);
        descriptors = m.reshape(1,num1).clone();

        for(const SiftGPU::SiftKeypoint &kp : keypoints){
            KeyPoint t(kp.x,kp.y,kp.s,kp.o);
            kpts.push_back(t);
        }     
    }

    void transToRootSift(const cv::Mat &siftFeature,cv::Mat &rootSiftFeature){
        for(int i = 0; i < siftFeature.rows; i ++){
            // Conver to float type
            Mat f;
            siftFeature.row(i).convertTo(f,CV_32FC1);

            normalize(f,f,1,0,NORM_L1); // l1 normalize
            sqrt(f,f); // sqrt-root  root-sift
            rootSiftFeature.push_back(f);
        }
    }

    int gpuMatch(const Mat &des1,const Mat &des2){
        
        m_siftGpuMatcher->SetDescriptors(0,des1.rows,des1.data);
        m_siftGpuMatcher->SetDescriptors(1,des2.rows,des2.data);

        int (*match_buf)[2] = new int[m_maxMatch][2];
        
        auto matchNum = m_siftGpuMatcher->GetSiftMatch(m_maxMatch,match_buf);
        
        delete[] match_buf;
        
        return matchNum;
    }

    int gpuMatch(const Mat &des1,const Mat &des2,vector<DMatch>& matches){
        m_siftGpuMatcher->SetDescriptors(0,des1.rows,(float*)des1.data);
        m_siftGpuMatcher->SetDescriptors(1,des2.rows,(float*)des2.data);

        int (*match_buf)[2] = new int[m_maxMatch][2];
        
        auto matchNum = m_siftGpuMatcher->GetSiftMatch(m_maxMatch,match_buf);

        for(int i = 0 ;i  < matchNum; i ++) {
            DMatch dm(match_buf[i][0],match_buf[i][1],0);
            matches.push_back(dm);
        }

        delete[] match_buf;
        return matchNum;
    }
private:
    SiftGPU *m_siftGpuDetector;
    SiftMatchGPU *m_siftGpuMatcher;

    int m_maxMatch;
};

m_maxMatch 是進行匹配時,最多的匹配點的個數。默認的是4096. 簡單的封裝,並無提供過多的參數設置。有如下功能:

  • sift特徵的提取,並將提取到的結果轉換爲OpenCV的數據形式,便於和OpenCV一塊兒使用
  • 將sift轉換爲RootSift
  • 利用SiftGPU進行特徵的匹配,其匹配進行了比率測試,刪除了不正確的匹配點。

其測試代碼以下:

GpuFeatureDetector fp;
    fp.create();

    Mat des11,des22;
    vector<KeyPoint> kpts11,kpts22;

    fp.detectAndCompute(img1,des11,kpts11);
    fp.detectAndCompute(img2,des22,kpts22);

    vector<DMatch> matches;
    cout << "matches:" << fp.gpuMatch(des11,des22,matches) << endl;

    Mat matchImg;
    t = getTickCount();
    drawMatches(img1,kpts11,img2,kpts22,matches,matchImg);
    cout << static_cast<double>(getTickCount() - t) / getTickFrequency() << endl;
    imshow("matches",matchImg);
    waitKey();

運行結果

其過濾後的效果,仍是不錯的。

下圖是相同的圖像,使用opencv提取特徵點後進行匹配(比例測試過濾,ratio=0.8,和gpu的同樣)的結果

上述代碼可從本人GitHub上clone https://github.com/brookicv/codeSnippet/tree/master/SiftGPU

Windows下的安裝與使用

首先從從Git上下載源代碼,在SiftGPU/msvc目錄下有兩個解決方案SiftGPU.slnSiftGPU_CUDA_Enabled.sln看名字就知道了,一個是使用GLSL的,另外一個是使用CUDA的。 windows沒有配置cuda的環境,這裏就只編譯SiftGPU.sln。打開該解決方案,以下圖:

SiftGPU項目就是須要的,編譯生成SiftGPU.dll。 其他的幾個是測試項目和一些使用的例子。該項目的解決方案是vs2010的使用的Windows SDK爲8.1,若是是windows10的系統會提示找不到相應的SDK,能夠右鍵解決方案選擇重定解決方案目標會從新設置使用Windows10的SDK。

這裏只描述SiftGPU的編譯過程,其他的幾個項目配置相似。

  • 配置GLEW 從http://glew.sourceforge.net/ 下載編譯好的windows的二進制庫,直接解壓開來,獲得include和lib目錄。右鍵 SifGPU項目,選擇屬性,添加C++的包含目錄 glew/include;添加庫目錄/glew/lib/Release/Win32,若是要生成64位的,這裏要將目錄配置到x64下面。

  • 配置DevIL DevIL是一個跨平臺的圖像庫,這裏須要使用期開發的SDK,下載地址http://openil.sourceforge.net/download.php 。 注意要選擇DevIL 1.8.0 SDK for Windows,須要其頭文件和lib。 下載後,如GLEW相似添加頭文件和lib目錄。 須要注意的是,因爲在代碼中,做者使用了相對路徑來加載DevIL.lib,由於這裏配置lib的路徑,須要修改這部分代碼。將GLTextImage.cpp中的49行附近的代碼修改成以下

#ifndef SIFTGPU_NO_DEVIL
    #include "IL/il.h"
    #if  defined(_WIN64)
        #pragma comment(lib, "DevIL64.lib")
    #elif  defined(_WIN32) 
        #pragma comment(lib, "DevIL.lib")
    #endif
#else
    #include <string.h>
#endif

就是去掉了"DevIL.lib"前面的相對路徑,改成只按名稱來查找(上面配置了lib的目錄)。

編譯SiftGPU,生成的lib文件位於SiftGPU/lib/SiftGPU_d.lib

使用的話,只須要配置c++項目的頭文件目錄到SiftGPU/src/SiftGPU下,lib目錄到SiftGPU/lib/。 或者,能夠精簡下,將SiftGPU_d.lib和頭文件複製到項目的目錄下。

相關文章
相關標籤/搜索