图片拼接简介与实验

一、 背景

VR借助计算机等设备产生一个逼真的三维视觉、触觉、嗅觉等多种感官体验的虚拟世界,从而使处于虚拟世界中的人产生一种身临其境的感觉。随着社会生产力和科学技术的不断发展,各行各业对VR技术的需求日益旺盛。VR技术也取得了巨大进步,并逐步成为一个新的科学技术领域。而VR所展现的360度观感的前身正是全景图。全景图通过广角的表现手段以及绘画相片视频三维模型等形式,尽可能多表现出周围的环境。360度全景,即通过对专业相机捕捉整个场景的图像信息或者使用建模软件渲染过后的图片,使用软件进行图片拼合,并用专门的播放器进行播放,即将平面照片或者计算机建模图片变为360 度全观,用于虚拟现实浏览,把二维的平面图模拟成真实的三维空间,呈现给观赏者。

对两张图片进行拼接是实现全景图展示的基本途径。为此,我们接下来主要讨论对两张图片进行拼接处理。

二、 图片的特征概述

1、颜色特征

在图像处理领域,图像的颜色特征是一个非常重要的特征,有时候根据图像的颜色特征就可以得出一些非常重要的信息。自然界常见的各种颜色光,都是由红( R)、绿(G)、蓝(B)三种颜色光按不同比例相配而成,同样绝大多数颜色也可以分解成红、绿、蓝三种色光,这就是色度学中最基本的原理—三基色原理。256级的RGB色彩总共能组合出约1678万种色彩,即256×256×256=16777216。通常也被简称为1600万色或千万色。也称为24位色(2的24次方)。除了RGB彩色图像表示法,还有HSV表示法。HSV是一种将RGB色彩空间中的点在倒圆锥体中的表示方法。HSV即色相(Hue)、饱和度(Saturation)、明度(Value),又称HSB(B即Brightness)。色相是色彩的基本属性,就是平常说的颜色的名称,如红色、黄色等。饱和度(S)是指色彩的纯度,越高色彩越纯,低则逐渐变灰,取0-100%的数值。明度(V),取0-max(计算机中HSV取值范围和存储的长度有关)。HSV颜色空间可以用一个圆锥空间模型来描述。圆锥的顶点处,V=0,H和S无定义,代表黑色。圆锥的顶面中心处V=max,S=0,H无定义,代表白色。

img

2、边缘特征

图像边缘是图像的重要特征,是图像中特性(如像素灰度、纹理等)分布的不连续处,图像周围特性有阶跃变化或屋脊状变化的那些像素集合。图像的边缘部分集中了图像的大部分信息,一幅图像的边缘结构与特点往往是决定图像特质的重要部分。图像边缘的另一个定义是指其周围像素灰度变化不连续的那些像素的集合。边缘广泛存在于物体与背景之间、物体与物体之间,因此,边缘是图像分割、图像理解及图像识别的重要特征。图像边缘检测主要用于增强图像中的轮廓边缘、细节以及灰度跳变部分,形成完整的物体边界,达到将物体从图像中分离出来或将表示同一物体表面的区域检测出来的目的。目前为止最通用的方法是检测亮度值的不连续性,用一阶和二阶导数检测的。常见的有微分法、差分边缘检测方法
、Roberts边缘检测算子、Sobel边缘检测算子、Prewitt边缘检测算子、拉普拉斯边缘检测算子等。

3、Harris角点

角点检测又称为特征点检测,是图像处理和计算机视觉中用来获取局部特征点的一类方法,广泛应用于运动检测、图像匹配、视频跟踪等领域。角点可以简单地定义为轮廓之间的交点,严格地定义是在两个主方向上的特征点,即在两个方向上灰度变化剧烈。通常具有以下特征:角点附近的像素点不论在梯度方向上还是梯度幅值上都存在着较大的变化、对于某一场景,当视角发生变化时,其任具备稳定性质的特征。角点检测的基本思想就是用固定窗口在图像上沿各个方向进行滑动,比较滑动前后窗口中像素点的灰度变化,如果在任意方向上滑动窗口内都存在较大的灰度变化,则认为该窗口中存在角点;如果任何方向上都不变化,则是均匀区域;如果灰度只在一个方向上变化,则可能是图像边缘。

img img

4、SIFT特征:

SIFT,(Scale-invariant feature transform,SIFT),尺度不变特征转换。是用于图像处理领域的一种特征描述,具有旋转不变性、尺度不变性、亮度变化保持不变性,也就是说在图片发生旋转、伸缩、明暗变化时,图片的SIFT特征都保持稳定。与HOG在整幅图像上均匀地提取梯度方向统计特征不同,SIFT是一种局部特征,可在图像中检测出关键点,SIFT特征提取分为在图片上寻找关键点和提取关键点邻域信息两部分,在提取特征时只关注稳定的关键点及其附近的信息,使得特征更加具有描述性。其应用范围包含物体辨识、机器人地图感知与导航、影像缝合、3D模型建立、手势辨识、影像追踪和动作比对。局部影像特征的描述与侦测可以帮助辨识物体,SIFT算法的实质是在不同的尺度空间上查找关键点(特征点),并计算出关键点的方向。SIFT所查找到的关键点是一些十分突出,不会因光照,仿射变换和噪音等因素而变化的点,如角点、边缘点、暗区的亮点及亮区的暗点等。

特征点描述是希望通过一种统一的数据方式,例如特征向量或者特征矩阵等,对图像中特征点方向、纹理等信息进行描述,便于后续不同图像间进行匹配和比较。由于SIFT算法的特征点描述方法考虑的是关键点领域内的梯度信息,既包含了梯度方向信息也包含了这些梯度方向的几何分布关系信息,这相当于对图像关键点处纹理信息进行了描述。由于关键点的提取考虑了尺度变化以及模糊程度变化,因此在这些关键点的信息同样具备不变性。生成了包含关键点信息的128维特征向量后,既可以直接计算不同图像关键点的特征向量之间的距离进行匹配,也可以将该特征向量用于后续其他特征提取和特征匹配方法,例如虹膜识别中将该向量进行了后续的序特征提取,进一步提高了特征的描述能力,这体现了SIFT算法的可拓展性。

img

三、 图片拼接方法与思路

1、SIFT特征提取算法:

SIFT特征是基于物体上的一些局部外观的兴趣点而与影像的大小和旋转无关。对于光线、噪声、些微视角改变的容忍度也相当高。基于这些特性,它们是高度显著而且相对容易撷取,在母数庞大的特征数据库中,很容易辨识物体而且鲜有误认。使用SIFT特征描述对于部分物体遮蔽的侦测率也相当高,甚至只需要3个以上的SIFT物体特征就足以计算出位置与方位。在现今的电脑硬件速度下和小型的特征数据库条件下,辨识速度可接近即时运算。SIFT特征的信息量大,适合在海量数据库中快速准确匹配。

img

SIFT特征检测主要有4个主要步骤:

1、尺度空间的极值检测 搜索所有尺度空间上的图像,通过高斯微分函数来识别潜在的对尺度和选择不变的兴趣点;

2、特征点定位,在每个候选的位置上,通过一个拟合精细模型来确定位置尺度,关键点的选取依据他们的稳定程度;

3、特征方向赋值,基于图像局部的梯度方向,分配给每个关键点位置一个或多个方向,后续的所有操作都是对于关键点的方向、尺度和位置进行变换,从而提供这些特征的不变性;

4、特征点描述,在每个特征点周围的邻域内,在选定的尺度上测量图像的局部梯度,这些梯度被变换成一种表示,这种表示允许比较大的局部形状的变形和光照变换。

如下图所示是使用python编写的SIFT特征检测算法:

img

2、图片特征匹配算法:

BF匹配,这一方法即暴力搜索法,它选择第一个集合中一个特征的描述符,计算与第二个集合中的所有其他特征描述符之间的距离,返回最接近的一个。Flann匹配算法:FLANN,即近似最近邻的快速库(Fast Library for Approximate Nearest Neighbors)。它包含一系列算法,这些算法针对大型数据集中的快速最近邻搜索(KD树)和高维特征进行了优化。对于大型数据集,它比BFMatcher工作得更快。

如下图所示是使用python编写的SIFT特征匹配算法:

img

3、图片视角变换及拼接算法:

透视变换本质是将图像投影到一个新的视平面。仿射变换可以理解为透视变换的特殊形式。利用透视中心、像点、目标点三点共线的条件,按透视旋转定律使承影面(透视面)绕迹线(透视轴)旋转某一角度,破坏原有的投影光线束,仍能保持承影面上投影几何图形不变的变换。如图所示;

img

对于透视投影,一束平行于投影面的平行线的投影可保持平行,而不平行于投影面的平行线的投影会聚集到一个点,该点称为灭点。当在行驶过程中,看向远处,马路两边的白线会向交于一点。数学原理:

img

其中,(x,y)是原图坐标,(x’,y’)是变换后的坐标;m11,m12,m21,m22,m31,m32为旋转量,m13,m23,m33为平移量。因为透视变换是非线性的,所以不能齐次性表示;透视变换矩阵为3*3。

如下图所示是使用python编写的图片视角变换和相应的拼接显示算法:

img

img

四、 实验结果与评价

将上述几部分算法综合起来,封装成一个类,调用相应函数即可

img

如下图所示是需要进行图片拼接的图片1和图片2,可见图片1的左半部分与图片2的右半部分是有相识的地方的,这部分的多数SIFT特征是大致相似的。

imgimg

图片1的SIFT特征检测结果如下:

img

图片2的SIFT特征检测结果如下:

img

图片1和图片2的SIFT特征匹配结果如下,可见匹配点还是挺多的。

img

图片1和图片2的最终拼接结果如下,可见拼接处的物体融合得还是非常好的,但是由于两张图的拍摄参数不一,会有光照、饱和度、色度的差别。

img

五、 附代码

import cv2

import numpy as np

 

class Stitcher:

  \# 两图拼接函数

  def Stitch(self, images, ratio=0.75, reprojThresh=4.0, method=1):

​    \# 获取输入图片

​    (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, method=method)

​    \# 如果返回结果为空,没有匹配成功的特征点,退出算法

​    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]))

​    \# 将图片B传入result图片最左端

​    result[0:imageB.shape[0], 0:imageB.shape[1]] = imageB

​    \# 生成匹配图片

​    vis = self.drawMatches(imageA, imageB, kpsA, kpsB, matches, status)return (result, vis)

 

  def detectAndDescribe(self, image):

​    \# 将彩色图片转换成灰度图

​    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

 

​    \# 建立SIFT生成器

​    descriptor = cv2.SIFT_create()

​    \# 检测SIFT特征点,并计算描述子

​    (kps, features) = descriptor.detectAndCompute(gray, None)

 

​    \# 将结果转换成NumPy数组

​    kps = np.float32([kp.pt for kp in kps])

 

​    \# 返回特征点集,及对应的描述特征

​    return (kps, features)

 

  def matchKeypoints(self, kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh, method): # method=1为BF暴力匹配, method=2为Flann匹配if method == 1:

​      \# 建立暴力匹配器

​      matcher = cv2.BFMatcher()

​      \# 使用KNN检测来自A、B图的SIFT特征匹配对,K=2

​      rawMatches = matcher.knnMatch(featuresA, featuresB, 2)

​      matches = []for m in rawMatches:

​        \# 当最近距离跟次近距离的比值小于ratio值时,保留此匹配对

​        if len(m) == 2 and m[0].distance < m[1].distance * ratio:

​          \# 存储两个点在featuresA, featuresB中的索引值

​          matches.append((m[0].trainIdx, m[0].queryIdx))

 

​      \# 当筛选后的匹配对大于4时,计算视角变换矩阵

​      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)

 

​      \# 如果匹配对小于4时,返回Nonereturn Noneelse:

​      \# 建立Flann匹配器

​      FLANN_INDEX_KDTREE = 1

​      index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)

​      search_params = dict(checks=50)

​      flann = cv2.FlannBasedMatcher(index_params, search_params)

​      rawMatches = flann.knnMatch(featuresA, featuresB, k=2)

 

​      matches = []for m in rawMatches:

​        \# 当最近距离跟次近距离的比值小于ratio值时,保留此匹配对

​        if len(m) == 2 and m[0].distance < m[1].distance * ratio:

​          \# 存储两个点在featuresA, featuresB中的索引值

​          matches.append((m[0].trainIdx, m[0].queryIdx))

 

​      \# 当筛选后的匹配对大于4时,计算视角变换矩阵

​      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)

 

​      \# 如果匹配对小于4时,返回Nonereturn None

 

 

 

  def drawMatches(self, imageA, imageB, kpsA, kpsB, matches, status):

​    \# 初始化可视化图片,将A、B图左右连接到一起

​    (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

 

 

\# 主程序

\# 读取拼接图片

imageA = cv2.imread("2.jpg")

imageB = cv2.imread("1.jpg")

 

\# 把图片拼接成全景图

stitcher = Stitcher()

(result, vis) = stitcher.Stitch([imageA, imageB], method=1)

 

\# 显示所有图片

cv2.imshow("Image A", imageA)

cv2.imshow("Image B", imageB)

cv2.imshow("Keypoint Matches", vis)

cv2.imwrite("Keypoint Matches.jpg", vis)

cv2.imshow("result", result)

cv2.imwrite("result.jpg", result)

cv2.waitKey(0)

cv2.destroyAllWindows()

相关推荐

  1. js 实现图片纵向拼接并下载

    2024-03-22 03:50:03       6 阅读
  2. LabVIEW实现多张图像拼接

    2024-03-22 03:50:03       8 阅读
  3. 实现两张图片的接缝线拼接

    2024-03-22 03:50:03       42 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-22 03:50:03       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-22 03:50:03       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-22 03:50:03       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-22 03:50:03       18 阅读

热门阅读

  1. 发现开源宝藏——stepui

    2024-03-22 03:50:03       20 阅读
  2. C++——类和对象(上)

    2024-03-22 03:50:03       20 阅读
  3. PCL拟合并绘制平面(一)

    2024-03-22 03:50:03       18 阅读
  4. DPDK系列之四十二DPDK应用网络编程

    2024-03-22 03:50:03       21 阅读
  5. 深入理解数据结构森林

    2024-03-22 03:50:03       23 阅读
  6. texStudio用Springer模板排坑

    2024-03-22 03:50:03       23 阅读
  7. 【leetcode】动态规划专题

    2024-03-22 03:50:03       16 阅读
  8. 使用Tesseract识别中文 并提高精度

    2024-03-22 03:50:03       19 阅读
  9. React面试题

    2024-03-22 03:50:03       15 阅读
  10. CCF-CSP认证考试 202303-4 星际网络II 100分题解

    2024-03-22 03:50:03       21 阅读