计算机视觉/视频分析

利用三维合成数据进行自举目标检测模型训练

训练人工智能模型需要大量的数据。获取大量训练数据可能很困难、耗时且成本高昂。此外,所收集的数据可能无法涵盖各种角落的情况,从而使人工智能模型无法准确预测各种场景。

Synthetic data提供了一种替代真实世界数据的方法,使人工智能研究人员和工程师能够引导人工智能模型训练。除了引导模型训练外,研究人员还可以通过改变许多不同的参数(如位置、颜色、对象大小或照明条件)来快速生成新的数据集,以生成有助于创建通用模型的不同数据

这篇文章向你展示了如何使用一个模型,使用从NVIDIA Omniverse Replicator,一个以编程方式生成物理上精确的 3D 合成数据的 SDKpretrained model使用这些数据,而不是收集真实世界的数据。使用合成数据,可以创建所需的确切场景,甚至可以添加新元素或调整场景,从而进一步迭代对象检测管道

Flowchart showing the various steps covered in this article. You start with data generation, train our model, evaluate our model, and deploy. Then monitor performance and start the cycle over again.
图 1 。工作流程包括数据生成、模型训练、模型评估和模型部署

构建数据集

要生成合成数据,首先要在数字世界中创建环境。对于这里给出的示例,环境是一个在所有生成的数据中都是一致的曲面

在本节中,您正在使用NVIDIA Omniverse Code以运行复制器脚本。以下屏幕截图来自 NVIDIA Omniverse 代码 GUI 。完成脚本后,您可以继续在 NVIDIA Omniverse 代码中运行,以查看生成的数据,也可以在本地终端中以无头模式运行。将对这两种方法进行描述。

为此,加载三个通用场景描述( USD )包括在NVIDIA Omniverse使用以下代码创建基本场景:

with rep.new_layer():
    CRATE = 'omniverse://localhost/NVIDIA/Samples/Marbles/assets/standalone/SM_room_crate_3/SM_room_crate_3.usd'    
    SURFACE = 'omniverse://localhost/NVIDIA/Assets/Scenes/Templates/Basic/display_riser.usd'    
    ENVS = 'omniverse://localhost/NVIDIA/Assets/Scenes/Templates/Interior/ZetCG_ExhibitionHall.usd'

加载这些资产后,使用 NVIDIA Omniverse Replicator API 将它们设置为场景中的静态元素。从装载的 USDs 中创建 NVIDIA Omniverse Replicator 元素,并将水果箱放在地面上的位置和重量。为了让板条箱位于表面顶部,制作两个物理对撞机,这样一个对撞机就不会“穿过”另一个:

env = rep.create.from_usd(ENVS)
    surface = rep.create.from_usd(SURFACE)
    with surface:
        rep.physics.collider()
    crate = rep.create.from_usd(CRATE)
    with crate:
        rep.physics.collider()
        rep.physics.mass(mass=10000)
        rep.modify.pose(
                position=(0, 20, 0),
                rotation=(0, 0, 90)
            )
Image of an empty fruit crate, showing the beginnings of a Replicator scene, with the fruit crate placed in the center of the screen, from a bird’s-eye view.
图 2: NVIDIA Omniverse 视口中表面上的空机箱

接下来,加载水果 USD 资产,这一次将它们存储在一个字典中,将它们的类名作为关键字,将资产位置作为值。使用这种方法,您可以稍后对它们进行迭代。

FRUIT_PROPS = {
        'apple': 'omniverse://localhost/NVIDIA/Assets/ArchVis/Residential/Food/Fruit/Apple.usd',
        'avocado': 'omniverse://localhost/NVIDIA/Assets/ArchVis/Residential/Food/Fruit/Avocado01.usd',
        'kiwi': 'omniverse://localhost/NVIDIA/Assets/ArchVis/Residential/Food/Fruit/Kiwi01.usd',
        'lime': 'omniverse://localhost/NVIDIA/Assets/ArchVis/Residential/Food/Fruit/Lime01.usd',
        'lychee': 'omniverse://localhost/NVIDIA/Assets/ArchVis/Residential/Food/Fruit/Lychee01.usd',
        'pomegranate': 'omniverse://localhost/NVIDIA/Assets/ArchVis/Residential/Food/Fruit/Pomegranate01.usd',
        'onion': 'omniverse://localhost/NVIDIA/Assets/ArchVis/Residential/Food/Vegetables/RedOnion.usd',
        'lemon': 'omniverse://localhost/NVIDIA/Assets/ArchVis/Residential/Decor/Tchotchkes/Lemon_01.usd',
        'orange': 'omniverse://localhost/NVIDIA/Assets/ArchVis/Residential/Decor/Tchotchkes/Orange_01.usd'    }

要生成每帧中不同的数据,请随机化每帧中出现的水果、水果在板条箱中出现的位置以及水果总数。这为实际生产线中可能看到的每种水果配置提供了最大的覆盖范围。

def random_props(file_name, class_name, max_number=1, one_in_n_chance=3):
        instances = rep.randomizer.instantiate(file_name, size=max_number, mode='scene_instance')
        print(file_name)
        with instances:
            rep.modify.semantics([('class', class_name)])
            rep.modify.pose(
                position=rep.distribution.uniform((-8, 5, -25), (8, 30, 25)),
                rotation=rep.distribution.uniform((-180,-180, -180), (180, 180, 180)),
                scale = rep.distribution.uniform((0.8), (1.2)), 
            )

            rep.modify.visibility(rep.distribution.choice([True],[False]*(one_in_n_chance)))
        return instances.node
Three images show variations of the fruits placed in the crate, some overlapping and in different orientations.
图 3 。水果以随机数量、数量和位置放置在板条箱内

为了进一步使数据集多样化,在每一帧中引入一些其他随机化。首先,使用以下代码随机化每帧的光的数量、颜色和数量:

def sphere_lights(num):
        lights = rep.create.light(
            light_type="Sphere",
            temperature=rep.distribution.normal(6500, 500),
            intensity=rep.distribution.normal(30000, 5000),
            position=rep.distribution.uniform((-300, -300, -300), (300, 300, 300)),
            scale=rep.distribution.uniform(50, 100),
            count=num        )
        return lights.node    
    rep.randomizer.register(sphere_lights)
Three fruit crates with clear variations in lighting.
图 4 。板条箱内水果的照明变化

摄像机角度是引入场景的另一种变化,以考虑板条箱的不同位置和摄像机高度。下面的代码还确保相机始终面向机箱的位置,即使其位置已调整。

with camera:
            rep.modify.pose(position=rep.distribution.uniform((-10, 105, -20), (5, 120, -5)), look_at=(0,20,0))
Three fruit crates, each showing a slightly different view as the orientation of the camera changes.
图 5 。摄像机角度变化

最后一步是运行数据生成脚本并记录所需的信息。对于本例,为生成的每一帧中的水果写出基线 RGB 数据、边界框和标签

with rep.trigger.on_frame(num_frames=10):
        for n, f in FRUIT_PROPS.items():
            random_props(f, n)

        rep.randomizer.sphere_lights(5)

    # Initialize and attach writer    
    writer = rep.WriterRegistry.get("BasicWriter")
    writer.initialize(output_dir="fruit_data", rgb=True, bounding_box_2d_tight=True)
    writer.attach([render_product])

创建 NVIDIA Omniverse Replicator 脚本后,有两种方法可以生成完整的数据集。前面的图像是来自 NVIDIA Omniverse 代码 GUI 的剧照,它使您能够调整场景并立即可视化更改。这个 GUI 使您有机会在每次代码更改时可视化数据。一旦您对脚本感到满意,就可以生成一个包含更多图像的完整数据集

接下来的步骤详细介绍了如何在 NVIDIA Omniverse 代码 GUI 的脚本编辑器中继续运行代码,或者完全在自己的本地终端中运行代码。

要运行无头脚本,请在现有脚本的末尾添加以下行:

rep.orchestrator.run

要在 NVIDIA Omniverse 容器内无头运行此代码,请首先找到omni.code.replicator.sh剧本打开 NVIDIA Omniverse 启动器并导航到代码应用程序。单击启动按钮右侧的菜单,查看 NVIDIA Omniverse 代码安装的位置。从该文件夹中,您可以运行以下命令,传入您自己的无头脚本的位置:

./omni.code.replicator.sh  --no-window --/omni/replicator/script="FruitBasketOVEReplicatorDemo/data_generation/generate_data_headless.py"

浏览您的数据

使用 Replicator 脚本创建第一个帧后,可以调整参数以获得所需的输出。为此,您可能需要查看放置在生成图像上的边界框

NVIDIA Omniverse 中的函数用于可视化生成图像上边界框数据的颜色和标签。这些函数获取生成的背景图像的路径、边界框数据、类标签以及使用彩色边界框存储可视化的位置。

def colorize_bbox_2d(rgb_path, data, id_to_labels, file_path):

    rgb_img = Image.open(rgb_path)
    colors = [data_to_colour(bbox["semanticId"]) for bbox in data]
    fig, ax = plt.subplots(figsize=(10, 10))
    ax.imshow(rgb_img)
    for bbox_2d, color, index in zip(data, colors, range(len(data))):
        labels = id_to_labels[str(index)]
        rect = patches.Rectangle(
            xy=(bbox_2d["x_min"], bbox_2d["y_min"]),
            width=bbox_2d["x_max"] - bbox_2d["x_min"],
            height=bbox_2d["y_max"] - bbox_2d["y_min"],
            edgecolor=color,
            linewidth=2,
            label=labels,
            fill=False,
        )
        ax.add_patch(rect)

    plt.legend(loc="upper left")

    plt.savefig(file_path)

要使用上面显示的函数,请使用生成的数据运行以下命令:

bbox2d_loose_file_name = "bounding_box_2d_tight_0.npy"
data = np.load(os.path.join(out_dir, bbox2d_tight_file_name))

bbox2d_tight_labels_file_name = "bounding_box_2d_tight_labels_0.json"
with open(os.path.join(out_dir, bbox2d_tight_labels_file_name), "r") as json_data:
    bbox2d_loose_id_to_labels = json.load(json_data)

colorize_bbox_2d(rgb_path, data, bbox2d_loose_id_to_labels, os.path.join(vis_out_dir, "bbox2d_tight.png"))
A fruit crate visualized in matplotlib with a legend with fruit labels and colors that correspond to labeled bounding boxes within our fruit crate.
图 6 。在 matplotlib 中可视化的水果箱,带有与边界框相对应的水果标签和颜色的图例

训练你的模特

生成数据后,可以启动模型训练工作流。此示例使用PyTorchtorchvision 软件包,用于微调预训练的 Faster R-CNN 模型。然而,合成数据也可以引入其他使用工具的管道,如NVIDIA TAO 工具包或 TensorFlow

训练脚本中的第一步是定义数据集以构建 PyTorch DataLoader 。做一些初步工作,对三种文件类型进行排序,并将边界框信息与正确的水果标签关联起来。这里的关键输出是具有边界框信息、水果标签、框区域和图像 ID 的目标

target = {}
        target["boxes"] = torch.as_tensor(boxes, dtype=torch.float32)
        target["labels"] = torch.as_tensor(labels_out, dtype=torch.int64)
        target["image_id"] = torch.tensor([idx]) 
        target["area"] = area

一旦您有了数据集,就可以将数据拆分为训练管道的训练、验证和测试部分。然后使用以下代码为训练和验证数据集创建数据加载器:

    data_loader = torch.utils.data.DataLoader(
    dataset, batch_size=16, shuffle=True, num_workers=4,
    collate_fn= collate_fn) 
    validloader = torch.utils.data.DataLoader(
    valid, batch_size=16, shuffle=True, num_workers=4,
    collate_fn= collate_fn) 

配置好数据集和数据加载器后,继续训练。跟踪每个历元的损失,并将数据配置为显示在 TensorBoard 中以进行可视化

params = [p for p in model.parameters() if p.requires_grad]
    optimizer = torch.optim.SGD(params, lr=0.001)
    len_dataloader = len(data_loader)
    model.train()
    for epoch in range(num_epochs):
        optimizer.zero_grad()

        i = 0    
        for imgs, annotations in data_loader:
            i += 1
            imgs = list(img.to(device) for img in imgs)
            annotations = [{k: v.to(device) for k, v in t.items()} for t in annotations]
            loss_dict = model(imgs, annotations)
            losses = sum(loss for loss in loss_dict.values())
            writer.add_scalar("Loss/train", losses, epoch)

            losses.backward()
            optimizer.step()

            print(f'Iteration: {i}/{len_dataloader}, Loss: {losses}')
Screenshot of web browser showing TensorBoard graph with loss over time. The loss decreases as the training continues.
图 7 。损失图显示训练随着时间的推移而改善

当损失充分减少,并且对模型的训练方式感到满意时,请保存模型。最后一步是将模型部署到生产环境中

将您的模型部署到生产中

首先,使用NVIDIA Triton 推理服务器使用以下脚本将模型导出为 ONNX 格式:

torch.onnx.export(model,
                    dummy_input,
                    os.path.join(OUTPUT_DIR, "model.onnx"),
                    opset_version=11,
                    input_names=["input"],
                    output_names=["boxes", "labels", "scores", "masks"]
                    )

接下来,从 NGC 运行 NVIDIA Triton 容器,然后使用以下命令启动服务器:

tritonserver --model-repository=/model_repository --model-control-mode explicit --exit-on-error 0 --repository-poll-secs 3

使用 NVIDIA Triton ,您可以“轮询”模型存储库,以查看是否发生了更改。然后,将模型复制到 model _ repository 目录中。

接下来,使用 bash 将模型复制到 NVIDIA Triton 目录中,使用以下代码:

mkdir model_repository/dmcount_onnx/ # create the folder with the model name
mkdir model_repository/dmcount_onnx/1/ # create the folder for the model version
cp model.onnx model_repository/dmcount_onnx/1/ # move the file to the directory

现在是推理的时候了。

完成循环

在部署模型之后,您可以选择进一步迭代对象检测管道的第一个完整实现。如果您选择添加到原始数据集,则可以在相对较少的开销下完成添加

例如,要将新水果(草莓)添加到原始选项集,请将新资源加载到原始字典并生成新数据:

'strawberry': 'omniverse://localhost/NVIDIA/Assets/ArchVis/Residential/Food/Berries/strawberry.usd',

在训练步骤中,调整标签映射以使草莓添加到数据中:

static_labels = {
            'apple' : 0,
            'avocado' : 1,
            'kiwi' : 2,
            'lime' : 3,
            'lychee' : 4,
            'pomegranate' : 5,
            'onion' : 6,
            'strawberry' : 7,
            'lemon' : 8,
            'orange' : 9,
        }

现在,您可以像以前一样可视化边界框数据,注意到在生成的一些帧中添加了草莓。管道的其他元素保持不变。在生产部署中,草莓显示为注释正确

A bird’s-eye view of a crate with fruit randomization and the new addition of a strawberry.
图 8 。添加草莓的水果箱

在使用合成数据时,将新数据引入对象检测管道是简化的,这使得部署到生产中成为一个触手可及的目标。合成数据释放了迭代训练工作流程的全部潜力

当您可以轻松地修改、调整和生成大量数据时,您就不再被培训管道的第一步所束缚,可以专注于微调和将模型推向生产。

总结

本教程展示了如何将合成数据集成到现有模型中。第一步展示了 NVIDIA Omniverse 代码如何为您提供一个用于编写自己的 Replicator 脚本的交互式 GUI 。此脚本可以根据场景和模型所需的数据首选项进行自定义。然后,您可以根据您的确切规范创建预先标记的数据。从那里,您可以将合成数据带到任何您想要的地方。本文中的示例将数据集成到 TorchVision 管道中进行微调

这个过程的最后一步是将经过训练的模型部署到 NVIDIA Triton 中进行推理。当您将合成数据集成到现有工作流中时,此工作流代表了您所拥有的所有选项。 NVIDIA Omniverse Replicator 为您提供了一个工具,可以迭代生成合成数据,以适应您现有的工作流程。

为了开始使用 Omniverse Replicator ,下载 NVIDIA Omniverse并安装NVIDIA Omniverse Code应用程序。要访问代码和其他 Omniverse Replicator 合成数据示例,请访问NVIDIA-Omniverse/synthetic-data-examples在 GitHub 上。

额外资源

想了解更多信息吗?看看这些专家引导的NVIDIA GTC 2023 关于生成合成数据和 Omniverse Replicator 的会话.

有关更多信息和最新消息,请参阅以下资源:

 

 

Tags