Semantic Segmentation

Label every pixel in an image. By the end you'll understand encoder-decoder networks like U-Net, compute IoU and Dice by hand, and know when each segmentation flavour and metric is the right tool.

Learn Semantic Segmentation in our free AI & Machine Learning course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a…

Part of the free AI & Machine Learning course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

Object detection draws rectangles around things. Segmentation goes further: it hands you a colouring book of the photo and asks you to colour every single pixel by what it is — sky blue, road grey, car red, person yellow. Nothing is left uncoloured, and the colour has to follow the object's exact outline, not a loose box.

A self-driving car needs that precision to know exactly where the road ends and the kerb begins. A radiologist needs it to trace the precise boundary of a tumour. That is the job of semantic segmentation : assign a class label to each pixel so the whole image becomes a clean, colour-coded map.

In image classification you predict one label for the whole image ("this is a cat"). In segmentation you predict one label for every pixel . The output is a grid the same height and width as the input, where each cell holds a class id — a mask .

Under the hood the model produces, for each pixel, a score per class. You then take the argmax (the index of the highest score) to get that pixel's predicted class. That single operation is what turns a stack of score maps into the final colour-coded picture.

Here is that whole idea in plain Python — no libraries — so you can see the argmax happen on a tiny grid:

There are three flavours of segmentation. They answer different questions:

Every pixel gets a class, but all objects of a class merge. Three cars are just one big "car" region. Question: what is this pixel?

Separates individual objects (car #1, car #2, car #3) with their own masks, but usually ignores background "stuff". Question: which object is this?

Both at once: every pixel gets a class and countable objects get a unique id. Question: what is it, and which one?

Jargon check: classes like road, sky and grass that you can't count are called "stuff" ; countable classes like cars and people are "things" . Semantic handles stuff well; instance handles things; panoptic does both.

A normal classifier ends in dense layers that throw away spatial layout — fine for one label, useless for a per-pixel map. The Fully Convolutional Network (FCN) fixed this by replacing those dense layers with convolutions, so the output stays a 2-D grid.

Modern segmentation networks share an encoder-decoder shape:

U-Net (2015) is the famous example. Its big idea is skip connections : at each decoder stage it concatenates the matching high-resolution feature map copied straight from the encoder. That restores the fine edge detail the encoder threw away, so boundaries come out crisp instead of blurry. The diagram below shows the symmetric "U" shape:

The worked example below builds a tiny U-Net in PyTorch so you can see the skip connection ( torch.cat ) and the upsampling step in real code. It's read-only here — run it locally with PyTorch installed:

Notice nn.ConvTranspose2d doing the learnable upsampling and torch.cat([x, s1], dim=1) performing the skip connection. The 1×1 head maps features to one score per class per pixel.

The bottleneck feature map is tiny — maybe 16×16 for a 256×256 input. The decoder must grow it back to full size. There are two common ways:

Either way, the goal is the same: recover the original height and width so you can output one prediction per input pixel. DeepLab takes a different route and uses dilated (atrous) convolutions — convolutions with gaps between weights — to widen the receptive field without downsampling as aggressively, keeping resolution high throughout.

How do you score a mask? You compare the predicted pixels for a class against the true pixels. Two measures dominate:

Mean IoU (mIoU) is just the IoU averaged across all classes — the headline number on datasets like Cityscapes and Pascal VOC. The runnable example below computes both IoU and Dice on two tiny masks, entirely in plain Python so you can follow every pixel:

You almost never hand-build U-Net for real work. Libraries like segmentation_models_pytorch give you battle-tested architectures with pretrained encoders in a few lines. Read-only here; run it locally:

Swap encoder_name for a deeper backbone, or smp.Unet for smp.DeepLabV3Plus , and the rest of your training loop stays identical.

Fill in the two blanks so the loop counts how many pixels were labelled correctly, then divides by the total. The expected output is in the comments — match it.

Complete the argmax comparison and the list comprehension so each pixel's highest-scoring class becomes its label. Match the expected output.

Time to fly with less support. Using only the comment outline, compute the IoU for each of the three classes and then their mean. The expected numbers are in the comments so you can self-check.

Background often fills 80–95% of an image. Train with plain cross-entropy and the model learns to predict "background" everywhere — small classes vanish.

✅ Fix: use Dice loss or a weighted/focal cross-entropy so rare classes count more, and always report per-class IoU, not just the average.

A model scoring 92% pixel accuracy can still miss every pedestrian if pedestrians are a tiny fraction of pixels.

✅ Fix: judge models by mean IoU (mIoU), which weights every class equally regardless of how many pixels it covers.

Edges of objects come out smeared and the mask leaks across object borders — usually because spatial detail was lost during heavy downsampling.

✅ Fix: use an architecture with skip connections (U-Net) or dilated convolutions (DeepLab) so high-resolution detail reaches the output.

A pure encoder-decoder with no skips upsamples only from the tiny bottleneck, so it never recovers fine structure and masks look like soft blobs.

✅ Fix: concatenate (or add) the matching encoder feature map into each decoder stage — that is exactly what makes U-Net work.

Lesson 33 complete — you can label every pixel!

You now know the three segmentation flavours, how encoder-decoder networks like FCN and U-Net rebuild a full-resolution mask, why skip connections and transposed convolutions matter, and how to compute IoU, Dice and mIoU by hand.

🚀 Up next: Speech Recognition — switch from pixels to audio and learn how models turn sound waves into text.

Practice quiz

What does semantic segmentation produce as output?

  • A single class label for the whole image
  • A bounding box around each object
  • A class label for every pixel in the image
  • A caption describing the image

Answer: A class label for every pixel in the image. Semantic segmentation predicts one class label per pixel, producing a mask the same height and width as the input.

How does semantic segmentation differ from instance segmentation?

  • Semantic merges all objects of a class into one region; instance separates individual objects
  • Semantic only works on grayscale images
  • Instance segmentation never labels background
  • There is no difference

Answer: Semantic merges all objects of a class into one region; instance separates individual objects. Semantic treats all cars as one 'car' region; instance segmentation gives each car its own separate mask (car #1 vs car #2).

Panoptic segmentation is best described as:

  • Only labelling countable 'things'
  • Combining semantic (every pixel gets a class) and instance (each object gets a unique id)
  • Segmentation done without any neural network
  • Detecting objects with bounding boxes only

Answer: Combining semantic (every pixel gets a class) and instance (each object gets a unique id). Panoptic segmentation assigns every pixel a class AND gives each countable object a unique instance id — both at once.

Why does U-Net use skip connections?

  • To reduce the number of classes
  • To copy high-resolution encoder features to the decoder so boundaries stay sharp
  • To skip training on easy images
  • To convert the image to grayscale

Answer: To copy high-resolution encoder features to the decoder so boundaries stay sharp. The encoder loses fine spatial detail; skip connections pass high-resolution feature maps to the decoder so it can recover exact boundaries.

How is the IoU (Jaccard index) of a class computed?

  • intersection / union of predicted and true pixels
  • 2 * intersection / (predicted + truth)
  • correct pixels / total pixels
  • predicted pixels / true pixels

Answer: intersection / union of predicted and true pixels. IoU = intersection over union: the overlap divided by the total area covered by either the prediction or the ground truth.

How is the Dice coefficient computed?

  • intersection / union
  • 2 * intersection / (predicted + truth)
  • intersection / predicted
  • union / intersection

Answer: 2 * intersection / (predicted + truth). Dice (the F1 score for pixels) is 2 * intersection / (predicted + truth); it is always slightly higher than IoU.

Why is pixel accuracy a misleading metric for segmentation?

  • It is too slow to compute
  • If background dominates, predicting 'background everywhere' scores high while ignoring small classes
  • It requires a GPU
  • It only works for instance segmentation

Answer: If background dominates, predicting 'background everywhere' scores high while ignoring small classes. When background fills most of an image, a model that always predicts background gets high pixel accuracy but is useless — so mean IoU is preferred.

What operation turns a stack of per-pixel class scores into the final class map?

  • Sigmoid
  • Argmax (the index of the highest score per pixel)
  • Convolution
  • Pooling

Answer: Argmax (the index of the highest score per pixel). For each pixel the model outputs one score per class; argmax picks the index of the highest score as that pixel's predicted class.

What is a transposed convolution used for in a segmentation decoder?

  • Learnable upsampling to grow feature maps back toward input resolution
  • Downsampling the image
  • Computing the loss function
  • Normalising the input pixels

Answer: Learnable upsampling to grow feature maps back toward input resolution. A transposed (fractionally-strided) convolution is a learnable upsampling that grows the small bottleneck feature maps back to full size.

What technique does DeepLab use to widen the receptive field without aggressive downsampling?

  • Dropout
  • Dilated (atrous) convolutions
  • Max pooling with large strides
  • Batch normalisation

Answer: Dilated (atrous) convolutions. DeepLab uses dilated/atrous convolutions — convolutions with gaps between weights — to widen the receptive field while keeping resolution high.