单应性变换函数

MVSNet网络pytorch版的单应性函数实现:

def homo_warping(src_fea, src_proj, ref_proj, depth_values):
    # src_fea: [B, C, H, W]
    # src_proj: [B, 4, 4]
    # ref_proj: [B, 4, 4]
    # depth_values: [B, Ndepth]
    # out: [B, C, Ndepth, H, W]
    batch, channels = src_fea.shape[0], src_fea.shape[1]
    num_depth = depth_values.shape[1]
    height, width = src_fea.shape[2], src_fea.shape[3]

    with torch.no_grad():
        proj = torch.matmul(src_proj, torch.inverse(ref_proj)) # 把像素坐标从ref到src  但是目的是要把源到参考?   答:特征是要src到ref,但是投影坐标是从ref到src
        rot = proj[:, :3, :3]  # [B,3,3]
        trans = proj[:, :3, 3:4]  # [B,3,1]
        # 生成网格坐标  meshgrid是ref的,因为矩阵从ref->src
        y, x = torch.meshgrid([torch.arange(0, height, dtype=torch.float32, device=src_fea.device),
                               torch.arange(0, width, dtype=torch.float32, device=src_fea.device)])
        y, x = y.contiguous(), x.contiguous()
        y, x = y.view(height * width), x.view(height * width)    # 展平为height * width个元素
        xyz = torch.stack((x, y, torch.ones_like(x)))  # [3, H*W] xyz是要得到像素坐标系(x,y,1)  [[x1...xn],[y1...yn],[1...1]],1 是齐次坐标中的常数分量
        xyz = torch.unsqueeze(xyz, 0).repeat(batch, 1, 1)  # [B, 3, H*W]  # 在指定维度 dim 插入一个新的维度
        rot_xyz = torch.matmul(rot, xyz)  # [B, 3, H*W]
        rot_depth_xyz = rot_xyz.unsqueeze(2).repeat(1, 1, num_depth, 1) * depth_values.view(batch, 1, num_depth, 1)  # [B, 3, Ndepth, H*W]  对于每个深度值的三维坐标。这个操作通过将深度值应用于旋转后的坐标,得到每个深度层的三维坐标
        proj_xyz = rot_depth_xyz + trans.view(batch, 3, 1, 1)  # [B, 3, Ndepth, H*W] 将旋转和位移应用到三维坐标上的结果,得到的是每个深度值的三维坐标
        proj_xy = proj_xyz[:, :2, :, :] / proj_xyz[:, 2:3, :, :]   # [B, 2, Ndepth, H*W]  将 proj_xyz 的前三维(x 和 y)除以第三维(z)来将三维坐标投影到二维平面上。
        proj_x_normalized = proj_xy[:, 0, :, :] / ((width - 1) / 2) - 1
        proj_y_normalized = proj_xy[:, 1, :, :] / ((height - 1) / 2) - 1
        proj_xy = torch.stack((proj_x_normalized, proj_y_normalized), dim=3)  # [B, Ndepth, H*W, 2]
        grid = proj_xy
    # grid 是一个采样网络,例如grid(0,0)表示输出图像的第一个像素是输入图像【1.5,1.5】位置的像素,实际上这个位置的像素值由插值算出来
    # grid = [[[1.5, 1.5], [2.5, 2.5]], // 第一行输出的两个像素位置
    #          [[1.5, 1.5], [2.5, 2.5]]] // 第二行输出的两个像素位置
    # 前面的几步都是为了生成采样网络,为了对原图像的特征图进行采样,采样的过程已经包含了将源图像投射到参考图像坐标系,并且建立了深度层(这里相当于是初始深度,后面还要计算实际的)
    warped_src_fea = F.grid_sample(src_fea, grid.view(batch, num_depth * height, width, 2), mode='bilinear',
                                   padding_mode='zeros')#
    warped_src_fea = warped_src_fea.view(batch, channels, num_depth, height, width)

    return warped_src_fea

0. 坐标转换

        proj = torch.matmul(src_proj, torch.inverse(ref_proj)) # 把像素坐标从ref到src  但是目的是要把源到参考?   答:特征是要src到ref,但是投影坐标是从ref到src
        rot = proj[:, :3, :3]  # [B,3,3]
        trans = proj[:, :3, 3:4]  # [B,3,1]
        # 生成网格坐标  meshgrid是ref的,因为矩阵从ref->src

这里非常关键的一点是,proj矩阵是把ref坐标转换到src坐标(src_proj乘ref-proj的逆),
就是采样的时候要按scr坐标系下采样他的特征值,比如ref的点x0,y0 要转到scr下的对应的x0’,y0’,来得到scr的特征值,作为ref 坐标下x0,y0的特征值,对应F.grid_sample函数

1. 生成采样网格 grid

y, x = torch.meshgrid([torch.arange(0, height, dtype=torch.float32, device=src_fea.device),
                       torch.arange(0, width, dtype=torch.float32, device=src_fea.device)])
y, x = y.contiguous(), x.contiguous()
y, x = y.view(height * width), x.view(height * width)
xyz = torch.stack((x, y, torch.ones_like(x)))  # [3, H*W]

这里我们生成了图像的二维像素网格坐标 (x, y),这里的坐标是参考图像坐标系下的坐标

  • torch.meshgrid 用于生成图像的网格坐标。
  • xy 表示图像的每个像素在二维平面上的位置。
  • torch.stack((x, y, torch.ones_like(x))) 将这些坐标堆叠成 [3, H*W] 形状的张量,其中第三个维度是1,表示齐次坐标。

2. 应用旋转和平移

rot_xyz = torch.matmul(rot, xyz)  # [B, 3, H*W]
rot_depth_xyz = rot_xyz.unsqueeze(2).repeat(1, 1, num_depth, 1) * depth_values.view(batch, 1, num_depth, 1)  # [B, 3, Ndepth, H*W]
proj_xyz = rot_depth_xyz + trans.view(batch, 3, 1, 1)  # [B, 3, Ndepth, H*W]
  • torch.matmul(rot, xyz) 计算旋转后的坐标。
  • rot_depth_xyz 将旋转后的坐标扩展到不同深度值上。
  • proj_xyz 将旋转和平移应用到这些三维坐标上。

2. 1参考影像视锥的建立

rot_depth_xyz = rot_xyz.unsqueeze(2).repeat(1, 1, num_depth, 1) * depth_values.view(batch, 1, num_depth, 1)  # [B, 3, Ndepth, H*W]   这行代码就是在建立参考影像的视锥

建立过程分为三步:1,建立depth维度 ->unsqueeze(2), 2,在depth维度重复num-depth层->repeat(1, 1, num_depth, 1),此时每层深度的xy坐标是相同的,建立的是一个方块,3.在方块上乘depth-value的值,不同深度的(x,y)坐标乘对应深度的深度值,也就是说在不同深度对(x,y)坐标缩放,深度越深,乘的depth值越大,也就是x,y值越大,看起来就是一个视锥。

第三步涉及到python的相乘操作

在代码 rot_depth_xyz = rot_xyz.unsqueeze(2).repeat(1, 1, num_depth, 1) * depth_values.view(batch, 1, num_depth, 1) 中,乘法操作执行的方式如下:

代码分析
  1. rot_xyz.unsqueeze(2).repeat(1, 1, num_depth, 1):

    • rot_xyz 形状为 [B, 3, H*W]
    • unsqueeze(2) 在第三个维度插入一个新的维度,将形状变为 [B, 3, 1, H*W]
    • repeat(1, 1, num_depth, 1) 将这个坐标重复 num_depth 次,使得它的形状变为 [B, 3, num_depth, H*W],即每个像素点在不同深度平面上都有一个 相同的3D 坐标。
  2. depth_values.view(batch, 1, num_depth, 1):

    • depth_values 是深度值,形状为 [B, num_depth],表示每张图像在批量中的不同深度平面上的深度值。
    • view(batch, 1, num_depth, 1) 将其重新调整为形状 [B, 1, num_depth, 1],以便与 rot_xyz 的形状相匹配。
乘法操作
  • 广播机制

    • rot_depth_xyzdepth_values 之间的乘法操作依赖于 PyTorch 的广播机制。
    • 两者形状分别是 [B, 3, num_depth, H*W][B, 1, num_depth, 1],根据广播机制,depth_values在第二和第四个维度上自动扩展,使得它的形状与 rot_depth_xyz 匹配。
  • 元素级别的乘法

    • 扩展后的 depth_valuesrot_depth_xyz 逐元素相乘
      • 对于每个 B(批次中的一张图像)和每个 num_depth(深度平面),rot_depth_xyz 中的每个 3D 坐标都乘以相应的深度值,结果是每个像素点在这个特定深度平面上的 3D 坐标。
      • 举例来说,如果 rot_xyz 中的一个元素是 [x, y, z],对应的深度值是 d,那么乘积结果就是 [dx, dy, dz]

通过这个乘法操作,每个像素点的 3D 坐标被调整为不同深度平面上的坐标,形成一个 3D 空间中的“视锥”,以便后续进行投影和变换。

3. 投影到二维平面

proj_xy = proj_xyz[:, :2, :, :] / proj_xyz[:, 2:3, :, :]  # [B, 2, Ndepth, H*W]
  • 这一步通过除以深度值,将三维坐标投影到二维平面,得到新的 (x, y) 坐标。

4. 归一化坐标并生成采样网格

proj_x_normalized = proj_xy[:, 0, :, :] / ((width - 1) / 2) - 1
proj_y_normalized = proj_xy[:, 1, :, :] / ((height - 1) / 2) - 1
proj_xy = torch.stack((proj_x_normalized, proj_y_normalized), dim=3)  # [B, Ndepth, H*W, 2]
grid = proj_xy
  • 归一化到 [-1, 1] 范围,这是 F.grid_sample 函数要求的格式。
  • proj_xy 包含了每个像素在不同深度值下的归一化二维坐标。

5. 重采样特征图

warped_src_fea = F.grid_sample(src_fea, grid.view(batch, num_depth * height, width, 2), mode='bilinear', padding_mode='zeros')
warped_src_fea = warped_src_fea.view(batch, channels, num_depth, height, width)
  • F.grid_sample 根据采样网格 grid 从源特征图中采样特征值,并进行插值(双线性插值)。
  • 将重采样后的特征图重新 reshape 为 [batch, channels, num_depth, height, width]

详细解析

  • 源图像像素的三维坐标:在实际的深度估计中,每个像素都可以看作是空间中的一个点。源图像像素的三维坐标(x, y, depth)表示该点在空间中的位置,其中 depth 是深度值。

  • 旋转和平移的目的:旋转和平移矩阵是用来将源图像的坐标转换到参考图像的坐标系中。通过这个转换,我们可以将源图像的特征映射到参考图像的视角中,从而对齐不同视角下的特征。

  • 投影到二维平面:在多视图立体视觉中,我们需要将三维坐标投影回到二维平面上,以便在图像平面上进行处理。这一步是通过将三维坐标除以深度值实现的。

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ocpro

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值