系列内容:OpenCV概述与环境配置,OpenCV基础知识和绘制图形,图像的算数与位运算,图像视频的加载和显示,图像基本变换,滤波器,形态学,图像轮廓,图像直方图,车辆统计项目,特征检测和匹配,图像查找和拼接,虚拟计算器项目,信用卡识别项目,图像的分割与修复,人脸检测与车牌识别,目标追踪,答题卡识别判卷与文档ocr扫描识别,光流估计

通过特征匹配和单应性矩阵我们可以实现图像查找

基本的原理是通过特征匹配得到匹配结果,作为输入,得到单应性矩阵,再经过透视变换就能找到最终的图像.

(一)单应性矩阵

单应性(Homography)变换:可以简单理解为它用来描述物体在世界坐标系和像素坐标系之间的位置映射关系。对应的变换矩阵称为单应性矩阵。

单应性(Homography)变换,如图12.1所示:

12.1-单应性(Homography)变换

单应性(Homography)矩阵的示例,如图12.2所示:

12.2-单应性(Homography)矩阵的示例

单应性矩阵的应用

(1)把图片摆正,如图12.3所示:

12.3-单应性(Homography)矩阵的应用(把图片摆正)

(2)图片替换,如图12.4所示:

12.4-单应性(Homography)矩阵的应用(图片替换)

findHomography(srcPoints, dstPoints[, method[, ransacReprojThreshold[, mask[, maxIters[, confidence]]]]])

  • srcPoints: 源平面中点的坐标矩阵,可以是CV_32FC2类型,也可以是vector 类型
  • dstPoints: 目标平面中点的坐标矩阵,可以是CV_32FC2类型,也可以是vector 类型
  • method: 计算单应矩阵所使用的方法。不同的方法对应不同的参数,具体如下:
  • 0 - 利用所有点的常规方法
  • RANSAC - RANSAC-基于RANSAC的鲁棒算法, Random Sample Consensus, 随机抽样一致性
  • LMEDS - 最小中值鲁棒算法
  • PROSAC-基于PROSAC的鲁棒算法
  • ransacReprojThreshold: 将点对视为内点的最大允许重投影错误阈值(仅用于RANSAC和RHO方法)。若srcPoints和dstPoints是以像素为单位的,则该参数通常设置在1到10的范围内。
  • mask: 可选输出掩码矩阵,通常由鲁棒算法(RANSAC或LMEDS)设置。请注意,输入掩码矩阵是不需要设置的。
  • maxIters: RANSAC算法的最大迭代次数,默认值为2000。
  • confidence: 可信度值,取值范围为0到1.

示例代码1,暴力特征匹配:

#单应性矩阵(暴力特征匹配)
import cv2
import numpy as np
img1=cv2.imread('./search01.png')
img2=cv2.imread('./orgin01.png')
#变成灰度图片
gray1=cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
gray2=cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)

#创建特征检测对象
sift=cv2.xfeatures2d.SIFT_create()

#计算描述子
kp1,des1=sift.detectAndCompute(img1,None)
kp2,des2=sift.detectAndCompute(img2,None)

#创建特征匹配器
bf=cv2.BFMatcher(cv2.NORM_L1)
#进行匹配
# match=bf.match(des1,des2)
#除了match可以进行匹配,还有knnMatch
#一般k=2
match=bf.knnMatch(des1,des2,k=2)
print(match)
print(len(match))
print(type(match))
print(match[0][0].distance)
#图1的queryIdx和图2的trainIdx对应
print(match[0][0].queryIdx)
print(match[0][0].trainIdx)
#绘制特征匹配
# result=cv2.drawMatches(img1,kp1,img2,kp2,match,None)
#专用knnMatch的匹配结果
good=[]
for m,n in match:
    #设定阈值,距离小于对方距离的0.7倍,我们认为是好的匹配点
    if m.distance<0.8*n.distance:
        good.append(m)

if len(good)>=4:
    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))
else:
    print('the number of good is less than 4.')
    exit()

result=cv2.drawMatchesKnn(img1,kp1,img2,kp2,[good],None)

cv2.imshow('result',result)
cv2.waitKey(0)
cv2.destroyAllWindows()

单应性暴力匹配运行结果,如图12.5所示:

12.5-单应性暴力匹配运行结果

示例代码2,FLANN匹配:

#FLANN
import cv2
import numpy as np
img1=cv2.imread('./search01.png')
img2=cv2.imread('./orgin01.png')
#变成灰度图片
gray1=cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
gray2=cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)

#创建特征检测对象
sift=cv2.xfeatures2d.SIFT_create()

#计算描述子
kp1,des1=sift.detectAndCompute(img1,None)
kp2,des2=sift.detectAndCompute(img2,None)

#创建FLANN特征匹配对象
index_params=dict(algorithm=1,tree=5)
#根据经验,kdtree设置5个tree,那么checks一般设置为50
search_params=dict(checks=50)
flann=cv2.FlannBasedMatcher(index_params,search_params)

matches=flann.knnMatch(des1,des2,k=2)

# print(len(matches))
good=[]
for (m,n) in matches:
    #设定阈值,距离小于对方距离的0.7倍,我们认为是好的匹配点
    if m.distance<0.7*n.distance:
        good.append(m)

print(good)
#最少要求四个点
if len(good)>=4:
    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)
    #通过单应性矩阵,计算小图(img1)小图在大图中的对应位置.
    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)
    print(dst)
    #在大图中,把dst画出来
    cv2.polylines(img2,[np.int32(dst)],True,(0,0,255))
else:
    print('the number of good is less than 4.')
    exit()

result=cv2.drawMatchesKnn(img1,kp1,img2,kp2,matches,None)

cv2.imshow('result',result)
cv2.waitKey(0)
cv2.destroyAllWindows()

单应性FLANN匹配运行结果(由于图片是截取的,质量差,匹配效果差),如图12.6所示:

12.6-单应性FLANN匹配运行结果

(二)图像拼接

图片拼接的思路

  • 1.读图片
  • 2.灰度化处理
  • 3.计算各自的特征点和描述子
  • 4.匹配特征
  • 5.根据匹配到的特征,计算单应性矩阵
  • 6.对图片进行透视变换,
  • 7.创建一个大图.
  • 8.放入两张图

高架桥的拼接,示例代码:

#图片拼接
import cv2
import numpy as np
#读图片
img1=cv2.imread('./map1.png')
img2=cv2.imread('./map2.png')
#变成灰度图片
gray1=cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
gray2=cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
#创建sift对象
sift=cv2.xfeatures2d.SIFT_create()
#计算描述子
kp1,des1=sift.detectAndCompute(img1,None)
kp2,des2=sift.detectAndCompute(img2,None)

#创建特征匹配器
bf=cv2.BFMatcher()
matches=bf.knnMatch(des1,des2,k=2)

goods=[]
for (m,n) in matches:
    #设定阈值,距离小于对方距离的0.75倍,我们认为是好的匹配点
    if m.distance<0.75*n.distance:
        goods.append(m)

if len(goods)>=4:
    #根据DMatch对象拿到各自的特征点
    src_points=np.float32([kp1[m.queryIdx].pt for m in goods]).reshape(-1,1,2)
    dst_points=np.float32([kp2[m.trainIdx].pt for m in goods]).reshape(-1,1,2)

    #计算单应性矩阵
    #第一个图对变成第二个图的视角,计算出来的单应性矩阵
    H,_ = cv2.findHomography(src_points,dst_points,cv2.RANSAC,5)
else:
    print('not enough point number to compute homography matrix')
    exit()

#获取原始图的高宽
h1,w1=img1.shape[:2]
h2,w2=img2.shape[:2]
img1_pts=np.float32([[0,0],[0,h1-1],[w1-1,h1-1],[w1-1,0]]).reshape(-1,1,2)
img2_pts=np.float32([[0,0],[0,h2-1],[w2-1,h2-1],[w2-1,0]]).reshape(-1,1,2)

#根据前面计算出来时四个角,计算img1的四个角变换后的坐标
img1_transform=cv2.perspectiveTransform(img1_pts,H)
print(img1_pts)
# print(img1_transform)
result_pts=np.concatenate((img2_pts,img1_transform),axis=0)
print(result_pts)
print(result_pts.min(axis=0))
print(result_pts.max(axis=0))
[x_min,y_min]=np.int32(result_pts.min(axis=0).ravel()-1)
[x_max,y_max]=np.int32(result_pts.max(axis=0).ravel()+1)

#构造平移矩阵
move_matrix=np.array([[1,0,-x_min],[0,1,-y_min],[0,0,1]])
#对img1进行平移和透视变换
result_img=cv2.warpPerspective(img1,move_matrix.dot(H),(x_max-x_min,y_max-y_min))
#如果不平移img1很大一部分都显示在图框外,我们看不到.
# result_img=cv2.warpPerspective(img1,H,(x_max-x_min,y_max-y_min))
#把img2放进来
# img2_window=result_img[-y_min:-y_min+h2,-x_min:-x_min+w2]
# print(img2_window.shape)

result_img[-y_min:-y_min+h2,-x_min:-x_min+w2]=img2

cv2.imshow('img1',img1)
cv2.imshow('img2',img2)
cv2.imshow('img2_window',img2_window)
cv2.imshow('result_img',result_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

运行结果:

(1)原图img1和img2,如图12.7所示:

12.7-原图img1和img2

(2)平移变换后的result_img01,如图12.8所示:

12.8-平移变换后的result_img01

(3)平移变换后的img2_window,如图12.9所示:

12.9-平移变换后的img2_window

(4)拼接后的最终结果result_img,如图12.10所示:

12.10-拼接后的最终结果result_img
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐