特征点检测与匹配
特征点检测与匹配
应用场景
图像搜索,如以图搜图(不是对整个图片进行处理的而是检测特征点进行搜索)
拼图游戏
拼图方法
寻找特征
特征是唯一的
特征可追踪的
能进行比较的
图像拼接,将两张有关联的图拼接到一起
什么是特征
图像特征就是指有意义的图像区域,具有独特性,易于识别性,比如角点,斑点以及高密度区域
角点
在特征中最重要的是角点,灰度梯度的最大值对应的像素
两条线的交点,极值点(一阶导数最大值,单二阶导数为0)
Harris角点检测
周围没有像素的变化就认为是平坦的
垂直运动没有变化左右运动有变化--边缘
无论怎样运动都产生变化--角点
总结
光滑地区,无论向哪里移动,衡量系数不变
边缘地址,垂直边缘移动时,衡量系数变化剧烈
在交点处,往哪个方向移动,衡量系数都变化剧烈
角点检测api
cornerHarris(img,dst,blockSize,ksize,k)
blockSize:检测窗口大小
ksize:Sobel的卷积核
k:权重系数,经验值,一般取0.02~0.04之间
import cv2
import numpy as np
blockSize = 2
ksize = 3
k = 0.04
img = cv2.imread('C:\\Users\dazhi\Desktop\jiaodian.png')
###灰度化
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
###Harris角点检测
dst = cv2.cornerHarris(gray,blockSize,ksize,k)
###角点展示
img[dst>0.01*dst.max()] = [0,0,255]
cv2.imshow('harris',img)
cv2.waitKey(0)
Shi-Tomasi角点检测
Shi-Tomasi是Harris角点检测的改进(k值不用设)
Harris角点检测算的稳定性和k有关,而k是个经验值,不好设定最佳值
api
goodFeaturesToTrack(img,maxCorners...)
maxCorners:角点的最大数,值为0表示无限制
qualityLevel:角点的质量 小于1.0的正数,一般在0.01-0.1之间
minDistance:角之间距离 角之间最小欧式距离,忽略小于此距离的点
mask 感兴趣的区域(若设置mask则只在指定区域检测)
blockSize:检测窗口
useHarrisDetector: 是否使用Harris算法(默认是不使用)
k 默认是0.04
import cv2
import numpy as np
maxCorners = 1000
ql = 0.01
minDistance = 10
img = cv2.imread('C:\\Users\dazhi\Desktop\jiaodian.png')
###灰度化
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
###角点检测
dst = cv2.goodFeaturesToTrack(gray,maxCorners,ql,minDistance)
###转换为整型
dst = np.int0(dst)
###绘制角点
for i in dst:
###转为一维
x,y = i.ravel()
cv2.circle(img,(x,y),3,(255,0,0),-1)
cv2.imshow('harris',img)
cv2.waitKey(0)
SIFI与缩放无关的特征转换
SIFI出现的原因
Harris角点具有旋转不变的特征
但缩放后,原来的角点有可能就不是角点了
放大后原来能检测出是角的现在可能就检测出不是角
SIFI解决的问题就是原来是角放大或缩小后依然把这个角检测出来
使用SIFI步揍
创建SIFI对象
进行检测,kp = sift.detect(img,...)
绘制关键点,drawKrypoints(gray,kp,img)
import cv2
import numpy as np
img = cv2.imread('C:\\Users\dazhi\Desktop\jiaodian.png')
###灰度化
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
####创建sift对象
####xfeatures2d扩展包中(因为版权问题所有放在扩展库中)
# sift = cv2.xfeatures2d.SIFT_create()
sift = cv2.SIFT_create()
###进行检测
###None对整个图进行检测
kp = sift.detect(gray,None)
###绘制
cv2.drawKeypoints(gray,kp,img)
cv2.imshow('img',img)
cv2.waitKey(0)
计算SIFT描述子
关键点和描述子
关键点:位置,大小和方向
关键点描述子:记录了关键点周围对其有贡献的像素点的一组向量值,其不受仿射变换,光照变换等影响
计算描述子
kp,des = sift.compute(img,kp)
作用是进行特征匹配
同时计算关键点和描述
kp,des = sift.detectAndCompute(img...)
mask:指明对img中哪个区域进行计算
import cv2
import numpy as np
img = cv2.imread('C:\\Users\dazhi\Desktop\jiaodian.png')
###灰度化
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
####创建sift对象
####xfeatures2d扩展包中
# sift = cv2.xfeatures2d.SIFT_create()
sift = cv2.SIFT_create()
###进行检测
###None对整个图进行检测
kp,des = sift.detectAndCompute(gray,None)
print(des)
###绘制
cv2.drawKeypoints(gray,kp,img)
cv2.imshow('img',img)
cv2.waitKey(0)
----
打印的描述子结果
[[ 5. 0. 0. ... 0. 0. 0.]
[ 0. 0. 0. ... 0. 0. 0.]
[ 0. 0. 0. ... 0. 0. 0.]
...
[ 14. 5. 49. ... 0. 0. 5.]
[ 0. 47. 153. ... 0. 0. 0.]
[ 0. 23. 153. ... 0. 0. 0.]]
SURF
优点
SIFT最大的问题是速度慢,因此才有SURF
使用步揍
surf = cv2.xfeatures2d.SURF_create()
kp,des = surf.detectAndCompute(img,mask)
import cv2
import numpy as np
img = cv2.imread('C:\\Users\dazhi\Desktop\jiaodian.png')
###灰度化
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
####创建sift对象
####xfeatures2d扩展包中
# sift = cv2.xfeatures2d.SIFT_create()
# sift = cv2.SIFT_create()
surf = cv2.xfeatures2d.SURF_create()
# surf = cv2.SURF_create()
###进行检测
###None对整个图进行检测
kp,des = surf.detectAndCompute(gray,None)
# print(des)
###绘制
cv2.drawKeypoints(gray,kp,img)
cv2.imshow('img',img)
cv2.waitKey(0)
###目前因为版权问题不可使用
ORB
两种技术组合而成:特征点检测 和 描述子的计算
优势
可以做到实时检测(做到实时性就要对数据量的缩减)
ORB = FAST + BRIEF
FAST:可以做到特征点的实时检测
BRIEF:对已检测到的特征点进行描述,他加快了特征描述符建立的速度,同时也极大的降低了特征匹配的时间
使用步揍
orb = cv2.ORB_create()
kp,des = orb.detectAndCompute(img,mask)
import cv2
import numpy as np
img = cv2.imread('C:\\Users\dazhi\Desktop\jiaodian.png')
###灰度化
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
###创建ORB对象
orb = cv2.ORB_create()
###进行检测
###None对整个图进行检测
kp,des = orb.detectAndCompute(gray,None)
###绘制
cv2.drawKeypoints(gray,kp,img)
cv2.imshow('img',img)
cv2.waitKey(0)
特征匹配
特征匹配方法
BF:暴力特征匹配方法(通过枚举)
FLANN:最快邻近区特征匹配方法
暴力特征匹配原理
它使用第一组中的每个特征的描述子(第一组指第一幅图的)与第二组中的所有特征描述子进行匹配计算他们之间的差距,然后将最接近一个(相似度最高的)匹配返回
匹配步骤
创建匹配器:BFMatcher(normType,crossCheck)
crossCheck:是否进行交叉匹配 默认为false
进行特征匹配 bf.match(des1,des2)
对两幅图的描述子进行计算
绘制匹配点 cv2.drawMatches(img1,kp1,img2,k2...)
搜索img,kp: 搜索的图和特征点
匹配图img,kp
import cv2
import numpy as np
###打开两个图片(要有交叉点)
img1 = cv2.imread('C:\\Users\dazhi\Desktop\opencv_01.png')
img2 = cv2.imread('C:\\Users\dazhi\Desktop\opencv_02.png')
###灰度化
g1 = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
g2 = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
####创建sift对象
sift = cv2.SIFT_create()
###进行检测
###None对整个图进行检测
###通过对象创建特征点和描述子
kp1,des1 = sift.detectAndCompute(g1,None)
kp2,des2 = sift.detectAndCompute(g2,None)
###创建匹配器
bf = cv2.BFMatcher(cv2.NORM_L1)
###调用匹配器的match方法,获得两幅图的匹配的点
match = bf.match(des1,des2)
###绘制
img3 = cv2.drawMatches(img1,kp1,img2,kp2,match,None)
cv2.imshow('img3',img3)
cv2.waitKey(0)
特征匹配
FLANN
在进行批量特征匹配时,FLANN速度更快
由于使用邻近近似值,所有精度较差
使用步揍
创建FLANN匹配器 FlannBasedMatcher()
index_parems字典:匹配算法KDTREE,LSH
search_params字典:指定KDTREE算法中遍历树的次数
KDTREE: index_params=dict(algorithm=FLANN_INDEX_KDTREE,trees=5)
进行特征匹配 flann.match/knnMatch()
参数为SIFT,SURF,ORB等计算的描述子
k,表示取欧式距离最近的前k个关键点
返回的是匹配的结果DMatch对象
distance 描述子之间的距离,值越低越好
queryIdx 第一个图像的描述子索引值
trainIdx 第二个图的描述子索引值
imgIdx 第二个图索引值
绘制匹配点 cv2.drawMatches/drawMatchesKnn()
import cv2
import numpy as np
###打开两个图片(要有交叉点)
img1 = cv2.imread('C:\\Users\dazhi\Desktop\opencv_01.png')
img2 = cv2.imread('C:\\Users\dazhi\Desktop\opencv_02.png')
###灰度化
g1 = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
g2 = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
###创建SIFT特征检测器
sift = cv2.SIFT_create()
###计算描述子与特征点
kp1,des1 = sift.detectAndCompute(g1,None)
kp2,des2 = sift.detectAndCompute(g2,None)
###创建匹配器
index_params = dict(algorithm = 1,trees=5)
search_params = dict(checks = 50)
flann = cv2.FlannBasedMatcher(index_params,search_params)
###对描述子进行匹配计算
matchs = flann.knnMatch(des1,des2,k=2)
good = []
for i,(m,n) in enumerate(matchs):
if m.distance < 0.7 * n.distance:
good.append(m)
###绘制
###上面使用knnMatch下面必须使用对应的
ret = cv2.drawMatchesKnn(img1,kp1,img2,kp2,[good],None)
cv2.imshow('result',ret)
cv2.waitKey(0)
图像查找
应用到 特征匹配+单应性矩阵
单应性矩阵
获取一个矩阵与图像1运算就可以得到图像2的位置,图像2通过运算可以得到原始的位置
opencv中只要将原始图片和目的图片将参数传入进去就可以获得单应性矩阵
import cv2
import numpy as np
###打开两个图片(要有交叉点)
img1 = cv2.imread('C:\\Users\dazhi\Desktop\opencv_01.png')
img2 = cv2.imread('C:\\Users\dazhi\Desktop\opencv_02.png')
###灰度化
g1 = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
g2 = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
###创建SIFT特征检测器
sift = cv2.SIFT_create()
###计算描述子与特征点
kp1,des1 = sift.detectAndCompute(g1,None)
kp2,des2 = sift.detectAndCompute(g2,None)
###创建匹配器
index_params = dict(algorithm = 1,trees=5)
search_params = dict(checks = 50)
flann = cv2.FlannBasedMatcher(index_params,search_params)
###对描述子进行匹配计算
matchs = flann.knnMatch(des1,des2,k=2)
good = []
for i,(m,n) in enumerate(matchs):
if m.distance < 0.7 * n.distance:
good.append(m)
if len(good) >= 4:
###查找单应性矩阵
#源,转换成浮点形,遍历获取匹配点,变换成无数行,每一行有一个元素,这个元素是由2个子元素组成
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)
###cv2.RANSAC匹配点进行过滤 5.0阈值,返回值 掩码不需要使用 _表示
H ,_ = cv2.findHomography(srcPts,dstPts,cv2.RANSAC,5.0)
###透视变换
###要搜索图的四个角点 .reshape():结构化
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))
else:
print("the number of good is less than 4.")
exit()
###绘制
###上面使用knnMatch下面必须使用对应的
ret = cv2.drawMatchesKnn(img1,kp1,img2,kp2,[good],None)
cv2.imshow('result',ret)
cv2.waitKey(0)
图像拼接
基础知识
步揍
读取文件并重置尺寸
根据特征点和计算描述子,得到单应性矩阵
图像变换
图像拼接并输出图像
超过(0,0)点不显示
import cv2
import numpy as np
#1读取文件,将图片设置成一样大小
#2找特征点,描述子,计算单应性矩阵
#3根据单应性矩阵对图象进行变换,然后平移
#4 拼接并输出最终结果
img1 = cv2.imread('C:\\Users\dazhi\Desktop\pinjie_01.png')
img2 = cv2.imread('C:\\Users\dazhi\Desktop\pinjie_02.png')
###将两张图片设置成同样大小
img1 = cv2.resize(img1,(640,480))
img2 = cv2.resize(img2,(640,480))
inputs = np.hstack((img1,img2))
cv2.imshow('inputs',inputs)
cv2.waitKey(0)
import cv2
import numpy as np
#1读取文件,将图片设置成一样大小
#2找特征点,描述子,计算单应性矩阵
#3根据单应性矩阵对图象进行变换,然后平移
#4 拼接并输出最终结果
##定义获取单向性矩阵接口
def get_homo(img1,img2):
#1创建特征转换对象
#2通过特征转换对象获得特征点和描述子
#3创建特征匹配器
#4进行特征匹配
#5过滤特征,找出有效的特征匹配点
sift =cv2.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_matches = 8
if len(verify_matches) > min_matches:
img1_pts = []
img2_pts = []
for m in verify_matches:
img1_pts.append(k1[m.queryIdx].pt)
img2_pts.append(k2[m.trainIdx].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()
img1 = cv2.imread('C:\\Users\dazhi\Desktop\pinjie_01.png')
img2 = cv2.imread('C:\\Users\dazhi\Desktop\pinjie_02.png')
###将两张图片设置成同样大小
img1 = cv2.resize(img1,(640,480))
img2 = cv2.resize(img2,(640,480))
inputs = np.hstack((img1,img2))
H = get_homo(img1,img2)
cv2.imshow('inputs',inputs)
cv2.waitKey(0)
import cv2
import numpy as np
#1读取文件,将图片设置成一样大小
#2找特征点,描述子,计算单应性矩阵
#3根据单应性矩阵对图象进行变换,然后平移
#4 拼接并输出最终结果
def stitch_image(img1,img2,H):
#1获得每张图的四个角点
#2对图片进行变换(但应性矩阵使图进行旋转,平移)
#3创建一张大图,将两张图拼接到一起
#4 将结果输出
#1获得每张图的四个角点
###获得原始图的高宽
h1,w1 = img1.shape[:2]
h2,w2 = img2.shape[:2]
img1_dims = np.float32([[0,0],[0,h1],[w1,h1],[w1,0]]).reshape(-1,1,2)
img2_dims = np.float32([[0,0],[0,h2],[w2,h2],[w2,0]]).reshape(-1,1,2)
###进行图像变换
img1_transform = cv2.perspectiveTransform(img1_dims,H)
print(img1_dims)
print(img2_dims)
print(img1_transform)
###图像最大x值减去最小x值就是宽度,最大y值减去最小y值就是高度
###axis=0横向拼接
result_dims = np.concatenate((img2_dims,img1_transform),axis=0)
print(result_dims) ###混合后结果
###求最大值和最小值
###所有值中的最小值,加上axis是按哪个轴进行获取数据会获得二维数组(x,y)最小值
####,ravel()转为一维 整形变换四舍五入原则最小值-0.5 最大值+0.5
[x_min,y_min] = np.int32(result_dims.min(axis=0).ravel()-0.5)
[x_max,y_max] = np.int32(result_dims.max(axis=0).ravel()+0.5)
###平移的距离
transform_dist = [-x_min,-y_min]
#[1,0,dx]
#[0,1,dy]
#[0,0,1]
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
##定义获取单向性矩阵接口
def get_homo(img1,img2):
#1创建特征转换对象
#2通过特征转换对象获得特征点和描述子
#3创建特征匹配器
#4进行特征匹配
#5过滤特征,找出有效的特征匹配点
sift =cv2.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
verify_matches=[]
for m1,m2 in matches:
if m1.distance < 0.8 * m2.distance:
verify_matches.append(m1)
###设置最小匹配数
min_matches = 8
if len(verify_matches) > min_matches:
img1_pts = []
img2_pts = []
for m in verify_matches:
img1_pts.append(k1[m.queryIdx].pt)
img2_pts.append(k2[m.trainIdx].pt)
img1_pts = np.float32(img1_pts).reshape(-1,1,2)
img2_pts = np.float32(img2_pts).reshape(-1,1,2)
H,mask = cv2.findHomography(img1_pts,img2_pts,cv2.RANSAC,5.0)
return H
else:
print('error')
exit()
img1 = cv2.imread('C:\\Users\dazhi\Desktop\pinjie_01.png')
img2 = cv2.imread('C:\\Users\dazhi\Desktop\pinjie_02.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('inputs',result_image)
cv2.waitKey(0)