Default to option 1 unless the user explicitly says otherwise.
-
Always inspect the HEF first with hailortcli parse-hef. Run this on every HEF — H15H and H15L — before doing any source edits:
hailortcli parse-hef <path-to-hef>
Check the Input line. The media-library pipeline feeds NV12 frames into the AI stage, so the HEF input must be:
Input <name> UINT8, NV12(<H/2>x<W>x3)
- H15L HEFs from the zoo ship as NV12 — they drop in as-is.
- All H15H HEFs from the zoo ship as
NHWC(HxWx3) (not NV12). They will not drop into a media-library pipeline without a format conversion. Flag this to the user before continuing — the pipeline needs an extra NV12→RGB/NHWC conversion stage, otherwise the AI stage will reject the frames.
Also note the input resolution and output tensor name from parse-hef — they feed steps 1 and 2 below (postprocess function selection + tiling/resolution sanity check).
-
Find the postprocess function name. Each model has its own entry point in the matching postprocess .so:
- Detection (yolo family):
hailo-postprocess/postprocesses/detection/yolo_hailortpp.cpp → hailo_yolov8n, hailo_yolov8s, hailo_yolov8m, yolov8n_personface, yolov5, …
- Face landmarks:
facial_landmarks_nv12 and similar in the landmarks postprocess source.
- Segmentation (privacy mask):
linknet_post, etc.
- OCR / LPR: the LPR postprocess.
The function name must match the HEF's tensor naming — e.g. hailo_yolov8n reads tensor hailo_yolov8n_384_640/yolov8_nms_postprocess. Mismatched function/HEF will silently produce no output (no detections, no landmarks, blank masks, …) with no error.
-
Find the JSON config (if the task uses one). Detection-family tasks read JSONs from /home/root/apps/webserver/resources/configs/ (yolov5.json, yolov8n.json, yolov5_personface.json, …). The JSON's detection_threshold and max_boxes are honored; iou_threshold, output_activation, label_offset are loaded but ignored by hailo_yolov* (hardcoded label maps in the headers). For yolov5*, the JSON's anchors array is required.
Some tasks have no JSON — LANDMARKS_POST_CONF and SEGMENTATION_POST_CONF are empty strings by default. For those, leave post_config.config_path unset (don't assign anything to it).
-
Edit the app's main.cpp (option 1). Find the generate_<task>_pipeline(...) call in create_pipeline() and add a <task>_config_t override before it. The pattern is the same across tasks — only the type name and the field path change.
Detection (tiled), e.g. yolov8s → yolov8n:
hailo_analytics::analytics::tiling::tiling_detection_config_t tiling_user_cfg;
tiling_user_cfg.detection_config.ai_config.hef_path = "<hef path on board>";
tiling_user_cfg.detection_config.post_config.function_name = "<postprocess function>";
tiling_user_cfg.detection_config.post_config.config_path = "<json path on board>";
auto tiling_pipeline_status = ...generate_tiling_detection_pipeline(TILING_PIPELINE, tiling_user_cfg);
Detection (non-tiled): same pattern, but detection::detection_config_t and generate_detection_pipeline(...).
Face landmarks (only the landmarks stage):
hailo_analytics::analytics::face_landmarks::face_landmarks_config_t lm_cfg;
lm_cfg.ai_config.hef_path = "<hef path on board>";
lm_cfg.post_config.function_name = "<postprocess function>";
auto status = ...generate_face_landmarks_pipeline(LANDMARKS_PIPELINE, lm_cfg);
Face landmarks (whole bbox-crop+landmarks bundle): use bbox_crop_landmarks_config_t and assign into .landmarks_config.ai_config.hef_path etc.
Segmentation / dynamic privacy mask: segmentation_config_t or bbox_crop_segmentation_config_t, same field path (.ai_config.hef_path, .post_config.function_name).
OCR / LPR: ocr_config_t or bbox_crop_ocr_config_t.
The *_config_t fields are std::optional<std::string> — assign the bare string and the optional engages.
-
Cross-compile. Hand off to /cross-compile with the modified main.cpp.
-
Deploy. Hand off to /deploy. Only the binary needs replacement — the HEF and JSON usually already exist on the board image.
-
Verify. Run the app for ~10s and confirm no errors at startup. To confirm the AI stage actually produces output, subscribe to the ZMQ port (tcp://<board-ip>:7000 by default) with the analytic_viewer tool and check the relevant payload (detections, landmarks, segmentation mask, OCR text, …).
When this skill's work is complete, ask the user (AskUserQuestion) whether they want to run the app now and see it live. If yes, invoke the /run-app skill.