source code from yenchenlin:
https://github.com/yenchenlin/nerf-pytorch

作者对于numpy的各种操作出神入化,其精炼程度令人叹为观止。本文总结其中两个函数的物理模型意义与(尤其是)矩阵计算意义,作为学习记录。

物理模型意义

详见:
https://zhuanlan.zhihu.com/p/593204605/
中的《3D空间射线怎么构造》。

矩阵计算意义(重点)

比较get_rays和get_rays_np可以发现,前者是在pytorch中、后者实在numpy中的同一操作(所以后者函数名以“np”结尾)。因此我们选择其中一个进行研究即可(get_rays):

def get_rays(H, W, K, c2w):i, j = torch.meshgrid(torch.linspace(0, W-1, W), torch.linspace(0, H-1, H))  # pytorch's meshgrid has indexing='ij'i = i.t()j = j.t()dirs = torch.stack([(i-K[0][2])/K[0][0], -(j-K[1][2])/K[1][1], -torch.ones_like(i)], -1)# Rotate ray directions from camera frame to the world framerays_d = torch.sum(dirs[..., np.newaxis, :] * c2w[:3,:3], -1)  # dot product, equals to: [c2w.dot(dir) for dir in dirs]# Translate camera frame's origin to the world frame. It is the origin of all rays.rays_o = c2w[:3,-1].expand(rays_d.shape)return rays_o, rays_d

接下来进行我学习花费良久的逐行解释——

输入参数

调用该函数的函数有相关注解:

H: int. Height of image in pixels.
W: int. Width of image in pixels.
c2w: array of shape [3, 4]. Camera-to-world transformation matrix.

K则是一个(3x3)矩阵。

第一行

    i, j = torch.meshgrid(torch.linspace(0, W-1, W), torch.linspace(0, H-1, H))

作者给了一句尾注:

pytorch’s meshgrid has indexing=‘ij’

torch.linspace(0,W-1,W)

的意思,从0到W-1取一共W个点,弄成一个行向量。同理,

torch.linspace(0,H-1,H)

从0到W-1取一共W个点,弄成一个行向量。然后把两个放入torch的meshgrid,就可以得到一个以第一个参数为而重复的矩阵,以及一个以第二个参数为而重复的矩阵。注意,这一点和numpy的meshgrid是恰恰相反的(无语)。所以这就解释了第二行和第三行(numpy的计算相对更加符合思考的惯用形式,相对):

i = i.t()
j = j.t()

另外,和numpy的meshgrid相比,torch的meshgrid自带默认的类似前者的“indexing=‘xy’”的功能。

第四行

  dirs = torch.stack([(i-K[0][2])/K[0][0], -(j-K[1][2])/K[1][1], -torch.ones_like(i)], -1)

这里,K[0][2])/K[0][0]只是一个标量,而i是一个(H,W)的矩阵,那这样就意味着广播的介入,因此i-K[0][2])/K[0][0]就是一个(H,W)的矩阵:它意思是每个像素点的横坐标都根据https://zhuanlan.zhihu.com/p/593204605/
中的《3D空间射线怎么构造》的公式计算好了。显然,每个像素点的纵坐标也同样通过一个(H,W)的矩阵 -(j-K[1][2])/K[1][1]得到了。类似地,z坐标的情况则是 -torch.ones_like(i)

好,那么torch.stack在这里是要做什么呢?观察其axis参数为-1。我参考了https://blog.csdn.net/weixin_44201525/article/details/109769214的讲法,特别是:

axis为0,表示它堆叠方向为第0维,堆叠的内容为数组第0维的数据。前面说了第0维是相对于堆叠的数组而言的,而这里数组的第0维其实就是整个3×4的数组(其中第1维为行,第2维为某一行中的一个值,这里有一个层层深入的感觉),所以就是以整个3×4的数组为堆叠内容在第0维上进行堆叠,等到的结果就是一个3×3×4的新数组。再通俗一点,就是将a,b,c分别作为堆叠内容进行堆叠得到3×3×4的输出。

以及

和刚才的解释一样,axis为1表示堆叠的方向为3×4数组的第1维(行),堆叠内容也为3×4数组的第1维的数据。而3×4的数组的第1维就是它的行,以数组a为例,它的堆叠数据分别是[0 1 2 3],[ 4 5 6 7],[ 8 9 10 11]。

意思就是说,根据层层深入的思想,axis=-1,也就是最后一个维度,那么就可以理解为,你通过这个维度之前的一层层维度,深入到了这个维度,然后开始堆叠。

所以我们看,这里dirs就是一个像素点一个像素点地“堆叠”,其中每个像素点的信息就是它的xyz坐标。

第五、六行

    # Rotate ray directions from camera frame to the world framerays_d = torch.sum(dirs[..., np.newaxis, :] * c2w[:3,:3], -1)  # dot product, equals to: [c2w.dot(dir) for dir in dirs]

这里确实十分令人头疼!
如前所述,dirs的维度在上一行应该是(W,H,3),其中最后的3“遍历”每个点的x、y、z。现在,

dirs[...,np.newaxis,:]

可以得到一个(W, H, 1, 3)的矩阵。那么它和c2w[:3,:3]的关系是啥?阅读https://blog.csdn.net/qq_51352578/article/details/125074264 学习numpy的广播机制,可以知道,它这里插入一个新的1维度,可以让逐点乘法*得以完成。但是这也不是乱加的axis。我们要问,这个操作的物理意义是啥?
事实上,答案和上一行的解读类似。就是说,你插入了一个newaxis,那么广播的时候你就自己复制了之前维度的东西。在咱的场景里,这个之前的维度不是别的,正是一个个像素点!事实上,c2w[:3,:3] 即3列分别表达关于x轴、y轴、z轴的信息
(参见 c2w矩阵的值直接描述了相机坐标系的朝向和原点 )。这里的*运算可理解为:


> (。。。)  点 点 点    *    c2w(3,3)
>           口 口 口
>            口 口 口
>            口 口 口

然后sum就是按列求和(其中同一个点被案列复制了三遍,这就是加了个newaxis的效果!,有转置的特性)。这也符合作者注释里面说的:

dot product, equals to: [c2w.dot(dir) for dir in dirs]

即每个dir是锁定了横坐标的点坐标数据,然后被c2w左乘。

第七、八行

Translate camera frame's origin to the world frame. It is the origin of all rays.rays_o = c2w[:3,-1].expand(rays_d.shape)

参看expand
也就是指定维度的一种广播

第九行

return rays_o, rays_d

不难知道此时返回的两个都是维度为(H,W,3)

解读nerf_pytorch中的get_rays和get_rays_np函数相关推荐

  1. R语言笔记6:在R中写一些简单的函数、functions基础和作用域

    R语言基础系列: 1数据类型(向量.数组.矩阵. 列表和数据框) 2读写数据所需的主要函数.与外部环境交互 3数据筛选--提取对象的子集 4向量.矩阵的数学运算 5控制结构 Your first R ...

  2. 初步解读Golang中的接口相关编写方法

    初步解读Golang中的接口相关编写方法 概述如果说goroutine和channel是Go并发的两大基石,那么接口是Go语言编程中数据类型的关键.在Go语言的实际编程中,几乎所有的数据结构都围绕接口 ...

  3. python方法_详细解读Python中的__init__()方法

    __init__()方法意义重大的原因有两个.第一个原因是在对象生命周期中初始化是最重要的一步:每个对象必须正确初始化后才能正常工作.第二个原因是__init__()参数值可以有多种形式. 因为有很多 ...

  4. return在php中用法,细致解读PHP中return用法(附代码)_后端开发

    在大部分编程言语中,return关键字能够将函数的实行效果返回,PHP中return的用法也迥然不同,对初学者来讲,控制PHP中return的用法也是进修PHP的一个入手下手. 起首,它的意义就是返回 ...

  5. 如何在sqlite3连接中创建并调用自定义函数

    #!/user/bin/env python # @Time :2018/6/8 14:44 # @Author :PGIDYSQ #@File :CreateFunTest.py '''如何在sql ...

  6. request中的内容存储_宜信开源|调用链系列(3):解读UAVStack中的调用链技术...

    拓展阅读:宜信开源|调用链系列(1):解读UAVStack中的贪吃蛇 调用链系列(二):解读UAVStack中的贪吃蛇-调用链 在Java中,HTTP协议的请求/响应模型是由Servlet规范+Ser ...

  7. WinCE中串口驱动及接口函数介绍(转载)

    作者:ARM-WinCE 在WinCE中,串口驱动实际上就是一个流设备驱动,具体架构如图: 串口驱动本身分为MDD层和PDD层.MDD层对上层的Device Manager提供了标准的流设备驱动接口( ...

  8. Dictionary作为数据源绑定,调用c++库中返回为BYTE*的函数,listView项排序

    最近在做一个电子档案管理的项目.现在还处于初期,只是做一个简单demo拿去跟客户演示.至于最后谈不谈得下来,到底做不做,反正我是不看好,但没因为这样就马马虎虎.草草了事.这个项目算是b/s加c/s混合 ...

  9. pandas将列表list插入到dataframe的单元格中、pandas使用read_csv函数读取文件并设置保留数值的前置0( leading zeroes)

    pandas将列表list插入到dataframe的单元格中.pandas使用read_csv函数读取文件并设置保留数值的前置0( leading zeroes) 目录

最新文章

  1. 云智易获上海CIO联盟“年度物联网云平台技术创新奖”
  2. 自然语言处理中的预训练技术发展史
  3. 妙用0元素数组 实现大小可变结构体
  4. C++11新特性,利用std::chrono精简传统获取系统时间的方法
  5. phpcms 文本溢出(······省略号)组合处理 - 代码篇
  6. java 定义多个变量_学了Java才搞懂JMeter测试计划
  7. MySQL(23)--- 正则表达式
  8. Decorator 装饰模式
  9. URL vs URI
  10. NYOJ 138 找球号(二) (哈希)
  11. Windows不同压缩软件、压缩算法、压缩率详细对比测试与选择
  12. MCMC算法之Metropolis-Hastings(MHs)算法(Matlab代码)
  13. HP台式计算机不能启动,惠普电脑不能启动怎么处理
  14. python开发工程师招聘要求
  15. Android 项目接入网易云信IM单聊,群聊
  16. vscode 无法输入输出
  17. 儿童在未来游戏中的监管与保护趋势
  18. 利用Github免费搭建个人主页(个人博客)
  19. 【github】Support for password authentication was removed on August 13,2021.
  20. 相机分辨率、图片分辨率、像素及图片尺寸关系的思考

热门文章

  1. C# 如何调用以管理员身份运行的cmd命令提示符
  2. android拍照图片如何存储空间不足,手机照相显示内存不足怎么办 解决方法【详解】...
  3. linux iic驱动整理(一)
  4. srsLTE+b210 搭建4G微基站
  5. Confluent Platform 的快速上手
  6. Windows Server搭建SSL 安全Web网站
  7. c语言二维数组和多维数组笔记
  8. 使用微擎自带的函数生成二维码
  9. C++ 二维vector初始化、resize()
  10. PHP修改php.ini中关于文件上传大小的配置项