Skip to content

Repository files navigation

Pet Image Segmentation with U-Net

Python PyTorch License

Semantic segmentation of pets using a U-Net implemented from scratch in PyTorch.

Trained on the Oxford-IIIT Pet Dataset, achieving Validation Dice 0.80 on CPU.


Overview

This is my first deep learning segmentation project. I built a U-Net from scratch in PyTorch and trained it to segment cats and dogs from background pixels in the Oxford-IIIT Pet Dataset.

The project includes the full ML pipeline - data loading, augmentation, training, validation, prediction, and several model interpretability visualizations (feature maps, Grad-CAM, U-shape architecture diagram).

Final Results

Metric Train Validation
Dice Score 0.8342 0.7993
IoU Score 0.7175 0.6681
Loss 0.3741 0.4399 (best)

Trained for 50 epochs on CPU (~12 hours).


Key Features

  • U-Net from scratch: 3-level encoder-decoder with skip connections
  • Combined loss: Dice Loss + 0.5 Γ— BCE Loss for stable training under class imbalance
  • Synced augmentation: Image and mask transformed together
  • Metrics: Dice Score and IoU computed per epoch
  • TensorBoard logging: Real-time training monitoring
  • Model interpretability: Feature maps, Grad-CAM, U-shape architecture diagram
  • Best model checkpointing: Saves the model with lowest validation loss

πŸ“Έ Results

Segmentation Prediction

Input β†’ Ground Truth β†’ Model Prediction:

Comparison Result

Probability Heatmap (Before Thresholding)

Raw model output - bright areas = high cat-probability:

Prediction Example

Feature Maps from First Encoder Block

8 of the 32 filters in enc1 - each detects different features (edges, textures, contrast):

Feature Maps

Feature Maps Across All Layers

Watching features get more abstract from shallow (top) to deep (bottom). Notice how the bottleneck compresses the cat into a small bright blob:

Feature Maps Deep

Full U-Net Architecture Visualization

The classic "U-shape" - encoder shrinks (left), bottleneck at the bottom, decoder grows (right):

U-Net Architecture

Grad-CAM Attention Map

Shows where the model "looks" when making predictions - concentrated on the cat's body:

Grad-CAM Heatmap

Training Curves (TensorBoard)

Validation Dice climbing steadily to ~0.80 over 50 epochs:

TensorBoard Metrics


Model Architecture

A real U-Net with 3 encoder levels, bottleneck, and 3 decoder levels with skip connections.

Encoder:
  enc1: ConvBlock(3 β†’ 32)    β†’ 128Γ—128
  enc2: ConvBlock(32 β†’ 64)   β†’ 64Γ—64
  enc3: ConvBlock(64 β†’ 128)  β†’ 32Γ—32

Bottleneck:
  ConvBlock(128 β†’ 256)       β†’ 16Γ—16

Decoder (with skip connections):
  upconv3 + dec3(256 β†’ 128)  β†’ 32Γ—32
  upconv2 + dec2(128 β†’ 64)   β†’ 64Γ—64
  upconv1 + dec1(64 β†’ 32)    β†’ 128Γ—128

Output:
  Conv2d(32 β†’ 1, 1Γ—1)        β†’ segmentation mask

Each ConvBlock is Conv β†’ BatchNorm β†’ ReLU repeated twice.


Dataset

Oxford-IIIT Pet Dataset - 7,390 images of 37 cat and dog breeds with pixel-level segmentation masks.

The original masks have three classes:

  • 1 = pet border/outline (~9.6% of pixels)
  • 2 = background (~82.8% of pixels)
  • 3 = pet body (~7.6% of pixels)

For binary segmentation, I treat border + body as foreground:

mask = ((mask == 1) | (mask == 3)).astype("float32")

This gives ~17% foreground coverage - much easier to learn than body-only (7.6%) and produces clean filled cat silhouettes.


πŸš€ Usage

Setup

git clone https://github.com/saeed-moo/pet-image-segmentation.git
cd pet-image-segmentation
python -m venv .venv

# Windows
.venv\Scripts\activate

# macOS/Linux
source .venv/bin/activate

pip install -r requirements.txt

Download dataset

python download_dataset.py

Inspect masks (optional)

python check_masks.py

This prints unique mask values and shows pet/background coverage - useful for verifying mask binarization.

Train

python train.py

Trains for 50 epochs, saves the best model as model_best.pth, and logs metrics to TensorBoard.

Monitor in another terminal:

tensorboard --logdir=runs

Predict

python compare_predictions.py    # Input + GT + Prediction (3 panels)
python predict.py                # Input + Prediction (2 panels)

Visualizations

python visualise_features.py     # Feature maps from enc1
python gradcam_visualise.py      # Grad-CAM attention heatmap

πŸ› Lessons Learned (Debugging Journey)

This project taught me a lot about real-world ML debugging. Three big issues I had to track down:

1. Inverted mask binarization

Initially I used mask <= 2 thinking it kept the pet. But values 1 and 2 in this dataset are border + background, not pet. This inversion gave a misleading fake Dice 0.937 because the model just predicted "everything is foreground" and got 92% pixel accuracy.

I diagnosed it by inspecting unique mask values and visualizing what each binarization option actually selected:

Mask Debugging

Value 1: 9.56%  (border)
Value 2: 82.83% (background β€” biggest!)
Value 3: 7.61%  (pet body)

The visualization made it obvious - mask <= 2 was selecting border + background (everything except the cat body), not the pet. This is why class imbalance was so extreme and the model collapsed to predicting "all foreground."

2. Augmentation sync bug

My RandomHorizontalFlip only flipped the image, not the mask. So the model was being trained on image-mask pairs where the cat was in one position and the label said it was in another - essentially noise. Fixed by using torchvision.transforms.functional to apply the same transform to both.

3. Class imbalance

With pet body at only 7.6% of pixels, plain BCE loss converged to "predict all background." Solved by combining Dice Loss (handles imbalance natively) with weighted BCE Loss for boundary detail.

Final result: Real Val Dice 0.80 (up from a broken fake 0.937 β†’ real but mediocre 0.42 β†’ genuinely good 0.80).


Project Structure

pet-image-segmentation/
β”œβ”€β”€ train.py                    # Training script
β”œβ”€β”€ predict.py                  # Single-image prediction
β”œβ”€β”€ compare_predictions.py      # 3-panel comparison
β”œβ”€β”€ visualise_features.py       # Feature map visualization
β”œβ”€β”€ gradcam_visualise.py        # Grad-CAM heatmap
β”œβ”€β”€ check_masks.py              # Mask inspection utility
β”œβ”€β”€ dataset.py                  # PyTorch Dataset
β”œβ”€β”€ metrics.py                  # Dice & IoU + DiceLoss class
β”œβ”€β”€ download_dataset.py         # Dataset downloader
β”œβ”€β”€ requirements.txt            # Python dependencies
β”œβ”€β”€ models/
β”‚   └── unet.py                 # U-Net model
β”œβ”€β”€ data/                       # Images and masks (not in repo)
β”œβ”€β”€ results/                    # Visualizations (in repo)
└── runs/                       # TensorBoard logs (not in repo)

Tech Stack

PyTorch β€’ Torchvision β€’ NumPy β€’ PIL β€’ OpenCV β€’ Matplotlib β€’ TensorBoard


References


License

MIT License


⭐ Star this repo if you found it useful!

About

Semantic segmentation of pet images with a PyTorch U-Net CNN, featuring Grad-CAM heatmaps and TensorBoard logging

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages