创建全景图
本示例演示了如何将多个图像自动拼接成全景图。图像拼接的流程是基于特征的图像配准的延伸。与仅对单组图像进行配准不同,该方法将多组图像对依次相互配准,从而形成全景图。
加载图像
本示例中使用的图像集包含一栋建筑的照片。这些照片是用未经标定的智能手机相机拍摄的,拍摄时将镜头从左向右沿地平线扫过,捕捉到了建筑物的各个部分。这些图像几乎未受任何镜头畸变的影响,因此无需进行相机标定。但是,如果存在镜头畸变,应在制作全景图之前对相机进行标定,并对图像去畸变。如有需要,请使用相机标定器对相机进行标定。
加载并显示图像。
buildingDir = fullfile(toolboxdir("vision"),"visiondata","building"); buildingScene = imageDatastore(buildingDir); montage(buildingScene.Files)

配对图像的配准
要创建全景图,请先按照以下步骤对连续的图像对进行配准:
检测并匹配 和 之间的特征。
求出将 映射到 的几何变换 。
计算将 映射到全景图像中并表示为 的变换。
读取图像集中的第一个图像。
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)

参考资料
[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.