特征点检测与匹配
特征检测概述
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)
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关键点检测
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
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= 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
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匹配器,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 &#61; cv2.drawMatchesKnn(img1,kp1,img2,kp2,[good],None)
图像查找
srcPts &#61; np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1,1,2)
dstPts &#61; np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1,1,2)
H,_ &#61; cv2.findHomography(srcPts,dstPts,cv2.RANSAC,5.0)
h,w &#61; img1.shape[:2]
pts &#61; np.float32([[0,0],[0,h-1],[w-1,h-1],[w-1,0]]).reshape(-1,1,2)
dst &#61; cv2.perspectiveTransform(pts,H)
cv2.polylines(img2,[np.int32(dst)],True,(0,0,255))
【实战】图像拼接
- 图像合并的步骤&#xff1a;
- 读文件并重置尺寸
- 根据特征点和计算描述子&#xff0c;得到单应性矩阵
- 图像变换
- 图像拼接并输出图像
import cv2
import numpy as np
def get_homo(img1,img2):
sift &#61; cv2.xfeatures2d.SIFT_create()
k1, d1 &#61; sift.detectAndCompute(img1, None)
k2, d2 &#61; sift.detectAndCompute(img2, None)
bf &#61; cv2.BFMatcher()
matches &#61; bf.knnMatch(d1, d2, k&#61;2)
verify_ratio &#61; 0.8
for m1,m2 in matches:
if m1.distance < 0.8 * m2.distance :
verify_matches.append(m1)
min_matcher &#61; 8
if len(verify_matches) > min_matcher:
img1_pts &#61; []
img2_pts &#61; []
for m in verify_matches:
img1_pts.append(k1[m.queryIdx].pt)
img2_pts.append(k2[m.queryIdx].pt)
img1_pts &#61; np.float(img1_pts).reshape(-1,1,2)
img2_pts &#61; np.float(img2_pts).reshape(-1,1,2)
H, mask &#61; cv2.findHomography(img1_pts, img2_pts, cv2.RANSAC, 5.0)
return H
else:
print(&#39;error&#39;)
exit()
def stitch_image(img1, img2, H):
h1, w1 &#61; img1.shape[:2]
h2, w2 &#61; img2.shape[:2]
img1_dims &#61; np.float([[0,0],[0,h1],[w1,h1],[w1,0]]).reshape(-1,1,2)
img2_dims &#61; np.float([[0,0],[0,h2],[w2,h2],[w2,0]]).reshape(-1,1,2)
img1_transform &#61; cv2.perspectiveTransform(img1_dims, H)
result_dims &#61; np.concatenate((img2_dims, img1_transform), axis &#61; 0)
[x_min, y_min] &#61; np.int32(result_dims.min(axis &#61; 0).ravel() - 0.5)
[x_max, y_max] &#61; np.int32(result_dims.min(axis &#61; 0).ravel() &#43; 0.5)
transform_dist &#61; [-x_min, -y_min]
transform_array &#61; np.array[1,0,transform_dist[0],[0,1,transform_dist[1]],[0,0,1]]
result_img &#61; cv2.warpPerspective(img1, transform_array.dot(H), (x_max - x_min, y_max - y_min))
result_img[transform_dist[1]:transform_dist[1] &#43; h2,transform_dist[0]:transform_dist[0] &#43; w2] &#61; img2
return result_img
img1 &#61; cv2.imread(&#39;map1.png&#39;)
img2 &#61; cv2.imread(&#39;map2.png&#39;)
img1 &#61; cv2.resize(img1,(640,480))
img2 &#61; cv2.resize(img2,(640,480))
inputs &#61; np.hstack((img1,img2))
H &#61; get_homo(img1,img2)
result_image &#61; stitch_image(img1, img2, H)
cv2.imshow(&#39;input img&#39;,inputs)
cv2.waitKey(0)