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
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

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.