SLAM拾萃(3):siftGPU


前言

  本周博客我們給大家介紹一下SiftGPU。由於特征匹配是SLAM中非常耗時間的一步,許多人都想把它的時間降至最短,因此目前ORB成了非常受歡迎的特征。而老牌SIFT,則一直給人一種“很嚴謹很精確,但計算非常慢”的印象。在一個普通的PC上,計算一個640$\times$480的圖中的SIFT大約需要幾百毫秒左右。如果特征都要算300ms,加上別的ICP什么的,一個SLAM就只能做成兩幀左右的速度了,這是很令人失望的。而ORB,FAST之類的特征,由於計算速度較快,在SLAM這種實時性要求較高的場合更受歡迎。

  那么,今天我們來說一個GPU版本的SIFT。它是由Wu Changchang同學寫的。它能夠明顯地提升你的程序提取SIFT的速度。同時,它的代碼大部分是基於OpenGL的,即使在沒有英偉達顯卡的機器上也能運行起來。但另一方面,出於某種(歷史或人為的)原因,SiftGPU的代碼配置起來並不很容易(特別是在Linux下,似乎SiftGPU作者是在win下開發的),代碼新人可能會覺得比較困難。現在我們帶着大家實踐一下SiftGPU,我會給出一個例程供大家測試。

  首先,說說我的運行配置。我用的機器是Thinkpad T450, Intel+Nvidia GetForce 940m顯卡。但我個人只用Intel卡,所以我就不編譯Cuda了。各位有上好N卡的同學也可以搞個Cuda下來編,可能會提高一點速度(但我不保證)。我使用的操作系統是Ubuntu 14.04,OpenCV3.1版本。所以我假設你OpenCV已經裝好啦!(所以c++編譯器總有的吧!) 不過opencv是不是3.1版本是沒關系的,程序在2.x版本上也是能正常運行的。

  小蘿卜:師兄你這真是宅男標配啊!你到底是在講配置環境還是在秀桌面啊!


下載SiftGPU與依賴庫

   SiftGPU主頁:http://www.cs.unc.edu/~ccwu/siftgpu/ 

  請找到“SiftGPU-V400"那個下載鏈接,保存到你的電腦上。然后解壓縮,進入壓縮后的文件夾。假定你也在用Ubuntu,那么你現在的目錄應該是 ~/Downloads/SiftGPU/ 。注意,為了和我保持一致,請你暫時不要下載github上面那個版本,那個與它稍有不同。如果你就是喜歡github,可以把這個編譯好,再考慮用github版本。

  現在我們來安裝依賴項。首先,確保你機器上有OpenGL,請安裝以下幾項工具:

1 sudo apt-get install libgl1-mesa-dev libglu1-mesa-dev freeglut3-dev 

   然后,要安裝glew1.5.1以上版本。據我個人經驗,最好是去下載glew網站的版本。

  glew的網址:http://glew.sourceforge.net/

  請下載那個1.13.0版本,zip文件或tgz均可。下載到本地並解壓,然后進入該文件夾。我的在~/Downloads/glew-1.13.0

  glew是用makefile直接編譯的,不用cmake。所以我們直接敲:

1 make 
2 sudo make install 

   即可。很快它就編譯好了。

  注意看make install輸出的信息。它默認把編譯好的庫文件libglew.so.1.13放到了/usr/lib64下。由於之后我們要用cmake去編,但是它可能找不到這個文件夾,所以我們現在先告訴系統,該文件夾下有要找的鏈接庫:  

1 sudo ldconfig /usr/lib64/

   ok,現在我們處理完了glew,轉去編譯SiftGPU。SiftGPU也是用Makefile編譯的。現在轉到SiftGPU所在文件夾。調用

1 make

   來完成編譯。如果順利的話,你會在bin/目錄里得到幾個二進制和一個libsiftgpu.so庫文件。我們主要使用這個庫文件。現在看一下它的鏈接是否正確:

1 ldd bin/libsiftgpu.so

   這個命令會輸出與它鏈接的庫的信息。請保證沒有出現某個鏈接(特別是剛才的GLEW)沒有找到的情況(否則這里會通過,但后面會出現undefined reference)。像我這樣:

  如果這步正確無誤,恭喜你,SiftGPU已經編譯完成了!真是可喜可賀呀!

  小蘿卜:然后呢?師兄我還沒看到什么感覺很厲害的東西啊?

  師兄:下面我們來實際找一個圖片,寫一段小程序調用SiftGPU,提一下特征試試。為測試速度,我們還要記錄一下代碼運行時間。


測試SiftGPU

  現在我們來寫一個測試程序。由於它比較短,我就不專門搞個github了。請大家跟着我做即可。

  首先,隨意新建一個目錄,比如test_siftgpu。我們要寫一個c++程序,然后用cmake編譯它。現在新建一個main.cpp,內容如下:

 1 // SiftGPU模塊
 2 #include <SiftGPU.h>
 3 
 4 //標准C++
 5 #include <iostream>
 6 #include <vector>
 7 
 8 // OpenCV圖像
 9 #include <opencv2/core/core.hpp>
10 #include <opencv2/highgui/highgui.hpp>
11 
12 // boost庫中計時函數
13 #include <boost/timer.hpp>
14 
15 // OpenGL
16 #include <GL/gl.h>
17 
18 using namespace std;
19 
20 int main( int argc, char** argv)
21 {
22     //聲明SiftGPU並初始化
23     SiftGPU sift;
24     char* myargv[4] ={ "-fo", "-1", "-v", "1"};
25     sift.ParseParam(4, myargv);
26 
27     //檢查硬件是否支持SiftGPU
28     int support = sift.CreateContextGL();
29     if ( support != SiftGPU::SIFTGPU_FULL_SUPPORTED )
30     {
31         cerr<<"SiftGPU is not supported!"<<endl;
32         return 2;
33     }
34 
35     //測試直接讀取一張圖像
36     cout<<"running sift"<<endl;
37     boost::timer timer;
38     //在此填入你想測試的圖像的路徑!不要用我的路徑!不要用我的路徑!不要用我的路徑!
39     sift.RunSIFT( "/home/xiang/wallE-slam/data/rgb1.png" );
40     cout<<"siftgpu::runsift() cost time="<<timer.elapsed()<<endl;
41     
42     // 獲取關鍵點與描述子
43     int num = sift.GetFeatureNum();
44     cout<<"Feature number="<<num<<endl;
45     vector<float> descriptors(128*num);
46     vector<SiftGPU::SiftKeypoint> keys(num);
47     timer.restart();
48     sift.GetFeatureVector(&keys[0], &descriptors[0]);
49     cout<<"siftgpu::getFeatureVector() cost time="<<timer.elapsed()<<endl;
50 
51     // 先用OpenCV讀取一個圖像,然后調用SiftGPU提取特征
52     cv::Mat img = cv::imread("/home/xiang/wallE-slam/data/rgb1.png", 0);
53     int width = img.cols;
54     int height = img.rows;
55     timer.restart();
56     // 注意我們處理的是灰度圖,故照如下設置
57     sift.RunSIFT(width, height, img.data, GL_INTENSITY8, GL_UNSIGNED_BYTE);
58     cout<<"siftgpu::runSIFT() cost time="<<timer.elapsed()<<endl;
59 
60     return 0;
61 }

   Sift接口還是相當簡單的。在這程序里,我們一共做了三件事。一是直接對一個圖像路徑提Sift,二是獲取Sift的關鍵點和描述子。三是對OpenCV讀取的一個圖像提取Sift。我們分別測了三者的效果和時間。

  接下來,寫一個CMakeLists.txt來編譯上面的文件。

cmake_minimum_required(VERSION 2.8.3)
project(test_siftgpu)

# OpenCV依賴
find_package( OpenCV REQUIRED )

# OpenGL
find_package(OpenGL REQUIRED)

# GLUT
find_package(GLUT REQUIRED)

# Glew
find_package(Glew REQUIRED)

# SiftGPU:手動設置其頭文件與庫文件所在位置
include_directories("/home/xiang/Downloads/SiftGPU/src/SiftGPU/" ${OpenGL_INCLUDE_DIR})
set(SIFTGPU_LIBS "/home/xiang/Downloads/SiftGPU/bin/libsiftgpu.so")

add_executable( testSIFTGPU main.cpp )

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

   對於SiftGPU,由於它本身沒有提供cmake的配置,我們手動去設置了它的頭文件與庫文件的鏈接方式。大家可以學習一下這種比較土的辦法……然后就是常見的cmake啦:

mkdir build
cd build
cmake ..
make

   等一下!是不是還忘了些什么呢?嗯,如果你直接去cmake的話,會報一個find_package找不到glew的錯!因為我們裝glew的時候是直接用make install裝的嘛,cmake怎么會知道我們干了這件事呢?所以此時find_package(Glew REQUIRED)就會出錯啦!

  小蘿卜:為什么出錯了你還是很高興的樣子……

  師兄:對!現在呢我們要自己寫一個FindGlew.cmake文件嘍。請打開你的編輯器,輸入:

 1 #
 2 # Try to find GLEW library and include path.
 3 # Once done this will define
 4 #
 5 # GLEW_FOUND
 6 # GLEW_INCLUDE_PATH
 7 # GLEW_LIBRARY
 8 # 
 9 
10 IF (WIN32)
11     FIND_PATH( GLEW_INCLUDE_PATH GL/glew.h
12         $ENV{PROGRAMFILES}/GLEW/include
13         ${PROJECT_SOURCE_DIR}/src/nvgl/glew/include
14         DOC "The directory where GL/glew.h resides")
15     FIND_LIBRARY( GLEW_LIBRARY
16         NAMES glew GLEW glew32 glew32s
17         PATHS
18         $ENV{PROGRAMFILES}/GLEW/lib
19         ${PROJECT_SOURCE_DIR}/src/nvgl/glew/bin
20         ${PROJECT_SOURCE_DIR}/src/nvgl/glew/lib
21         DOC "The GLEW library")
22 ELSE (WIN32)
23     FIND_PATH( GLEW_INCLUDE_PATH GL/glew.h
24         /usr/include
25         /usr/local/include
26         /sw/include
27         /opt/local/include
28         DOC "The directory where GL/glew.h resides")
29     FIND_LIBRARY( GLEW_LIBRARY
30         NAMES GLEW glew
31         PATHS
32         /usr/lib64
33         /usr/lib
34         /usr/local/lib64
35         /usr/local/lib
36         /sw/lib
37         /opt/local/lib
38         DOC "The GLEW library")
39 ENDIF (WIN32)
40 
41 IF (GLEW_INCLUDE_PATH)
42     SET( GLEW_FOUND 1 CACHE STRING "Set to 1 if GLEW is found, 0 otherwise")
43 ELSE (GLEW_INCLUDE_PATH)
44     SET( GLEW_FOUND 0 CACHE STRING "Set to 1 if GLEW is found, 0 otherwise")
45 ENDIF (GLEW_INCLUDE_PATH)
46 
47 MARK_AS_ADVANCED( GLEW_FOUND )

   然后呢,把這個文件放到cmake的modules文件夾中去!這樣cmake就會知道你在調用find_package(Glew)時怎么找啦!

sudo cp ./FindGlew.cmake /usr/share/cmake-2.8/Modules/

   注意到這個文件所在的目錄通常是沒有寫權限的的哦!所以我們要用sudo提升到管理員權限才行呢。

  這時,再調用cmake ..,就不會報上面的錯誤啦!而編譯也得以順利進行下去了。

  但是!但是!編譯還是出錯了,錯誤如下:

/home/xiang/Downloads/SiftGPU/src/SiftGPU/SiftGPU.h:336:40: error: declaration of ‘operator newas non-function SIFTGPU_EXPORT void* operator new (size_t size);

   這是什么原因呢?g++的編譯錯誤很難懂,一直為人詬病。師兄仔細查了查,發現SiftGPU作者重載了new運算符,但是它的參數"size_t size"中的"size_t"類型,在linux下編譯是需要指定一個頭文件的!所以我們打開~/Downloads/SiftGPU/src/SiftGPU/SiftGPU.h文件,在上頭加入一個

#include <stddef.h>

  

  這樣編譯器就會找到size_t類型啦!編譯就能通過嘍!


SiftGPU運行結果

  以下就是在師兄電腦上的運行結果啦,大家可以看一下:

  對於OpenCV已經讀入的數據,在640x480的分辨率下,用SiftGPU只需40多毫秒即可完成計算了呢!GPU真的是很強大啊!即使在沒有Cuda的情況下都取得了近十倍的加速啊!效果拔群!

  小蘿卜:我的ORB只要30毫秒就行了,哼.


小結

  本篇介紹了SiftGPU,我們帶領讀者完成了它的編譯,並在自己的程序內實現了調用。可以看到它的加速效果還是不錯的!

  另外,這也是我的一次嘗試,告訴讀者在編譯過程中遇到問題該如何處理。我本可以直接跳過這些buggy的部分,告訴大家運行的結果。但我覺得這樣子講可能對讀者更有幫助啦!


 

  如果你覺得我的博客有幫助,可以進行幾塊錢的小額贊助,幫助我把博客寫得更好。

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM