这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战
基本思路
我们先说一说今天代码主要做了那些事,首先要在图像中找到好的特征点、例如角点,这些点具有旋转不变性,平移和缩放不变性。我们希望这些点在画面上分布式比较均匀的。然后需要计算出这些特征点的描述符,什么又是描述符呢? 就是通过一些列特性或者说是指标来描述一个特征点,例如描述一篇文章,我们文章类型,作者,篇幅,这些都是文章特征或者说描述符。这些描述用来检验两个特征点相关性,拿到特征点描述符后,通过描述符来在前后帧,也就是前一帧和后一帧画面对特征点进行匹配。这就是接下来要做的事
明确了目标接下来就是如何做了,先是如何拿到好的特征点,上一次分享,采样goodFeaturesToTrack
提取了特征点用于进行匹配,goodFeaturesToTrack
提取到特征点要优于之前采用 orb
提取到特征点。我们将提取特征点作为一个单独的类提取到一个文件中。在 FeatureExtractor 类中,初始化函数 __init__
函数中,初始化一个 orb 特征提取器,可以通过 ORB_create
来创建 ORB 对象,在创建对象时,通过参数 nfeatures
可以指定特征点最大数
class FeatureExtractor(object):
def __init__(self) -> None:
self.orb = cv2.ORB_create()
self.bf = cv2.BFMatcher()
self.last = None
def extract(self,img):
feats = cv2.goodFeaturesToTrack(np.mean(img,axis=2).astype(np.uint8),
3000, qualityLevel=0.01, minDistance=3)
# keyPoints
kps = [cv2.KeyPoint(x=f[0][0], y=f[0][1],_size=20) for f in feats]
kps,des = self.orb.compute(img, kps)
if self.last is not None:
matches = self.bf.match(des,self.last['des'])
print(matches)
self.last = {'kps':kps,'des':des}
return kps,des
复制代码
通过 goodFeaturesToTrack 提取特征的分布要优于之前 orb 获取特征点,有了特征点我们需要使用 orb.compute
来计算特征点的描述符 des,des 可以反应特征点的一些特征,我们通过特征点的特征再对特征点进行匹配。orb 提供 compute 来计算特征点的描述符,接受
import cv2
import numpy as np
class Extractor(object):
def __init__(self) -> None:
self.orb = cv2.ORB_create()
#
self.bf = cv2.BFMatcher()
self.last = None
def extract(self,img):
# detection
feats = cv2.goodFeaturesToTrack(np.mean(img,axis=2).astype(np.uint8),
3000, qualityLevel=0.01, minDistance=3)
# extraction
kps = [cv2.KeyPoint(x=f[0][0], y=f[0][1],_size=20) for f in feats]
kps,des = self.orb.compute(img, kps)
matches = None
if self.last is not None:
matches = self.bf.match(des,self.last['des'])
self.last = {'kps':kps,'des':des}
return kps,des,matches
复制代码
self.bf = cv2.BFMatcher()
这里用的 Brute-Force 匹配,这里 BF 就是 Brute-Force 的缩写,其实 Brute-Force 匹配比较简单,在当前帧图像中选取一个关键点然后依次与上一帧图像的每个关键点进行(描述符)距离测量,最后返回距离最近的关键点。
def extract(self,img):
# detection
feats = cv2.goodFeaturesToTrack(np.mean(img,axis=2).astype(np.uint8),
3000, qualityLevel=0.01, minDistance=3)
# extraction
kps = [cv2.KeyPoint(x=f[0][0], y=f[0][1],_size=20) for f in feats]
kps,des = self.orb.compute(img, kps)
matches = None
if self.last is not None:
matches = self.bf.match(des,self.last['des'])
matches = zip([kps[m.queryIdx] for m in matches],[self.last['kps'][m.trainIdx] for m in matches])
self.last = {'kps':kps,'des':des}
return matches
复制代码
这里简单说一下,这里需要解释一下,对于配置 matches 提供 queryIdx 在当前帧关键点 kps 的匹配对的索引,trainIdx 索引进行匹配。
这里多说一句,我们跟着高手学习,时候主要学习高手的 coding 习惯,以及高手编程遇到问题,是如何解决问题,这样来开阔自己思路。
class Extractor(object):
def __init__(self) -> None:
self.orb = cv2.ORB_create(100)
#
self.bf = cv2.BFMatcher(cv2.NORM_HAMMING)
self.last = None
def extract(self,img):
# detection
feats = cv2.goodFeaturesToTrack(np.mean(img,axis=2).astype(np.uint8),
3000, qualityLevel=0.01, minDistance=3)
# extraction
kps = [cv2.KeyPoint(x=f[0][0], y=f[0][1],_size=20) for f in feats]
kps,des = self.orb.compute(img, kps)
# matches = None
ret = []
if self.last is not None:
matches = self.bf.knnMatch(des,self.last['des'],k=2)
for m, n in matches:
if m.distance < 0.75*n.distance:
ret.append((kps[m.queryIdx],self.last['kps'][m.trainIdx]))
self.last = {'kps':kps,'des':des}
return ret
复制代码
上面代码,我们用 bf 的 knnMatch 来进行匹配通过机器学习 knn 方法来获取距离特征点最近的 k 个点,这里 knnMatch 中 k 表示返回 2 的结果,通过 m.distance < 0.75*n.distance
来筛选。这一次效果要比上一次好多了,只有少数几个连接,而且在 cv2.BFMatcher(cv2.NORM_HAMMING)
设置为 NORM_HAMMING
。