VisionFlow 入门教程#

我们已经在 在开发环境中引入VisionFlow 文档中解释了如何将 VisionFlow 添加为 您项目的依赖项。现在,我们将通过一个简单的示例快速演示 VisionFlow 中的主要功能及接口的使用。

这个示例将包括从创建工程、添加工具、添加数据和标注,训练模型,到部署模型的完整工作流程。如果您对如 何通过 VisionFlow 接口创建工程和训练模型不感兴趣,只想知道如何部署已导出为模型文件的训练好的模型, 那么您可以从 加载导出的模型 部分开始阅读。

初始化VisionFlow#

VisionFlow 库有一些在运行时需要指定并且必须在调用任何其他接口之前设置的全局配置,包括:

  1. 日志输出设置:通过设置日志输出选项,您可以自定义 VisionFlow 的日志输出方式,例如指定文件路径、 回调函数、标准输出/终端或 MSVC 调试器。

  2. 语言偏好:VisionFlow 支持多种语言。默认情况下,在初始化过程中,语言偏好会自动设置为与您的操作系统 语言匹配。如果您希望使用不同的语言,可以通过初始化参数来指定。

  3. 授权设置:VisionFlow 中的部分功能需要商业授权(加密狗),一般情况下,VisionFlow会自动从你的执行 环境中自动查找授权。但部分情况下,你可能希望自行指定使用的授权设备的ID,授权设备ID也可以在初始化时 指定。

  4. 产品标识设置:详情请移步 产品标识

以下是 VisionFlow 初始化的示例:

#include <iostream>
#include "visionflow/visionflow.hpp"

namespace vflow = visionflow;

void my_logger_output(int log_level, const char *content, size_t len) {
    if (log_level > 2) {
        std::cout << std::string(content, len) << std::endl;
    }
}

int main(int /*argc*/, char ** /*argv*/) try {

    vflow::InitOptions opts;

    // Set the log output file.
    opts.logger.file_sink = "visionflow.log";
    // Set whether to output the logs to the standard output terminal.
    opts.logger.stdout_sink = true;
    // You can customize the handling of VisionFlow's log output by setting a log output callback function.
    // opts.logger.func_sink = my_logger_output;

    // Set the language to Chinese.
    opts.language = "zh_CN";

    // opts.license.license_id = "your license device id";

    // opts.product_mark = "YourProductName";
    // opts.compatible_product_marks = {"AIDI", "OtherProduct"};

    vflow::initialize(opts);

    return 0;
} catch (const std::exception &ex) {
    std::cerr << "Unexpected Escaped Exception: " << ex.what();
    return -1;
}
To be completed
using System;
using System.Runtime.InteropServices;

public class Program
{
    public static void Main()
    {
        try
        {
            visionflow.InitOptions opts = new visionflow.InitOptions();

            // Set the log output file.
            opts.logger.file_sink = "visionflow.log";
            // Set whether to output the logs to the standard output terminal.
            opts.logger.stdout_sink = true;

            // Set the language to Chinese.
            opts.language = "zh_CN";

            // opts.license.license_id = "your license device id";

            // opts.product_mark = "YourProductName";
            // opts.compatible_product_marks = {"AIDI", "OtherProduct"};

            visionflow_global.initialize(opts);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }
    }
}

创建工程#

Project 是 VisionFlow 中管理数据和处理工作流的基本单位。在继续进行任何后续步骤之前, 我们必须首先创建一个项目:

vflow::ProjectDescriptor desc;

desc.workspace_token = "D:/the/path/to/workspace";
desc.project_name = "my_first_project";

auto out_desc = vflow::Project::Create(desc);

std::cout << "My first VisionFlow project created at: "
          << out_desc.created_time << std::endl;
std::cout << "The VisionFlow version which the project created by: "
          << out_desc.sdk_version << std::endl;

// And then you can open the project with the desc or the output_desc.
auto project = vflow::Project::Open(desc);

std::cout << "My first VisionFlow project name is: "
          << project->descriptor().project_name << std::endl;
To be completed
To be completed

添加工具#

VisionFlow 提供了不同的算法工具,您可以自由地组合和连接这些工具以完成不同的处理流程。 您可以在 工具及详细流程图 中找到 VisionFlow 提供的所有 工具的功能说明及每一个工具的详细内部图结构。

在这个示例中,我们将添加一个 Input 工具和一个 Segmentation 工具,并将 Segmentation 工具连接到 Input 工具的输出:

std::string input_id = project->add_tool("Input");
std::string segmentation_id = project->add_tool("Segmentation");

bool connected = project->auto_connect(input_id, segmentation_id);
if (!connected) {
    std::cerr << "auto_connect failed, Please use Project::connect instead."
              << std::endl;
}
To be completed
To be completed

导入图片#

在 VisionFlow 中,我们使用 SampleSet 来管理项目内的数据。所有对 Sample 数据的管理都通过 SampleSetPropertySet 进行。一个项目可以有多个 SampleSet ,通常包括一个用于训练和验证模型的主数据集。不过,您也可以根据需要创建额外的 SampleSet 。将图像数据添加到项目中意味着将图像数据添加到项目内的一个数据集。以下是一个 从工程中获取 SampleSetPropertySet 的示例:

auto sample_set = project->main_sample_set();
auto input_image_set = sample_set.property_set({input_id, "image"});

// Or you can get the read only sample set and read only property set:
// auto ro_sample_set = project->readonly_main_sample_set();
// auto ro_input_image_set = sample_set.readonly_property_set({input_id, "image"});
To be completed
To be completed

导入图像到项目中有几种不同的方法。如果您想要将一系列图像文件导入到项目中,可以使用辅助类型 visionflow::helper::InputHelper 来完成这个任务。以下是一个示例:

const auto old_sample_num = sample_set.size();

vflow::helper::InputHelper input_helper(project.get());

for (int i = 0; i < 100; ++i) {
    input_helper.add_image("D:/path/to/image/" + std::to_string(i) + ".png");
}
// Import and commit the images.
auto receipts = input_helper.commit();

// Some image may be improted failed, you can get the reason from the receipts:
size_t success_count = 0;
for (const auto &receipt : receipts) {
    if (receipt.is_success) {
        success_count ++;
    }else {
        std::cout << "Failed to import image " << receipt.image_files[0]
                  << " as: " << receipt.error_message << std::endl;
    }
}

// And then you can get the sample from the sample set:
assert(sample_set.size() - old_sample_num == success_count);
To be completed
To be completed

在某些情况下,您的图像可能不存在于文件系统中,而是已经加载到内存中。在这种情况下,您可以使用函数 visionflow::helper::add_image_to_sample() 快速将图像添加到一个样本集中:

// We have a image loaded into memory:
auto image = vflow::Image::FromFile("D:/path/to/image/0.png");

// Create a sample template from the sample set:
auto sample = sample_set.create_empty_sample();
// Add the the image into sample:
vflow::helper::add_image_to_sample(sample, image, input_id, true, 512);
// Add the sample to sample set:
sample_set.add(sample);
To be completed
To be completed

Note

函数 visionflow::helper::add_image_to_sample() 的接口文档提供了关于该函数 详细的功能说明,这可以帮助您深入理解其更多详细使用方法和运行原理。如果您想更深入地了解 VisionFlow 的数据管理机制,强烈建议仔细阅读该文档。

Note

通过 visionflow::helper::add_image_to_sample() 初始化的sample,若需将其添 加到样本集,则thumbnail_long_side参数不能为0。

添加标注#

在介绍如何向数据集添加注释之前,让我们简要解释一下如何通过 :term:PropertySet` 中读取和写入数据。 PropertySet 是一个可以被迭代访问的数据容器。每个属性集对应处理流程中的一个数据节点, 并包含数据集中所有样本在该节点上的数据。以我们之前添加的图像属性集为例,我们可以按如下方式访问其 中的数据(您可以参考 visionflow::data::PropertySet 的文档,了解与 PropertySet 相关的更多接口信息):

// 获取属性集.
auto image_set = sample_set.property_set({input_id, "image"});
// 遍历属性集中的数据.
for (const auto &[sample_id, image_prop] : image_set) {
    if (image_prop) { // 如果某个样本在此数据节点上的数据不存在,则会返回空指针.
        image_prop->as<vflow::props::Image>().image().show(10);
    } else {
        auto modify_image = vflow::Image::FromFile("D:/path/to/image/0.png");
        vflow::props::Image new_prop(modify_image);
        // 更新属性集中的数据.
        image_set.update(sample_id, new_prop);
    }
}
To be completed
To be completed

通过这种方式,我们可以将在图像导入过程中生成的所有视图添加到训练集中(这对于后续的训练是必要的, 因为只有添加到训练集的视图才会参与训练过程):

auto views_set = sample_set.property_set({input_id, "views"});

for (auto [sample_id, views_prop] : views_set) {
    if (views_prop) {
        for (auto &[view_id, view] : views_prop->as<vflow::props::ViewList>()) {
            view.set_split_tag(vflow::kTrain);
        }
        views_set.update(sample_id, *views_prop);
    }
}
To be completed
To be completed

在理解了属性集的功能和用法之后,添加注释本质上是将我们的注释数据更新到与注释数据节点相对应的属性集中:

auto label_set = sample_set.property_set({segmentation_id, "truth"});

for (const auto &[sample_id, old_label] : label_set) {
    // 你可以使用你自定义的函数替换下面的流程,例如:
    // auto new_label = show_and_modify_label(image_prop, old_label);
    if (old_label) {
        auto new_label = old_label->as<vflow::props::PolygonRegionList>();
        for (auto &[region_id, region] : new_label) {
            region.set_name("MyClass");
        }
        label_set.update(sample_id, new_label);
    }
}
To be completed
To be completed

设置训练参数#

不同的工具在不同的阶段需要不同的参数集。对于我们示例中的分割工具,在训练之前,我们需要设置以下参数组:

// 设置标注的类别清单.
vflow::param::LabelClasses classes;
classes.add("MyClass");
project->set_param({segmentation_id, "classes"}, classes);

// 设置用于训练的图像颜色.
vflow::param::BaseColor color;
color.set_color(vflow::param::kGray);
project->set_param({segmentation_id, "base_color"}, color);

// 设置训练参数.
vflow::param::SegmentationTrainingParameters train_param;
train_param.set_epoch(10)
    .get_augmentations()
    .get_geometry_augmentation()
    .set_flip_horizontal(true)
    .set_flip_vertical(true);
project->set_param({segmentation_id, "trainer.args"}, train_param);
To be completed
To be completed

训练和配置参数#

一旦所有数据准备好,训练模型非常简单:

// 创建训练器的策略,你可以通过阅读有关类型的详细接口文档.
// 详细了解这些参数的作用.
vflow::runtime::StrategyOptions strategy;
strategy.allow_auto_update = true;
strategy.allow_auto_update_rely_on_prop = true;
strategy.ignore_update_time_requirement = true;
// 你可以通过设置自定义的回调函数来接收训练进度信息.
strategy.call_back = nullptr;

// 创建训练执行器.
auto trainer = project->create_config_runtime({segmentation_id, "trainer"}, strategy);

// 为训练执行器创建数据服务.
auto data_server = vflow::adapt(project.get(), trainer);

// 初始化和执行训练,训练成功后模型会自动保存到工程中.
trainer.initialize(data_server);
trainer.execute(data_server);
To be completed
To be completed

导出模型#

在配置好参数并训练好模型之后,您可能希望将模型部署到其他主机。一种方法是通过直接将工程复制到另一台 主机,并使用复制的工程来部署您的检测检测流程。但是,由于工程包含了用于训练和验证模型的所有数据, 其体积可能相当大,导致直接复制非常不方便。为了便于部署,您可以将所有模型和配置的参数导出到一个独立 的文件中,如下所示:

project->export_model("D:/path/to/save/my_first_project.vfmodel");
To be completed
To be completed

如果一切顺利,现在您应该能够在 D:/path/to/save/ 目录中找到名为 my_first_project.vfmodel 的文件。您可以将此文件复制到需要部署检测流程的机器上,并继续按照以下的部署步骤来完成模型的部署。

加载导出的模型#

在加载导出的模型之前,请确保 VisionFlow 依赖库已经初始化。有关初始化依赖库的详细过程,请参阅 初始化VisionFlow 部分。

之后,您可以按照下面的代码示例打开导出的模型:

vflow::Model model("D:/path/to/my_first_project.vfmodel");
To be completed
visionflow.Model model = new visionflow.Model("D:/path/to/my_first_project.vfmodel");

设置推理参数#

在部署阶段,我们提供了一些便捷的接口,允许您读取和修改模型中的某些参数。虽然在实际情况中,部署阶段 可以修改的参数是有限的,但在接口上,我们仍然允许通过接口修改模型中的任何参数。打开模型后,您可以通 过从模型中读取参数、进行必要的更改,然后将其保存回模型中来修改参数,如下所示:

// 根据你的工程中的工具的名称读取相应工具中参数.
std::string segmentation_id = "Segmentation";
auto filter_param = model.get_param({segmentation_id, "filter.args"});
if (!filter_param) {
    std::cerr << "Filter parameter for Segmentation not exist." << std::endl;
    exit(-1);
}

// 在这里,我们修改了类别名为"MyClass"的缺陷的过滤参数.
filter_param->as<vflow::param::PolygonsFilterParameters>()
    .get_class_thresholds("MyClass")
    .set_enable(true)
    .set_area_range({100, 50000});

// 然后, 你可以将修改后的参数重新保存到模型中.
model.set_param({segmentation_id, "filter.args"}, *filter_param);
To be completed
// 根据你的工程中的工具的名称读取相应工具中参数.
string segmentation_id = "Segmentation";
// 这里读取了推理结果之后的过滤参数.
var tool_node_id = new visionflow.ToolNodeId(segmentation_id, "filter.args");
var param = model.get_param(tool_node_id);

// 可以序列化为json文件并输出.
// 注意这里是 `to_string()` 而不是 `ToString()`.
Console.WriteLine(param.to_json().to_string());

// 注意这里的类型的必须和节点的类型一致.
// 否则之后反序列化会出现问题.
var filter_args = new visionflow.param.PolygonsFilterParameters();

// 也可以反序列化输入内容.
filter_args.from_json(param.to_json());

// 在这里,我们修改了类别名为"1"的缺陷的过滤参数.
var area_range = new std.VectorInt {100, 5000};
filter_args.get_class_thresholds("1").set_enable(true).set_area_range(area_range);

// 然后, 你可以将修改后的参数重新保存到模型中.
model.set_param(tool_node_id, filter_args);

// 确认是否保存成功.
param = model.get_param(tool_node_id);
Console.WriteLine(param.to_json().to_string());

Warning

使用 model.set_param(***) 接口设置的参数或其他任何对于导出模型的修改,都仅在当前打开的模型 中生效。在关闭此模型并重新打开时,这些修改将丢失。如果您想要永久保存这些修改,您需要创建一个打 开模型的备份,如下面的代码所示:

model.resave_to("D:/other/path/model_2.vfmodel");
To be completed
model.resave_to("D:/other/path/model_2.vfmodel");

执行模型#

在开始执行模型之前,我们需要创建一个可以基于模型数据执行的运行时。创建运行时有各种策略, 但在这里,为了简单起见,我们将使用 所有工具 策略来运行模型中的所有工具:

vflow::runtime::AllTools strategy;
// 若你的模型中存在前面的模型或参数晚于后面的模型或参数更新的情况.
// 而你又确信这不是一种错误那么使用此选项忽略这些问题.
strategy.options.ignore_update_time_requirement = true;
// 如果的相机采集图像的流程没有注册到VisionFlow中,
// 那么你的流程图中对应的节点是虚拟节点,需启用此选项.
strategy.options.allow_unrealized_oper = true;
auto runtime = model.create_runtime(strategy);
To be completed
var strategy = new visionflow.runtime.AllTools();
// 若你的模型中存在前面的模型或参数晚于后面的模型或参数更新的情况.
// 而你又确信这不是一种错误那么使用此选项忽略这些问题.
strategy.options.ignore_update_time_requirement = true;
// 如果的相机采集图像的流程没有注册到VisionFlow中,
// 那么你的流程图中对应的节点是虚拟节点,需启用此选项.
strategy.options.allow_unrealized_oper = true;
var runtime = model.create_runtime(strategy);

创建了运行时之后,我们可以使用它来检测我们想要检测的图像。运行时需要一个 Sample 来存储输入图像、中间结果以及整个检测过程的最终输出。因此,我们需要创建一个 Sample, 并将我们需要检测的数据添加到其中,如下所示,然后我们可以在这个样本上执行我们的处理流程:

std::string input_id = "Input"; // The id of the Input tool.

auto sample = runtime.create_sample();
auto image = vflow::Image::FromFile("D:/path/to/image.png");
vflow::helper::add_image_to_sample(sample, image, input_id);

// Then we can execute the processing flow on the sample.
runtime.execute(sample);
To be completed
string input_id = "Input"; // The id of the Input tool.

var sample1 = runtime.create_sample();
var image1 = visionflow.img.Image.FromFile("D:/path/to/save/1.bmp");
visionflow_helpers_global.add_image_to_sample(sample1, image1, input_id);

// Then we can execute the processing flow on the sample.
runtime.execute(sample1);

在执行之后,我们可以从样本中检索每个工具的检测结果(及中间结果),并根据我们的要求处理这些结果 (你可以通过查阅每个 工具的详细流程图 获得每个中间或最终结果的ID):

auto result = sample.get({segmentation_id, "pred"});
std::cout << "Result: " << result->to_json().to_string() << std::endl;

const auto &segment_pred = result->as<vflow::props::PolygonRegionList>();
for (const auto &[id, region] : segment_pred) {
    std::cout << "Found a defect: " << id
              << ", name: " << region.name()
              << ", area: " << region.area() << std::endl;
}

// Draw the defects on the image and show the image:
vflow::img::draw(image, segment_pred.to_multi_polygons(), {10, 10, 240}, 2);
image.show();
To be completed
var pred_node_id = new visionflow.ToolNodeId(segmentation_id, "pred");
var result = sample1.get(pred_node_id);
var segment_pred = new visionflow.props.PolygonRegionList();
segment_pred.from_json(result.to_json());

// 输出推理结果的json内容.
// 注意这里是 `to_string()` 而不是 `ToString()`.
Console.WriteLine(segment_pred.to_json().to_string());

// 遍历推理结果.
var ids = segment_pred.keys();
foreach (var id in ids) {
    // 对照C++接口确定对应类型.
    var region = segment_pred.at(id) as visionflow.PolygonRegion;
    Console.WriteLine("id = {0}, region_name = {1}, region_area = {2}", id, region.name(), region.area());
}

// 设置颜色.
var color = new std.VectorInt();
color.Add(10);
color.Add(10);
color.Add(240);

// 在原图上画上推理结果并延时展示一会儿.
visionflow_img_global.draw(image1, segment_pred.to_multi_polygons(), color, 2);
image1.show(2000);

完整的示例#

我们维护了一个从创建工程、添加图像、标注、设置参数到训练和导出模型,并用导出的模型进行推理的 完整的C++示例工程,详情请查看这个仓库:vflow-example

下面还提供了一个部署导出的模型的完整示例代码:

Online Inference Usage#
 1#include <exception>
 2#include <iostream>
 3#include <string>
 4
 5#include "visionflow/visionflow.hpp"
 6
 7namespace vflow = visionflow;
 8
 9void my_logger_output(int log_level, const char *content, size_t len) {
10  if (log_level > 2) {
11    std::cout << std::string(content, len) << std::endl;
12  }
13}
14
15int main(int /*argc*/, char ** /*argv*/) try {
16
17  vflow::InitOptions opts;
18
19  // 设置日志输出文件
20  opts.logger.file_sink = "visionflow.log";
21  // 设置是否将日志输出到标准输出终端
22  opts.logger.stdout_sink = true;
23  // 你可以通过设置日志输出的回调函数自定义处理VisionFlow输出的日志
24  // opts.logger.func_sink = my_logger_output;
25
26  // 设置语言为中文
27  opts.language = "zh_CN";
28
29  vflow::initialize(opts);
30
31  vflow::Model model("D:/path/to/save/my_first_project.vfmodel");
32
33  // 根据你的工程中的工具的名称读取相应工具中参数
34  std::string segmentation_id = "Segmentation";
35  auto filter_param = model.get_param({segmentation_id, "filter.args"});
36  if (!filter_param) {
37    std::cerr << "Filter parameter for Segmentation not exist." << std::endl;
38    exit(-1);
39  }
40
41  // 在这里,我们修改了类别名为"MyClass"的缺陷的过滤参数.
42  filter_param->as<vflow::param::PolygonsFilterParameters>()
43      .get_class_thresholds("MyClass")
44      .set_enable(true)
45      .set_area_range({0, 500000});
46
47  // 然后, 你可以将修改后的参数重新保存到模型中
48  model.set_param({segmentation_id, "filter.args"}, *filter_param);
49
50  model.resave_to("D:/other/path/model_2.vfmodel");
51
52  // 在开始推理之前,我们需要根据模型数据创建出一个可以执行的运行时,创建运行时有很多不同的策略,
53  // 在这里,为了简单起见,我们直接使用默认参数运行模型中所有的工具:
54  vflow::runtime::AllTools strategy;
55  // 若你的模型中存在前面的模型或参数晚于后面的模型或参数更新的情况
56  // 而你又确信这不是一种错误那么使用此选项忽略这些问题
57  strategy.options.ignore_update_time_requirement = true;
58  // 如果的相机采集图像的流程没有注册到VisionFlow中,
59  // 那么你的流程图中对应的节点是虚拟节点,需启用此选项
60  strategy.options.allow_unrealized_oper = true;
61  auto runtime = model.create_runtime(strategy);
62
63  // 在创建出运行时之后,我们就可以使用运行时来检测我们需要检测的图像了。
64  // 运行时需要一个样本来保存整个检测流程的输入图像,中间结果和最终的
65  // 输出,因此,我们需要像下面这样,先创建出一个样本,并将我们的图像添加到样本中:
66  std::string input_id = "Input";
67  while (true) {
68    auto sample = runtime.create_sample();
69    auto image = vflow::Image::FromFile("D:/path/to/image.png");
70    vflow::helper::add_image_to_sample(sample, image, input_id);
71    runtime.execute(sample);
72
73    auto result = sample.get({segmentation_id, "pred"});
74
75    std::cout << "Result: " << result->to_json().to_string() << std::endl;
76
77    const auto &segment_pred = result->as<vflow::props::PolygonRegionList>();
78    for (const auto &[id, region] : segment_pred) {
79      std::cout << "Found a defect: " << id << ", name: " << region.name()
80                << ", area: " << region.area() << std::endl;
81    }
82
83    // Draw the defects on the image and show the image:
84    vflow::img::draw(image, segment_pred.to_multi_polygons(), {10, 10, 240}, 2);
85    image.show();
86  }
87
88  return 0;
89
90} catch (const std::exception &ex) {
91  std::cerr << "Unexpected Escaped Exception: " << ex.what();
92  return -1;
93}
To be completed
Online Inference Usage#
  1using System;
  2using System.Runtime.InteropServices;
  3
  4public class Program
  5{
  6    public static void Main()
  7    {
  8        try
  9        {
 10            visionflow.InitOptions opts = new visionflow.InitOptions();
 11
 12            // 设置日志输出文件.
 13            opts.logger.file_sink = "visionflow.log";
 14            // 设置是否输出到终端.
 15            opts.logger.stdout_sink = true;
 16
 17            // 设置语言为中文.
 18            opts.language = "zh_CN";
 19
 20            visionflow_global.initialize(opts);
 21
 22            // 加载模型.
 23            visionflow.Model model = new visionflow.Model("D:/path/to/save/segmentation.vfmodel");
 24
 25            // 根据你的工程中的工具的名称读取相应工具中参数.
 26            string segmentation_id = "Segmentation";
 27            // 这里读取了推理结果之后的过滤参数.
 28            var tool_node_id = new visionflow.ToolNodeId(segmentation_id, "filter.args");
 29            var param = model.get_param(tool_node_id);
 30
 31            // 可以序列化为json文件并输出.
 32            // 注意这里是 `to_string()` 而不是 `ToString()`.
 33            Console.WriteLine(param.to_json().to_string());
 34
 35            // 注意这里的类型的必须和节点的类型一致.
 36            // 否则之后反序列化会出现问题.
 37            var filter_args = new visionflow.param.PolygonsFilterParameters();
 38
 39            // 也可以反序列化输入内容.
 40            filter_args.from_json(param.to_json());
 41
 42            // 在这里,我们修改了类别名为"1"的缺陷的过滤参数.
 43            var area_range = new std.VectorInt { 100, 10000 };
 44            filter_args.get_class_thresholds("1").set_enable(true).set_area_range(area_range);
 45
 46            // 然后, 你可以将修改后的参数重新保存到模型中.
 47            model.set_param(tool_node_id, filter_args);
 48
 49            // 确认是否保存成功.
 50            param = model.get_param(tool_node_id);
 51            Console.WriteLine(param.to_json().to_string());
 52
 53            // 在开始推理之前,我们需要根据模型数据创建出一个可以执行的运行时,创建运行时有很多不同的策略,
 54            // 在这里,为了简单起见,我们直接使用默认参数运行模型中所有的工具:
 55            var strategy = new visionflow.runtime.AllTools();
 56            // 若你的模型中存在前面的模型或参数晚于后面的模型或参数更新的情况.
 57            // 而你又确信这不是一种错误那么使用此选项忽略这些问题.
 58            strategy.options.ignore_update_time_requirement = true;
 59            // 如果的相机采集图像的流程没有注册到VisionFlow中,
 60            // 那么你的流程图中对应的节点是虚拟节点,需启用此选项.
 61            strategy.options.allow_unrealized_oper = true;
 62            var runtime = model.create_runtime(strategy);
 63
 64            // 在创建出运行时之后,我们就可以使用运行时来检测我们需要检测的图像了.
 65            // 运行时需要一个样本来保存整个检测流程的输入图像,中间结果和最终的
 66            // 输出,因此,我们需要像下面这样,先创建出一个样本,并将我们的图像添加到样本中:
 67            string input_id = "Input";
 68            while (true)
 69            {
 70                // 对于多张图片,自然也要创建多个样本:
 71                var sample1 = runtime.create_sample();
 72                var sample2 = runtime.create_sample();
 73                var image1 = visionflow.img.Image.FromFile("D:/path/to/save/1.bmp");
 74                var image2 = visionflow.img.Image.FromFile("D:/path/to/save/2.bmp");
 75
 76                visionflow_helpers_global.add_image_to_sample(sample1, image1, input_id);
 77                visionflow_helpers_global.add_image_to_sample(sample2, image2, input_id);
 78                runtime.execute(sample1);
 79
 80                // 获取推理后的预测结果.
 81                var pred_node_id = new visionflow.ToolNodeId(segmentation_id, "pred");
 82                var result = sample1.get(pred_node_id);
 83                var segment_pred = new visionflow.props.PolygonRegionList();
 84                segment_pred.from_json(result.to_json());
 85
 86                // 输出推理结果的json内容.
 87                // 注意这里是 `to_string()` 而不是 `ToString()`.
 88                Console.WriteLine(segment_pred.to_json().to_string());
 89
 90                // 遍历推理结果.
 91                var ids = segment_pred.keys();
 92                foreach (var id in ids)
 93                {
 94                    // 对照C++接口确定对应类型.
 95                    var region = segment_pred.at(id) as visionflow.PolygonRegion;
 96                    Console.WriteLine("id = {0}, region_name = {1}, region_area = {2}", id, region.name(), region.area());
 97                }
 98
 99                // 设置颜色.
100                var color = new std.VectorInt();
101                color.Add(10);
102                color.Add(10);
103                color.Add(240);
104
105                // 在原图上画上推理结果并延时展示一会儿.
106                visionflow_img_global.draw(image1, segment_pred.to_multi_polygons(), color, 2);
107                image1.show(2000);
108
109                // 执行另一张图.
110                runtime.execute(sample2);
111                result = sample2.get(pred_node_id);
112                segment_pred.from_json(result.to_json());
113                visionflow_img_global.draw(image2, segment_pred.to_multi_polygons(), color, 2);
114                image2.show(2000);
115            }
116        }
117        catch (Exception ex)
118        {
119            Console.WriteLine(ex);
120        }
121    }
122}