بنقرة واحدة
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.