Skip to content

Step 6 — Object Detection Model Training

1. Scope and compliance constraints

Per course requirements: pre-trained weights and pre-existing models are prohibited. All data must be self-collected and self-annotated, and the model trained from scratch with a minimum of 200 images per class.

Item Requirement
Classes fire, smoke, person, mask, weapon (5 total)
Architecture YOLOv11n, initialized from .yaml (no .pt pretrained weights)
Resolution 640px (also required for downstream IMX500 export)
Data source Roboflow-annotated COCO JSON exports, self-collected
Deployment target Raspberry Pi AI Camera (Sony IMX500 sensor) — NOT Hailo-8L / AI HAT+

2. Final design decisions

From-scratch training only

Model is instantiated from architecture definition (yolo11n.yaml), not yolo11n.pt. This is the compliance-critical decision — any pretrained checkpoint use disqualifies the deliverable.

IMX500 export path, not Hailo

Confirmed hardware target is the AI Camera (IMX500), which requires Sony's Model Compression Toolkit and exports to .rpk / packerOut.zip — not Hailo Dataflow Compiler's .hef. These are non-interchangeable pipelines; using the wrong one produces broken inference on-device even with a correctly trained model.

In-memory train/valid split correction

One source dataset (weapon/knife) had no valid split — only train and test. Rather than writing back into the read-only Kaggle input mount, a 15% validation carve-out was performed in-memory during the merge step, preserving compliance with the 70/15/15 split requirement without modifying source data.

3. Step-by-step replication procedure

Run in a Kaggle notebook with GPU T4 and Internet enabled.

3.1 Dataset ingestion

Upload Roboflow COCO exports (per class) as a Kaggle Dataset. Kaggle auto-extracts .zip uploads on creation — attach via Add Input; no manual unzip needed.

3.2 Auto-discover dataset folders

coco_json_files = glob.glob(f"{INPUT_ROOT}/**/_annotations.coco.json", recursive=True)
dataset_folders = sorted({os.path.dirname(os.path.dirname(jf)) for jf in coco_json_files})

Classifies each folder to a target class by keyword match on folder name (fire/smoke/person/weapon/mask), avoiding hardcoded paths that break on naming variance.

3.3 Custom COCO to YOLO conversion

Ultralytics' built-in convert_coco() expects a flat JSON layout and does not match Roboflow's per-split (train/valid/test) folder structure — it silently produces empty output against this layout. Replaced with a direct COCO JSON parser that reads bbox fields, converts to normalized YOLO cx cy w h format, and copies images alongside remapped label files into a unified 5-class tree.

3.4 Missing valid-split handling

For datasets lacking a valid/ folder, images are shuffled (seed=42) and 15% carved into validation in-memory — no writes to the read-only /kaggle/input mount.

3.5 Train YOLOv11n from scratch

model = YOLO("yolo11n.yaml")
model.train(
    data=data_yaml_path, epochs=200, imgsz=640, batch=8, patience=40,
    optimizer="AdamW", cos_lr=True, cls=1.0,
    copy_paste=0.5, scale=0.7, perspective=0.0005, ...
)

cls=1.0 (doubled classification loss weight) and heavier offline and online augmentation specifically target weak classes identified in v1 (mask and weapon had visibly lower mAP50 than fire, person, and smoke).

3.6 Export for IMX500

best_model.export(format="imx", data=data_yaml_path, imgsz=640)

Produces best_saved_model/packerOut.zip — the actual deployable artifact for the IMX500 sensor. best.onnx alone is not deployable to this hardware.

4. Verification and metrics

Post-training validation reports overall and per-class metrics.

Target thresholds

Metric Target
mAP50 ≥ 0.90
mAP50-95 > 0.70
Precision ≥ 0.87
Recall ≥ 0.88

Actual results

Metric Value
mAP50 0.8620
mAP50-95 0.6231
Precision 0.8719
Recall 0.8435

5. Results

Per-class mAP50 performance

The model fell slightly short of the target thresholds across most metrics: mAP50 at 0.862 (target ≥ 0.90), mAP50-95 at 0.623 (target > 0.70), and Recall at 0.844 (target ≥ 0.88). Precision (0.872) just meets its target. The hardest-hit classes remain mask and weapon, which had fewer training samples and weaker per-class mAP50 scores as shown in the chart above.

6. Critical findings and reproducibility traps

The convert_coco() silent failure

Ultralytics' convert_coco() utility assumes flat-file COCO JSON layout. Roboflow's per-split export (train/_annotations.coco.json, valid/_annotations.coco.json, etc.) does not match this assumption — the function runs without error but produces zero images in output. No exception is raised at conversion time; the failure only surfaces later as AssertionError: No images found during model.train(). Always verify converted output image counts before proceeding to training.

Read-only input mount

/kaggle/input is always read-only, including datasets you own. Any fix requiring a new file (e.g., a missing valid/ split) must be created in /kaggle/working or handled in-memory — attempting os.makedirs() or file writes against /kaggle/input raises OSError: Read-only file system.

IMX500 vs Hailo-8L export confusion

Both chips were issued as project hardware, but they are not interchangeable export targets. A model exported via Hailo Dataflow Compiler (.hef) will not run on the IMX500 sensor, and vice versa. Confirm which physical sensor the deployment team is targeting before running the final export — this determines the entire export path.

7. Complete data.yaml reference

path: /kaggle/working/yolo_dataset/unified_5class
train: train/images
val: valid/images
test: test/images

nc: 5
names: ['fire', 'smoke', 'person', 'mask', 'weapon']