【源码精读】As-Projective-As-Possible Image Stitching with Moving DLT(APAP)第一部分:全局单应Global homography

文章目录

  • 前言
  • 准备工作与全局变量
  • 基于全局单应的图像拼接过程
    • 1.读入图像,特征点检测与匹配
    • 2.数据归一化,RANSAC剔除异常值
    • 3.计算全局单应,获取拼接图大小,拼接
    • 4.加权融合
  • 总结


前言

论文及源码地址:APAP项目入口
论文精读:【论文精读】As-Projective-As-Possible Image Stitching with Moving DLT

源码用的MDLT code,解压后的文件夹是mdlt
注意,matlab是基于向量优先的!
让我们从main.m开始!

准备工作与全局变量

比较高版本的matlab需要将多线程部分改成如下代码

%poolsize = matlabpool('size');
poolsize = parpool('local');
if poolsize == 0 %if not, we attempt to do it:
    parpool open;
end

全局变量如下,fitfn等是准备好的求全局单应的函数,在文件夹modelspecific中,C1,C2是网格数

%-------------------------
% User defined parameters.
%-------------------------
% Global model specific function handlers.
clear global;
global fitfn resfn degenfn psize numpar
fitfn = 'homography_fit';
resfn = 'homography_res';
degenfn = 'homography_degen';
psize   = 4;
numpar  = 9;

M     = 500;  % Number of hypotheses for RANSAC.
thr   = 0.1;  % RANSAC threshold.

C1 = 100; % Resolution/grid-size for the mapping function in MDLT (C1 x C2).
C2 = 100;

gamma = 0.1; % Normalizer for Moving DLT. (0.0015-0.1 are usually good numbers).
sigma = 8.5;  % Bandwidth for Moving DLT. (Between 8-12 are good numbers).   
scale = 1;    % Scale of input images (maybe for large images you would like to use a smaller scale).

基于全局单应的图像拼接过程

1.读入图像,特征点检测与匹配

采用原作者注释掉的读取自己输入的两张图片形式,具体代码细节及思想见代码注释,下同。

%------------------
% Images to stitch.
%------------------
path1 = 'images/case26/6.JPG';
path2 = 'images/case26/7.JPG';

%-------------
% Read images.
%-------------
fprintf('Read images and SIFT matching\n');tic;
fprintf('> Reading images...');tic;
img1 = imresize(imread(sprintf('%s',path1)),scale);
img2 = imresize(imread(sprintf('%s',path2)),scale);
fprintf('done (%fs)\n',toc);

%--------------------------------------
% SIFT keypoint detection and matching.
%--------------------------------------
fprintf('  Keypoint detection and matching...');tic;
%single(rgb2gray(img1))将img1转换成单精度灰度图,sift固定格式,必须是单精度的
%'PeakThresh':DoG算法的阈值,值越小,检测到的特征点越多
%'edgethresh':消除DoG尺度空间峰值,值越大,检测到的特征点越多
%kp每一列是一个四元组[x,y,s,th],代表一个特征点信息,分别x,y坐标,s为长度空间大小,th指的是主方向
%ds是特征描述子,也就是那个128维的向量,128×特征点数的矩阵
[ kp1,ds1 ] = vl_sift(single(rgb2gray(img1)),'PeakThresh', 0,'edgethresh',500);
[ kp2,ds2 ] = vl_sift(single(rgb2gray(img2)),'PeakThresh', 0,'edgethresh',500);
%vl_ubcmatch:欧式距离匹配,返回匹配矩阵和匹配对间距离
%[matches, scores] = vl_ubcmatch(ds1, ds2) ;
%matches返回的是2*特征点数量矩阵,
%第一行是一幅图中的匹配点索引,第二行是另一幅图匹配点索引
%每一列是一个匹配点对索引,scores是距离
matches   = vl_ubcmatch(ds1,ds2);
fprintf('done (%fs)\n',toc);

2.数据归一化,RANSAC剔除异常值

% Normalise point distribution.
% 数据归一化的作用是方便后面RANSAC剔除错误点,消除干扰,提高精度,简化计算
fprintf('  Normalising point distribution...');tic;
%matches(1,:):匹配点第一行索引
%matches(2,:):另一张图中的匹配点索引
%kp1(1:2,matches(1,:)):提取出匹配点索引的对应横纵坐标
%size(matches,2):取matches的第二维也就是列,共482列,也就是匹配点数目
%ones(1,size(matches,2)):生成1×4821矩阵
%data_orig是6×482,每个列是两个点的[x y 1]
data_orig = [ kp1(1:2,matches(1,:)) ; ones(1,size(matches,2)) ; kp2(1:2,matches(2,:)) ; ones(1,size(matches,2)) ];
%T矩阵为3×3矩阵
%normalise2dpts作用:把一系列的齐次坐标[x y 1]归一化,使得这些点以原点为中心,距离原点均值为sqrt(2)[ dat_norm_img1,T1 ] = normalise2dpts(data_orig(1:3,:));
[ dat_norm_img2,T2 ] = normalise2dpts(data_orig(4:6,:));

%data_norm:6*482
data_norm = [ dat_norm_img1 ; dat_norm_img2 ];
fprintf('done (%fs)\n',toc);

% %展示输入图像
% if size(img1,1) == size(img2,1)    
%     % Show input images.
%     fprintf('  Showing input images...');tic;
%     figure;
%     imshow([img1,img2]);
%     title('Input images');
%     fprintf('done (%fs)\n',toc);
% end

%-----------------
% Outlier removal.前半部分没读懂代码 mutigs文件夹里有该函数
%-----------------
fprintf('Outlier removal\n');tic;
% Multi-GS
%设置随机种子,rand('state',0),相当于C++中的srand(0)
rng(0);
[ ~,res,~,~ ] = multigsSampling(100,data_norm,M,10);
con = sum(res<=thr);
[ ~, maxinx ] = max(con);
%找到匹配度最高的特征点序列,inliers存的是匹配对的索引
inliers = find(res(:,maxinx)<=thr);

if size(img1,1) == size(img2,1)
    % Show results of RANSAC.
    fprintf('  Showing results of RANSAC...');tic;
    figure;
    imshow([img1 img2]);
    %添加新绘图保持原绘图
    hold on;
    %ro是形状:红圈,LineWidth线宽为2
    %data_orig前两行是一个图的匹配点,用红圈画,45行是另一个图的点,红圈画
    plot(data_orig(1,:),data_orig(2,:),'ro','LineWidth',2);
    %+size(img1,2)为了让它在第二张图位置显示
    plot(data_orig(4,:)+size(img1,2),data_orig(5,:),'ro','LineWidth',2);
    
    for i=1:length(inliers)
        %选择对应内点索引的坐标,12行是一幅图,45行是另一幅图,绿圈画
        plot(data_orig(1,inliers(i)),data_orig(2,inliers(i)),'go','LineWidth',2);
        plot(data_orig(4,inliers(i))+size(img1,2),data_orig(5,inliers(i)),'go','LineWidth',2);
        %14行对应横坐标矩阵,25行对应纵坐标矩阵,绿线相连
        plot([data_orig(1,inliers(i)) data_orig(4,inliers(i))+size(img1,2)],[data_orig(2,inliers(i)) data_orig(5,inliers(i))],'g-');
    end
    title('Ransac''s results');
    fprintf('done (%fs)\n',toc);
end

3.计算全局单应,获取拼接图大小,拼接

%-----------------------
% Global homography (H).计算全局单应
%-----------------------
fprintf('DLT (projective transform) on inliers\n');
% Refine homography using DLT on inliers.
fprintf('> Refining homography (H) using DLT...');tic;
%feval调用参数中fitfn函数,参数为data_norm(:,inliers),即归一化后数据内点索引所在的列
[ h,A,D1,D2 ] = feval(fitfn,data_norm(:,inliers));
%上一步处理之后要再处理回去
Hg = T2\(reshape(h,3,3)*T1);%Hg是全局单应矩阵
fprintf('done (%fs)\n',toc);

%----------------------------------------------------
% Obtaining size of canvas (using global Homography).
%----------------------------------------------------
fprintf('Canvas size and offset (using global Homography)\n');
fprintf('> Getting canvas size...');tic;
%获得拼接图大小,左图不变,右图单应变换
% Map four corners of the right image.
%由于求得的H 是将左图映射到右图,
%即 H*x_left = x_right,所以 x_left = inv(H) * x_right.
%取右图的四个顶点的齐次坐标 分别作为 x_right 的值
%以img1左顶点为坐标源点,得到新的四个顶点坐标:TL, BL, TR, BR
TL = Hg\[1;1;1];%inv(Hg)*[1;1;1], 得到非齐次形式
TL = round([ TL(1)/TL(3) ; TL(2)/TL(3) ]);% 齐次化!
BL = Hg\[1;size(img2,1);1];
BL = round([ BL(1)/BL(3) ; BL(2)/BL(3) ]);
TR = Hg\[size(img2,2);1;1];
TR = round([ TR(1)/TR(3) ; TR(2)/TR(3) ]);
BR = Hg\[size(img2,2);size(img2,1);1];
BR = round([ BR(1)/BR(3) ; BR(2)/BR(3) ]);

% Canvas size.确定画布的尺寸(cw, ch)
% 投影面的 宽,通过最大的x()-最小的x()+1
cw = max([1 size(img1,2) TL(1) BL(1) TR(1) BR(1)]) - min([1 size(img1,2) TL(1) BL(1) TR(1) BR(1)]) + 1;
% 投影面的 高
ch = max([1 size(img1,1) TL(2) BL(2) TR(2) BR(2)]) - min([1 size(img1,1) TL(2) BL(2) TR(2) BR(2)]) + 1;
fprintf('done (%fs)\n',toc);

% Offset for left image.确定左图的偏移量off,即左图img1 左顶点 在画布坐标系中的 坐标
fprintf('> Getting offset...');tic;
off = [ 1 - min([1 size(img1,2) TL(1) BL(1) TR(1) BR(1)]) + 1 ; 1 - min([1 size(img1,1) TL(2) BL(2) TR(2) BR(2)]) + 1 ];
fprintf('done (%fs)\n',toc);

%--------------------------------------------
% Image stitching with global homography (H).
%--------------------------------------------
% Warping source image with global homography 
fprintf('Image stitching with global homography (H) and linear blending\n');
fprintf('> Warping images by global homography...');tic;
%创建一个拼接图的空画布
warped_img1 = uint8(zeros(ch,cw,3));
%将左图按照左图偏移off放到画布上
warped_img1(off(2):(off(2)+size(img1,1)-1),off(1):(off(1)+size(img1,2)-1),:) = img1;
%调用imagewarping.cpp,实现warp
warped_img2 = imagewarping(double(ch),double(cw),double(img2),Hg,double(off));
%c++中维度变了,变回matlab所需维度
warped_img2 = reshape(uint8(warped_img2),size(warped_img2,1),size(warped_img2,2)/3,3);
fprintf('done (%fs)\n',toc);

在这之后我添加了单张图放在画布上的结果,以及融合之前的结果

%--------------------------------------------
% 显示单独放在画布上的两张图和融合前的拼接图
%--------------------------------------------
%得到的warped_img1,warped_img2是单独放在整个画布上的
figure;
imshow(warped_img1);
title('warpedimg1');
figure;
imshow(warped_img2);
title('warpedimg2');

output_canvas(:,:,1) = warped_img1(:,:,1)+warped_img2(:,:,1);
output_canvas(:,:,2) = warped_img1(:,:,2)+warped_img2(:,:,2);
output_canvas(:,:,3) = warped_img1(:,:,3)+warped_img2(:,:,3);
output_canvas = uint8(output_canvas);

figure;
imshow(output_canvas);
title('output_canvas');

融合前的效果:

可见融合是为了消除两张拼接图光照不同的影响。

4.加权融合

% Blending images by simple average (linear blending)
fprintf('  Homography linear image blending (averaging)...');tic;
linear_hom = imageblending(warped_img1,warped_img2);
fprintf('done (%fs)\n',toc);
figure;
imshow(linear_hom);
title('Image Stitching with global homography');

imageblending.m:

function output_canvas = imageBlending(warped_img1,warped_img2)
    %本函数作用:加权平均融合
    
    %将输入warp图形二值化并填充孔
    w1 = imfill(im2bw(uint8(warped_img1), 0),'holes');
    w2 = imfill(im2bw(uint8(warped_img2), 0),'holes');
    
    %转换为灰度图
    w1 = mat2gray(w1);
    w2 = mat2gray(w2);
    
    %注意转换类型
    %matlab处理图像一定先将图像转换为double,im2double
    %1 有些函数支持double型,而不支持uint8的数据类型,所以要转换
    %2 精度问题了,因为uint8进行数据处理的时候,容易造成数据溢出或精度不够。
    warped_img1 = double(warped_img1);
    warped_img2 = double(warped_img2);
    %每个维度根据两张图的二值化灰度图进行融合,主要是处理两张图亮度不同
    %注意都是矩阵运算: .*,./ 点乘点除
    output_canvas(:,:,1) = ((warped_img1(:,:,1).*w1)+(warped_img2(:,:,1).*w2))./(w1+w2);
    output_canvas(:,:,2) = ((warped_img1(:,:,2).*w1)+(warped_img2(:,:,2).*w2))./(w1+w2);
    output_canvas(:,:,3) = ((warped_img1(:,:,3).*w1)+(warped_img2(:,:,3).*w2))./(w1+w2);
    %最后转换回uint8
    output_canvas = uint8(output_canvas);

融合之后的结果:

消除了光照带来的影响,但是没对齐。

总结

本章讲述了用matlab实现全局单应Global homography的过程及源码分析,本章并不算论文APAP中的部分,而是用来对比APAP方法的优点的,那么我们将在下一章重点讲解APAP的实现原理及过程,欢迎对图像拼接领域的感兴趣的朋友们讨论!

你可能感兴趣的:(图像拼接论文源码精读,matlab,计算机视觉,图像拼接,APAP,image,stitching)