SURF局部特征(OpenCV+GPU)

简单的图像匹配需求使用局部特征是很方便的。最近,我重新编译了OpenCV 3.1.0,并利用GPU加速提取图片的SURF局部特征。简单记录一下这个过程。

OpenCV编译

工作环境:
- Linux Red Hat
- gcc 4.8.4
- Tesla K40m
- cuda 7.0

  • 从下载页面获取opencv 3.1.0源代码
  • 为了使用SURF特征库,我们需要从github上获取opencv_contrib的代码
  • 编译opencv 3.1.0,如下:
$ cd <opencv_build_directory>
$ cmake -DOPENCV_EXTRA_MODULES_PATH=<opencv_contrib>/modules -DCMAKE_INSTALL_PREFIX=<install_directory> <opencv_source_directory>
$ make -j5; make install
  • 以上编译工程并非绝对,可以参考相关文档做相应的参数选择

cmake可能因为无法下载ippicy库而失败,仔细查看失败信息,手动下载相应版本的ippicy库到3rdparty/ippicv/downloads/linux-文件夹下,例如ippicv_linux_20151201.tgz

我在工作中还遇到另外一个问题,就是ippicy库没有被自动添加到install目录中。可以手动解压ippicy_linux_.tgz获得ippicv_lnx/lib/intel64/libippicv.a后添加到系统的lib目录下(32位机器对应ippicv_lnx/lib/ia32/libippicv.a

使用GPU提取SURF局部特征

GPU做SURF特征提取的接口和CPU版本有所不同,不过这一部分可以完全参考/samples/gpu/surf_keypoint_matcher.cpp的例子代码。
我这里给出一个更加简化的例子,并添加一些中文注释和说明。

/*surf.cpp*/                                                                                                                                             

#include 
#include 
#include 

using namespace std;

int GetMatchPointCount(const char * pic_path_1,const char * pic_path_2) {
  /*指定使用的GPU序号,相关的还有下面几个函数可以使用
    cv::cuda::getCudaEnabledDeviceCount();
    cv::cuda::getDevice();
    cv::cuda::DeviceInfo*/
  cv::cuda::setDevice(0);

  /*向显存加载两张图片。这里需要注意两个问题:
    第一,我们不能像操作(主)内存一样直接一个字节一个字节的操作显存,也不能直接从外存把图片加载到显存,一般需要通过内存作为媒介
    第二,目前opencv的GPU SURF仅支持8位单通道图像,所以加上参数IMREAD_GRAYSCALE*/
  cv::cuda::GpuMat gmat1;
  cv::cuda::GpuMat gmat2;
  gmat1.upload(cv::imread(pic_path_1,cv::IMREAD_GRAYSCALE));
  gmat2.upload(cv::imread(pic_path_2,cv::IMREAD_GRAYSCALE));

  /*下面这个函数的原型是:
  explicit SURF_CUDA(double 
      _hessianThreshold, //SURF海森特征点阈值
      int _nOctaves=4, //尺度金字塔个数
      int _nOctaveLayers=2, //每一个尺度金字塔层数
      bool _extended=false, //如果true那么得到的描述子是128维,否则是64维
      float _keypointsRatio=0.01f, 
      bool _upright = false 
      );
  要理解这几个参数涉及SURF的原理*/
  cv::cuda::SURF_CUDA surf(
      100,4,3
      );  

  /*分配下面几个GpuMat存储keypoint和相应的descriptor*/
  cv::cuda::GpuMat keypt1,keypt2;
  cv::cuda::GpuMat desc1,desc2;

  /*检测特征点*/
  surf(gmat1,cv::cuda::GpuMat(),keypt1,desc1);
  surf(gmat2,cv::cuda::GpuMat(),keypt2,desc2);

  /*匹配,下面的匹配部分和CPU的match没有太多区别,这里新建一个Brute-Force Matcher,一对descriptor的L2距离小于0.1则认为匹配*/
  auto matcher=cv::cuda::DescriptorMatcher::createBFMatcher(cv::NORM_L2);
  vector match_vec;
  matcher->match(desc1,desc2,match_vec);

  int count=0;
  for(auto & d:match_vec){
    if(d.distance<0.1) count++;
  }
  return count;
}

int main(int argc, const char* argv[])
{
  GetMatchPointCount(argv[1],argv[2]);
  return 0;
}  

最后,编译这个例子,可以参考下面通用的编译命令:

g++ -std=c++11 surf.cpp `pkg-config --cflags opencv` `pkg-config --libs opencv`

更简化的编译命令:

g++ -std=c++11 surf.cpp -lopencv_xfeatures2d -lopencv_cudafeatures2d

你可能感兴趣的:(图像处理)