Sift特征應該是使用最多的局部特征了,但是相比其他的一些特征描述符,計算sift特征描述符的時間較長。Changchang Wu使用GPU加速,實現了GPU版的sift特征提取SiftGPU。 SiftGPU應該是在Windows環境下完成的,其在Windows下的配置較為簡單。
本文首先解釋了,在Ubuntu下SiftGPU的編譯,並簡單的實現了一個類,封裝SiftGPU的特征提取和匹配。在最后簡單的介紹了下,SiftGPU在Windows下的使用。
Ubuntu下的安裝與使用
- 安裝依賴庫
sudo apt-get install libgl1-mesa-dev libglu1-mesa-dev freeglut3-dev
- 編譯
glew下載地址 glew
make
sudo make install
安裝位置為/usr/lib64
編譯SiftGPU
從Git上下載SiftGPU的源代碼,下載的原始代碼在編譯的時候需要修改兩個部分,可以從原作者處clone,也可以clone我修改后的代碼
具體編譯的過程如下:
-
在執行
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); }
修改為
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如下:
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").
配置好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和頭文件復制到項目的目錄下。
