06-图像特征提取与描述

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Eric_lmy/article/details/80962689

前言

OpenCV可以检测图像的主要特征,然后提取这些特征,使其成为图像描述符,这就类似与人的眼睛与大脑。这些图像特征可以作为图像搜索的数据库。可以利用关键点将图像拼接起来,组成一个更大的图像。本章将介绍如何使用OpenCV来检测图像特征,并利用这些特征进行图像匹配和搜索等;

正文

1、 理解图像特征和特征描述
特征就是有意义的图像区域,该区域具有独特性或易于识别性。因此角点以及高密度区域是很好的特征,而大量重复的区域或低密度区域不是好的特征。边缘可以将图像分为两个区域,因此也可以看做好的特征,斑点也是有意义的特征。
找到图像特征的技术被称为特征检测。计算机也要对特征周围的区域进行描述,这样它才能在其他图像中找到相同的特征,这种描述称为特征描述。
enter image description here
如上图所示,蓝色框中的区域是一个平面很难被找到和跟踪。无论你向哪个方向移动蓝色框,长的都一样。对于黑色框中的区域,它是一个边缘。如果你沿垂直方向移动,它会改变。但是如果沿水平方向移动就不会改变。而红色框中的角点,无论你向那个方向移动,得到的结果都不同,这说明它是唯一的。所以,基本上来说角点是一个好的图像特征。(不仅仅是角点,有些情况斑点也是好的图像特征);
2、 特征检测算法
OpenCV中最常使用的特征检测和提取的算法有:
Harris:用于检测角点的算法。
FAST:用于检测角点的算法。
SIFT:用于检测斑点的算法。
SURF:用于检测斑点的算法。
BRIEF:用于检测斑点的算法。
ORB:该算法是代表带方向的FAST算法与具有旋转不变性的BRIEF算法。
下面几个是进行特征匹配的方法:
暴力(Brute-Force)匹配法。
基于FLANN的匹配法。

3、 Harris角点检测
角点的一个特性:向任何方向移动变化都很大。
Harris角点检测的结果是一个由角点分数构成的灰度图像。选取适当的阈值对结果图像进行二值化就检测到了图像中的角点。
OpenCV中的函数cv2.cornerHarris()可以用来进行角点检测。参数如下:
img:数据类型为float32的输入图像。
blockSize:角点检测中要考虑的领域大小。
ksize:sobel求导中使用的窗口大小
k:Harris角点检测方程中的自由参数,取值参数为[0.04,0.06]

import cv2 
import numpy as np 

filename = './image/blox.jpg'
img = cv2.imread(filename)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = np.float32(gray)
# 输入图像必须是float32,最后一个单数在0.04到0.05之间
dst = cv2.cornerHarris(gray, 2, 3, 0.04)
# result is dilated for marking the corners, not important
dst = cv2.dilate(dst, None)
# Threshold for optimal value, it may vary depending on the image
img[dst>0.01*dst.max()] = [0,0,255]
cv2.imshow('dst', img)
if cv2.waitKey(0) & 0xff == 27:
    cv2.destroyAllWindows()

enter image description here
有时候需要最大精度的角点检测。OpenCV为我们提供了函数cv2.cornerSubPix(),它可以提供亚像素级别的角点检测,感兴趣的可以去官网查看一下具体的用法,这里不做具体的详解;

4、 SIFT算法
上面介绍的Harris,它具有旋转不变特性。即使图像发生了旋转也能找到同样的角点,但是如果对图像进行缩放,角点就可能不再是角点了。所有就有了SIFT(尺度不变特征变换)算法。SIFT算法主要由5步构成,如下:
1. 尺度空间极值检测
在不同的尺度空间不能使用相同的窗口检测极值点。对小的角点要使用小的窗口,对大的角点只能使用大的窗口。为了达到这个目的我们要使用尺度空间滤波器(可以使用一些具有不同方差a的高斯卷积核构成)。使用具有不同方差a的高斯拉普拉斯算子(LoG)对图像进行卷积,LoG由于具有不同的方差值a所以可以用来检测不同大小的斑点(当LoG的方差a与斑点直径相等时能后使斑点完全平滑)。简单来说方差a就是一个尺度变换因子。使用一个小方差a的高斯卷积核是可以很好的检测出小的角点,而使用大方差a的高斯卷积核时可以很好的检测除大的角点。所以可以在尺度空间和二维平面中检测到局部最大值,eg(x,y,a),这表示在a的初度中(x,y)点可能是一个关键点。(高斯方差的大小与窗口的大小存在一个倍数关系:窗口大小等于6倍方差加1,所以方差的代销也决定了窗口大小)。
但是这个LoG的计算量非常大,所以SIFT算法使用高斯差分算子(DoG)来对LoG做近似。
2. 关键点(极值点)定位
一旦找到关键点,就要对它们进行修正从而得到更准确的结果。使用尺度空间的泰勒级数展开来获得极值的准确位置,如果极值点的灰度值小于阈值(0.03)就会被忽略掉。在OpenCV中这种阈值被称为contrastThreshold。
DoG算法对边界非常敏感,所以必须把边界去除,Harris算法除了可以用于角点检测之外还可以用于检测边界。
3. 为关键点(极值点)指定方向参数
为每一个关键点指定一个方向参数,这样它才会具有旋转不变性。获取关键点的领域,然后计算这个区域的梯度级和方向。根据计算得到的结果创建一个含有36个bins(每10度一个bin)的方向直方图。(使用当前尺度空间a值的1.5倍的方差的图形高斯窗口和梯度级做权重)。直方图中的峰值为主方向参数,如果其他的任何高度高于峰值的80%被认为是辅方向。这就会在相同的尺度空间相同的位置构建具有不同方向的关键点。
4. 关键点描述符
新的关键点描述符被创建之后,就选取与关键点周围一个16x16的邻域,把它分成16个4x4的小方块,为每个小方块创建一个具有8个bin的方向直方图。总共加起来有128个bin。由此组成长为128的向量就构成了关键点描述符。除此之外还要进行几个测量已达到对关照变化,旋转等的稳定性。
5. 关键点匹配
接下来就可以采用关键点特征向量的欧式距离来作为两幅图像中关键点的相似性断定度量。取第一个图的某个关键点,通过遍历找到第二福图像中距离最近的那个关键点。有时候,第二个距离最近的关键点与第一个距离最近的关键点靠的太近。这可能是由于噪声等引起的。此时要几段最近距离与第二近距离的比值。如果比值大于0.8,就忽略掉。这会去除90%的错误匹配,同事只去除5%的正确匹配。

import cv2 
import numpy as np 
img = cv2.imread('image/blox.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# sift = cv2.SIFT() #没有这种写法了
sift = cv2.xfeatures2d.SIFT_create()
kp = sift.detect(gray, None)
#img = cv2.drawKeypoints(gray, kp, img)
img = cv2.drawKeypoints(gray, kp, img, 
    flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv2.imwrite('sss.png', img)

函数sift.detect()可以在图像中找到关键点。如果你想在图像中的一个区域搜索的话,也可以创建一个掩模图像作为参数使用。返回的关键点是一个带有很多不同属性的特殊结构体,这些属性中包含它的坐标(x,y),有意义的领域大小,确定其方向的角度等。
OpenCV提供了两种方法来计算关键点描述符:
1. 使用函数sift.compute() : kp,des = sift.compute(gray, kp)
这里kp是一个关键点列表。Des是一个numpy数组,其大小是关键点数目乘以128.
2. 如果还没有找到关键点,可以使用函数sift.detectAndCompute()一步到位直接找到关键点并计算出其描述符。

5、 SURF算法
在SIFT中,Lowe在构建尺度空间时使用DoG对LoG进行近似。SURF使用盒子滤波器(box_filter)对LoG进行近似。在进行卷积计算时可以利用卷积图像(卷积图像的一大特点是:计算图像中某个窗口内左右像素和时,计算量的大小与窗口大小无关),是盒子滤波器的一大优点。而且这种计算可以在不同空间同事进行。同样SURF算法计算关键点的尺度和位置也是依赖于Hessian矩阵行列式的
此外,尽管SURF和SIFT这两个特征检测的算法所提供的API不同,但只需要做简单的选择就可以实现动态选择SURF和SIFT特征检测算法,如下:

import cv2
import sys 
import numpy as np 

imgpath = sys.argv[1]
img = cv2.imread(imgpath)
alg = sys.argv[2]

def fd(algorithm):
    if algorithm == "SIFT":
        return cv2.xfeatures2d.SIFT_create()
    if algorithm == "SURF":
        return cv2.xfeatures2d.SURF_create(float(sys.argv[3]) if len(sys.argv) == 4 else 4000)

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

fd_alg = fd(alg)
keypoints, descriptor = fd_alg.detectAndCompute(gray, None)

img = cv2.drawKeypoints(image=img, outImage=img, keypoints=keypoints, flags=4, color=(51,163,236))

cv2.imshow('keypoints', img)
while True:
    if cv2.waitKey(int(1000/12)) & 0xff == ord('q'):
        break
cv2.destroyAllWindows()

如下方式运行即可:当然也可以更少阈值如8000等,效果不一样。
python test.py data\butterfly.jpg SURF
enter image description here

6、 FAST特征检测
FAST算法会在像素周围绘制一个圆,该圆包括16个像素,然后FAST会将每个像素与加上一个阈值的圆心像素值进行比较,若有四分之三的像素比加上一个阈值的圆心的像素值还亮或暗的像素,则认为圆心是一个角点。FAST是一个不错的算法,但FAST方法与阈值精密相关,这就要求用户输入参数;

7、BRIEF
BRIEF不去计算描述符而是直接找到一个二进制字符串。这种算法使用的是已经平滑后的图像,它会按照一种特定的方式选取一组像素点对 ,然后在这些像素点对之间进行灰度值对比。BRIEF 是一种特征描述符,它不提供查找特征的方法。所以我们不得不使用其他特征检测器,比如 SIFT 和 SURF 等;
特征描述符是图像的一种表示,因为可以比较两个图像的关键点描述符,并找到他们的共同之处,所以描述符可以作为特征匹配的一种方法,BRIEF是目前比较快的描述符,其理论比较复杂,但BRIEF采用了一系列的优化措施,使其成为不错的特征匹配的方法;

8、ORB(Oriented FAST and Rotated BRIEF)
ORB 基本是 FAST 关键点检测和 BRIEF 关键点描述器的结合体,并通过很多修改增强了性能。首先它使用 FAST 找到关键点,然后再使用 Harris角点检测对这些关键点进行排序找到其中的前 N 个点。它也使用金字塔从而产生尺度不变性特征。使用灰度矩的算法计算出角点的方向。以角点到角点所在(小块)区域质心的方向为向量的方向。为了进一步提高旋转不变性,要计算以角点为中心半径为 r 的圆形区域的矩,再根据矩计算除方向。
对于描述符, ORB 使用的是 BRIEF 描述符。但是我们已经知道 BRIEF对与旋转是不稳定的。所以我们在生成特征前,要把关键点领域的这个 patch的坐标轴旋转到关键点的方向。
OpenCV中的ORB算法使用函数 cv2.ORB_create()创建一个 ORB 对象。实例如下:

import numpy as np
import cv2
from matplotlib import pyplot as plt

img = cv2.imread('./data/blox.jpg',0)
# Initiate STAR detector
orb = cv2.ORB_create()
# find the keypoints with ORB
kp = orb.detect(img,None)
# compute the descriptors with ORB
kp, des = orb.compute(img, kp)
# draw only keypoints location,not size and orientation
img2 = img
img2 = cv2.drawKeypoints(img,kp,img2,color=(0,255,0), flags=0)
plt.imshow(img2),plt.show()

enter image description here

9、 特征匹配
9.1 对ORB描述符进行蛮力匹配
在本例中我们有一个查询图像和一个目标图像。我们要使用特征匹配的方法在目标图像中寻
找查询图像的位置;

import numpy as np
import cv2 
from matplotlib import pyplot as plt 

img1 = cv2.imread('box.png', cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread('box_in_scene.png', cv2.IMREAD_GRAYSCALE)

orb = cv2.ORB_create() 
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)# 创建一个 BFMatcher 对象,并将距离计算设置为 cv2.NORM_HAMMING(因为我们使用的是 ORB),并将 crossCheck 设置为 True。
# ORB
matches = bf.match(des1, des2)# 使用 match()方法获得两幅图像的最佳匹配
matches = sorted(matches, key=lambda x:x.distance )
img3 = cv2.drawMatches(img1, kp1, img2, kp2, matches[:40], img2, flags=2)
plt.imshow(img3)
plt.show()

matches = bf:match(des1; des2) 返回值是一个 DMatch 对象列表。这个DMatch 对象具有下列属性:
1、 DMatch.distance - 描述符之间的距离。越小越好。
2、 DMatch.trainIdx - 目标图像中描述符的索引。
3、 DMatch.queryIdx - 查询图像中描述符的索引。
4、 DMatch.imgIdx - 目标图像的索引。

enter image description here

9.2 对SIFT描述符进行蛮力匹配和比值测试

import numpy as np
import cv2
from matplotlib import pyplot as plt
img1 = cv2.imread('box.png',0) # queryImage
img2 = cv2.imread('box_in_scene.png',0) # trainImage
# Initiate SIFT detector
sift = cv2.xfeatures2d.SIFT_create()
# find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)
# BFMatcher with default params
bf = cv2.BFMatcher()
matches = bf.knnMatch(des1,des2, k=2)
# 比值测试,首先获取与 A 距离最近的点 B(最近)和 C(次近),只有当 B/C
# 小于阈值时(0.75)才被认为是匹配,因为假设匹配是一一对应的,真正的匹配的理想距离为 0
good = []
for m,n in matches:
    if m.distance < 0.75*n.distance:
        good.append([m])
# cv2.drawMatchesKnn expects list of lists as matches.
img3 = np.zeros(img1.shape, dtype=np.uint8)
img3 = cv2.drawMatchesKnn(img1,kp1,img2,kp2,good[:10],img3,flags=2)
plt.imshow(img3),plt.show()

enter image description here

10、 FLANN匹配器
FLANN 是快速最近邻搜索包(Fast_Library_for_Approximate_Nearest_Neighbors)的简称。它是一个对大数据集和高维特征进行最近邻搜索的算法的集合,而且这些算法都已经被优化过了。在面对大数据集时它的效果要好于 BFMatcher。实例如下:

import numpy as np 
import cv2 
from matplotlib import pyplot as plt

queryImage = cv2.imread('box.png', 0)
trainingImage = cv2.imread('box_in_scene.png', 0)

# create SIFT and detect / compute
sift = cv2.xfeatures2d.SIFT_create()
kp1, des1 = sift.detectAndCompute(queryImage, None)
kp2, des2 = sift.detectAndCompute(trainingImage, None)

# FLANN matcher parameters
FLANN_INDEX_KDTREE = 0
indexParams = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
searchParams = dict(checks=50) # or pass empty dictionary

flann = cv2.FlannBasedMatcher(indexParams, searchParams)
matches = flann.knnMatch(des1, des2, k=2)

# prepare an empty mask to draw good matches
matchesMask = [[0,0] for i in range(len(matches))]

for i , (m,n) in enumerate(matches):
    if m.distance < 0.7*n.distance:
        matchesMask[i] = [1,0]

drawParams = dict(matchColor=(0,255,0), singlePointColor=(255,0,0), matchesMask=matchesMask,flags=0)

resultImage = cv2.drawMatchesKnn(queryImage, kp1, trainingImage, kp2, matches, None, **drawParams)

plt.imshow(resultImage)
plt.show() 

enter image description here

结语:到这里图像特征提取以及描述基本上都讲完了,当然有些比较复杂的理论知识没有过细的讲解,而是使用代码直接表现出来的,感兴趣的话可以深入的去了解,或者去OpenCV官网教材中去学习,这里只是分享一下本人的学习过程中的比较注重的知识点,如发现错误之处欢迎之处加以改正,谢谢。

猜你喜欢

转载自blog.csdn.net/Eric_lmy/article/details/80962689