跳转至主要内容
Version: develop

导出结果

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

导出图像

  • 有两种方法可以将程序的可视化结果导出为图像。
  • 第一种也是较简单的方式是使用 ti.GUI
  • 第二种方式是调用一些 Taichi 函数,如 ti.tools.imwrad

通过 ti.GUI.show 导出图像

  • ti.GUI.show(文件名)不仅可以在屏幕上显示 GUI 画布,还可以将 GUI 中的图像保存到指定文件filename
  • 请注意,图像的格式完全由filename中的后缀所决定。
  • Taichi 现在支持将图片保存为 pngjpgbmp 格式。
  • 我们建议使用 png 格式。 例如:
import taichi as ti
import os

ti.init()

pixels = ti.field(ti.u8, shape=(512, 512, 3))

@ti.kernel
def paint():
for i, j, k in pixels:
pixels[i, j, k] = ti.random() * 255

iterations = 1000
gui = ti.GUI("Random pixels", res=512)

# mainloop
for i in range(iterations):
paint()
gui.set_image(pixels)

filename = f'frame_{i:05d}.png' # create filename with suffix png
print(f'Frame {i} is recorded in {filename}')
gui.show(filename) # export and show in GUI

运行上述代码后,你将在当前文件夹中获得一系列图像。

通过 ti.tools.imwrite 导出图像

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

import taichi as ti

ti.init()

pixels = ti.field(ti.u8, shape=(512, 512, 3))

@ti.kernel
def set_pixels():
for i, j, k in pixels:
pixels[i, j, k] = ti.random() * 255

set_pixels()
filename = f'imwrite_export.png'
ti.tools.imwrite(pixels.to_numpy(), filename)
print(f'The image has been saved to {filename}')
  • ti.tools.imwrite 可以导出 Taichi field(ti.Matrix.fieldti.Vector.field, ti.field)和 numpy 数组 np.ndarray
  • 与之前提到的 ti.GUI.show(filename) 一样,图像格式(png, jpgbmp)也由 ti.imwrite(filename) 中的filename所决定。
  • 同时,得到的图像类型(灰度、RGB 或 RGBA)由输入的 field 的通道数决定,也就是第三维的长度(field.shape[2])。
  • 换言之,形状是 (w, h)(w, h, 1) 的 field 会被导出成灰度图。
  • 如果你想导出 RGBRGBA 的图像,输入的 field 的形状应该是 (w, h, 3)(w, h, 4)
note

Taichi 中所有的 field 都有自己的数据类型,比如 ti.u8ti.f32。 不同的数据类型会导致 ti.imwrite 产生不同的行为。 请参阅 GUI 系统来了解更多细节。

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

将 PNG 图片转换为视频

有时候,为了更好地向别人呈现结果,我们需要将一系列的 png 文件转换为单个视频。

例如,假设你按照导出你的结果你当前的工作目录中生成了000000.png000001.png... 等一系列文件。

然后你可以通过运行 ti video 来创建一个包含了所有图片文件作为帧(按文件名排序)的名为 video.mp4 的视频文件。

使用 ti video -f40 来创建一个 40 FPS 的视频。

导出视频

note

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

  • ti.VideoManager 可以帮助你以 mp4gif 格式导出结果。 例如,
import taichi as ti

ti.init()

pixels = ti.field(ti.u8, shape=(512, 512, 3))

@ti.kernel
def paint():
for i, j, k in pixels:
pixels[i, j, k] = ti.random() * 255

result_dir = "./results"
video_manager = ti.tools.VideoManager(output_dir=result_dir, framerate=24, automatic_build=False)

for i in range(50):
paint()

pixels_img = pixels.to_numpy()
video_manager.write_frame(pixels_img)
print(f'\rFrame {i+1}/50 is recorded', end='')

print()
print('Exporting .mp4 and .gif videos...')
video_manager.make_video(gif=True, mp4=True)
print(f'MP4 video is saved to {video_manager.get_output_filename(".mp4")}')
print(f'GIF video is saved to {video_manager.get_output_filename(".gif")}')

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

将视频转换为 GIF

有时为了将结果上传到论坛中,我们需要一些格式为 gif 的图片文件。

你可以运行 ti gif -i video.mp4 来做到这一点,这里 video.mp4mp4 视频文件(通过前面的指令生成)。

使用 ti gif -i video.mp4 -f40 来创建一个 40 FPS 的 GIF 文件。

安装 ffmpeg

在 Windows 上安装 ffmpeg

  • ffmpeg 下载 ffmpeg 打包文件(ffmpeg-2020xxx.zip)。
  • 将压缩文件解压到指定文件夹中,比如,D:/YOUR_FFMPEG_FOLDER
  • 重要: 添加 D:/YOUR_FFMPEG_FOLDER/binPATH 环境变量。
  • 打开 Windows 下的 cmdPowerShell,然后输入下面这行命令来测试你的安装是否成功。 如果 ffmpeg 已经正确安装完毕,那么它的版本信息就会被打印出来。
ffmpeg -version

在 Linux 上安装 ffmpeg

  • 大多数 Linux 发行版都会原生自带 ffmpeg,所以如果你的机器上已经有了 ffmpeg 命令,那么这部分内容可以直接跳过。
  • 在 Ubuntu 上安装 ffmpeg
sudo apt-get update
sudo apt-get install ffmpeg
  • 在 CentOS 和 RHEL 上安装 ffmpeg
sudo yum install ffmpeg ffmpeg-devel
  • 在 Arch Linux 上安装 ffmpeg
pacman -S ffmpeg
  • 使用下面这行命令测试你的安装是否成功
ffmpeg -h

在 macOS 上安装 ffmpeg

  • ffmpeg 可以在 macOS 上使用 homebrew 安装:
brew install ffmpeg

导出 PLY 文件

  • ti.PLYwriter 可以帮助你将结果导出为 ply 格式。 下面是一个导出 10 帧顶点随机着色的移动立方体的简单示例。
import taichi as ti
import numpy as np

ti.init(arch=ti.cpu)

num_vertices = 1000
pos = ti.Vector.field(3, dtype=ti.f32, shape=(10, 10, 10))
rgba = ti.Vector.field(4, dtype=ti.f32, shape=(10, 10, 10))


@ti.kernel
def place_pos():
for i, j, k in pos:
pos[i, j, k] = 0.1 * ti.Vector([i, j, k])


@ti.kernel
def move_particles():
for i, j, k in pos:
pos[i, j, k] += ti.Vector([0.1, 0.1, 0.1])


@ti.kernel
def fill_rgba():
for i, j, k in rgba:
rgba[i, j, k] = ti.Vector(
[ti.random(), ti.random(), ti.random(), ti.random()])


place_pos()
series_prefix = "example.ply"
for frame in range(10):
move_particles()
fill_rgba()
# now adding each channel only supports passing individual np.array
# so converting into np.ndarray, reshape
# remember to use a temp var to store so you dont have to convert back
np_pos = np.reshape(pos.to_numpy(), (num_vertices, 3))
np_rgba = np.reshape(rgba.to_numpy(), (num_vertices, 4))
# create a PLYWriter
writer = ti.tools.PLYWriter(num_vertices=num_vertices)
writer.add_vertex_pos(np_pos[:, 0], np_pos[:, 1], np_pos[:, 2])
writer.add_vertex_rgba(
np_rgba[:, 0], np_rgba[:, 1], np_rgba[:, 2], np_rgba[:, 3])
writer.export_frame_ascii(frame, series_prefix)

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

  • 设置 ti.PLYWriter
# num_vertices must be a positive int
# num_faces is optional, default to 0
# face_type can be either "tri" or "quad", default to "tri"

# in our previous example, a writer with 1000 vertices and 0 triangle faces is created
num_vertices = 1000
writer = ti.tools.PLYWriter(num_vertices=num_vertices)

# in the below example, a writer with 20 vertices and 5 quadrangle faces is created
writer2 = ti.tools.PLYWriter(num_vertices=20, num_faces=5, face_type="quad")
  • 添加必需的通道
# A 2D grid with quad faces
# y
# |
# z---/
# x
# 19---15---11---07---03
# | | | | |
# 18---14---10---06---02
# | | | | |
# 17---13---19---05---01
# | | | | |
# 16---12---08---04---00

writer = ti.tools.PLYWriter(num_vertices=20, num_faces=12, face_type="quad")

# For the vertices, the only required channel is the position,
# which can be added by passing 3 np.array x, y, z into the following function.

x = np.zeros(20)
y = np.array(list(np.arange(0, 4))*5)
z = np.repeat(np.arange(5), 4)
writer.add_vertex_pos(x, y, z)

# For faces (if any), the only required channel is the list of vertex indices that each face contains.
indices = np.array([0, 1, 5, 4]*12)+np.repeat(
np.array(list(np.arange(0, 3))*4)+4*np.repeat(np.arange(4), 3), 4)
writer.add_faces(indices)
  • 添加可选通道
# Add custom vertex channel, the input should include a key, a supported datatype and, the data np.array
vdata = np.random.rand(20)
writer.add_vertex_channel("vdata1", "double", vdata)

# Add custom face channel
foo_data = np.zeros(12)
writer.add_face_channel("foo_key", "foo_data_type", foo_data)
# error! because "foo_data_type" is not a supported datatype. Supported ones are
# ['char', 'uchar', 'short', 'ushort', 'int', 'uint', 'float', 'double']

# PLYwriter already defines several useful helper functions for common channels
# Add vertex color, alpha, and rgba
# using float/double r g b alpha to reprent color, the range should be 0 to 1
r = np.random.rand(20)
g = np.random.rand(20)
b = np.random.rand(20)
alpha = np.random.rand(20)
writer.add_vertex_color(r, g, b)
writer.add_vertex_alpha(alpha)
# equivilantly
# add_vertex_rgba(r, g, b, alpha)

# vertex normal
writer.add_vertex_normal(np.ones(20), np.zeros(20), np.zeros(20))

# vertex index, and piece (group id)
writer.add_vertex_id()
writer.add_vertex_piece(np.ones(20))

# Add face index, and piece (group id)
# Indexing the existing faces in the writer and add this channel to face channels
writer.add_face_id()
# Set all the faces is in group 1
writer.add_face_piece(np.ones(12))
  • 导出文件
series_prefix = "example.ply"
series_prefix_ascii = "example_ascii.ply"
# Export a single file
# use ascii so you can read the content
writer.export_ascii(series_prefix_ascii)

# alternatively, use binary for a bit better performance
# writer.export(series_prefix)

# Export a sequence of files, ie in 10 frames
for frame in range(10):
# write each frame as i.e. "example_000000.ply" in your current running folder
writer.export_frame_ascii(frame, series_prefix_ascii)
# alternatively, use binary
# writer.export_frame(frame, series_prefix)

# update location/color
x = x + 0.1*np.random.rand(20)
y = y + 0.1*np.random.rand(20)
z = z + 0.1*np.random.rand(20)
r = np.random.rand(20)
g = np.random.rand(20)
b = np.random.rand(20)
alpha = np.random.rand(20)
# re-fill
writer = ti.tools.PLYWriter(num_vertices=20, num_faces=12, face_type="quad")
writer.add_vertex_pos(x, y, z)
writer.add_faces(indices)
writer.add_vertex_channel("vdata1", "double", vdata)
writer.add_vertex_color(r, g, b)
writer.add_vertex_alpha(alpha)
writer.add_vertex_normal(np.ones(20), np.zeros(20), np.zeros(20))
writer.add_vertex_id()
writer.add_vertex_piece(np.ones(20))
writer.add_face_id()
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 的插件来加载结果序列。 详细的 文档 是由作者提供的关于如何安装和使用插件的。 如果你在使用最新版本的 Blender(2.80+),请下载并安装最新版本的 Stop-motion-OBJ。 对于 Blender 2.79 或更早的版本,使用版本为 v1.1.1 的插件。