Meet YOLO26: next-gen vision AI.

Link to this section用于 Ultralytics YOLO 模型的 Hailo 导出#

非直接的 Ultralytics 导出格式

Hailo HEF 目前尚未直接支持作为 Ultralytics model.export(format="hailo") 的目标格式。下述工作流程是一种手动替代方案,即先导出为 ONNX,然后使用 Hailo 的外部 Dataflow Compiler 工具链生成 .hef 文件。一个无缝的 Ultralytics 工作流程应该像支持其他硬件格式一样,通过相同的 Python 和 CLI 导出 API 来支持 Hailo。

Hailo 工具链使用 HEF 文件,适用于包括 Raspberry Pi AI KitAI HAT+、工业相机、边缘网关以及 AI PC 在内的嵌入式平台。

本指南介绍了如何使用 Hailo Dataflow Compiler (DFC) SDK 将选定的 Ultralytics YOLO 模型导出为 Hailo 的 HEF (Hailo Executable Format)。该工作流程从 YOLO .pt 模型开始,导出为 ONNX,使用 Hailo 工具进行编译,并生成适用于受支持 Hailo 加速器的 .hef 文件。

Link to this section何时使用 Hailo HEF#

HEF 是由 HailoRT 在 Hailo 目标设备上使用的编译制品。仅当你的部署硬件特别要求使用 Hailo HEF,且尚未提供直接的 Ultralytics Hailo 导出支持时,才使用此手动指南。

HEF 在部署作用上类似于针对特定硬件的格式,例如用于 Rockchip NPU 的 RKNN、用于 Raspberry Pi AI 相机的 IMX500 和用于 Snapdragon NPU 的 Qualcomm QNN,但它目前并非由 Ultralytics 直接生成。

当你需要以下功能时,此工作流程非常适用:

  • Raspberry Pi AI Kit 兼容性:Hailo-8L 用于官方的 Raspberry Pi AI Kit 和 AI HAT+。
  • HailoRT 后处理:HailoRT 可以在编译的推理流水线中包含 YOLO 非极大值抑制
  • INT8 编译:Hailo DFC 使用代表性校准图像对模型进行量化,从而为 Hailo 硬件生成 INT8 图。了解更多关于 模型量化 的信息。

Link to this sectionHailo HEF 导出格式#

HEF is a hardware-specific executable generated by the Hailo Dataflow Compiler. It contains the quantized model graph, memory allocation, scheduling, and optional post-processing configured for a target Hailo architecture. Unlike standard YOLO Export mode formats that are produced directly by model.export(format=...), HEF compilation currently uses a two-stage flow:

  1. 使用 Ultralytics 将 YOLO 导出为 ONNX
  2. 使用 Hailo DFC 工具解析、优化、量化并将 ONNX 模型编译为 HEF。

完整工作流程可扩展为以下流水线:

YOLO (.pt) -> ONNX -> HAR (parse) -> HAR (optimize/quantize) -> HEF (compile)
  1. 使用 Ultralytics 导出模式 导出为 ONNX
  2. 解析 ONNX 模型为 Hailo 的中间 HAR 格式
  3. 加载模型脚本 (.alls),其中包含归一化和后处理指令
  4. 使用代表性图像进行 校准和量化
  5. 编译 为可部署的 HEF 文件

Link to this section支持的任务#

当前的手动示例重点介绍 YOLO11 目标检测,因为 Hailo 模型脚本和后处理配置是针对检测头特定的。未来直接的 model.export(format="hailo") 实现应该使 Hailo 导出体验与其他所有 Ultralytics 导出格式一样,任务支持由模型头和 Hailo 编译器兼容性决定,而不是由外部工作流步骤决定。

任务直接 Hailo 导出目标注意事项
目标检测✅ 主要目标YOLOv8、YOLO11 和 YOLO26 检测应该是首个直接导出路径。
实例分割✅ 目标YOLOv8、YOLO11 和 YOLO26 分割需要任务特定的掩码输出处理和验证。
语义分割⚠️ 需验证YOLO26 语义分割需要专门的编译器和输出验证路径。
姿态估计⚠️ 需验证姿态估计(Pose)需要除了检测 NMS 路径之外的关键点输出处理。
OBB 检测⚠️ 需验证旋转目标检测(OBB)需要除了标准检测 NMS 路径之外的旋转框输出处理。
分类⚠️ 需验证分类(Classification)具有更简单的输出头,但仍然需要 Hailo 编译和运行时验证。

在 Ultralytics 中实现直接 Hailo 导出之前,仅记录下方的手动 ONNX 转 HEF 工作流程。

Link to this sectionHailo SDK 版本#

直接 Hailo 导出必须考虑到 Hailo 的硬件和 SDK 版本差异:

  • Hailo-8 和 Hailo-8L:使用 Hailo Dataflow Compiler v3.x。这是 Raspberry Pi AI Kit 和 13 TOPS AI HAT+ 部署的相关路径。
  • Hailo-10 和 Hailo-15:使用 Hailo Dataflow Compiler v5.x。

此版本差异会影响编译器 API、支持的架构、生成的 HEF 兼容性,以及直接导出器应公开哪些 hw_arch 值。如果不验证目标 DFC 版本和 hw_arch,不应将某种 Hailo 硬件代际的任务支持视为在另一种硬件上也受支持。

Link to this section兼容性说明#

Hailo 导出兼容性取决于模型头、输入图像大小、类别数、Hailo 架构、生成的模型脚本 (.alls) 以及后处理配置。静态配置不是通用模板。例如,为 COCO 80 类 YOLO11n 模型创建的 NMS JSON 不适用于自定义的 3 类模型或不同的固定 imgsz

范围预期支持注意事项
YOLOv8 / YOLO11 检测✅ 良好共享解耦检测头;.alls 指令、结束节点和 NMS 配置仍需与导出的模型图及固定 imgsz 相匹配。
自定义 YOLOv8 / YOLO11 检测✅ 可能需要根据类别数、步长和检测头布局生成的每个模型专用的 NMS 配置;静态 JSON 无法匹配。
YOLO26 检测✅ 目标无 NMS 架构需要单独的编译器/后处理路径;不要为 YOLO26 重用下方的 YOLO11/YOLOv8 NMS 工作流。
YOLO26 实例分割✅ 目标需要 YOLO26 分割特定的掩码输出处理和准确性验证。
YOLO26 语义分割、姿态估计、OBB、分类⚠️ 研究中这些任务需要专门的编译器和运行时验证,才能被宣传为直接支持。
动态或任意图像大小❌ 不支持Hailo 编译使用固定的输入形状;.alls 和后处理设置必须与导出的 imgsz 匹配。

Link to this section安装#

Link to this section第 1 步:安装 Ultralytics#

pip install ultralytics

Link to this section第 2 步:安装 Hailo DFC SDK#

解析、优化和编译需要 Hailo DFC。从 Hailo Developer Zone 下载 Python wheel(需免费注册)并安装它:

pip install /path/to/hailo_dataflow_compiler-*.whl
注意

Hailo DFC SDK 需要 Linux x86_64 机器。导出和编译无法在 Raspberry Pi 等 ARM 设备上执行。将生成的 .hef 文件复制到你的 Hailo 驱动设备上,以便通过 HailoRT 进行部署。

Link to this sectionYOLO11n HEF 导出示例#

下面的脚本以 640 像素的固定输入大小将 YOLO11n 检测模型从 .pt 编译为 .hef。它使用 Ultralytics 导出为 ONNX,然后使用 COCO128 作为小型校准数据集,通过 Hailo DFC 进行编译。

在运行脚本之前,请提供一个与确切的 YOLO11n 导出模型图、类别数、步长和固定输入尺寸相匹配的 Hailo NMS JSON。将此脚本用作已知的 YOLO11n 起点;自定义模型需要匹配的结束节点、.alls 指令以及 NMS 设置。

YOLO26 使用不同的 Hailo 路径

YOLO26 模型是无 NMS 的。直接的 Ultralytics Hailo 导出器需要针对检测或实例分割的专用 YOLO26 编译和后处理路径,而不是使用下方的 YOLO11 NMS 示例。

完整流水线
import ast
import random
from pathlib import Path

import numpy as np
import onnx
from hailo_sdk_client import ClientRunner
from PIL import Image

from ultralytics import YOLO
from ultralytics.data.utils import check_det_dataset
from ultralytics.utils import DATASETS_DIR, YAML

# Configuration
MODEL = "yolo11n"
HW_ARCH = "hailo8"  # hailo8 | hailo8l | hailo15h
IMGSZ = 640
CALIB_IMAGES = 128
NMS_CONFIG = "yolo11n_nms_config.json"  # Download or generate for your exact model.
OUT_DIR = Path(f"{MODEL}_hailo_model")  # deploy folder (mirrors Ultralytics <model>_<format>_model exports)
OUT_DIR.mkdir(exist_ok=True)

# YOLO11 detection head end nodes. See "Supported Models and End Nodes" for YOLOv8 and other families.
END_NODES = [
    "/model.23/cv2.0/cv2.0.2/Conv",
    "/model.23/cv3.0/cv3.0.2/Conv",
    "/model.23/cv2.1/cv2.1.2/Conv",
    "/model.23/cv3.1/cv3.1.2/Conv",
    "/model.23/cv2.2/cv2.2.2/Conv",
    "/model.23/cv3.2/cv3.2.2/Conv",
]

# Step 1: Export to ONNX, then move it into the deploy folder to keep the working directory tidy
model = YOLO(f"{MODEL}.pt")
onnx_path = Path(model.export(format="onnx", imgsz=IMGSZ, opset=11))
onnx_path = onnx_path.rename(OUT_DIR / onnx_path.name)

# Copy the metadata Ultralytics embedded in the ONNX into the standard metadata.yaml sidecar.
# The HEF stores no class names, so inference reads them from this file.
meta = {p.key: p.value for p in onnx.load(onnx_path, load_external_data=False).metadata_props}
for k in ("stride", "batch", "channels"):
    if k in meta:
        meta[k] = int(meta[k])
for k in ("imgsz", "names", "args", "end2end"):
    if k in meta:
        meta[k] = ast.literal_eval(meta[k])
YAML.save(OUT_DIR / "metadata.yaml", meta)

# Step 2: Parse ONNX with Hailo DFC
# The DFC prints the detected end nodes after parsing; use them if unsure.
runner = ClientRunner(hw_arch=HW_ARCH)
runner.translate_onnx_model(str(onnx_path), end_node_names=END_NODES)

# Step 3: Load model script (normalization + HailoRT NMS)
# The conv layer names are generated by DFC and can change for other model sizes/families.
model_script = (
    "normalization1 = normalization([0.0, 0.0, 0.0], [255.0, 255.0, 255.0])\n"
    "change_output_activation(conv54, sigmoid)\n"
    "change_output_activation(conv65, sigmoid)\n"
    "change_output_activation(conv80, sigmoid)\n"
    f'nms_postprocess("{NMS_CONFIG}", meta_arch=yolov8, engine=cpu)\n'
    "allocator_param(width_splitter_defuse=disabled)"
)
runner.load_model_script(model_script)

# Step 4: Build calibration dataset (auto-downloads COCO128)
check_det_dataset("coco128.yaml")
calib_dir = DATASETS_DIR / "coco128" / "images" / "train2017"
image_files = list(calib_dir.glob("*.jpg")) + list(calib_dir.glob("*.png"))
if not image_files:
    raise FileNotFoundError(f"No calibration images found in {calib_dir}")

calibset = np.zeros((CALIB_IMAGES, IMGSZ, IMGSZ, 3), dtype=np.float32)
for i in range(CALIB_IMAGES):
    img = Image.open(random.choice(image_files)).convert("RGB").resize((IMGSZ, IMGSZ))
    calibset[i] = np.array(img, dtype=np.float32)

# Step 5: Optimize and quantize
runner.optimize(calibset)
runner.save_har(str(OUT_DIR / f"{MODEL}.o.har"))  # optional intermediate HAR

# Step 6: Compile to HEF
hef = runner.compile()
hef_path = OUT_DIR / f"{MODEL}.hef"
with open(hef_path, "wb") as f:
    f.write(hef)

# Note: the Hailo SDK writes *.log files (acceleras.log, allocator.log, hailo_sdk.client.log,
# hailo_sdk.core.log) to the working directory. They are diagnostic scratch, safe to ignore or delete.
print(f"Compiled HEF saved to: {hef_path}")

导出脚本组织产物和日志的方式如下:

  • 部署文件夹:产物保存至 yolo11n_hailo_model/,这与其它 Ultralytics 导出格式使用的标准 <model>_<format>_model/ 布局一致。
  • 所需文件:部署所需的两个文件是编译好的 yolo11n.hef 和配套的 metadata.yaml 附属文件。
  • 元数据metadata.yaml 包含从 ONNX 元数据中提取的关键字段(namesimgsztaskstride 等)。由于 HEF 格式不存储类别名称,推理脚本会从该文件中加载这些名称。
  • 中间文件:导出文件夹还包含中间生成的 yolo11n.onnxyolo11n.o.har 检查点文件。
  • 日志文件:Hailo SDK 会在工作目录中生成若干诊断日志(例如 acceleras.logallocator.loghailo_sdk.client.loghailo_sdk.core.log);这些文件可以安全忽略或删除。
  • Raspberry Pi AI Kit:对于此特定硬件,请务必在执行编译步骤前设置 HW_ARCH = "hailo8l"

Link to this section逐步拆解#

上述完整脚本可端到端运行。本节将解释每个阶段的作用,以及在将其适配到你自己的模型时需要注意的模型特定细节。

Link to this section第 1 步:导出为 ONNX 并保存元数据#

Ultralytics 会将你训练好的模型导出为 ONNX 格式,作为 Hailo DFC 的输入。使用 opset=11 可提供广泛的 DFC 兼容性,且 ONNX 文件会被移至 yolo11n_hailo_model/ 部署文件夹中(模仿其它 Ultralytics 导出的 <model>_<format>_model/ 布局),以保持工作目录整洁。

由于 HEF 不存储类别名称,Ultralytics 嵌入在 ONNX 中的元数据会被复制到旁边的标准 metadata.yaml 附属文件中。这与其它导出格式生成的 metadata.yaml(包含 namesimgsztaskstride 等)相同,推理过程从中读取类别名称,因此该工作流程无需硬编码任何标签即可适用于自定义模型。

Link to this section第 2 步:解析 ONNX 模型#

runner.translate_onnx_model(...) 将 ONNX 图转换为 Hailo 的中间 HAR 表示。end_node_names 列表会告诉 DFC 在 NMS 之前截断图结构,以便 Hailo 能够接入其硬件加速的后处理模块。

查找端节点

DFC 在解析后会打印一条建议:

[info] In order to use HailoRT post-processing capabilities, these end node names should be used: ...

如果你不确定使用哪些节点,或者正在使用自定义或不太常见的架构,请复制这些节点名称。

Link to this section第 3 步:加载模型脚本#

模型脚本 (.alls) 用于配置输入归一化、输出激活和 NMS 后处理。meta_arch=yolov8 设置适用于 YOLOv8 和 YOLO11,因为它们共享相同的检测头布局。

MODEL = "yolo11n"
NMS_CONFIG = "yolo11n_nms_config.json"
model_script = (
    "normalization1 = normalization([0.0, 0.0, 0.0], [255.0, 255.0, 255.0])\n"
    "change_output_activation(conv54, sigmoid)\n"
    "change_output_activation(conv65, sigmoid)\n"
    "change_output_activation(conv80, sigmoid)\n"
    f'nms_postprocess("{NMS_CONFIG}", meta_arch=yolov8, engine=cpu)\n'
    "allocator_param(width_splitter_defuse=disabled)"
)
runner.load_model_script(model_script)
注意

change_output_activation 层名称 (conv54conv65conv80) 是由 DFC 在解析过程中分配的,并且是模型特定的。如果你正在编译不同尺寸或架构的模型,请检查 DFC 输出以获取正确名称,或根据导出的模型图生成 .alls 指令。

NMS_CONFIG 文件也是模型特定的。请使用与你导出的模型完全匹配的配置。

engine=cpu 通过主机 CPU 上的 HailoRT 运行 NMS。仅对 Hailo 文档说明由目标硬件和 SDK 版本支持的模型/脚本组合使用 engine=nn_core

如果你希望在应用程序代码中完全运行 NMS,请移除 nms_postprocess 行。如果这样做,请更新推理解析器,因为 HEF 将输出原始检测头张量,而不是分组后的 NMS 检测结果。

Link to this section第 4 步:构建校准数据集#

INT8 量化需要一组代表性图像,并将其堆叠为 (N, imgsz, imgsz, 3) 的 float32 数组。该脚本使用 COCO128 数据集,Ultralytics 会通过 check_det_dataset 自动下载。

提示

校准至少使用 64 张图像。更多图像通常能提高量化质量。为了获得最佳效果,请使用来自你的部署领域的图像,而不是 COCO128。

Link to this section第 5 步:优化和量化#

runner.optimize(calibset) 应用量化感知微调和层噪声分析,随后 runner.save_har(...) 会写入一个可选的中间检查点。强烈建议使用 GPU;如果没有 GPU,此步骤可能需要数小时。

Link to this section第 6 步:编译为 HEF#

runner.compile() 生成最终的 HEF 文件,并写入 yolo11n_hailo_model/yolo11n.hef。现在它与 metadata.yaml 一起存放,准备好复制到设备上进行推理。

Link to this section支持的模型和端节点#

对于检测模型,end_node_names 标识了 Hailo 在附加其 NMS 后处理之前应该编译的 ONNX 检测头输出。这些名称因架构而异,并可能随着导出图的更改而变化。

下方的结束节点示例适用于使用 Hailo YOLOv8 风格 NMS 后处理的 YOLOv8 和 YOLO11 检测模型。YOLO26 是无 NMS 的,不使用此 YOLO11 NMS 配置。

Link to this sectionYOLO11 和 YOLOv8#

YOLO11 和 YOLOv8 共享相同的解耦检测头。两个系列之间的层索引相差一个:

模型系列检测头层端节点模式
YOLO11 (全部)model.23/model.23/cv2.0/cv2.0.2/Conv (6 个节点)
YOLOv8 (全部)model.22/model.22/cv2.0/cv2.0.2/Conv (6 个节点)

YOLO11 端节点 (所有尺寸:n, s, m, l, x):

END_NODES = [
    "/model.23/cv2.0/cv2.0.2/Conv",
    "/model.23/cv3.0/cv3.0.2/Conv",
    "/model.23/cv2.1/cv2.1.2/Conv",
    "/model.23/cv3.1/cv3.1.2/Conv",
    "/model.23/cv2.2/cv2.2.2/Conv",
    "/model.23/cv3.2/cv3.2.2/Conv",
]

YOLOv8 端节点 (所有尺寸:n, s, m, l, x):

END_NODES = [
    "/model.22/cv2.0/cv2.0.2/Conv",
    "/model.22/cv3.0/cv3.0.2/Conv",
    "/model.22/cv2.1/cv2.1.2/Conv",
    "/model.22/cv3.1/cv3.1.2/Conv",
    "/model.22/cv2.2/cv2.2.2/Conv",
    "/model.22/cv3.2/cv3.2.2/Conv",
]

Link to this section其他架构#

对于其他检测架构,首先在不带 end_node_names 的情况下运行解析步骤,从 DFC 日志输出中读取建议的节点,然后使用这些节点重新运行:

# First pass: let the DFC suggest end nodes
runner = ClientRunner(hw_arch=HW_ARCH)
runner.translate_onnx_model(f"{MODEL}.onnx")
# Check the printed log for: "[info] In order to use HailoRT post-processing..."

对于直接的 Ultralytics 支持,这些 .alls 指令和后处理设置应由导出器生成或选择,而不是要求用户手动组合。

Link to this section支持的硬件架构#

架构设备峰值算力 (供应商规格)常见用例
hailo8Hailo-826 TOPSHailo 加速卡
hailo8lHailo-8L13 TOPSRaspberry Pi AI Kit
hailo15hHailo-15H20 TOPSHailo-15 目标设备

在编译之前,请在脚本中设置 HW_ARCH 以匹配你的目标设备。

Link to this section在 Hailo 硬件上运行推理#

编译完成后,将整个 yolo11n_hailo_model/ 文件夹(包含 .hef 及其 metadata.yaml)复制到你的 Hailo 驱动设备上,并使用 HailoRT Python API (hailo_platform 包) 或者在 Raspberry Pi 上使用 picamera2 Hailo 辅助工具(HailoRT 的封装)运行推理。下方选项卡展示了这两种方法。将这两个文件存放在一起,可以让下方的脚本从 HEF 旁边的 metadata.yaml 中读取类别名称。与 DFC 导出步骤不同,推理直接在边缘设备上运行。

注意

下方的推理代码是在 Hailo 设备上(例如 Raspberry Pi + AI Kit)运行的,而不是在用于编译的 x86 机器上运行。

Link to this section第 1 步:在设备上安装 HailoRT#

在目标设备上,安装 HailoRT 和 Python 绑定。对于 Raspberry Pi AI Kit 和 AI HAT+ 用户,官方 Raspberry Pi AI 软件指南 会安装 HailoRT、设备驱动程序和 Python 绑定,命令如下:

sudo apt install dkms
sudo apt install hailo-all
sudo reboot

对于非 Raspberry Pi 的 Hailo 设备,请从 Hailo 开发者专区 (Hailo Developer Zone) 安装与你的设备、驱动程序和 SDK 版本匹配的 HailoRT 软件包。

AI HAT+ 2 设备使用不同的 Raspberry Pi 软件包 (hailo-h10-all) 和 Hailo-10H 工作流。请遵循该硬件代系的 Raspberry Pi AI 软件指南。

Link to this section第 2 步:快速完整性检查#

在运行 Python 推理之前,请确认 Hailo 设备已被识别:

hailortcli fw-control identify

你应该能看到打印出的设备类型、固件版本和序列号。

Executing on device: 0001:01:00.0
Identifying board
Control Protocol Version: 2
Firmware Version: 4.23.0 (release,app,extended context switch buffer)
Logger Version: 0
Board Name: Hailo-8
Device Architecture: HAILO8

Link to this section第 3 步:运行推理#

下方的脚本使用编译好的 HEF 文件运行目标检测。两个选项卡接受相同的 --source 输入(图像、视频、USB 网络摄像头索引,或 Raspberry Pi 摄像头模块的 csi),仅推理 API 有所不同:Hailo SDK 选项卡使用底层的 hailo_platform API(可移植,依赖项最少),而 picamera2 选项卡使用 Raspberry Pi 的 picamera2 Hailo 辅助工具。图像和视频会被写入标注文件;网络摄像头和 CSI 数据流则在实时窗口中显示。

供应商原生 HailoRT 路径可在任何配备 Hailo 设备的平台上运行,无需额外依赖。为 --source 传递图像路径、视频路径、用于实时 USB/V4L2 采集的网络摄像头索引(例如 0),或用于 Raspberry Pi 摄像头模块的 csi。CSI 选项需要安装 picamera2,因为现代 Raspberry Pi OS 通过 libcamera 而非传统的 V4L2 设备来调度摄像头。

import argparse
from pathlib import Path

import cv2
import numpy as np
import yaml
from hailo_platform import (
    HEF,
    ConfigureParams,
    FormatType,
    HailoStreamInterface,
    InferVStreams,
    InputVStreamParams,
    OutputVStreamParams,
    VDevice,
)
from tqdm import tqdm

IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".bmp", ".webp", ".tif", ".tiff"}

def parse_and_draw(per_class, frame, conf, names):
    """Draw HailoRT NMS detections (grouped by class, normalized [0, 1] coords) onto a BGR frame."""
    h, w = frame.shape[:2]
    for cls_idx, cls_dets in enumerate(per_class):
        for det in cls_dets:
            score = float(det[4])
            if score < conf:
                continue
            # HailoRT NMS returns normalized [0, 1] coords as (y1, x1, y2, x2)
            y1, x1, y2, x2 = det[:4]
            x1, y1, x2, y2 = int(x1 * w), int(y1 * h), int(x2 * w), int(y2 * h)
            label = f"{names[cls_idx]} {score:.2f}"
            cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
            cv2.putText(frame, label, (x1 + 2, y1 + 15), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1, cv2.LINE_AA)

def preprocess(frame, imgsz):
    """BGR frame -> (1, imgsz, imgsz, 3) float32 in 0-255 (HEF normalizes internally)."""
    rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    resized = cv2.resize(rgb, (imgsz, imgsz))
    return np.expand_dims(resized.astype(np.float32), axis=0)

def csi_frames(width=1280, height=720):
    """Yield BGR frames from the Pi CSI Camera Module via picamera2."""
    from picamera2 import Picamera2

    picam2 = Picamera2()
    # picamera2 "RGB888" is BGR-ordered in memory, so it drops straight into OpenCV
    picam2.configure(picam2.create_preview_configuration(main={"size": (width, height), "format": "RGB888"}))
    picam2.start()
    try:
        while True:
            yield picam2.capture_array("main")  # BGR
    finally:
        picam2.stop()
        picam2.close()

def cv2_frames(src):
    """Yield BGR frames from a video file or USB/V4L2 webcam via OpenCV."""
    cap = cv2.VideoCapture(src)
    if not cap.isOpened():
        raise RuntimeError(f"Could not open source {src}")
    total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))  # 0 for live webcams
    pbar = tqdm(total=total, desc="Processing video", unit="frame") if total > 0 else None
    try:
        while True:
            ok, frame = cap.read()  # BGR
            if not ok:
                break
            yield frame
            if pbar is not None:
                pbar.update(1)
    finally:
        if pbar is not None:
            pbar.close()
        cap.release()

def open_source(source):
    """Yield (frame, kind) pairs where kind is 'image', 'video', or 'stream'."""
    if source == "csi":
        yield from ((f, "stream") for f in csi_frames())
    elif source.isdigit():
        yield from ((f, "stream") for f in cv2_frames(int(source)))
    elif Path(source).suffix.lower() in IMAGE_EXTS:
        frame = cv2.imread(source)
        if frame is None:
            raise FileNotFoundError(f"Could not read image {source}")
        yield frame, "image"
    else:
        yield from ((f, "video") for f in cv2_frames(source))

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Hailo YOLO inference (image, video, webcam, or CSI camera)")
    parser.add_argument("-m", "--model", default="yolo11n_hailo_model/yolo11n.hef", help="Path to the HEF model.")
    parser.add_argument("--source", default="0", help="Image/video path, webcam index (e.g. 0), or 'csi'.")
    parser.add_argument("--imgsz", type=int, default=640)
    parser.add_argument("--conf", type=float, default=0.25)
    args = parser.parse_args()

    # Load class names from metadata.yaml saved next to the HEF during compilation (keyed by class index)
    with open(Path(args.model).parent / "metadata.yaml") as f:
        names = yaml.safe_load(f)["names"]

    # Configure the device and network group ONCE
    hef = HEF(args.model)
    target = VDevice(VDevice.create_params())
    configure_params = ConfigureParams.create_from_hef(hef, interface=HailoStreamInterface.PCIe)
    network_group = target.configure(hef, configure_params)[0]
    network_group_params = network_group.create_params()
    input_vstreams_params = InputVStreamParams.make(network_group, quantized=False, format_type=FormatType.FLOAT32)
    output_vstreams_params = OutputVStreamParams.make(network_group, quantized=False, format_type=FormatType.FLOAT32)
    input_name = hef.get_input_vstream_infos()[0].name

    writer = None  # lazily created for video output

    # Keep the pipeline and activation OPEN across frames (re-opening per frame is slow)
    with InferVStreams(network_group, input_vstreams_params, output_vstreams_params) as pipeline:
        with network_group.activate(network_group_params):
            try:
                for frame, kind in open_source(args.source):
                    raw = pipeline.infer({input_name: preprocess(frame, args.imgsz)})
                    parse_and_draw(raw[next(iter(raw.keys()))][0], frame, args.conf, names)

                    if kind == "image":
                        cv2.imwrite("output.jpg", frame)
                        print("Saved output.jpg")
                    elif kind == "video":
                        if writer is None:
                            h, w = frame.shape[:2]
                            writer = cv2.VideoWriter("output.mp4", cv2.VideoWriter_fourcc(*"mp4v"), 30, (w, h))
                        writer.write(frame)
                    else:  # live stream
                        cv2.imshow("Hailo YOLO", frame)
                        if cv2.waitKey(1) & 0xFF == ord("q"):
                            break
            finally:
                if writer is not None:
                    writer.release()
                    print("Saved output.mp4")
                cv2.destroyAllWindows()

针对任何来源运行它(图像保存为 output.jpg,视频保存为 output.mp4,实时流显示在窗口中,按 q 键退出):

python hailo_infer.py --source bus.jpg  # single image
python hailo_infer.py --source clip.mp4 # video file
python hailo_infer.py --source 0        # USB webcam, live
python hailo_infer.py --source csi      # Raspberry Pi Camera Module
提示

The detection output format assumes the HEF was compiled with nms_postprocess in the .alls script. If you compiled without NMS, the raw outputs are the 6 detection head tensors and you must run NMS in your application separately.

Link to this section使用 TAPPAS 进行视频推理#

对于高吞吐量的视频流水线,TAPPAS 提供了 GStreamer 元素,可以实时将视频流传输通过 Hailo 芯片:

MODEL=yolo11n
gst-launch-1.0 filesrc location=video.mp4 ! decodebin ! \
  hailonet hef-path=${MODEL}.hef ! \
  hailofilter function-name=yolov8 ! \
  hailooverlay ! autovideosink

查看 TAPPAS 文档 以获取完整的流水线配置选项。

Link to this section总结#

本指南介绍了将 Ultralytics YOLO 检测模型导出为 Hailo HEF 格式的完整工作流:

  1. 使用 Ultralytics 导出为 ONNX (model.export(format="onnx"))。
  2. 使用 Hailo DFC 解析 ONNX 模型并指定检测头终端节点。
  3. 通过模型脚本配置归一化和 NMS。
  4. 使用校准数据集(通过 Ultralytics 使用 COCO128)进行量化。
  5. 编译为可用于 Hailo-8、Hailo-8L 或 Hailo-15 的 .hef 文件。

更多详情,请参见 Hailo Developer ZoneHailo 文档。关于其他 Ultralytics 导出目标,请参见相关的 ONNXOpenVINOTensorRTNCNNTFLite Edge TPURKNNSony IMX500Qualcomm QNN 指南。要比较不同格式下导出模型的速度和准确性,请使用 Benchmark 模式。有关完整格式和选项列表,请访问 Export 模式文档和集成指南页面

Link to this section常见问题解答#

Link to this section支持哪些 Hailo 设备?#

Hailo DFC 支持 Hailo-8 (hailo8)、Hailo-8L (hailo8l) 和 Hailo-15H (hailo15h)。请参阅 支持的硬件架构 表以获取匹配的 HW_ARCH 值。

Link to this section哪些 Ultralytics 模型可以导出?#

本指南重点介绍检测模型。请参阅 支持的任务 了解任务范围,兼容性说明 了解模型兼容性限制,以及 支持的模型和终端节点 了解 YOLO11 和 YOLOv8 终端节点示例。

Link to this section为什么模型脚本对 YOLO11 使用 meta_arch=yolov8#

YOLO11 使用与 YOLOv8 相同的解耦检测头架构。Hailo DFC 在两个模型系列的 NMS 配置中都使用 meta_arch=yolov8

Link to this section优化步骤是否需要 GPU?#

强烈建议在 runner.optimize() 中进行量化感知微调时使用 GPU。如果没有 GPU,该过程仍然可以运行,但速度会慢得多(需要几个小时,而使用 GPU 仅需约 10-20 分钟)。

Link to this section我该如何找到模型的正确终端节点?#

运行 runner.translate_onnx_model(...) 而不指定 end_node_names,然后使用 DFC 打印出的建议检测头节点。有关示例命令,请参阅 其他架构

Link to this section我从哪里可以获得 Hailo DFC SDK?#

Hailo DFC SDK Python wheel 可从 Hailo Developer Zone 获取。对于直接的 Ultralytics Hailo 导出器,模型脚本和后处理配置应在导出工作流内部生成或选择。

评论