本週博客咱們給你們介紹一下SiftGPU。因爲特徵匹配是SLAM中很是耗時間的一步,許多人都想把它的時間降至最短,所以目前ORB成了很是受歡迎的特徵。而老牌SIFT,則一直給人一種「很嚴謹很精確,但計算很是慢」的印象。在一個普通的PC上,計算一個640$\times$480的圖中的SIFT大約須要幾百毫秒左右。若是特徵都要算300ms,加上別的ICP什麼的,一個SLAM就只能作成兩幀左右的速度了,這是很使人失望的。而ORB,FAST之類的特徵,因爲計算速度較快,在SLAM這種實時性要求較高的場合更受歡迎。linux
那麼,今天咱們來講一個GPU版本的SIFT。它是由Wu Changchang同窗寫的。它可以明顯地提高你的程序提取SIFT的速度。同時,它的代碼大部分是基於OpenGL的,即便在沒有英偉達顯卡的機器上也能運行起來。但另外一方面,出於某種(歷史或人爲的)緣由,SiftGPU的代碼配置起來並不很容易(特別是在Linux下,彷佛SiftGPU做者是在win下開發的),代碼新人可能會以爲比較困難。如今咱們帶着你們實踐一下SiftGPU,我會給出一個例程供你們測試。ios
首先,說說個人運行配置。我用的機器是Thinkpad T450, Intel+Nvidia GetForce 940m顯卡。但我我的只用Intel卡,因此我就不編譯Cuda了。各位有上好N卡的同窗也能夠搞個Cuda下來編,可能會提升一點速度(但我不保證)。我使用的操做系統是Ubuntu 14.04,OpenCV3.1版本。因此我假設你OpenCV已經裝好啦!(因此c++編譯器總有的吧!) 不過opencv是否是3.1版本是不要緊的,程序在2.x版本上也是能正常運行的。c++
小蘿蔔:師兄你這真是宅男標配啊!你究竟是在講配置環境仍是在秀桌面啊!git
SiftGPU主頁:http://www.cs.unc.edu/~ccwu/siftgpu/ github
請找到「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網站的版本。ide
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,提一下特徵試試。爲測試速度,咱們還要記錄一下代碼運行時間。
如今咱們來寫一個測試程序。因爲它比較短,我就不專門搞個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 new’ as 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類型啦!編譯就能經過嘍!
如下就是在師兄電腦上的運行結果啦,你們能夠看一下:
對於OpenCV已經讀入的數據,在640x480的分辨率下,用SiftGPU只需40多毫秒便可完成計算了呢!GPU真的是很強大啊!即便在沒有Cuda的狀況下都取得了近十倍的加速啊!效果拔羣!
小蘿蔔:個人ORB只要30毫秒就好了,哼.
本篇介紹了SiftGPU,咱們帶領讀者完成了它的編譯,並在本身的程序內實現了調用。能夠看到它的加速效果仍是不錯的!
另外,這也是個人一次嘗試,告訴讀者在編譯過程當中遇到問題該如何處理。我本能夠直接跳過這些buggy的部分,告訴你們運行的結果。但我以爲這樣子講可能對讀者更有幫助啦!
若是你以爲個人博客有幫助,能夠進行幾塊錢的小額贊助,幫助我把博客寫得更好。