OpenCV实战-全景图像拼接

代码地址见文末

实现效果


1. 项目背景

随着计算机视觉技术的不断发展,图像拼接技术已被广泛应用于虚拟现实、地图生成、全景摄影等领域。图像拼接(Image Stitching)旨在将多张部分重叠的图像无缝拼接成一幅完整的全景图像。此任务要求图像处理系统能够从不同角度获取的图像中识别出匹配的特征点,并通过计算视角变换将它们对齐。

本项目实现了一个基于OpenCV的图像拼接算法,使用SIFT特征提取和RANSAC算法来估计图像之间的变换矩阵,进而拼接多个图像生成全景图像。该技术可用于大规模图像拼接、三维建模以及图像增强等应用场景。


2. RANSAC算法

RANSAC(RANdom SAmple Consensus)是一种迭代方法,常用于估算模型参数,尤其是在数据中包含噪声时。RANSAC算法通过以下步骤找到最优解:

  1. 从数据中随机选择一个小的样本集。
  2. 根据该样本集计算模型参数。
  3. 使用计算得到的模型参数,评估所有数据点,找到符合模型的内点。
  4. 计算内点的数量,并与当前最优模型比较,更新最优模型。
  5. 重复上述过程,直到达到最大迭代次数或找到最优解。

在图像拼接中,RANSAC用于计算图像间的变换矩阵(即视角变换矩阵),它能够有效地剔除匹配过程中由于噪声或错误匹配产生的外点。


3. 主要步骤及其目的

步骤 1:特征点检测与描述
  • 目的:从输入的两幅图像中提取出关键点,并为每个关键点计算其描述符。特征描述符可以帮助我们在图像中找到相似的区域,从而找到匹配的特征点。

    关键操作

    • 使用SIFT算法(尺度不变特征转换)检测关键点并计算特征描述符。
步骤 2:特征点匹配
  • 目的:通过暴力匹配器(Brute Force Matcher)将两幅图像中的特征点进行匹配。匹配过程中使用KNN算法(K-最近邻),并应用比值测试来筛选出高质量的匹配。

    关键操作

    • 使用KNN匹配(K=2)并根据比值测试筛选匹配对。
步骤 3:RANSAC算法计算视角变换矩阵
  • 目的:利用RANSAC算法计算匹配点的视角变换矩阵(单应性矩阵)。这个矩阵能够将其中一幅图像转换到与另一幅图像对齐的位置。

    关键操作

    • 使用cv2.findHomography()计算变换矩阵,并通过RANSAC剔除错误匹配点。
步骤 4:图像变换与拼接
  • 目的:使用计算出的视角变换矩阵对其中一幅图像进行透视变换(即图像扭曲),使得两幅图像能够无缝拼接。

    关键操作

    • 使用cv2.warpPerspective()对图像进行视角变换。
    • 将两幅图像拼接在一起,得到全景图像。
步骤 5:结果展示
  • 目的:展示拼接结果,并且如果需要,还可以展示图像之间的匹配过程和结果。

    关键操作

    • 使用cv2.imshow()展示拼接后的全景图像。
    • 如果需要,展示匹配的关键点对。

4. 相关代码片段

以下是本项目中实现图像拼接的关键代码:

import numpy as np
import cv2

class Stitcher:

    # 拼接函数
    def stitch(self, images, ratio=0.75, reprojThresh=4.0, showMatches=False):
        # 获取输入图片
        (imageB, imageA) = images

        # 检测A、B图片的SIFT关键特征点,并计算特征描述子
        (kpsA, featuresA) = self.detectAndDescribe(imageA)
        (kpsB, featuresB) = self.detectAndDescribe(imageB)

        # 匹配两张图片的所有特征点,返回匹配结果
        M = self.matchKeypoints(kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh)

        # 如果返回结果为空,没有匹配成功的特征点,退出算法
        if M is None:
            return None

        # 否则,提取匹配结果
        # H是3x3视角变换矩阵      
        (matches, H, status) = M

        # 将图片A进行视角变换,result是变换后图片
        result = cv2.warpPerspective(imageA, H, (imageA.shape[1] + imageB.shape[1], imageA.shape[0]))
        self.cv_show('result', result)

        # 将图片B传入result图片最左端
        result[0:imageB.shape[0], 0:imageB.shape[1]] = imageB
        self.cv_show('result', result)

        # 检测是否需要显示图片匹配
        if showMatches:
            # 生成匹配图片
            vis = self.drawMatches(imageA, imageB, kpsA, kpsB, matches, status)
            # 返回结果
            return (result, vis)

        # 返回匹配结果
        return result

    # SIFT特征提取和描述
    def detectAndDescribe(self, image):
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        descriptor = cv2.xfeatures2d.SIFT_create()
        (kps, features) = descriptor.detectAndCompute(image, None)
        kps = np.float32([kp.pt for kp in kps])
        return (kps, features)

    # 特征点匹配
    def matchKeypoints(self, kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh):
        matcher = cv2.BFMatcher()
        rawMatches = matcher.knnMatch(featuresA, featuresB, 2)
        matches = []
        for m in rawMatches:
            if len(m) == 2 and m[0].distance < m[1].distance * ratio:
                matches.append((m[0].trainIdx, m[0].queryIdx))
        if len(matches) > 4:
            ptsA = np.float32([kpsA[i] for (_, i) in matches])
            ptsB = np.float32([kpsB[i] for (i, _) in matches])
            (H, status) = cv2.findHomography(ptsA, ptsB, cv2.RANSAC, reprojThresh)
            return (matches, H, status)
        return None

    # 可视化匹配结果
    def drawMatches(self, imageA, imageB, kpsA, kpsB, matches, status):
        (hA, wA) = imageA.shape[:2]
        (hB, wB) = imageB.shape[:2]
        vis = np.zeros((max(hA, hB), wA + wB, 3), dtype="uint8")
        vis[0:hA, 0:wA] = imageA
        vis[0:hB, wA:] = imageB
        for ((trainIdx, queryIdx), s) in zip(matches, status):
            if s == 1:
                ptA = (int(kpsA[queryIdx][0]), int(kpsA[queryIdx][1]))
                ptB = (int(kpsB[trainIdx][0]) + wA, int(kpsB[trainIdx][1]))
                cv2.line(vis, ptA, ptB, (0, 255, 0), 1)
        return vis

    # 显示图片
    def cv_show(self,name,img):
        cv2.imshow(name, img)
        cv2.waitKey(0)
        cv2.destroyAllWindows()


通过网盘分享的文件:opencv
链接: https://pan.baidu.com/s/1FHrtVqgysy_mtBIiVg9X-A?pwd=r2k7 提取码: r2k7 
压缩包中包含的具体内容: 对给定数据中的6个不同场景图像,进行全景拼接操作,具体要求如下: (1) 寻找关键点,获取关键点的位置和尺度信息(DoG检测子已由KeypointDetect文件夹中的detect_features_DoG.m文件实现;请参照该算子,自行编写程序实现Harris-Laplacian检测子)。 (2) 在每一幅图像中,对每个关键点提取待拼接图像的SIFT描述子(编辑SIFTDescriptor.m文件实现该操作,运行EvaluateSIFTDescriptor.m文件检查实现结果)。 (3) 比较来自两幅不同图像的SIFT描述子,寻找匹配关键点(编辑SIFTSimpleMatcher.m文件计算两幅图像SIFT描述子间的Euclidean距离,实现该操作,运行EvaluateSIFTMatcher.m文件检查实现结果)。 (4) 基于图像中的匹配关键点,对两幅图像进行配准。请分别采用最小二乘方法(编辑ComputeAffineMatrix.m文件实现该操作,运行EvaluateAffineMatrix.m文件检查实现结果)和RANSAC方法估计两幅图像间的变换矩阵(编辑RANSACFit.m 文件中的ComputeError()函数实现该操作,运行TransformationTester.m文件检查实现结果)。 (5) 基于变换矩阵,对其中一幅图像进行变换处理,将其与另一幅图像进行拼接。 (6) 对同一场景的多幅图像进行上述操作,实现场景的全景拼接(编辑MultipleStitch.m文件中的makeTransformToReferenceFrame函数实现该操作)。可以运行StitchTester.m查看拼接结果。 (7) 请比较DoG检测子和Harris-Laplacian检测子的实验结果。图像拼接的效果对实验数据中的几个场景效果不同,请分析原因。 已经实现这些功能,并且编译运行均不报错!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

樱花的浪漫

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

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

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

打赏作者

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

抵扣说明:

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

余额充值