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
和头文件复制到项目的目录下。