當有讀者看到我這篇SiftGPU入門的學習筆記時,相信你已經讀過了高博那篇《SLAM拾萃:SiftGPU》,那篇文章寫於16年,已經過去兩年的時間。在我嘗試配置SiftGPU的環境時,遇到了幾個問題,在網絡上也少有較為系統的關於SiftGPU的介紹,因此覺得有必要記錄下來,以便同樣對此感興趣的同學們少走彎路。
暑假的時候參加了高分舉辦的無人機大賽,在進行圖像處理的時候用到過特征提取,當時主要是考慮SIFT和SURF兩種方法,由於提取速度上的優勢,我采用了SURF。比賽之后讀過一些博客和文章,發覺SIFT的准確率應該更高一些,而我在比賽中也發現SURF偶爾會出現無法匹配的情況。OpenCV集成了SIFT算子,我們可以比較容易地利用其中的函數進行特征點的檢測,而由於傳統的SIFT算法速度較為緩慢,檢測一張圖片在台式機上通常都需要100+ms,因此傳統的SIFT算法很難應用在無人機這種資源緊張而且對速度要求很高的平台上。目前我們組的無人平台上主要應用過ORBSLAM和VINS。
我的原計划是閱讀Lowe的論文,理解算法的原理,而后對源碼進行一定的優化以在特定的情景中加快檢測速度,然而在一次組會中,老板提到了SiftGPU讓我去了解一下,於是就有了這篇學習筆記。原作者Wu Changchang來自北卡羅來納大學教堂山分校,高博文章中的下載鏈接基於此。
筆者使用的是Ubuntu 16.04的系統,工作機配置了CUDA9.0,筆記本配置的是CUDA9.2,OpenCV的版本都是3.2.0,關於OpenCV及其contrib的編譯可以參照我的另一篇博文[環境配置]Ubuntu 16.04 源碼編譯安裝OpenCV-3.2.0+OpenCV_contrib-3.2.0及產生的問題。SiftGPU大部分代碼是基於OpenGL的,因此不編譯CUDA也沒有問題,兩者速度的對比后面會提到。
下載和編譯
源碼編譯之前我們需要安裝一些包以及Glew。
$ sudo apt-get install libgl1-mesa-dev libglu1-mesa-dev freeglut3-dev libdevil-dev
Glew的源碼位於其官網,下載最新版的即可,之后直接解壓。
$ cd Downloads/glew-2.1.0
$ make
$ sudo make install
我們需要告訴系統的cmake工具編譯好的文件的位置。
$ sudo ldconfig /usr/lib64/
接下來就是SiftGPU的編譯了,Wu Changchang的源碼鏈接已經失效,因此只能去萬能的github上下載源碼了。但是這個版本的SiftGPU有幾處問題,編譯之前需要更正,否則無法正確編譯完成,為方便起見,我fork之后更新了需要更正的文件,放在我的github上,大家可以直接clone我更正后的代碼,然后編譯SiftGPU。
$ cd Downloads/SiftGPU
$ make
檢查一下得到的bin/libsiftgpu.so的鏈接是否正確
$ ldd bin/libsiftgpu.so
如果得到如下的圖片,說明編譯成功,每一個庫都找到了對應的位置。
筆者對pitzer的源碼主要更改了兩個文件,首先一個關於freeglut的問題,報錯如下
freeglut ERROR: Function <glutDestroyWindow> called without first calling 'glutInit'.
我們打開src/SiftGPU/LiteWindow.h,找到
virtual ~LiteWindow() { if(glut_id > 0) glutDestroyWindow(glut_id); }
改為
virtual ~LiteWindow() { if(glut_id > 0) { int argc = 0; char** argv; glutInit(&argc, argv); glutDestroyWindow(glut_id); } }
第二個文件是src/SiftGPU/SiftGPU.h,在頭文件處加一個
#include <stddef.h>
如果缺少這個頭文件,會報如下的錯誤
/home/yao/Environment/SiftGPU/src/SiftGPU/SiftGPU.h:336:40: error: declaration of ‘operator new’ as non-function SIFTGPU_EXPORT void* operator new (size_t size);
測試與結果
測試代碼我放在了我的github上,有興趣的同學可以下載下來測試。
OpenGL
編譯好之后我們當然需要來測試一下,筆者主要使用cmake從命令行進行編譯,使用編譯器的同學如果是調用cmake工具的話,應該步驟相同。首先我們創建一個工程文件夾,名字就叫test_SiftGPU,在文件夾下創建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)
find_package(CUDA REQUIRED) # SiftGPU:手動設置其頭文件與庫文件所在位置 include_directories("/home/yao/Environment/SiftGPU/src/SiftGPU/" ${OpenGL_INCLUDE_DIR}) set(SIFTGPU_LIBS "/home/yao/Environment/SiftGPU/bin/libsiftgpu.so") add_executable( test_SiftGPU main.cpp ) target_link_libraries( testSiftGPU ${OpenCV_LIBS} ${SIFTGPU_LIBS} ${GLEW_LIBRARIES} ${GLUT_LIBRARIES} ${OPENGL_LIBRARIES} )
注意設置SiftGPU的路徑時讀者要改成自己的路徑。此外高博的文章中寫到需要為Glew寫一個尋找其路徑的cmake文件,但在我安裝完Glew后cmake的modules文件夾下出現了FindGLEW.cmake這個文件,因此我們不需要專門為Glew寫這個文件,直接加上GLEW的find_package代碼,注意大寫。
main.cpp我一開始使用了高博的代碼,在用OpenCV讀入圖像的條件下,檢測出一張640*480圖像中所有的特征點需要不到10ms,但是我發現這個測試程序有一些問題,前一部分是測試直接讀取一張圖片進行Sift檢測,后面是對同一張圖片先讀取再檢測。當我注釋掉前一段代碼時,發現后一段代碼無法運行,我必須取消注釋檢測那一句代碼才可以運行,因此我推測后一段代碼引用了前一段的結果。我重新改寫了一份測試代碼,測試速度還算是比較快,通過調參,可以在我的筆記本上達到30ms/幀的檢測速度。
在定義部分,默認調用OpenGL,如果想要調用CUDA,換用另一個字符串指針即可,但是要預先安裝好CUDA,以及配置好CUDA,配置過程在后面會寫。
#include <SiftGPU.h>
#include <iostream>
#include <vector>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include "opencv2/imgproc/imgproc.hpp"
#include <chrono>
#include <GL/gl.h>
using namespace std;
using namespace chrono;
int main( int argc, char** argv)
{
//聲明SiftGPU並初始化
SiftGPU sift;
char* myargv[5] = { "-m", "-s", "-unpa", "0"};
//char* myargv[4] = {"-fo", "-1", "-cuda", "0"};
sift.ParseParam(5, myargv);
//檢查硬件是否支持SiftGPU
int support = sift.CreateContextGL();
if ( support != SiftGPU::SIFTGPU_FULL_SUPPORTED )
{
std::cerr << "SiftGPU is not supported!" << std::endl;
return 2;
}
sift.ParseParam(5, myargv);
cv::Mat img = cv::imread("/home/yao/workspace/SIFT_detection/image/2.png");
int width = img.cols;
int height = img.rows;
sift.AllocatePyramid(width, height);
sift.SetTightPyramid(1);
auto start_siftgpu = std::chrono::system_clock::now();
sift.RunSIFT(width, height, img.data, GL_RGB, GL_UNSIGNED_BYTE);
float time_cost = chrono::duration_cast<microseconds>(std::chrono::system_clock::now() - start_siftgpu).count() / 1000.0;
std::cout << "siftgpu::runSIFT() cost time=" << time_cost << "ms" << std::endl;
int num = sift.GetFeatureNum();
std::cout << "Feature number=" << num << std::endl;
std::vector<float> descriptors(128*num);
std::vector<SiftGPU::SiftKeypoint> keys(num);
auto start_siftfeature = std::chrono::system_clock::now();
sift.GetFeatureVector(&keys[0], &descriptors[0]);
return 0;
}
然后就是輕車熟路的cmake編譯過程了。
$ mkdir build
$ cd build
$ cmake ..
$ make
$ ./test_SiftGPU
結果如下圖所示
測試代碼只調用了OpenGL,我筆記本的配置是i7-7700HQ,顯卡GTX1050,讀取一張圖像后,提取出一張圖像中所有的SIFT特征點只需要35毫秒,這相比較於傳統的SIFT提取消耗的時間大大減小。多數情況下,我們都是調用OpenCV進行圖像的讀取以及后續的處理,因此使用SiftGPU可以加快提取特征點的速度。在無人機平台上,圖像處理速度一般要求在20HZ以上,因此SiftGPU獲取特征點的策略可以應用於無人機平台,與ORB等算子速度相當。
CUDA
我們切換至CUDA下進行特征點提取,關於調用CUDA來完成SiftGPU的測試,github上的原作者寫的比較含糊,網絡上也鮮有教程,因此特做記錄如下。
首先切換至SiftGPU的安裝路徑,找到makefile中的
ifneq ($(simple_find_cuda), )
siftgpu_enable_cuda = 0
else
siftgpu_enable_cuda = 0
endif
CUDA_INSTALL_PATH = /usr/local/cuda
#change additional settings, like SM version here if it is not 1.0 (eg. -arch sm_13 for GTX280)
#siftgpu_cuda_options = -Xopencc -OPT:unroll_size=200000
#siftgpu_cuda_options = -arch sm_10
改為
ifneq ($(simple_find_cuda), )
siftgpu_enable_cuda = 1
else
siftgpu_enable_cuda = 0
endif
CUDA_INSTALL_PATH = /usr/local/cuda
#change additional settings, like SM version here if it is not 1.0 (eg. -arch sm_13 for GTX280)
#siftgpu_cuda_options = -Xopencc -OPT:unroll_size=200000
siftgpu_cuda_options = -arch sm_50
其中最后一行的sm_50取決於讀者電腦的GPU算力,筆者筆記本使用的GPU是Pascal架構的GTX1050,算力為5.2,因此采用sm_50這個參數,關於不同GPU的算力可以參考這篇博客。之后重新編譯安裝SiftGPU。
$ make clean
$ make
我們切回到SiftGPU的測試程序,找到主程序main.cpp,取消注釋下面這句
//char* myargv[4] = {"-fo", "-1", "-cuda", "0"};
然后cmake編譯,就可以測試了,測試結果如下
調用CUDA檢測處同樣的一張圖片中的所有特征點需要消耗31ms,與不調用CUDA的情況時間相差無幾,依據SiftGPU的手冊,分辨率低於1080p的時候,OpenGL速度較快,因此這種結果也可以接受。
總結
SIFT在特征點檢測領域是一個非常優秀的算子,用於匹配准確率高,缺點是速度慢,而Wu Changchang提出的SiftGPU算法加快了特征點的提取,在CUDA的加成下相較於只調用OpenGL的SiftGPU速度提升有限,因此對於沒有裝CUDA的同學們來說這算是一個利好。7700HQ的CPU+GTX1050的顯卡可以將一幅640*480的圖像只用31毫秒便找出了所有的特征點,因此在無人機的輕量級運算平台上的應用很可期。
本文主要是介紹了SiftGPU的編譯和使用過程,改進了源碼的幾處錯誤以便於正確編譯,嘗試了調用CUDA的方法,給出了解決方案,對SiftGPU的使用提供了較為系統的方法,同時給出了一個測試程序。歡迎讀者提出指正與問題,便於討論與共同進步。