Meet YOLO26: next-gen vision AI.

Link to this sectionUltralytics YOLO 모델을 위한 Hailo 내보내기#

직접적인 Ultralytics 내보내기 형식이 아님

Hailo HEF는 현재 Ultralytics model.export(format="hailo")의 직접적인 대상으로는 지원되지 않습니다. 아래 워크플로는 ONNX로 먼저 내보낸 다음 Hailo의 외부 Dataflow Compiler 툴체인을 사용하여 .hef 파일을 생성하는 수동 대체 방법입니다. 원활한 Ultralytics 워크플로라면 다른 하드웨어 형식과 마찬가지로 동일한 Python 및 CLI 내보내기 API를 통해 Hailo를 노출해야 합니다.

Hailo 툴체인은 Raspberry Pi AI KitAI HAT+, 산업용 카메라, 엣지 게이트웨이 및 AI PC를 포함한 임베디드 플랫폼용으로 HEF 파일을 사용합니다.

이 가이드는 Hailo Dataflow Compiler (DFC) SDK를 사용하여 선택된 Ultralytics YOLO 모델을 Hailo의 **HEF(Hailo Executable Format)**로 내보내는 과정을 안내합니다. 워크플로는 YOLO .pt 모델에서 시작하여 ONNX로 내보내고, Hailo 도구로 컴파일한 뒤, 지원되는 Hailo 가속기용 .hef 파일을 생성합니다.

Link to this sectionHailo HEF 사용 시기#

HEF는 Hailo 대상 장치에서 HailoRT가 사용하는 컴파일된 아티팩트입니다. 이 수동 가이드는 Ultralytics에서 직접적인 Hailo 내보내기 지원을 사용할 수 없는 경우 배포 하드웨어에서 Hailo HEF를 명시적으로 요구할 때만 사용하십시오.

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 비최대 억제(NMS)를 포함할 수 있습니다.
  • 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. 구문 분석(Parse): ONNX 모델을 Hailo의 중간 HAR 형식으로 변환
  3. 모델 스크립트(.alls) 로드: 정규화 및 후처리 지시문 포함
  4. 보정 및 양자화(Calibrate and quantize): 대표 이미지를 사용하여 수행
  5. 컴파일(Compile): 배포 가능한 HEF 파일 생성

Link to this section지원되는 작업#

현재 수동 예제는 YOLO11 객체 탐지에 초점을 맞추고 있는데, 이는 Hailo 모델 스크립트와 후처리 구성이 탐지 헤드별로 다르기 때문입니다. 향후 직접적인 model.export(format="hailo") 구현을 통해 Hailo 내보내기가 다른 모든 Ultralytics 내보내기 형식과 동일하게 느껴지도록 해야 하며, 작업 지원은 외부 워크플로 단계가 아닌 모델 헤드 및 Hailo 컴파일러 호환성에 의해 결정되어야 합니다.

작업직접 Hailo 내보내기 대상참고
객체 탐지✅ 기본 대상YOLOv8, YOLO11 및 YOLO26 탐지가 첫 번째 직접 내보내기 경로가 되어야 합니다.
인스턴스 세분화(Instance Segmentation)✅ 대상YOLOv8, YOLO11 및 YOLO26 세그멘테이션은 작업별 마스크 출력 처리 및 검증이 필요합니다.
시맨틱 세그멘테이션⚠️ 확인 필요YOLO26 의미론적 세그멘테이션은 전용 컴파일러 및 출력 검증 경로가 필요합니다.
포즈 추정⚠️ 확인 필요포즈(Pose)는 탐지 NMS 경로를 넘어선 키포인트 출력 처리가 필요합니다.
OBB 탐지⚠️ 확인 필요OBB는 표준 탐지 NMS 경로를 넘어선 회전된 박스(rotated-box) 출력 처리가 필요합니다.
분류⚠️ 확인 필요분류(Classification)는 더 간단한 출력 헤드를 가지지만, 여전히 Hailo 컴파일 및 런타임 검증이 필요합니다.

Ultralytics에 직접적인 Hailo 내보내기가 구현될 때까지는 아래의 수동 ONNX-to-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 값에 영향을 미칩니다. 한 Hailo 하드웨어 세대에서의 작업 지원을 대상 DFC 버전 및 hw_arch를 검증하지 않은 채 다른 세대에서의 지원으로 간주해서는 안 됩니다.

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가 없는 아키텍처는 별도의 컴파일러/후처리 경로가 필요합니다. 아래의 YOLO11/YOLOv8 NMS 워크플로를 YOLO26에 재사용하지 마십시오.
YOLO26 인스턴스 세그멘테이션✅ 대상YOLO26 세그멘테이션 전용 마스크 출력 처리 및 정확도 검증이 필요합니다.
YOLO26 의미론적, 포즈, OBB, 분류⚠️ 연구 중이러한 작업은 직접 지원되는 것으로 홍보되기 전에 전용 컴파일러 및 런타임 검증이 필요합니다.
동적 또는 임의 이미지 크기❌ 지원 안 함Hailo 컴파일은 고정된 입력 형태를 사용합니다. .alls 및 후처리 설정은 내보낸 imgsz와 일치해야 합니다.

Link to this section설치#

Link to this section1단계: Ultralytics 설치#

pip install ultralytics

Link to this section2단계: Hailo DFC SDK 설치#

Hailo DFC는 구문 분석, 최적화 및 컴파일에 필요합니다. Hailo 개발자 존에서 Python 휠을 다운로드하고(무료 등록 필요) 설치하십시오:

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 내보내기 예제#

아래 스크립트는 YOLO11n 탐지 모델을 .pt에서 고정 640픽셀 입력 크기의 .hef로 컴파일합니다. Ultralytics를 사용하여 ONNX로 내보낸 다음, COCO128을 작은 보정 데이터셋으로 사용하여 Hailo DFC로 컴파일합니다.

스크립트를 실행하기 전에 정확한 YOLO11n 내보내기 그래프, 클래스 수, 스트라이드 및 고정 입력 크기와 일치하는 Hailo NMS JSON을 제공하십시오. 이 스크립트를 알려진 YOLO11n 시작 지점으로 재사용하십시오. 사용자 지정 모델은 일치하는 엔드 노드, .alls 지시어 및 NMS 설정이 필요합니다.

YOLO26은 다른 Hailo 경로를 사용합니다

YOLO26 모델은 NMS가 필요 없습니다. 직접적인 Ultralytics Hailo 내보내기 도구는 아래의 YOLO11 NMS 예제 대신 탐지 또는 인스턴스 세그멘테이션을 위한 전용 YOLO26 컴파일 및 후처리 경로가 필요합니다.

전체 파이프라인
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.hefmetadata.yaml 사이드카입니다.
  • 메타데이터: metadata.yaml에는 ONNX 메타데이터에서 추출된 필수 필드(names, imgsz, task, stride 등)가 포함되어 있습니다. HEF 형식에는 클래스 이름이 저장되지 않으므로 추론 스크립트가 이 파일에서 클래스 이름을 로드합니다.
  • 중간 파일: 내보내기 폴더에는 중간 단계의 yolo11n.onnxyolo11n.o.har 체크포인트도 포함됩니다.
  • 로그 파일: Hailo SDK는 작업 디렉토리에 여러 진단 로그(예: acceleras.log, allocator.log, hailo_sdk.client.log, hailo_sdk.core.log)를 생성하며, 이는 안전하게 무시하거나 삭제할 수 있습니다.
  • Raspberry Pi AI Kit: 이 특정 하드웨어의 경우, 컴파일 단계를 실행하기 전에 HW_ARCH = "hailo8l"로 설정해야 합니다.

Link to this section단계별 분석#

위의 전체 스크립트는 처음부터 끝까지 실행됩니다. 이 섹션에서는 각 단계가 수행하는 작업과 이를 자신의 모델에 적용할 때 주의해야 할 모델별 세부 사항을 설명합니다.

Link to this section1단계: ONNX로 내보내기 및 메타데이터 저장#

Ultralytics는 학습된 모델을 ONNX 형식으로 내보내며, Hailo DFC가 이를 입력으로 가져옵니다. opset=11은 폭넓은 DFC 호환성을 제공하며, 작업 디렉토리를 깔끔하게 유지하기 위해 ONNX는 yolo11n_hailo_model/ 배포 폴더로 이동합니다(다른 Ultralytics 내보내기의 <model>_<format>_model/ 레이아웃 반영).

HEF에는 클래스 이름이 저장되지 않으므로, Ultralytics가 ONNX에 포함한 메타데이터가 바로 옆의 표준 metadata.yaml 사이드카로 복사됩니다. 이는 다른 내보내기 형식이 생성하는 것과 동일한 metadata.yaml(names, imgsz, task, stride 등)이며, 추론 시 여기서 클래스 이름을 읽으므로 레이블을 하드코딩하지 않고도 사용자 정의 모델에 대해 워크플로우가 작동합니다.

Link to this section2단계: ONNX 모델 구문 분석#

runner.translate_onnx_model(...)은 ONNX 그래프를 Hailo의 중간 HAR 표현으로 변환합니다. end_node_names 목록은 Hailo가 자체 하드웨어 후처리를 연결할 수 있도록 DFC에 그래프를 어디서 끊어야 하는지 알려줍니다.

종료 노드 찾기

DFC는 구문 분석 후 제안 사항을 출력합니다:

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

사용할 노드가 확실하지 않거나 사용자 지정 또는 덜 일반적인 아키텍처로 작업하는 경우 해당 노드 이름을 복사하십시오.

Link to this section3단계: 모델 스크립트 로드#

모델 스크립트(.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 레이어 이름(conv54, conv65, conv80)은 구문 분석 중에 DFC에 의해 할당되며 모델별로 다릅니다. 다른 모델 크기나 아키텍처를 컴파일하는 경우, DFC 출력에서 올바른 이름을 확인하거나 내보낸 그래프에서 .alls 지시어를 생성하십시오.

NMS_CONFIG 파일 또한 모델별로 다릅니다. 내보낸 모델과 정확히 일치하는 구성을 사용하십시오.

engine=cpu는 호스트 CPU의 HailoRT를 통해 NMS를 실행합니다. Hailo가 대상 하드웨어 및 SDK 버전에서 지원한다고 문서화한 모델/스크립트 조합에 대해서만 engine=nn_core를 사용하십시오.

애플리케이션 코드에서 NMS를 완전히 실행하려면 nms_postprocess 줄을 제거하십시오. 이렇게 하면 HEF가 그룹화된 NMS 탐지 대신 원시 탐지 헤드 텐서를 출력하므로 추론 파서를 업데이트해야 합니다.

Link to this section4단계: 보정 데이터셋 구축#

INT8 양자화에는 (N, imgsz, imgsz, 3) float32 배열로 쌓인 대표 이미지 세트가 필요합니다. 스크립트는 Ultralytics가 check_det_dataset을 통해 자동으로 다운로드하는 COCO128을 사용합니다.

보정을 위해 최소 64개의 이미지를 사용하십시오. 일반적으로 이미지가 많을수록 양자화 품질이 향상됩니다. 최상의 결과를 얻으려면 COCO128보다는 실제 배포 도메인의 이미지를 사용하십시오.

Link to this section5단계: 최적화 및 양자화#

runner.optimize(calibset)는 양자화 인식 미세 조정 및 레이어 노이즈 분석을 적용한 다음, runner.save_har(...)가 선택적 중간 체크포인트를 기록합니다. GPU를 강력히 권장하며, GPU가 없으면 이 단계는 몇 시간이 걸릴 수 있습니다.

Link to this section6단계: 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은 동일한 디커플링된 탐지 헤드를 공유합니다. 레이어 인덱스는 두 제품군 간에 1만큼 차이가 납니다:

모델 제품군탐지 헤드 레이어종료 노드 패턴
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 sectionHailo 하드웨어에서 추론 실행하기#

컴파일이 완료되면 전체 yolo11n_hailo_model/ 폴더(.hefmetadata.yaml 포함)를 Hailo 기반 장치로 복사하고, HailoRT Python API(hailo_platform 패키지) 또는 Raspberry Pi의 경우 picamera2 Hailo 헬퍼(HailoRT 래퍼)를 사용하여 추론을 실행하십시오. 두 방법 모두 아래 탭에 나와 있습니다. 두 파일을 함께 보관하면 아래 스크립트가 HEF 옆의 metadata.yaml에서 클래스 이름을 읽을 수 있습니다. DFC 내보내기 단계와 달리 추론은 에지 장치에서 직접 실행됩니다.

참고

아래 추론 코드는 컴파일에 사용된 x86 머신이 아니라 Hailo 기반 디바이스(예: Raspberry Pi + AI Kit)에서 실행됩니다.

Link to this section1단계: 디바이스에 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 Developer Zone에서 디바이스, 드라이버 및 SDK 버전에 맞는 HailoRT 패키지를 설치하십시오.

AI HAT+ 2 디바이스는 다른 Raspberry Pi 패키지(hailo-h10-all)와 Hailo-10H 워크플로우를 사용합니다. 해당 하드웨어 세대에 대해서는 Raspberry Pi AI 소프트웨어 가이드를 따르십시오.

Link to this section2단계: 빠른 상태 확인#

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 section3단계: 추론 실행하기#

아래 스크립트는 컴파일된 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 옵션을 사용하려면 최신 Raspberry Pi OS가 일반 V4L2 장치 대신 libcamera를 통해 카메라를 라우팅하므로 picamera2가 설치되어 있어야 합니다.

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 sectionTAPPAS를 이용한 비디오 추론#

고처리량 비디오 파이프라인의 경우, TAPPAS는 실시간으로 Hailo 칩을 통해 비디오를 스트리밍하는 GStreamer 요소를 제공합니다:

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 내보내기 대상은 관련 ONNX, OpenVINO, TensorRT, NCNN, TFLite Edge TPU, RKNN, Sony IMX500Qualcomm QNN 가이드를 참조하십시오. 형식 간의 내보낸 모델 속도와 정확도를 비교하려면 Benchmark mode를 사용하십시오. 전체 형식 및 옵션 목록은 Export mode 설명서와 통합 가이드 페이지를 방문하십시오.

Link to this sectionFAQ#

Link to this section어떤 Hailo 디바이스가 지원되나요?#

The Hailo DFC supports Hailo-8 (hailo8), Hailo-8L (hailo8l), and Hailo-15H (hailo15h). See the Supported Hardware Architectures table for the matching HW_ARCH value.

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()에서 양자화 인식 미세 조정(Quantization-aware fine-tuning)을 수행할 때 GPU 사용을 강력히 권장합니다. GPU 없이도 프로세스는 작동하지만 훨씬 느립니다(GPU 사용 시 약 10~20분인 반면, 그렇지 않을 경우 수 시간 소요).

Link to this section모델에 적합한 엔드 노드는 어떻게 찾나요?#

Run runner.translate_onnx_model(...) without specifying end_node_names, then use the suggested detection-head nodes printed by the DFC. See Other Architectures for the example command.

Link to this sectionHailo DFC SDK는 어디서 얻을 수 있나요?#

Hailo DFC SDK Python 휠은 Hailo Developer Zone에서 제공됩니다. 직접적인 Ultralytics Hailo 내보내기 도구의 경우, 모델 스크립트와 후처리 구성이 내보내기 워크플로 내에서 생성되거나 선택되어야 합니다.

댓글