This commit is contained in:
Hamza Zakaria 2026-06-03 10:08:07 +01:00
commit fb52a536c6
9 changed files with 379 additions and 2 deletions

View File

@ -1,5 +1,20 @@
# Product Detection & Classification Pipeline # Product Detection & Classification Pipeline
<table>
<tr>
<td align="center">
<img src="assets/Scene.jpeg" width="700"><br>
<b>Scene</b>
</td>
<td align="center">
<img src="assets/Scene - Product Detection.jpeg" width="700"><br>
<b>Product Detection - YOLOv8n</b>
</td>
<td align="center">
<img src="assets/Scene_frame000000.jpg" width="700"><br>
<b>Product Classification - EfficientNet B0</b>
</td>
</tr>
</table>
A two-stage computer vision pipeline that combines a fine-tuned **YOLOv8n** detector with a **EfficientNet** classifier to detect and identify products on retail shelves. A two-stage computer vision pipeline that combines a fine-tuned **YOLOv8n** detector with a **EfficientNet** classifier to detect and identify products on retail shelves.
``` ```
@ -196,12 +211,22 @@ data/
Use any file manager, or a tool like [Label Studio](https://labelstud.io/) for faster annotation. Use any file manager, or a tool like [Label Studio](https://labelstud.io/) for faster annotation.
**Class Labeller**
<div align="center">
<video src="assets/video/Labeling.mp4" title="Labeling.mp4" width="75%" controls></video>
</div>
--- ---
### Step 2 — Auto-label & Review ### Step 2 — Auto-label & Review
> Use this step once you have a trained classifier. It replaces manual labelling almost entirely. > Use this step once you have a trained classifier. It replaces manual labelling almost entirely.
<div align="center">
<video src="assets/video/Auto Label.mp4" title="Auto Label.mp4" width="75%" controls></video>
</div>
Runs the full detect → crop → classify pipeline in one command, then opens a browser UI where you review, confirm, fix, or reject each prediction. Runs the full detect → crop → classify pipeline in one command, then opens a browser UI where you review, confirm, fix, or reject each prediction.
```bash ```bash
@ -428,6 +453,13 @@ python 2_train_classifier.py \
- Upload the zip, give it a name (e.g. `my-product-crops`) - Upload the zip, give it a name (e.g. `my-product-crops`)
- Wait for processing to complete - Wait for processing to complete
*Note: Don't need to create New Dataset you just need to update it from [here](https://www.kaggle.com/datasets/triztechdata/product-dataset)*
<div align="center">
<video src="assets/video/Update Dataset.mp4" title="Update Dataset.mp4" width="75%" controls></video>
</div>
2. Create a new Kaggle Notebook: 2. Create a new Kaggle Notebook:
- Go to [kaggle.com/code](https://www.kaggle.com/code) → **New Notebook** - Go to [kaggle.com/code](https://www.kaggle.com/code) → **New Notebook**
- Upload `train_classifier_kaggle.ipynb` via **File → Import Notebook** - Upload `train_classifier_kaggle.ipynb` via **File → Import Notebook**
@ -438,7 +470,7 @@ python 2_train_classifier.py \
- It will appear at `/kaggle/input/my-product-crops/` - It will appear at `/kaggle/input/my-product-crops/`
4. Enable the GPU: 4. Enable the GPU:
- Right panel → **Session options → Accelerator → GPU T4 x1** - Right panel → **Session options → Accelerator → GPU T4 x2**
5. Edit **Cell 2 — Configuration** only: 5. Edit **Cell 2 — Configuration** only:
```python ```python
@ -470,6 +502,10 @@ After training, download from `/kaggle/working/runs/classify/`:
These two files are all you need to run `3_inference.py` and `api_server.py`. These two files are all you need to run `3_inference.py` and `api_server.py`.
<div align="center">
<video src="assets/video/Training Model.mp4" title="Training Model.mp4" width="75%" controls></video>
</div>
#### Option C — Fully automated via `kaggle_train.py` #### Option C — Fully automated via `kaggle_train.py`
Run the entire Kaggle pipeline — upload, train, download — from a single command with no browser needed: Run the entire Kaggle pipeline — upload, train, download — from a single command with no browser needed:
@ -780,3 +816,28 @@ balance_and_augment.py (equalise class counts, generate augmented copies)
``` ```
Each iteration your classifier gets better, which means the auto-labelling in the next iteration needs less manual correction. Each iteration your classifier gets better, which means the auto-labelling in the next iteration needs less manual correction.
---
<table>
<tr>
<td align="center">
<img src="runs/classify/per_class_accuracy.png" height="600"><br>
<b>Per-Class Accuracy</b>
</td>
<td align="center">
<img src="runs/classify/class_distribution.png" height="600"><br>
<b>Class Distribution</b>
</td>
</tr>
<tr>
<td align="center">
<img src="runs/classify/confusion_matrix.png" width="700"><br>
<b>Confusion Matrix</b>
</td>
<td align="center">
<img src="runs/classify/training_curves.png" height="400"><br>
<b>Training Curves</b>
</td>
</tr>
</table>

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 KiB

BIN
assets/Scene.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 KiB

BIN
assets/video/Auto Label.mp4 Normal file

Binary file not shown.

BIN
assets/video/Labeling.mp4 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

316
detector/README.md Normal file
View File

@ -0,0 +1,316 @@
# Phase 1 — Shelf Product Detection with YOLOv8n
### Pipeline Documentation · SKU110K Dataset
---
## Overview
This document covers Phase 1 of a two-phase retail shelf analysis pipeline. The goal of Phase 1 is to **detect all products** visible on retail shelves using a fine-tuned YOLOv8n object detection model trained on the SKU110K dataset. The output (bounding boxes around each product) feeds directly into Phase 2, which classifies each detected product.
```
Shelf Image → [Phase 1: YOLOv8n Detection] → Bounding Boxes → [Phase 2: Classifier] → Product Labels
```
---
## Environment
| Item | Details |
|---|---|
| Platform | Kaggle Notebooks (GPU) |
| GPU | Required (auto-selected with `batch=-1`) |
| Framework | Ultralytics YOLOv8 |
| Language | Python 3 |
---
## Prerequisites
### 1. Install Dependencies
```bash
pip install -q ultralytics
```
This installs the Ultralytics library, which includes YOLOv8 and all required dependencies (PyTorch, OpenCV, etc.).
---
## Dataset — SKU110K
### What It Is
SKU110K is the primary dataset used for training. It contains:
- **11,762 shelf images** capturing dense retail environments
- **~1.2 million annotated products** (bounding boxes)
- Designed for dense object detection in challenging, cluttered shelf scenarios
| Resource | Link |
|---|---|
| GitHub (paper + info) | https://github.com/eg4000/SKU110K_CVPR19 |
| Kaggle (download) | https://www.kaggle.com/datasets/thedatasith/sku110k-annotations |
### Alternative Datasets (for future reference)
If you need to augment or swap the dataset, consider:
- **Grocery Store Dataset (Grozi-120)** — 120 grocery product classes
- **WebMarket** — web-scraped retail product images
- **RPC (Retail Product Checkout)** — checkout-counter product recognition
---
## Step-by-Step Reproduction Guide
### Step 1 — Locate the Dataset Folder
After adding the SKU110K dataset to your Kaggle notebook, run the following to find and verify the dataset structure:
```python
import os
BASE_PATH = "/kaggle/input/datasets/thedatasith/sku110k-annotations"
DATASET_FOLDER = None
# Find SKU110K_fixed folder dynamically
for item in os.listdir(BASE_PATH):
if "SKU110K" in item:
DATASET_FOLDER = os.path.join(BASE_PATH, item)
break
print("📁 Dataset folder:", DATASET_FOLDER)
# Print directory tree (2 levels deep)
for root, dirs, files in os.walk(DATASET_FOLDER):
level = root.replace(DATASET_FOLDER, '').count(os.sep)
indent = ' ' * 2 * level
print(f"{indent}📁 {os.path.basename(root)}/")
for f in files[:5]:
print(f"{indent} 📄 {f}")
if level >= 2:
break
```
**Why:** The folder name inside the dataset archive contains `SKU110K` but may have a suffix (e.g., `SKU110K_fixed`), so we detect it dynamically rather than hardcoding it.
---
### Step 2 — Inspect the YAML Configuration
The dataset comes with a `data_kaggle.yaml` file that defines paths and class names. Read it to verify its contents before making any changes:
```python
import yaml
YAML_PATH = BASE_PATH + "/data_kaggle.yaml"
with open(YAML_PATH, "r") as f:
data = yaml.safe_load(f)
print("Classes:", data.get("names"))
print("Number of classes:", len(data.get("names", [])))
print("Train path:", data.get("train"))
print("Val path:", data.get("val"))
```
---
### Step 3 — Fix the YAML Paths
The paths in the original YAML file point to locations that don't match the Kaggle input filesystem. You must override them with the correct absolute paths before training:
```python
import yaml
YAML_PATH = BASE_PATH + "/data_kaggle.yaml"
with open(YAML_PATH, "r") as f:
data = yaml.safe_load(f)
# Override with correct Kaggle paths
data["train"] = DATASET_FOLDER + "/images/train"
data["val"] = DATASET_FOLDER + "/images/val"
# Save the corrected YAML to the working directory
FIXED_YAML_PATH = "/kaggle/working/fixed_data.yaml"
with open(FIXED_YAML_PATH, "w") as f:
yaml.dump(data, f)
print("✅ Fixed YAML saved at:", FIXED_YAML_PATH)
print(data)
```
> **Important:** Always write the fixed YAML to `/kaggle/working/`, not back to the input directory (which is read-only on Kaggle).
---
### Step 4 — Train YOLOv8n
Verify GPU availability and launch training:
```python
import torch
import numpy
from ultralytics import YOLO
# Verify GPU
print("GPU:", torch.cuda.get_device_name(0) if torch.cuda.is_available() else "None")
print("CUDA Capability:", torch.cuda.get_device_capability(0))
# Load pretrained YOLOv8 nano model
model = YOLO('yolov8n.pt')
# Train
model.train(
device='0', # GPU index
data=FIXED_YAML_PATH, # Path to the fixed dataset config
epochs=30, # Total training epochs
imgsz=800, # Input image size (px)
batch=-1, # Auto-select largest batch that fits in GPU memory
workers=4, # Dataloader worker threads
optimizer="AdamW", # Optimizer
lr0=0.002, # Initial learning rate
patience=10, # Early stopping: stop if no improvement for 10 epochs
save_period=5, # Save a checkpoint every 5 epochs
project="kaggle/working/runs", # Output directory
name="exp", # Experiment name (auto-increments: exp, exp2, ...)
save=True # Save final weights
)
```
#### Training Hyperparameters Explained
| Parameter | Value | Rationale |
|---|---|---|
| `imgsz` | 800 | Higher resolution improves detection of small/dense products |
| `batch` | -1 | Auto-batch maximizes GPU utilization without OOM errors |
| `optimizer` | AdamW | Better generalization than SGD for this type of task |
| `lr0` | 0.002 | Starting learning rate |
| `patience` | 10 | Stops training early if validation loss plateaus |
| `save_period` | 5 | Checkpoint every 5 epochs for resumability |
| `epochs` | 30 | Total epochs (may stop earlier due to `patience`) |
---
### Step 5 — Download Trained Weights
After training completes, package the experiment output folder as a ZIP file and create a download link:
```python
import shutil
from IPython.display import FileLink
# Update this path to match your actual experiment folder name
folder_path = '/kaggle/working/runs/detect/kaggle/working/runs/exp-6'
zip_path = '/kaggle/working/exp-6.zip'
shutil.make_archive('/kaggle/working/exp-6', 'zip', folder_path)
print("Weights saved to:", zip_path)
FileLink(zip_path)
```
> **Note:** The experiment folder name auto-increments (e.g., `exp`, `exp-2`, `exp-6`). Check `/kaggle/working/runs/detect/` to find your folder name before running this cell.
The ZIP will contain:
- `weights/best.pt` — best checkpoint (highest validation mAP)
- `weights/last.pt` — final checkpoint (used for resuming)
- Training curves, confusion matrix, and metrics logs
---
### Step 6 — Resume an Interrupted Training Run
If training is interrupted (e.g., Kaggle session timeout), you can resume from the last saved checkpoint. Upload `last.pt` as a Kaggle dataset input first, then:
```python
from ultralytics import YOLO
model = YOLO("/kaggle/input/weights/last.pt")
model.train(resume=True)
```
> `resume=True` automatically picks up the training configuration, optimizer state, and epoch counter from the checkpoint.
---
## Output Files
After a successful training run, the experiment folder contains:
```
exp/
├── weights/
│ ├── best.pt ← Use this for inference and Phase 2 input
│ └── last.pt ← Use this to resume training
├── results.csv ← Per-epoch metrics (loss, mAP, precision, recall)
├── results.png ← Training curves plot
├── confusion_matrix.png
├── val_batch*.jpg ← Validation batch visualizations
└── args.yaml ← Full training config (for reproducibility)
```
---
## Using the Trained Model for Inference (Phase 2 Input)
After training, use `best.pt` to detect products in new shelf images:
```python
from ultralytics import YOLO
model = YOLO("best.pt")
results = model.predict(source="shelf_image.jpg", conf=0.25, iou=0.45)
for result in results:
boxes = result.boxes.xyxy # Bounding boxes [x1, y1, x2, y2]
confs = result.boxes.conf # Confidence scores
# Pass boxes to Phase 2 classifier
```
---
## Troubleshooting
| Problem | Likely Cause | Fix |
|---|---|---|
| `DATASET_FOLDER` is `None` | Folder name doesn't contain "SKU110K" | Print `os.listdir(BASE_PATH)` and update the `if` condition |
| YAML path errors during training | Paths still point to original locations | Re-run Step 3 and confirm `FIXED_YAML_PATH` exists |
| CUDA out of memory | Batch size too large | Set `batch` to a fixed value (e.g., `8` or `16`) instead of `-1` |
| Training not resuming | Wrong path to `last.pt` | Upload `last.pt` as a Kaggle dataset, verify the input path |
| Experiment folder not found | Auto-increment changed the name | List `/kaggle/working/runs/detect/` to find the correct name |
---
## Pipeline Context
```
┌─────────────────────────────────────────────────────────────────────┐
│ Full Pipeline │
│ │
│ Input Image │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────┐ │
│ │ PHASE 1 — Detection │ ← This notebook │
│ │ YOLOv8n trained on SKU110K │ │
│ │ Output: bounding boxes │ │
│ └─────────────────────────────────┘ │
│ │ │
│ │ Cropped product regions │
│ ▼ │
│ ┌─────────────────────────────────┐ │
│ │ PHASE 2 — Classification │ ← Next phase │
│ │ Classify each detected product │ │
│ │ Output: product labels │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```
Phase 1 produces bounding boxes for every product on the shelf. Each cropped region is then passed to the Phase 2 classifier for product identification.
---
*Documentation generated from `shelf-products-detection-train-yolov8n.ipynb`*