python+opencv实现图像特征检测,图像拼接

摘要:

使用基于python的opencv中的sift算法检测图像中的特征点。通过knn匹配,每个关键点两个match,即最近邻与次近邻。 采用SIFT作者提出的比较最近邻距离与次近邻距离的SIFT匹配方式来筛选出最近邻远优于次近邻的匹配作为good matches。最后,根据投影映射关系,使用计算出来的单应性矩阵H进行透视变换,再进行拼接。
在这里插入图片描述

准备:

首先,准备好几个库:

import cv2
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline

注:使用的是opencv中的SIFT算法,由于涉及到专利,有些opencv版本没有这个算法。需将opencv调制4.4.0以上或3.4.2及之前的版本。

print(cv2.__version__)
4.7.0

不急,我们再看一下图片:

# 使用cv2读取
img_left = cv2.imread('1.png', 1)
img_right = cv2.imread('2.png', 1)

查看图片

plt.subplot(121)
plt.imshow(cv2.cvtColor(img_left, cv2.COLOR_BGR2RGB))
plt.subplot(122)
plt.imshow(cv2.cvtColor(img_right, cv2.COLOR_BGR2RGB))
plt.show()

在这里插入图片描述
注:cv2.cvtColor(imgA, cv2.COLOR_BGR2RGB)的作用是对cv2读取的图片进行通道变换,cv2读取的通道顺序是BGR,而matplotlib.pyplot对应的是RGB。

# 下面是没有进行通道变换的显示结果
plt.subplot(121)
plt.imshow(img_left)
plt.subplot(122)
plt.imshow(img_right)
plt.show()

在这里插入图片描述

第一步、检测关键点
def detect(image):
    # 转化为灰度图
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    # 创建SIFT生成器
    # descriptor是一个对象,这里使用的是SIFT算法
    # 由于检查出来的特征点较多,这里选取最优的1000个
    descriptor = cv2.SIFT_create(nfeatures=2000)
    # 检测特征点及其描述子(128维向量)
    kps, features = descriptor.detectAndCompute(image, None)
    return (kps,features)

看下效果:

def show_points(image):
    kps, features = detect(image)
    print(f"特征点数:{len(kps)}")
    img_left_points = cv2.drawKeypoints(image, kps, image)
    plt.figure(figsize=(10,10)) 
    plt.imshow(img_left_points)
show_points(deepcopy(img_left))

特征点数:1793
在这里插入图片描述

第二步、匹配所有特征点

使用knnMatch最近邻匹配:取一幅图像中的一个sift关键点,并找出另一幅图像中最近的前两个关键点。在这两个关键点中,如果最近的距离除以次近的距离得到的ratio少于某个阀值T,则接受这对匹配点。

def match_keypoints(kps_left,kps_right,features_left,features_right,ratio,threshold,k=50):
    """
    kpsA,kpsB,featureA,featureB: 两张图的特征点坐标及特征向量
    threshold: 阀值
    """
    # 建立暴力匹配器
    matcher = cv2.DescriptorMatcher_create("BruteForce")
    # 使用knn检测,匹配left,right图的特征点
    raw_matches = matcher.knnMatch(features_left, features_right, 2)
    print(len(raw_matches))
    matches = []  # 存坐标,为了后面
    good = [] # 存对象,为了后面的演示
    
    """
    queryIdx:测试图像的特征点描述符的下标==>img_keft
    trainIdx:样本图像的特征点描述符下标==>img_right
    distance:代表这怡翠匹配的特征点描述符的欧式距离,数值越小也就说明俩个特征点越相近。
    """
    
    
    # 筛选匹配点 方法一 featureA远优于featureB的点
    """
    for m in raw_matches:
        # 筛选条件
        if len(m) == 2 and m[0].distance < m[1].distance * ratio:
            good.append([m[0]])
            matches.append((m[0].queryIdx, m[0].trainIdx))
    """
    #end
    
    # 筛选匹配点 方法二 最优排序,选前k个
    matches_list = list(raw_matches)
    matches_list.sort(key=lambda m:(m[0].distance / m[1].distance))
    good = [[m[0]] for m in matches_list[:k]]
    matches = [(m[0].queryIdx, m[0].trainIdx) for m in matches_list[:k]]
    #end
    
    # 特征点对数大于4就够用来构建变换矩阵
    kps_left = np.float32([kp.pt for kp in kps_left])
    kps_right = np.float32([kp.pt for kp in kps_right])
    print("特征点对数",len(matches))
    if len(matches) > 4:
        # 获取匹配点坐标
        pts_left = np.float32([kps_left[i] for (i,_) in matches])
        pts_right = np.float32([kps_right[i] for (_,i) in matches])
        # 计算变换矩阵(采用ransac算法从pts中选择一部分点)
        H,status = cv2.findHomography(pts_right, pts_left, cv2.RANSAC, threshold)
        return (matches, H, good)
    return None

这里我们可以看一下匹配的点,图中只显示了一部分:

img_left = cv2.imread('1.png', 1)
img_right = cv2.imread('2.png', 1)
kps_left, features_left = detect(img_left)
kps_right, features_right = detect(img_right)
matches, H, good = match_keypoints(kps_left,kps_right,features_left,features_right,0.5,0.99)
img = cv2.drawMatchesKnn(img_left,kps_left,img_right,kps_right,good[:30],None,flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
plt.figure(figsize=(20,20))
plt.imshow(img)

在这里插入图片描述

print(H) # 单应性矩阵
[[ 3.66077113e-01 -2.46664063e-02  4.23006873e+02]
 [-2.46890577e-01  8.17757134e-01  6.32946430e+01]
 [-8.24224575e-04 -7.01709596e-05  1.00000000e+00]]
第三步、变换,拼接

获取左边图像到右边图像的投影映射关系,透视变换将左图象放在相应的位置,将图像拷贝到特定位置完成拼接。

def drawMatches(img_left, img_right, kps_left, kps_right, matches, H):
    # 获取图片宽度和高度
    h_left, w_left = img_left.shape[:2]
    h_right, w_right = img_right.shape[:2]
    """对imgB进行透视变换
    由于透视变换会改变图片场景的大小,导致部分图片内容看不到
    所以对图片进行扩展:高度取最高的,宽度为两者相加"""
    image = np.zeros((max(h_left, h_right), w_left+w_right, 3), dtype='uint8')
    # 初始化
    image[0:h_right, 0:w_right] = img_right
    """利用以获得的单应性矩阵进行变透视换"""
    image = cv2.warpPerspective(image, H, (image.shape[1], image.shape[0]))#(w,h
    """将透视变换后的图片与另一张图片进行拼接"""
    image[0:h_left, 0:w_left] = img_left
    return image

老样子,我们还是先看下效果先:

vis = drawMatches(img_left, img_right, kps_left, kps_right, matches, H)
plt.xticks([]), plt.yticks([])
plt.imshow(cv2.cvtColor(vis, cv2.COLOR_BGR2RGB))
plt.show()

在这里插入图片描述

除了明暗和边缘有点不太协调,需要处理一下,效果还可以。可以写个函数方便调用了。

# 现在该定义一个主函数了
def main(img_left,img_right, size=(20,20)):
    # 模块一:提取特征
    kps_left, features_left = detect(img_left)
    kps_right, features_right = detect(img_right)
    # 模块二:特征匹配
    matches, H, good = match_keypoints(kps_left,kps_right,features_left,features_right,0.5,0.99)
    # 模块三:透视变换-拼接
    vis = drawMatches(img_left, img_right, kps_left, kps_right, matches, H)
    # show
    plt.figure(figsize= size)
    plt.imshow(cv2.cvtColor(vis, cv2.COLOR_BGR2RGB))
    plt.show()

测试一下:

# 读取图片
img_left = cv2.imread('1.png', 1)
img_right = cv2.imread('2.png', 1)
main(img_left,img_right)

1793
141
在这里插入图片描述
换两个图片瞧瞧:

# 读取图片
img_left = cv2.imread('left.jpg', 1)
img_right = cv2.imread('right.jpg', 1)
# 查看原图
plt.subplot(121)
plt.imshow(cv2.cvtColor(img_left, cv2.COLOR_BGR2RGB))
plt.subplot(122)
plt.imshow(cv2.cvtColor(img_right, cv2.COLOR_BGR2RGB))
plt.show()
main(img_left,img_right,(22,22))

在这里插入图片描述426
118
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码地址(2023-06-10更新):
https://gitee.com/lgmee/ImageProcessing.git
> ImageMosaic.ipynb 
  • 29
    点赞
  • 136
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 42
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 42
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乔布斯砸牛顿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值