计算机视觉学习7:全景拼接

全景拼接原理

全景图象是通过将普通照相机拍照到的边界部分重叠的图象进行拼接而创建的。可以利用图象重叠部分对应像素的相似性, 通过采用一种行之有效的拼接算法, 使得到的图象无缝平滑。
图像的拼接技术包括三大部分:特征点提取与匹配、图像配准、图像融合。
本文采用的是基于sift算法的全景图像拼接。

一、算法原理

  1. 拼接步骤:
    (1)读入两张图片并分别提取SIFT特征
    (2) 匹配两个图像之间的描述符
    (3) 利用RANSAC算法筛选匹配点并计算变换矩阵
    (4) 图像融合
  2. SIFT算法原理介绍之前有简单介绍过Sift算法介绍
  3. 单应性矩阵及图像仿射(Homegraph)原理上一篇文章有简单介绍过图像仿射原理
    这里总结一下Homographies:
    假设我们有两个相同平面对象的图像,它们之间有一些重叠(存在对应点)。让P1成为第一图像中的点的像素坐标[P1x,P1y,1 ],P2是第二图像中的点的像素坐标[P2X,P2Y,1 ]。我们将第一图像称为目的图像,第二图像称为源图像。单应性1H2是将第二图像中的每个像素坐标与第一图像中的像素坐标映射关系。
    P1 = 1H2 * P2
    获取对应点
    可以使用特征点检测,如 SIFT,SURF,ORB等,这里,我们使用sift。
  4. 图像配准
    图像配准是一种确定待拼接图像间的重叠区域以及重叠位置的技术,它是整个图像拼接的核心。本文采用的是基于特征点的图像配准方法,即通过匹配点对构建图像序列之间的变换矩阵,从而完成全景图像的拼接。
    变换单应性矩阵H求解是图像配准的核心,其求解的算法流程如下。
    1)检测每幅图像中特征点。
    2)计算特征点之间的匹配。
    3)计算图像间变换矩阵的初始值。
    4)迭代精炼H变换矩阵。
    5)引导匹配。用估计的H去定义对极线附近的搜索区域,进一步确定特征点的对应。
    6)重复迭代4)和5)直到对应点的数目稳定为止。
    设图像序列之间的变换为投影变换
    可用4组最佳匹配计算出H矩阵的8 个自由度参数hi=( i=0,1,…,7),并以此作为初始值。
    为了提高图像配准的精度,本文采用RANSAC算法对图像变换矩阵进行求解与精炼,达到了较好的图像拼接效果。
    计算机视觉学习7:全景拼接_第1张图片计算机视觉学习7:全景拼接_第2张图片
    RANSAC算法的基本原理可通过上图来描述。
    RANSAC算法的思想简单而巧妙:首先随机地选择两个点,这两个点确定了一条直线,并且称在这条直线的一定范围内的点为这条直线的支撑。这样的随机选择重复数次,然后,具有最大支撑集的直线被确认为是样本点集的拟合。在拟合的误差距离范围内的点被认为是内点,它们构成一致集,反之则为外点。根据算法描述,可以很快判断,如果只有少量外点,那么随机选取的包含外点的初始点集确定的直线不会获得很大的支撑,值得注意的是,过大比例的外点将导致RANSAC算法失败。在直线拟合的例子中,由点集确定直线至少需要两个点;而对于透视变换,这样的最小集合需要有4个点。
    图中蓝色点属于内点(正确点),而第红色点属于外点(偏移点)。此时用最小二乘法拟合这组数据,实际的最佳拟合直线是那条穿越了最多点数的蓝色实线。
    5.图像合成
    估计单应矩阵 H后,我们需要将两个图像拼接在一起。这里我们采用透视变换,输入想要扭曲的图像,单应矩阵( H ),还有输出图像的形状。我们通过获取两个图像的宽度之和然后使用第二个图像的高度确定输出图像的导出形状。透视变换(Perspective Transformation)是将图片投影到一个新的视平面(Viewing Plane),也称作投影映射(Projective Mapping)。透视变换如下图:计算机视觉学习7:全景拼接_第3张图片
    参考博客:https://blog.csdn.net/u012384044/article/details/73162354
    https://blog.csdn.net/masibuaa/article/details/9246493

二、算法实现

from PIL import Image
import os
from numpy import *
from pylab import *


def process_image(imagename, resultname, params="--edge-thresh 10 --peak-thresh 5"):
    """ process an image and save the results in a file"""

    if imagename[-3:] != 'pgm':
        # create a pgm file
        im = Image.open(imagename).convert('L')
        im.save('tmp.pgm')
        imagename = 'tmp.pgm'

    cmmd = str("sift " + imagename + " --output=" + resultname +
               " " + params)
    print(cmmd)
    os.system(cmmd)
    if os.system(cmmd) == 0:
        print('processed', imagename, 'to', resultname)


def read_features_from_file(filename):
    """ read feature properties and return in matrix form"""
    print('test2')
    f = loadtxt(filename)
    print('test3')
    return f[:, :4], f[:, 4:]  # feature locations, descriptors


def write_features_to_file(filename, locs, desc):
    """ save feature location and descriptor to file"""
    savetxt(filename, hstack((locs, desc)))


def plot_features(im, locs, circle=False):
    """ show image with features. input: im (image as array),
        locs (row, col, scale, orientation of each feature) """

    def draw_circle(c, r):
        t = arange(0, 1.01, .01) * 2 * pi
        x = r * cos(t) + c[0]
        y = r * sin(t) + c[1]
        plot(x, y, 'b', linewidth=2)

    imshow(im)
    if circle:
        [draw_circle([p[0], p[1]], p[2]) for p in locs]
    else:
        plot(locs[:, 0], locs[:, 1], 'ob')
    axis('off')


def match(desc1, desc2):
    """ for each descriptor in the first image,
        select its match in the second image.
        input: desc1 (descriptors for the first image),
        desc2 (same for second image). """

    desc1 = array([d / linalg.norm(d) for d in desc1])
    desc2 = array([d / linalg.norm(d) for d in desc2])

    dist_ratio = 0.6
    desc1_size = desc1.shape

    matchscores = zeros((desc1_size[0], 1))
    desc2t = desc2.T  # precompute matrix transpose
    for i in range(desc1_size[0]):
        dotprods = dot(desc1[i, :], desc2t)  # vector of dot products
        dotprods = 0.9999 * dotprods
        # inverse cosine and sort, return index for features in second image
        indx = argsort(arccos(dotprods))

        # check if nearest neighbor has angle less than dist_ratio times 2nd
        if arccos(dotprods)[indx[0]] < dist_ratio * arccos(dotprods)[indx[1]]:
            matchscores[i] = int(indx[0])

    return matchscores


def appendimages(im1, im2):
    """ return a new image that appends the two images side-by-side."""

    # select the image with the fewest rows and fill in enough empty rows
    rows1 = im1.shape[0]
    rows2 = im2.shape[0]

    if rows1 < rows2:
        im1 = concatenate((im1, zeros((rows2 - rows1, im1.shape[1]))), axis=0)
    elif rows1 > rows2:
        im2 = concatenate((im2, zeros((rows1 - rows2, im2.shape[1]))), axis=0)
    # if none of these cases they are equal, no filling needed.

    return concatenate((im1, im2), axis=1)


def plot_matches(im1, im2, locs1, locs2, matchscores, show_below=True):
    """ show a figure with lines joining the accepted matches
        input: im1,im2 (images as arrays), locs1,locs2 (location of features),
        matchscores (as output from 'match'), show_below (if images should be shown below). """

    im3 = appendimages(im1, im2)
    if show_below:
        im3 = vstack((im3, im3))

    # show image
    imshow(im3)

    # draw lines for matches
    cols1 = im1.shape[1]
    for i in range(len(matchscores)):
        if matchscores[i] > 0:
           x1=int(locs1[i,0])
           aa=int(matchscores[i,0])
           y1=int(locs2[aa,0]+cols1)
           x2=int(locs1[i,1])
           y2=int(locs2[aa,1])
           plot([x1,y1],[x2,y2],'c')
            # plot([locs1[i, 0], locs2[matchscores[i, 0], 0] + cols1], [locs1[i, 1], locs2[matchscores[i, 0], 1]], 'c')
    axis('off')


def match_twosided(desc1, desc2):
    """ two-sided symmetric version of match(). """

    matches_12 = match(desc1, desc2)
    matches_21 = match(desc2, desc1)

    ndx_12 = matches_12.nonzero()[0]

    # remove matches that are not symmetric
    for n in ndx_12:
        if matches_21[int(matches_12[n])] != n:
            matches_12[n] = 0

    return matches_12


if __name__ == "__main__":
    process_image('box.pgm', 'tmp.sift')
    l, d = read_features_from_file('tmp.sift')

    im = array(Image.open('box.pgm'))
    figure()
    plot_features(im, l, True)
    gray()

    process_image('scene.pgm', 'tmp2.sift')
    l2, d2 = read_features_from_file('tmp2.sift')
    im2 = array(Image.open('scene.pgm'))

    m = match_twosided(d, d2)
    figure()
    plot_matches(im, im2, l, l2, m)

    gray()
    show()

  1. 室内图像(五张)拼接
    计算机视觉学习7:全景拼接_第4张图片
  2. 室外景深落差较小的图像(五张)拼接
    计算机视觉学习7:全景拼接_第5张图片
  3. 室外景深落差较大的图像(五张)拼接
    计算机视觉学习7:全景拼接_第6张图片

总结

1.本文拼接过程中,室外景深落差较小且平移拍摄的图像拼接效果最好,几乎实现了无缝拼接。室内图像由于拍摄的时候有旋转角度,拼接出来的结果也出现了扭曲。室外景深落差较大的图像采用的是集美大学尚大楼及周边,因为尚大楼左右两侧的美岭楼和吕振万楼太过相似,在拍摄时角度没有掌握好,拼接时匹配效果不好。
2. 本次实验采用的图像依然是来自集美大学,由于本人拍照的手法不够专业,拼接出来的图像效果差强人意,在此提醒大家拍照的时候手一定要稳,双脚保持站立状态不要动,两手持设备平稳移动,平移,这样拼接时不会出现太大的扭曲。选择场景时也尽量选择差异度大的,拼接效果会比较好。

你可能感兴趣的:(计算机视觉学习7:全景拼接)