天天看点

opencv Stitcher多图拼接Opencv使用Stitcher类图像拼接生成全景图像

Opencv使用Stitcher类图像拼接生成全景图像

Opencv中自带的Stitcher类可以实现全景图像,效果不错。下边的例子是Opencv Samples中的stitching.cpp的简化,源文件可以在这个路径里找到:

\opencv\sources\samples\cpp\stitching.cpp

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

#include <fstream>

#include "opencv2/highgui/highgui.hpp"

#include "opencv2/stitching/stitcher.hpp"

#include <iostream>

using

namespace

cv;

using

namespace

std;

vector<Mat> imgs;

//保存拼接的原始图像向量

//导入所有原始拼接图像函数

void

parseCmdArgs(

int

argc,

char

** argv);

int

main(

int

argc,

char

* argv[])

{

//导入拼接图像

parseCmdArgs(argc, argv);  

Mat pano;

Stitcher stitcher = Stitcher::createDefault(

false

);

Stitcher::Status status = stitcher.stitch(imgs, pano);

//拼接

if

(status != Stitcher::OK)

//判断拼接是否成功

{

cout <<

"Can't stitch images, error code = "

<<

int

(status) << endl;

return

-1;

}

namedWindow(

"全景拼接"

,0);

imshow(

"全景拼接"

,pano);

imwrite(

"D:\\全景拼接.jpg"

,pano);

waitKey();  

return

0;

}

//导入所有原始拼接图像函数

void

parseCmdArgs(

int

argc,

char

** argv)

{

for

(

int

i=1;i<argc;i++)

{

Mat img = imread(argv[i]);

if

(img.empty())

{

cout <<

"Can't read image '"

<< argv[i] <<

"'\n"

;

}

imgs.push_back(img);

}

}

图1:

opencv Stitcher多图拼接Opencv使用Stitcher类图像拼接生成全景图像

图2:

opencv Stitcher多图拼接Opencv使用Stitcher类图像拼接生成全景图像

图3:

opencv Stitcher多图拼接Opencv使用Stitcher类图像拼接生成全景图像

图4:

opencv Stitcher多图拼接Opencv使用Stitcher类图像拼接生成全景图像

图5:

opencv Stitcher多图拼接Opencv使用Stitcher类图像拼接生成全景图像

5个图片的拍摄角度合起来在180°左右,没有经过压缩的,下载下来可以直接测试使用,传入顺序随意,Stitcher会自动排列。全景拼接效果很赞:

下面是python的:

一、拼接介绍

在同一位置拍摄的两幅或多幅图像是单应性相关的。我们可以使用该约束将很多图像拼接起来,拼成一幅大的图像来创建全景图像

全景图像拼接最重要的两个步骤就是:

(一)特征点匹配

这部分主要采用SIFT算法实现,之前的博客有介绍就不再详细介绍了,主要是为了找到两幅图像相同的特征点并将其进行匹配。

(二)图片匹配

图片匹配就是找到图像之间所有重叠的部分,将其拼接后就能得到一幅全景图。

基本原理

1.单应性矩阵

定义:在计算机视觉领域,空间同一平面的任意两幅图像被单应矩阵联系着(假设在针孔相机模型中),即一个相机拍摄空间同一平面的两张图像,这两张图像之间的映射关系可以用单应矩阵表示。

在两视几何中,也可以这样理解,两架相机拍同一空间上得到两幅图像A、B,其中图像A到图像B存在一种变换,而且这种变换是一一对应的关系,这个变换矩阵用单应矩阵表示。OpenCV中可以用函数findHomography计算得到单应矩阵H。

要实现两张图片的简单拼接,其实只需找出两张图片中相似的点 (至少四个,因为 homography 矩阵的计算需要至少四个点), 计算一张图片可以变换到另一张图片的变换矩阵 (homography 单应性矩阵),用这个矩阵把那张图片变换后放到另一张图片相应的位置 ( 就是相当于把两张图片中定好的四个相似的点給重合在一起)。如此,就可以实现简单的全景拼接。当然,因为拼合之后图片会重叠在一起,所以需要重新计算图片重叠部分的像素值,否则结果会很难看。

2.RANSAC算法

RANSAC(Random Sample Consensus)即随机采样一致性,该方法是用来找到正确模型来拟合带有噪声数据的迭代方法。给定一个模型,例如点集之间的单应性矩阵,RANSAC的基本思想在于,找到正确数据点的同时摒弃噪声点。

3.利用RANSAC算法求解单应性矩阵

虽然SIFT是具有很强稳健性的描述子,当这方法仍远非完美,还会存在一些错误的匹配。而单应性矩阵需要选取4对特征点计算,万一选中了不正确的匹配点,那么计算的单应性矩阵肯定是不正确的。因此,为了提高计算结果的鲁棒性,我们下一步就是要把这些不正确的匹配点给剔除掉,获得正确的单应性矩阵。在这里使用了RANSAC算法:随机抽取不同的4对特征匹配坐标(在图1中随机抽取4个特征坐标,以及这4个特征坐标在图2中匹配的4个特征坐标,组成4对特征匹配坐标),利用这4对特征匹配坐标计算出矩阵H1(3x3的一个矩阵,图2经过矩阵变换后,可以把图2映射到图1的坐标空间中,再将图2进行简单的平移即可与图1实现无缝拼接),再将图2中所有特征匹配点经过该透视矩阵H1映射到图1的坐标空间,然后与图1匹配点实际坐标求欧氏距离(就是为了验证计算出来的这个H1矩阵是否满足绝大多数特征匹配点);之后重复上面内容,再随机抽取不同的四组特征匹配坐标,再计算透视矩阵H2,再求欧式距离,如此重复多次。最后以欧式距离最小的那个透视矩阵(表示这个特征矩阵H满足最多的特征匹配点,它最优秀)作为最终计算结果。

4.图片融合

在用计算出的变换矩阵对其中一张图做变换,然后把变换的图片与另一张图片重叠在一起,并重新计算重叠区域新的像素值。对于计算重叠区域的像素值,其实可以有多种方法去实现一个好的融合效果,这里就用最简单粗暴的但效果也不错的方式。直白来说就是实现一个图像的线性渐变,对于重叠的区域,靠近左边的部分,让左边图像内容显示的多一些,靠近右边的部分,让右边图像的内容显示的多一些。用公式表示就是,假设 alpha 表示像素点横坐标到左右重叠区域边界横坐标的距离,新的像素值就为 newpixel = 左图像素值 × (1 - alpha) + 右图像素值 × alpha 。这样就可以实现一个简单的融合效果。

2代码及运行结果

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

# 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.
"""

# set paths to data folder
featname = ['C:\\Users\DELL\Desktop\PCV\jmu\panorama/z0'+str(i+1)+'.sift' for i in range(5)]
imname = ['C:\\Users\DELL\Desktop\PCV\jmu\panorama/z0'+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)
'''
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 

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 = 500 # 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)


figure()
imshow(array(im_42, "uint8"))
axis('off')
show()
           
opencv Stitcher多图拼接Opencv使用Stitcher类图像拼接生成全景图像
opencv Stitcher多图拼接Opencv使用Stitcher类图像拼接生成全景图像

因为相机和光照强度的差异,会造成一幅图像内部,以及图像之间亮度的不均匀,拼接后的图像会出现明暗交替,这样给观察造成极大的不便。室外场景的拼接效果比室内的要好很多,多图拼接对图片的要求也比较高,差异性大或太小(几乎相同)的拼接效果都很差。