前言

  最近接到了一个需求,又是熟悉的 拍屏工具。
  其实老早之前我就有写过类似的需求,只是表现形式各不相同。
  这里打算将不同的拍屏方案汇总到一起,这样大家可以挑选一个合适的情景的方式完成这个任务。

拍屏方案汇总

Maya Python Publish 检查功能开发

  最早在华强实习的时候,就写过将 Arnold 渲染的界面合成并打开 RV 进行预览。
  背后主要用 renderWindowEditor 命令导出。


https://github.com/FXTD-ODYSSEY/MayaViewportCapture

  后来进入腾讯前,我写了 Maya Viewport Capture 工具。
  那个时候写的比较粗糙,我通过 UI 可以定义几个相机的位置,然后规定进行拍屏。
  当时研究用 Maya 或者 Qt 的 API 将 Viewport 的画面截取下来。
  背后主要用 Maya API M3dViewreadColorBuffer
  Qt 部分其实也是在拿到 Maya 的 MImage 之后转成 QImage 而已。


Maya Python 模型拍屏合并工具

  后来正式工作之后,发现前辈用的是 ogsRender 命令将 Maya Hardware 2.0 输出来。
  相较于 renderWindowEditor 命令不需要打开渲染窗口。


playblast

  实现拍屏有太多的方案,当然最为基础的方法就是使用 playblast 命令。
  建议安装上 QuickTime 这样可以极大压缩 Maya 拍屏的文件大小,同时提升 Maya 拍屏的质量。
  playblast 命令既可以直接生成视频也可以拍屏序列帧。

拍屏需求汇总

  上面提供四种拍屏方案,最常用的时 playblast 方案,因为可以直接输出视频。
  如果是图片序列还需要借助 ffmpeg 等命令行工具将图片序列合成为视频。

  拍屏的需求千变万化,但是有一些点其实大差不差。

  1. 拍屏信息
  2. 镜头角度

  比较常见的信息有 时间,影片的归属名字(比如动画的某一段),影片负责人 等等。
  添加这些信息可以用 headsUpMessage 将相关信息叠加到 Viewport 上。
  但是 headsUpMessage 非常难用,而且字体大小等各种非常不方便自定义。
  要解决这个问题可以用 插件,它通过 OpenMaya API 扩展了 headsUpMessage 的功能。
  作者是 zurbrigg ,只可惜它之前免费的工具现在变成付费了。
  劲爆羊工具盒 里面有拍屏王,它就是通过 ZShotmask VP2 插件,将各种信息贴到屏幕上。
  具体可以在 劲爆羊工具盒 里面找到脚本 resource\tools\MSTools\MST_DATA\plug-ins\zshotmask.py
  当然它是一个 Maya Python 插件,注册之后提供了一个节点,只要设置节点的属性就可以了。


  这个方式可以结合 playblast 解决大部分拍屏的问题。
  但有些情况并不能很好解决,比如我遇到的问题就是,每一帧都要重新矫正一下镜头的位置。
  而且这个矫正还不能单纯使用约束,需要每一帧单独进行计算。
  所以我只能改用 ogsRender 的方式,在后台进行拍屏。

Maya ogsRender 输出序列帧

  使用 ogsRender 输出序列帧只能输出到默认工程 images 文件夹的路径。
  因此要控制 ogsRender 输出的位置只能通过修改工程位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import contextlib
@contextlib.contextmanager
def change_workspace_images(folder):
"""Change maya project images folder temporarily.

Args:
folder (str): Image folder path.
"""
workspace_settings = pm.workspace(q=1, fr=1)
image_index = workspace_settings.index("images")
original_image_folder = workspace_settings[image_index + 1]
pm.workspace(fr=["images", folder])
yield
pm.workspace(fr=["images", original_image_folder])

  我写了一个函数,可以修改输出位置,在修改回去。
  这样我可以输出到任意路径。

Python ThreadPool 多线程后处理

  上面拍屏生成的图片,可以放到 imagemagick 进行图片后处理。
  imagemagick 是 maya 自带的命令行图形处理库。
  在 Maya 2022 之前叫做 imconvert.exe, 2022 之后叫做 magick.exe

  之前也研究过通过 imagemagick 处理图片,真的是拳打 Pillow 脚踢 QImage

ImageMagick 图像处理介绍

  imagemagick 用 C 和 C++ 编写的,非常小巧,而且运行速度很快~
  这里我没有使用 ZShotmask VP2 直接拍屏输出我要的信息,因为有些信息想要通过 imagemagick 叠加到图片上。
  于是我想到可以利用 Pool 线程池的方式多线程后台调用命令行。

  其中 from multiprocessing.dummy import Pool 可以导入 Python 隐藏的线程池。
  这个用起来比起使用 threading 库要简单方便很多。
注: from multiprocessing import Pool 导入进程池, Maya 不太支持这个。
  下面来个实例演示一下多线程调度后处理函数的好处。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from multiprocessing.dummy import Pool
from functools import partial
from functools import wraps
import time

def log_time(func=None, msg="elapsed time:"):
if not func:
return partial(log_time, msg=msg)

@wraps(func)
def wrapper(*args, **kwargs):
curr = time.time()
res = func(*args, **kwargs)
print("[{0}]".format(func.__name__),msg, time.time() - curr)
return res

return wrapper

def post_process(index):
time.sleep(0.1)
print("test", index)


@log_time
def multi_thread():
pool = Pool()

results = []
for index in range(10):
time.sleep(0.1)
results.append(pool.apply_async(partial(post_process, index)))

[result.wait() for result in results]
pool.close()
print("done")

@log_time
def sequence_run():
for index in range(10):
time.sleep(0.1)
post_process(index)
print("done")

if __name__ == "__main__":
multi_thread()
sequence_run()

  执行上面的代码

1
2
[sequence_run] elapsed time: 2.201172351837158
[multi_thread] elapsed time: 1.2311382293701172

  最后会得到用线程池的方式可以比直接执行快1倍。
  而且这个代码是 py2 兼容的。
  通过这个方式可以在 Maya 拍屏的时候用多线程调用 imagemagick 来对生成的图像进行处理。
  这样用户几乎感受不到图像后处理的时间。

总结

  以上就是 Maya 各种拍屏方案汇总,使用序列帧的自由度比较高,但是需要 ffmpeg 和 imagemagick 等依赖进行处理。
  简单的需求可以直接用 playblast 加上 ZShotmask VP2 完成。