Monocular Depth Estimation#
Monocular depth estimation predicts a per-pixel depth map from a single RGB image. Each output pixel holds a depth value in meters representing the estimated distance from the camera to that surface point.
The output of a depth model is a dense float map of shape (H, W) aligned to the input image. This per-pixel representation makes monocular depth estimation well-suited for 3D scene reconstruction, robot navigation, AR/VR content creation, and any application that requires spatial layout from a single camera.
Use task=depth or the yolo depth CLI task for monocular depth estimation. YOLO26 depth model files use the -depth suffix, such as yolo26n-depth.pt.
Watch: Monocular Depth Estimation with Ultralytics YOLO26 | Python Tutorial | Vision AI 🚀
Models#
YOLO26 depth models pretrained on a broad multi-dataset mix (indoor + outdoor, ~2.19M images) are shown below. The metrics columns are reported on the NYU Depth V2 Eigen test split.
Models download automatically from the latest Ultralytics release on first use.
| Model | size (pixels) | delta1NYU | abs_relNYU | rmseNYU | Speed CPU ONNX (ms) | Speed T4 TensorRT10 (ms) | params (M) | FLOPs (B) |
|---|---|---|---|---|---|---|---|---|
| YOLO26n-depth | 768 | 0.882 | 0.109 | 0.414 | 272.0 ± 27.2 | 2.7 ± 0.1 | 6.4 | 46.9 |
| YOLO26s-depth | 768 | 0.896 | 0.104 | 0.399 | 393.7 ± 13.1 | 3.8 ± 0.0 | 13.2 | 67.9 |
| YOLO26m-depth | 768 | 0.921 | 0.089 | 0.364 | 621.5 ± 49.7 | 6.0 ± 0.1 | 23.3 | 130.7 |
| YOLO26l-depth | 768 | 0.930 | 0.083 | 0.351 | 821.9 ± 50.7 | 7.7 ± 0.1 | 27.7 | 157.2 |
| YOLO26x-depth | 768 | 0.933 | 0.080 | 0.344 | 1240.9 ± 73.3 | 13.6 ± 0.2 | 57.0 | 302.0 |
- delta1NYU is the percentage of pixels where the predicted depth is within a factor of 1.25 of the ground truth, on the NYU Depth V2 Eigen test split (654 images) with multi-scale + horizontal-flip TTA and log-least-squares alignment.
- Single-scale accuracy without TTA is reproducible with
yolo depth val model=yolo26n-depth.pt data=nyu-depth.yaml imgsz=768 device=0(substitutemodel=for each size), which uses median (scale-only) alignment and scores lower: delta1 0.783 (n), 0.793 (s), 0.840 (m), 0.853 (l), 0.860 (x). - abs_rel is the mean absolute relative error between predicted and ground-truth depth values.
- rmse is the root mean squared error in meters.
- Speed is inference-only latency (pre/post-processing excluded) at
imgsz=768,batch=1, reported as mean ± std over timed runs after warmup. CPU ONNX is ONNX Runtime fp32 on a 32-core Intel Xeon (Skylake); T4 TensorRT10 is TensorRT fp16 on a Tesla T4. - params and FLOPs are measured at 768×768, the training resolution of the released weights.
Speed compared to Depth Anything V2#
Depth Anything V2 is a widely used open baseline for monocular depth. Its DINOv2 vision transformer backbone and DPT decoder are compute-heavy, so on the same Tesla T4 under TensorRT fp16 the smallest released Depth Anything V2 model is slower than every YOLO26 depth model — including YOLO26x-depth, which carries more than twice the parameters.
At ~768 px — imgsz=768 for YOLO26, the resolution its released weights are trained at, and 770 px for Depth Anything V2, whose DINOv2 backbone requires a multiple of its patch size of 14:
| Model | params (M) | FLOPs (B) | T4 TensorRT10 (ms) | FPS | Speedup vs V2 Small | Speedup vs V2 Base |
|---|---|---|---|---|---|---|
| Depth Anything V2 Base | 97.5 | 1025.5 | 55.40 | 18.1 | — | — |
| Depth Anything V2 Small | 24.8 | 346.9 | 20.99 | 47.6 | — | — |
| YOLO26x-depth | 57.0 | 302.0 | 13.57 | 73.7 | 1.5× | 4.1× |
| YOLO26l-depth | 27.7 | 157.2 | 7.68 | 130.2 | 2.7× | 7.2× |
| YOLO26m-depth | 23.3 | 130.7 | 6.00 | 166.7 | 3.5× | 9.2× |
| YOLO26s-depth | 13.2 | 67.9 | 3.82 | 261.6 | 5.5× | 14.5× |
| YOLO26n-depth | 6.4 | 46.9 | 2.73 | 365.8 | 7.7× | 20.3× |
At ~640 px — imgsz=640 and 644 px respectively — the ordering is unchanged, and YOLO26n-depth is 5.9× faster than Depth Anything V2 Small:
| Model | T4 TensorRT10 (ms) | FPS |
|---|---|---|
| Depth Anything V2 Base | 35.10 | 28.5 |
| Depth Anything V2 Small | 13.62 | 73.4 |
| YOLO26x-depth | 10.26 | 97.5 |
| YOLO26l-depth | 6.14 | 162.8 |
| YOLO26m-depth | 4.71 | 212.1 |
| YOLO26s-depth | 3.08 | 324.4 |
| YOLO26n-depth | 2.29 | 436.9 |
- Latency is inference-only,
batch=1, TensorRT fp16 on a Tesla T4 — the same harness as the model table above, shown here to two decimals; FPS is the reciprocal of the unrounded latency. The Speedup columns divide the Depth Anything V2 Small (20.99 ms) and Base (55.40 ms) latency by the YOLO26 latency. - Depth Anything V2 Small and Base are the released ViT-S and ViT-B checkpoints.
- The comparison covers latency only — the two model families are not evaluated here under a shared accuracy protocol.
Depth range and the log-depth head#
The depth head predicts exp(logit) — unbounded (~0.02–150 m) — and decouples scene shape from absolute scale: the network predicts a relative log-depth field, and absolute meters are set by a separate two-parameter transform (exp(a·log d + b)) recovered at evaluation, by lightweight calibration, or by fine-tuning. The common alternative, a bounded sigmoid × max_depth head, instead bakes a fixed ceiling into the architecture, so any depth beyond max_depth is clipped — which prevents training on, and predicting, longer-range scenes.
Why the head is unbounded: evidence across depth ranges#
Controlled A/B — same data, same schedule, only the head differs. Training both heads from scratch on an identical mix of indoor (≤10 m) and outdoor (≤80 m) data:
| Head | Output range | Mixed-range val δ1 | Training behavior |
|---|---|---|---|
sigmoid × max_depth (10 m) | bounded 0–10 m | 0.221 | plateaus at epoch 1 |
log (unbounded) | 0–~150 m | 0.367 (+66%) | improves throughout |
The bounded head cannot represent the >10 m majority of outdoor pixels, so it stops improving almost immediately; the log head learns from the full range.
The failure mode it fixes — single-dataset fine-tuning, stratified by range. Fine-tuning a bounded (10 m) head on individual datasets works when the data fits the cap and collapses when it exceeds it:
| Dataset | Depth range | Bounded-head δ1 |
|---|---|---|
| SUN RGB-D | ≤10 m | 0.78 ✅ |
| Hypersim | mostly ≤10 m | 0.74 ✅ |
| KITTI | ~80 m | 0.08 ❌ (abs_rel ≈ 7.0 — the 80/10 scale mismatch) |
| vKITTI2 | 80 m | 0.04 ❌ |
The collapse lands exactly at the cap boundary. The log head has no such boundary.
Released models — cross-range performance. The shipped log-head family, evaluated across benchmarks spanning 10 m (NYU, iBims-1) to 80 m (KITTI), with scale-aligned δ1:
| Benchmark | Range | YOLO26x-depth (log) | prior bounded release |
|---|---|---|---|
| NYU Eigen | 10 m | 0.933 | 0.934 |
| iBims-1 | 10 m | 0.961 | 0.945 |
| ETH3D | ~60 m | 0.959 | 0.931 |
| Make3D | ~70 m | 0.302 | 0.296 |
| KITTI Eigen | 80 m | 0.942 | 0.891 |
| Mean | — | 0.819 | 0.799 |
Both columns are as published. The other rows are scored with the TTA and log-least-squares protocol described on their dataset pages, which the validator in this repo does not implement, so the KITTI row cannot be re-measured on the same footing; the KITTI page carries numbers measured on the canonical 652-frame Eigen split instead.
The largest gains are on the longer-range outdoor benchmarks (KITTI, ETH3D) — exactly where a fixed 10 m ceiling hurts most — while indoor performance is retained.
Train#
Train YOLO26n-depth on the Depth8 dataset for 100 epochs at image size 640. For a full list of available arguments see the Configuration page.
from ultralytics import YOLO
# Load a model
model = YOLO("yolo26n-depth.yaml") # build a new model from YAML
model = YOLO("yolo26n-depth.pt") # load a pretrained model (recommended for training)
model = YOLO("yolo26n-depth.yaml").load("yolo26n-depth.pt") # build from YAML and transfer weights
# Train the model
results = model.train(data="depth8.yaml", epochs=100, imgsz=640)See full train mode details in the Train page.
Fine-tuning on your own data#
When adapting a pretrained depth model to a custom dataset, lower the learning rate and use the AdamW optimizer. The default optimizer settings are tuned for training from scratch (SGD with lr0=0.01); applied to an already-converged depth model they can overwrite the pretrained knowledge and degrade results — especially when fine-tuning on a single domain.
from ultralytics import YOLO
model = YOLO("yolo26s-depth.pt") # start from pretrained weights
model.train(
data="path/to/your_dataset.yaml",
epochs=20,
imgsz=640,
optimizer="AdamW",
lr0=1e-4, # ~100x below the from-scratch default
warmup_bias_lr=1e-4, # keep warmup gentle too
)Additional tips:
- Augmentation is controlled by the standard args (
degrees,translate,scale,shear,perspective,flipud,fliplr,hsv_h,hsv_s,hsv_v); the geometric warp and flips are applied identically to the paired depth map. To see the exact recipe used for the released YOLO26-Depth weights, inspect thetrain_argsstored in the checkpoint — see Inspecting YOLO26 Checkpoint Training Args. mosaic,mixup,cutmix, andcopy_pasteare not implemented for depth. The depth dataset loader automatically sets these probabilities to 0, so passing them has no effect. These augmentations are not supported because they combine multiple images, which would produce invalid paired depth maps.- Any depth range works out of the box. The
log-head models predict unbounded depth, so they adapt to short-range (macro) or long-range (outdoor/driving) data without changes. Settingmax_depth:in your dataset YAML (in meters) bounds which GT pixels count toward validation metrics. - Retain general performance. If you need the model to stay accurate on scenes beyond your training set, mix a small fraction (~5–10%) of diverse general-purpose images into your training data; this substantially reduces forgetting during fine-tuning.
- Train from scratch (
model=yolo26s-depth.yaml) only if your domain is very different and you have a large dataset — there the default SGDlr0=0.01is appropriate, since there are no pretrained weights to preserve.
Calibrating the depth scale#
The depth head separates shape (relative scene structure) from scale (absolute meters). If a model already produces good relative depth on your scenes but the absolute values are off for your camera, you can correct the scale in seconds with model.calibrate() — a closed-form fit of a two-parameter log-affine against a small labeled set, with no gradient training and no change to the network weights, so it cannot degrade the relative structure.
from ultralytics import YOLO
model = YOLO("yolo26s-depth.pt")
# Fit scale on a labeled split (~100+ images is enough), then persist it
model.calibrate(data="path/to/your_dataset.yaml")
model.save("yolo26s-depth-calibrated.pt")Calibration needs ground-truth depth to fit against, so it runs on a labeled split — it is not something that can happen at blind inference. It fits and scores on the same pixels the val metrics evaluate: GT at or beyond the dataset YAML's max_depth (default 100 m) is excluded. Use it when relative depth is already good and only the scale/range is wrong; if the relative structure itself needs to change for your domain, fine-tune instead.
Training does this for you automatically: after model.train(...) completes, the best and last checkpoints are calibrated on the validation set so they output metric-scaled depth out of the box.
The released yolo26*-depth.pt checkpoints ship with this calibration already baked in, fit on the pretraining validation mix. It is a single global scale across all domains, so for the most accurate absolute depth on a specific camera or scene type, run model.calibrate() on a small labeled split from your own data — it replaces the baked-in fit.
Dataset format#
Depth estimation datasets pair each RGB image with a corresponding depth file. Depth targets are stored as .npy arrays containing float32 values in meters. The dataset YAML points to an images/ directory; the loader derives the depth file path by replacing the images component with depth and replacing the image extension with .npy.
dataset/
├── images/
│ ├── train/
│ └── val/
└── depth/
├── train/
└── val/For example, an image at images/train/scene_001.jpg is paired with a depth map at depth/train/scene_001.npy. See the Depth Estimation Dataset Guide for the full format specification.
Val#
Validate a trained YOLO26n-depth model accuracy on a depth estimation dataset. Pass data explicitly so validation uses the intended dataset YAML. The released weights are trained at imgsz=768, so validate and predict at that size for best accuracy.
from ultralytics import YOLO
# Load a model
model = YOLO("yolo26n-depth.pt") # load an official model
model = YOLO("path/to/best.pt") # load a custom model
# Validate the model
metrics = model.val(data="nyu-depth.yaml")
metrics.delta1 # percentage of pixels within threshold δ=1.25
metrics.abs_rel # mean absolute relative error
metrics.rmse # root mean squared error (meters)
metrics.silog # scale-invariant logarithmic errorPredict#
Use a trained YOLO26n-depth model to run predictions on images.
from ultralytics import YOLO
# Load a model
model = YOLO("yolo26n-depth.pt") # load an official model
model = YOLO("path/to/best.pt") # load a custom model
# Predict with the model
results = model("https://ultralytics.com/images/bus.jpg") # predict on an image
# Access the results
for result in results:
depth_map = result.depth.data.cpu().numpy() # torch.Tensor -> NumPy float32, shape (H, W), metersSee full predict mode details in the Predict page.
Results Output#
YOLO depth estimation returns one Results object per image. Each result stores one dense float depth map for the full image.
| Attribute | Type | Shape | Description |
|---|---|---|---|
result.depth | DepthMap | (H,W) | Dense per-pixel depth map. |
result.depth.data | torch.Tensor | (H,W) | Depth values in meters; call .cpu().numpy() for NumPy. |
result.boxes | - | - | No instance boxes. |
result.masks | - | - | No instance masks. |
For task-specific Results fields across every task, see the Predict Results by Task section.
Colorizing the depth map#
The raw depth map is a single-channel float array in meters — useful for computation, but hard to read directly. To turn it into a color image, use the colorize_depth helper in ultralytics.utils.plotting, which maps the (H, W) depth array to a (H, W, 3) BGR uint8 image (invalid pixels <= 0 are rendered black).
result.plot() already blends this colorization over the input image using the defaults (cmap="jet", mode="disparity"); call colorize_depth directly when you want a standalone colored depth image or a different colormap or normalization.
import cv2
from ultralytics import YOLO
from ultralytics.utils.plotting import colorize_depth
model = YOLO("yolo26n-depth.pt")
result = model("https://ultralytics.com/images/bus.jpg")[0]
depth = result.depth.data.cpu().numpy() # (H, W) float32, meters
# Colorize with near = warm and save
cv2.imwrite("depth_colored.png", colorize_depth(depth, cmap="spectral")) # (H, W, 3) BGR uint8
# Fix the range to 0-20 m so the same color means the same distance across frames
cv2.imwrite("depth_metric.png", colorize_depth(depth, vmin=0.0, vmax=20.0, cmap="inferno", mode="metric"))
# Blended overlay straight from the Results object (uses cmap="jet", mode="disparity")
result.save("depth_overlay.png")Every colormap runs cool/dark → warm/bright. The mode decides which end is near, and vmin/vmax lock the range across frames so a color always means the same distance.
| Argument | Value | Description |
|---|---|---|
cmap | jet (default) | High-contrast blue → green → red, the palette result.plot() uses. |
cmap | inferno | Black → purple → orange → yellow. Perceptually uniform, colorblind-friendly, readable in grayscale. |
cmap | spectral | Red → yellow → green → blue (matplotlib Spectral_r). |
mode | disparity (default) | Normalizes inverse depth (1/d) between the 2nd and 98th percentiles; warm marks near and far outliers are absorbed. |
mode | metric | Normalizes depth in meters linearly between vmin and vmax; warm marks far, so colors track true distance. |
Export#
Export a YOLO26n-depth model to a different format like ONNX, CoreML, etc.
from ultralytics import YOLO
# Load a model
model = YOLO("yolo26n-depth.pt") # load an official model
model = YOLO("path/to/best.pt") # load a custom model
# Export the model
model.export(format="onnx")Available YOLO26 depth estimation export formats are in the table below. You can export to any format using the format argument, i.e., format='onnx' or format='engine'. You can predict or validate directly on exported models, i.e., yolo predict model=yolo26n-depth.onnx. Usage examples are shown for your model after export completes.
| Format | format Argument | Model | Metadata | Arguments |
|---|---|---|---|---|
| PyTorch | - | yolo26n-depth.pt | ✅ | - |
| TorchScript | torchscript | yolo26n-depth.torchscript | ✅ | imgsz, quantize, dynamic, nms, batch, device |
| ONNX | onnx | yolo26n-depth.onnx | ✅ | imgsz, quantize, dynamic, simplify, opset, nms, batch, data, fraction, device |
| OpenVINO | openvino | yolo26n-depth_openvino_model/ | ✅ | imgsz, quantize, dynamic, nms, batch, data, fraction, device |
| TensorRT | engine | yolo26n-depth.engine | ✅ | imgsz, quantize, dynamic, simplify, opset, workspace, nms, batch, data, fraction, device |
| CoreML | coreml | yolo26n-depth.mlpackage | ✅ | imgsz, dynamic, quantize, nms, batch, device |
| TF SavedModel | saved_model | yolo26n-depth_saved_model/ | ✅ | imgsz, keras, quantize, opset, nms, batch, data, fraction, device |
| TF GraphDef | pb | yolo26n-depth.pb | ❌ | imgsz, opset, batch, device |
| TF Edge TPU | edgetpu | yolo26n-depth_edgetpu.tflite | ✅ | imgsz, quantize, opset, data, fraction, device |
| PaddlePaddle | paddle | yolo26n-depth_paddle_model/ | ✅ | imgsz, batch, device |
| MNN | mnn | yolo26n-depth.mnn | ✅ | imgsz, batch, dynamic, quantize, simplify, opset, nms, device |
| NCNN | ncnn | yolo26n-depth_ncnn_model/ | ✅ | imgsz, quantize, batch, device |
| IMX500 | imx | yolo26n-depth_imx_model/ | ✅ | imgsz, quantize, data, fraction, nms, device |
| RKNN | rknn | yolo26n-depth_rknn_model/ | ✅ | imgsz, batch, name, quantize, simplify, opset, data, fraction, device |
| ExecuTorch | executorch | yolo26n-depth_executorch_model/ | ✅ | imgsz, batch, device |
| Axelera | axelera | yolo26n-depth_axelera_model/ | ✅ | imgsz, batch, quantize, data, fraction, device |
| DEEPX | deepx | yolo26n-depth_deepx_model/ | ✅ | imgsz, quantize, simplify, opset, data, optimize, device |
| Qualcomm QNN | qnn | yolo26n-depth_qnn.onnx | ✅ | imgsz, batch, name, quantize, simplify, opset, data, fraction, device |
| LiteRT | litert | yolo26n-depth.tflite | ✅ | imgsz, quantize, batch, data, fraction, device |
| Hailo | hailo | yolo26n-depth_hailo_model/ | ✅ | imgsz, name, quantize, data, fraction, simplify, conf, iou |
| Huawei Ascend | ascend | yolo26n-depth_ascend_model/ | ✅ | imgsz, batch, name, quantize, opset, simplify, nms |
See full export details in the Export page.
FAQ#
Prepare paired RGB images and
.npydepth files, then create a dataset YAML pointing to yourimages/directory. The loader finds depth files automatically by replacingimageswithdepthin the path and swapping the image extension for.npy.Start from pretrained weights and use a low learning rate with AdamW so the fine-tune retains what the model already knows (see Fine-tuning on your own data for why):
Examplefrom ultralytics import YOLO # Load a pretrained YOLO26 depth model model = YOLO("yolo26s-depth.pt") # Fine-tune on a custom depth dataset results = model.train( data="path/to/your_dataset.yaml", epochs=20, imgsz=640, optimizer="AdamW", lr0=1e-4, warmup_bias_lr=1e-4, )Check the Configuration page for more available arguments.
Depth estimation validation reports the metric set used by Depth Anything and related monocular-depth work:
- delta1 / delta2 / delta3 — percentage of pixels where the ratio of predicted to ground-truth depth (or its inverse) is below 1.25, 1.25², and 1.25³ respectively. Higher is better.
- abs_rel — mean absolute relative error. Lower is better.
- rmse — root mean squared error in meters. Lower is better.
- silog — scale-invariant logarithmic error. Lower is better.
Each prediction is median-aligned to its ground truth per image, each metric is finalized on that image, and those per-image results are averaged over the validation set, so every image weighs the same regardless of how many valid depth pixels it holds. Images with fewer than 10 valid ground-truth pixels are skipped and do not enter that average, since aligning the median of a handful of pixels is meaningless; non-finite predictions are instead scored at the depth bounds. This matches the per-sample averaging and the 10-pixel floor used by Depth Anything V2.
The depth map is stored as a
torch.Tensorof shape(H, W)where each value is the predicted depth in meters (useresult.depth.data.cpu().numpy()for a NumPyfloat32array). Access it throughresult.depth.dataafter running prediction. The map is aligned to the input image resolution.Ultralytics YOLO26 provides built-in dataset YAML configurations for several depth estimation datasets including NYU Depth V2, KITTI, Hypersim, SUN RGB-D, and ARKitScenes. See the Depth Estimation Dataset Guide for the full list and format details.
Validate a pretrained YOLO26 depth model by supplying the dataset YAML used for evaluation:
Examplefrom ultralytics import YOLO # Load a pretrained model model = YOLO("yolo26n-depth.pt") # Validate the model metrics = model.val(data="nyu-depth.yaml") print("delta1:", metrics.delta1) print("abs_rel:", metrics.abs_rel) print("rmse:", metrics.rmse)Export a YOLO26 depth model to ONNX with Python or CLI:
Examplefrom ultralytics import YOLO # Load a pretrained model model = YOLO("yolo26n-depth.pt") # Export the model to ONNX format model.export(format="onnx")For more details on exporting to various formats, refer to the Export page.