目录
[1. 特征检测与描述(Feature Detection & Description)](#1. 特征检测与描述(Feature Detection & Description))
[2. 特征匹配(Feature Matching)](#2. 特征匹配(Feature Matching))
[3. 图像配准(Image Registration)](#3. 图像配准(Image Registration))
[4. 图像变换与投影(Warping)](#4. 图像变换与投影(Warping))
[5. 图像融合(Blending)](#5. 图像融合(Blending))
[1、 匹配方法](#1、 匹配方法)
(3)创建特征检测函数detectAndDescribe()
(5)调用detectAndDescribe()函数,获得两个图像的关键点信息,关键点坐标、描述符信息
(7)绘制匹配成功的关键点之间的连线,使用drawMatchesKnn()匹配方法
OpenCV中的图像拼接(Image Stitching)主要基于计算机视觉和图像处理技术,将多张具有重叠区域的图像拼接成一张全景图。其核心原理可以分为以下几个步骤:
一、图像拼接的原理过程
1. 特征检测与描述(Feature Detection & Description)
-
目标:在不同图像中检测关键特征点(如角点、边缘等),并生成特征描述子。
-
常用算法:
-
SIFT(尺度不变特征变换):对旋转、尺度、光照变化具有鲁棒性。
-
SURF(加速版SIFT):计算速度更快。
-
ORB(Oriented FAST and Rotated BRIEF):轻量级,适合实时应用。
-
-
输出:每张图像的特征点坐标及其对应的描述子(特征向量)。
-
过程:图像拼接的第一步是检测并描述图像中的关键特征点。通过使用如SIFT、SURF或ORB等算法,系统能够提取对尺度、旋转和光照变化具有鲁棒性的特征点(如角点或边缘)。这些算法不仅定位特征点的位置,还会生成对应的特征描述子(如128维的SIFT向量),用于后续的特征匹配。例如,SIFT通过高斯差分金字塔检测极值点并赋予方向,ORB则结合FAST关键点检测和BRIEF二进制描述子以提升效率,从而适应不同场景的需求。下图就是通过sift特征提取找到图像中特征相同的位置。
2. 特征匹配(Feature Matching)
-
目标:在不同图像的特征点之间建立对应关系。
-
方法:
-
暴力匹配(Brute-Force Matcher):直接比较所有特征描述子的距离(如欧式距离或汉明距离)。
-
FLANN(快速最近邻搜索):适合大规模数据集,效率更高。
-
-
筛选策略:
-
比率测试(Ratio Test):保留匹配距离比值(最近邻/次近邻)小于阈值的匹配对。
-
RANSAC(随机采样一致性):通过迭代剔除误匹配,估计最优几何变换模型(如单应性矩阵)。
-
-
过程:在获取特征点后,需在不同图像间建立特征点的对应关系。暴力匹配法直接计算所有特征描述子间的距离(如欧氏距离或汉明距离),而FLANN通过构建高效的数据结构加速最近邻搜索。为排除误匹配,通常会采用比率测试(保留最近邻与次近邻距离比值小于阈值的结果)和RANSAC算法。RANSAC通过随机采样一致性迭代估计最优单应性矩阵,同时剔除不符合几何约束的异常点,确保匹配的准确性。
3. 图像配准(Image Registration)
-
目标:计算图像间的几何变换关系,对齐图像。
-
单应性矩阵(Homography Matrix):
-
描述两个平面之间的透视变换关系(3×3矩阵)。
-
通过匹配的特征点对求解(至少需要4对点)。
-
使用 RANSAC 或 LMEDS 算法鲁棒地估计单应性矩阵。
-
-
变换模型:
若相机仅旋转(无平移),单应性矩阵能完美对齐图像。若存在视差(如平移),可能需要更复杂的模型(如Bundle Adjustment)。
-
过程:配准的目标是计算图像间的几何变换关系,通常使用单应性矩阵(3×3矩阵)描述平面间的透视变换。通过至少4对匹配点可求解该矩阵,RANSAC在此过程中进一步优化模型的鲁棒性。若相机仅绕光心旋转(如全景拍摄),单应性矩阵能精确对齐图像;若存在视差(如平移拍摄),则需引入光束法平差(Bundle Adjustment)等优化方法,联合调整多图像的相机参数以减少投影误差。
4. 图像变换与投影(Warping)
-
目标:将图像投影到同一坐标系(全景画布)。
-
方法:
-
使用
cv2.warpPerspective()
对图像应用单应性矩阵变换。 -
根据所有图像的变换结果,计算全景图的尺寸和偏移量。
-
-
优化:
-
曝光补偿:调整不同图像的亮度差异。
-
相机参数优化:若已知相机参数(如焦距),可提升对齐精度。
-
-
配准后,需将图像投影到统一坐标系以构建全景画布。使用
cv2.warpPerspective()
对图像应用单应性变换,并根据所有图像的变换结果计算全景图的尺寸和偏移量。例如,若将第二张图像投影到第一张的坐标系中,需扩展画布以容纳所有像素。此外,需处理因曝光差异导致的亮度不一致问题,可能通过直方图匹配或全局优化调整颜色平衡。
5. 图像融合(Blending)
-
目标:消除拼接处的缝隙和光照差异,实现平滑过渡。
-
常用方法:
-
简单混合(Alpha Blending):在重叠区域对像素进行线性加权平均。
-
多频段融合(Multi-Band Blending):
-
将图像分解为不同频率的子带(如拉普拉斯金字塔)。
-
对各频段分别融合,避免高频细节(如边缘)错位。
-
-
光照一致性处理:调整重叠区域的亮度和颜色。
-
-
最后一步是消除拼接缝隙与光照差异。简单线性混合(Alpha Blending)在重叠区域对像素加权平均,但可能导致模糊。多频段融合则通过拉普拉斯金字塔分解图像,对不同频率的子带分别融合:低频(如光照)平滑过渡,高频(如边缘)保留细节,从而避免重影和错位。对于动态物体(如行人),可通过分割掩模或运动检测排除干扰,进一步提升视觉效果。
二、图像拼接的简单实现
1、 匹配方法
特征匹配的方法: 关键点A与找到的两个关键点 X、Y的欧氏距离分别 d1、d2,且d1<d2。 欧氏距离(关键点A,关键点X)=d1。 欧氏距离(关键点A,关键点Y)=d2。 (1)d1<d2,比值较大:可能不是匹配点,通常是由噪声引起的。 (2)d1<d2,比值较小:是匹配点。
2、实现上述两个图片的拼接
(1)导入opencv的库以及sys库
import cv2
import numpy as np
import sys
(2)定义cv_show()函数
在做opencv项目时,如果想显示一张图片,总会使用到cv2.imshow()和cv2.waitKey()这两个函数,因此,为了方便将该功能封装成一个小的函数方便使用。
def cv_show(name,value):
cv2.imshow(name,value)
cv2.waitKey(0)
(3)创建特征检测函数detectAndDescribe()
def detectAndDescribe(image):
#将输入的图片转化为灰度图
gray=cv2.cvtColor(image,cv2.COLOR_BGRA2GRAY)
#创建SIFT特征检测器
describe=cv2.SIFT_create()
#使用SIFT特征检测器,调用detectAndCompute()函数,获得关键特征点的坐标kps和描述符des
(kps,des)=describe.detectAndCompute(gray,None)
#使用列表生成式将获得到的坐标信息转化为32位的浮点数
kps_float=np.float32([kp.pt for kp in kps])
#返回关键点、关键点浮点数坐标、描述符
return (kps,kps_float,des)
(4)读取待拼接的两张图片并显示在窗口中
imageA=cv2.imread("../data/imageA.jpg")
cv_show("imageA",imageA)
imageB=cv2.imread("../data/imageB.jpg")
cv_show("imageB",imageB)
(5)调用detectAndDescribe()函数,获得两个图像的关键点信息,关键点坐标、描述符信息
#计算图片特征点及描述符
(kpsA,kps_floatA,desA)=detectAndDescribe(imageA)
(kpsB,kps_floatB,desB)=detectAndDescribe(imageB)
(6)创建暴力匹配器,进行关键点之间的匹配
这里的匹配器会在指纹识别、指验证项目中详细讲解,其中有关Knnmatch()为什么要选择两个点会详细讲解。
#建立暴力匹配器BFmatcher,在匹配大训练集和时使用flannbasematcher
matcher=cv2.BFMatcher()
#使用KNN匹配器,使用B去匹配图片A,每次会选取两个结果,一个最近的点一个次近邻的点
rawMatches=matcher.knnMatch(desB,desA,2)
good=[]
matches=[]
for m in rawMatches:
if len(m)==2 and m[0].distance < 0.65* m[1].distance:
good.append(m)
matches.append((m[0].queryIdx,m[0].trainIdx))
print(len(good))
print(matches)
这里进行条件判断,判断m的长度是否为2,m是对rawMatches的遍历,所以取到的每个值都会有两个点的信息,所以长度为2,当某一个图中匹配的关键点缺失时就会出现长度小于2的情况。
并且如果最近点距离当前点的距离小于次近点的距离当前点距离的0.65,则认定这个点是一个比较优秀的点,并将该点保存到good这个列表中,将最近点的两个索引值放到matches这个列表中,这两个索引分别代表,因为是使用图片B去匹配图片A中的点,所以这里表示的是第一个索引queryIdx表示匹配图片中关键点的索引,第二个索引trainIdx则是待匹配的图片A中的关键点索引。
(7)绘制匹配成功的关键点之间的连线,使用drawMatchesKnn()匹配方法
vis=cv2.drawMatchesKnn(imageB,kpsB,imageA,kpsA,good,None,flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv_show('Keypoint Matches',vis)

(8)透视变换
因为图片拍摄角度的不同所以,使用透视变换将图片扶正,将图片转换成同一个弯曲程度的图像
"""透视变换"""
if len(matches)>4:
ptsB=np.float32([kps_floatB[i] for (i,_) in matches])
ptsA=np.float32([kps_floatA[i] for (_,i) in matches])
(H,mask)=cv2.findHomography(ptsB,ptsA,cv2.RANSAC,10)
else:
print("图片未找到四个以上的匹配点")
sys.exit()
result=cv2.warpPerspective(imageB,H,(imageB.shape[1]+imageA.shape[1],imageB.shape[0]))
cv_show('resultB',result)

因为透视变换是四个点进行匹配,所以这里使用条件判断,当图片A和B之间有四个以上的匹配点时则依次取出图片A和B中的坐标点信息,matches这个列表中,这两个索引分别代表,因为是使用图片B去匹配图片A中的点,所以这里表示的是第一个索引queryIdx表示匹配图片B中关键点的索引,第二个索引trainIdx则是待匹配的图片A中的关键点索引。因此上面使用列表生成式分别取出图片A和B中的关键点,findHomography函数计算变化矩阵,H是变化矩阵,mask是指外点和内点值,图片的宽变成两图片的宽相加。
(9)将图片A填充到指定位置
result[0:imageA.shape[0],0:imageA.shape[1]]=imageA
cv_show('result',result)
