OpenCV中图像特征提取与描述

目录

  • 图像特征提取与描述
    • 图像的特征
    • Harris和Shi-Tomas算法
      • Harris角点检测
      • Shi-Tomasi角点检测
      • 小结
    • SIFT/SURF算法
      • SIFT原理
        • 基本流程
        • 尺度空间极值检测
        • 关键点定位
        • 关键点方向确定
        • 关键点描述
      • SURF原理
      • 小结
    • Fast和ORB算法
      • Fast算法
        • FAST算法的基本流程
        • 机器学习的角点检测器
        • 非极大值抑制
      • ORB 算法
        • ORB算法流程
        • BRIEF算法
      • 小结
    • LBP和HOG特征算子
      • LBP算法
        • LBP特征描述
        • 圆形LBP算子
        • 旋转不变LBP特征
        • Uniform Pattern LBP特征
        • 实现
      • HOG算法
        • 特征提取流程
        • 颜色空间归一化
        • 图像梯度计算
        • 梯度直方图计算
        • 重叠块直方图归一化
        • 收集HOG特征
        • HOG特征的优缺点
        • 实现
        • 小结

图像特征提取与描述

主要内容是:

  • 图像的特征
  • Harris和Shi-Tomasi算法的原理及角点检测的实现
  • SIFT/SURF算法的原理及使用SIFT/SURF进行关键点的检测方法
  • Fast算法角点检测的原理角及其应用
  • ORB算法的原理,及特征点检测的实现

图像的特征

大多数人都玩过拼图游戏。首先拿到完整图像的碎片,然后把这些碎片以正确的方式排列起来从而重建这幅图像。如果把拼图游戏的原理写成计算机程序,那计算机就也会玩拼图游戏了。

在拼图时,我们要寻找一些唯一的特征,这些特征要适于被跟踪,容易被比较。我们在一副图像中搜索这样的特征,找到它们,而且也能在其他图像中找到这些特征,然后再把它们拼接到一起。我们的这些能力都是天生的。

那这些特征是什么呢?我们希望这些特征也能被计算机理解。

如果我们深入的观察一些图像并搜索不同的区域,以下图为例:

OpenCV中图像特征提取与描述_第1张图片

在图像的上方给出了六个小图。找到这些小图在原始图像中的位置。你能找到多少正确结果呢?

A 和 B 是平面,而且它们的图像中很多地方都存在。很难找到这些小图的准确位置。

C 和 D 也很简单。它们是建筑的边缘。可以找到它们的近似位置,但是准确位置还是很难找到。这是因为:沿着边缘,所有的地方都一样。所以边缘是比平面更好的特征,但是还不够好。

最后 E 和 F 是建筑的一些角点。它们能很容易的被找到。因为在角点的地方,无论你向哪个方向移动小图,结果都会有很大的不同。所以可以把它们当 成一个好的特征。为了更好的理解这个概念我们再举个更简单的例子。

OpenCV中图像特征提取与描述_第2张图片

如上图所示,蓝色框中的区域是一个平面很难被找到和跟踪。无论向哪个方向移动蓝色框,都是一样的。对于黑色框中的区域,它是一个边缘。如果沿垂直方向移动,它会改变。但是如果沿水平方向移动就不会改变。而红色框中的角点,无论你向那个方向移动,得到的结果都不同,这说明它是唯一的。 所以,我们说角点是一个好的图像特征,也就回答了前面的问题。

角点是图像很重要的特征,对图像图形的理解和分析有很重要的作用。角点在三维场景重建运动估计,目标跟踪、目标识别、图像配准与匹配等计算机视觉领域起着非常重要的作用。在现实世界中,角点对应于物体的拐角,道路的十字路口、丁字路口等

那我们怎样找到这些角点呢?接下来我们使用 OpenCV 中的各种算法来查找图像的特征,并对它们进行描述。

小结:

图像特征:图像特征要有区分性,容易被比较。一般认为角点,斑点等是较好的图像特征

特征检测:找到图像中的特征

特征描述:对特征及其周围的区域进行描述

Harris和Shi-Tomas算法

学习目标

  • 理解Harris和Shi-Tomasi算法的原理
  • 能够利用Harris和Shi-Tomasi进行角点检测

Harris角点检测

Harris角点检测的思想是通过图像的局部的小窗口观察图像,角点的特征是窗口沿任意方向移动都会导致图像灰度的明显变化,如下图所示:

OpenCV中图像特征提取与描述_第3张图片

将上述思想转换为数学形式,即将局部窗口向各个方向移动(u,v)并计算所有灰度差异的总和,表达式如下:

E ( u , v ) = ∑ x , y w ( x , y ) [ I ( x + u , y + v ) − I ( x , y ) ] 2 E(u, v)=\sum_{x, y} w(x, y)[I(x+u, y+v)-I(x, y)]^{2} E(u,v)=x,yw(x,y)[I(x+u,y+v)I(x,y)]2

其中I(x,y)是局部窗口的图像灰度,I(x+u,y+v)是平移后的图像灰度,w(x,y)是窗口函数,该可以是矩形窗口,也可以是对每一个像素赋予不同权重的高斯窗口,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M5t4GaIT-1646741526291)(笔记图片/image-20191008153014984.png)]OpenCV中图像特征提取与描述_第4张图片

角点检测中使E(u,v)的值最大。利用一阶泰勒展开有:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9BCm4U3s-1646741526292)(笔记图片/image-20220308154736341.png)]

其中Ix和 Iy是沿x和y方向的导数,可用sobel算子计算。

推导如下:

OpenCV中图像特征提取与描述_第5张图片

M矩阵决定了E(u,v)的取值,下面我们利用M来求角点,M是Ix和Iy的二次项函数,可以表示成椭圆的形状,椭圆的长短半轴由M的特征值λ1和λ2决定,方向由特征矢量决定,如下图所示:

OpenCV中图像特征提取与描述_第6张图片

椭圆函数特征值与图像中的角点、直线(边缘)和平面之间的关系如下图所示。

OpenCV中图像特征提取与描述_第7张图片

共可分为三种情况:

  • 图像中的直线。一个特征值大,另一个特征值小,λ1>>λ2或 λ2>>λ1。椭圆函数值在某一方向上大,在其他方向上小。
  • 图像中的平面。两个特征值都小,且近似相等;椭圆函数数值在各个方向上都小。
  • 图像中的角点。两个特征值都大,且近似相等,椭圆函数在所有方向都增大

Harris给出的角点计算方法并不需要计算具体的特征值,而是计算一个角点响应值R来判断角点。R的计算公式为:

R = det ⁡ M − α (  trace  M ) 2 R=\operatorname{det} M-\alpha(\text { trace } M)^{2} R=detMα( trace M)2

式中,detM为矩阵M的行列式;traceM为矩阵M的迹;α为常数,取值范围为0.04~0.06。事实上,特征是隐含在detM和traceM中,因为:

OpenCV中图像特征提取与描述_第8张图片

那我们怎么判断角点呢?如下图所示:

OpenCV中图像特征提取与描述_第9张图片

  • 当R为大数值的正数时是角点
  • 当R为大数值的负数时是边界
  • 当R为小数是认为是平坦区域

在OpenCV中实现Hariis检测使用的API是:

dst=cv.cornerHarris(src, blockSize, ksize, k)

参数:

  • img:数据类型为 float32 的输入图像。
  • blockSize:角点检测中要考虑的邻域大小。
  • ksize:sobel求导使用的核大小
  • k :角点检测方程中的自由参数,取值参数为 [0.04,0.06].
import cv2 as cv
import numpy as np 
import matplotlib.pyplot as plt
# 1 读取图像,并转换成灰度图像
img = cv.imread('./image/chessboard.jpg')
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 2 角点检测
# 2.1 输入图像必须是 float32
gray = np.float32(gray)

# 2.2 最后一个参数在 0.04 到 0.05 之间
dst = cv.cornerHarris(gray,2,3,0.04)
# 3 设置阈值,将角点绘制出来,阈值根据图像进行选择
img[dst>0.001*dst.max()] = [0,0,255]
# 4 图像显示
plt.figure(figsize=(10,8),dpi=100)
plt.imshow(img[:,:,::-1]),plt.title('Harris角点检测')
plt.xticks([]), plt.yticks([])
plt.show()

OpenCV中图像特征提取与描述_第10张图片

Harris角点检测的优缺点:

优点:

  • 旋转不变性,椭圆转过一定角度但是其形状保持不变(特征值保持不变)
  • 对于图像灰度的仿射变化具有部分的不变性,由于仅仅使用了图像的一介导数,对于图像灰度平移变化不变;对于图像灰度尺度变化不变

缺点:

  • 对尺度很敏感,不具备几何尺度不变性。
  • 提取的角点是像素级的

Shi-Tomasi角点检测

Shi-Tomasi算法是对Harris角点检测算法的改进,一般会比Harris算法得到更好的角点。Harris 算法的角点响应函数是将矩阵 M 的行列式值与 M 的迹相减,利用差值判断是否为角点。后来Shi 和Tomasi 提出改进的方法是,若矩阵M的两个特征值中较小的一个大于阈值,则认为他是角点,即:

R = min ⁡ ( λ 1 , λ 2 ) R=\min \left(\lambda_{1}, \lambda_{2}\right) R=min(λ1,λ2)

OpenCV中图像特征提取与描述_第11张图片

从这幅图中,可以看出来只有当 λ1 和 λ 2 都大于最小值时,才被认为是角点。

在OpenCV中实现Shi-Tomasi角点检测使用API:

corners = cv2.goodFeaturesToTrack ( image, maxcorners, qualityLevel, minDistance )

参数:

  • Image: 输入灰度图像
  • maxCorners : 获取角点数的数目。
  • qualityLevel:该参数指出最低可接受的角点质量水平,在0-1之间。
  • minDistance:角点之间最小的欧式距离,避免得到相邻特征点。

返回:

  • Corners: 搜索到的角点,在这里所有低于质量水平的角点被排除掉,然后把合格的角点按质量排序,然后将质量较好的角点附近(小于最小欧式距离)的角点删掉,最后找到maxCorners个角点返回。
import numpy as np 
import cv2 as cv
import matplotlib.pyplot as plt
# 1 读取图像
img = cv.imread('./image/tv.jpg') 
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
# 2 角点检测
corners = cv.goodFeaturesToTrack(gray,1000,0.01,10)  
# 3 绘制角点
for i in corners:
    x,y = i.ravel()
    cv.circle(img,(x,y),2,(0,0,255),-1)
# 4 图像展示
plt.figure(figsize=(10,8),dpi=100)
plt.imshow(img[:,:,::-1]),plt.title('shi-tomasi角点检测')
plt.xticks([]), plt.yticks([])
plt.show()

小结

  1. Harris算法

    思想:通过图像的局部的小窗口观察图像,角点的特征是窗口沿任意方向移动都会导致图像灰度的明显变化。

    API: cv.cornerHarris()

  2. Shi-Tomasi算法

    对Harris算法的改进,能够更好地检测角点

    API: cv2.goodFeatureToTrack()

SIFT/SURF算法

学习目标

  • 理解SIFT/SURF算法的原理,
  • 能够使用SIFT/SURF进行关键点的检测

SIFT原理

前面两节我们介绍了Harris和Shi-Tomasi角点检测算法,这两种算法具有旋转不变性,但不具有尺度不变性,以下图为例,在左侧小图中可以检测到角点,但是图像被放大后,在使用同样的窗口,就检测不到角点了。

OpenCV中图像特征提取与描述_第12张图片

所以,下面我们来介绍一种计算机视觉的算法,尺度不变特征转换即SIFT (Scale-invariant feature transform)。它用来侦测与描述影像中的局部性特征,它在空间尺度中寻找极值点,并提取出其位置、尺度、旋转不变量,此算法由 David Lowe在1999年所发表,2004年完善总结。应用范围包含物体辨识、机器人地图感知与导航、影像缝合、3D模型建立、手势辨识、影像追踪和动作比对等领域。

SIFT算法的实质是在不同的尺度空间上查找关键点(特征点),并计算出关键点的方向。SIFT所查找到的关键点是一些十分突出,不会因光照,仿射变换和噪音等因素而变化的点,如角点、边缘点、暗区的亮点及亮区的暗点等。

基本流程

Lowe将SIFT算法分解为如下四步

  1. 尺度空间极值检测:搜索所有尺度上的图像位置。通过高斯差分函数来识别潜在的对于尺度和旋转不变的关键点。
  2. 关键点定位:在每个候选的位置上,通过一个拟合精细的模型来确定位置和尺度。关键点的选择依据于它们的稳定程度。
  3. 关键点方向确定:基于图像局部的梯度方向,分配给每个关键点位置一个或多个方向。所有后面的对图像数据的操作都相对于关键点的方向、尺度和位置进行变换,从而保证了对于这些变换的不变性。
  4. 关键点描述:在每个关键点周围的邻域内,在选定的尺度上测量图像局部的梯度。这些梯度作为关键点的描述符,它允许比较大的局部形状的变形或光照变化。

我们就沿着Lowe的步骤,对SIFT算法的实现过程进行介绍:

尺度空间极值检测

在不同的尺度空间是不能使用相同的窗口检测极值点,对小的关键点使用小的窗口,对大的关键点使用大的窗口,为了达到上述目的,我们使用尺度空间滤波器。

高斯核是唯一可以产生多尺度空间的核函数。-《Scale-space theory: A basic tool for analysing structures at different scales》。

一个图像的尺度空间L(x,y,σ),定义为原始图像I(x,y)与一个可变尺度的2维高斯函数G(x,y,σ)卷积运算 ,即:

L ( x , y , σ ) = G ( x , y , σ ) ∗ I ( x , y ) L(x, y, \sigma)=G(x, y, \sigma) * I(x, y) L(x,y,σ)=G(x,y,σ)I(x,y)

其中:

G ( x , y , σ ) = 1 2 π σ 2 e − x 2 + y 2 2 σ 2 G(x, y, \sigma)=\frac{1}{2 \pi \sigma^{2}} e^{-\frac{x^{2}+y^{2}}{2 \sigma^{2}}} G(x,y,σ)=2πσ21e2σ2x2+y2

σ是尺度空间因子,它决定了图像的模糊的程度。在大尺度下(σ值大)表现的是图像的概貌信息,在小尺度下(σ值小)表现的是图像的细节信息。

在计算高斯函数的离散近似时,在大概3σ距离之外的像素都可以看作不起作用,这些像素的计算也就可以忽略。所以,在实际应用中,只计算**(6σ+1)*(6σ+1)**的高斯卷积核就可以保证相关像素影响。

下面我们构建图像的高斯金字塔,它采用高斯函数对图像进行模糊以及降采样处理得到的,高斯金字塔构建过程中,首先将图像扩大一倍,在扩大的图像的基础之上构建高斯金字塔,然后对该尺寸下图像进行高斯模糊,几幅模糊之后的图像集合构成了一个Octave,然后对该Octave下选择一幅图像进行下采样,长和宽分别缩短一倍,图像面积变为原来四分之一。这幅图像就是下一个Octave的初始图像,在初始图像的基础上完成属于这个Octave的高斯模糊处理,以此类推完成整个算法所需要的所有八度构建,这样这个高斯金字塔就构建出来了,整个流程如下图所示:

OpenCV中图像特征提取与描述_第13张图片

利用LoG(高斯拉普拉斯方法),即图像的二阶导数,可以在不同的尺度下检测图像的关键点信息,从而确定图像的特征点。但LoG的计算量大,效率低。所以我们通过两个相邻高斯尺度空间的图像的相减,得到DoG(高斯差分)来近似LoG。

为了计算DoG我们构建高斯差分金字塔,该金字塔是在上述的高斯金字塔的基础上构建而成的,建立过程是:在高斯金字塔中每个Octave中相邻两层相减就构成了高斯差分金字塔。如下图所示:

高斯差分金字塔的第1组第1层是由高斯金字塔的第1组第2层减第1组第1层得到的。以此类推,逐组逐层生成每一个差分图像,所有差分图像构成差分金字塔。概括为DOG金字塔的第o组第l层图像是有高斯金字塔的第o组第l+1层减第o组第l层得到的。后续Sift特征点的提取都是在DOG金字塔上进行的

在 DoG 搞定之后,就可以在不同的尺度空间中搜索局部最大值了。对于图像中的一个像素点而言,它需要与自己周围的 8 邻域,以及尺度空间中上下两层中的相邻的 18(2x9)个点相比。如果是局部最大值,它就可能是一个关键点。基本上来说关键点是图像在相应尺度空间中的最好代表。如下图所示:

OpenCV中图像特征提取与描述_第14张图片

搜索过程从每组的第二层开始,以第二层为当前层,对第二层的DoG图像中的每个点取一个3×3的立方体,立方体上下层为第一层与第三层。这样,搜索得到的极值点既有位置坐标(DoG的图像坐标),又有空间尺度坐标(层坐标)。当第二层搜索完成后,再以第三层作为当前层,其过程与第二层的搜索类似。当S=3时,每组里面要搜索3层,所以在DOG中就有S+2层,在初使构建的金字塔中每组有S+3层。

关键点定位

由于DoG对噪声和边缘比较敏感,因此在上面高斯差分金字塔中检测到的局部极值点需经过进一步的检验才能精确定位为特征点。

使用尺度空间的泰勒级数展开来获得极值的准确位置, 如果极值点的 灰度值小于阈值(一般为0.03或0.04)就会被忽略掉。 在 OpenCV 中这种阈值被称为 contrastThreshold。

DoG 算法对边界非常敏感, 所以我们必须要把边界去除。 Harris 算法除了可以用于角点检测之外还可以用于检测边界。从 Harris 角点检测的算法中,当一个特征值远远大于另外一个特征值时检测到的是边界。那在DoG算法中欠佳的关键点在平行边缘的方向有较大的主曲率,而在垂直于边缘的方向有较小的曲率,两者的比值如果高于某个阈值(在OpenCV中叫做边界阈值),就认为该关键点为边界,将被忽略,一般将该阈值设置为10。

将低对比度和边界的关键点去除,得到的就是我们感兴趣的关键点。

关键点方向确定

经过上述两个步骤,图像的关键点就完全找到了,这些关键点具有尺度不变性。为了实现旋转不变性,还需要为每个关键点分配一个方向角度,也就是根据检测到的关键点所在高斯尺度图像的邻域结构中求得一个方向基准。

对于任一关键点,我们采集其所在高斯金字塔图像以r为半径的区域内所有像素的梯度特征(幅值和幅角),半径r为: r = 3 × 1.5 σ r = 3\times1.5\sigma r=3×1.5σ

其中σ是关键点所在octave的图像的尺度,可以得到对应的尺度图像。

梯度的幅值和方向的计算公式为:

m ( x , y ) = ( L ( x + 1 , y ) − L ( x − 1 , y ) 2 + ( L ( x , y + 1 ) − L ( x , y − 1 ) ) 2 θ ( x , y ) = arctan ⁡ ( L ( x , y + 1 ) − L ( x , y − 1 ) L ( x + 1 , y ) − L ( x − 1 ) , y ) \begin{array}{c} m(x, y)=\sqrt{\left(L(x+1, y)-L(x-1, y)^{2}+(L(x, y+1)-L(x, y-1))^{2}\right.} \\ \theta(x, y)=\arctan \left(\frac{L(x, y+1)-L(x, y-1)}{L(x+1, y)-L(x-1), y}\right) \end{array} m(x,y)=(L(x+1,y)L(x1,y)2+(L(x,y+1)L(x,y1))2 θ(x,y)=arctan(L(x+1,y)L(x1),yL(x,y+1)L(x,y1))

邻域像素梯度的计算结果如下图所示:

OpenCV中图像特征提取与描述_第15张图片

完成关键点梯度计算后,使用直方图统计关键点邻域内像素的梯度幅值和方向。具体做法是,将360°分为36柱,每10°为一柱,然后在以r为半径的区域内,将梯度方向在某一个柱内的像素找出来,然后将他们的幅值相加在一起作为柱的高度。因为在r为半径的区域内像素的梯度幅值对中心像素的贡献是不同的,因此还需要对幅值进行加权处理,采用高斯加权,方差为1.5σ。如下图所示,为简化图中只画了8个方向的直方图。

OpenCV中图像特征提取与描述_第16张图片

每个特征点必须分配一个主方向,还需要一个或多个辅方向,增加辅方向的目的是为了增强图像匹配的鲁棒性。辅方向的定义是,当一个柱体的高度大于主方向柱体高度的80%时,则该柱体所代表的的方向就是给特征点的辅方向。

直方图的峰值,即最高的柱代表的方向是特征点邻域范围内图像梯度的主方向,但该柱体代表的角度是一个范围,所以我们还要对离散的直方图进行插值拟合,以得到更精确的方向角度值。利用抛物线对离散的直方图进行拟合,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tvI16CIU-1646741526304)(笔记图片/image-20191009150008701.png)]

获得图像关键点主方向后,每个关键点有三个信息(x,y,σ,θ):位置、尺度、方向。由此我们可以确定一个SIFT特征区域。通常使用一个带箭头的圆或直接使用箭头表示SIFT区域的三个值:中心表示特征点位置,半径表示关键点尺度,箭头表示方向。如下图所示:

OpenCV中图像特征提取与描述_第17张图片

关键点描述

通过以上步骤,每个关键点就被分配了位置,尺度和方向信息。接下来我们为每个关键点建立一个描述符,该描述符既具有可区分性,又具有对某些变量的不变性,如光照,视角等。而且描述符不仅仅包含关键点,也包括关键点周围对其有贡献的的像素点。主要思路就是通过将关键点周围图像区域分块,计算块内的梯度直方图,生成具有特征向量,对图像信息进行抽象。

描述符与特征点所在的尺度有关,所以我们在关键点所在的高斯尺度图像上生成对应的描述符。以特征点为中心,将其附近邻域划分为d∗d个子区域(一般取d=4),每个子区域都是一个正方形,边长为3σ,考虑到实际计算时,需进行三次线性插值,所以特征点邻域的为 3 σ ( d + 1 ) ∗ 3 σ ( d + 1 ) 3\sigma(d+1)*3\sigma(d+1) 3σ(d+1)3σ(d+1)的范围,如下图所示:

OpenCV中图像特征提取与描述_第18张图片

为了保证特征点的旋转不变性,以特征点为中心,将坐标轴旋转为关键点的主方向,如下图所示:

OpenCV中图像特征提取与描述_第19张图片

计算子区域内的像素的梯度,并按照σ=0.5d进行高斯加权,然后插值计算得到每个种子点的八个方向的梯度,插值方法如下图所示:

OpenCV中图像特征提取与描述_第20张图片

每个种子点的梯度都是由覆盖其的4个子区域插值而得的。如图中的红色点,落在第0行和第1行之间,对这两行都有贡献。对第0行第3列种子点的贡献因子为dr,对第1行第3列的贡献因子为1-dr,同理,对邻近两列的贡献因子为dc和1-dc,对邻近两个方向的贡献因子为do和1-do。则最终累加在每个方向上的梯度大小为:  weight  = w ∗ d r k ( 1 − d r ) ( 1 − k ) d c m ( 1 − d c ) 1 − m d o n ( 1 − d o ) 1 − n \text { weight }=w * d r^{k}(1-d r)^{(1-k)} d c^{m}(1-d c)^{1-m} d o^{n}(1-d o)^{1-n}  weight =wdrk(1dr)(1k)dcm(1dc)1mdon(1do)1n

其中k,m,n为0或为1。 如上统计4∗4∗8=128个梯度信息即为该关键点的特征向量,按照特征点的对每个关键点的特征向量进行排序,就得到了SIFT特征描述向量。

小结:SIFT在图像的不变特征提取方面拥有无与伦比的优势,但并不完美,仍然存在实时性不高,有时特征点较少,对边缘光滑的目标无法准确提取特征点等缺陷,自SIFT算法问世以来,人们就一直对其进行优化和改进,其中最著名的就是SURF算法。

SURF原理

使用 SIFT 算法进行关键点检测和描述的执行速度比较慢, 需要速度更快的算法。 2006 年 Bay提出了 SURF 算法,是SIFT算法的增强版,它的计算量小,运算速度快,提取的特征与SIFT几乎相同,将其与SIFT算法对比如下:

在OpenCV中利用SIFT检测关键点的流程如下所示:

实例化sift

sift = cv.xfeatures2d.SIFT_create()

利用sift.detectAndCompute()检测关键点并计算

kp,des = sift.detectAndCompute(gray,None)

参数:

  • gray: 进行关键点检测的图像,注意是灰度图像

返回:

  • kp: 关键点信息,包括位置,尺度,方向信息
  • des: 关键点描述符,每个关键点对应128个梯度信息的特征向量

将关键点检测结果绘制在图像上

cv.drawKeypoints(image, keypoints, outputimage, color, flags)

参数:

  • image: 原始图像
  • keypoints:关键点信息,将其绘制在图像上
  • outputimage:输出图片,可以是原始图像
  • color:颜色设置,通过修改(b,g,r)的值,更改画笔的颜色,b=蓝色,g=绿色,r=红色。
  • flags:绘图功能的标识设置
    1. cv2.DRAW_MATCHES_FLAGS_DEFAULT:创建输出图像矩阵,使用现存的输出图像绘制匹配对和特征点,对每一个关键点只绘制中间点
    2. cv2.DRAW_MATCHES_FLAGS_DRAW_OVER_OUTIMG:不创建输出图像矩阵,而是在输出图像上绘制匹配对
    3. cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS:对每一个特征点绘制带大小和方向的关键点图形
    4. cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS:单点的特征点不被绘制

SURF算法的应用与上述流程是一致,这里就不在赘述。

利用SIFT算法在中央电视台的图片上检测关键点,并将其绘制出来:

import cv2 as cv 
import numpy as np
import matplotlib.pyplot as plt
# 1 读取图像
img = cv.imread('./image/tv.jpg')
gray= cv.cvtColor(img,cv.COLOR_BGR2GRAY)
# 2 sift关键点检测
# 2.1 实例化sift对象
sift = cv.xfeatures2d.SIFT_create()

# 2.2 关键点检测:kp关键点信息包括方向,尺度,位置信息,des是关键点的描述符
kp,des=sift.detectAndCompute(gray,None)
# 2.3 在图像上绘制关键点的检测结果
cv.drawKeypoints(img,kp,img,flags=cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
# 3 图像显示
plt.figure(figsize=(8,6),dpi=100)
plt.imshow(img[:,:,::-1]),plt.title('sift检测')
plt.xticks([]), plt.yticks([])
plt.show()

小结

SIFT原理:

  • 尺度空间极值检测:构建高斯金字塔,高斯差分金字塔,检测极值点。
  • 关键点定位:去除对比度较小和边缘对极值点的影响。
  • 关键点方向确定:利用梯度直方图确定关键点的方向。
  • 关键点描述:对关键点周围图像区域分块,计算块内的梯度直方图,生成具有特征向量,对关键点信息进行描述。

API:cv.xfeatures2d.SIFT_create()

SURF算法:

对SIFT算法的改进,在尺度空间极值检测,关键点方向确定,关键点描述方面都有改进,提高效率

Fast和ORB算法

学习目标

  • 理解Fast算法角点检测的原理,能够完成角点检测
  • 理解ORB算法的原理,能够完成特征点检测

Fast算法

我们前面已经介绍过几个特征检测器,它们的效果都很好,特别是SIFT和SURF算法,但是从实时处理的角度来看,效率还是太低了。为了解决这个问题,Edward Rosten和Tom Drummond在2006年提出了FAST算法,并在2010年对其进行了修正。

FAST (全称Features from accelerated segment test)是一种用于角点检测的算法,该算法的原理是取图像中检测点,以该点为圆心的周围邻域内像素点判断检测点是否为角点,通俗的讲就是若一个像素周围有一定数量的像素与该点像素值不同,则认为其为角点

FAST算法的基本流程

  1. 在图像中选取一个像素点 p,来判断它是不是关键点。Ip等于像素点 p的灰度值。
  2. 以r为半径画圆,覆盖p点周围的M个像素,通常情狂下,设置 r=3,则 M=16,如下图所示:

OpenCV中图像特征提取与描述_第21张图片

  1. 设置一个阈值t,如果在这 16 个像素点中存在 n 个连续像素点的灰度值都高于Ip + t,或者低于Ip - t,那么像素点 p 就被认为是一个角点。如上图中的虚线所示,n 一般取值为 12。

  2. 由于在检测特征点时是需要对图像中所有的像素点进行检测,然而图像中的绝大多数点都不是特征点,如果对每个像素点都进行上述的检测过程,那显然会浪费许多时间,因此采用一种进行非特征点判别的方法:首先对候选点的周围每个 90 度的点:1,9,5,13 进行测试(先测试 1 和 19, 如果它们符合阈值要求再测试 5 和 13)。如果 p 是角点,那么这四个点中至少有 3 个要符合阈值要求,否则直接剔除。对保留下来的点再继续进行测试(是否有 12 的点符合阈值要求)。

虽然这个检测器的效率很高,但它有以下几条缺点:

  • 获得的候选点比较多
  • 特征点的选取不是最优的,因为它的效果取决与要解决的问题和角点的分布情况。
  • 进行非特征点判别时大量的点被丢弃
  • 检测到的很多特征点都是相邻的

前 3 个问题可以通过机器学习的方法解决,最后一个问题可以使用非最大值抑制的方法解决。

机器学习的角点检测器

  1. 选择一组训练图片(最好是跟最后应用相关的图片)
  2. 使用 FAST 算法找出每幅图像的特征点,对图像中的每一个特征点,将其周围的 16 个像素存储构成一个向量P。

OpenCV中图像特征提取与描述_第22张图片

  1. 每一个特征点的 16 像素点都属于下列三类中的一种

OpenCV中图像特征提取与描述_第23张图片

  1. 根据这些像素点的分类,特征向量 P 也被分为 3 个子集:Pd ,Ps ,Pb,

  2. 定义一个新的布尔变量Kp,如果 p 是角点就设置为 Ture,如果不是就设置为 False。

  3. 利用特征值向量p,目标值是 K p K_p Kp,训练ID3 树(决策树分类器)。

  4. 将构建好的决策树运用于其他图像的快速的检测。

非极大值抑制

在筛选出来的候选角点中有很多是紧挨在一起的,需要通过非极大值抑制来消除这种影响。

为所有的候选角点都确定一个打分函数V , V的值可这样计算:先分别计算Ip与圆上16个点的像素值差值,取绝对值,再将这16个绝对值相加,就得到了V的值 V = ∑ i 16 ∣ I p − I i ∣ V=\sum_{i}^{16}\left|I_{p}-I_{i}\right| V=i16IpIi

最后比较毗邻候选角点的 V 值,把V值较小的候选角点pass掉。

FAST算法的思想与我们对角点的直观认识非常接近,化繁为简。FAST算法比其它角点的检测算法快,但是在噪声较高时不够稳定,这需要设置合适的阈值。

OpenCV中的FAST检测算法是用传统方法实现的,

实例化fast

fast = =cv.FastFeatureDetector_create( threshold, nonmaxSuppression)

参数:

  • threshold:阈值t,有默认值10
  • nonmaxSuppression:是否进行非极大值抑制,默认值True

返回:

  • Fast:创建的FastFeatureDetector对象

利用fast.detect检测关键点,没有对应的关键点描述

kp = fast.detect(grayImg, None)

参数:

  • gray: 进行关键点检测的图像,注意是灰度图像

返回:

  • kp: 关键点信息,包括位置,尺度,方向信息

将关键点检测结果绘制在图像上,与在sift中是一样的

cv.drawKeypoints(image, keypoints, outputimage, color, flags)

示例:

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
# 1 读取图像
img = cv.imread('./image/tv.jpg')
# 2 Fast角点检测
# 2.1 创建一个Fast对象,传入阈值,注意:可以处理彩色空间图像
fast = cv.FastFeatureDetector_create(threshold=30)

# 2.2 检测图像上的关键点
kp = fast.detect(img,None)
# 2.3 在图像上绘制关键点
img2 = cv.drawKeypoints(img, kp, None, color=(0,0,255))

# 2.4 输出默认参数
print( "Threshold: {}".format(fast.getThreshold()) )
print( "nonmaxSuppression:{}".format(fast.getNonmaxSuppression()) )
print( "neighborhood: {}".format(fast.getType()) )
print( "Total Keypoints with nonmaxSuppression: {}".format(len(kp)) )


# 2.5 关闭非极大值抑制
fast.setNonmaxSuppression(0)
kp = fast.detect(img,None)

print( "Total Keypoints without nonmaxSuppression: {}".format(len(kp)) )
# 2.6 绘制为进行非极大值抑制的结果
img3 = cv.drawKeypoints(img, kp, None, color=(0,0,255))

# 3 绘制图像
fig,axes=plt.subplots(nrows=1,ncols=2,figsize=(10,8),dpi=100)
axes[0].imshow(img2[:,:,::-1])
axes[0].set_title("加入非极大值抑制")
axes[1].imshow(img3[:,:,::-1])
axes[1].set_title("未加入非极大值抑制")
plt.show()

结果:

ORB 算法

SIFT和SURF算法是受专利保护的,在使用他们时我们是要付费的,但是ORB(Oriented Fast and Rotated Brief)不需要,它可以用来对图像中的关键点快速创建特征向量,并用这些特征向量来识别图像中的对象。

ORB算法流程

ORB算法结合了Fast和Brief算法,提出了构造金字塔,为Fast特征点添加了方向,从而使得关键点具有了尺度不变性和旋转不变性。具体流程描述如下:

  • 构造尺度金字塔,金字塔共有n层,与SIFT不同的是,每一层仅有一幅图像。第s层的尺度为: σ s = σ 0 s \sigma_{s}=\sigma_{0}^{s} σs=σ0s

σ0是初始尺度,默认为1.2,原图在第0层。

OpenCV中图像特征提取与描述_第24张图片

第s层图像的大小: S I Z E = ( H ∗ 1 σ s ) × ( W ∗ 1 σ s ) S I Z E=\left(H * \frac{1}{\sigma_{s}}\right) \times\left(W * \frac{1}{\sigma_{s}}\right) SIZE=(Hσs1)×(Wσs1)

  • 在不同的尺度上利用Fast算法检测特征点,采用Harris角点响应函数,根据角点的响应值排序,选取前N个特征点,作为本尺度的特征点。
  • 计算特征点的主方向,计算以特征点为圆心半径为r的圆形邻域内的灰度质心位置,将从特征点位置到质心位置的方向做特征点的主方向。

计算方法如下:

m p q = ∑ x , y x p y q I ( x , y ) m_{p q}=\sum_{x, y} x^{p} y^{q} I(x, y) mpq=x,yxpyqI(x,y)

质心位置:

C = ( m 10 m 00 , m 01 m 10 ) C=\left(\frac{m_{10}}{m_{00}}, \frac{m_{01}}{m_{10}}\right) C=(m00m10,m10m01)

主方向:

θ = arctan ⁡ ( m 01 , m 10 ) \theta=\arctan \left(m_{01}, m_{10}\right) θ=arctan(m01,m10)

  • 为了解决旋转不变性,将特征点的邻域旋转到主方向上利用Brief算法构建特征描述符,至此就得到了ORB的特征描述向量。

BRIEF算法

BRIEF是一种特征描述子提取算法,并非特征点的提取算法,一种生成二值化描述子的算法,不提取代价低,匹配只需要使用简单的汉明距离(Hamming Distance)利用比特之间的异或操作就可以完成。因此,时间代价低,空间代价低,效果还挺好是最大的优点。

算法的步骤介绍如下

  1. 图像滤波:原始图像中存在噪声时,会对结果产生影响,所以需要对图像进行滤波,去除部分噪声。
  2. 选取点对:以特征点为中心,取S*S的邻域窗口,在窗口内随机选取N组点对,一般N=128,256,512,默认是256,关于如何选取随机点对,提供了五种形式,结果如下图所示:
    • x,y方向平均分布采样
    • x,y均服从Gauss(0,S^2/25)各向同性采样
    • x服从Gauss(0,S2/25),y服从Gauss(0,S2/100)采样
    • x,y从网格中随机获取
    • x一直在(0,0),y从网格中随机选取

OpenCV中图像特征提取与描述_第25张图片

图中一条线段的两个端点就是一组点对,其中第二种方法的结果比较好。

  1. 构建描述符:假设x,y是某个点对的两个端点,p(x),p(y)是两点对应的像素值,则有:

t ( x , y ) = { 1  ifp  ( x ) > p ( y ) 0  else  t(x, y)=\left\{\begin{array}{ll} 1 & \text { ifp }(x)>p(y) \\ 0 & \text { else } \end{array}\right. t(x,y)={10 ifp (x)>p(y) else 

对每一个点对都进行上述的二进制赋值,形成BRIEF的关键点的描述特征向量,该向量一般为 128-512 位的字符串,其中仅包含 1 和 0,如下图所示:

OpenCV中图像特征提取与描述_第26张图片

在OPenCV中实现ORB算法,使用的是:

实例化ORB

orb = cv.xfeatures2d.orb_create(nfeatures)

参数:

  • nfeatures: 特征点的最大数量

利用orb.detectAndCompute()检测关键点并计算

kp,des = orb.detectAndCompute(gray,None)

参数:

  • gray: 进行关键点检测的图像,注意是灰度图像

返回:

  • kp: 关键点信息,包括位置,尺度,方向信息
  • des: 关键点描述符,每个关键点BRIEF特征向量,二进制字符串,

将关键点检测结果绘制在图像上

cv.drawKeypoints(image, keypoints, outputimage, color, flags)

示例:

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
# 1 图像读取
img = cv.imread('./image/tv.jpg')

# 2 ORB角点检测
# 2.1 实例化ORB对象
orb = cv.ORB_create(nfeatures=500)
# 2.2 检测关键点,并计算特征描述符
kp,des = orb.detectAndCompute(img,None)

print(des.shape)

# 3 将关键点绘制在图像上
img2 = cv.drawKeypoints(img, kp, None, color=(0,0,255), flags=0)

# 4. 绘制图像
plt.figure(figsize=(10,8),dpi=100)
plt.imshow(img2[:,:,::-1])
plt.xticks([]), plt.yticks([])
plt.show()

小结

  1. Fast算法

    原理:若一个像素周围有一定数量的像素与该点像素值不同,则认为其为角点

    API: cv.FastFeatureDetector_create()

  2. ORB算法

    原理:是FAST算法和BRIEF算法的结合

    API:cv.ORB_create()

LBP和HOG特征算子

学习目标

  1. 了解LBP特征的原理
  2. 了解LBP的改进算法:圆形LBP,旋转LBP和等价模式
  3. 了解HOG算法的原理
  4. 熟悉灰度图像的γ变换
  5. 了解HOG特征的提取流程
  6. 了解LBP特征的提取方法
  7. 了解HOG特征的提取方法

LBP算法

LBP(Local Binary Pattern)指局部二值模式,是一种用来描述图像局部特征的算子,LBP特征具有灰度不变性和旋转不变性等显著优点。它是由T. Ojala, M.Pietikäinen, 和 D. Harwood在1994年提出,由于LBP特征计算简单、效果较好,因此LBP特征在计算机视觉的许多领域都得到了广泛的应用。

LBP特征描述

原始的LBP算子定义为在 3 ∗ 3 3*3 33的窗口内,以窗口中心像素为阈值,将相邻的8个像素的灰度值与其进行比较,若周围像素值大于中心像素值,则该像素点的位置被标记为1,否则为0。这样, 3 ∗ 3 3*3 33邻域内的8个点经比较可产生8位二进制数(通常转换为十进制数即LBP码,共256种),即得到该窗口中心像素点的LBP值,并用这个值来反映该区域的纹理信息。如下图所示:

OpenCV中图像特征提取与描述_第27张图片

LBP值是从左上角像素,顺时针旋转得到的结果,如下图所示:

OpenCV中图像特征提取与描述_第28张图片

用公式来进行定义如下所示: L B P ( x c , y c ) = ∑ p = 0 p − 1 2 p s ( i p − i c ) L B P\left(x_{c}, y_{c}\right)=\sum_{p=0}^{p-1} 2^{p} s\left(i_{p}-i_{c}\right) LBP(xc,yc)=p=0p12ps(ipic)

其中,(xc,yc)表示 3 ∗ 3 3*3 33邻域内的中心元素,它的像素值为ic,ip代表邻域内其他像素的值。s(x)是符号函数,定义如下:

对于一幅大小为W∗H 的图像,因为边缘的像素无法计算8位的LBP值,所以将LBP值转换为灰度图像时,它的大小是(W-2)*(H-2)。

LBP算子利用了周围点与该点的关系对该点进行量化。量化后可以更有效地消除光照对图像的影响。只要光照的变化不足以改变两个点像素值之间的大小关系,那么LBP算子的值不会发生变化,**所以一定程度上,基于LBP的识别算法解决了光照变化的问题,**但是当图像光照变化不均匀时,各像素间的大小关系被破坏,对应的LBP模式也就发生了变化。

原始的LBP提出后,研究人员不断对其提出了各种改进和优化。

圆形LBP算子

原始LBP特征使用的是固定邻域内的灰度值,当图像的尺度发生变化时,LBP特征的编码将会发生变换,LBP特征将不能正确的反映像素点周围的纹理信息,因此研究人员对其进行了改进。基本的 LBP 算子的最大缺陷在于它只覆盖了一个固定半径范围内的小区域,只局限在3*3的邻域内,对于较大图像大尺度的结构不能很好的提取需要的纹理特征,因此研究者们对LBP算子进行了扩展。

新的LBP算子 L B P p R LBP_{p}^{R} LBPpR计算不同半径邻域大小和不同像素点数的特征值,其中P表示周围像素点个数,R表示邻域半径,同时把原来的方形邻域扩展到了圆形,下图给出了三种扩展后的LBP例子,其中,R可以是小数:

OpenCV中图像特征提取与描述_第29张图片

对于没有落到整数位置的点,根据轨道内离其最近的两个整数位置像素灰度值,利用双线性插值的方法可以计算它的灰度值。

该算子的计算公式与原始的LBP描述算子计算方法相同,区别在邻域的选择上。

旋转不变LBP特征

从 LBP 的定义可以看出,LBP算子不是旋转不变的。图像的旋转就会得到不同的 LBP值。所以 Maenpaa等人又将 LBP算子进行了扩展,提出了具有旋转不变性的 LBP 算子,即不断旋转圆形邻域得到一系列初始定义的 LBP值,取其最小值作为该邻域的 LBP 值。即:

L B P p , R r i = min ⁡ { R O R ( L B P p R , i ) ∣ i = 0 , 1 , … p − 1 } L B P_{p, R}^{r i}=\min \left\{R O R\left(L B P_{p}^{R}, i\right) \mid i=0,1, \ldots p-1\right\} LBPp,Rri=min{ROR(LBPpR,i)i=0,1,p1}

其中,ROR(x,i)指沿顺时针方向旋转LBP算子i次。如下图所示:

OpenCV中图像特征提取与描述_第30张图片

上图给出了求取旋转不变的 LBP 的过程示意图,算子下方的数字表示该算子对应的 LBP值,图中所示的 8 种 LBP模式,经过旋转不变的处理,最终得到的具有旋转不变性的 LBP值为 15。也就是说,图中的 8种 LBP 模式对应的旋转不变的 LBP模式都是 00001111。

Uniform Pattern LBP特征

Uniform Pattern,也被称为等价模式或均匀模式,由于一个LBP特征有多种不同的二进制形式,对于半径为R的圆形区域内含有P个采样点的LBP算子将会产生 2 P 2^P 2P种模式。很显然,随着邻域集内采样点数的增加,二进制模式的种类是以指数形式增加的。例如:5×5邻域内20个采样点,有2^20=1,048,576种二进制模式。这么多的二进制模式不利于纹理的提取、分类、识别及存取。例如,将LBP算子用于纹理分类或人脸识别时,常采用LBP模式的统计直方图来表达图像的信息,而较多的模式种类将使得数据量过大,且直方图过于稀疏。因此,需要对原始的LBP模式进行降维,使得数据量减少的情况下能最好的表示图像的信息。

为了解决二进制模式过多的问题,提高统计性,Ojala提出了采用一种“等价模式”(Uniform Pattern)来对LBP算子的模式种类进行降维。Ojala等认为,在实际图像中,绝大多数LBP模式最多只包含两次从1到0或从0到1的跳变。因此,Ojala将“等价模式”定义为:当某个LBP所对应的循环二进制数从0到1或从1到0最多有两次跳变时,该LBP所对应的二进制就称为一个等价模式类。如00000000(0次跳变),00000111(只含一次从0到1的跳变),10001111(先由1跳到0,再由0跳到1,共两次跳变)都是等价模式类。除等价模式类以外的模式都归为另一类,称为混合模式类,例如10010111(共四次跳变)。

下图所示的LBP值属于等价模式类:

OpenCV中图像特征提取与描述_第31张图片

下图中包含四次跳变,属于非等价模式。

OpenCV中图像特征提取与描述_第32张图片

通过这样的改进,二进制模式的种类大大减少,而不会丢失任何信息。模式数量由原来的 2 P 2^P 2P种减少为 P ( P-1)+2种,其中P表示邻域集内的采样点数。对于3×3邻域内8个采样点来说,二进制模式由原始的256种减少为58种,即:它把值分为59类,58个uniform pattern为一类,其它的所有值为第59类。这样直方图从原来的256维变成59维。这使得特征向量的维数更少,并且可以减少高频噪声带来的影响。

等价特征的具体实现:采样点数目为8个,即LBP特征值有2^8种,共256个值,正好对应灰度图像的0-255,因此原始的LBP特征图像是一幅正常的灰度图像,而等价模式LBP特征,根据0-1跳变次数,将这256个LBP特征值分为了59类,从跳变次数上划分:跳变0次—2个,跳变1次—0个,跳变2次—56个,跳变3次—0个,跳变4次—140个,跳变5次—0个,跳变6次—56个,跳变7次—0个,跳变8次—2个。共9种跳变情况,将这256个值进行分配,跳变小于2次的为等价模式类,共58个,他们对应的值按照从小到大分别编码为1—58,即它们在LBP特征图像中的灰度值为1—58,而除了等价模式类之外的混合模式类被编码为0,即它们在LBP特征中的灰度值为0,因此等价模式LBP特征图像整体偏暗。

实现

在OpenCV中实现了LBP特征的计算,但没有提供一个单独的计算LBP特征的接口。所以我们使用skimage给大家演示该算法。

skimage即是Scikit-Image。基于python脚本语言开发的数字图片处理包,scikit-image是基于scipy的一款图像处理包,它将图片作为numpy数组进行处理。安装方法:

pip install scikit-image

skimage包的全称是scikit-image SciKit (toolkit for SciPy) ,它对scipy.ndimage进行了扩展,提供了更多的图片处理功能。它是由python语言编写的,由scipy 社区开发和维护。skimage包由许多的子模块组成,各个子模块提供不同的功能。其中feature模块进行特征检测与提取。

使用的API是:

skimage.feature.local_binary_pattern(image, P, R, method=‘default’)

参数:

  • image: 输入的灰度图像
  • P,R: 进行LBP算子计算时的半径和像素点数
  • method: 算法类型:{‘default’, ‘ror’, ‘nri-uniform’, ‘var’}

default: “默认”,原始的LBP特征;ror: 圆形LBP算子;nri-uniform: 等价LBP算子;var:旋转不变LBP算子

示例:

我们在下图中提取LBP特征:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QniurKUH-1646741526314)(笔记图片/face.jpeg)]

import cv2 as cv
from skimage.feature import local_binary_pattern
import matplotlib.pyplot as plt
# 1.读取图像
img = cv.imread("face.jpeg")
face = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
# 2.特征提取
# 2.0 需要的参数
# LBP算法中范围半径的取值
radius = 1  
# 领域像素点数
n_points = 8 * radius 

# 2.1 原始LBP特征
lbp = local_binary_pattern(face, 8, 1)

# 2.2 圆形LBP特征
clbp = local_binary_pattern(face,n_points,radius,method="ror")

# 2.3 旋转不变LBP特征
varlbp = local_binary_pattern(face,n_points,radius,method="var")

# 2.4 等价特征
uniformlbp = local_binary_pattern(face,n_points,radius,method="nri-uniform")

fig,axes=plt.subplots(nrows=2,ncols=2,figsize=(10,8))
axes[0,0].imshow(lbp,'gray')
axes[0,0].set_title("原始的LBP特征")
axes[0,0].set_xticks([])
axes[0,0].set_yticks([])
axes[0,1].imshow(clbp,'gray')
axes[0,1].set_title("圆形LBP特征")
axes[0,1].set_xticks([])
axes[0,1].set_yticks([])
axes[1,0].imshow(varlbp,'gray')
axes[1,0].set_title("旋转不变LBP特征")
axes[1,0].set_xticks([])
axes[1,0].set_yticks([])
axes[1,1].imshow(uniformlbp,"gray")
axes[1,1].set_title("等价特征")
axes[1,1].set_xticks([])
axes[1,1].set_yticks([])
plt.show()

检测结果如下所示:

OpenCV中图像特征提取与描述_第33张图片

HOG算法

HOG(Histogram of Oriented Gridients的简写)特征检测算法,最早是由法国研究员Dalal等在CVPR-2005上提出来的,一种解决人体目标检测的图像描述子,是一种用于表征图像局部梯度方向和梯度强度分布特性的描述符。其主要思想是:在边缘具体位置未知的情况下,边缘方向的分布也可以很好的表示行人目标的外形轮廓。

特征提取流程

HOG的主要思想是:在一副图像中,局部目标的表象和形状(appearance and shape)能够被梯度或边缘的方向密度分布(即梯度的统计信息,而梯度主要位于边缘的地方)很好地描述。

HOG特征检测算法的几个步骤:颜色空间归一化—>梯度计算—>梯度方向直方图—>重叠块直方图归一化—>HOG特征。如下图所示:

OpenCV中图像特征提取与描述_第34张图片

整体流程简单描述如下:

  1. 将输入图像(你要检测的目标或者扫描窗口)灰度化,即将彩色图转换为灰度图
  2. 颜色空间归一化:采用Gamma校正法对输入图像进行颜色空间的标准化(归一化),目的是调节图像的对比度,降低图像局部的阴影和光照变化所造成的影响,同时可以抑制噪音的干扰
  3. 梯度计算:计算图像每个像素的梯度(包括大小和方向);主要是为了捕获轮廓信息,同时进一步弱化光照的干扰
  4. 梯度方向直方图:将图像划分成小cells(例如8*8像素/cell), 统计每个cell的梯度直方图(不同梯度的个数),即可形成每个cell的描述符
  5. 重叠直方图归一化:将每几个cell组成一个block(例如3*3个cell/block),一个block内所有cell的特征descriptor串联起来便得到该block的HOG特征描述符。
  6. HOG特征:将图像image内的所有block的HOG特征描述符串联起来就可以得到该image(你要检测的目标)的HOG特征描述符,就得到最终的可供分类使用的特征向量了

下面我们详细介绍每一步骤的内容:

颜色空间归一化

为了减少光照因素的影响,首先需要将整个图像进行规范化(归一化)。在图像的纹理强度中,局部的表层曝光贡献的比重较大,所以,这种压缩处理能够有效地降低图像局部的阴影和光照变化。因为颜色信息作用不大,通常先转化为灰度图,然后在进行伽马校正。

伽马校正能够有效的降低图像的局部阴影和光照所带来的的影响,从而降低算法对光照的敏感度,增强算法的鲁棒性。

伽马校正使用下式所得: Y ( x , y ) = I ( x , y ) γ Y(x, y)=I(x, y)^{\gamma} Y(x,y)=I(x,y)γ

其中,I(x,y)是图像在伽马校正前像素点(x,y)处的灰度值,Y(x,y)为标准化的像素点(x,y)处的灰度值。伽马校正如图所示:

OpenCV中图像特征提取与描述_第35张图片

从上图中可以看出:

  1. 当γ<1时,如上图中虚线所示,在低灰度值区域内,动态范围变大(当x在[0,0.2]时,y的范围是[0,0.5]),进而图像的对比度增强;在高灰度值区域内,动态范围变小(当x在[0.8,1]时,y的范围是[0.9,1]),图像的对比度降低;同时,图像整体的灰度值变大。
  2. 当γ>1时,如上图中实线所示,在低灰度值区域内,动态范围变小(当x在[0,0.5]时,y的范围是[0,0.2]),进而图像的对比度降低;在高灰度值区域内,动态范围变大,图像的对比度增强;同时,图像整体的灰度值变小。

下图中左图是原图,中图是γ=1/2.2的校正结果,右图是γ=2.2的校正结果。

OpenCV中图像特征提取与描述_第36张图片

在HOG特征提取中,γ一般取0.5,此时,图像的灰度值被拉伸,且灰度越低,拉伸的幅度越大,也就是说,对于光照较暗的图像处理较好,能较大程度提升他们的亮度。

图像梯度计算

边缘是由图像局部特征包括灰度、颜色和纹理的突变导致的。一幅图像中相邻的像素点之间变化比较少,区域变化比较平坦,则梯度幅值就会比较小,反之,则梯度幅值就会比较大。梯度在图像中对应的就是其一阶导数,所以图像梯度计算利用一阶微分求导处理,不仅能够捕获轮廓、人影以及一些纹理信息,还能进一步弱化光照的影响。Dalal研究了很多算子,如下表所示:

OpenCV中图像特征提取与描述_第37张图片

最终表明[-1,0,1]和$[1,0,-1]^T $的效果最好,所以有梯度:

G x ( x , y ) = I ( x + 1 , y ) − I ( x − 1 , y ) G y ( x , y ) = I ( x , y + 1 ) − I ( x , y − 1 ) \begin{aligned} G_{x}(x, y) &=I(x+1, y)-I(x-1, y) \\ G_{y}(x, y) &=I(x, y+1)-I(x, y-1) \end{aligned} Gx(x,y)Gy(x,y)=I(x+1,y)I(x1,y)=I(x,y+1)I(x,y1)

式中,Gx,Gy,I(x,y)分别表示的是像素点(x,y)在水平方向上及垂直方向上的梯度以及像素的灰度值,其梯度的幅值及方向计算公式如下:

G ( x , y ) = G x 2 + G y 2 α ( x , y ) = tan ⁡ − 1 ( G y G x ) \begin{array}{c} G(x, y)=\sqrt{G_{x}^{2}+G_{y}^{2}} \\ \alpha(x, y)=\tan ^{-1}\left(\frac{G_{y}}{G_{x}}\right) \end{array} G(x,y)=Gx2+Gy2 α(x,y)=tan1(GxGy)

利用该算子计算梯度不仅效果好,而且运算量低。

梯度直方图计算

Dalal的研究结果表明,梯度方向为无符号且通道数为9时得到最好的检测效果,此时一个梯度方向的一个通道为180/9=20°,代表的是角度0,20,40,60…160。梯度方向矩阵中可以看到角度是0-180度,不是0-360度,这种被称之为"无符号"梯度(“unsigned” gradients)因为一个梯度和它的负数是用同一个数字表示的,也就是说一个梯度的方向以及它旋转180度之后的方向被认为是一样的,如下图所示:

OpenCV中图像特征提取与描述_第38张图片

假设图像被分为多个cell单元,如下所示:

OpenCV中图像特征提取与描述_第39张图片

每个cell单元中包含8*8个像素,如下所示:

OpenCV中图像特征提取与描述_第40张图片

我们将上一步得到的梯度方向投影到9个通道中,并将梯度幅值作为投影时的权值。

在进行梯度方向投影处理时采用加权的方式,确定某个通道的权值,如下所示:

OpenCV中图像特征提取与描述_第41张图片

还有一个细节是,如果角度大于在(160,180)之间时,角度0和180是相等的,所以,角度为165时将其按照幅值比例投影到0和160两个通道中:(180-165)/(165-160)

OpenCV中图像特征提取与描述_第42张图片

遍历整个cell中的所有像素点,便可以得到该cell单元的梯度方向直方图:

OpenCV中图像特征提取与描述_第43张图片

重叠块直方图归一化

由于图像的局部曝光度以及前景与背景之间的对比度存在多样化的情况,所以梯度值的变化范围非常广,引进归一化的直方图对于检测结果的提高有着非常重要的作用。

在上一步中我们在每一个cell单元中创建了梯度方向直方图,在这一步中我们将在block块中进行梯度直方图的归一化,每一个block是由2*2=4个cell单元构成,如下图所示:

OpenCV中图像特征提取与描述_第44张图片

在每个块中梯度直方图应该是4*9=36维的。

在解释直方图是如何进行归一化之前,让我们看看长度为3的向量是如何进行归一化的:假设一个像素向量[128,64,32],向量的长度则为:sqrt{128^2 + 64^2 + 32^2} = 146.64 这也被称为向量的L2范数。将向量的每一个元素除以146.64得到归一化向量[0.87,0.43,0.22]。

我们将一个block块中的梯度直方图串联成一个36*1维向量,并进行归一化,就得到了该block块内的特征,因为block块之间是有重叠的,也就是说每个cell单元中的特征会多次出现在不同的block块中。

收集HOG特征

上一步中我们得到一个block块的归一化后的梯度方向直方图,现在我们只需遍历检测图像中所有的块便可以得到整个图像的梯度方向直方图,这就是我们要求解的HOG特征向量。

OpenCV中图像特征提取与描述_第45张图片

如上图所示,block块与block块之间是可以重叠的,假设我们的检测图像大小为(64×128),其x方向的有(64−8×2)/8=7个block块,其中64为检测图像的宽度,第一个8为cell宽度,2为一个block块中的cell单元宽度,第二个8为block块的滑动增量,同理,y方向有(128−8×2)/8+1=15个block块,其中128为检测图像的高度,第一个8为cell高度,2为一个block块中的cell单元高度,第二个8为block块的滑动增量,因此一共有7×15=105 个block,每一个block中梯度直方图的维数为36,那么检测图像为(64×128)的HOG特征向量的维数为105×36=3780。将其显示在图像上如下图所示:

OpenCV中图像特征提取与描述_第46张图片

从上图中,我们可以发现直方图的主要方向捕捉了人的外形,尤其是躯干和腿的部位。我们得到归一化的HOG特征之后,就可以使用分类器对行人进行检测,比如使用支持向量机SVM进行人与背景的分类,如下图所示:

OpenCV中图像特征提取与描述_第47张图片

HOG特征的优缺点

HOG特征具有以下优点:

  • HOG表示的是边缘的结构特征,因此可以描述局部的形状信息
  • 位置和方向的量化在一定程度上可以一直平移和旋转带来的影响
  • 采取局部区域归一化直方图,可以部分抵消光照变换带来的影响

它也有不少缺点:

  • 描述子生成冗长,维数较高,导致速度慢,实时性差
  • 很难处理遮挡问题
  • 由于梯度的性质,对噪声非常敏感

实现

OpenCV提供了计算HOG特征的API,实现HOG特征提取的流程是:

  1. 实例化HOG特征提取算子,使用的API是:
hog = cv2.HOGDescriptor(winSize,blockSize,blockStride,cellSize,nbins)

参数:

  • winSize: 检测窗口的大小
  • blockSize: block块的大小
  • blockStride: block块的滑动步长
  • cellSize: cell单元的大小
  • Nbins:统计梯度的方向的数目,一般取为9,即在一个cell单元中计算9个方向的梯度直方图

返回:

  • hog: 实例化后的Hog特征检测对象
  • 搜索整个图像,计算图像的HOG特征,调用:
hogDes = hog.compute(img, winStride, padding)

参数:

  • img: 输入图像
  • winStrise:检测窗口的滑动步长
  • padding:填充,在图像的周围填充点对边界进行处理。

返回:

  • hogDes: 整幅图像的HOG特征描述符,当padding为默认的(0,0)时,特征向量的维数:[(img_size - window_size) / window_stride +1 )]*(每个检测窗口中的特征维数)。

示例:

import cv2 as cv 
import numpy as np
import matplotlib.pyplot as plt

# 1.读取图像
img = cv.imread('xingren.jpeg')
gray= cv.cvtColor(img,cv.COLOR_BGR2GRAY)

# 2.Hog特征提取
# 2.1 参数设置
winSize = (64,128)
blockSize = (16,16)
blockStride = (8,8)
cellSize = (8,8)
nbins = 9

# 2.2 实例化hog对象
hog = cv.HOGDescriptor(winSize,blockSize,blockStride,cellSize,nbins)

# 2.3 计算Hog特征描述符
hogDes = hog.compute(img,winStride=(8,8))

# 2.4 输出描述符的大小
print(hogDes.size)

输出结果为:578340,该图的大小为 ( 128 ∗ 256 ) (128*256) (128256),窗口大小为(64∗128) ,block块大小为(16,16),块的移动步长为 (8,8),cell单元大小为(8,8)时,每一个窗口的特征维度为3780,窗口移动步长为(8,8)时,则图像的特征的维度:

( 128 − 64 8 + 1 ) ∗ ( 256 − 128 8 + 1 ) ∗ 3780 = 578340 \left(\frac{128-64}{8}+1\right) *\left(\frac{256-128}{8}+1\right) * 3780=578340 (812864+1)(8256128+1)3780=578340

小结

  1. LBP算法:

    原始LBP特征:在3∗3的窗口内,以窗口中心像素为阈值,将相邻的8个像素的灰度值与其进行比较,若周围像素值大于中心像素值,则该像素点的位置被标记为1,否则为0。这样,3∗3邻域内的8个点经比较可产生8位二进制数,即LBP值。

    圆形LBP算子:计算不同半径邻域大小和不同像素点数的特征值

    旋转不变LBP算子:不断旋转圆形邻域得到一系列初始定义的 LBP值,取其最小值作为该邻域的 LBP 值

    Uniform Pattern LBP特征:当某个LBP所对应的循环二进制数从0到1或从1到0最多有两次跳变时,该LBP所对应的二进制就称为一个等价模式类。除等价模式类以外的模式都归为另一类,称为混合模式类。

    API:

    Skiimage.feature.Local_binary_pattern()

  2. HOG算法

    思想:在一副图像中,局部目标的表象和形状(appearance and shape)能够利用梯度或边缘的方向密度分布来描述。

    HOG特征检测算法的步骤:

    颜色空间归一化—>梯度计算—>梯度方向直方图—>重叠块直方图归一化—>HOG特征

    简单描述如下:

    1)将输入图像灰度化,即将彩色图转换为灰度图

    2)颜色空间归一化:采用Gamma校正法对输入图像进行颜色空间的标准化(归一化),目的是调节图像的对比度,降低图像局部的阴影和光照变化所造成的影响,同时可以抑制噪音的干扰

    3)梯度计算:计算图像每个像素的梯度(包括大小和方向);主要是为了捕获轮廓信息,同时进一步弱化光照的干扰

    4)梯度方向直方图:将图像划分成小cells(例如6*6像素/cell), 统计每个cell的梯度直方图(不同梯度的个数),即可形成每个cell的描述符

    5)重叠直方图归一化:将每几个cell组成一个block(例如3*3个cell/block),一个block内所有cell的特征descriptor串联起来便得到该block的HOG特征描述符。

    6)HOG特征:将图像image内的所有block的HOG特征描述符串联起来就可以得到该image的HOG特征描述符,就得到最终的可供分类使用的特征向量了。

  3. API:

    1)实例化HOG对象:

    hog = cv.HOGDescriptor()

    2)计算HOG特征描述符

    hogdes = hog.Compute()

你可能感兴趣的:(#,图像处理与OpenCV,计算机视觉,人工智能,深度学习,图像处理,opencv)