导出结果

Taichi 提供的函数可以帮助你以 图像或视频的形式导出可视化结果 。本节文档将对它们的使用方法逐步演示。

导出图像

  • 这里有两种方法可以将程序的可视化结果导出为图像。
  • 第一种也是较简单的方式是使用 ti.GUI.
  • 第二种方式是调用一系列相关 Taichi 函数,比如 ti.imwrite.

通过 ti.GUI.show 导出图像

  • ti.GUI.show(文件名) 不仅可以在屏幕上显示 GUI 画布,还可以将 GUI 中的图像保存到指定的 文件名 中。
  • 请注意,图像的格式完全由 文件名 里的后缀所决定。
  • Taichi 现在支持将图片保存为 png, jpg, 和 bmp 格式。
  • 我们建议使用 png 格式。例如:
  1. import taichi as ti
  2. import os
  3. ti.init()
  4. pixels = ti.var(ti.u8, shape=(512, 512, 3))
  5. @ti.kernel
  6. def paint():
  7. for i, j, k in pixels:
  8. pixels[i, j, k] = ti.random() * 255
  9. iterations = 1000
  10. gui = ti.GUI(''Random pixels'', res=512)
  11. # mainloop
  12. for i in range(iterations):
  13. paint()
  14. gui.set_image(pixels)
  15. filename = f'frame_{i:05d}.png' # 创建带有 png 后缀的文件名
  16. print(f'Frame {i} is recorded in {filename}')
  17. gui.show(filename) # 导出并显示在 GUI 中
  • 运行上述代码后,你将在当前文件夹中获得一系列 png 图像。

通过 ti.imwrite 导出图像

如果不想通过调用 ti.GUI.show(文件名) 保存图像的话,可以使用 ti.imwrite(文件名)。例如:

  1. import taichi as ti
  2. ti.init()
  3. pixels = ti.var(ti.u8, shape=(512, 512, 3))
  4. @ti.kernel
  5. def set_pixels():
  6. for i, j, k in pixels:
  7. pixels[i, j, k] = ti.random() * 255
  8. set_pixels()
  9. filename = f'imwrite_export.png'
  10. ti.imwrite(pixels.to_numpy(), filename)
  11. print(f'The image has been saved to {filename}')
  • ti.imwrite 可以导出 Taichi 张量 (ti.Matrix, ti.Vector, ti.var) 和 numpy 张量 np.ndarray.
  • 与之前讲到的 ti.GUI.show(filename) 一样, 图像格式 (png, jpgbmp) 依旧由 ti.imwrite(文件名)文件名 包含的后缀所决定.
  • 同时,得到的图像类型(灰度、RGB 或 RGBA)由 输入张量的通道数 决定。即,第三维的长度( tensor.shape[2] )。
  • 换言之,形状是 (w, h)(w, h, 1) 的张量会被导出成灰度图。
  • 如果你想导出 RGBRGBA 的图像,输入的张量形状应该分别是 (w, h, 3)(w, h, 4)

注解

Taichi 中所有的张量都有自己的数据类型,比如 ti.u8ti.f32。不同的数据类型会导致 ti.imwrite 产生不同的输出。请参阅 GUI system 了解更多细节。

  • 除了 ti.imwrite 之外,Taichi 还提供了其他读取和显示图像的辅助函数。在 GUI system 中也会有它们的示例。

导出视频

注解

Taichi 的视频导出工具依赖于 ffmpeg。如果你的机器上还没有安装 ffmpeg,请按照本节末尾的 ffmpeg 安装说明进行操作。

  • ti.VideoManager 可以帮助你导出 mp4 或 gif 格式的结果。例如,
  1. import taichi as ti
  2. ti.init()
  3. pixels = ti.var(ti.u8, shape=(512, 512, 3))
  4. @ti.kernel
  5. def paint():
  6. for i, j, k in pixels:
  7. pixels[i, j, k] = ti.random() * 255
  8. result_dir = "./results"
  9. video_manager = ti.VideoManager(output_dir=result_dir, framerate=24, automatic_build=False)
  10. for i in range(50):
  11. paint()
  12. pixels_img = pixels.to_numpy()
  13. video_manager.write_frame(pixels_img)
  14. print(f'\rFrame {i+1}/50 is recorded', end='')
  15. print()
  16. print('Exporting .mp4 and .gif videos...')
  17. video_manager.make_video(gif=True, mp4=True)
  18. print(f'MP4 video is saved to {video_manager.get_output_filename(".mp4")}')
  19. print(f'GIF video is saved to {video_manager.get_output_filename(".gif")}')

运行上述代码后,你将在 ./results/ 文件夹中找到输出的视频。

安装 ffmpeg

在 Windows 上安装 ffmpeg

  • ffmpeg 上下载 ffmpeg 存档文件(具体名称为,ffmpeg-2020xxx.zip);
  • 解压存档到指定文件夹中,比如, “D:/YOUR_FFMPEG_FOLDER”;
  • 关键步骤: 添加路径 D:/YOUR_FFMPEG_FOLDER/bin 到环境变量 PATH 中;
  • 打开 Windows 下的 cmdPowerShell ,然后输入下面这行命令来测试你的安装是否成功。 如果 ffmpeg 已经正确安装完毕,那么它的版本信息就会被打印出来。
  1. ffmpeg -version

在 Linux 上安装 ffmpeg

  • 大多数 Linux 发行版都会自带 ffmpeg ,所以如果你的机器上已经有了 ffmpeg 命令,那么你就不需要阅读这一部分了。
  • 在 Ubuntu 上安装 ffmpeg
  1. sudo apt-get update
  2. sudo apt-get install ffmpeg
  • 在 CentOS 和 RHEL 上安装 ffmpeg
  1. sudo yum install ffmpeg ffmpeg-devel
  • 在 Arch Linux 上安装 ffmpeg:

  • 使用下面这行命令测试你的安装是否成功

  1. ffmpeg -h

在 OS X 上安装 ffmpeg

  • 在 OS X 上可以通过 homebrew 安装 ffmpeg:
  1. brew install ffmpeg

导出 PLY 文件

  • ti.PLYwriter 可以帮助你将结果导出为 ply 格式。下面是导出一个顶点随机着色的立方体动画中10帧画面的短例,
  1. import taichi as ti
  2. import numpy as np
  3. ti.init(arch=ti.cpu)
  4. num_vertices = 1000
  5. pos = ti.Vector(3, dt=ti.f32, shape=(10, 10, 10))
  6. rgba = ti.Vector(4, dt=ti.f32, shape=(10, 10, 10))
  7. @ti.kernel
  8. def place_pos():
  9. for i, j, k in pos:
  10. pos[i, j, k] = 0.1 * ti.Vector([i, j, k])
  11. @ti.kernel
  12. def move_particles():
  13. for i, j, k in pos:
  14. pos[i, j, k] += ti.Vector([0.1, 0.1, 0.1])
  15. @ti.kernel
  16. def fill_rgba():
  17. for i, j, k in rgba:
  18. rgba[i, j, k] = ti.Vector(
  19. [ti.random(), ti.random(), ti.random(), ti.random()])
  20. place_pos()
  21. series_prefix = "example.ply"
  22. for frame in range(10):
  23. move_particles()
  24. fill_rgba()
  25. # 当前只支持通过传递单个 np.array 来添加通道
  26. # 所以需要转换为 np.ndarray 并且 reshape
  27. # 记住使用一个临时变量来存储,这样你就不必再转换回来
  28. np_pos = np.reshape(pos.to_numpy(), (num_vertices, 3))
  29. np_rgba = np.reshape(rgba.to_numpy(), (num_vertices, 4))
  30. # 创建一个 PLYWriter 对象
  31. writer = ti.PLYWriter(num_vertices=num_vertices)
  32. writer.add_vertex_pos(np_pos[:, 0], np_pos[:, 1], np_pos[:, 2])
  33. writer.add_vertex_rgba(
  34. np_rgba[:, 0], np_rgba[:, 1], np_rgba[:, 2], np_rgba[:, 3])
  35. writer.export_frame_ascii(frame, series_prefix)

运行上述代码后,你将在当前工作目录中找到 ply 文件的输出序列。接下来,我们将 ti.PLYWriter 的使用方式分解为4个步骤,并相应的展示一些示例。

  • 设置 ti.PLYWriter
  1. # num_vertices 必须是正整数
  2. # num_faces 是可选的,默认为0
  3. # face_type 可以是 "tri" 或 "quad", 默认为 "tri"
  4. # 在之前的例子中,创建了一个带有1000个顶点和0个三角形面片的写入器(writer)
  5. num_vertices = 1000
  6. writer = ti.PLYWriter(num_vertices=num_vertices)
  7. # 在下面的例子中,创建了一个带有20个顶点和5个四边形面片的写入器
  8. writer2 = ti.PLYWriter(num_vertices=20, num_faces=5, face_type="quad")
  • 添加必需的通道信息
  1. # 一个由四边形面片组成的二维网格
  2. # y
  3. # |
  4. # z---/
  5. # x
  6. # 19---15---11---07---03
  7. # | | | | |
  8. # 18---14---10---06---02
  9. # | | | | |
  10. # 17---13---19---05---01
  11. # | | | | |
  12. # 16---12---08---04---00
  13. writer = ti.PLYWriter(num_vertices=20, num_faces=12, face_type="quad")
  14. # 对于顶点来说,唯一必需的通道信息就是位置,
  15. # 可以通过向下列函数中传递三个 np.array x,y,z 来添加
  16. x = np.zeros(20)
  17. y = np.array(list(np.arange(0, 4))*5)
  18. z = np.repeat(np.arange(5), 4)
  19. writer.add_vertex_pos(x, y, z)
  20. # 对于面片来说(如果有的话),唯一必需的通道信息是每个面片所包含的顶点索引列表。
  21. indices = np.array([0, 1, 5, 4]*12)+np.repeat(
  22. np.array(list(np.arange(0, 3))*4)+4*np.repeat(np.arange(4), 3), 4)
  23. writer.add_faces(indices)
  • 添加可选的通道信息
  1. # 添加自定义顶点通道信息,输入应该包括一个键(key),支持的数据类型,np.array格式的数据
  2. vdata = np.random.rand(20)
  3. writer.add_vertex_channel("vdata1", "double", vdata)
  4. # 添加自定义面片通道信息
  5. foo_data = np.zeros(12)
  6. writer.add_face_channel("foo_key", "foo_data_type", foo_data)
  7. # 错误! 因为 "foo_data_type" 并不是支持的数据类型. 支持的数据类型有如下
  8. # ['char', 'uchar', 'short', 'ushort', 'int', 'uint', 'float', 'double']
  9. # PLYwriter 已经为常用通道定义了几个有用的辅助函数
  10. # 添加顶点的颜色, alpha通道, 及 rgba
  11. # 使用 float/double r g b alpha 来表示颜色值, 范围应该在0到1之间
  12. r = np.random.rand(20)
  13. g = np.random.rand(20)
  14. b = np.random.rand(20)
  15. alpha = np.random.rand(20)
  16. writer.add_vertex_color(r, g, b)
  17. writer.add_vertex_alpha(alpha)
  18. # 相当于
  19. # add_vertex_rgba(r, g, b, alpha)
  20. # 顶点法向
  21. writer.add_vertex_normal(np.ones(20), np.zeros(20), np.zeros(20))
  22. # 顶点索引和块(组内 id)
  23. writer.add_vertex_id()
  24. writer.add_vertex_piece(np.ones(20))
  25. # 添加面片索引和块 (组内 id)
  26. # 在 writer 中索引已有面片并将其通道信息添加到面片通道信息中
  27. writer.add_face_id()
  28. # 将所有的面片都放到第一组
  29. writer.add_face_piece(np.ones(12))
  • 导出文件
  1. series_prefix = "example.ply"
  2. series_prefix_ascii = "example_ascii.ply"
  3. # 导出一个简单的文件
  4. # 使用 ascii 编码这样你可以对内容进行概览
  5. writer.export_ascii(series_prefix_ascii)
  6. # 或者,使用二进制编码以获得更好的性能
  7. # writer.export(series_prefix)
  8. # 导出文件序列,即10帧
  9. for frame in range(10):
  10. # 将每一帧写入你的当前运行的文件夹中( 即, "example_000000.ply")
  11. writer.export_frame_ascii(frame, series_prefix_ascii)
  12. # 或者相应的, 使用二进制编码这样写
  13. # writer.export_frame(frame, series_prefix)
  14. # 更新 位置/颜色
  15. x = x + 0.1*np.random.rand(20)
  16. y = y + 0.1*np.random.rand(20)
  17. z = z + 0.1*np.random.rand(20)
  18. r = np.random.rand(20)
  19. g = np.random.rand(20)
  20. b = np.random.rand(20)
  21. alpha = np.random.rand(20)
  22. # 重新填充
  23. writer = ti.PLYWriter(num_vertices=20, num_faces=12, face_type="quad")
  24. writer.add_vertex_pos(x, y, z)
  25. writer.add_faces(indices)
  26. writer.add_vertex_channel("vdata1", "double", vdata)
  27. writer.add_vertex_color(r, g, b)
  28. writer.add_vertex_alpha(alpha)
  29. writer.add_vertex_normal(np.ones(20), np.zeros(20), np.zeros(20))
  30. writer.add_vertex_id()
  31. writer.add_vertex_piece(np.ones(20))
  32. writer.add_face_id()
  33. writer.add_face_piece(np.ones(12))

ply 文件导出到 Houdini 和 Blender

Houdini 支持导入一组共享相同前缀/后缀的 ply 文件。我们的 export_frame 就可以为你满足这种需求。在 Houdini 中,点击 File->Import->Geometry 并导航至包含你的框架输出的文件夹中,这些输出结果应该被梳理成一个单一的条目,比如 example_$F6.ply (0-9)。双击该条目以完成导入过程。

Blender 需要一个名为 Stop-motion-OBJ 的插件来加载结果序列。这里有一个非常详尽的 教程视频,是由其作者提供的关于如何安装、授权和使用这个插件的演示。需要注意的一点是,Stop-motion-OBJ 工作流的重大变更是在最近的测试版本中才释放来的。如要跟随他们的说明文档和视频学习和使用,请使用 v2.0.2 版本。