65.9K
CodeProject 正在变化。 阅读更多。
Home

嵌入式计算机视觉的无服务器开发体验

2023年12月27日

Apache

6分钟阅读

viewsIcon

15970

了解如何通过几个 Python 函数和一个模型 URI 来创建实时计算机视觉应用程序。

引言 - 计算机视觉应用程序内部

通过摄像头界面识别视觉事件并对其做出反应的艺术。

如果有人问我用一句话来描述什么是计算机视觉,我会给出上面的回答。但这可能不是你想听的。所以,让我们深入探讨一下计算机视觉应用程序通常是如何构建的,以及每个子系统需要什么。

通常你需要

  • 极快的帧处理速度。 请注意,要在实时中处理 60 FPS 的流,你只有 16 毫秒的时间来处理每一帧。这部分是通过多线程和多进程实现的。在许多情况下,你希望在上一帧处理完成之前就开始处理下一帧。
  • 一个 AI 模型来对每一帧进行推理,执行对象检测、分割、姿态估计等。幸运的是,越来越多表现不错的开源模型可用,因此我们不必从头开始创建自己的模型,通常只需要微调模型的参数以适应你的用例(今天我们不会深入探讨这一点)。
  • 一个推理运行时。推理运行时负责加载模型并在不同的可用设备(GPU 或 CPU)上高效地运行它。
  • 一个 GPU。为了足够快地运行模型推理,我们需要一个 GPU。这是因为 GPU 可以处理比 CPU 多几个数量级的并行操作,而模型最底层就是大量的数学运算。你需要处理帧所在的内存。它们可能位于 GPU 内存或 CPU 内存(RAM)中,由于帧的大小,在两者之间复制帧是一项非常繁重的操作,会使处理速度变慢。
  • 多媒体管道。这些组件允许你从源获取流,将其分割成帧,将它们作为输入提供给模型,有时还会进行修改并将流重新构建以转发出去。
  • 流管理。你可能希望应用程序能够抵抗流中断、重新连接、动态添加和删除流、同时处理多个流等。

所有这些系统都需要创建和维护。问题在于,你最终维护的代码实际上并没有专注于你的应用程序,而是围绕实际用例特定代码的子系统。

Pipeless 框架

为了避免从头开始构建以上所有内容,你可以使用 Pipeless。它是一个用于计算机视觉的 开源 框架,允许你只提供特定于用例的逻辑,并开箱即用地处理所有其他事情。

Pipeless 将应用程序逻辑分成“阶段”(stages),其中阶段就像一个用于单个模型的微型应用程序。它可以包括预处理、使用预处理后的输入运行推理,以及对模型输出进行后处理以采取任何操作。

这种架构最优点之一是你可以根据需要多次链接该结构来处理一个流,因此你可以链接多个模型来处理不同的任务。

为了提供每个阶段的步骤,你只需添加一个代码函数,这个函数非常特定于你的应用程序,Pipeless 会在需要时负责运行它。这就是为什么你可以将 Pipeless 视为嵌入式计算机视觉的无服务器框架。你提供几个函数即可,你无需担心所有必需的周边系统。

Pipeless 的另一个很棒的功能是,你可以动态地添加、删除和更新流,这可以通过 CLI 或 REST API 来完成,以实现工作流的完全自动化。你甚至可以指定重启策略,指示何时应该重新启动流的处理,是否应该在错误后重新启动等。

最后,要部署 Pipeless,你只需在任何设备上安装它并与你的代码函数一起运行,无论是在云 VM 或容器化模式下,还是直接在边缘设备(如 Nvidia Jetson、Raspberry Pi 或其他设备)上运行。

创建一个对象检测应用程序

让我们深入探讨如何使用 Pipeless 创建一个简单的对象检测应用程序。

我们要做的第一件事是安装它。感谢安装脚本,这非常简单。

curl https://raw.githubusercontent.com/pipeless-ai/pipeless/main/install.sh | bash 

现在,我们必须创建一个项目。Pipeless 项目是一个包含阶段的目录。每个阶段都位于一个子目录中,在每个子目录内,我们创建函数的文件。我们为每个阶段文件夹提供的名称将是我们希望 Pipeless 为流运行该阶段时必须指定的阶段名称。

pipeless init my-project --template empty
cd my-project

这里,空模板告诉 CLI 只创建目录,如果你不提供任何模板,CLI 会询问你几个问题以交互方式创建阶段。

如上所述,我们现在需要向我们的项目添加一个阶段。让我们使用以下命令从 GitHub 下载一个示例阶段。

wget -O - https://github.com/pipeless-ai/pipeless/archive/main.tar.gz | 
          tar -xz --strip=2 "pipeless-main/examples/onnx-yolo" 

这将创建一个名为 onnx-yolo 的目录,其中包含我们的应用程序函数,并且它是我们的阶段名称。

让我们检查一下每个阶段文件的内容。

我们有 pre-process.py 文件,它定义了一个函数(hook),该函数接受一个帧和一个上下文。该函数执行一些操作,从接收到的 RGB 帧中准备一些输入数据,以匹配模型期望的格式。这些数据被添加到 frame_data['inference_input'],这是 Pipeless 将传递给模型的。

def hook(frame_data, context):
    frame = frame_data["original"].view()
    yolo_input_shape = (640, 640, 3) # h,w,c
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    frame = resize_rgb_frame(frame, yolo_input_shape)
    frame = cv2.normalize(frame, None, 0.0, 1.0, cv2.NORM_MINMAX)
    frame = np.transpose(frame, axes=(2,0,1)) # Convert to c,h,w
    inference_inputs = frame.astype("float32")
    frame_data['inference_input'] = inference_inputs

... (some other auxiliar functions that we call from the hook function)

我们还有 process.json 文件,它指示 Pipeless 使用的推理运行时,在这种情况下是 ONNX Runtime,在哪里找到它应该加载的模型,以及推理运行时的一些可选参数,例如要使用的 execution_provider,即 CPUCUDATensortRT 等。

{ 
    "runtime": "onnx",
    "model_uri": "https://pipeless-public.s3.eu-west-3.amazonaws.com/yolov8n.onnx",
    "inference_params": { 
        "execution_provider": "tensorrt" 
    }
}

最后,post-process.py 文件也定义了一个函数,该函数接受帧数据和上下文,就像 pre-process.py 一样。这次,它获取 Pipeless 存储在 frame_data["inference_output"] 中的推理输出,并执行操作以将该输出解析为边界框。稍后,它会在帧上绘制边界框,最后将修改后的帧分配给 frame_data['modified']。有了这个,Pipeless 将转发我们提供的流,但带有包含边界框的修改后的帧。

def hook(frame_data, _):
    frame = frame_data['original']
    model_output = frame_data['inference_output']
    yolo_input_shape = (640, 640, 3) # h,w,c
    boxes, scores, class_ids = 
           parse_yolo_output(model_output, frame.shape, yolo_input_shape)
    class_labels = [yolo_classes[id] for id in class_ids]
    for i in range(len(boxes)):
        draw_bbox(frame, boxes[i], class_labels[i], scores[i])

    frame_data['modified'] = frame

... (some other auxiliar functions that we call from the hook function)

最后一步是启动 Pipeless 并提供一个流。要启动 Pipeless,只需从项目目录运行以下命令:

pipeless start --stages-dir .

启动后,让我们从网络摄像头(v4l2)提供一个流,并直接在屏幕上显示输出,请注意,我们必须提供流应该按顺序执行的阶段列表,在本例中只有一个 onnx-yolo 阶段。

pipeless add stream --input-uri "v4l2" --output-uri "screen" --frame-path "onnx-yolo"

就是这样!

结论

我们已经描述了创建计算机视觉应用程序是一项复杂的任务,原因在于我们必须围绕它实现的许多因素和子系统。但是,使用 Pipeless 这样的框架,只需几分钟即可启动并运行,你可以专注于编写特定用例的代码。此外,Pipeless 的阶段高度可重用且易于维护,因此维护将变得容易,你可以非常快速地迭代。

如果你想参与 Pipeless 并为其开发做出贡献,可以通过其 GitHub 存储库 https://github.com/pipeless-ai/pipeless 来完成。

别忘了通过点星支持其发展!

历史

  • 2023年12月27日:初始版本
© . All rights reserved.