特征点检测与匹配
特征检测概述
OpenCV特征的场景
- 图像搜索,如以图搜图
- 拼图游戏
- 拼图方法:
- 寻找特征
- 特征是唯一的
- 可追踪的
- 能比较的
- 拼图方法:
- 图像拼接,将两张有关联的图拼接到一起
实例:寻找目标图在原图中的位置
- 平坦部分很难找到它在原图中的位置
- 边缘相比平坦要好找一些,但也不能一下确定具体位置
- 角点可以一下就能找到其在原图中的位置
图像特征定义
- 图像特征就是指有意义的图像区域,具有独特性、易于识别性,比如角点、斑点以及高密度区
角点
- 在特征中最重要的是角点
- 灰度梯度的最大值对应的像素
- 两条线的交点
- 极值点(一阶导数最大值,但二阶导数为0)
Harris角点检测
-
用一个方块移动进行衡量
-
光滑地区,无论向哪里移动,衡量系数不变
-
边缘地区,垂直边缘移动时,衡量系数变化剧烈
-
角点处,无论向哪里移动,衡量系数都变化剧烈
-
cornerHarris(img,dst,blockSize,ksize,k)
- blockSize:检测窗口大小,窗口越大,敏感度越高
- ksize:Sobel的卷积核
- k:权重系数,经验值,一般取0.02-0.04之间
#灰度化
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#处理的是灰度化后的图片
dst = cv2.cornerHarris(gray,2.3.0.04)
#Harris角点的展示
img[dst>0.01 * dst.max()] = [0,0,255]
Shi-Tomasi角点检测
- Shi-Tomasi是Harris角点检测的改进,尤其是不用设置k值
- goodFeaturesToTrack(img,maxCorners,…)
- maxCorners:角点的最大数,值为0表示无限制
- qualityLevel:小于1.0的正数,一般在0.01-0.1之间
- minDistance:角之间最小欧式距离,忽略小于此距离的点,距离越大检测角数越少,反之越大
- mask:感兴趣的区域,不设置则会对整张图片进行检测
- blockSize:检测窗口
- useHarrisDetector:是否用Harris算法
- true:使用原始Harris算法,k默认为0.04
- flase:使用Shi-Tomasi,默认为flase
corners = cv2.goodfeaturesToTrack(gray,1000,0.01,10)
corners = np.int0(corners)
for i in corners:
x,y = i.ravel()
cv2.circle(img,(x,y),3,(255,0,0),-1)
SIFT关键点检测
-
SIFT:Scale-Invariant Feature Transform 与缩放无关的特征转换
-
SIFT出现的原因:
- Harris角点具有旋转不变的特性
- 但缩放后,原有的角点可能不会被检测出来
-
使用SIFT步骤:
- 创建SIFT对象,sift = cv2.xfeatures2d.SIFT_create()
- 进行检测,kp = sift.detect(img,…)
- 绘制关键点,drawKeypoints(gray,kp,img)
#灰度化
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#创建SIFT对象
sift = cv2.xfeatures2d.SIFT_create()
#进行检测
kp = sift.detect(gray,None)
#绘制关键点
cv2.drawKeypoints(gray,kp,img)
- 关键点:位置,大小和方向
- 关键点描述子:记录里关键点周围对其有贡献的像素点的一组向量值,其不受仿射变换、光照变换等影响
- 计算描述子:
- kp,des = sift.compute(img,kp)
- 其作用时进行特征匹配
- 同时计算关键点和描述子:
- kp,des = sift.detectAndcompute(img,mask)
- mask:指明对img中那个区域进行计算,未设置/设置成None即对整个图片进行计算
SURF特征检测
- SURF: Speeded-Up Robust Features 加速的鲁棒性特征检测
- SURF优点:SIFT最大的问题是速度慢,因此才有SURF,SURF检测速度快
- 使用SURF步骤
- surf= cv2.xfeatures2d.SURF_create()
- kp,des = surf.detectAndcompute(img,mask)
#创建SURF对象
surf= cv2.xfeatures2d.SURF_create()
#进行检测
kp,des = surf.detectAndcompute(gray,None)
#绘制关键点
cv2.drawKeypoints(gray,kp,img)
OBR特征检测
-
OBR:Oriented FAST and Rotated BRIEF
-
OBR优势:可以做到实时监测
-
抛弃部分数据以提升速度,因此准确性不如SIFT、SURF
-
FAST:可以做到特征点的实时监测
-
BRIEF是对已检测到的特征点进行描述,它加快了特征描述符建立的速度,同时也极大的降低了特征匹配的时间
-
使用ORB步骤:
- obr = cv2.ORB_create()
- kp,des = obr.detectAndcompute(img,mask)
obr = cv2.ORB_create()
kp,des = obr.detectAndcompute(gray,None)
暴力特征匹配BF
-
BF(Brute-Force) 暴力特征匹配
-
暴力特征匹配原理:它使用第一组中的每个特征的描述子与第二组中的所有特征描述子进行匹配,计算它们之间的差距,然后将最接近的一个匹配值返回
OpenCV特征匹配步骤:
-
创建匹配器,BFMatchaer(normType,crossCheck)
- normType:NORM_L1,NORM_L2,HAMMING1…
- crossCheck:是否进行交叉匹配,默认为flase
-
进行特征匹配,bf.match(des1,des2)
- 参数为SIFT、SURF、OBR等计算的描述子
- 对两幅图的描述子进行计算
-
绘制匹配点,cv2.drawMatches(img1,kp1,img2,kp2,match
- 搜索图img,kp
- 匹配图img,kp
- match()方法返回的匹配结果
bf = BFMatchaer(cv2.NORM_L1)
match = bf.match(des1,des2)
cv2.drawMatches(img1,kp1,img2,kp2,match,None)
FLANN特征匹配
- 优缺点:
-
在进行批量特征匹配时,FLANN速度更快
-
由于它使用的是临近近似值,所以精度较差
-
使用FLANN特征匹配的步骤:
- 创建FLANN匹配器,FlannBasedMatcher()
- index_params字典:匹配算法KDTREE(SIFT、SURF使用)、LSH(orb使用)
- 进行特征匹配,flann.match/knnMatch()
- 绘制匹配点,cv2.drawMatches/drawMatchesKnn()
KDTREE
- search_params字典:知道KDTREE算法中遍历树的次数
- 一般KDTREE设为5,search_params设为50,经验值,准确率高,速度快
- index_params = dict(algorithm = FLANN_INDEX_KDTREE,trees = 5)
- search_params = dict(checks = 50)
knnMatch()
- 参数为SIFT、SURF、OBR等计算的描述子
- k,表示取欧式距离最近的前k个关键点
- 返回的是匹配的结果DMatch对象:
- distance,描述子之间的距离,值越低越好
- queryIdx,第一个图像的描述子索引值
- trainIdx,第二个图的描述子索引值
- imgIdx,第二个图的索引值
drawMatchesKnn()
- 搜索img,kp
- 匹配图img,kp
- match()方法返回的匹配结果
#创建匹配器
index_params = dict(algorithm = FLANN_INDEX_KDTREE,trees = 5)
search_params = dict(checks = 50)
flann = cv2.FlannBasedMatcher(index_params,search_params)
#对描述子进行匹配计算
matches = flann.knnMatch(des1,des2,k = 2)
for i,(m,n) in enumerate(matches):
if m.distance < 0.7 * n.distance:
good.append(m)
ret = cv2.drawMatchesKnn(img1,kp1,img2,kp2,[good],None)
图像查找
- 特征匹配 + 单应性矩阵
srcPts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1,1,2)
dstPts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1,1,2)
H,_ = cv2.findHomography(srcPts,dstPts,cv2.RANSAC,5.0)
h,w = img1.shape[:2]
pts = np.float32([[0,0],[0,h-1],[w-1,h-1],[w-1,0]]).reshape(-1,1,2)
dst = cv2.perspectiveTransform(pts,H)
cv2.polylines(img2,[np.int32(dst)],True,(0,0,255))
【实战】图像拼接
- 图像合并的步骤:
- 读文件并重置尺寸
- 根据特征点和计算描述子,得到单应性矩阵
- 图像变换
- 图像拼接并输出图像
import cv2
import numpy as np
#第一步,读取文件,将图片设置成一样的大小 640*480
#第二步,找特征点,描述子,计算单应性矩阵
#第三步,根据单应性矩阵对图像进行变换,然后平移
#第四步,拼接并输出最终结果
def get_homo(img1,img2):
#1.创建特征转换对象
#2.通过调整转换对象获得特征点和描述子
#3.创建特征匹配器
#4.进行特征匹配
#5.过滤特征,找出有效的特征匹配点
sift = cv2.xfeatures2d.SIFT_create()
k1, d1 = sift.detectAndCompute(img1, None)
k2, d2 = sift.detectAndCompute(img2, None)
bf = cv2.BFMatcher()
matches = bf.knnMatch(d1, d2, k=2)
verify_ratio = 0.8
for m1,m2 in matches:
if m1.distance < 0.8 * m2.distance :
verify_matches.append(m1)
min_matcher = 8
if len(verify_matches) > min_matcher:
img1_pts = []
img2_pts = []
for m in verify_matches:
img1_pts.append(k1[m.queryIdx].pt)
img2_pts.append(k2[m.queryIdx].pt)
img1_pts = np.float(img1_pts).reshape(-1,1,2)
img2_pts = np.float(img2_pts).reshape(-1,1,2)
H, mask = cv2.findHomography(img1_pts, img2_pts, cv2.RANSAC, 5.0)
return H
else:
print('error')
exit()
def stitch_image(img1, img2, H):
#1.获得每张图片的四个角点
#2.对图片进行变换(单应性矩阵使图进行旋转,平移)
#3.创建一张大图,将两张图拼接到一起
#4.将结果输出
#获得原始图的高/宽
h1, w1 = img1.shape[:2]
h2, w2 = img2.shape[:2]
img1_dims = np.float([[0,0],[0,h1],[w1,h1],[w1,0]]).reshape(-1,1,2)
img2_dims = np.float([[0,0],[0,h2],[w2,h2],[w2,0]]).reshape(-1,1,2)
img1_transform = cv2.perspectiveTransform(img1_dims, H)
result_dims = np.concatenate((img2_dims, img1_transform), axis = 0)
[x_min, y_min] = np.int32(result_dims.min(axis = 0).ravel() - 0.5)
[x_max, y_max] = np.int32(result_dims.min(axis = 0).ravel() + 0.5)
#平移的距离
transform_dist = [-x_min, -y_min]
transform_array = np.array[1,0,transform_dist[0],[0,1,transform_dist[1]],[0,0,1]]
result_img = cv2.warpPerspective(img1, transform_array.dot(H), (x_max - x_min, y_max - y_min))
result_img[transform_dist[1]:transform_dist[1] + h2,transform_dist[0]:transform_dist[0] + w2] = img2
return result_img
#读取两张图片
img1 = cv2.imread('map1.png')
img2 = cv2.imread('map2.png')
#将两张图片设置成同样大小
img1 = cv2.resize(img1,(640,480))
img2 = cv2.resize(img2,(640,480))
inputs = np.hstack((img1,img2))
#获取单应性矩阵
H = get_homo(img1,img2)
#进行图像拼接
result_image = stitch_image(img1, img2, H)
cv2.imshow('input img',inputs)
cv2.waitKey(0)