【翻译】Blender 中能否通过脚本在平面上实时显示网络摄像头的图像?

268 阅读3分钟

StackExchange 上的原问题

Blender 中能否通过脚本在平面上实时显示网络摄像头的图像?

高赞回答

你可以编写一个 python 操作符,它可以连续地从网络摄像头抓取帧,并将它们写入图像纹理。

  1. 添加一个图像纹理并命名为 image
  2. 添加一个 3D 对象和纹理与图像。你将不得不去材质或渲染视图看到图像。
  3. 创建一个新的文本块(text block)。编写一个模态定时器操作符(Operator Modal Timer)。你可以在文本编辑器菜单中获得该脚本的结构: Templates > Python > Operator Modal Timer

模态定时器操作符 是一个 Blender 操作符,可以从接口调用它并启动一个计时器。计时器以固定的时间间隔调用函数。在这个函数中,我们将抓取相机帧并将其写到图像上。

  1. 安装 opencv-python,可以访问摄像头。确定 Blender 正在使用的是包含 python 可执行文件的文件夹。然后你可以进入 python 控制台,并输入 import sys,另起一行输入 sys.exec_prefix,按下回车键后,你会看到 python 可执行文件夹的路径。转到该路径 (例如 …\2.82\python\bin) 并打开一个新的命令行界面。使用命令 python -m pip Install opencv-python 安装 opencv-python,该命令还将安装其他相关模块,如 numpy。重新启动 Blender 。

现在已经设置好了,打开 模态操作符 并从它的类方法中删除所有内容。当进入 模态操作符 时,调用 execute。在这个方法中,我们必须初始化 模态中的 _timer、opencv 中的 VideoCapture 和 Blender 中的图像引用。

def execute(self, context):
        wm = context.window_manager
        self._timer = wm.event_timer_add(0.5, window=context.window)
        self._cap = cv2.VideoCapture(0)
        self._img = bpy.data.images['image']
        self._size = tuple(self._img.size)

        wm.modal_handler_add(self)
        return {'RUNNING_MODAL'}

模态方法在每个计时器滴答的时候被调用。和模板中一样,当我们退出模态操作符时,让我们保留退出条件(如按下 Esc 键)的检查。在计时器滴答声中,我们首先用 opencv VideCapture 对象抓取帧。

ret, frame = self._cap.read()

然后我们调整捕获的帧的大小,以适应我们的 Blender 图像的尺寸。

resized = cv2.resize(frame, self._size, interpolation = cv2.INTER_AREA)

为了使 numpy 数组(这是 opencv 从网络摄像头产生的图像)与 Blender 的图像数据块兼容,我们必须翻转 RB 通道以及 y 维,将图像像素转换为 浮点数 并将其 平展。(Blender 期望图像数据是一长串 rgba 像素值)

这个生成的 “列表” 可以分配给 Blender 图像的像素属性。

rgb = np.flip(resized, axis=[0, 2])
rgba = np.ones((self._size[1], self._size[0], 4), dtype=np.float32)
rgba[:,:,:-1] = np.float32(rgb) / 255
self._img.pixels = rgba.flatten()

完整的脚本在这个答案的最后。在文本编辑器中按 Run Script 运行它。这将把 模态运算符 添加到 Blender 运行时内存中。在 3D 视图中,按 F3 并键入您的 模态操作符的名称(您的类的 bl_label)。我将这些类命名为 bl_label Modal Timer Operator,就像在模板中一样。然后单击结果。

此脚本仅供学习,运行此脚本,风险自负。Blender 可能会崩溃,记得备份。

虽然帧率非常低,但我可以看到我的相机捕捉到的图像。然而,viewport 更新采样方法无法获得足够的样本来对图像进行足够的修改。

myFgM.gif

虽然我的回答给出了一种将摄像头图像 “实时” 注入 Blender 的方法,但它显然还是太卡顿了,而且几乎从用户界面中破坏了交互性。要使其远程可用,您必须实现多线程,将图像存储在某种并发包中。

完整代码

import bpy
import cv2
import numpy as np

class ModalTimerOperator(bpy.types.Operator):
    bl_idname = "wm.modal_timer_operator"
    bl_label = "Modal Timer Operator"

    _timer = None
    _cap = None
    _img = None
    _size = None

    def modal(self, context, event):
        if event.type in {'ESC'}:
            self.cancel(context)
            return {'CANCELLED'}

        if event.type == 'TIMER':
            ret, frame = self._cap.read()
            resized = cv2.resize(frame, self._size, interpolation = cv2.INTER_AREA)
            rgb = np.flip(resized, axis=[0, 2])

            rgba = np.ones((self._size[1], self._size[0], 4), dtype=np.float32)
            rgba[:,:,:-1] = np.float32(rgb) / 255
            self._img.pixels = rgba.flatten()

        return {'PASS_THROUGH'}

    def execute(self, context):
        wm = context.window_manager
        self._timer = wm.event_timer_add(0.5, window=context.window)
        self._cap = cv2.VideoCapture(0)
        self._img = bpy.data.images['image']
        self._size = tuple(self._img.size)

        wm.modal_handler_add(self)
        return {'RUNNING_MODAL'}

    def cancel(self, context):
        wm = context.window_manager
        wm.event_timer_remove(self._timer)
        self._cap.release()

def register():
    bpy.utils.register_class(ModalTimerOperator)

def unregister():
    bpy.utils.unregister_class(ModalTimerOperator)

if __name__ == "__main__":
    register()