[环境配置]Ubuntu 16.04 源码编译安装OpenCV-3.2.0+OpenCV_contrib-3.2.0及产生的问题
当有读者看到我这篇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);
测试与结果
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) # 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我直接用高博的代码,后续的学习之后可能会写一个新的测试代码,更便于学习。
// siftgpu模块 #include <siftgpu.h> //标准c++ #include <iostream> #include <vector> // opencv图像 #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> // boost库中计时函数 #include <boost/timer.hpp> // opengl #include <gl/gl.h> using namespace std; int main( int argc, char** argv) { //声明siftgpu并初始化 siftgpu sift; char* myargv[4] ={ "-fo", "-1", "-v", "1"}; sift.parseparam(4, myargv); //检查硬件是否支持siftgpu int support = sift.createcontextgl(); if ( support != siftgpu::siftgpu_full_supported ) { cerr<<"siftgpu is not supported!"<<endl; return 2; } //测试直接读取一张图像 cout<<"running sift"<<endl; boost::timer timer; //在此填入你想测试的图像的路径!不要用我的路径!不要用我的路径!不要用我的路径! sift.runsift( "/home/yao/workspace/sift_detection/image/1.png" ); cout<<"siftgpu::runsift() cost time="<<timer.elapsed()<<endl; // 获取关键点与描述子 int num = sift.getfeaturenum(); cout<<"feature number="<<num<<endl; vector<float> descriptors(128*num); vector<siftgpu::siftkeypoint> keys(num); timer.restart(); sift.getfeaturevector(&keys[0], &descriptors[0]); cout<<"siftgpu::getfeaturevector() cost time="<<timer.elapsed()<<endl; // 先用opencv读取一个图像,然后调用siftgpu提取特征 cv::mat img = cv::imread("/home/yao/workspace/sift_detection/image/1.png", 0); int width = img.cols; int height = img.rows; timer.restart(); // 注意我们处理的是灰度图,故照如下设置 sift.runsift(width, height, img.data, gl_intensity8, gl_unsigned_byte); cout<<"siftgpu::runsift() cost time="<<timer.elapsed()<<endl; return 0; }
然后就是轻车熟路的cmake编译过程了。
$ mkdir build $ cd build $ cmake .. $ make $ ./test_siftgpu
结果如下图所示
测试代码只调用了opengl,我笔记本的配置是i7-7700hq,显卡gtx1050,可以看到在opencv已经读取了图片的情况下,提取出一张图像中所有的sift特征点只需要31毫秒,直接读取一张图进行sift特征提取的时候,需要100多毫秒,这与传统的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", "-v", "1"}; sift.parseparam(4, myargv);
改为
char* myargv[5] ={ "-fo", "-1", "-v", "1", "-cuda"}; sift.parseparam(5, myargv);
然后cmake编译,就可以测试了,测试结果如下
直接读取图片进行siftgpu的测试运行时间约为88ms,相较于使用opengl的测试速度提升了少许,但还没有质的飞跃,当然也可能跟我的笔记本gpu算力不够强大有关。opencv读取后的数据格式不符,目前还没有找到对应的函数,后续会补充更新的测试程序。
总结
sift在特征点检测领域是一个非常优秀的算子,用于匹配准确率高,缺点是速度慢,而wu changchang提出的siftgpu算法加快了特征点的提取,在cuda的加成下相较于只调用opengl的siftgpu速度提升有限,因此对于没有装cuda的同学们来说这算是一个利好。7700hq的cpu+gtx1050的显卡可以将一幅640*480的图像只用31毫秒便找出了所有的特征点,因此在无人机的轻量级运算平台上的应用很可期。
本文主要是介绍了siftgpu的编译和使用过程,改进了源码的几处错误以便于正确编译,尝试了调用cuda的方法,给出了解决方案,对siftgpu的使用提供了较为系统的方法。欢迎读者提出指正与问题,便于讨论与共同进步。