· John Smith · Tutorials  · 43 min read

使用 DORA-rs 构建摄像头人脸检测应用攻略

从零起步构建一个基于dora的相机人脸检测小应用

从零起步构建一个基于dora的相机人脸检测小应用

I. DORA-rs 简介及我们的目标

A. DORA-rs 是什么?

DORA-rs(Dataflow-Oriented Robotic Architecture,面向数据流的机器人架构)是一个为简化 AI 驱动的机器人应用开发而设计的现代框架。 在机器人基础框架多年来发展有限的领域,dora-rs 引入了一种现代方法,强调性能和易用性,尤其是在集成人工智能方面。  

dora-rs 设计的一个基石是其对高性能数据处理的承诺。它通过共享内存和 Apache Arrow 实现的“零拷贝”消息传输等特性来实现这一点。 这种架构使得在单台机器上运行的机器人应用的不同部分之间能够进行极快的通信,最大限度地减少延迟——这在机器人技术中是一个关键因素,因为实时响应性至关重要。这种性能改进不仅仅是渐进式的;它们可以显著影响复杂 AI 算法在机器人系统中的可行性和有效性。该框架还支持可组合和分布式数据流,允许应用程序由模块化组件构建,这些组件可以潜在地跨多台机器或机器人运行。 这种可扩展性,再加上对 Python 的强大支持,使得 dora-rs 成为 AI 从业者、研究人员和爱好者等的一个有吸引力的选择,他们可以在高性能机器人框架内利用 Python 广泛的 AI/ML 生态系统。  

B. 本教程的目标:一个简单的摄像头人脸检测应用

本教程旨在提供一个实用的、分步的指南,用于安装 dora-rs 并构建一个基础的摄像头应用程序。该应用程序将从标准网络摄像头捕获视频,执行实时人脸检测,并在任何检测到的人脸周围渲染边界框来显示视频流。该项目将作为 dora-rs 核心概念和开发工作流程的实践介绍。

C. 您将学到什么

通过本指南,用户将获得以下实践技能:

  • 安装 dora-rs 框架,包括其命令行界面 (CLI) 和 Python API。
  • 理解 dora-rs 数据流的基础知识及其定义方式。
  • 使用 dora-rs Python API 创建自定义处理单元,称为算子(operators)。
  • dora-rs 应用程序的不同组件之间处理和传输图像数据。
  • 配置和运行一个完整的 dora-rs 应用程序。

D. 关于“人脸跟踪”与“人脸检测”的说明

为清晰起见,区分“人脸检测”和“人脸跟踪”非常重要。本教程将实现人脸检测,即在视频流的每个单独帧中识别人脸的存在和位置。将在该特定帧中检测到的每个人脸周围绘制一个边界框。

真正的人脸跟踪是一项更复杂的任务,它不仅涉及检测人脸,还涉及在多个帧中为每个检测到的人脸保持唯一身份,即使它们移动、外观改变或暂时被遮挡。实现强大的人脸跟踪通常需要更高级的算法(例如,卡尔曼滤波器、Siamese 网络或专门的跟踪器),并且超出了本入门指南的范围。本教程侧重于每帧检测的基础步骤,以通过一个易于管理的示例来说明 dora-rs 的功能。

II. 设置您的 DORA-rs 开发环境

A. 先决条件

在开始安装 dora-rs 和开发摄像头应用程序之前,请确保满足以下先决条件:

  • Python: 安装了兼容版本的 Python。建议使用 3.8 或更新版本;dora-rs 文档中的示例有时使用 Python 3.11。  

  • pip: Python 包安装程序,通常随 Python 安装一起提供。

  • 命令行/终端: 访问命令行界面(例如,Linux/macOS 上的 Bash,Windows 上的 PowerShell 或 CMD)。

  • 网络摄像头: 强烈建议将功能正常的网络摄像头连接到系统以运行示例应用程序。

B. 安装 DORA-rs 命令行界面 (CLI)

dora-rs CLI 是管理数据流、启动和停止应用程序以及与 dora-rs 节点交互的重要工具。该框架提供了多种安装方法,以适应不同的操作系统和用户偏好,这反映了对开发人员拥有不同工具链的理解。这种灵活性有助于降低入门门槛。

  • 推荐 (Linux/macOS): 安装最新 CLI 版本的最快方法是使用 curl 下载并执行安装程序脚本 :

    Bash

    curl --proto '=https' --tlsv1.2 -LsSf https://github.com/dora-rs/dora/releases/latest/download/dora-cli-installer.sh | sh
  • Windows: 对于 Windows 用户,PowerShell 可以使用类似的安装程序脚本 :

    PowerShell

    powershell -ExecutionPolicy ByPass -c "irm https://github.com/dora-rs/dorareleases/latest/download/dora-cli-installer.ps1 | iex"
  • 替代方案 (pip): 如果首选 pip,可以将 CLI 安装为 Python 包 :

    Bash

    pip install dora-rs-cli
  • 替代方案 (针对 Rust 用户的 Cargo): 拥有现有 Rust 开发环境的用户可以使用 Cargo 安装 CLI :

    Bash

    cargo install dora-cli

    如果出现问题,尝试使用 --locked 标志的命令可能会解决问题。  

  • 替代方案 (Nix): 对于 Nix 包管理器的用户,dora-rs 也提供了安装选项,包括将其添加到 dev-shell flake 或系统配置中。  

安装后,通过运行以下命令验证其是否成功:

Bash

dora --help

此命令应显示 dora CLI 的帮助信息。  

C. 安装 DORA-rs Python API

dora-rs Python API 支持在 Python 中开发自定义节点和算子,从而可以与 Python 丰富的 AI、计算机视觉等库生态系统无缝集成。Python API 的安装过程通常涉及 maturin,这是一个用于构建和发布带有 Python 绑定的 Rust crates 的工具。 对 maturin 的依赖表明 dora-rs Python API 不是纯 Python 实现,而是一个围绕高性能 Rust核心的复杂封装器。这种架构选择允许开发人员受益于 Python 的易用性,同时利用 Rust 的速度和效率进行底层操作。  

  1. 安装 maturin (如果尚未安装):

Bash

pip install maturin
  1. 构建并安装 Python API: 此步骤通常需要克隆 dora-rs 存储库,并在克隆的目录中运行 maturin,特别是针对 Python API 的 Cargo.toml 文件。

Bash

git clone https://github.com/dora-rs/dora.git
cd dora
maturin develop -m apis/python/node/Cargo.toml

maturin develop 命令会构建 Rust 组件,并在当前 Python 环境中以可编辑模式安装 Python 绑定。  

D. 我们项目所需的基本 Python 库

对于摄像头人脸检测应用程序,需要几个 Python 库:

  • opencv-python: 用于访问网络摄像头、执行图像操作以及其内置的 Haar 级联人脸检测器。
  • numpy: Python 中用于数值计算的基础包,OpenCV 使用它来表示图像。
  • pyarrow: 便于高效地序列化和反序列化数据,特别是用于在 dora-rs 节点之间传输图像数据。  

使用 pip 安装这些库:

Bash

pip install opencv-python numpy pyarrow

强烈建议使用 Python 虚拟环境来管理项目依赖项并避免与系统范围的包发生冲突。可以使用诸如 venv (标准库) 或 uv (如 dora-rs 文档中所示 ) 之类的工具。例如,使用 venv:  

Bash

python -m venv.venv
source.venv/bin/activate # 在 Linux/macOS 上
#.venv\Scripts\activate # 在 Windows 上

dora-rs 的“入门”指南中包含虚拟环境创建 ,强调了其对于创建隔离的、可复现的开发环境的重要性,这在处理可能具有特定版本兼容性或混合 Python 和本机依赖项的框架时尤其有价值。  

表 1: DORA-rs 安装命令

组件推荐命令注意事项
DORA-rs CLI (Linux/macOS)`curl —proto ‘=https’ —tlsv1.2 -LsSf https://github.com/dora-rs/dora/releases/latest/download/dora-cli-installer.shsh`
DORA-rs CLI (Windows)`powershell -ExecutionPolicy ByPass -c “irm https://github.com/dora-rs/dorareleases/latest/download/dora-cli-installer.ps1iex”`
DORA-rs CLI (pip)pip install dora-rs-cli跨平台,需要 Python 和 pip。
Maturinpip install maturin从源代码构建 Python API 的先决条件。
DORA-rs Python APIgit clone https://github.com/dora-rs/dora.git && cd dora && maturin develop -m apis/python/node/Cargo.toml从克隆的 dora-rs 存储库构建并安装 Python 绑定。需要 Maturin 和 Rust 工具链。
项目依赖pip install opencv-python numpy pyarrow人脸检测应用程序的基本库。

Export to Sheets

III. 我们摄像头应用的核心 DORA-rs 概念

A. 理解数据流:YAML 配置

dora-rs 应用程序的核心是数据流的概念。应用程序被建模为有向图,通常称为管道,其中数据在不同的处理单元之间流动。 此数据流在 YAML 文件中以声明方式指定,通常命名为 dataflow.yml。  

这种使用 YAML 描述系统架构和数据路径的声明式方法 是一个重要特性。它允许开发人员定义系统应该做什么以及其组件如何互连,而不是命令式地编写设置和通信逻辑。这种抽象简化了复杂系统的创建,并且对于构建组件可能在不同机器上运行的分布式应用程序尤其有益。  

B. 节点和算子:构建模块

数据流图由节点组成,节点是基本的处理单元。 dataflow.yml 文件中的每个节点都被分配一个唯一的 id。  

  • 节点 (Nodes): 节点可以表示自定义可执行文件(例如,Python 脚本、已编译的 C++ 程序),或者可以配置为运行一个或多个称为算子 (operators) 的专门处理单元。 对于本教程,我们的每个 Python 脚本(camera_node.pyface_detector_node.pydisplay_node.py)都将在 dataflow.yml 中定义为一个自定义节点,其中 source 键指向相应的脚本文件。  

  • 算子 (Operators) (在 Python 节点内): 在为 dora-rs 编写 Python 节点时,算子 API (Operator API) 是实现处理逻辑的推荐方法。 算子是一个 Python 类,dora-rs 在其整个生命周期中对其进行管理。算子类的关键方法包括:  

    • __init__(self): 在算子初始化时调用一次。用于设置资源、加载模型等。  

    • on_event(self, dora_event, send_output): 这是发生数据处理的主要方法。每当有可供算子使用的输入事件(例如新数据到达、计时器滴答或停止信号)时,dora-rs 就会调用它。 dora_event 字典包含有关事件的详细信息,send_outputdora-rs 提供的可调用函数,用于将数据发送到下游节点。  

    • __del__(self): 在算子关闭时调用。用于释放资源。  

on_event 方法的一个关键方面是其返回值:一个 DoraStatus。这可以是 DoraStatus.CONTINUE,表示算子应继续运行并处理事件;也可以是 DoraStatus.STOP,表示算子已完成其工作或遇到需要其停止的条件。 这种显式的状态返回使算子能够对其生命周期进行细粒度控制,这对于稳健的资源管理以及在可能需要根据任务完成、错误或预定义条件(例如 webcam.py 示例中的 20 秒运行时 )终止的复杂机器人应用程序中实现优雅的关闭序列至关重要。  

dora-rs 还提供了一个 自定义节点 (Custom Node) API (使用 node = Node()node.next()) ,它提供了对输入/输出处理更直接的控制。这对于将 dora-rs 集成到具有较少传统结构的现有应用程序中可能很有用。然而,由于框架提供的优化和生命周期管理,通常首选 算子 (Operator) API 进行新开发。这两种 API 风格的可用性满足了不同的集成需求和控制级别。  

C. 定义用于数据交换的输入和输出

节点通过将数据从一个节点的输出发送到另一个节点的输入来进行通信。这些连接在 dataflow.yml 文件中定义。

  • 输出 (Outputs):

    • dataflow.yml 中,节点的输出在其定义下使用 outputs: 键声明,后跟输出名称列表(例如,outputs: [image_data])。  

    • 在 Python 算子的 on_event 方法中,使用 send_output 可调用函数发送数据:send_output("output_id", data_bytes, metadata)output_id 字符串必须与该节点在 YAML 配置的 outputs 列表中声明的名称之一匹配。 data_bytes 是有效负载,通常是序列化的。  

  • 输入 (Inputs):

    • dataflow.yml 中,节点的输入在其定义下使用 inputs: 键声明。每个输入将一个本地名称(在节点内使用)映射到数据流中另一个节点或算子的输出。 语法是 local_input_name: source_node_id/source_output_id。这种 <operator>/<output> (或 <node_id>/<output_id>) 命名空间是一种经过深思熟虑且重要的设计选择。它确保输出名称在其源节点内是唯一的,并防止在连接多个节点时发生命名冲突,从而促进算子的模块化和可重用性。  

    • 在 Python 算子中,传入数据作为 on_event 方法中 dora_event 字典的一部分到达。如果 dora_event["type"] == "INPUT",则 dora_event["id"] 将包含 local_input_name (如 YAML 中所定义),而 dora_event["value"] (或有时是 dora_event["data"]) 将保存实际的数据有效负载。  

  • 使用 PyArrow 进行数据序列化: dora-rs 利用 Apache Arrow 来实现组件之间高效的、潜在的零拷贝数据传输。 对于 Python 算子,使用 pyarrow 库来实现此目的。在发送数据时,尤其是像图像这样的大数据(在 OpenCV 中是 NumPy 数组),通常的做法是在发送之前将其转换为 PyArrow 数组或缓冲区。例如,一个 NumPy 图像数组 frame 可能会准备好作为 pa.array(frame.ravel()) 发送。 frame.ravel() 将多维图像数组展平为一维数组,然后 pa.array() 将其转换为 PyArrow 数组。 在接收端,数据(通常作为字节或 dora_event["value"] 内的通用 PyArrow 对象到达)需要反序列化回所需的 Python/NumPy 格式。例如,从 dora_event["data"] 接收的数据可能会使用 np.array(pa.Buffer.from_buffer(dora_event["data"]).to_numpy()) 转换回 NumPy 数组,然后重塑为原始图像尺寸。 这种使用 PyArrow 仔细处理序列化和反序列化是实现 dora-rs 旨在提供的性能优势的关键。  

表 2: 关键 dataflow.yml 元素

YAML 键描述示例用法 (片段)
nodes顶级键;数据流中所有节点定义的列表。nodes:<br/>- id: node_a <br/>#... <br/>- id: node_b<br/>#...
id (用于节点)节点的唯一标识符。- id: camera_sensor
custom定义一个运行外部可执行文件(例如 Python 脚本)的节点。custom:<br/>source: path/to/my_script.py
source (在 custom 下)指定自定义节点的可执行文件或脚本的路径。source: operators/camera_node.py
operator定义一个运行 dora-rs 算子(通常由包含算子类的 Python 脚本指定)的节点。operator:<br/>python: operators/processing_op.py
python (在 operator 下)指定包含 dora-rs 算子类的 Python 脚本。python: image_processor.py
inputs定义节点输入的字典。将本地输入名称映射到源输出。inputs:<br/>raw_frame: camera_sensor/image_stream
outputs此节点可以产生的输出名称列表。outputs:<br/>- processed_data<br/>- status_info
tick (常见输入值)来自 dora-rs 的特殊输入源,提供周期性计时器事件。inputs:<br/>trigger: dora/timer/millis/100 (每 100 毫秒触发一次)

Export to Sheets

IV. 构建摄像头人脸检测应用:分步操作

A. 设计数据流:从摄像头到显示

人脸检测应用程序将由三个相互连接的节点组成,形成一个简单的管道:

  1. 摄像头节点 (camera_node.py): 此节点的职责是访问网络摄像头,连续捕获视频帧,并将这些帧作为输出发送。
  2. 人脸检测器节点 (face_detector_node.py): 此节点从摄像头节点接收原始图像帧。它处理每个帧以使用 Haar 级联分类器检测人脸。一旦检测到人脸,它会直接在图像帧上围绕它们绘制边界框。然后将此修改后的帧(带有边界框的图像)作为输出发送。
  3. 显示节点 (display_node.py): 此节点从人脸检测器节点接收处理后的图像帧(已绘制边界框),并在屏幕上的窗口中显示它们。

数据流如下: 网络摄像头 -> [摄像头节点] --image_data--> [人脸检测器节点] --processed_image_data--> [显示节点] -> 屏幕

这种设计,其中 face_detector_node 输出已绘制框的图像,显著简化了 display_node,使其与基本的图像绘制示例非常相似。 虽然另一种设计可能涉及从 face_detector_node 发送原始图像和边界框坐标作为单独的输出以获得更大的模块化,但这可能会给 display_node 带来同步复杂性(确保在正确的图像上绘制正确的框)。对于入门示例,所选方法优先考虑简单性。  

表 3: 应用程序节点概述

节点脚本名称主要职责输入 (从 YAML 角度)输出 (从 YAML 角度)
camera_node.py从网络摄像头捕获图像并发送。tick: dora/timer/millis/100 (示例速率)image_data
face_detector_node.py接收图像,检测人脸,绘制边界框,并发送修改后的图像。raw_image: camera_node/image_dataprocessed_image_data
display_node.py接收带有边界框的图像并显示它们。image_to_show: face_detector_node/processed_image_data(无数据流输出)

Export to Sheets

B. 实现摄像头节点 (camera_node.py)

此节点将负责从网络摄像头捕获帧。它将构建为一个 dora-rs 算子。使用 dora/timer/millis/X 作为输入 提供了一种干净的外部机制来控制帧捕获速率,将计时逻辑与算子的核心功能解耦。  

Python

camera_node.py
import cv2
import numpy as np
import pyarrow as pa
from dora import DoraStatus
import os
import time # 用于摄像头可能需要的初始延迟
CAMERA_WIDTH = 640
CAMERA_HEIGHT = 480
# 允许通过环境变量设置摄像头索引,默认为 0
CAMERA_INDEX = int(os.getenv("CAMERA_INDEX", 0))
class Operator:
""" 从网络摄像头捕获图像并将其作为 PyArrow 数组发送。 """
def __init__(self):
self.video_capture = cv2.VideoCapture(CAMERA_INDEX)
if not self.video_capture.isOpened():
print(f"[摄像头节点] 错误:无法从摄像头索引 {CAMERA_INDEX} 打开视频流")
# 考虑如何处理 - 也许发送错误图像或停止
# 目前,我们让它在 on_event 中尝试并失败
else:
print(f"[摄像头节点] 成功打开摄像头索引 {CAMERA_INDEX}") self.video_capture.set(cv2.CAP_PROP_FRAME_WIDTH, CAMERA_WIDTH) self.video_capture.set(cv2.CAP_PROP_FRAME_HEIGHT, CAMERA_HEIGHT)
# 有些摄像头需要一点时间来初始化
time.sleep(0.5)
def on_event(
self, dora_event: dict, send_output: callable, ) -> DoraStatus:
if dora_event["type"] == "INPUT":
if not self.video_capture.isOpened():
print("[摄像头节点] 摄像头不可用,发送空白帧。")
frame = np.zeros((CAMERA_HEIGHT, CAMERA_WIDTH, 3), dtype=np.uint8)
cv2.putText(frame, f"无网络摄像头:索引 {CAMERA_INDEX}", (30, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.75, (255, 255, 255), 2)
else:
ret, frame = self.video_capture.read()
if not ret:
print("[摄像头节点] 错误:未能捕获帧。")
# 发送空白帧或错误图像
frame = np.zeros((CAMERA_HEIGHT, CAMERA_WIDTH, 3), dtype=np.uint8)
cv2.putText(frame, "帧捕获失败", (30, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.75, (0, 0, 255), 2)
else:
frame = cv2.resize(frame, (CAMERA_WIDTH, CAMERA_HEIGHT))
# 将帧(或错误帧)作为 PyArrow 数组发送
send_output("image_data", pa.array(frame.ravel()), dora_event["metadata"])
return DoraStatus.CONTINUE
elif dora_event["type"] == "STOP":
print("[摄像头节点] 收到停止信号。")
else:
print(f"[摄像头节点] 收到意外事件:{dora_event['type']}")
return DoraStatus.CONTINUE # 持续运行直到显式停止或出错
def __del__(self):
if hasattr(self, 'video_capture') and self.video_capture.isOpened():
self.video_capture.release()
print("[摄像头节点] 网络摄像头已释放。")

说明:

  • __init__: 使用 CAMERA_INDEX 初始化 cv2.VideoCapture。它尝试设置帧宽度和高度。添加了一个小延迟,因为某些网络摄像头需要一点时间来初始化。

  • on_event: 此方法由输入触发,该输入将在 dataflow.yml 中配置为计时器滴答(例如,dora/timer/millis/100)。

    • 它使用 self.video_capture.read() 读取一帧。

    • 如果读取失败(ret 为 false)或摄像头未打开,它会创建一个带有错误消息的黑色帧。这确保了即使出现摄像头问题,数据流也能继续,这是示例中显示的稳健做法。  

    • 帧(一个 NumPy 数组)使用 frame.ravel() 展平,然后使用 pa.array() 转换为 PyArrow 数组。

    • send_output("image_data",...) 将此 PyArrow 数组发送到订阅了 “image_data” 输出的任何节点。

  • __del__: 在算子销毁时释放网络摄像头资源。

dora-rs 算子结构中直接集成像 OpenCV 这样的标准 Python 库进行摄像头 I/O,展示了该框架的灵活性,允许开发人员使用熟悉的工具来完成特定任务。

C. 实现人脸检测器节点 (face_detector_node.py)

此节点接收图像数据,检测人脸,绘制边界框,并发送修改后的图像。为简单起见,使用 OpenCV 的 Haar 级联分类器。虽然更高级的模型,如 YOLO 或轻量级替代方案(例如 BlazeFace),可以提供更好的准确性,但 Haar 级联易于使用,并且除了 OpenCV 提供的之外不需要外部模型文件。  

Python

face_detector_node.py
import cv2
import numpy as np
import pyarrow as pa
from dora import DoraStatus
CAMERA_WIDTH = 640
CAMERA_HEIGHT = 480
class Operator:
""" 接收图像,检测人脸,绘制边界框, 并发送修改后的图像。 """
def __init__(self):
# 加载预训练的 Haar 级联用于人脸检测
# OpenCV 通常会自动找到此文件路径
cascade_path = cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
self.face_cascade = cv2.CascadeClassifier(cascade_path)
if self.face_cascade.empty():
print(" 错误:无法加载 Haar 级联分类器。")
# 如果此操作失败,应用程序可能无法正常运行。
# 考虑如何处理(例如,不修改地传递图像)。
else:
print(" Haar 级联分类器加载成功。")
def on_event(
self, dora_event: dict, send_output: callable, ) -> DoraStatus:
if dora_event["type"] == "INPUT":
if dora_event["id"] == "raw_image":
# 假设数据是从 camera_node 作为 PyArrow 数组接收的
# 将 PyArrow 数组转换回 NumPy 数组
try:
frame_data = dora_event["value"] # PyArrow 数组
frame = np.array(frame_data.to_numpy()).reshape((CAMERA_HEIGHT, CAMERA_WIDTH, 3)).astype(np.uint8)
except Exception as e:
print(f" 反序列化帧时出错:{e}")
# 发送空白帧或重新引发以停止
frame = np.zeros((CAMERA_HEIGHT, CAMERA_WIDTH, 3), dtype=np.uint8)
cv2.putText(frame, "反序列化错误", (30,30), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (0,0,255), 2)
send_output("processed_image_data", pa.array(frame.ravel()), dora_event["metadata"])
return DoraStatus.CONTINUE
if self.face_cascade.empty():
# 如果级联未加载,则不修改地传递图像
print(" 人脸级联未加载。传递图像。")
send_output("processed_image_data", pa.array(frame.ravel()), dora_event["metadata"])
return DoraStatus.CONTINUE
# 将帧转换为灰度图以进行人脸检测
gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 检测人脸
faces = self.face_cascade.detectMultiScale(
gray_frame,
scaleFactor=1.1,
minNeighbors=5,
minSize=(30, 30)
)
# 在检测到的人脸周围绘制边界框
for (x, y, w, h) in faces:
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
# 发送修改后的帧(带有边界框)
send_output("processed_image_data", pa.array(frame.ravel()), dora_event["metadata"])
return DoraStatus.CONTINUE
elif dora_event["type"] == "STOP":
print(" 收到停止信号。")
else:
print(f" 收到意外事件:{dora_event['type']}")
return DoraStatus.CONTINUE
def __del__(self):
print(" 算子正在关闭。")

说明:

  • __init__: 使用 cv2.CascadeClassifier 加载 haarcascade_frontalface_default.xml 文件。OpenCV 通常知道在其安装目录中找到此文件。

  • on_event:

    • 它期望一个 id 为 “raw_image” 的输入。

    • 传入的 dora_event["value"] (一个 PyArrow 数组) 使用 to_numpy() 转换回 NumPy 数组,然后重塑为原始图像尺寸 (CAMERA_HEIGHT, CAMERA_WIDTH, 3)。  

    • 帧被转换为灰度图,因为 Haar 级联在灰度图像上工作。

    • self.face_cascade.detectMultiScale(...) 执行人脸检测,返回一个矩形列表 (x, y, w, h)

    • 在原始彩色帧上为每个检测到的人脸绘制一个绿色矩形。

    • 修改后的帧(现在带有边界框)被序列化回 PyArrow 数组,并通过 send_output("processed_image_data",...) 发送。

D. 实现显示节点 (display_node.py)

此节点负责显示最终的视频流。它将与 dora-rs 教程中经常出现的 plot.py 示例非常相似。  

Python

display_node.py
import cv2
import numpy as np
import pyarrow as pa
from dora import DoraStatus
CAMERA_WIDTH = 640
CAMERA_HEIGHT = 480
WINDOW_NAME = "DORA 人脸检测"
class Operator:
""" 接收带有绘制边界框的图像并显示它。 """
def __init__(self):
cv2.namedWindow(WINDOW_NAME, cv2.WINDOW_AUTOSIZE)
print(" 窗口已创建。")
def on_event(
self, dora_event: dict, send_output: callable, # 此节点未使用
) -> DoraStatus:
if dora_event["type"] == "INPUT":
if dora_event["id"] == "image_to_show":
try:
# 将 PyArrow 数组转换回 NumPy 数组
frame_data = dora_event["value"]
frame = np.array(frame_data.to_numpy()).reshape((CAMERA_HEIGHT, CAMERA_WIDTH, 3)).astype(np.uint8)
except Exception as e:
print(f" 反序列化用于显示的帧时出错:{e}")
return DoraStatus.CONTINUE # 或如果严重则为 STOP
cv2.imshow(WINDOW_NAME, frame)
if cv2.waitKey(1) & 0xFF == ord('q'): # 允许按 'q' 退出
print(" 按下 'q',请求停止。")
return DoraStatus.STOP
return DoraStatus.CONTINUE
elif dora_event["type"] == "STOP":
print(" 收到停止信号。")
# DoraStatus.STOP 最终将由协调器返回
# 但如果我们发起了停止(例如按下 'q'),我们已经返回了它。
else:
print(f" 收到意外事件:{dora_event['type']}")
return DoraStatus.CONTINUE
def __del__(self):
cv2.destroyAllWindows()
print(" 窗口已销毁。")

说明:

  • __init__: 使用 cv2.namedWindow 创建一个名为 “DORA 人脸检测” 的窗口。
  • on_event:
    • 它期望一个 id 为 “image_to_show” 的输入。
    • PyArrow 数组数据被转换回 NumPy 帧,类似于人脸检测器节点。
    • cv2.imshow(WINDOW_NAME, frame) 显示帧。
    • cv2.waitKey(1) 对于 OpenCV GUI 事件处理至关重要,并且还允许捕获按键。如果按下 ‘q’,它将返回 DoraStatus.STOP 以指示数据流终止。
  • __del__: 当算子停止时销毁 OpenCV 窗口。

dora-rs Python 算子中的事件驱动 on_event 模型 简化了异步输入的处理。在此示例中,由于 face_detector_node 发送完全处理的图像,display_node 不需要关联单独的图像和边界框流,否则这将需要更复杂的状态管理。  

E. 编写 dataflow.yml 文件

此 YAML 文件定义了我们应用程序的架构:节点、它们的源脚本以及它们如何连接。

YAML

dataflow.yml
nodes:
- id: camera_node
custom:
source: camera_node.py # 相对于 dataflow.yml 的路径
inputs:
tick: dora/timer/millis/100 # 大约每秒捕获 10 帧
outputs:
- image_data
- id: face_detector_node
custom:
source: face_detector_node.py # 相对于 dataflow.yml 的路径
inputs:
raw_image: camera_node/image_data
outputs:
- processed_image_data
- id: display_node
custom:
source: display_node.py # 相对于 dataflow.yml 的路径
inputs:
image_to_show: face_detector_node/processed_image_data
# display_node 没有输出到其他 dora 节点

说明:

  • camera_node:
    • custom: source: camera_node.py: 指定此节点运行 camera_node.py
    • inputs: tick: dora/timer/millis/100: 此节点每 100 毫秒从 dora-rs 的内部计时器接收一个 “tick” 输入。此滴答声会触发 camera_node.py 中的 on_event
    • outputs: [image_data]: 声明一个名为 “image_data” 的输出流。
  • face_detector_node:
    • custom: source: face_detector_node.py: 运行 face_detector_node.py
    • inputs: raw_image: camera_node/image_data: 其输入 “raw_image” 连接到 “camera_node” 的 “image_data” 输出。
    • outputs: [processed_image_data]: 声明一个名为 “processed_image_data” 的输出流。
  • display_node:
    • custom: source: display_node.py: 运行 display_node.py
    • inputs: image_to_show: face_detector_node/processed_image_data: 其输入 “image_to_show” 连接到 “face_detector_node” 的 “processed_image_data” 输出。

此 YAML 配置根据 IV.A 节中的设计清晰地定义了管道结构,并参考了 dora-rs 文档中的示例。  

V. 运行您的 DORA-rs 人脸检测应用

A. 项目结构

按如下方式在目录中组织项目文件:

face_detection_app/
├── camera_node.py
├── dataflow.yml
├── display_node.py
└── face_detector_node.py

确保 camera_node.pyface_detector_node.pydisplay_node.pydataflow.yml 都在同一个目录 (face_detection_app/) 中。

B. 构建和启动数据流

  1. 导航到项目目录: 打开终端并切换到 face_detection_app 目录。

Bash

cd path/to/face_detection_app
  1. 激活虚拟环境 (如果使用):

Bash

source.venv/bin/activate # 或您特定的 venv 激活命令
  1. 构建数据流 (对于这个简单示例是可选的,但对于复杂示例是好习惯): 如果算子在 pyproject.toml 文件中有特定的依赖项列出(在此简单设置中未使用),dora build 命令将处理它们。 对于此示例,由于依赖项已在虚拟环境中全局安装,因此此步骤可能不是严格必需的,但了解一下是好的。  

Bash

dora build dataflow.yml # 如果使用 uv 进行环境管理,则添加 --uv

build 步骤与 run/start 分开表明 dora-rs 能够处理预处理或每个节点的依赖项管理,这增强了更复杂项目的可重现性和组织性。

  1. 运行/启动数据流: 使用 dora rundora start 命令执行数据流。  

Bash

dora run dataflow.yml --attach # 或 `dora start dataflow.yml --attach`

可以附加几个有用的标志:

  • --attach: 此标志使 dora 命令在前台运行,直接在终端中显示来自节点的日志。当数据流停止时(例如,通过在显示窗口中按 ‘q’ 或如果节点返回 DoraStatus.STOP),该命令将退出。  

  • --hot-reload: 一个非常强大的开发功能。如果使用此标志,dora-rs 将监视 Python 算子脚本的更改。如果脚本被修改并保存,dora-rs 将自动重新加载该算子,应用更改而无需重新启动整个数据流。 这极大地加快了迭代开发和调试周期。  

  • --name <dataflow_name>: 为此数据流实例分配一个人类可读的名称(例如,--name face_app)。然后,如果数据流以分离模式运行(不带 --attach),则此名称可用于其他 dora 命令,如 dora stop <dataflow_name>dora logs <dataflow_name>。  

带有更多标志的示例:

Bash

dora run dataflow.yml --attach --hot-reload --name my_face_detector

C. 验证输出

如果一切设置正确(网络摄像头已连接且可访问,库已安装,代码正确),则应出现一个名为“DORA 人脸检测”的窗口。此窗口将显示来自网络摄像头的实时馈送。当人脸对摄像头可见时,应在它们周围绘制绿色矩形(边界框)。应用程序将继续运行、处理帧并显示它们,直到停止。

D. 停止数据流

有几种方法可以停止数据流:

  • 如果使用 --attach 运行:

    • 在“DORA 人脸检测”窗口中按 ‘q’(如 display_node.py 中所实现)。
    • 在运行 dora rundora start 的终端中按 Ctrl+C
  • 如果在分离模式下运行 (不带 --attach):

    • 使用 dora stop 命令以及使用 --name 分配的名称或数据流的 UUID(dora start 会打印出来):

      Bash

      dora stop my_face_detector # 如果已命名
      # dora stop <UUID>

E. 基本故障排除技巧

  • “未找到网络摄像头”或来自 camera_node.py 的错误:
    • 确保网络摄像头已正确连接且功能正常。
    • 验证 camera_node.py 中的 CAMERA_INDEX(对于默认的内置网络摄像头通常为 0,但对于外部网络摄像头可以为 1 或更高)。
    • 在某些系统(尤其是 Linux)上,网络摄像头权限可能是个问题。
  • dora: command not found:
    • dora-rs CLI 未正确安装,或者其安装目录不在系统的 PATH 环境变量中。请重新访问第二节 B 部分。
  • 节点日志中的 Python 错误 (使用 --attach 可见或通过 dora logs 查看):
    • 仔细阅读错误消息和回溯信息。它通常会指向导致问题的 Python 脚本和行号。
    • 常见问题包括不正确的库导入、文件路径问题(例如,如果未自动找到 Haar 级联的文件路径),或者如果节点之间的数据格式意外更改导致的数据反序列化错误。
  • 人脸检测不起作用 (没有边界框):
    • 确保 face_detector_node.py 正确加载了 haarcascade_frontalface_default.xml 文件。OpenCV 通常会处理这个问题,但如果 self.face_cascade.empty() 为 true,则加载时出现问题。
    • 光照条件会显著影响 Haar 级联的性能。确保人脸光线充足。
    • 对于默认的检测器参数,人脸可能太小、太远或角度过大。
  • ModuleNotFoundError: dora-rs 使用的 Python 环境中未安装所需的 Python 库(例如 cv2numpypyarrow)。确保第二节 D 部分中的所有依赖项都安装在活动的虚拟环境中。

VI. 结论和后续步骤

A. 成就回顾

本教程指导用户完成了设置 dora-rs 框架并构建一个功能齐全(尽管简单)的 AI 驱动的摄像头应用程序的过程。主要成就包括:

  • 成功安装 dora-rs CLI 和 Python API。
  • 理解核心 dora-rs 概念:数据流、节点和 Python 算子。
  • 实现了一个多节点数据流,包括:
    • 一个用于图像捕获的摄像头节点。
    • 一个用于基于 AI 的处理(人脸检测和边界框绘制)的人脸检测器节点。
    • 一个用于可视化结果的显示节点。
  • dora-rs 中进行数据序列化(使用 PyArrow 处理图像)和节点间通信的实践经验。
  • 了解如何配置、运行和管理 dora-rs 应用程序。

B. DORA-rs 的强大功能和灵活性

这个例子虽然基础,但展示了 dora-rs 的几个强大方面:

  • 模块化: 应用程序被分解为不同的、可重用的节点,每个节点都有明确的职责。这促进了更清晰的代码和更容易的维护。

  • 声明式数据流: YAML 配置提供了一种清晰简洁的方式来定义应用程序的架构和数据路径。

  • Python 集成: 无缝使用 Python 及其丰富的生态系统(OpenCV、NumPy、PyArrow)来完成计算机视觉等复杂任务。

  • 性能潜力: 虽然此处未进行深入基准测试,但使用 Rust 和 Apache Arrow 的底层架构专为低延迟通信而设计,这对于实时机器人技术至关重要。  

  • 开发者体验:--hot-reload 这样的功能显著提高了开发速度。  

dora-rs 框架,凭借其活跃的开发(例如 dora-drives 等示例项目以及参与 Google Summer of Code 等计划),正在发展成为一个用于机器人技术的综合工具。这些活动表明了致力于围绕现实世界的机器人应用程序构建强大的生态系统并培养社区的承诺。  

C. 进一步探索的建议

完成的应用程序是更高级项目的绝佳起点。鼓励用户进一步探索:

  • 使用更高级的模型:

    • 用更准确、更强大的基于深度学习的人脸检测器(例如 MTCNN、BlazeFace 或来自 torch.hub 的轻量级模型,如 dora-rs 文档中用于对象检测的提示 )替换 Haar 级联分类器。  
  • 实现真正的人脸跟踪:

    • 在人脸检测器之后添加一个新节点,该节点将边界框作为输入。
    • 实现跟踪算法(例如,OpenCV 的内置跟踪器,如 KCF 或 MOSSE,或带有关联机制的简单卡尔曼滤波器),以跨帧分配和维护人脸 ID。
  • 模块化数据传输:

    • 修改 face_detector_node 以将原始图像和边界框坐标列表(以及可能的置信度分数)作为单独的数据流输出。
    • 更新 display_node 以接收两个流并执行边界框的绘制。这增强了关注点的分离。
  • 探索其他 dora-rs 功能:

    • 试验算子中的不同输入类型和事件处理。
    • 在算子内实现更复杂的错误处理和恢复机制。
    • 如果构建分布式系统,研究 dora-rs 的跨机器通信能力(该框架专为跨机器扩展而设计 )。  
  • 查阅官方资源:

    • 深入研究官方 dora-rs 文档以获取更多示例、API 参考和高级指南。  

    • 探索 dora-rs GitHub 存储库以获取示例项目,如 dora-drives ,并了解该框架的持续开发。  

虽然本教程侧重于 Python,但 dora-rs 还支持 Rust、C 和 C++ API ,这表明其设计用于构建可能需要混合语言的高性能、复杂的机器人系统。这使得 dora-rs 不仅仅是一个脚本工具,而且是应对各种机器人挑战的基础框架。

Back to Blog

Related Posts

View All Posts »