前言

图片拼接(image stitching)就是将统一场景的不同拍摄出的图片拼接到一起,如图所示

就是拼接全景图,是图片拼接的应用之一,手机拍照都有全景拍摄功能

仔细观察全景图,寻找它们相似性,图8-2的全景图可以通过缩放,旋转,射影等操作进行拼接而成,我们首先介绍几个常用的图像变换

图像变换

平移变换

平移变换通过向量 ( \mathbf{t} = (t_x, t_y) ) 实现,图像上点 ( \mathbf{p} = (i, j) ) 平移后得到新点 ( \mathbf{p}' = (i', j') ),满足: [ \mathbf{p}' = \mathbf{p} + \mathbf{t} ] 其中 ( t_x ) 和 ( t_y ) 分别表示水平和垂直方向的平移距离。

旋转变换

旋转变换绕原点逆时针旋转角度 ( \theta ),点 ( \mathbf{p} = (i, j) ) 旋转后得到 ( \mathbf{p}' = R\mathbf{p} ),旋转矩阵 ( R ) 为: [ R = \begin{bmatrix} \cos \theta & -\sin \theta \ \sin \theta & \cos \theta \end{bmatrix} ]

缩放变换

以原点为中心,沿 ( x ) 轴缩放 ( s_x ) 倍,沿 ( y ) 轴缩放 ( s_y ) 倍,点 ( \mathbf{p} = (i, j) ) 缩放后得到 ( \mathbf{p}' = S\mathbf{p} ),缩放矩阵 ( S ) 为: [ S = \begin{bmatrix} s_x & 0 \ 0 & s_y \end{bmatrix} ]

对称变换

  • 关于 ( y ) 轴对称:点 ( \mathbf{p} = (i, j) ) 变换后为 ( \mathbf{p}' = (-i, j) ),对应矩阵: [ P_y = \begin{bmatrix} -1 & 0 \ 0 & 1 \end{bmatrix} ]
  • 关于直线 ( y = x ) 对称:点 ( \mathbf{p} = (i, j) ) 变换后为 ( \mathbf{p}' = (j, i) ),对应矩阵: [ P_{y=x} = \begin{bmatrix} 0 & 1 \ 1 & 0 \end{bmatrix} ]

射影变换(透视变换)

射影变换是更一般的线性变换,可用齐次坐标表示。对于点 ( \mathbf{p} = (i, j, 1) )(齐次坐标),变换后 ( \mathbf{p}' = H\mathbf{p} ),其中 ( H ) 为 ( 3 \times 3 ) 变换矩阵: [ H = \begin{bmatrix} h_{11} & h_{12} & h_{13} \ h_{21} & h_{22} & h_{23} \ h_{31} & h_{32} & h_{33} \end{bmatrix} ] 射影变换能实现倾斜、透视等复杂几何变换。

几何相似性分析

图8-1的子图与图8-2全景图的相似性体现在:

  1. 局部与全局关系:子图通过上述变换(平移、旋转、缩放、射影)可拼接为全景图。
  2. 几何一致性:变换后的子图边缘对齐、视角连贯,满足几何约束(如特征点匹配)。
  3. 变换组合:实际拼接中常组合多种变换,例如先旋转后平移,或射影校正透视差异。

数学表达统一性

所有变换均可表示为矩阵乘法(齐次坐标下): [ \mathbf{p}' = M\mathbf{p} ] 其中 ( M ) 为对应变换矩阵。平移需扩展为仿射变换: [ M_{\text{平移}} = \begin{bmatrix} 1 & 0 & t_x \ 0 & 1 & t_y \ 0 & 0 & 1 \end{bmatrix} ]

计算变化矩阵

1.通过SIFT计算出两幅图片的特征点

2.将两幅图片的特征点进行匹配

3.更具匹配的特征点计算图片变换矩阵

利用RANSAC算法去除误匹配


当利用SIFT进行特征匹配时,有些时候可能会出现图8-6的情况。图8-6中右图绿色圆圈
内的特征点是与左图匹配的特征点,但利用SIFT匹配特征点时,会将左图中部分特征点匹配到
右图绿色圆圈之外的特征点(如红色圆圈内的特征点)。这些特征点匹配是错误的匹配,应该被
移除,从而保证变换矩阵计算的鲁棒性。应该如何移除错误的匹配点对呢?

可以用到RANSAC算法

RANSAC算法简介

RANSAC(Random Sample Consensus)是一种鲁棒的模型拟合算法,常用于处理包含大量噪声或异常值的数据。在计算机视觉中,RANSAC常用于去除特征匹配中的误匹配(outliers),仅保留满足几何约束的正确匹配(inliers)。

算法原理

RANSAC通过随机采样最小数据集迭代估计模型参数,并统计支持该模型的样本数量。算法核心思想是:正确的匹配应满足某种几何变换(如单应性矩阵或基础矩阵),而误匹配则不符合该约束。

实现步骤

输入准备

  • 两组匹配的特征点对:points1points2(形状为N×2的数组)
  • 模型类型:单应性矩阵(Homography)或基础矩阵(Fundamental Matrix)
  • 最大迭代次数:max_iterations(默认1000)
  • 内点阈值:threshold(像素距离,默认3.0)

核心流程

  1. 随机从匹配点对中选取最小样本集(如单应性矩阵需4对点)
  2. 根据样本集计算候选模型参数(如调用cv2.findHomography
  3. 统计所有点在该模型下的投影误差小于阈值的内点数量
  4. 保留内点数量最多的模型参数
  5. 重复上述过程直到达到最大迭代次数

OpenCV代码实现

import cv2
import numpy as npdef ransac_filter_matches(points1, points2, model='homography', max_iter=1000, threshold=3.0):"""points1, points2: 匹配的点坐标 (N×2 numpy数组)model: 拟合模型类型 ('homography' 或 'fundamental')"""if len(points1) < 4:return np.arange(len(points1))  # 不足4对点时返回所有索引if model == 'homography':H, mask = cv2.findHomography(points1, points2, cv2.RANSAC, threshold, maxIters=max_iter)elif model == 'fundamental':F, mask = cv2.findFundamentalMat(points1, points2, cv2.FM_RANSAC, threshold, max_iter)return mask.ravel().astype(bool)  # 返回内点掩码

参数选择建议

  • 阈值选择:通常设置为1-5像素,取决于特征点定位精度。对于SIFT/SURF等特征可设为3,ORB等二进制特征建议设为5

  • 迭代次数:默认1000次可满足大多数场景。可通过公式估算:

    $$ N = \frac{\log(1-p)}{\log(1-(1-\epsilon)^s)} $$

    其中p为置信度(如0.99),ε为异常值比例估计值,s为最小样本数

应用示例

# 假设已有匹配结果
matches = flann.knnMatch(des1, des2, k=2)
good_matches = [m for m,n in matches if m.distance < 0.7*n.distance]# 提取匹配点坐标
pts1 = np.float32([kp1[m.queryIdx].pt for m in good_matches]).reshape(-1,2)
pts2 = np.float32([kp2[m.trainIdx].pt for m in good_matches]).reshape(-1,2)# RANSAC过滤
inlier_mask = ransac_filter_matches(pts1, pts2)
final_matches = [good_matches[i] for i in range(len(good_matches)) if inlier_mask[i]]

注意事项

  • 匹配点对数量较少时(<10),RANSAC可能失效
  • 场景中存在多个运动平面时,需改用多模型拟合方法(如PEARL)
  • 对于纯旋转相机运动,建议使用单应性矩阵;一般运动建议用基础矩阵

图像变换与缝合

图像拼接的最后一步是将输入图像变换并缝合到一幅图像中。对于两幅图像A和B,在已
经检测出对应的特征点对,并利用RANSAC算法计算得到变换矩阵T之后,将图像B转换为
TB。然后,对转换后的图像,即TB,与图像A在重叠部分的像素值求平均值,以优化图像缝
合的边界。如此,便可得到最终缝合好的拼接图像。
综上所述,我们把图像拼接的全过程总结为以下4步:
(1)计算两幅图像的特征点;
(2)将两幅图像的特征点进行匹配;
(3)根据匹配的特征点对,利用RANSAC算法计算图像变换矩阵;
(4)将图像进行拼接。

代码实现

方法一:使用OpenCV内置的Stitcher类(最简单)

import cv2# 读取图像
image1 = cv2.imread('image1.jpeg')
image2 = cv2.imread('image2.jpeg')# 检查图像是否成功读取
if image1 is None or image2 is None:print("无法读取图像文件")exit()# 创建拼接器 效果:拼接结果出现了边缘黑边和形变
stitcher = cv2.Stitcher_create() if hasattr(cv2, 'Stitcher_create') else cv2.createStitcher()# 执行拼接
(status, stitched) = stitcher.stitch([image1, image2])# 保存结果
if status == cv2.Stitcher_OK:cv2.imwrite('stitched_output.jpg', stitched)print("拼接成功,结果已保存为 'stitched_output.jpg'")
else:print(f'拼接失败,错误代码: {status}')

方法二:完整实现

import cv2
import numpy as npdef stitch_images(images, ratio=0.75, reproj_thresh=4.0, show_matches=False):"""图像拼接函数参数:images: 要拼接的图像列表ratio: Lowe's ratio test参数reproj_thresh: RANSAC重投影阈值show_matches: 是否显示特征匹配结果返回:拼接后的图像"""# 初始化OpenCV的SIFT特征检测器sift = cv2.SIFT_create()# 检测关键点和描述符(kpsA, featuresA) = sift.detectAndCompute(images[0], None)(kpsB, featuresB) = sift.detectAndCompute(images[1], None)# 匹配特征点matcher = cv2.DescriptorMatcher_create("BruteForce")raw_matches = matcher.knnMatch(featuresA, featuresB, 2)# 应用Lowe's ratio test筛选好的匹配点good_matches = []for m in raw_matches:if len(m) == 2 and m[0].distance < m[1].distance * ratio:good_matches.append((m[0].trainIdx, m[0].queryIdx))# 至少需要4个匹配点才能计算单应性矩阵if len(good_matches) > 4:ptsA = np.float32([kpsA[i].pt for (_, i) in good_matches])ptsB = np.float32([kpsB[i].pt for (i, _) in good_matches])# 计算单应性矩阵(H, status) = cv2.findHomography(ptsA, ptsB, cv2.RANSAC, reproj_thresh)# 拼接图像result = cv2.warpPerspective(images[0], H, (images[0].shape[1] + images[1].shape[1], images[0].shape[0]))result[0:images[1].shape[0], 0:images[1].shape[1]] = images[1]# 如果需要显示匹配结果if show_matches:vis = np.zeros((max(images[0].shape[0], images[1].shape[0]), images[0].shape[1] + images[1].shape[1], 3), dtype=np.uint8)vis[0:images[0].shape[0], 0:images[0].shape[1]] = images[0]vis[0:images[1].shape[0], images[0].shape[1]:] = images[1]for ((trainIdx, queryIdx), s) in zip(good_matches, status):if s == 1:ptA = (int(kpsA[queryIdx].pt[0]), int(kpsA[queryIdx].pt[1]))ptB = (int(kpsB[trainIdx].pt[0]) + images[0].shape[1], int(kpsB[trainIdx].pt[1]))cv2.line(vis, ptA, ptB, (0, 255, 0), 1)cv2.imshow("Feature Matches", vis)cv2.waitKey(0)cv2.destroyAllWindows()return resultreturn None# 示例用法
if __name__ == "__main__":# 读取两张要拼接的图像image1 = cv2.imread("image1.jpeg")image2 = cv2.imread("image2.jpeg")# 确保图像读取成功if image1 is None or image2 is None:print("无法读取图像文件")exit()# 调整图像大小(可选)image1 = cv2.resize(image1, (0, 0), fx=0.5, fy=0.5)image2 = cv2.resize(image2, (0, 0), fx=0.5, fy=0.5)# 拼接图像stitched_image = stitch_images([image1, image2], show_matches=True)if stitched_image is not None:# 显示并保存结果cv2.imshow("Stitched Image", stitched_image)cv2.waitKey(0)cv2.destroyAllWindows()cv2.imwrite("stitched_result.jpg", stitched_image)else:print("图像拼接失败,可能匹配点不足")

使用建议

  • 如果只是需要快速拼接,推荐使用第一种方法(Stitcher类)
  • 如果需要了解基本原理或进行简单定制,可以使用第二种方法
  • 确保图像有足够重叠区域(建议30%以上重叠)
  • 图像大小不宜过大,可以先缩小处理

两种方法都需要安装OpenCV:

pip install opencv-python opencv-contrib-python

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/93398.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/93398.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/93398.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Web第二次作业

作业一&#xff1a;学校官网1.1学校官网代码如下&#xff1a;​<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0">&l…

【CV 目标检测】②R-CNN模型

二、R-CNN网络基础 2.R-CNN模型 2014年提出R-CNN&#xff01;网络&#xff0c;该网络不再使用暴力穷举的方法&#xff0c;而是使用候选区域方法&#xff08;region proposal method&#xff09;创建目标检测的区域来完成目标检测的任务&#xff0c;R-CNN是以深度神经网络为基础…

STM32L051C8与STM32L151C8的主要区别

STM32L051C8与STM32L151C8 有什么区别&#xff1f; LPTIM 有什么特点,为什么STM32L151C8没有LPTIM,而STM32L051C8有1个? 1. STM32L051C8与STM32L151C8的主要区别 STM32L051C8STM32L151C8内核Cortex-M0Cortex-M3主频32MHz32MHz闪存/ SRAM64KB/8KB64KB/16KB工作电压1.65V-3.6V…

【软考中级网络工程师】知识点之网关协议深度剖析

目录一、网关协议基础探秘1.1 网关协议概念1.2 网关协议作用1.3 网关协议分类总览二、内部网关协议&#xff08;IGP&#xff09;深度解析2.1 距离矢量协议2.2 链路状态协议2.3 混合型协议三、外部网关协议&#xff08;EGP&#xff09;探秘3.1 BGP 协议详解3.2 BGP 协议的关键特…

JavaScript 中 call、apply 和 bind 方法的区别与使用

一、核心作用与基础概念这三个方法都用于显式改变函数执行时的 this 指向&#xff0c;解决 JavaScript 中函数上下文动态绑定的问题。1.call()立即执行函数&#xff0c;第一个参数为 this 指向对象&#xff0c;后续参数为逗号分隔的参数列表语法&#xff1a;func.call(thisArg,…

【Android】适配器与外部事件的交互

三三要成为安卓糕手 引入&#xff1a;在上一篇文章中我们完成了新闻展示页面多布局案例的展示&#xff0c;感悟颇多&#xff0c;本篇文章&#xff0c;继续去开发一些新的功能 一&#xff1a;关闭广告 所有的view都可以和我们的用户做交互&#xff0c;循环视图中也给我们提供了相…

MySQL的分析查询语句(EXPLAIN):

目录 基本语法&#xff1a; 各个字段的含义&#xff1a; id&#xff1a; select_type&#xff1a; table&#xff1a; partitions&#xff1a; type&#xff1a; possible_keys&#xff1a; key&#xff1a; key_len&#xff1a; ref&#xff1a; row&#xff1a; …

C++ #if

在 C 中&#xff0c;#if 是 预处理器指令&#xff08;Preprocessor Directive&#xff09;&#xff0c;用于 条件编译&#xff0c;即在编译阶段根据条件决定是否包含某段代码。它通常与 #define、#ifdef、#ifndef、#else 和 #endif 配合使用。基本语法#if 条件表达式// 如果条件…

方案 | 动车底部零部件检测实时流水线检测算法改进

项目背景随着我国高速铁路运营里程突破4.5万公里&#xff0c;动车组日均开行超过8000列次&#xff0c;传统人工巡检方式已无法满足密集运行下的安全检测需求。车底关键部件如制动系统、悬挂装置、牵引电机等长期承受高强度振动和冲击&#xff0c;易产生疲劳裂纹、螺栓松动、部件…

企业收款统计:驱动业务决策的核心引擎设计开发——仙盟创梦IDE

代码完整代码<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><title>黑金风格职员统计</title><style>/* 页面基础样式 - 黑金风格 */body {font-family: Segoe UI, Tahoma, Geneva, Verdana, …

CIAIE 2025上海汽车内外饰展观察:从美学到功能的产业跃迁

在智能化、电动化浪潮推动下&#xff0c;汽车产业的市场格局、技术路线、供应链结构与用户体验正被系统性重塑。汽车感知空间核心的“内外饰件”&#xff0c;正从原本的结构性、功能性部件&#xff0c;逐步跃升为智能化、情感化和差异化体验的重要承载载体&#xff0c;开启了从…

Spring IOC容器在Web环境中的启动奥秘:深入源码解析

一、为何需要关注IOC容器启动&#xff1f;在Java Web开发中&#xff0c;Spring MVC框架的基石正是IOC容器。但你是否思考过&#xff1a;独立的IOC模块如何与Tomcat等Servlet容器协同工作&#xff1f; 其启动过程与Web容器的生命周期深度绑定&#xff0c;这是构建稳定Spring应用…

前端JS处理时间,适用于聊天、操作记录等(包含刚刚、x分钟前、x小时前、x天前)

export default {// 首页时间转化formatDate(val) {var nowDate new Date()var oldDate new Date(val)const Y oldDate.getFullYear()const M oldDate.getMonth() 1const D oldDate.getDate()var diff nowDate.getTime() - oldDate.getTime()var minutes Math.floor(di…

C#---StopWatch类

老方法&#xff0c;想要全面了解和学习一个类必先看文档 微软文档 1.StopWatch 提供一组方法和属性&#xff0c;可用来测量运行时间。 1.1 属性和方法 属性&#xff1a; 方法&#xff1a; 1.2 使用 using System.Diagnostics;namespace Study04_反射专题 {internal cla…

3DTiles转OSGB格式逆向转换方法研究

一、概述 在倾斜摄影的应用领域中&#xff0c;3DTiles与OSGB格式的互转是常见的技术需求。作为专业的GIS处理平台&#xff0c;GISBox凭借其先进的倾斜摄影反切功能&#xff0c;为用户提供了高效、稳定的跨格式数据转换解决方案。 二、3DTiles转OSGB的意义 保留原始几何与纹理…

【门诊进销存出入库管理系统】佳易王医疗器械零售进销存软件:门诊进销存怎么操作?系统实操教程 #医药系统进销存

前言&#xff1a; &#xff08;一&#xff09;试用版获取方式 资源下载路径&#xff1a;进入博主头像主页第一篇文章末尾&#xff0c;点击卡片按钮&#xff1b;或访问左上角博客主页&#xff0c;通过右侧按钮获取详细资料。 说明&#xff1a;下载文件为压缩包&#xff0c;使用…

华为交换机配置文件的相关命令和用法

文章目录一、基本配置命令一、基本配置命令 1、查看当前运行的配置文件 <Huawei>display current-configuration2、配置文件保存 <Huawei>save <Huawei>save vrpcfg-20250623.zip #保存为指定文件名3、查看保存的配置 <Huawei>display saved-configu…

【汽车标定数据】动态优先级线程池在异步多文件解析中的应用

目录 一、需求背景 项目背景&#xff1a;电控数据管理系统优化 优化方案&#xff1a;引入OLAP数据库和动态线程池 线程池性能急需解决的问题 资源过载与闲置的平衡&#xff1a; 优先级处理与公平性&#xff1a; 任务类型适配性&#xff1a; 二、线程池介绍 2.1、线程池…

Unity人形角色IK优化指南

目录 Unity中人形角色的IKI 站立、奔跑IK 1. 接触面法线 2. 调整质心位置 3. 保持原本朝向 攀爬IK 1. 四肢贴合 2. 保持身体与攀爬面的距离 3. 适应外拐角 瞄准IK 1. 头部朝向 2. 手臂朝向 尾声 本文将尝试仅使用Untiy内置的Animator来解决常见的几种运动所需的IK…

基于wireshark的USB 全速硬件抓包工具USB Sniffer Lite的使用

1、前言 随着MCU的发展和需求的增多&#xff0c;USB已成为主流MCU的标配外设&#xff0c;但很多还是全速或低速IP&#xff0c;因此往往用不上高速抓包设备。 2、安装wireshark和拷贝抓包插件 将抓包插件拷贝到wireshark的extcap目录里&#xff0c;可参考基于wireshark的USB …