计算机视觉--图像的拼接融合

一、全景图像拼接原理介绍

1.1 背景介绍

    图片的全景拼接如今已不再稀奇,现在的智能摄像机和手机摄像头基本都带有图片自动全景拼接的功能,但是一般都会要求拍摄者保持设备的平稳以及单方向的移动取景以实现较好的拼接结果。这是因为拼接的图片之间必须要有相似的区域以保证拼接结果的准确性和完整性。本文主要简单描述如何用 Python 和 OpenCV 库实现多张图片的自动拼合。

1.2 基本原理

    要实现图像的拼接融合,首先我们先看两张图片的简单拼接,其实只需找出两张图片中相似的点 (至少四个,因为 homography 矩阵的计算需要至少四个点), 计算一张图片可以变换到另一张图片的变换矩阵 (homography 单应性矩阵),用这个矩阵把那张图片变换后放到另一张图片相应的位置 ( 就是相当于把两张图片中定好的四个相似的点给重合在一起)。如此,就可以实现简单的全景拼接。

    举个例子,下面是书上原图的全景拼接结果:
在这里插入图片描述
全景图:
在这里插入图片描述
(一) RANSAC算法
    回顾上一篇博客内容,SIFT算法的描述子稳健性很强,比Harris角点要来得精确,但是它的匹配正确率也并不是百分百的,会受到一些噪声点的干扰,有时就会因为不同地方有类似的图案导致匹配错误。那么RANSAC算法便是用来找到正确模型来拟合带有噪声数据的迭代方法。
    RANSAC通过反复取样,也就是从整个观测数据中随机抽一些数据估算模型参数之后看和所有数据误差有多大,然后取误差最小视为最好以及分离内群与离群数据。基本的思想是,数据中包含正确的点和噪声点,合理的模型应该能够在描述正确数据点的同时摒弃噪声点。
    举个简单的例子,直线的拟合便是RANSAC的一个标准化体现:
在这里插入图片描述
    同理,RANSAC算法可以应用到其它模块中,例如用于图像变换的单应性矩阵的计算。
    在拼接的过程中,通过将响速和单应矩阵H相乘,然后对齐次坐标进行归一化来实现像素间的映射。通过查看H中的平移量,我们可以决定应该将该图像填补到左边还是右边。

(二) 全景拼接
    SIFT特征匹配前面写过,这里不多做介绍。这次将其应用到图像拼接上,根据特征点匹配的方式,则利用这些匹配的点来估算单应矩阵(使用上面的RANSAC算法),也就是把其中一张通过个关联性和另一张匹配的方法。通过单应矩阵H,可以将原图像中任意像素点坐标转换为新坐标点,转换后的图像即为适合拼接的结果图像。
(1)图像配准
    图像配准是对图像进行变换,使变换后的图像能够在常见的坐标系中对齐。为了能够进行图像对比和更精细的图像分析,图像配准是一步非常重要的操作。
    图像配准的方法有很多,这里以APAP算法为例:
    1.提取两张图片的sift特征点。
    2.对两张图片的特征点进行匹配。
    3.匹配后,仍有很多错误点,用RANSAC的改进算法进行特征点对的筛选。筛选后的特征点基本能够异议一 一对应。
    4.使用DLT算法,将剩下的特征点对进行透视变换矩阵的估计。
    5.因为得到的透视变换矩阵是基于全局特征点对进行的,即一个刚性的单应性矩阵完成配准。为提高配准的精度,Apap将图像切割成无数多个小方块,对每个小方块的变换矩阵逐一估计。

(2)图割方法
1.关于最小割
    如以下图1所示,这是一个有向带权图,共有4个顶点和5条边。每条边上的箭头代表了边的方向,每条边上的数字代表了边的权重。
在这里插入图片描述
什么是最小割?
    现在要求剪短图中的某几条边,使得不存在从s到t的路径,并且保证所减的边的权重和最小。那么很明显,剪掉边”s->a”和边”b->t”。我们就能得到上图中的图2.
    图中已不存在从s到t的路径,且所修剪的边的权重和为:2 + 3 = 5,为所有修剪方式中权重和最小的。我们把这样的修剪称为最小割。
2.关于最大流
什么是最大流?
    如以上图1,因为s->a只能通过2,那么s->a->t这条路的流量被限制在了2,同理,s->b->t被b->t限制,最多只能通过3的流量,所以s->t的流量总和为2+3=5,为这条路的最大流。
(3)图像融合-blending
    其实图像拼接完会发现在拼接的交界处有明显的衔接痕迹,存在边缘效应,因为光照色泽的原因使得图片交界处的过渡很糟糕,所以需要特定的处理解决这种不自然。那么这时候可以采用blending方法,在opencv内部已经实现了multi-band blending。

1.3 图像拼接整体流程

  • 根据给定图像/集,实现特征匹配
  • 通过匹配特征计算图像之间的变换结构
  • 利用图像变换结构,实现图像映射
  • 针对叠加后的图像,采用APAP之类的算法,对齐特征点
  • 通过图割方法,自动选取拼接缝
  • 根据multi-band bleding策略实现融合

二、全景图像拼接实验

2.1 代码实现

# -*- coding: utf-8
from pylab import *
from numpy import *
from PIL import Image
import numpy as np
# If you have PCV installed, these imports should work
from PCV.geometry import homography, warp
from PCV.localdescriptors import sift

"""
This is the panorama example from section 3.3.
"""
np.seterr(divide='ignore', invalid='ignore')
# set paths to data folder

featname = ['D:/Alike/python_dw/Code_a/admin_code/data/data_2/o' + str(i + 1) + '.sift' for i in range(5)]
imname = ['D:/Alike/python_dw/Code_a/admin_code/data/data_2/o' + str(i + 1) + '.jpg' for i in range(5)]

# extract features and match
l = {}
d = {}
for i in range(5):
    sift.process_image(imname[i], featname[i])
    l[i], d[i] = sift.read_features_from_file(featname[i])

matches = {}
for i in range(4):
    matches[i] = sift.match(d[i + 1], d[i])

# visualize the matches (Figure 3-11 in the book)
# sift匹配可视化
for i in range(4):
    im1 = array(Image.open(imname[i]))
    im2 = array(Image.open(imname[i + 1]))
    figure()
    sift.plot_matches(im2, im1, l[i + 1], l[i], matches[i], show_below=True)


# function to convert the matches to hom. points
# 将匹配转换成齐次坐标点的函数
def convert_points(j):
    ndx = matches[j].nonzero()[0]
    fp = homography.make_homog(l[j + 1][ndx, :2].T)
    ndx2 = [int(matches[j][i]) for i in ndx]
    tp = homography.make_homog(l[j][ndx2, :2].T)

    # switch x and y - TODO this should move elsewhere
    fp = vstack([fp[1], fp[0], fp[2]])
    tp = vstack([tp[1], tp[0], tp[2]])
    return fp, tp


# estimate the homographies
# 估计单应性矩阵
model = homography.RansacModel()

fp, tp = convert_points(1)
H_12 = homography.H_from_ransac(fp, tp, model)[0]  # im 1 to 2  # im1 到 im2 的单应性矩阵


fp, tp = convert_points(0)
H_01 = homography.H_from_ransac(fp, tp, model)[0]  # im 0 to 1

tp, fp = convert_points(2)  # NB: reverse order
H_32 = homography.H_from_ransac(fp, tp, model)[0]  # im 3 to 2

tp, fp = convert_points(3)  # NB: reverse order
H_43 = homography.H_from_ransac(fp, tp, model)[0]  # im 4 to 3

# warp the images
# 扭曲图像

delta = 2000  # for padding and translation 用于填充和平移

im1 = array(Image.open(imname[1]), "uint8")
im2 = array(Image.open(imname[2]), "uint8")
im_12 = warp.panorama(H_12, im1, im2, delta, delta)

im1 = array(Image.open(imname[0]), "f")
im_02 = warp.panorama(dot(H_12, H_01), im1, im_12, delta, delta)

im1 = array(Image.open(imname[3]), "f")
im_32 = warp.panorama(H_32, im1, im_02, delta, delta)

im1 = array(Image.open(imname[4]), "f")
im_42 = warp.panorama(dot(H_32, H_43), im1, im_32, delta, 2 * delta)
imsave('ping3.jpg', array(im_42, "uint8"))
figure()
imshow(array(im_42, "uint8"))
axis('off')
show()

2.2 不同场景的实验结果与分析

2.2.1 针对固定点拍摄图片

针对固定点拍摄多张图片,实现图像的拼接融合
室外场景
原图:
在这里插入图片描述
拼接结果:
在这里插入图片描述
在这里插入图片描述
室内场景
原图:
在这里插入图片描述
拼接结果:
在这里插入图片描述
在这里插入图片描述
分析:

  • 观察第一组室外场景得到的结果,发现检测出的匹配特征丰富,整体融合效果好。前两张图片融合几乎没有拼接痕迹,后面三张图片在拼接的交界处有明显的衔接痕迹,存在边缘效应,出现参数曝光的问题。这种现象在图像拼接中普遍存在,即使同一时间同一地点同样拍摄设备,也会有些影响,要解决这个问题可以用multi-band bleding策略实现融合。
  • 观察第二组室内场景得到的结果,发现全景图融合不完整,只有原图的k4.jpg和k5.jpg实现了融合,其他三张图片没有体现在全景图中。观察SIFT特征匹配的结果图,发现序号1与序号2的两个匹配结果出现错配,猜测是室内的窗户、柱子等相似物体多,造成干扰。
  • 从一个固定点拍摄多张图,实现融合,会发现室外场景的融合效果比室内好,可能原因是室外场景提取到的特征点更丰富,有更大参考性,使得拼接效果更优。

2.2.2 移动拍摄位置

室外场景
原图:
在这里插入图片描述
拼接结果;
在这里插入图片描述
在这里插入图片描述
分析:
    上面这个场景与前一组实验中的室外场景取景相同,不同的是这组图片在拍摄时我改变了位置,即不再站在一个固定点拍照,但是可以看到得到的全景图杂乱无章。
    猜测这是由于镜头拍照的位置不同,会导致图片虽然有相同的拍摄区域,但是并不能简单的将五张图片的重叠区域覆盖进行拼接。因为拍摄五张图片时,相机的世界坐标位置发生变化。试想一下,你首先在A位置看到物体一,然后换到B位置再看物体一,物体一虽然仍在你的视野里,但是,它在你眼中与周围参照物的距离会发生变化,位置不同,光线也可能不同,这都是拼接过程中存在的问题。

视差变化大的场景
原图:
在这里插入图片描述
拼接结果:
在这里插入图片描述
在这里插入图片描述
分析:
    在该场景下,护栏以及靠在护栏上的拖把是近景目标,建筑物是远景目标,视差变化大。观察近景目标拼接,可以看到右侧护栏处出现了一些偏差,观察远景目标,看到后面的建筑物出现鬼影现象;但是观察SIFT特征匹配结果,看到匹配结果基本正确。这是因为景深变化大,这时即使都是正确的匹配点,但是不能用homography单应性矩阵表示,要解决这种问题可以使用APAP算法。
    全景图出现像素失真的现象,可能与我改变了图片的像素值大小有关。

三、总结

实验内容总结

  • 室外场景的图像拼接结果要好于室内,可能原因是室外特征点丰富,进行拼接等操作时有更大的参考性。
  • 景深大的场景进行图像融合易出现鬼影现象,原因是景深大时,即使匹配点是正确的,但无法用homography单应性矩阵表示。可以用APAP(As-Projective-As-Possible)算法解决该问题,此算法将图像切割成无数多个小方块,对每个小方块的单应性矩阵逐一估计。
  • 经过多组实验发现,拍摄时如果位置移动容易导致图像无法正确融合,相对来说,固定位置拍摄得到的融合效果更好。

实验过程总结

①问题一:出现报错如下:
在这里插入图片描述
解决方案:
在代码中添加——

import numpy as np
np.seterr(divide='ignore', invalid='ignore')

②问题二:得到的运行结果仅有非常窄的一个长条,无法看清内容。
解决方案:修改图片像素值,原本图像像素值是200×160,修改成1400×1120,即可得到较清晰的全景图。

发布了19 篇原创文章 · 获赞 7 · 访问量 5300

猜你喜欢

转载自blog.csdn.net/qq_41784565/article/details/105068137