视频重放器
到目前为止,我们一直在使用简单的算子来演示 Holoscan SDK 的概念。在本示例中,我们将介绍两个内置的 Holoscan 算子,它们具有许多实际应用。
在本示例中,我们将介绍
如何使用 VideoStreamReplayerOp 算子从磁盘加载视频文件。
如何使用 HolovizOp 算子显示视频。
如何使用 YAML 配置文件配置算子的参数。
示例源代码和运行说明可以在 GitHub 上的 examples 目录中找到,或者在 NGC 容器和 Debian 包中的 /opt/nvidia/holoscan/examples
下找到,以及它们的可执行文件。
以下是本示例中使用的算子和工作流程的示意图。

图 8 从文件加载和显示视频的工作流程
我们将重放器算子的“output”端口连接到 Holoviz 算子的“receivers”端口。
内置的视频流重放器算子可用于重放已编码为 gxf 实体的视频流。您可以使用 convert_video_to_gxf_entities.py
脚本(安装在 /opt/nvidia/holoscan/bin
中或在 GitHub 上 提供)将视频文件编码为 gxf 实体,供此算子使用。
此算子按顺序处理编码文件,并支持预先录制数据的实时、快于实时或慢于实时播放。输入数据可以选择重复循环播放,或者仅循环播放指定的次数。有关更多详细信息,请参阅 VideoStreamReplayerOp
。
我们将使用重放器从磁盘读取 GXF 实体,并将帧向下游发送到 Holoviz 算子。
内置的 Holoviz 算子提供以下功能:将实时帧流与多个不同的其他层(如分割掩码层、几何层和 GUI 层)合成。
我们将使用 Holoviz 显示由重放器算子发送到其“receivers”端口的帧,该端口可以接收任意数量的输入。在更复杂的工作流程中,此端口可以接收多个输入数据流,例如,一个流是原始视频数据,而其他流检测视频中的对象以创建边界框和/或文本覆盖。
SDK 支持读取可选的 YAML 配置文件,可用于自定义应用程序的工作流程和算子。对于更复杂的工作流程,使用应用程序配置文件来帮助将算子参数设置与代码分离可能很有用。有关更多详细信息,请参阅 配置应用程序。
对于 C++ 应用程序,配置文件提供了一种在运行时设置应用程序行为的便捷方法,而无需重新编译代码。
本示例使用以下配置文件来配置重放器和 Holoviz 算子的参数。完整参数列表可以在 VideoStreamReplayerOp
和 HolovizOp
中找到。
%YAML 1.2
replayer:
directory: "../data/racerx" # Path to gxf entity video data
basename: "racerx" # Look for <basename>.gxf_{entities|index}
frame_rate: 0 # Frame rate to replay. (default: 0 follow frame rate in timestamps)
repeat: true # Loop video? (default: false)
realtime: true # Play in realtime, based on frame_rate/timestamps (default: true)
count: 0 # Number of frames to read (default: 0 for no frame count restriction)
holoviz:
width: 854 # width of window size
height: 480 # height of window size
tensors:
- name: "" # name of tensor containing input data to display
type: color # input type e.g., color, triangles, text, depth_map
opacity: 1.0 # layer opacity
priority: 0 # determines render order, higher priority layers are rendered on top
以下代码显示了我们的 video_replayer
示例。算子参数通过 from_config()
(C++) 和 self.**kwargs()
(Python) 从配置文件配置。
#include <holoscan/holoscan.hpp>
#include <holoscan/operators/video_stream_replayer/video_stream_replayer.hpp>
#include <holoscan/operators/holoviz/holoviz.hpp>
class VideoReplayerApp : public holoscan::Application {
public:
void compose() override {
using namespace holoscan;
// Define the replayer and holoviz operators and configure using yaml configuration
auto replayer = make_operator<ops::VideoStreamReplayerOp>("replayer", from_config("replayer"));
auto visualizer = make_operator<ops::HolovizOp>("holoviz", from_config("holoviz"));
// Define the workflow: replayer -> holoviz
add_flow(replayer, visualizer, {{"output", "receivers"}});
}
};
int main(int argc, char** argv) {
// Get the yaml configuration file
auto config_path = std::filesystem::canonical(argv[0]).parent_path();
config_path /= std::filesystem::path("video_replayer.yaml");
if ( argc >= 2 ) {
config_path = argv[1];
}
auto app = holoscan::make_application<VideoReplayerApp>();
app->config(config_path);
app->run();
return 0;
}
内置的 VideoStreamReplayerOp 和 HolovizOp 算子分别从第 1 行和第 2 行引入。
我们创建了一个名为“replayer”的 VideoStreamReplayerOp 实例,其参数使用调用
from_config()
(第 11 行) 从 YAML 配置文件初始化。我们创建了一个名为“holoviz”的 HolovizOp 实例,其参数使用调用
from_config()
(第 12 行) 从 YAML 配置文件初始化。“replayer”算子的“output”端口连接到“holoviz”算子的“receivers”端口,并定义应用程序工作流程(第 34 行)。
应用程序的 YAML 配置文件包含我们算子的参数,并在第 28 行加载。如果未将任何参数传递给可执行文件,则应用程序会在与可执行文件相同的目录中查找名为“video_replayer.yaml”的文件(第 21-22 行),否则它会将该参数视为应用程序 YAML 配置文件的路径(第 23-25 行)。
import os
import sys
from holoscan.core import Application
from holoscan.operators import HolovizOp, VideoStreamReplayerOp
sample_data_path = os.environ.get("HOLOSCAN_INPUT_PATH", "../data")
class VideoReplayerApp(Application):
"""Example of an application that uses the operators defined above.
This application has the following operators:
- VideoStreamReplayerOp
- HolovizOp
The VideoStreamReplayerOp reads a video file and sends the frames to the HolovizOp.
The HolovizOp displays the frames.
"""
def compose(self):
video_dir = os.path.join(sample_data_path, "racerx")
if not os.path.exists(video_dir):
raise ValueError(f"Could not find video data:{video_dir=}")
# Define the replayer and holoviz operators
replayer = VideoStreamReplayerOp(
self, name="replayer", directory=video_dir, **self.kwargs("replayer")
)
visualizer = HolovizOp(self, name="holoviz", **self.kwargs("holoviz"))
# Define the workflow
self.add_flow(replayer, visualizer, {("output", "receivers")})
def main(config_file):
app = VideoReplayerApp()
# if the --config command line argument was provided, it will override this config_file
app.config(config_file)
app.run()
if __name__ == "__main__":
config_file = os.path.join(os.path.dirname(__file__), "video_replayer.yaml")
main(config_file=config_file)
内置的 VideoStreamReplayerOp 和 HolovizOp 算子在第 5 行导入。
我们创建了一个名为“replayer”的 VideoStreamReplayerOp 实例,其参数使用
**self.kwargs()
(第 28-30 行) 从 YAML 配置文件初始化。对于 Python 脚本,GXF 实体视频数据的路径未在应用程序配置文件中设置,而是由第 7 行和第 23 行的代码确定,并直接作为“directory”参数传递(第 29 行)。这允许用户通过设置
HOLOSCAN_INPUT_PATH
目录(第 7 行)从任何目录运行脚本,从而获得更大的灵活性。我们创建了一个名为“holoviz”的 HolovizOp 实例,其参数使用
**self.kwargs()
(第 31 行) 从 YAML 配置文件初始化。“replayer”算子的“output”端口连接到“holoviz”算子的“receivers”端口,并定义应用程序工作流程(第 34 行)。
应用程序的 YAML 配置文件包含我们算子的参数,并在第 45 行加载。如果未将任何参数传递给 Python 脚本,则应用程序会在与脚本相同的目录中查找名为“video_replayer.yaml”的文件(第 39 行)。否则,它会将该参数视为应用程序 YAML 配置文件的路径(第 41-42 行)。
运行应用程序应启动 YAML 文件中引用的视频的视频播放。
