主要内容

本页采用了机器翻译。点击此处可查看英文原文。

创建全景图

本示例演示了如何将多个图像自动拼接成全景图。图像拼接的流程是基于特征的图像配准的延伸。与仅对单组图像进行配准不同,该方法将多组图像对依次相互配准,从而形成全景图。

加载图像

本示例中使用的图像集包含一栋建筑的照片。这些照片是用未经标定的智能手机相机拍摄的,拍摄时将镜头从左向右沿地平线扫过,捕捉到了建筑物的各个部分。这些图像几乎未受任何镜头畸变的影响,因此无需进行相机标定。但是,如果存在镜头畸变,应在制作全景图之前对相机进行标定,并对图像去畸变。如有需要,请使用相机标定器对相机进行标定。

加载并显示图像。

buildingDir = fullfile(toolboxdir("vision"),"visiondata","building");
buildingScene = imageDatastore(buildingDir);

montage(buildingScene.Files)

Figure contains an axes object. The hidden axes object contains an object of type image.

配对图像的配准

要创建全景图,请先按照以下步骤对连续的图像对进行配准:

  1. 检测并匹配 I(n)I(n-1) 之间的特征。

  2. 求出将 I(n) 映射到 I(n-1) 的几何变换 T(n)

  3. 计算将 I(n) 映射到全景图像中并表示为 T(1)*T(2)*...*T(n-1)*T(n) 的变换。

读取图像集中的第一个图像。

I = readimage(buildingScene,1);

初始化第一个图像的特征。

grayImage = im2gray(I);
points = detectSIFTFeatures(grayImage);
[features,points] = extractFeatures(grayImage,points);

将所有变换初始化为单位矩阵。请注意,之所以使用投影变换,是因为建筑图像距离相机相当近。对于从更远距离拍摄的场景,建议考虑使用仿射变换。

numImages = numel(buildingScene.Files);
tforms(numImages) = projtform2d;

初始化一个变量来存储图像尺寸。

imageSize = zeros(numImages,2);

遍历剩余的图像对。对于每个图像,请存储上一个图像中的点和特征。先读取图像,然后将其转换为灰度,并保存图像尺寸。从图像中检测并提取 SIFT 特征,并找出该图像与前一个图像之间的对应关系。然后,估算这两组对应关系之间的变换。

for n = 2:numImages
    pointsPrevious = points;
    featuresPrevious = features;
        
    I = readimage(buildingScene, n);
    grayImage = im2gray(I);    
    imageSize(n,:) = size(grayImage);
    
    points = detectSIFTFeatures(grayImage,ContrastThreshold=0.02);
    [features,points] = extractFeatures(grayImage,points);
  
    indexPairs = matchFeatures(features,featuresPrevious,MatchThreshold=20,MaxRatio=0.7,Unique=true);
    matchedPoints = points(indexPairs(:,1), :);
    matchedPointsPrev = pointsPrevious(indexPairs(:,2), :);        
    
    tforms(n) = estgeotform2d(matchedPoints, matchedPointsPrev,...
        "projective",Confidence=99.9,MaxNumTrials=2000,MaxDistance=4);
    
    tforms(n).A = tforms(n-1).A * tforms(n).A; 
end

在此阶段,tforms 中的所有变换器均以第一个图像为基准,这种方法通过支持对图像进行顺序处理,简化了图像配准过程的编码工作。然而,如果从第一个图像开始拼接全景图,往往会导致效果不尽如人意,因为这通常会使构成全景图的大部分图像产生畸变。通过调整变换,使场景中心产生的畸变最小,可以获得视觉效果更佳的全景图。该改进方法包括对中间图像进行变换反转,并将该反转后的变换应用于所有其他图像。首先,使用 projtform2d outputLimits 方法找出每次变换器的输出范围。随后利用这些输出限制,自动找出大致位于场景中心位置的图像。

计算每个变换的输出范围。

for idx = 1:numel(tforms)           
    [xlim(idx,:),ylim(idx,:)] = outputLimits(tforms(idx),[1 imageSize(idx,2)],[1 imageSize(idx,1)]);    
end

接下来,计算每次变换器的平均 X 坐标极限,并找出位于中心位置的图像。这里仅使用 X 方向的限制,因为已知该场景是水平的。如果使用另一组图像,可能需要同时使用 X 和 Y 边界来确定中心图像的位置。

avgXLim = mean(xlim, 2);
[~,idx] = sort(avgXLim);
centerIdx = floor((numel(tforms)+1)/2);
centerImageIdx = idx(centerIdx);

最后,将中心图像的逆变换应用到所有其他图像上。

Tinv = invert(tforms(centerImageIdx));
for idx = 1:numel(tforms)    
    tforms(idx).A = Tinv.A * tforms(idx).A;
end

初始化全景图

创建一个初始的、空的全景图,将所有图像映射到该全景图中。使用 outputLimits 方法计算所有变换器的输出最小值和最大值。这些值用于自动计算全景图的大小。

for idx = 1:numel(tforms)           
    [xlim(idx,:),ylim(idx,:)] = outputLimits(tforms(idx),[1 imageSize(idx,2)],[1 imageSize(idx,1)]);
end
maxImageSize = max(imageSize);

求输出值的最小值和最大值。

xMin = min([1; xlim(:)]);
xMax = max([maxImageSize(2); xlim(:)]);

yMin = min([1; ylim(:)]);
yMax = max([maxImageSize(1); ylim(:)]);

计算全景图的宽度和高度。

width  = round(xMax - xMin);
height = round(yMax - yMin);

通过设置一个维度为 [高度 宽度 3] 的全零数组,初始化全景图,该数组应与图像 I 的类型和特征相匹配。

panorama = zeros([height width 3],"like",I);

创建全景图

使用 imwarp 将图像映射到全景图中,并使用 imblend 将这些图像叠加在一起。

创建一个二维空间参考对象,用于定义全景图的尺寸。

xLimits = [xMin xMax];
yLimits = [yMin yMax];
panoramaView = imref2d([height width],xLimits,yLimits);

通过变形处理每个图像,将其转换为全景图,从而生成全景图。然后生成一个二值掩膜,并将变形后的图像叠加到全景图上。

for idx = 1:numImages
    I = readimage(buildingScene,idx);   
    warpedImage = imwarp(I,tforms(idx),OutputView=panoramaView);                 
    mask = imwarp(true(size(I,1),size(I,2)),tforms(idx),OutputView=panoramaView);
    panorama = imblend(warpedImage,panorama,mask,foregroundopacity=1);
end

imshow(panorama)

Figure contains an axes object. The hidden axes object contains an object of type image.

参考资料

[1] Matthew Brown and David G. Lowe.2007.Automatic Panoramic Image Stitching using Invariant Features.Int.J. Comput.Vision 74, 1 (August 2007), 59-73.

另请参阅

函数

对象