Ultralytics YOLO27:
Lessonintermediate

Semantic Segmentation: Label Every Pixel

Predict a per-pixel class map with yolo26n-sem.pt, turn it into coverage numbers, build PNG class-map datasets, and read mIoU without being fooled by pixel accuracy.

Some questions have no objects to count: how much of the sidewalk is blocked, how much of a field is weeds, where the drivable road ends. Semantic segmentation answers them with one class ID per pixel. Its headline metric is easy to misread, though: the model you train in this lesson scores 91% pixel accuracy while never finding a single person.

Outcome

Turn a yolo26n-sem.pt class map into a coverage number for a region of the frame, train on Cityscapes8, and report mIoU, pixel accuracy, and the classes the model misses.

Fast Track
If you already know your way around, here's the short version.
  1. Predict with yolo26n-sem.pt and read result.semantic_mask.data: an H × W tensor of class IDs at the original size, 255 = ignore.

  2. Answer coverage questions by comparing the class map with class IDs from result.names.

  3. Store labels as lossless PNG class maps under masks/, mirroring images/, with masks_dir: masks in the YAML.

  4. Gate releases on mIoU and per-class IoU, never on pixel accuracy alone.

Hands-on

Instance or semantic?#

Semantic segmentation of a city street, with every pixel colored by class: road, sidewalk, car, person, bicycle, vegetation, building, and sky

Instance segmentation (segment)Semantic segmentation (semantic)
Outputone mask per object, plus a boxone class ID per pixel
Counts objectsyesno — two touching cars are one "car" region
Handles "stuff" (road, sky, grass)poorly — no natural instancesyes
LabelspolygonsPNG class maps, or polygons
Gating metricmask mAP50-95mIoU
Pretrained weightsyolo26n-seg.pt (COCO, 80 classes)yolo26n-sem.pt (Cityscapes, 19 classes)
How many, or how much?

If the downstream question includes "how many", use instance segmentation. If it is "how much" or "where", semantic segmentation is simpler to label and to read. The Semantic Segmentation task page has the model table.

Predict a class map#

result.semantic_mask.data is an H × W tensor at the original image size; each value is a class ID and 255 means ignore. result.summary() turns it into per-class pixel ratios:

from ultralytics import YOLO

model = YOLO("yolo26n-sem.pt")  # Cityscapes, 19 classes
result = model("https://ultralytics.com/images/bus.jpg")[0]

class_map = result.semantic_mask.data  # H x W tensor of class ids, 255 = ignore
print("class map:", tuple(class_map.shape), class_map.dtype)
for row in result.summary():
    if row["pixel_ratio"] >= 0.02:
        print(f"{row['name']:12s} {row['pixel_ratio']:.1%}")
class map: (1080, 810) torch.uint8
road         27.5%
sidewalk     2.0%
building     19.3%
vegetation   4.5%
person       14.6%
train        30.9%

Look at the last row. Cityscapes has a bus class, yet the model calls the bus a train. It learned from car-mounted footage of mostly German streets, and a bus photographed from the sidewalk at close range is outside that distribution. Semantic models inherit their training set's viewpoint and class definitions — its dataset bias — more rigidly than detectors do, so fine-tune on your own camera before trusting the map.

Turn the class map into an answer#

A class map becomes a product number with plain tensor comparisons. Here is "how much of the area a pedestrian would walk into is road or sidewalk, and how much is people?":

from ultralytics import YOLO

result = YOLO("yolo26n-sem.pt")("https://ultralytics.com/images/bus.jpg", verbose=False)[0]
class_map = result.semantic_mask.data  # H x W class ids at the original size, 255 = ignore
ids = {name: i for i, name in result.names.items()}

h = class_map.shape[0]
lower = class_map[h // 2 :]  # the half of the frame a pedestrian would walk into
walkable = (lower == ids["road"]) | (lower == ids["sidewalk"])
people = lower == ids["person"]
print(f"lower half: {walkable.float().mean():.0%} road or sidewalk, {people.float().mean():.0%} person")
result.save("semantic_overlay.jpg")  # colored class map over the photo, for a visual check
lower half: 59% road or sidewalk, 23% person

Three habits make these numbers trustworthy:

  • Look up IDs by name from result.names, never by hard-coded number; a model trained on your own classes will number them differently.
  • Restrict to the region that matters. Whole-frame ratios mix in sky and buildings; slice the class map (or multiply by a region mask) first.
  • Open the overlay. semantic_overlay.jpg shows in one glance whether the "road" the number counts is really road.

The dataset format#

Semantic datasets pair each image with a single-channel mask image whose pixel values are class IDs. The semantic dataset guide is the reference; the layout mirrors images/ file for file:

cityscapes8/
├── images/
│   ├── train/aachen_000000_000019.png
│   └── val/aachen_000004_000019.png
└── masks/
    ├── train/aachen_000000_000019.png
    └── val/aachen_000004_000019.png

The YAML adds masks_dir and, when the IDs stored in your masks differ from the training IDs, a label_mapping block:

path: cityscapes8
train: images/train
val: images/val
masks_dir: masks # semantic mask directory

names:
  0: road
  1: sidewalk
  # ... 19 classes in total

label_mapping: # raw mask value -> training id
  0: ignore_label
  7: 0 # road
  8: 1 # sidewalk
  # ...

Cityscapes masks store raw label IDs from -1 to 33; the mapping keeps 19 of them and sends the rest to ignore_label, stored as 255. Pixels set to 255 are excluded from both the loss and the metrics — use the same value for regions your annotators could not decide on.

If you already have polygon labels and no masks_dir, the loader rasterizes the polygons for you and adds a background class for uncovered pixels.

Never save masks as JPEG

Lossy compression blends neighboring pixel values, turning a border between class 7 and class 8 into a smear of 7s, 8s, and IDs that do not exist. Save every mask as PNG or TIFF.

Train and read mIoU#

Cityscapes8 has 8 urban-scene images, 4 for training and 4 for validation, enough for a CPU smoke test:

from ultralytics import YOLO

model = YOLO("yolo26n-sem.pt")
model.train(data="cityscapes8.yaml", epochs=1, imgsz=320, device="cpu")
metrics = model.val()
print(f"mIoU: {metrics.miou:.3f}  pixel accuracy: {metrics.pixel_accuracy:.3f}")
                 Class     Images     Pixels       mIoU     PixAcc
                   all          4     192315      0.415      0.906
                  road          4      78529      0.974      0.996
            vegetation          4      37602      0.844      0.936
                person          3        722          0          0
                 rider          1         21          0          0
mIoU: 0.415  pixel accuracy: 0.906

(Per-class rows trimmed.) Pixel accuracy says 91%; mIoU says 42%; the per-class rows say the model never found a single person. All three are true. Road, building, and vegetation own most of the pixels — road alone is 78,529 of 192,315 — so a model that paints them well scores high pixel accuracy while missing every small class. mIoU averages per-class IoU with equal weight, so each missed class drags it down.

Intersection over Union on a bird: the overlap of predicted and ground-truth regions divided by their union, for both boxes and masks

Decision rule

Gate on mIoU and on the IoU of the classes your product acts on. A pedestrian-safety feature with person IoU 0 fails, whatever the pixel accuracy says.

Try It

Run semantic.py on a street photo of your own and list the three largest classes. Adapt walkable.py to answer one coverage question about your photo, such as the share of the lower half that is road. Then train one epoch on cityscapes8.yaml and write down mIoU, pixel accuracy, and every class with IoU 0.

Done When
You've finished the lesson when all of these are true.
  • You have the per-class pixel ratios and one region coverage number for your own image, checked against the saved overlay.

  • You recorded mIoU and pixel accuracy from the training run and listed the zero-IoU classes.

  • You can explain in one sentence why pixel accuracy is higher than mIoU on this run.

Show solution

In our run: mIoU 0.415, pixel accuracy 0.906, and IoU 0 for wall, person, rider, motorcycle, and bicycle, plus every class with no pixels in the four validation images.

Pixel accuracy is higher because it counts pixels, and road, building, vegetation, and sky make up most of every Cityscapes frame. mIoU gives each class equal weight, so five missed small classes pull the average down even though they cover only a few hundred pixels. For a product that acts on people, this model fails its gate on person IoU alone.

What's next

Next, we switch from regions to points and label and train a pose model.