上一节我们已经介绍了SIFT算法,SIFT算法对旋转、尺度缩放、亮度变化等保持不变性,对视角变换、仿射变化、噪声也保持一定程度的稳定性,是一种非常优秀的局部特征描述算法。但是其实时性相对不高。

SURF(Speeded Up Robust Features)算法改进了特征了提取和描述方式,用一种更为高效的方式完成特征点的提取和描述。

一 使用快速Hessian算法和SURF来提取和检测特征

我们先用OpenCV库函数演示一下快速Hessian算法和SURF来提取的效果,然后再来讲述一下SURF算法的原理。

SURF特征检测算法由Herbert Lowe于2006年发表,该算法比SIFT算法快好几倍,它吸收了SIFT算法的思想。

SURF算法采用快速Hessian算法检测关键点,而SURF算子会通过一个特征向量来描述关键点周围区域的情况。这和SIFT算法很像,SIFT算法分别采用DoG和SIFT算子来检测关键点和提取关键点的描述符。下面我们来演示一个例子:

  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on Fri Aug 24 20:09:32 2018
  4.  
  5. @author: lenovo
  6. """
  7.  
  8. # -*- coding: utf-8 -*-
  9. """
  10. Created on Wed Aug 22 16:53:16 2018
  11.  
  12. @author: lenovo
  13. """
  14.  
  15. '''
  16. SURF算法
  17. '''
  18. import cv2
  19. import numpy as np
  20.  
  21. img = cv2.imread('./image/cali.bmp')
  22. img = cv2.resize(img,dsize=(600,400))
  23. #转换为灰度图像
  24. gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
  25. #创建一个SURF对象
  26. surf = cv2.xfeatures2d.SURF_create(20000)
  27. #SIFT对象会使用Hessian算法检测关键点,并且对每个关键点周围的区域计算特征向量。该函数返回关键点的信息和描述符
  28. keypoints,descriptor = surf.detectAndCompute(gray,None)
  29. print(type(keypoints),len(keypoints),keypoints[0])
  30. print(descriptor.shape)
  31. #在图像上绘制关键点
  32. img = cv2.drawKeypoints(image=img,keypoints = keypoints,outImage=img,color=(255,0,255),flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
  33. #显示图像
  34. cv2.imshow('surf_keypoints',img)
  35. cv2.waitKey(0)
  36. cv2.destroyAllWindows()

我们把Hessian阈值设置为20000,阈值越高,能识别的特征就越少,因此可以采用试探法来得到最优检测。

二 SURF算法原理

1、SURF特征检测的步骤

  1. 尺度空间的极值检测:搜索所有尺度空间上的图像,通过Hessian来识别潜在的对尺度和选择不变的兴趣点。
  2. 特征点过滤并进行精确定位。
  3. 特征方向赋值:统计特征点圆形邻域内的Harr小波特征。即在60度扇形内,每次将60度扇形区域旋转0.2弧度进行统计,将值最大的那个扇形的方向作为该特征点的主方向。
  4. 特征点描述:沿着特征点主方向周围的邻域内,取$4×4$个矩形小区域,统计每个小区域的Haar特征,然后每个区域得到一个4维的特征向量。一个特征点共有64维的特征向量作为SURF特征的描述子。

2、构建Hessian(黑塞矩阵)

构建Hessian矩阵的目的是为了生成图像稳定的边缘点(突变点),跟Canny、拉普拉斯边缘检测的作用类似,为特征提取做准备。构建Hessian矩阵的过程对应着SIFT算法中的DoG过程。

黑塞矩阵(Hessian Matrix)是由一个多元函数的二阶偏导数构成的方阵,描述了函数的局部曲率。由德国数学家Ludwin Otto Hessian于19世纪提出。

对于一个图像$I(x,y)$,其Hessian矩阵如下:

$$H(I(x,y))=\begin{bmatrix} \frac{\partial^2I}{\partial{x^2}} & \frac{\partial^2I}{\partial{x}\partial{y}}  \\ \frac{\partial^2I}{\partial{x}\partial{y}} & \frac{\partial^2I}{\partial{y^2}} \end{bmatrix}$$

H矩阵的判别式是:

$$Det(H)=\frac{\partial^2I}{\partial{x^2}}*\frac{\partial^2I}{\partial{y^2}}-\frac{\partial^2I}{\partial{x}\partial{y}} * \frac{\partial^2I}{\partial{x}\partial{y}}$$

在构建Hessian矩阵前需要对图像进行高斯滤波,经过滤波后的Hessian矩阵表达式为:

$$H(x,y,\sigma)=\begin{bmatrix} L_{xx}(x,y,\sigma) & L_{xy}(x,y,\sigma) \\ L_{xy}(x,y,\sigma) & L_{yy}(x,y,\sigma) \end{bmatrix}$$

其中$(x,y)$为像素位置,$L(x,y,\sigma)=G(\sigma)*I(x,y)$,代表着图像的高斯尺度空间,是由图像和不同的高斯卷积得到。

我们知道在离散数学图像中,一阶导数是相邻像素的灰度差:

$$L_x=L(x+1,y)-L(x,y)$$

二阶导数是对一阶导数的再次求导:

$$L_{xx}=[L(x+1,y)-L(x,y)]-[L(x,y)-L(x-1,y)]$$

$$=L(x+1,y)+L(x-1,y)-2L(x,y)$$

反过来看Hessian矩阵的判别式,其实就是当前点对水平方向二阶偏导数乘以垂直方向二阶偏导数再减去当前水平、垂直二阶偏导的二次方:

$$Det(H)=L_{xx}*L_{yy}-L_{xy}*L_{xy}$$

通过这种方法可以为图像中每个像素计算出其H行列式的决定值,并用这个值来判别图像局部特征点。Hession矩阵判别式中的$L(x,y)$是原始图像的高斯卷积,由于高斯核服从正太分布,从中心点往外,系数越来越小,为了提高运算速度,SURF算法使用了盒式滤波器来替代高斯滤波器$L$,所以在$L_{xy}$上乘了一个加权系数0.9,目的是为了平衡因使用盒式滤波器近似所带来的误差,则H矩阵判别式可表示为:

$$Det(H)=L_{xx}*L_{yy}-(0.9*L_{xy})^2$$

盒式滤波器和高斯滤波器的示意图如下:

上面两幅图是$9×9$高斯滤波器模板分别在图像垂直方向上二阶导数$L_{yy}$和$L_{xy}$对应的值,下边两幅图是使用盒式滤波器对其近似,灰色部分的像素值为0,黑色为-2,白色为1.

那么为什么盒式滤波器可以提高运算速度呢?这就涉及到积分图的使用,盒式滤波器对图像的滤波转化成计算图像上不同区域间像素的加减运算问题,这正是积分图的强项,只需要简单积分查找积分图就可以完成。

3、构造尺度空间

同SIFT算法一样,SURF算法的尺度空间由$O$组$S$层组成,不同的是,SIFT算法下一组图像的长宽均是上一组的一半,同一组不同层图像之间尺寸一样,但是所使用的尺度空间因子(高斯模糊系数$\sigma$)逐渐增大;而在SURF算法中,不同组间图像的尺寸都是一致的,不同的是不同组间使用的盒式滤波器的模板尺寸逐渐增大,同一组不同层图像使用相同尺寸的滤波器,但是滤波器的尺度空间因子逐渐增大。如下图所示:

4、特征点过滤并进行精确定位

SURF特征点的定位过程和SIFT算法一致,将经过Hessian矩阵处理的每个像素点(即获得每个像素点Hessian矩阵的判别式值)与其图像域(相同大小的图像)和尺度域(相邻的尺度空间)的所有相邻点进行比较,当其大于(或者小于)所有相邻点时,该点就是极值点。如图所示,中间的检测点要和其所在图像的$3×3$邻域8个像素点,以及其相邻的上下两层$3×3$邻域18个像素点,共26个像素点进行比较。

初步定位出特征点后,再经过滤除能量比较弱的关键点以及错误定位的关键点,筛选出最终的稳定的特征点。

5、计算特征点主方向

SIFT算法特征点的主方向是采用在特征点邻域内统计其梯度直方图,横轴是梯度方向的角度,纵轴是梯度方向对应梯度幅值的累加,取直方图bin最大的以及超过最大80%的那些方向作为特征点的主方向。

而在SURF算法中,采用的是统计特征点圆形邻域内的Harr小波特征,即在特征点的圆形邻域内,统计60度扇形内所有点的水平、垂直Harr小波特征总和,然后扇形以0.2弧度大小的间隔进行旋转并再次统计该区域内Harr小波特征值之后,最后将值最大的那个扇形的方向作为该特征点的主方向。该过程示意图如下:

Harr特征的具体内容可以参考第九节、人脸检测之Haar分类器

6、生成特征描述

在SIFT算法中,为了保证特征矢量的旋转不变性,先以特征点为中心,在附近邻域内将坐标轴旋转$\theta$(特征点的主方向)角度,然后提取特征点周围$4×4$个区域块,统计每小块内8个梯度方向,这样一个关键点就可以产生128维的SIFT特征向量。

SURF算法中,也是提取特征点周围$4×4$个矩形区域块,但是所取得矩形区域方向是沿着特征点的主方向,而不是像SIFT算法一样,经过旋转$\theta$角度。每个子区域统计25个像素点水平方向和垂直方向的Haar小波特征,这里的水平和垂直方向都是相对主方向而言的。该Harr小波特征为水平方向值之和、垂直方向值之和、水平方向值绝对值之和以及垂直方向绝对之和4个方向。该过程示意图如下:

把这4个值作为每个子块区域的特征向量,所以一共有$4×4×4$=64维向量作为SURF特征的描述子,比SIFT特征的描述子减少了一半。

三 特征点匹配

与SIFT特征点匹配类似,SURF也是通过计算两个特征点间特征向量的欧氏距离来确定匹配度,欧式距离越短,代表两个特征点的匹配度越好。不同的是SURF还加入了Hessian矩阵迹(矩阵特征值的和)的判断,如果两个特征点的矩阵迹正负号相同,代表着两个特征点具有相同方向上的对比度变化,如果不同,说明这两个特征点的对比度方向是相反的,即使欧氏距离为0,也直接剔除。

  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on Fri Aug 24 20:09:32 2018
  4.  
  5. @author: lenovo
  6. """
  7.  
  8. # -*- coding: utf-8 -*-
  9. """
  10. Created on Wed Aug 22 16:53:16 2018
  11.  
  12. @author: lenovo
  13. """
  14.  
  15. '''
  16. SURF算法
  17. '''
  18. import cv2
  19. import numpy as np
  20.  
  21. '''1、加载图片'''
  22. img1 = cv2.imread('./image/cali1.bmp',cv2.IMREAD_GRAYSCALE)
  23. img1 = cv2.resize(img1,dsize=(600,400))
  24. img2 = cv2.imread('./image/cali2.bmp',cv2.IMREAD_GRAYSCALE)
  25. img2 = cv2.resize(img2,dsize=(600,400))
  26. image1 = img1.copy()
  27. image2 = img2.copy()
  28.  
  29. '''2、提取特征点'''
  30. #创建一个SURF对象
  31. surf = cv2.xfeatures2d.SURF_create(25000)
  32. #SIFT对象会使用Hessian算法检测关键点,并且对每个关键点周围的区域计算特征向量。该函数返回关键点的信息和描述符
  33. keypoints1,descriptor1 = surf.detectAndCompute(image1,None)
  34. keypoints2,descriptor2 = surf.detectAndCompute(image2,None)
  35. print('descriptor1:',descriptor1.shape,'descriptor2',descriptor2.shape)
  36. #在图像上绘制关键点
  37. image1 = cv2.drawKeypoints(image=image1,keypoints = keypoints1,outImage=image1,color=(255,0,255),flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
  38. image2 = cv2.drawKeypoints(image=image2,keypoints = keypoints2,outImage=image2,color=(255,0,255),flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
  39. #显示图像
  40. cv2.imshow('surf_keypoints1',image1)
  41. cv2.imshow('surf_keypoints2',image2)
  42. cv2.waitKey(20)
  43.  
  44. '''3、特征点匹配'''
  45. matcher = cv2.FlannBasedMatcher()
  46. matchePoints = matcher.match(descriptor1,descriptor2)
  47. print(type(matchePoints),len(matchePoints),matchePoints[0])
  48.  
  49. #提取强匹配特征点
  50. minMatch = 1
  51. maxMatch = 0
  52. for i in range(len(matchePoints)):
  53. if minMatch > matchePoints[i].distance:
  54. minMatch = matchePoints[i].distance
  55. if maxMatch < matchePoints[i].distance:
  56. maxMatch = matchePoints[i].distance
  57. print('最佳匹配值是:',minMatch)
  58. print('最差匹配值是:',maxMatch)
  59.  
  60. #获取排雷在前边的几个最优匹配结果
  61. goodMatchePoints = []
  62. for i in range(len(matchePoints)):
  63. if matchePoints[i].distance < minMatch + (maxMatch-minMatch)/16:
  64. goodMatchePoints.append(matchePoints[i])
  65.  
  66. #绘制最优匹配点
  67. outImg = None
  68. outImg = cv2.drawMatches(img1,keypoints1,img2,keypoints2,goodMatchePoints,outImg,matchColor=(0,255,0),flags=cv2.DRAW_MATCHES_FLAGS_DEFAULT)
  69. cv2.imshow('matche',outImg)
  70. cv2.waitKey(0)
  71. cv2.destroyAllWindows()

我们来看一看特征点匹配效果,可以看到好多点都匹配错误,这主要与我选择的图片有关,由于我选择的图片是用来做相机标点的,而当我们使用SURF算法提取特征点,图片上大部分特征点都具有相同的性质,特征向量也近似相等,因此在匹配时会出现很大的误差。

下面我们更换两张图片,再次进行特征点匹配:

我们可以看到这个匹配效果比刚才好了不少,而且我对Hessian阈值也进行了修改,这个值需要自己不断的调整,以达到自己的期望。但是总体上来看,我们选择的这两幅图片亮度和对比度差异都是很大的,而且拍摄所使用的相机也是不同的,左侧是我自己用手机拍摄到的,右侧是从网上下载的,匹配能有这样的效果也已经不错了。但是如果我们想达到更高的匹配度,我们应该尽量选择两张更为相似的图片。下面是我稍微修改后的代码:

  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on Fri Aug 24 20:09:32 2018
  4.  
  5. @author: lenovo
  6. """
  7.  
  8. # -*- coding: utf-8 -*-
  9. """
  10. Created on Wed Aug 22 16:53:16 2018
  11.  
  12. @author: lenovo
  13. """
  14.  
  15. '''
  16. SURF算法
  17. '''
  18. import cv2
  19.  
  20. '''1、加载图片'''
  21. img1 = cv2.imread('./image/match1.jpg')
  22. img1 = cv2.resize(img1,dsize=(600,400))
  23. gray1 = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
  24. img2 = cv2.imread('./image/match2.jpg')
  25. img2 = cv2.resize(img2,dsize=(600,400))
  26. gray2 = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
  27. image1 = gray1.copy()
  28. image2 = gray2.copy()
  29.  
  30. '''2、提取特征点'''
  31. #创建一个SURF对象
  32. surf = cv2.xfeatures2d.SURF_create(10000)
  33. #SIFT对象会使用Hessian算法检测关键点,并且对每个关键点周围的区域计算特征向量。该函数返回关键点的信息和描述符
  34. keypoints1,descriptor1 = surf.detectAndCompute(image1,None)
  35. keypoints2,descriptor2 = surf.detectAndCompute(image2,None)
  36. print('descriptor1:',descriptor1.shape,'descriptor2',descriptor2.shape)
  37. #在图像上绘制关键点
  38. image1 = cv2.drawKeypoints(image=image1,keypoints = keypoints1,outImage=image1,color=(255,0,255),flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
  39. image2 = cv2.drawKeypoints(image=image2,keypoints = keypoints2,outImage=image2,color=(255,0,255),flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
  40. #显示图像
  41. cv2.imshow('surf_keypoints1',image1)
  42. cv2.imshow('surf_keypoints2',image2)
  43. cv2.waitKey(20)
  44.  
  45. '''3、特征点匹配'''
  46. matcher = cv2.FlannBasedMatcher()
  47. matchePoints = matcher.match(descriptor1,descriptor2)
  48. print(type(matchePoints),len(matchePoints),matchePoints[0])
  49.  
  50. #提取强匹配特征点
  51. minMatch = 1
  52. maxMatch = 0
  53. for i in range(len(matchePoints)):
  54. if minMatch > matchePoints[i].distance:
  55. minMatch = matchePoints[i].distance
  56. if maxMatch < matchePoints[i].distance:
  57. maxMatch = matchePoints[i].distance
  58. print('最佳匹配值是:',minMatch)
  59. print('最差匹配值是:',maxMatch)
  60.  
  61. #获取排雷在前边的几个最优匹配结果
  62. goodMatchePoints = []
  63. for i in range(len(matchePoints)):
  64. if matchePoints[i].distance < minMatch + (maxMatch-minMatch)/4:
  65. goodMatchePoints.append(matchePoints[i])
  66.  
  67. #绘制最优匹配点
  68. outImg = None
  69. outImg = cv2.drawMatches(img1,keypoints1,img2,keypoints2,goodMatchePoints,outImg,matchColor=(0,255,0),flags=cv2.DRAW_MATCHES_FLAGS_DEFAULT)
  70. cv2.imshow('matche',outImg)
  71. cv2.waitKey(0)
  72. cv2.destroyAllWindows()

参考文章:

[1]图像识别基本算法之SURF

[2]SURF算法

[3]SURF算法与源码分析、上

[4]SURF算法与源码分析、下

[5]SURF原理及源码解析(C++)

第十三节、SURF特征提取算法的更多相关文章

  1. Surf特征提取分析

    Surf特征提取分析 Surf Hessian SIFT 读"H.Bay, T. Tuytelaars, L. V. Gool, SURF:Speed Up Robust Features[ ...

  2. OpenCV Using Python——基于SURF特征提取和金字塔LK光流法的单目视觉三维重建 (光流、场景流)

    https://blog.csdn.net/shadow_guo/article/details/44312691 基于SURF特征提取和金字塔LK光流法的单目视觉三维重建 1. 单目视觉三维重建问题 ...

  3. centos LB负载均衡集群 三种模式区别 LVS/NAT 配置 LVS/DR 配置 LVS/DR + keepalived配置 nginx ip_hash 实现长连接 LVS是四层LB 注意down掉网卡的方法 nginx效率没有LVS高 ipvsadm命令集 测试LVS方法 第三十三节课

    centos   LB负载均衡集群 三种模式区别 LVS/NAT 配置  LVS/DR 配置  LVS/DR + keepalived配置  nginx ip_hash 实现长连接  LVS是四层LB ...

  4. (转)第二十三节 inotify事件监控工具

    第二十三节 inotify事件监控工具 标签(空格分隔): Linux实战教学笔记-陈思齐 原文:http://www.cnblogs.com/chensiqiqi/p/6542268.html 第1 ...

  5. 【特征检测】BRISK特征提取算法

    [特征检测]BRISK特征提取算法原创hujingshuang 发布于2015-07-24 22:59:21 阅读数 17840 收藏展开简介        BRISK算法是2011年ICCV上< ...

  6. 【十大经典数据挖掘算法】PageRank

    [十大经典数据挖掘算法]系列 C4.5 K-Means SVM Apriori EM PageRank AdaBoost kNN Naïve Bayes CART 我特地把PageRank作为[十大经 ...

  7. 【十大经典数据挖掘算法】EM

    [十大经典数据挖掘算法]系列 C4.5 K-Means SVM Apriori EM PageRank AdaBoost kNN Naïve Bayes CART 1. 极大似然 极大似然(Maxim ...

  8. 【十大经典数据挖掘算法】AdaBoost

    [十大经典数据挖掘算法]系列 C4.5 K-Means SVM Apriori EM PageRank AdaBoost kNN Naïve Bayes CART 1. 集成学习 集成学习(ensem ...

  9. 【十大经典数据挖掘算法】SVM

    [十大经典数据挖掘算法]系列 C4.5 K-Means SVM Apriori EM PageRank AdaBoost kNN Naïve Bayes CART SVM(Support Vector ...

随机推荐

  1. Prism框架研究(三)

    这一篇主要用来介绍一下基于Prism Library中的核心服务以及如何配置Container,还有一个重要的部分是如何管理各个组件之间的依赖性,下面就这些内容来做一一的介绍. 1 Prism中的核心 ...

  2. shit vue & shit iview

    shit vue & shit iview <Switch> !== <i-switch> https://www.iviewui.com/components/swi ...

  3. python设计模式第二十天【迭代器模式】

    1.不使用迭代器出现的问题 (1)容器承担了太多的功能,一方面提供添加和删除等功能,还需提供遍历访问功能 (2)在容器访问遍历过程中,需要保存遍历状态,当和元素的添加和删除混杂在一起时,容易引起混乱 ...

  4. Supervisord管理进程实践

    今天凑空研究了下Supervisord,这是一款linux进程管理工具,使用python开发,主要用于在后台维护进程(类似master守护进程),可以实现监控进程的状态.自动重启进程等操作,便于一些服 ...

  5. Nintex Workflow Get Attachment link

    不多解释,直接上图,操作简单

  6. jdk6使用WebSocket

    pom.xml <dependency> <groupId>org.java-websocket</groupId> <artifactId>Java- ...

  7. NIKKEI Programming Contest 2019 翻车记

    A:签到. #include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> ...

  8. 微信小程序——创建自己的第一个小程序【一】

    注册 微信小程序注册 https://mp.weixin.qq.com/wxopen/waregister?action=step1   填写账号信息  作为登录帐号,请填写未被微信公众平台注册,未被 ...

  9. 用随机投掷飞镖法计算Pi值(Randomness Throwing dart Pi Python)

    画一个边长为r的正方形和半径为r的四分之一的圆(如下图所示),向上面随机投掷飞镖,通过计算落在星星区域和整体区域的飞镖比例,即可求出π值. 公式推导如下: 假设正方形的边长r为1,那么飞镖落在星星区域 ...

  10. 对strom的理解

    1.什么是strom: storm是一个分布式实时计算系统,用户只需要提供自己的插件(例如一个jar包,其中编写用户自己的逻辑代码),然后将它部署到storm服务器上,storm的master服务器就 ...