SPHP_stitching算法解读(一)

function SPHP_stitching(in_name, edge_list, ref, tar, warp_type, zeroR_ON)  
  
% 输入参数:  
% in_name: 一个cell数组,包含要拼接的图像的文件名  
% edge_list: 一个矩阵,每一行包含两个图像的索引,表示这两个图像是相邻的  
% ref: 参考图像的索引  
% tar: 目标图像的索引(通常是要拼接到的图像)  
% warp_type, zeroR_ON: 
  
img_n = size(in_name, 1); % 图像的数量  
edge_n = size(edge_list, 1); % 相邻图像对的数量  
  
% 检查edge_list中的索引对是否有自环(即i不等于j)  
for ei = 1 : edge_n  
    i = edge_list(ei, 1);  
    j = edge_list(ei, 2);  
    if i == j  
        fprintf('Index pair error.\n'); % 打印错误信息  
        pause; % 暂停程序  
    end  
end  
  
% 加载图像  
I = cell(img_n, 1); % 初始化一个cell数组来存储图像  
for i = 1 : img_n  
    I{i} = imread(in_name{i}); % 读取图像  
end  
  
% 预处理  
% T{i}是一个坐标变换矩阵,用于将图像坐标(原点在图像的左上角,x向右,y向下)  
% 转换为标准坐标(原点在图像中心,x向右,y向上)  
max_size = 1000 * 1000; % 最大像素数量限制  
imgw = zeros(img_n, 1); % 初始化图像的宽度数组  
imgh = zeros(img_n, 1); % 初始化图像的高度数组  
resized_in_name = cell(img_n, 1); % 初始化一个cell数组来存储调整大小后的图像的文件名  
T = cell(img_n, 1); % 初始化一个cell数组来存储坐标变换矩阵  
for i = 1 : img_n  
    % 如果图像的像素数量超过了最大限制,则对其进行下采样  
    if numel(I{i}(:, :, 1)) > max_size  
        I{i} = imresize(I{i}, sqrt(max_size / numel(I{i}(:, :, 1))));  
    end  
    resized_in_name{i} = sprintf('%s_resized%s', in_name{i}(1:end-4), in_name{i}(end-3:end)); % 生成新的文件名  
    imwrite(I{i}, resized_in_name{i}); % 保存调整大小后的图像  
    imgw(i) = size(I{i}, 2); % 获取图像的宽度  
    imgh(i) = size(I{i}, 1); % 获取图像的高度  
    % 计算坐标变换矩阵  
    T{i} = [1  0 -(imgw(i)+1)/2; ...  
            0 -1  (imgh(i)+1)/2; ...  
            0  0  1];  
end  
  
% 将图像转换为双精度浮点数格式  
for i = 1 : img_n  
    I{i} = im2double(I{i});  
end  
  
% 计算图像对之间的单应性矩阵  
H = cell(img_n, img_n); % H{i,j}是从I{j}到I{i}的变换矩阵(使用标准坐标)  
for i = 1 : img_n  
    H{i, i} = eye(3); % 对角线上的元素是单位矩阵  
end  
for ei = 1 : edge_n  
    i = edge_list(ei, 1);  
    j = edge_list(ei, 2);  
    tmpH = sift_mosaic(I{i}, I{j}); % 使用SIFT算法计算从I{i}到I{j}的单应性矩阵(使用图像坐标)
    disp(tmpH)
    % 将坐标从图像坐标转换为标准坐标  
    H{j, i} = T{j} * tmpH * inv(T{i});  
    H{i, j} = inv(H{j, i}); % 计算逆变换  
end  
mdltpara = [];  
mdlt_results = [];  
% 对于每张图像I,计算从I到参考图像的单应性矩阵,以及从I到目标图像的单应性矩阵  
% 构造图  
G = sparse([edge_list(:, 1); edge_list(:, 2)], ...  
           [edge_list(:, 2); edge_list(:, 1)], ...  
           [ones(edge_n, 1); ones(edge_n, 1)], img_n, img_n); % 使用稀疏矩阵表示图  
disp(edge_list(:, 1))
% 计算从第i张图像到参考图像的单应性矩阵  
for i = 1 : img_n  
    if i == ref  
        continue;  
    end  
    [~, path, ~] = graphshortestpath(G, i, ref); % 计算从第i张图像到参考图像的最短路径  
    tmpH = eye(3);  
    for ppi = 1 : numel(path)-1  
        tmpH = H{path(ppi+1), path(ppi)} * tmpH; % 累积单应性矩阵  
    end  
    H{ref, i} = tmpH;  
    H{i, ref} = inv(H{ref, i}); % 计算逆变换  
end  
  
% 计算从第i张图像到目标图像的单应性矩阵  
for i = 1 : img_n  
    if i == tar  
        continue;  
    end  
    [~, path, ~] = graphshortestpath(G, i, tar); % 计算从第i张图像到目标图像的最短路径  
    tmpH = eye(3);  
    for ppi = 1 : numel(path)-1  
        tmpH = H{path(ppi+1), path(ppi)} * tmpH; % 累积单应性矩阵  
    end  
    H{tar, i} = tmpH;  
    H{i, tar} = inv(H{tar, i}); % 计算逆变换  
end  
  
% 规范化所有的单应性矩阵,使得h9 = 1  
for hi = 1 : numel(H)  
    if H{hi}  
        H{hi} = H{hi} / H{hi}(3, 3);  
    end  
end  
  
% 提取从参考图像到目标图像的单应性矩阵的参数,以便计算我们的warp  
H0 = H{tar, ref};  
A = H0(1:2, 1:2);  
t = H0(1:2, 3);  
h31 = H0(3, 1);  
h32 = H0(3, 2);  
B = A - t*[h31 h32];  
c = sqrt(h31^2+h32^2);  
theta = atan2(-h32, -h31); % 计算ub1和ub2所需的角度 
disp(H0)
disp(A)
disp(t)
disp(h31)
disp(h32)
return;
%% compute ub1 and ub2  
% 这部分代码的目的是计算图像变换后的边界值ub1和ub2,以及为优化过程准备一个成本表totalcost_table。  
  
% 初始化一个用于存储计算结果的数组cu,其大小与图像数量img_n相同。  
% cu数组将用于存储每张图像经过变换后在参考图像中的x坐标。  
cu = zeros(img_n, 1);  
  
% 对每一张图像进行遍历。  
for i = 1 : img_n  
    % 使用apply_transform函数将第i张图像的中心点(0, 0)变换到参考图像的坐标系中。  
    % H{ref, i}是一个单应性矩阵,描述了从第i张图像到参考图像的变换。  
    % 返回的tmpx和tmpy是经过变换后的x和y坐标。  
    [tmpx, tmpy] = apply_transform(0, 0, H{ref, i});  
      
    % 再次使用apply_transform函数,这次是为了应用一个旋转变换。  
    % theta是旋转角度,这里没有给出其定义,可能是在代码的其他部分定义的。  
    % 这个旋转变换将上一步得到的tmpx和tmpy进一步变换。  
    % 注意这里的~[...]表示我们不关心这个输出值,即用~忽略了第二个输出。  
    [cu(i), ~] = apply_transform(tmpx, tmpy, [cos(theta), sin(theta), 0; ...  
                                             -sin(theta), cos(theta), 0; ...  
                                                       0,          0, 1]);  
end  
  
% 从cu数组中找到最小和最大的x坐标值,这些值将用于定义优化的边界。  
oriub1 = min(cu);  
oriub2 = max(cu);  
  
% 定义采样间隔s_itv,这里设置为20。  
s_itv = 20; % sample interval  
  
% 使用meshgrid函数创建两个二维网格矩阵offset_table1和offset_table2。  
% 这些矩阵将用于在优化过程中尝试不同的偏移量组合。  
% meshgrid的参数定义了网格的范围和步长,这里offset_table1的范围是-300到600,步长为10;  
% offset_table2的范围是-300到200,步长也为10。  
[offset_table1, offset_table2] = meshgrid(-300:10:600, -300:10:200);  
  
% 初始化totalcost_table为零矩阵,其大小与offset_table1相同。  
% 这个矩阵将用于存储在优化过程中计算的总成本。  
totalcost_table = zeros(size(offset_table1));
% 遍历offset_table1和offset_table2的所有元素,这两个表格定义了可能的偏移量组合  
for oi = 1 : size(offset_table1, 1)  
for oj = 1 : size(offset_table1, 2)  
      
    % 根据当前的偏移量组合更新ub1和ub2的值  
    ub1 = oriub1 + offset_table1(oi, oj);  
    ub2 = oriub2 + offset_table2(oi, oj);  
      
    % 如果ub2和ub1的差小于120,则将该组合的成本设置为NaN,并继续下一个组合  
    if ub2 - ub1 < 120  
        totalcost_table(oi, oj) = nan;  
        continue;  
    end  
      
    % 计算用于图像变换的系数c1para  
    c1para = compute_c1_warp_coeff(H0, t, c, theta, ub1, ub2, zeroR_ON);  
      
    % 定义旋转变换的逆矩阵  
    invR = [cos(theta), sin(theta), 0; ...  
           -sin(theta), cos(theta), 0; ...  
                     0,          0, 1];  
      
    % 初始化成本列表,用于存储每个图像的成本  
    cost_list = zeros(1, img_n);  
      
    % 遍历每个图像  
    for i = 1 : img_n  
          
        % 在图像上创建网格,用于后续的坐标变换  
        x = linspace(1, imgw(i), ceil(imgw(i)/s_itv));  
        y = linspace(1, imgh(i), ceil(imgh(i)/s_itv));  
        [x, y] = meshgrid(x, y);  
          
        % 应用变换T{i}来改变坐标  
        [x, y] = apply_transform(x, y, T{i});  
          
        % 计算从第i个图像到参考图像的变换矩阵tmpH  
        tmpH = invR * H{ref, i};  
          
        % 应用变换tmpH得到在参考图像中的坐标  
        [u, v] = apply_transform(x, y, tmpH);  
          
        % 根据u的值定义三个区域  
        reg1_mask = u < ub1;  
        reg2_mask = u > ub1 & u < ub2;  
        reg3_mask = u > ub2;  
          
        % 初始化用于存储Jacobian矩阵元素的映射  
        J11_map = zeros(size(x));  
        J12_map = zeros(size(x));  
        J21_map = zeros(size(x));  
        J22_map = zeros(size(x));  
          
        % 遍历每个区域  
        for reg = 1 : 3  
              
            % 根据区域设置相应的掩码  
            if reg == 1  
                reg_mask = u < ub1;  
            elseif reg == 3  
                reg_mask = u > ub2;  
            else  
                reg_mask = ~(u < ub1 | u > ub2);  
            end  
              
            % 如果没有有效的点,则继续下一个区域  
            if nnz(reg_mask) == 0  
                continue;  
            end  
              
            % 提取满足当前区域条件的坐标  
            x1 = x(reg_mask);  
            y1 = y(reg_mask);  
            u1 = u(reg_mask);  
            v1 = v(reg_mask);  
              
            % 根据区域设置变换矩阵A  
            if reg == 1  
                A = c1para.H * H{ref, i};  
            elseif reg == 2  
                A = tmpH;  
            elseif reg == 3  
                A = c1para.S * H{ref, i};  
            end  
              
            % 归一化变换矩阵A  
            A = A / A(3, 3);  
              
            % 提取变换矩阵A的元素  
            h1 = A(1, 1); h2 = A(1, 2); h3 = A(1, 3); h4 = A(2, 1); h5 = A(2, 2); h6 = A(2, 3); h7 = A(3, 1); h8 = A(3, 2);  
              
            % 计算Jacobian矩阵的元素  
            J11 = h1./(h7*x1 + h8*y1 + 1) - (h7*(h3 + h1*x1 + h2*y1))./(h7*x1 + h8*y1 + 1).^2;  
            J12 = h2./(h7*x1 + h8*y1 + 1) - (h8*(h3 + h1*x1 + h2*y1))./(h7*x1 + h8*y1 + 1).^2;  
            J21 = h4./(h7*x1 + h8*y1 + 1) - (h7*(h6 + h4*x1 + h5*y1))./(h7*x1 + h8*y1 + 1).^2;  
            J22 = h5./(h7*x1 + h8*y1 + 1) - (h8*(h6 + h4*x1 + h5*y1))./(h7*x1 + h8*y1 + 1).^2;  
              
            % 如果是第二个区域,还需要进一步计算Jacobian矩阵  
            if reg == 2  
                if c1para.zeroR_ON == 0  
                    JT11 = (2*c1para.a(1)*u1 + c1para.a(2)) .* v1 + (2*c1para.b(1)*u1 + c1para.b(2));  
                    JT12 = c1para.a(1)*u1.^2 + c1para.a(2)*u1 + c1para.a(3);  
                    JT21 = (2*c1para.e(1)*u1 + c1para.e(2)) .* v1 + (2*c1para.f(1)*u1 + c1para.f(2));  
                    JT22 = c1para.e(1)*u1.^2 + c1para.e(2)*u1 + c1para.e(3);  
                else  
                    JT11 = (3*c1para.a(1)*u1.^2 + 2*c1para.a(2)*u1 + c1para.a(3)) .* v1 + (2*c1para.b(1)*u1 + c1para.b(2));  
                    JT12 = c1para.a(1)*u1.^3 + c1para.a(2)*u1.^2 + c1para.a(3)*u1 + c1para.a(4);  
                    JT21 = (3*c1para.e(1)*u1.^2 + 2*c1para.e(2)*u1 + c1para.e(3)) .* v1 + (2*c1para.f(1)*u1 + c1para.f(2));  
                    JT22 = c1para.e(1)*u1.^3 + c1para.e(2)*u1.^2 + c1para.e(3)*u1 + c1para.e(4);  
                end  
                tmp11 = JT11.*J11 + JT12.*J21;  
                tmp12 = JT11.*J12 + JT12.*J22;  
                tmp21 = JT21.*J11 + JT22.*J21;  
                tmp22 = JT21.*J12 + JT22.*J22;  
                J11 = tmp11; J12 = tmp12; J21 = tmp21; J22 = tmp22;  
            end  
              
            % 更新Jacobian矩阵映射  
            J11_map(reg_mask) = J11;  
            J12_map(reg_mask) = J12;  
            J21_map(reg_mask) = J21;  
            J22_map(reg_mask) = J22;  
        end
        % 计算平均的alpha和beta值 
        % 计算平均的alpha值  
        % 通过将J11_map和J22_map中的所有元素相加,然后除以x的元素数量的两倍来得到  
        avg_alpha = (sum(J11_map(:)) + sum(J22_map(:))) / (numel(x)*2);   
        % 计算平均的beta值  
        % 通过将J21_map中的所有元素相加,然后减去J12_map中的所有元素之和,  
        % 最后再除以x的元素数量的两倍来得到  
        avg_beta = (sum(J21_map(:)) + (-sum(J12_map(:)))) / (numel(x)*2);  
        % 计算cost  
        % cost是通过计算每个map与其对应的平均值的差的平方和来得到的  
        cost = sum( (J11_map(:)-avg_alpha).^2 + (J12_map(:)-(-avg_beta)).^2 + ...  
                    (J21_map(:)-avg_beta).^2  + (J22_map(:)-avg_alpha).^2 );  
          
        % 将计算得到的cost保存到cost_list中的第i个位置  
        cost_list(i) = cost;
    end
    % 计算cost_list中所有cost的总和  
    totalcost = sum(cost_list);  
    % 将计算得到的总cost保存到totalcost_table中的(oi, oj)位置  
    totalcost_table(oi, oj) = totalcost;
end
end
% 在totalcost_table中找到最小值的位置索引  
% idx1和idx2分别表示该最小值在totalcost_table中的行索引和列索引  
[idx1, idx2] = find(totalcost_table == min(totalcost_table(:)));  
  
% 使用找到的索引idx1和idx2从offset_table1中获取对应的偏移量,并更新ub1的值  
ub1 = oriub1 + offset_table1(idx1, idx2);  
  
% 使用找到的索引idx1和idx2从offset_table2中获取对应的偏移量,并更新ub2的值  
ub2 = oriub2 + offset_table2(idx1, idx2);
%% Compute parameters/coefficients of our warp
% 调用compute_c1_warp_coeff函数来计算warp的c1系数  
% 输入参数包括:H0, t, c, theta, ub1, ub2, 和 zeroR_ON  
% 返回的c1para将包含计算得到的warp系数  
c1para = compute_c1_warp_coeff(H0, t, c, theta, ub1, ub2, zeroR_ON);
%% c1 warp for each image  
% 这一部分代码将对每个图像应用c1类型的warp(扭曲/变形)  
  
img_grid_size = 10; % for warping  
% 设置用于warp的网格大小为10  
  
[c1out, c1omask, Td] = texture_mapping(resized_in_name, imgw, imgh, img_grid_size, T, warp_type, H, ref, tar, c1para, mdltpara, 0, 'white');  
% 调用texture_mapping函数来对resized_in_name中的图像进行纹理映射(可能是warp的一种形式)  
% 输入参数包括:图像名称、图像宽度、图像高度、网格大小、变换矩阵T、warp类型、单应性矩阵H、参考图像、目标图像、c1warp系数、模型参数、填充选项和填充颜色  
% 函数返回:warp后的图像c1out、对应的遮罩c1omask以及可能的变换数据Td  
  
for i = 1 : img_n  
    % 遍历所有图像  
    %figure; imshow(c1out{i}); % the warped result of each image  
    % (可选)为每个warp后的图像创建一个新窗口并显示它  
    % 注意:这行代码被注释掉了,因此不会实际执行显示图像的操作  
end  
  
for i = 1 : img_n  
    eval(['delete ' resized_in_name{i}]);  
    % 遍历所有图像并删除对应的内存中的变量  
    % 使用eval函数执行删除操作,这可能是因为resized_in_name{i}包含了需要删除的变量的名称  
    % 注意:通常不推荐使用eval,因为它可能带来安全风险和执行效率问题  
    % 一个更好的做法可能是直接将图像数据存储在一个数组中,然后通过索引来管理它们  
end

%% composite by linear blending  
% 通过线性混合来合成图像的代码段  
  
for i = 1 : img_n  
    % 遍历所有图像  
    if i == 1  
        % 对于第一张图像,直接将其设置为输出  
        out = c1out{1};  
        out_mask = c1omask{1};  
        % 计算输出图像的中心  
        [r, c] = find(c1omask{1}(:, :, 1));  
        out_center = [mean(r) mean(c)];  
    else % blend out and c1out{i}  
        % 对于其他图像,将其与之前的输出进行混合  
        % 计算当前图像的中心  
        [r, c] = find(c1omask{i}(:, :, 1));  
        out_i_center = [mean(r) mean(c)];  
        % 计算加权遮罩  
        vec = out_i_center - out_center; % 从out_center到out_i_center的向量  
        intsct_mask = c1omask{i}(:, :, 1) & out_mask(:, :, 1); % 交集遮罩,单通道  
        out_only_mask = out_mask(:, :, 1) - intsct_mask;  
        out_i_only_mask = c1omask{i}(:, :, 1) - intsct_mask;  
          
        [r, c] = find(intsct_mask(:, :, 1));  
        idx = sub2ind(size(c1omask{i}(:, :, 1)), r, c);  
        out_wmask = zeros(size(c1omask{i}(:, :, 1)));  
        proj_val = (r - out_center(1))*vec(1) + (c- out_center(2))*vec(2); % 向量的内积  
        % 计算加权遮罩的值(重叠区域的权重图)  
        out_wmask(idx) = (proj_val - (min(proj_val)+(1e-3))) / ...  
                         ((max(proj_val)-(1e-3)) - (min(proj_val)+(1e-3)));  
        % 混合图像  
        mask1 = out_mask(:, :, 1)&(out_wmask==0);  
        mask2 = out_wmask;  
        mask3 = c1omask{i}(:, :, 1)&(out_wmask==0);  
        mask1 = cat(3, mask1, mask1, mask1); mask2 = cat(3, mask2, mask2, mask2); mask3 = cat(3, mask3, mask3, mask3);  
        out = out.*(mask1+(1-mask2).*(mask2~=0)) + c1out{i}.*(mask2+mask3);  
        % 更新遮罩和中心  
        out_mask = out_mask | c1omask{i};  
        out_center = out_i_center; % 用当前图像的中心更新输出中心  
    end  
end  
c1all = out;  
% 计算分母,用于处理背景色  
denom = zeros(size(c1out{1}));  
for i = 1 : img_n  
    denom = denom + c1omask{i};  
end  
bgcolor = 'white'; % 设置背景色  
% 根据背景色设置未覆盖区域的像素值  
if strcmp(bgcolor, 'white')  
    c1all(denom==0) = 1; % 如果是白色背景,则设置为1(白色)  
else  
    c1all(denom==0) = 0; % 否则设置为0(黑色)  
end  
figure; imshow(c1all); % 显示最终结果

% for i = 1 : img_n
%     imwrite(c1out{i}, sprintf('%s_out%d.png', warp_type, i));
% end
% imwrite(c1all, [warp_type '_outall.png']);
  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值