Ultralytics YOLO27: Join the waitlist.
Get Started
Computer Vision Foundations·Pick a Task·Lesson 3/10
Lessonbeginner

Understand Object Detection

Read a detector's output — class, box, confidence — and set the confidence and IoU thresholds from the cost of each mistake.

Most production vision systems are detection systems with logic on top: counters, trackers, zone alerts, privacy blurs. When one of them misbehaves, the cause is usually not the model but a threshold nobody chose on purpose. Know exactly what a detector returns and which two knobs shape that list, and you can debug almost anything built on it.

Outcome

Read YOLO26 detections in code and choose conf and iou from the cost of a miss versus a false alarm.

Fast Track
If you already know your way around, here's the short version.
  1. Each detection is a class, a box, and a confidence score.

  2. conf drops weak boxes (default 0.25); iou decides when NMS treats two boxes as duplicates (default 0.7).

  3. Misses are expensive? Lower conf. False alarms are visible? Raise it.

  4. Neighbors disappearing? Raise iou.

  5. Confidence ranks boxes; it isn't a calibrated probability.

Hands-on

What a detector returns#

Waterfront promenade with Ultralytics YOLO detections: people, two cyclists, a dog, and two cars, each box tagged with its class and confidence

Every tag in that image is one row of output: a class name, a confidence, and the box around it. A detection model returns exactly that list:

  • Class — an integer index mapped to a name through result.names (0 is person, 5 is bus).
  • Box — four numbers locating the object; xyxy gives the top-left and bottom-right corners in pixels.
  • Confidence — a score from 0 to 1 used to rank and filter detections.

Counting, alerting, tracking, and blurring are code you write on top of these rows.

from ultralytics import YOLO

model = YOLO("yolo26n.pt")
result = model("https://ultralytics.com/images/bus.jpg", verbose=False)[0]

for box in result.boxes:
    name = result.names[int(box.cls)]
    x1, y1, x2, y2 = box.xyxy[0].tolist()
    print(f"{name:8} conf={float(box.conf):.2f} box=({x1:.0f}, {y1:.0f}, {x2:.0f}, {y2:.0f})")
bus      conf=0.88 box=(0, 230, 803, 751)
person   conf=0.87 box=(49, 398, 240, 902)
person   conf=0.86 box=(223, 405, 345, 861)
person   conf=0.85 box=(670, 395, 810, 880)
person   conf=0.65 box=(0, 553, 60, 871)

Line by line: the bus box starts at x=0 because the bus runs off the left edge; three people score 0.85–0.87; the fourth person, cut off at the left border, scores only 0.65 — the model sees half a body. Low confidence on truncated objects is normal, and it's the first place a threshold bites.

Each box comes in four formats; use the one your downstream code expects:

AttributeFormatTypical use
boxes.xyxyCorner pixels (x1, y1, x2, y2)Cropping, drawing, overlap rules
boxes.xywhCenter pixels plus width and heightSize filters, motion
boxes.xyxynCorners normalized to 0–1Rules that survive resolution changes
boxes.xywhnCenter and size normalized to 0–1YOLO label files

From candidates to a final list#

Internally the model scores many candidate boxes per object. Two filters turn them into the list you see:

Confidence filter. conf drops boxes below a threshold; the prediction default is 0.25. On the bus image the count changes quickly:

confBoxes on bus.jpg
0.107
0.255
0.505
0.754

Duplicate filter. Non-maximum suppression (NMS) keeps the highest-confidence box and drops other boxes of the same class that overlap it by more than iou (default 0.7). Overlap is measured as intersection over union: the shared area divided by the combined area.

Intersection over Union on a bird: overlapping predicted and ground-truth boxes, and matching masks, with IoU as their overlap divided by their union

iouBoxes on bus.jpgEffect
0.35Strict: overlapping boxes are merged
0.75Default
0.958Permissive: near-duplicates survive

Choose thresholds from the cost of mistakes#

If…Then…
A miss is expensive (safety alerts, defects)Lower conf and accept some false alarms
A false alarm is visible to users (search, tagging)Raise conf
You report countsSweep conf on validation images and keep the one with the smallest count error
Real neighbors vanish in crowdsRaise iou, e.g. 0.7 → 0.8
The same object gets two boxesLower iou
Classes differ in difficultyPredict at the lowest conf you need, then filter per class in code

Warehouse safety monitoring is the classic low-threshold case; Predict mode lists every inference argument.

Pitfalls#

Reading confidence as a probability

Symptom: "the model is 88% sure this is a bus", and thresholds chosen by intuition. Fix: treat confidence as a ranking score. In YOLO26 one score covers both "there is an object" and "it is this class", and it isn't calibrated — pick the threshold from validation data, not from the number's meaning.

One of two neighbors is missing? Check NMS

When two people stand shoulder to shoulder, their boxes overlap heavily and NMS can delete a real person as a "duplicate". Raise iou and the second box comes back. YOLO26 also ships an end-to-end head that needs no NMS at all; pass nms=False to use it.

Tuning on one image

Symptom: a threshold that looks perfect on the demo image fails on the real camera. Fix: sweep thresholds on a validation set from the deployment camera; one image only shows you what the knobs do.

Four ways to be wrong#

FailureWhat's brokenTypical fix
Right class, loose boxLocalizationTighter, consistent box rules; larger imgsz for small objects
Good box, wrong classClassificationMore examples of the confused pair, or merge the classes
No box at allRecallMore examples of that condition: lighting, size, occlusion
Box on backgroundPrecisionHard-negative images of whatever fooled the model

Two lists make diagnosis fast: true positives with low confidence (the model is unsure about real objects) and false positives with high confidence (it's sure about the wrong thing). They point to different fixes.

Try It

Run the solution with conf=0.05 and open the saved image. Mark every box below 0.5 as a true or false positive and note why each false positive happened. Then write down the conf you'd ship for a pedestrian counter on this camera, with one sentence of justification.

Done When
You've finished the lesson when all of these are true.
  • You have a list of every box below 0.5 on bus.jpg, each marked true or false positive with a reason.

  • You have written down a conf value for a pedestrian counter and the mistake it protects against.

  • You can explain, with the iou table, why raising iou brings back a suppressed neighbor.

Show solution
from ultralytics import YOLO

model = YOLO("yolo26n.pt")
result = model("https://ultralytics.com/images/bus.jpg", conf=0.05, save=True, verbose=False)[0]

for box in sorted(result.boxes, key=lambda b: -float(b.conf)):
    name, conf = result.names[int(box.cls)], float(box.conf)
    x1, y1, x2, y2 = box.xyxy[0].tolist()
    print(f"{conf:.2f} {name:10} ({x1:.0f}, {y1:.0f}, {x2:.0f}, {y2:.0f})")
0.88 bus        (0, 230, 803, 751)
0.87 person     (49, 398, 240, 902)
0.86 person     (223, 405, 345, 861)
0.85 person     (670, 395, 810, 880)
0.65 person     (0, 553, 60, 871)
0.18 person     (0, 600, 40, 871)
0.12 stop sign  (0, 252, 35, 326)
0.08 handbag    (43, 732, 78, 812)
0.07 skateboard (669, 822, 808, 881)
0.06 handbag    (225, 634, 291, 705)

Every box below 0.5 is a false positive. The 0.18 person duplicates the 0.65 person cut off at the left edge. The 0.12 stop sign is a real road sign with the wrong class. The handbags and the skateboard sit on hands and shoes. Every real object scores above 0.6, so conf=0.5 keeps all four people and drops all the noise for a pedestrian counter — it protects against double-counting. Confirm it on a validation set from the real camera before shipping: the truncated person at 0.65 shows how close real objects can get to the line.

What's next

Next: when a rectangle isn't enough and you need the object's exact shape.