원클릭으로
hl-build-pipeline-app
Build a complete GStreamer pipeline app for real-time video processing on Hailo-8/8L/10H.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Build a complete GStreamer pipeline app for real-time video processing on Hailo-8/8L/10H.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Build an AI agent application with LLM tool calling for Hailo-10H.
Build an LLM chat application for Hailo-10H.
Build a GStreamer pipeline application for Hailo accelerators.
Build a standalone HailoRT inference application.
Build a Vision-Language Model application for Hailo-10H.
Build a voice assistant with Whisper STT and Piper TTS for Hailo-10H.
| name | hl-build-pipeline-app |
| description | Build a complete GStreamer pipeline app for real-time video processing on Hailo-8/8L/10H. |
Build a complete GStreamer pipeline app for real-time video processing on Hailo-8/8L/10H.
The canonical pipeline app is detection/. Other examples: pose_estimation/, instance_segmentation/, face_recognition/.
Do NOT read these source files. This SKILL.md contains all patterns needed to build any pipeline app. The sections below cover: basic pipelines, frame overlays, custom backgrounds, pose extraction, detection data, and subclassing existing pipeline classes.
Read this SKILL.md (full file, single read) + common_pitfalls.md. That's it. Build immediately.
Create the app directory:
hailo_apps/python/<type>/<app_name>/
├── app.yaml # App manifest (required)
├── run.sh # Launch wrapper
├── __init__.py
├── <app_name>.py # Main app
└── README.md # Usage documentation (REQUIRED — never skip)
Create app.yaml with type: pipeline and run.sh wrapper.
Do NOT register in defines.py or resources_config.yaml.
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst
import hailo # Required for detection/landmark extraction in callbacks
from hailo_apps.python.core.common.hailo_logger import get_logger
from hailo_apps.python.core.common.core import resolve_hef_path, handle_list_models_flag
from hailo_apps.python.core.common.parser import get_pipeline_parser
# If your app uses resolve_hef_path with an app name, register it in defines.py.
# Otherwise use a local string constant:
# APP_NAME = "my_pipeline_app"
from hailo_apps.python.core.gstreamer.gstreamer_app import GStreamerApp, app_callback_class
from hailo_apps.python.core.gstreamer.gstreamer_helper_pipelines import (
SOURCE_PIPELINE,
INFERENCE_PIPELINE,
INFERENCE_PIPELINE_WRAPPER,
DISPLAY_PIPELINE,
TRACKER_PIPELINE,
USER_CALLBACK_PIPELINE,
QUEUE,
)
from hailo_apps.python.core.common.buffer_utils import (
get_caps_from_pad,
get_numpy_from_buffer,
)
logger = get_logger(__name__)
APP_NAME = "my_pipeline_app"
class UserAppCallback(app_callback_class):
"""Custom callback class for per-frame state."""
def __init__(self):
super().__init__()
self.detection_count = 0
def app_callback(element, buffer, user_data):
"""Per-frame callback — runs on every GStreamer buffer."""
# Access detections from buffer
# user_data.detection_count += len(detections)
return Gst.FlowReturn.OK
class MyPipelineApp(GStreamerApp):
def __init__(self, app_callback, user_data, parser=None):
parser = parser or get_pipeline_parser()
handle_list_models_flag(parser, APP_NAME)
args = parser.parse_args()
super().__init__(args, user_data)
self.hef_path = resolve_hef_path(args.hef_path, APP_NAME, self.arch)
logger.info("HEF: %s", self.hef_path)
def get_pipeline_string(self):
return (
SOURCE_PIPELINE(self.video_source, self.arch)
+ " ! "
+ INFERENCE_PIPELINE(
hef_path=self.hef_path,
batch_size=self.batch_size,
)
+ " ! "
+ USER_CALLBACK_PIPELINE()
+ " ! "
+ DISPLAY_PIPELINE(video_sink=self.video_sink, sync=self.sync)
)
def main():
user_data = UserAppCallback()
app = MyPipelineApp(app_callback, user_data)
app.run()
if __name__ == "__main__":
main()
python3 .github/scripts/validate_app.py hailo_apps/python/pipeline_apps/my_pipeline_app --smoke-test
--input usb for USB cameras — the framework auto-detects the correct device. NEVER hardcode /dev/video0 — that is often the integrated webcam, not the USB camera. If you need a specific device, run v4l2-ctl --list-devices first.get_pipeline_parser() (NOT get_standalone_parser())SOURCE_PIPELINE, INFERENCE_PIPELINE, DISPLAY_PIPELINEapp_callback(element, buffer, user_data) — never call user_data.increment()INFERENCE_PIPELINE_WRAPPER for full-res displayTRACKER_PIPELINE() for ByteTrackCROPPER_PIPELINE() for crop → second modelQUEUE("vaapi_queue") + vaapi_convert_pipeline for HW decode| Pattern | Helper | Use Case |
|---|---|---|
| Basic inference | INFERENCE_PIPELINE(hef_path=...) | Single model |
| With tracking | + TRACKER_PIPELINE() | Object tracking |
| With user callback | + USER_CALLBACK_PIPELINE() | Per-frame processing |
| Cascaded | CROPPER_PIPELINE(...) | Face detection → recognition |
| Multi-source | Multiple SOURCE_PIPELINE + compositor | Dashboard view |
| Tiling | Custom tiling pipeline | Small object detection |
When you need to draw on frames (overlays, game graphics, custom visualizations), use the use_frame pattern:
class UserAppCallback(app_callback_class):
def __init__(self):
super().__init__()
self.use_frame = True # Enables frame access in callback
CRITICAL: Setting use_frame = True in the callback class alone is NOT enough when subclassing a pipeline class (e.g., GStreamerPoseEstimationApp). GStreamerApp.__init__() overwrites user_data.use_frame from the CLI default (False). You MUST also force it in the app class:
class MyApp(GStreamerPoseEstimationApp):
def __init__(self, app_callback, user_data, parser=None):
super().__init__(app_callback, user_data, parser)
self.options_menu.use_frame = True # starts display process
user_data.use_frame = True # enables frame extraction
Without this, set_frame() calls are silently ignored and only the raw camera feed is shown.
import cv2
import hailo
from hailo_apps.python.core.common.buffer_utils import get_caps_from_pad, get_numpy_from_buffer
def app_callback(element, buffer, user_data):
pad = element.get_static_pad("src")
format, width, height = get_caps_from_pad(pad)
frame = None
if user_data.use_frame and format and width and height:
# Signature: get_numpy_from_buffer(buffer, format, width, height)
# Returns RGB numpy array (H, W, 3)
frame = get_numpy_from_buffer(buffer, format, width, height)
if user_data.use_frame and frame is not None:
# Draw on the frame (frame is RGB from GStreamer)
cv2.circle(frame, (x, y), 10, (0, 255, 0), -1)
cv2.putText(frame, "Hello", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 255, 255), 2)
# CRITICAL: Convert RGB → BGR before set_frame (OpenCV expects BGR)
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
user_data.set_frame(frame)
return Gst.FlowReturn.OK
Key rules:
get_numpy_from_buffer(buffer, format, width, height) — NOT (buffer, pad, format)cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) before set_frame()user_data.use_frame and that frame is not None--use-frame on CLI to enable (or set self.use_frame = True in callback class)When the user wants a custom background image (not the live camera feed), use background.copy() — never blend the camera feed with the background via addWeighted or similar.
# ✅ CORRECT — background only, no camera feed visible
if self.background is not None:
output = self.background.copy()
else:
output = np.zeros_like(frame)
# Draw game elements on output...
# Draw hand/body markers from pose data on output...
output = cv2.cvtColor(output, cv2.COLOR_RGB2BGR)
user_data.set_frame(output)
# ❌ WRONG — blends camera feed, user sees themselves + background = confusing
output = cv2.addWeighted(self.background, 0.4, frame, 0.6, 0)
Rule: If the user provides a background image or asks for a virtual scene, the camera feed is used only for pose/detection data extraction — it must NOT appear in the rendered output. The frame from get_numpy_from_buffer() is still needed to extract detections, but the display output should be background.copy() with game elements drawn on top.
import hailo
def app_callback(element, buffer, user_data):
roi = hailo.get_roi_from_buffer(buffer)
detections = roi.get_objects_typed(hailo.HAILO_DETECTION)
for detection in detections:
label = detection.get_label() # e.g., "person", "car"
confidence = detection.get_confidence() # 0.0 - 1.0
bbox = detection.get_bbox() # Normalized bounding box
# bbox.xmin(), bbox.ymin(), bbox.width(), bbox.height() — all normalized [0,1]
track_id = 0
track = detection.get_objects_typed(hailo.HAILO_UNIQUE_ID)
if len(track) == 1:
track_id = track[0].get_id()
landmarks = detection.get_objects_typed(hailo.HAILO_LANDMARKS)
if landmarks:
points = landmarks[0].get_points()
# Each point has .x() and .y() — normalized to bounding box
# Convert to pixel coordinates:
for point in points:
pixel_x = int((point.x() * bbox.width() + bbox.xmin()) * frame_width)
pixel_y = int((point.y() * bbox.height() + bbox.ymin()) * frame_height)
| Type | Constant | Method | Returns |
|---|---|---|---|
| Detection boxes | hailo.HAILO_DETECTION | roi.get_objects_typed() | List of detections |
| Track IDs | hailo.HAILO_UNIQUE_ID | detection.get_objects_typed() | List with single ID object |
| Pose landmarks | hailo.HAILO_LANDMARKS | detection.get_objects_typed() | List of landmark sets |
| Classification | hailo.HAILO_CLASSIFICATION | detection.get_objects_typed() | List of classifications |
| Masks | hailo.HAILO_CONF_CLASS_MASK | detection.get_objects_typed() | Segmentation masks |
For apps that extend existing pipelines (e.g., a pose estimation game), subclass the domain-specific pipeline class instead of the base GStreamerApp:
from hailo_apps.python.pipeline_apps.pose_estimation.pose_estimation_pipeline import (
GStreamerPoseEstimationApp,
)
class MyPoseGame(GStreamerPoseEstimationApp):
"""Inherits full pose pipeline: SOURCE → INFERENCE → TRACKER → USER_CALLBACK → DISPLAY"""
pass # Pipeline is already configured — just write your callback
def app_callback(element, buffer, user_data):
# All pose detection data is available here
...
def main():
user_data = UserAppCallback()
app = MyPoseGame(app_callback, user_data) # No pipeline config needed
app.run()
Available pipeline classes to subclass:
| Class | Module | Pipeline includes |
|---|---|---|
GStreamerPoseEstimationApp | pose_estimation.pose_estimation_pipeline | Inference + Tracker + User Callback |
GStreamerDetectionApp | detection.detection_pipeline | Inference + User Callback |
GStreamerInstanceSegmentationApp | instance_segmentation.instance_segmentation_pipeline | Inference + User Callback |
GStreamerFaceRecognitionApp | face_recognition.face_recognition_pipeline | Cascaded inference + Tracker |
When subclassing, you get the full pipeline for free — just provide your custom app_callback and app_callback_class.