User Guide 3: Simplifying Object Detection on a Hailo Device Using DeGirum PySDK

Simplifying Object Detection on a Hailo Device Using DeGirum PySDK

In this guide, we illustrate how to leverage the built-in features of PySDK to simplify running object detection models on Hailo devices with just a few lines of code. We recommend reviewing User Guide 2 before proceeding. By the end of this guide, you’ll understand how to integrate any precompiled object detection model (with Hailo NMS postprocessing) with DeGirum PySDK and adapt the process to your needs.


Overview of the Inference Pipeline

The overall inference pipeline in PySDK can be summarized as follows:

  1. Pre-Processing: Prepare the input image by resizing and formatting it (e.g., applying letterboxing) to meet model requirements before inference.
  2. Inference: Run the precompiled model (e.g., .hef file) on the Hailo device.
  3. Post-Processing: Convert raw model outputs into human-readable detections (bounding boxes, labels, etc.) using a post-processor class.
  4. Visualization: Overlay the detection results onto the original image for easy inspection.

A diagram of this flow looks like:

Input Image
    │
    ▼
Pre-Processing (resize, quantize, etc.)
    │
    ▼
Model Inference (Hailo device using .hef file)
    │
    ▼
Post-Processing (custom Python class: PostProcessor)
    │
    ▼
Detection Results (bounding boxes, labels, confidence scores)
    │
    ▼
Visualization (image overlay using OpenCV)

What You’ll Need to Begin

To follow this guide, you’ll need a machine equipped with a Hailo AI accelerator (Hailo8 or Hailo8L). The host CPU can be an x86 system or an Arm-based system (e.g., Raspberry Pi). Ensure that all necessary drivers and software tools are correctly installed by following the Hailo + PySDK setup instructions.

Here’s what else you’ll need:

  1. A Model File (.hef):
    For this guide, we use the yolov11n detection model. The model is available for Hailo8 devices at Hailo8 Object Detection Models.

  2. An Input Image:
    The image you want to process. For instance, you can download this Cat Image.

  3. A Labels File (labels_coco.json):
    This file maps class indices to human-readable labels for the 80 classes of the COCO dataset. You can download it from Hugging Face.

Download these assets and keep them handy, as you will use them throughout this guide.


Summary

In this guide, you will learn to:

  • Configure the Model JSON File: Set up a JSON file that defines key configurations for pre-processing, model parameters, and post-processing.
  • Define the Post-Processor Class: Develop a post-processor class (named PostProcessor as required by PySDK) to process model outputs.
  • Prepare the Model Zoo: Organize model files (the .json configuration, .hef file, Python post-processor file, and the labels file) for seamless integration with PySDK.
  • Run Inference: Write Python code to load the model, run inference, and print the detection results.
  • Visualize the Output: Use the image_overlay method to visualize the detection results.

Configuring the Model JSON File

Below is the JSON file that leverages PySDK’s built-in pre-processor and post-processor features.

Model JSON (yolov11n.json)

{
    "ConfigVersion": 10,
    "DEVICE": [
        {
            "DeviceType": "HAILO8",
            "RuntimeAgent": "HAILORT",
            "SupportedDeviceTypes": "HAILORT/HAILO8"
        }
    ],
    "PRE_PROCESS": [
        {
            "InputType": "Image",
            "InputN": 1,
            "InputH": 640,
            "InputW": 640,
            "InputC": 3,
            "InputPadMethod": "letterbox",
            "InputResizeMethod": "bilinear",                
            "InputQuantEn": true
        }
    ],
    "MODEL_PARAMETERS": [
        {
            "ModelPath": "yolov11n.hef"
        }
    ],
    "POST_PROCESS": [
        {
            "OutputPostprocessType": "Detection",
            "PythonFile": "HailoDetectionYolo.py",
            "OutputNumClasses": 80,
            "LabelsPath": "labels_coco.json",
            "OutputConfThreshold": 0.3           
        }
    ]
}

Key Entries

  • Pre-Processing Section:
    Specifies that the input is an image that must be resized to 1 x 640 x 640 x 3 using letterboxing (to preserve aspect ratio) and that the model input is quantized to UINT8.

  • Post-Processing Section:
    Indicates that the model output will be post-processed using the Python code in HailoDetectionYolo.py. Additional parameters like OutputNumClasses, OutputConfThreshold, and LabelsPath are provided to fine-tune the detection results.


Defining the Post-Processor Class

The PySDK requires a post-processor class to be named PostProcessor in the module for automatic detection. This class reads the JSON-based configuration, processes the raw outputs from the model, and formats the outputs into a list of detections (bounding boxes, labels, etc.).

Post-Processor Code (HailoDetectionYolo.py)

import numpy as np
import json

# Post-processor class. Note: It must be named 'PostProcessor' for PySDK to detect and invoke it.
class PostProcessor:
    def __init__(self, json_config):
        """
        Initialize the post-processor with configuration settings.

        Parameters:
            json_config (str): JSON string containing post-processing configuration.
        """
        # Parse the JSON configuration
        self._json_config = json.loads(json_config)

        # Extract configuration parameters
        self._num_classes = int(self._json_config["POST_PROCESS"][0]["OutputNumClasses"])
        self._label_json_path = self._json_config["POST_PROCESS"][0]["LabelsPath"]
        self._input_height = int(self._json_config["PRE_PROCESS"][0]["InputH"])
        self._input_width = int(self._json_config["PRE_PROCESS"][0]["InputW"])

        # Load label dictionary from JSON file
        with open(self._label_json_path, "r") as json_file:
            self._label_dictionary = json.load(json_file)

        # Extract confidence threshold; defaults to 0.0 if not specified
        self._output_conf_threshold = float(
            self._json_config["POST_PROCESS"][0].get("OutputConfThreshold", 0.0)
        )

    def forward(self, tensor_list, details_list):
        """
        Process the raw output tensor to produce formatted detection results.

        Parameters:
            tensor_list (list): List of output tensors from the model.
            details_list (list): Additional details (e.g., quantization info); not used in this example.

        Returns:
            list: A list of dictionaries, each representing a detection result.
        """
        # Initialize a list to store detection results
        new_inference_results = []

        # The first tensor is assumed to contain all detection data.
        # Reshape it to a 1D array for easier processing.
        output_array = tensor_list[0].reshape(-1)

        index = 0  # Track current position in output_array

        # Iterate over each class
        for class_id in range(self._num_classes):
            # Read the number of detections for the current class
            num_detections = int(output_array[index])
            index += 1

            if num_detections == 0:
                # No detections for this class; move to the next one.
                continue

            # Process each detection for the current class
            for _ in range(num_detections):
                # Ensure there are enough elements for a complete detection record (4 bbox coordinates + score)
                if index + 5 > len(output_array):
                    break

                # Extract bounding box coordinates and the confidence score.
                # The format is assumed to be: [y_min, x_min, y_max, x_max, score]
                y_min, x_min, y_max, x_max = map(float, output_array[index : index + 4])
                score = float(output_array[index + 4])
                index += 5

                # Apply confidence threshold; skip detection if below threshold
                if score < self._output_conf_threshold:
                    continue

                # Convert normalized coordinates to absolute pixel values based on input dimensions
                x_min_abs = x_min * self._input_width
                y_min_abs = y_min * self._input_height
                x_max_abs = x_max * self._input_width
                y_max_abs = y_max * self._input_height

                # Build the detection result dictionary
                result = {
                    "bbox": [x_min_abs, y_min_abs, x_max_abs, y_max_abs],
                    "score": score,
                    "category_id": class_id,
                    "label": self._label_dictionary.get(str(class_id), f"class_{class_id}"),
                }
                new_inference_results.append(result)

            # If the remainder of the output_array is nearly or fully consumed, exit early.
            if index >= len(output_array) or all(v == 0 for v in output_array[index:]):
                break

        # Return the list of formatted detection results.
        return new_inference_results

Explanation of Key Sections

  1. Class Definition & Naming

    • The class is named PostProcessor because PySDK requires this exact name to automatically detect and invoke the post-processing logic.
  2. Initialization (__init__)

    • The JSON configuration string is parsed to extract model parameters such as the number of classes (OutputNumClasses), the path to the labels file (LabelsPath), input dimensions (InputH and InputW), and the confidence threshold (OutputConfThreshold).
    • The labels file is loaded to map class IDs to human-readable labels.
  3. Forward Method (forward)

    • This method processes the raw tensor output by iterating through each class and its corresponding detections.
    • It applies the confidence threshold, converts normalized bounding box coordinates to absolute pixel values, and assembles each detection into a dictionary.
    • Detailed inline comments explain the purpose of key code sections, aiding in future customization or debugging.

Preparing the Model Zoo

A model zoo is a structured repository of model assets (configuration JSON files, model files, post-processor code, and labels) that simplifies model management. To organize your assets:

  1. Save the above JSON configuration as yolov11n.json.
  2. Place the corresponding yolov11n.hef file in the same directory.
  3. Save the post-processor code in a file named HailoDetectionYolo.py.
  4. Save the labels file as labels_coco.json.

Tip: For easier maintenance, you can organize models into separate subdirectories. PySDK will automatically search for model JSON files in all subdirectories inside the directory specified by the zoo_url.


Running Inference

The yolov11n model, configured by the above JSON file, takes an image as input and outputs a list of dictionaries, each containing a bounding box, confidence score, class ID, and label. The results are scaled to the original image size.

import degirum as dg
from pprint import pprint

# Load the model from the model zoo.
# Replace '<path_to_model_zoo>' with the directory containing your model assets.
model = dg.load_model(
    model_name='yolov11n',
    inference_host_address='@local',
    zoo_url='<path_to_model_zoo>'
)

# Run inference on the input image.
# Replace '<path_to_cat_image>' with the actual path to your cat image.
inference_result = model('<path_to_cat_image>')

# Pretty print the detection results.
pprint(inference_result.results)

Expected output (example):

[{'bbox': [254.35779146128206,
           94.76393992199104,
           899.6128147829603,
           707.9610544375571],
  'category_id': 15,
  'label': 'cat',
  'score': 0.8902369737625122}]

Visualizing the Output

PySDK supports automatic visualization of inference results. The returned inference_result object includes an image_overlay method that overlays bounding boxes and labels on the input image. The code below shows how to use OpenCV for visualization:

import cv2

# Display the image with overlayed detection results.
cv2.imshow("AI Inference", inference_result.image_overlay)

# Wait for the user to press 'x' or 'q' to exit.
while True:
    key = cv2.waitKey(0) & 0xFF  # Wait indefinitely until a key is pressed.
    if key == ord('x') or key == ord('q'):
        break
cv2.destroyAllWindows()  # Close all OpenCV windows.

Note: The cv2.waitKey(0) function waits indefinitely for a key press, which is useful for pausing the display until the user is ready to close it.

An example of the output is shown below:


Troubleshooting and Debug Tips

  • File Naming and Paths:
    Ensure that file names (yolov11n.json, yolov11n.hef, HailoDetectionYolo.py, and labels_coco.json) are consistent and that the paths specified in the JSON file are correct.

  • Configuration Mismatches:
    Verify that the input dimensions and quantization settings in the JSON file match your model’s requirements.


Conclusion

This guide demonstrated how PySDK simplifies running object detection models on Hailo devices by integrating built-in pre-processing, post-processing, and visualization features. Although we used the yolov11n model as an example, the outlined method applies to other object detection models that utilize built-in NMS post-processing on Hailo devices.

Below is a list of supported models for reference:

nanodet_repvgg.hef nanodet_repvgg_a12.hef nanodet_repvgg_a1_640.hef
yolov10b.hef yolov10n.hef yolov10s.hef
yolov10x.hef yolov11l.hef yolov11m.hef
yolov11n.hef yolov11s.hef yolov11x.hef
yolov5m.hef yolov5m_6.1.hef yolov5m6_6.1.hef
yolov5m_wo_spp.hef yolov5s.hef yolov5s_c3tr.hef
yolov5s_wo_spp.hef yolov5xs_wo_spp.hef yolov5xs_wo_spp_nms_core.hef
yolov6n.hef yolov6n_0.2.1_nms_core.hef yolov7.hef
yolov7_tiny.hef yolov7e6.hef yolov8l.hef
yolov8m.hef yolov8n.hef yolov8s.hef
yolov8x.hef yolov9c.hef yolox_l_leaky.hef
yolox_s_leaky.hef yolox_s_wide_leaky.hef yolox_tiny.hef

The same post-processor work for the 5 models below as well but the efficientdet models have 89 output classes and ssd_mobilenet models have 90 output classes. For these models, the appropriate labels file should be used. These models are listed below:

model name number of classes
efficientdet_lite0 89
efficientdet_lite1 89
efficientdet_lite2 89
ssd_mobilenet_v1 90
ssd_mobilenet_v2 90

The above lists together cover 41 of the 56 models available in the object detection model zoo. The remaining 15 models listed below require their own post-processors. We will cover them in later user guides.

centernet_resnet_v1_18_postprocess centernet_resnet_v1_50_postprocess damoyolo_tinynasL20_T
damoyolo_tinynasL25_S damoyolo_tinynasL35_M detr_resnet_v1_18_bn
detr_resnet_v1_50 tiny_yolov3 tiny_yolov4
yolov3 yolov3_416 yolov3_gluon
yolov3_gluon_416 yolov4_leaky yolov6n_0.2.1

Custom Models

The method described in this guide is not limited to precompiled models from the Hailo model zoo. It works equally well with custom models. If you plan to deploy your own object detection models, make sure to adjust the JSON configuration with the correct values. In particular, update the OutputNumClasses field to match the number of classes your model detects and provide an appropriate labels file in the LabelsPath field. This ensures that the post-processor correctly interprets the raw output and maps class indices to human-readable labels.

1 Like

Hello, I enjoyed your guide and thank you so much. What I’m curious about while setting the path is that if I put necessary files such as json and hef model in the source/assets directory, does this assets directory correspond to model_name and does the path to the source correspond to zoo_url? I keep getting errors in the path.

Hi @kim_jiseob
The name of the model json file without .json extension is the model name and the directory that has all the files is the zoo_url. Hope this helps.

Thank you for your reply. Then, I run the code in a directory called basic_pipeline, and within this directory, I create a directory called yolov11n and put yolov11n.hef, yolov11n.json, HailoDetectionYolo.py, and labels_coco.json in it. So, is this yolov11n directory the model zoo? Then model_name = “yolov11n” and zoo_url is the path to basic_pipeline?

Could it be that the path recognition error occurs because the hailoRT version is not 4.19.0 or is it related to permissions?

Hi @kim_jiseob
If your files are in \home\something\basic_pipeline\yolo11n then zoo_url=\home\something\basic_pipeline\yolo11n and model name is yolo11n. Please note that zoo_url should not have \ after yolo11n.

Thank you for your reply. We will solve your problem. This is a different question, but the reason I’m trying this guide is to calculate the mAP of the hef model by inferring 100 images, saving the results as json, and then using pycocotools. However, pycocotools is downloaded together with the degirum library. Is there a code to calculate this mAP or a command to run in the terminal? As always, thank you for your answers.

Hi @kim_jiseob
Yes, we have code to support model evaluation that we use internally. I will ask my team to provide an example as we do not have full documentation yet. Also, can you confirm if the previous issue is solved?

Thanks @shashi Now i understand how to set up the path, i will solve the problem.

@shashi. Are you going to upload your model evaluation example here? or another post? and my issue is solved. thanks.

Hi @kim_jiseob
Here is the code snippet for model evaluation using degirum_tools. You just need to change img_folder_path, anno_json and of course the proper hw_location and model parameters (zoo_name and model_name).

import degirum as dg
import degirum_tools as dg_tools
from degirum_tools.detection_eval import ObjectDetectionModelEvaluator

hw_location = dg.CLOUD

zoo_name = 'models_hailort'

model_names = {
    "yolo11n_silu_coco--640x640_quant_hailort_hailo8l_1",
}

img_folder_path = "/data/ml-data/ultralytics_datasets/coco/images/val/val2017/"
anno_json = "/data/ml-data/ultralytics_datasets/coco/annotations/instances_val2017.json"

# COCO91to80
classmap = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19,
            20, 21, 22, 23, 24, 25, 27, 28, 31, 32, 33, 34, 35, 36, 37, 38,
            39, 40, 41, 42, 43, 44, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
            56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 67, 70, 72, 73, 74, 75, 
            76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 89, 90]


for model_name in model_names:
    print(model_name)

    model = dg.load_model(model_name, hw_location, "https://hub.degirum.com/degirum/" + zoo_name, dg_tools.get_token())

    model.output_confidence_threshold =  0.001
    model.output_nms_threshold = 0.7
    model.output_max_detections = 300
    model.output_max_detections_per_class = 300
    model.input_letterbox_fill_color = (114, 114, 114)

    map_evaluator = ObjectDetectionModelEvaluator(model, output_max_detections=300, classmap=classmap, show_progress=True, kpt_sigmas=sigmas)#pred_path='predictions.json')

    map_results = map_evaluator.evaluate(
        img_folder_path,
        ground_truth_annotations_path=anno_json,
    )

    # [mAP50:95, mAP50, mAP75, mAP50:95 small, mAP50:95 medium, mAP50:95 large, mAR50:95 maxDet=1, mAR50:95 maxDet=10, mAR50:95 maxDet=100, mAR50:95 small maxDet=100, mAR50:95 medium maxDet=100, mAR50:95 large maxDet=100]
    print(map_results)

thank you @shashi. I modified the token and removed sigmas, which caused a problem, and ran it, and the following error occurred. yolov8n
free(): invalid next size (fast).0s elapsed, 0.0 FPS]

Process ended with exit code -6.

The code I ran is as follows:
import degirum as dg
import degirum_tools as dg_tools
from degirum_tools.detection_eval import ObjectDetectionModelEvaluator

hw_location = “@local

token = ‘’

zoo_url = ‘/home/dteam59/hailo_rpi5_examples/pipelines/yolov8n’

model_names = {
“yolov8n”,
}

img_folder_path = “/home/dteam59/hailo_rpi5_examples/resources/detect_test_coco”
anno_json = “/home/dteam59/hailo_rpi5_examples/resources/detect_test_coco/detect_ground_truth.coco.json”

COCO91to80

classmap = [0]

for model_name in model_names:
print(model_name)

model = dg.load_model(model_name, hw_location, zoo_url, token)

model.output_confidence_threshold =  0.001
model.output_nms_threshold = 0.7
model.output_max_detections = 300
model.output_max_detections_per_class = 300
model.input_letterbox_fill_color = (114, 114, 114)

map_evaluator = ObjectDetectionModelEvaluator(model, output_max_detections=300, classmap=classmap, show_progress=True)#pred_path='predictions.json')

map_results = map_evaluator.evaluate(
    img_folder_path,
    ground_truth_annotations_path=anno_json,
)

# [mAP50:95, mAP50, mAP75, mAP50:95 small, mAP50:95 medium, mAP50:95 large, mAR50:95 maxDet=1, mAR50:95 maxDet=10, mAR50:95 maxDet=100, mAR50:95 small maxDet=100, mAR50:95 medium maxDet=100, mAR50:95 large maxDet=100]
print(map_results)

The version of my hailoRT is 4.20.0. Is this by any chance a problem?

Hi @kim_jiseob
Can you please copy+paste the full error message. It is not clear to us where the error message is coming from. There were some changes in 4.20.0 that broke compatibility with PySDK but we need to see the full error message to be sure. Before running evaluation, did you check on a couple of images to see if the model works?

Hello @shashi
This is the output when I modified the code that calculates mAP to my custom model and ran it.

yolov8n
corrupted size vs. prev_size0 [0.0s elapsed, 0.0 FPS]

Process ended with exit code -6.

When I analyzed this error using GDB, it was as follows.

gdb) bt
#0 __pthread_kill_implementation (threadid=140732765368704,
signo=signo@entry=6, no_tid=no_tid@entry=0) at ./nptl/pthread_kill.c:44
#1 0x00007fff204c0aa4 in __pthread_kill_internal (signo=6,
threadid=) at ./nptl/pthread_kill.c:78
#2 0x00007fff2047a72c in __GI_raise (sig=sig@entry=6)
at …/sysdeps/posix/raise.c:26
#3 0x00007fff2046747c in __GI_abort () at ./stdlib/abort.c:79
#4 0x00007fff204b4aac in __libc_message (action=action@entry=do_abort,
fmt=fmt@entry=0x7fff20596d28 “%s\n”) at …/sysdeps/posix/libc_fatal.c:155
#5 0x00007fff204caeac in malloc_printerr (
str=str@entry=0x7fff20591fe8 “free(): invalid next size (fast)”)
at ./malloc/malloc.c:5660
#6 0x00007fff204ccca0 in _int_free (av=0x7ffed0000030,
p=p@entry=0x7ffed026f610, have_lock=have_lock@entry=0)
at ./malloc/malloc.c:4518
#7 0x00007fff204cf76c in __GI___libc_free (mem=)
at ./malloc/malloc.c:3385
#8 0x00007ffefe9e1e58 in std::vector<DG::BasicTensor, std::allocatorDG::BasicTensor >::~vector() ()
from /home/dteam59/degirum_env/lib/python3.11/site-packages/degirum/CoreClient.cpython-311-aarch64-linux-gnu.so
#9 0x00007ffefeaf6b00 in DG::CorePipelineProcessorDGFrame::run() ()
from /home/dteam59/degirum_env/lib/python3.11/site-packages/degirum/CoreClien–Type for more, q to quit, c to continue without paging–

I’m wondering if it’s possible to change 4.20.0 to 4.19.0 as the cause may be a version issue with hailoRT.

Hi @kim_jiseob
Yes, you can change hailort back to 4.19.0 and check if the error is gone. Nothing needs to be done on PySDK side.

1 Like

[quote=“shashi, post:1, topic:10711”]
Hailo + PySDK setup instructions
[/quote] yes.
I followed this guide to create a virtual environment and downloaded the package with pip install degirum_cli. So, is it correct for hailoRT to set the version to 4.19.0 when downloading degirum_cli? Or is it right to change it from the current state?

HI @kim_jiseob
PySDK installation does not install HailoRT. It just picks up the HailoRT currently istalled on your system. Currently PySDK supports 4.19 and 4.20. SO if your system contains 4.19 it will pick it up. You can check for the version after your install PySDK by typing degirum sys-info in command line.

Ah, thank you. However, when I changed hailort to 4.19.0, the error details are as follows.
(degirum_env) dteam59@DTeam59:~ $ hailortcli fw-control identify
[HailoRT] [error] CHECK failed - Driver version (4.20.0) is different from library version (4.19.0)
[HailoRT] [error] Driver version mismatch, status HAILO_INVALID_DRIVER_VERSION(76)
[HailoRT] [error] CHECK_SUCCESS failed with status=HAILO_INVALID_DRIVER_VERSION(76)
[HailoRT] [error] CHECK_SUCCESS failed with status=HAILO_INVALID_DRIVER_VERSION(76)
[HailoRT] [error] CHECK_SUCCESS failed with status=HAILO_INVALID_DRIVER_VERSION(76)
[HailoRT CLI] [error] CHECK_SUCCESS failed with status=HAILO_INVALID_DRIVER_VERSION(76)

I think I need to match the driver version, but I don’t know what command to use.

Hi @kim_jiseob
Someone from Hailo can help. Maybe a fresh install of Hailo tools.

Ah, I ran the code that calculates mAP on cloud, and the value came out fine, without any errors. I don’t think it was a version issue. However, when I run it locally, the model path is not recognized.