Task 6 ยท AI Inference

Object Detection & AI Inference

Real-time face coverage detection using YOLOv8n on the Hailo AI HAT+ NPU โ€” from dataset collection through model training, format conversion, and live inference at 20 FPS.

๐Ÿ‘ค Lead: Nauman Iftikhar โœ“ Complete YOLOv8n ยท Hailo AI HAT+ ยท ZMQ ยท Roboflow

Overview

The goal of Task 6 is to run real-time AI object detection on the cluster. EdgeGuard detects face coverage as a threat indicator โ€” a person with a covered face (mask, hood, scarf covering features) triggers a suspicious alert, while a visible face is classified as safe. This is a binary classification per detected person in the camera frame.

The system consists of a Raspberry Pi 4 with an AI Camera Module capturing video, streaming it via ZMQ to the Raspberry Pi 5 which runs inference on the Hailo AI HAT+ neural processing unit. Inference results feed into the alert pipeline โ€” suspicious detections are saved with a snapshot, stored in PostgreSQL, and pushed to Telegram.

Hardware

Hailo AI HAT+ (on Pi 5)

The Hailo AI HAT+ is an M.2 AI accelerator that sits on top of the Raspberry Pi 5. It contains the Hailo-8L NPU (Neural Processing Unit) with 13 TOPS (Tera Operations Per Second) of AI compute โ€” dedicated hardware designed specifically for running neural networks efficiently.

The key advantage over running on the Pi 5 CPU: the NPU handles matrix multiplications (the core operation in neural networks) in dedicated silicon at orders of magnitude higher speed and lower power than a general-purpose CPU core.

13 TOPS
Hailo-8L NPU compute
14โ€“17ms
Inference per frame on Hailo
2โ€“3 min
Same inference on Pi 5 CPU
~8000ร—
Speedup: NPU vs CPU

Raspberry Pi AI Camera (on Pi 4)

The Raspberry Pi AI Camera Module contains the IMX500 sensor โ€” a Sony imaging sensor with an onboard ISP. The Pi 4 captures 1080p video from this camera and streams frames to Pi 5 for inference. The camera runs on a dedicated Pi 4 (sensor-node, 10.10.10.40) to keep camera capture isolated from inference workload.

โ„น๏ธ Why a dedicated Pi 4 for the camera?

Running both camera capture and AI inference on the same Pi 5 caused frame drops due to CPU contention. Separating them โ€” Pi 4 captures, Pi 5 infers โ€” gave clean 20 FPS at 1080p with no dropped frames.

Dataset & Model Training

Dataset

We used Roboflow Universe as the primary data source โ€” a public repository of annotated computer vision datasets. We found existing datasets for face mask detection and combined them with custom raw images captured by our own camera setup.

All data was processed and augmented inside Roboflow's online platform before export:

textDataset augmentation applied in Roboflow
Classes:       face_visible, face_covered
Augmentations: Horizontal flip
               Rotation (ยฑ15ยฐ)
               Brightness variation (ยฑ25%)
               Blur (up to 1.5px)
               Mosaic augmentation
Split:         Train 80% / Validation 15% / Test 5%

Training โ€” YOLOv8n

We chose YOLOv8n (nano variant) from Ultralytics. The "n" (nano) variant was specifically chosen because it is the smallest and fastest YOLOv8 model โ€” optimized for edge devices with limited compute. It trades some accuracy for significantly lower inference time, which is the right tradeoff for real-time surveillance at 20 FPS.

Training was done in Google Colab using the free GPU tier (T4 GPU). Output: a best.pt PyTorch weights file.

pythonTraining command (Google Colab)
from ultralytics import YOLO

model = YOLO('yolov8n.pt')  # start from pretrained nano weights

model.train(
    data='dataset.yaml',     # Roboflow exported dataset config
    epochs=100,
    imgsz=640,
    batch=16,
    device='cuda'            # Colab T4 GPU
)
# Output: runs/detect/train/weights/best.pt

Model Evaluation

After training, we evaluated the model on the held-out test set to measure how well it detects face coverage. The key metrics for object detection are:

Training Configuration

textTraining run summary
Hardware:    Google Colab โ€” Tesla T4 GPU (14.9GB VRAM)
Framework:   Ultralytics YOLOv8 8.4.63 ยท PyTorch 2.11 ยท CUDA 12.8
Model:       YOLOv8n (nano) โ€” 73 layers ยท 3,006,038 parameters ยท 8.1 GFLOPs
Epochs:      20
Image size:  640ร—640
Batch size:  16
Dataset:     326 validation images ยท 1,018 instances
Classes:     face_covered ยท non_covered_face
Training time: 0.300 hours (~18 minutes) on T4
Output:      best.pt โ€” 6.2MB

Evaluation Results โ€” best.pt (Final Model)

MetricWhat it measuresAll classesface_coverednon_covered_face
mAP@0.5 Mean Average Precision at IoU=0.5. Primary benchmark for object detection โ€” measures how well the model finds AND localizes objects. 1.0 = perfect. 0.951 (95.1%) 0.962 0.939
mAP@0.5:0.95 Stricter version โ€” averages mAP across IoU thresholds from 0.5 to 0.95. Penalizes imprecise bounding boxes. Industry standard for COCO benchmark. 0.674 (67.4%) 0.670 0.677
Precision Of all detections the model made, how many were correct? High precision = few false alarms. A person incorrectly flagged as suspicious when they are not. 0.906 (90.6%) 0.890 0.921
Recall Of all actual face-covered people in the frame, how many did the model find? High recall = few missed detections. A suspicious person who is not caught. 0.857 (85.7%) 0.911 0.803
Confidence threshold Minimum score to trigger alert. Below this = detection ignored. We chose 0.5 to balance false alarms vs missed detections. 0.5 (50%) โ€” tuned based on validation results
Inference speed (T4 GPU) Speed during training validation on Colab T4 GPU. Reference only โ€” not the deployment target. Preprocess: 0.3ms ยท Inference: 3.0ms ยท Postprocess: 3.6ms = ~7ms/frame
Inference speed (Hailo NPU) Actual deployment speed on the Hailo AI HAT+ NPU on Pi 5. This is the production performance figure. 14โ€“17ms/frame โ†’ 20 FPS @ 1080p

Training Progress โ€” How the Model Improved

textmAP@0.5 progression across 20 epochs
Epoch  1:  mAP50=0.789  mAP50-95=0.460  โ† starting point
Epoch  5:  mAP50=0.890  mAP50-95=0.587  โ† rapid improvement
Epoch 10:  mAP50=0.923  mAP50-95=0.628  โ† strong performance
Epoch 15:  mAP50=0.937  mAP50-95=0.653  โ† approaching plateau
Epoch 20:  mAP50=0.951  mAP50-95=0.673  โ† final model (best.pt)

All losses decreasing consistently:
  box_loss:  1.499 โ†’ 1.007  โ† localization improving
  cls_loss:  2.107 โ†’ 0.543  โ† classification improving
  dfl_loss:  1.172 โ†’ 0.971  โ† distribution focal loss improving

โœ… Strong model โ€” 95.1% mAP@0.5

A mAP@0.5 of 0.951 is excellent for a 2-class detection task. The model is particularly strong at detecting face_covered (mAP=0.962, recall=0.911) โ€” the critical class for our surveillance use case. Missing a covered face is more costly than a false alarm, and the high recall on this class means very few suspicious events go undetected.

๐Ÿ” Why mAP@0.5:0.95 is lower than mAP@0.5

mAP@0.5 requires bounding boxes to overlap with the ground truth by at least 50%. mAP@0.5:0.95 averages across thresholds from 50% to 95% overlap โ€” it penalizes imprecise bounding box locations. Our 0.674 score reflects that while the model correctly identifies and classifies people, bounding box precision varies. For a surveillance alert system, exact bounding box location matters less than correct classification โ€” so 0.951 mAP@0.5 is the more relevant metric here.

โ„น๏ธ Why mAP matters

mAP (Mean Average Precision) is the standard evaluation metric for object detection models used in academic papers and industry benchmarks. It combines both precision and recall into a single number โ€” a model that is precise but misses many detections, or finds many objects but with wrong bounding boxes, will both score poorly on mAP. A value of 1.0 is perfect; typical real-world models score 0.5โ€“0.8.

Precision-Recall Curve
๐Ÿ“ˆ BoxPR_curve.png Precision-Recall curve per class at different confidence thresholds
Figure 1 โ€” PR curve showing precision vs recall tradeoff for face_covered and non_covered_face at varying confidence thresholds.
Confusion Matrix
๐Ÿ”ข confusion_matrix_normalized.png Normalized confusion matrix โ€” % correct vs misclassified per class
Figure 2 โ€” Normalized confusion matrix showing classification accuracy per class. Diagonal = correct predictions.

The Conversion Challenge: PT โ†’ HEF

This was the most technically challenging part of Task 6. The Hailo AI HAT+ does not run standard PyTorch or ONNX models โ€” it requires its own proprietary .HEF (Hailo Executable Format) file. Converting from PyTorch to HEF is a multi-step process that requires significant GPU compute.

Why Not Use RPK?

The Raspberry Pi AI Camera comes with RPK (Raspberry Pi Kit) โ€” a simpler way to deploy models. However RPK only works with the IMX500 sensor's onboard processing, not with the Hailo AI HAT+. Since we specifically wanted to use the Hailo AI HAT+ for its superior compute (13 TOPS vs IMX500's limited onboard processing), we had to go through the full HEF conversion pipeline.

The Conversion Pipeline

1

PyTorch (.pt) โ†’ ONNX (.onnx)

Export from Ultralytics YOLOv8 to ONNX (Open Neural Network Exchange) โ€” a framework-agnostic format. This step runs on any machine with Python and PyTorch.

Runs on: any machine
2

ONNX (.onnx) โ†’ HAR (.har)

Hailo's Model Zoo parser converts ONNX to HAR (Hailo Archive). This step uses Hailo's Dataflow Compiler to optimize the model for Hailo hardware. Requires the Hailo SDK and a GPU for quantization calibration.

Requires: GPU + Hailo SDK
3

HAR (.har) โ†’ HEF (.hef)

Compile the HAR into HEF โ€” the final binary that runs directly on the Hailo-8L NPU hardware. This is the most compute-intensive step โ€” the compiler performs hardware-specific optimization and quantization.

Requires: GPU + Hailo Compiler
4

Deploy HEF on Pi 5 + Hailo AI HAT+

Copy the .hef file to Pi 5. The HailoRT runtime loads and runs it on the Hailo AI HAT+ at inference time. No GPU needed for inference.

Runs on: Hailo AI HAT+

The GPU Problem โ€” and How We Solved It

Steps 2 and 3 require a powerful GPU to run the Hailo Dataflow Compiler. None of the team members had access to a local GPU machine, and the Pi 5 itself cannot run the compiler. This blocked deployment.

โŒ Problem: No local GPU available

The Hailo Dataflow Compiler requires CUDA-capable GPU hardware with significant VRAM. Google Colab (where we trained) does not provide a persistent environment โ€” the session times out and the Hailo SDK installation is lost. The Pi 5 CPU cannot run the compiler in a reasonable time.

โœ… Solution: Massive Compute cloud GPU service

We used Massive Compute โ€” a cloud GPU rental service โ€” to provision a GPU machine with sufficient VRAM and a persistent environment. We installed the Hailo SDK and Dataflow Compiler there, ran the full ONNX โ†’ HAR โ†’ HEF conversion pipeline, and downloaded the resulting .hef file. This took approximately 2โ€“3 hours of GPU time.

bashHEF conversion pipeline (ran on Massive Compute GPU)
# Step 1: Export PyTorch to ONNX
python3 -c "
from ultralytics import YOLO
model = YOLO('best.pt')
model.export(format='onnx', imgsz=640)
"

# Step 2: Parse ONNX to HAR using Hailo Model Zoo
hailomz parse --hw-arch hailo8l \
    --ckpt best.onnx \
    yolov8n

# Step 3: Optimize and compile HAR to HEF
hailomz optimize --hw-arch hailo8l \
    --har yolov8n.har \
    --calib-path calibration_images/

hailomz compile --hw-arch hailo8l \
    --har yolov8n_optimized.har

# Output: yolov8n.hef โ€” deploy this to Pi 5

Video Streaming โ€” From Pi 4 to Pi 5

Problem with HTTP MJPEG

Our first approach was streaming via HTTP MJPEG โ€” the Pi 4 serves camera frames as a continuous HTTP response. This worked but had a 2โ€“4 second latency due to HTTP connection overhead, buffering, and TCP retransmission. A 2โ€“4 second delay between what the camera sees and what the AI processes makes real-time surveillance impossible.

Solution: ZMQ PubSub

We replaced HTTP MJPEG with ZeroMQ (ZMQ) PubSub โ€” a high-performance asynchronous messaging library. The Pi 4 publishes compressed JPEG frames on a ZMQ socket. The Pi 5 subscribes and receives frames as fast as the network allows โ€” with no HTTP overhead, no buffering, and no connection management. Latency dropped from 2โ€“4 seconds to ~50ms.

pythonZMQ streaming โ€” simplified
# Pi 4 โ€” Publisher (det.py)
import zmq, cv2

context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind("tcp://0.0.0.0:5555")

while True:
    ret, frame = camera.read()
    _, buffer = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, 80])
    socket.send(buffer.tobytes())   # fire and forget โ€” no ACK needed

# Pi 5 โ€” Subscriber (inference service)
socket = context.socket(zmq.SUB)
socket.connect("tcp://10.10.10.40:5555")
socket.setsockopt(zmq.SUBSCRIBE, b"")

while True:
    frame_bytes = socket.recv()     # ~50ms latency
    frame = cv2.imdecode(np.frombuffer(frame_bytes, np.uint8), cv2.IMREAD_COLOR)
    # โ†’ run Hailo inference on frame

Detection Logic & Alert Pipeline

What counts as suspicious?

The model outputs a bounding box and class label for each detected person in the frame. We defined two classes during training:

โœ…
face_visible
Full face visible โ€” classified as SAFE. No alert generated.
โš ๏ธ
face_covered
Face partially or fully covered โ€” classified as SUSPICIOUS. Alert triggered.

A detection only triggers an alert if the model's confidence score is above 0.5 (50%). This threshold was chosen to balance false alarms (too low) against missed detections (too high).

Alert Pipeline

๐ŸŽฅ Camera frame
โ†’
๐Ÿง  Hailo inference
โ†’
โš ๏ธ face_covered detected
โ†’
๐Ÿ“ธ Snapshot saved to MinIO
โ†’
๐Ÿ—„๏ธ Alert stored in PostgreSQL
โ†’
๐Ÿ“ฑ Telegram notification
โ†’
๐Ÿ–ฅ๏ธ EdgeGuard dashboard

Each alert stored in PostgreSQL contains: event type, confidence score, timestamp, camera ID, and MinIO image URL. The EdgeGuard frontend displays all alerts with pagination and the snapshot image.

Screenshots

Live Detection
๐Ÿ“ท detection_live.png Live camera feed with detection overlay and bounding boxes
Figure 1 โ€” Live detection in EdgeGuard dashboard showing bounding boxes and confidence scores.
Alert History
๐Ÿ”” alert_history.png Alert history page showing suspicious event snapshots
Figure 2 โ€” Alert history with MinIO snapshot images and confidence scores per alert.

Key Findings

โœ… ~8,000ร— speedup: Hailo NPU vs Pi 5 CPU

CPU inference on Pi 5 ARM: 2โ€“3 minutes per frame. Hailo AI HAT+ NPU: 14โ€“17ms per frame. This makes the difference between a system that cannot do real-time video (CPU) and one that sustains 20 FPS at 1080p (NPU).

โš ๏ธ PT โ†’ HEF conversion requires external GPU

The Hailo Dataflow Compiler cannot run on the Pi 5 CPU within a reasonable timeframe. We solved this by using Massive Compute cloud GPUs. This is an important real-world constraint: edge deployment does not mean edge development โ€” model compilation still requires cloud resources.

โœ… ZMQ reduced streaming latency from 2โ€“4s to ~50ms

Replacing HTTP MJPEG with ZMQ PubSub was essential for real-time operation. The 50ms latency is imperceptible to the human eye and allows the AI system to react to events within the same video second they occur.

๐Ÿ” Why YOLOv8n and not a larger model?

YOLOv8n (nano) is the smallest variant โ€” optimized for edge devices. Larger models (YOLOv8s, m, l, x) would give better mAP but require more compute and memory, potentially exceeding what the Hailo-8L can run efficiently. For a binary classification task (face visible vs covered), the nano variant provides sufficient accuracy at the required frame rate.