Taichi 模拟结果导出实战:图片、视频与 PLY 序列文件的完整指南 Taichi 模拟结果导出实战图片、视频与 PLY 序列文件的完整指南【免费下载链接】taichiProductive, portable, and performant GPU programming in Python.项目地址: https://gitcode.com/GitHub_Trending/ta/taichi导读Taichi 为 Python GPU 编程提供了从仿真计算到视觉呈现的一站式工具链。当你的物理模拟、流体或粒子系统运行结束后如何将ti.field中的结果导出为图片、视频甚至能被 Houdini、Blender 等 DCC 软件读取的 PLY 序列文件是连接「计算」与「展示」的关键一环。本文基于 Taichi 仓库中的 export_results.md 教程结合 tools 模块 的真实源码系统讲解ti.GUI.show、ti.tools.imwrite、ti.tools.VideoManager、ti.tools.PLYWriter以及ti video/ti gif命令行工具的使用方法与底层原理读完即可在自己的项目中完成图像帧导出、视频合成与网格序列输出的完整工作流。一、导出图片的两种途径Taichi 提供两条导出图片的路径适用场景不同ti.GUI.show(filename)适合「边仿真边实时预览、同时保存关键帧」的场景调用一次即可完成窗口渲染与文件落盘ti.tools.imwrite(img, filename)适合「不需要弹窗、直接保存计算结果的图像」的场景例如离线渲染或批量后处理。两种方式下图片的实际格式完全由filename的后缀决定。Taichi 当前支持png、jpg与bmp三种格式官方推荐优先使用无损的png。1.1 使用ti.GUI.show(filename)导出帧下面的例子创建一个 512×512 的随机噪点画布在 1000 次迭代中逐帧保存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, res512) # mainloop for i in range(iterations): paint() gui.set_image(pixels) filename fframe_{i:05d}.png # create filename with suffix png print(fFrame {i} is recorded in {filename}) gui.show(filename) # export and show in GUI运行后当前目录下会生成frame_00000.png、frame_00001.png等一系列图片。从源码看gui.py 中show(file)的实现逻辑是先调用self.core.update()刷新窗口内容当传入file时再调用self.core.screenshot(file)将当前帧写入文件随后帧计数加一并清空画布。因此每次调用gui.show(filename)都等价于「截屏 存档」。要点gui.show()不带参数时仅刷新窗口不保存文件传入文件名后缀为png/jpg/bmp时才会触发落盘。这也意味着「一边实时预览、一边录制视频帧」是完全可行的。1.2 使用ti.tools.imwrite直接写盘如果不希望唤起 GUI 窗口可以直接调用ti.tools.imwriteimport 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 fimwrite_export.png ti.tools.imwrite(pixels.to_numpy(), filename) print(fThe image has been saved to {filename})ti.tools.imwrite支持以下输入类型Taichi 字段ti.field、ti.Vector.field、ti.Matrix.fieldNumPy 数组np.ndarray字段会先通过to_numpy()转出。输出格式同样由文件后缀决定支持png、jpg、bmp。通道数决定图像类型输出图像是灰度、RGB 还是 RGBA完全取决于输入字段第三维的长度即field.shape[2]输入字段形状导出图像类型(w, h)或(w, h, 1)灰度图grayscale(w, h, 3)RGB 彩色图(w, h, 4)RGBA 彩色图数据类型决定像素映射规则原文档特别提醒Taichi 字段自带ti.u8、ti.f32等数据类型不同类型在imwrite中的行为不同。这一点在 image.py 的cook_image_to_bytes函数中有精确实现整数类型uint16/uint32/uint64按像素值 // (类型上限 // 256)线性映射到[0, 255]区间。例如ti.u16的 65535 会映射为 255浮点类型float32/float64先clip(img, 0, 1)截断到[0.0, 1.0]再乘以 255。即浮点字段的像素值应规范在 0~1 之间超出部分会被截断uint8直接使用无需转换其他类型抛出ValueError。处理完数据类型后二维数组会被补成(w, h, 1)的三维形状最后通过swapaxes(0, 1)[::-1, :]调整坐标方向再经底层_ti_core.imwrite写入文件。扩展阅读GUI 系统 中还介绍了与imwrite配套的imread、imresize、imshow等辅助函数分别用于读图、缩放图像与在 GUI 窗口或 Jupyter Notebook 中直接显示图像实现完整的图像 I/O 闭环。二、将 PNG 序列转换为视频ti video当你通过上述方式导出了一系列png帧例如000000.png、000001.png、000002.png……可以方便地用ti video命令把它们合并成单个video.mp4# 默认把当前工作目录下的所有 *.png 按文件名排序合成为 video.mp4 ti video # 指定 40 FPS 输出 ti video -f40 # 指定输出路径 ti video -o output/my_video.mp4该命令定义在 _main.py 中参数行为如下参数默认值说明inputs位置参数当前目录下全部*.png排序后也可显式传入多个 PNG 文件-o, --output./video.mp4输出 MP4 文件路径-f, --framerate24输出视频帧率-c, --crf20恒定速率因子范围 1~511 近乎无损20 为默认51 画质最差底层通过 video.py 的make_video调用 ffmpeg将输入帧统一拷贝到临时目录并重命名为%06d.png然后以libx264 profile:v high编码为yuv420p像素格式的 MP4。若输入尺寸为奇数无法被 2 整除会自动减 1 以保证 H.264 编码兼容性。三、导出视频ti.tools.VideoManager3.1 前置依赖ffmpeg视频导出工具链依赖ffmpeg。若机器上未安装请先参照本文末尾「安装 ffmpeg」一节完成安装。3.2 基本用法ti.tools.VideoManager可以把逐帧写入的图像合成为mp4或gifimport 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_dirresult_dir, framerate24, automatic_buildFalse) for i in range(50): paint() pixels_img pixels.to_numpy() video_manager.write_frame(pixels_img) print(f\rFrame {i1}/50 is recorded, end) print() print(Exporting .mp4 and .gif videos...) video_manager.make_video(gifTrue, mp4True) print(fMP4 video is saved to {video_manager.get_output_filename(.mp4)}) print(fGIF video is saved to {video_manager.get_output_filename(.gif)})运行后./results/目录下会出现生成的视频文件。3.3 构造参数与工作流程解析对照 video.py 的VideoManager实现各参数与内部行为如下参数默认值说明output_dir必填帧文件与视频的输出目录帧图会存放在其下的frames/子目录video_filenameNone视频文件名如sim.mp4缺省为video.mp4width/heightNone视频分辨率需同时指定或同时省略省略时取第一帧的尺寸post_processorNone实现process(img)方法的对象可对每帧做后处理framerate24视频帧率automatic_buildTrue是否在写帧过程中自动增量合成视频工作流程分三步write_frame(img)接收np.ndarray帧内部以%06d.png模板即000000.png、000001.png……命名后调用imwrite存入output_dir/frames/。实现中会检查宽高是否为奇数若是则打印警告并丢弃最后一行/列保证 H.264 兼容若设置了post_processor则先处理再写入make_video(mp4True, gifTrue)调用 ffmpeg以-framerate {framerate} -c:v libx264 -profile:v high -crf 1 -pix_fmt yuv420p参数合成 MP4crf 1意味着近无损的高质量输出若gifTrue则进一步调用mp4_to_gif基于 palettegen / paletteuse 双趟法生成 GIFget_output_filename(suffix)按output_dir与video_filename计算最终输出路径例如./results/video.mp4。若automatic_buildTruewrite_frame会在第 4、8、16……帧checkpoint 每轮翻倍自动调用一次make_video适合长时录制时边跑边出片文档示例中显式设为False由用户在循环结束后统一合成。提示帧文件目录可通过get_frame_directory()获取clean_frames()可清理已记录的帧图write_frames(images)支持一次性写入帧列表。四、将视频转换为 GIFti gif需要把 MP4 转成 GIF 发布到论坛时使用ti gif# 将 video.mp4 转为同名 video.gif帧率默认 24 ti gif -i video.mp4 # 以 40 FPS 生成 GIF ti gif -i video.mp4 -f40从 _main.py 可以看到-i/--input为必填参数且必须是以.mp4结尾的文件否则报错filename must be of type .mp4-f/--framerate默认 24。输出文件自动取输入文件的同名.gif后缀底层调用mp4_to_gif通过 ffmpeg 的两步 palette 策略得到质量更稳的动图。五、导出 PLY 网格序列ti.tools.PLYWriterPLYPolygon File Format是点云与网格数据的通用格式。ti.tools.PLYWriter可以把 Taichi 字段中的顶点位置、颜色、法线等通道写入ply文件供 Blender、Houdini、MeshLab 等三维软件读取是粒子系统、MPM 等无网格模拟结果可视化的标准出口。5.1 完整示例导出 10 帧随机运动立方体import taichi as ti import numpy as np ti.init(archti.cpu) num_vertices 1000 pos ti.Vector.field(3, dtypeti.f32, shape(10, 10, 10)) rgba ti.Vector.field(4, dtypeti.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_verticesnum_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)运行后在当前目录得到example_000000.ply、example_000001.ply…… 共 10 个帧文件。接下来把PLYWriter的用法拆解为 4 步。5.2 第一步初始化PLYWriter# num_vertices 必须是正整数 # num_faces 可选默认 0 # face_type 只能为 tri 或 quad默认 tri # 上一个例子创建包含 1000 个顶点、0 个三角面的 writer num_vertices 1000 writer ti.tools.PLYWriter(num_verticesnum_vertices) # 下面的例子创建包含 20 个顶点、5 个四边形面的 writer writer2 ti.tools.PLYWriter(num_vertices20, num_faces5, face_typequad)对照 np2ply.py 的实现构造函数还支持comment参数默认created by PLYWriter会写入 PLY 文件头部的comment行并包含三条断言num_vertices 0、num_faces 0、face_type仅为tri或quad。5.3 第二步添加必需通道顶点与面各有一个必需通道顶点必须有位置坐标面必须有顶点索引列表。以一个 20 顶点、12 个四边形面的 2D 网格为例# 网格拓扑示意 # 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_vertices20, num_faces12, face_typequad) # 顶点位置将 3 个 np.arrayx、y、z传入 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) # 面索引每个面包含的顶点编号列表 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_vertex_pos(x, y, z)实际是分别以x、y、z为键调用add_vertex_channel类型固定为floatadd_faces(indices)会把一维索引数组 reshape 成(num_faces, vert_per_face)并转为int32其中tri每面 3 个索引、quad每面 4 个索引总数必须严格等于vert_per_face * num_faces。5.4 第三步添加可选通道# 自定义顶点通道传入 key、受支持的数据类型和数据 np.array vdata np.random.rand(20) writer.add_vertex_channel(vdata1, double, vdata) # 自定义面通道 foo_data np.zeros(12) writer.add_face_channel(foo_key, foo_data_type, foo_data) # error! 因为 foo_data_type 不是受支持的类型。 # 受支持的类型为 # [char, uchar, short, ushort, int, uint, float, double] # PLYWriter 内置了多个常用通道的辅助函数 # 顶点颜色使用 0~1 区间的 float/double 表示 r、g、b、alpha 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) # 等价写法 # add_vertex_rgba(r, g, b, alpha) # 顶点法线 writer.add_vertex_normal(np.ones(20), np.zeros(20), np.zeros(20)) # 顶点 id 与 piece分组 id writer.add_vertex_id() writer.add_vertex_piece(np.ones(20)) # 面 id 与 piece分组 id writer.add_face_id() # 将所有面划入组 1 writer.add_face_piece(np.ones(12))对照源码add_vertex_channel/add_face_channel的底层规则值得注意数据类型映射PLY 的 8 种类型[char, uchar, short, ushort, int, uint, float, double]分别映射到 NumPy 的int8/uint8/int16/uint16/int32/uint32/float32/float64见 np2ply.py传入未知类型会打印Unknown type ... skipping this channel并跳过该通道不会直接崩溃多维数据自动展开若传入的数据是二维数组会按列拆分为key_1、key_2…… 等多个单列通道便于一次性写入向量分量重复键警告通道键重复时会打印WARNING: duplicate key ... detected便捷函数add_vertex_normal写入nx/ny/nzadd_vertex_color写入red/green/blueadd_vertex_alpha写入Alpha注意首字母大写add_vertex_rgba为前两者的组合add_vertex_id/add_face_id自动写入np.arange索引add_vertex_piece/add_face_piece用于给顶点/面标记分组 IDPLY 中常配合piece实现多物体合并。5.5 第四步导出文件series_prefix example.ply series_prefix_ascii example_ascii.ply # 导出单个文件使用 ascii 以便直接阅读内容 writer.export_ascii(series_prefix_ascii) # 或者使用二进制格式以获得更好性能 # writer.export(series_prefix) # 导出一系列文件例如 10 帧 for frame in range(10): # 每帧写入如 example_000000.ply 的文件 writer.export_frame_ascii(frame, series_prefix_ascii) # 或使用二进制 # writer.export_frame(frame, series_prefix) # 更新位置/颜色 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) # 重新填充 writer ti.tools.PLYWriter(num_vertices20, num_faces12, face_typequad) 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))导出的四种方法对应两种格式方法格式文件命名export(path)二进制binary_little_endian或binary_big_endian取决于平台字节序即path本身export_ascii(path)ASCII 文本即path本身export_frame(series_num, path)二进制path去除.ply后加_{series_num:06d}.plyexport_frame_ascii(series_num, path)ASCII 文本同上从 np2ply.py 可以看到每种导出前都会执行sanity_check()确保顶点通道中包含x、y、z三个必需键若存在面则逐索引校验其落在[0, num_vertices)区间内防止写出越界引用的损坏文件。文件头会按 PLY 规范写入element vertex、element face、property list uchar int vertex_indices等声明。提示帧号使用 6 位补零%06d因此 10 帧会输出example_000000.ply~example_000009.ply便于外部软件按字典序识别帧序列。5.6 将 PLY 序列导入 Houdini 与 BlenderHoudini支持导入共享同一前缀/后缀的 PLY 序列export_frame系列方法生成的命名恰好满足该要求。导入时点击File - Import - Geometry导航到帧结果所在目录序列会折叠为单个条目形如example_$F6.ply (0-9)双击该条目即可完成导入Houdini 会自动按帧号推进。Blender需要借助名为 Stop-motion-OBJ 的第三方插件来加载序列结果Blender 2.80 用户应安装该插件的最新版本Blender 2.79 及更早版本则需使用v1.1.1版本插件作者提供了详细的安装与使用文档。装好插件后即可按帧序列方式把 Taichi 导出的 PLY 动画导入 Blender 继续做材质、打光与渲染。六、安装 ffmpegti video、ti gif与VideoManager均依赖 ffmpeg以下按平台说明安装方法。6.1 Windows从 ffmpeg 官网下载发行压缩包形如ffmpeg-2020xxx.zip解压到某个目录例如D:/YOUR_FFMPEG_FOLDER重要将D:/YOUR_FFMPEG_FOLDER/bin添加到系统PATH环境变量打开cmd或PowerShell执行以下命令验证ffmpeg -version若能打印出版本信息说明安装成功。6.2 Linux大多数 Linux 发行版已原生附带ffmpeg若ffmpeg命令已存在则无需操作。否则按发行版选择安装方式Ubuntusudo apt-get update sudo apt-get install ffmpegCentOS / RHELsudo yum install ffmpeg ffmpeg-develArch Linuxpacman -S ffmpeg验证安装ffmpeg -h6.3 macOS使用 Homebrew 安装brew install ffmpeg七、小结按需选择导出方案需求推荐方案底层实现边实时预览边存图ti.GUI.show(filename)gui.py无窗口直接存图ti.tools.imwriteimage.pyPNG 序列合成 MP4ti video_main.py video.py逐帧录制并合成视频ti.tools.VideoManagervideo.pyMP4 转 GIFti gif_main.py点云/网格序列导出ti.tools.PLYWriternp2ply.py这套工具链覆盖了「计算字段 → 图像帧 → 视频 → 三维网格序列」的完整导出路径仿真中逐帧imwrite或VideoManager.write_frame落盘结束后用ti video或make_video合成 MP4/GIF 用于演示与分享需要进入 Blender、Houdini 做进一步可视化的场景则用PLYWriter输出带颜色、法线、分组信息的帧序列。配合 GUI 系统 中的imread、imresize、imshow等辅助函数你可以把 Taichi 的每一帧计算结果无缝接入主流 CG 工作流。【免费下载链接】taichiProductive, portable, and performant GPU programming in Python.项目地址: https://gitcode.com/GitHub_Trending/ta/taichi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考