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/lib64
git
編譯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})
就是設置linclud
和lib
的位置,手動指定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.sln
和SiftGPU_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
和頭文件複製到項目的目錄下。