Ultralytics YOLO27:
Beyond Detection: Segment, Pose, OBB, and Classify·Classify, Combine, Ship·Lesson 10/10
Lessonintermediate

Evaluate and Ship Every Task

Gate each task on its own metric at the deployment size, validate every task in one loop, and export every task to ONNX with a verified prediction.

Each task has its own definition of correct: pixel overlap, keypoint similarity, rotated-box IoU, a top-1 hit, or a depth ratio. Teams that reuse their detection checklist ship segmentation models gated on box mAP and pose models gated on boxes that were never the product. And a model that passes its gate can still fail after export: in this lesson, the same pose weights find 5 people at one input size and 3 at another, and the depth model's distances nearly double.

Outcome

Produce a release table with the gating metric and imgsz for each task model, and a verified ONNX export of each one that reloads with the right task and predicts correctly on an in-domain image.

Fast Track
If you already know your way around, here's the short version.
  1. Gate segment on mask mAP50-95, semantic on mIoU, pose on pose mAP50-95, OBB on OBB mAP50-95, classify on top-1, depth on δ1.

  2. Read headline numbers from metrics.results_dict with the task's key, and record imgsz next to every number.

  3. Export with model.export(format="onnx", imgsz=...) at the size you validated.

  4. Reload the export with YOLO("model.onnx") and predict once on an image from the model's own domain before shipping.

Hands-on

One gate per task#

TaskGate onresults_dict keyPython attributeScored by
segmentmask mAP50-95metrics/mAP50-95(M)metrics.seg.mappixel IoU per instance
semanticmIoU (+ per-class IoU)metrics/mIoUmetrics.miouper-class pixel IoU
posepose mAP50-95metrics/mAP50-95(P)metrics.pose.mapOKS
obbOBB mAP50-95metrics/mAP50-95(B)metrics.box.maprotated-box IoU
classifytop-1 accuracymetrics/accuracy_top1metrics.top1correct top class
depthδ1 (+ AbsRel)metrics/delta1metrics.delta1share of pixels within 1.25× of true depth

The performance metrics guide explains each score. Segment and pose runs also report box metrics; keep them as a diagnostic — a box-versus-mask gap points at outline quality — but they are not the product. Detection-specific reading, such as threshold sweeps and the confusion matrix, carries over from Validate and Diagnose.

Validate every task in one loop#

Val mode accepts any task's weights with the matching dataset, so one loop produces the release table's numbers:

from ultralytics import YOLO

runs = {
    "yolo26n-seg.pt": ("coco8-seg.yaml", "metrics/mAP50-95(M)"),
    "yolo26n-sem.pt": ("cityscapes8.yaml", "metrics/mIoU"),
    "yolo26n-pose.pt": ("coco8-pose.yaml", "metrics/mAP50-95(P)"),
    "yolo26n-obb.pt": ("dota8.yaml", "metrics/mAP50-95(B)"),
    "yolo26n-depth.pt": ("depth8.yaml", "metrics/delta1"),
}
for weights, (data, key) in runs.items():
    metrics = YOLO(weights).val(data=data, device="cpu", plots=False, verbose=False)
    print(f"{weights:17s} {key:22s} {metrics.results_dict[key]:.3f}")
yolo26n-seg.pt    metrics/mAP50-95(M)    0.538
yolo26n-sem.pt    metrics/mIoU           0.610
yolo26n-pose.pt   metrics/mAP50-95(P)    0.419
yolo26n-obb.pt    metrics/mAP50-95(B)    0.857
yolo26n-depth.pt  metrics/delta1         0.377

Without an explicit imgsz, each model validates at the size it was trained at: 640 for segment and pose, 768 for depth, 1024 for semantic and OBB. These pretrained numbers are higher than the 320-pixel training runs earlier in the course — partly the training, partly the resolution. Four validation images make every number here a smoke test; the depth task page, for example, reports δ1 on the full NYU benchmark. Classification is left out on purpose: validate your fine-tuned classifier on its own folders, as in the classifier lesson.

Resolution is part of the metric

The same weights score differently at different imgsz. Record imgsz next to every number, validate at the size you will deploy, and never compare numbers measured at different sizes.

Export every task#

Each task exports to ONNX with the same Export mode call. The task is stored in the model metadata, so YOLO("model.onnx") applies the right postprocessing. Runtime choices, parity checks, and TensorRT or OpenVINO tuning belong to Export to ONNX (and Verify Parity); this loop checks that every task survives the round trip:

from ultralytics import YOLO

for weights in ["yolo26n-seg.pt", "yolo26n-sem.pt", "yolo26n-pose.pt", "yolo26n-obb.pt", "yolo26n-cls.pt", "yolo26n-depth.pt"]:
    onnx_path = YOLO(weights).export(format="onnx", imgsz=320)
    onnx_model = YOLO(onnx_path)  # task comes from the ONNX metadata
    result = onnx_model("https://ultralytics.com/images/bus.jpg", imgsz=320, verbose=False)[0]
    print(f"{onnx_model.task:9s} {result.verbose()}")
segment   4 persons, 1 bus,
semantic
pose      3 persons,
obb       (no detections),
classify  minibus 0.38, police_van 0.21, trolleybus 0.05, amphibian 0.05, bullet_train 0.03,
depth     depth 4.67-26.91m,

Every export reloaded with the right task. Three results deserve a second look:

  • Pose found 3 people at 320 pixels, where the 640-pixel PyTorch run found 5.
  • Depth read the scene as 4.7–26.9 m, where the same weights at their native 768 pixels read 2.1–14.9 m.
  • OBB found nothing on a street photo — correct behavior for an aerial model, not an export bug.

PyTorch at imgsz=320 gives the same pose and depth numbers as ONNX, so neither is an export bug either: the input size changed the answer. Export at the imgsz you validated. Semantic results have no per-object summary line, so check result.semantic_mask instead.

Smoke-test every export in its own domain

Predict once with each exported model on an image like the ones it was trained on — street scenes for COCO models, aerial tiles for OBB, indoor or street scenes at the right scale for depth — before you call an export broken or ship it.

If you write your own postprocessing, these are the raw ONNX outputs at imgsz=320, from the export log:

TaskONNX output shape(s)Contents
segment(1, 116, 2100) and (1, 32, 80, 80)boxes + 80 class scores + 32 mask coefficients; mask prototypes
semantic(1, 320, 320)class map, argmax already in the graph
pose(1, 56, 2100)boxes + 1 score + 17 × 3 keypoint values
obb(1, 20, 2100)boxes + 15 class scores + 1 angle
classify(1, 1000)class probabilities
depth(1, 1, 320, 320)depth in meters per pixel

On Ultralytics Platform, the model page shows the task's own metrics (mask mAP for a segmentation model) above an Export tab that produces the same formats:

Ultralytics Platform model page for a segmentation model, with mask mAP50, mAP50-95, precision, and recall cards above the Export tab listing TorchScript, ONNX, OpenVINO, TensorRT, and CoreML

ONNX is the interchange point: one exported file runs in ONNX Runtime or converts onward to other runtimes.

ONNX as an interchange format: models from PyTorch, TensorFlow, Keras, and other frameworks export to ONNX and load in ONNX Runtime or another framework

Ship checklist#

  • The gating metric from the table above meets its target on a held-out set, at the deployment imgsz.
  • The exported model reloads with the expected task and matches PyTorch predictions on a fixed image set.
  • Per-task code paths handle empty results: masks is None, zero keypoints, zero OBBs.
  • Downstream numbers (areas, angles, distances, class probabilities) are logged so you can watch them drift, as described in Observability and Drift.

When the gate is not met, "back to data" usually means one of three levers:

Three ways to improve mAP: data quality, model optimization, and annotation process

Fix data quality and the annotation process first — the label checks from lessons 2 and 6 are the cheapest place to start — and change the model only when the data is clean.

Try It

Pick two of the tasks you trained in this course. For each, write a release table row with the gating metric at your deployment imgsz, export the trained best.pt to ONNX at that size, reload it, and confirm it predicts sensibly on one in-domain image.

Done When
You've finished the lesson when all of these are true.
  • Your release table lists the correct gating metric, its value, and the imgsz for both models.

  • Both models exported to ONNX and reloaded with the expected task.

  • Each export produced a sensible prediction on an image from its own domain.

Show solution
from ultralytics import YOLO

checks = {
    "runs/segment/train/weights/best.pt": ("coco8-seg.yaml", "metrics/mAP50-95(M)", "https://ultralytics.com/images/bus.jpg"),
    "runs/obb/train/weights/best.pt": ("dota8.yaml", "metrics/mAP50-95(B)", "https://ultralytics.com/images/boats.jpg"),
}
for weights, (data, key, image) in checks.items():
    model = YOLO(weights)
    gate = model.val(data=data, imgsz=320, plots=False, verbose=False).results_dict[key]
    onnx_model = YOLO(model.export(format="onnx", imgsz=320))
    result = onnx_model(image, imgsz=320, verbose=False)[0]
    print(f"{onnx_model.task:8s} {key} @320 = {gate:.3f} | ONNX: {result.verbose()}")

Adjust the runs/... paths to the folders your training runs printed (Results saved to ...). With the one-epoch runs from lessons 2 and 7:

segment  metrics/mAP50-95(M) @320 = 0.377 | ONNX: 4 persons, 1 bus,
obb      metrics/mAP50-95(B) @320 = 0.832 | ONNX: 185 ships, 3 harbors,
ModelGating metricValueimgszONNX
segmentmask mAP50-950.377320reloads as segment, finds the bus and 4 people
obbOBB mAP50-950.832320reloads as obb, finds 185 ships and 3 harbors

Each check uses its own domain image: the OBB model gets the aerial harbor photo, not the street scene. Both rows pass the export check; neither model is ready to ship, because one epoch on four validation images is a smoke test, not a gate.

What's next

Take the course exam to earn your certificate, then continue with Ultralytics YOLO in Production to deploy these models with optimized runtimes, tracking, and monitoring.