DA3 COLMAP Export: From Depth Maps to Point Clouds
Overview
Depth Anything 3 (DA3) uses a fundamentally different approach than traditional COLMAP for generating point clouds. Instead of feature extraction, matching, and bundle adjustment, DA3 directly predicts depth maps and camera poses, then converts them to 3D points using geometric projection.
Traditional COLMAP Workflow
Traditional COLMAP follows this pipeline:
- Feature Extraction: Extract keypoints and descriptors (e.g., SIFT ) from images
- Feature Matching: Match features across image pairs
- Initial Reconstruction: Triangulate initial 3D points from matched features
- Bundle Adjustment: Optimize camera poses and 3D points jointly to minimize reprojection error
- Dense Reconstruction: Generate dense point cloud (optional, using MVS)
Key characteristic: COLMAP starts with sparse features and builds up to a reconstruction through optimization.
DA3 Workflow
DA3 takes a direct approach:
Model Inference: Neural network directly predicts:
- Depth maps:
(N, H, W)- per-pixel depth values - Camera extrinsics:
(N, 3, 4)- world-to-camera transformation matrices - Camera intrinsics:
(N, 3, 3)- focal length and principal point - Confidence maps:
(N, H, W)- per-pixel confidence scores
- Depth maps:
Depth-to-Point Conversion: For each pixel with valid depth:
- Convert pixel coordinates
(u, v)to camera-space ray direction - Scale ray by depth to get 3D point in camera space
- Transform to world space using camera pose
- Convert pixel coordinates
COLMAP Structure Creation: Build COLMAP reconstruction structure:
- Create 3D points from converted depth maps
- Set up cameras, images, and frames
- Link 2D observations to 3D points
Key characteristic: DA3 starts with dense depth predictions and directly converts them to 3D points.
Detailed Process: Depth Maps β COLMAP Point Cloud
Step 1: Model Inference
The DA3 model takes images as input and outputs:
prediction = model.inference(images)
# prediction.depth: (N, H, W) - depth maps
# prediction.extrinsics: (N, 3, 4) - camera poses (w2c format)
# prediction.intrinsics: (N, 3, 3) - camera intrinsics
# prediction.conf: (N, H, W) - confidence maps
Step 2: Depth-to-World Point Conversion
File: src/depth_anything_3/utils/export/glb.py (lines 205-252)
The function _depths_to_world_points_with_colors() performs the conversion:
def _depths_to_world_points_with_colors(
depth: np.ndarray, # (N, H, W)
K: np.ndarray, # (N, 3, 3) - intrinsics
ext_w2c: np.ndarray, # (N, 3, 4) - extrinsics (world-to-camera)
images_u8: np.ndarray, # (N, H, W, 3) - RGB images
conf: np.ndarray, # (N, H, W) - confidence
conf_thr: float, # confidence threshold
) -> tuple[np.ndarray, np.ndarray]: # (points, colors)
For each frame i:
Filter valid pixels:
valid = np.isfinite(depth[i]) & (depth[i] > 0) & (conf[i] >= conf_thr)Create pixel grid:
us, vs = np.meshgrid(np.arange(W), np.arange(H)) pix = np.stack([us, vs, ones], axis=-1) # (H*W, 3) - homogeneous pixel coordsConvert pixels to camera-space rays:
K_inv = np.linalg.inv(K[i]) # Inverse intrinsics rays = K_inv @ pix[valid].T # (3, M) - ray directions in camera spaceScale rays by depth to get 3D points in camera space:
Xc = rays * depth[i][valid][None, :] # (3, M) - 3D points in camera frameTransform to world space:
c2w = np.linalg.inv(ext_w2c[i]) # Convert w2c to c2w (camera-to-world) Xc_h = np.vstack([Xc, np.ones((1, M))]) # Homogeneous coordinates Xw = (c2w @ Xc_h)[:3].T # (M, 3) - 3D points in world spaceExtract colors:
colors = images_u8[i][valid] # (M, 3) - RGB colors
Result: Dense point cloud with M points (where M = number of valid pixels across all frames after confidence filtering).
Step 3: COLMAP Structure Creation
File: src/depth_anything_3/utils/export/colmap.py (lines 28-127)
The export_to_colmap() function builds the COLMAP reconstruction:
3.1 Create 3D Points
reconstruction = pycolmap.Reconstruction()
# Add all 3D points
for vidx in range(num_points):
point3d_id = reconstruction.add_point3D(
points[vidx], # 3D position (x, y, z)
pycolmap.Track(), # Empty track (will be populated)
colors[vidx] # RGB color
)
3.2 Set Up Cameras, Images, and Frames
For each frame:
Create Camera:
camera = pycolmap.Camera() camera.model = pycolmap.CameraModelId.PINHOLE camera.params = [fx, fy, cx, cy] # Intrinsics in COLMAP format camera.width = orig_w camera.height = orig_hCreate Rig (COLMAP's camera rig structure):
rig = pycolmap.Rig() rig.rig_id = camera.camera_id rig.add_ref_sensor(camera.sensor_id)Create Frame (camera pose):
frame = pycolmap.Frame() frame.rig_from_world = cam_from_world # w2c transformationCreate Image:
image = pycolmap.Image() image.name = os.path.basename(image_paths[fidx]) image.camera_id = camera.camera_id
3.3 Link 2D Observations to 3D Points
For each frame, create 2D point observations that correspond to the 3D points:
# Find which 3D points are visible in this frame
points_in_frame = points_xyf[:, 2] == fidx # points_xyf: (x, y, frame_idx)
# Create 2D observations
for vidx in np.where(points_in_frame)[0]:
point2d = points_xyf[vidx][:2] # (u, v) pixel coordinates
point3d_id = point3d_ids[vidx] # Corresponding 3D point ID
# Add 2D observation
point2d_list.append(pycolmap.Point2D(point2d, point3d_id))
# Update track (which images see this 3D point)
reconstruction.point3D(point3d_id).track.add_element(
image.image_id, len(point2d_list) - 1
)
3.4 Export
reconstruction.write(export_dir) # Writes COLMAP binary format
Key Differences from Traditional COLMAP
| Aspect | Traditional COLMAP | DA3 |
|---|---|---|
| Input | Images only | Images (poses optional) |
| Output | Sparse β Dense reconstruction | Dense depth maps + poses |
| Point Cloud Source | Triangulated from matched features | Directly from depth maps |
| Optimization | Bundle adjustment optimizes poses & points | Poses & depth predicted by model |
| Density | Sparse initially, dense via MVS | Dense from the start |
| Feature Matching | Required | Not needed |
| Bundle Adjustment | Required | Not needed |
Advantages of DA3 Approach
- Dense from the start: Every pixel with valid depth becomes a 3D point
- No feature matching: Avoids issues with textureless regions or repetitive patterns
- Consistent geometry: Model enforces geometric consistency across views
- Faster: No iterative optimization required
- Works with fewer images: Can work with just 2 images (traditional COLMAP needs more)
Limitations
- No bundle adjustment: Poses and depths are fixed from model prediction
- Model-dependent quality: Quality depends on model training, not geometric optimization
- Scale ambiguity: For monocular inputs, scale may need to be recovered (unless using metric models)
Confidence Filtering
DA3 uses confidence maps to filter unreliable depth predictions:
conf_thresh = np.percentile(prediction.conf, conf_thresh_percentile) # Default: 40th percentile
valid = conf >= conf_thresh
This means only the top 60% most confident depth predictions are converted to 3D points, reducing noise in the point cloud.
Deep Dive: How Confidence is Calculated
Confidence in DA3 is learned by the neural network during training, not computed from depth errors or geometric consistency at inference time. Here's the complete pipeline:
1. Network Architecture
File: src/depth_anything_3/model/dpt.py and src/depth_anything_3/model/dualdpt.py
The depth head outputs 2 channels when confidence is enabled (output_dim=2):
- Channel 0: Depth logits
- Channel 1: Confidence logits
Configuration (from da3-large.yaml):
head:
output_dim: 2 # 2 channels = 1 for depth + 1 for confidence
conf_activation: 'expp1' # Activation function for confidence
2. Confidence Activation Function
File: src/depth_anything_3/model/dpt.py, lines 286-309
The confidence logits go through the expp1 activation:
def _apply_activation_single(self, x: torch.Tensor, activation: str = "expp1"):
if activation == "expp1":
return torch.exp(x) + 1
Formula: confidence = exp(logits) + 1
Properties:
- Range:
[1, +β)- Confidence is always β₯ 1, unbounded above - Interpretation: Higher values = higher confidence
- Minimum: The
+1ensures minimum confidence of 1 (no zero confidence)
3. Forward Pass Flow
File: src/depth_anything_3/model/dpt.py, lines 244-252
# Main head outputs logits with 2 channels
main_logits = self.scratch.output_conv2(feat) # (B, S, 2, H, W)
# Permute to (B, S, H, W, 2) for channel-wise processing
fmap = main_logits.permute(0, 2, 3, 1)
# Split channels: depth (channel 0) and confidence (channel 1)
pred = self._apply_activation_single(fmap[..., :-1], self.activation) # depth = exp(logits)
conf = self._apply_activation_single(fmap[..., -1], self.conf_activation) # conf = exp(logits) + 1
# Output
output["depth"] = pred.squeeze(1) # (B, S, H, W)
output["depth_conf"] = conf.squeeze(1) # (B, S, H, W)
4. What Confidence Represents
Based on the DA3 paper (Section 3.3) and architecture:
Confidence is learned to predict:
- Multi-view visibility: Whether a pixel is visible and consistent across multiple views
- Geometric reliability: How reliable the depth prediction is based on:
- Texture richness (textured regions β higher confidence)
- Edge alignment (depth edges aligned with image edges β higher confidence)
- Multi-view consistency (consistent across views β higher confidence)
- Occlusion handling (occluded regions β lower confidence)
Training supervision (from DA3 paper Section 3.3):
The paper explicitly defines confidence training in the loss function:
L = LD(DΜ, D) + LM(RΜ, M) + LP(DΜ β d + t, P) + Ξ²LC(Δ, v) + Ξ±Lgrad(DΜ, D)
Where:
LC(Δ, v)is the confidence loss with weightΞ² = 1Δis the predicted confidence (from network)vis the ground truth visibility mask (binary mask indicating pixel visibility across views)
Depth loss with confidence weighting (from paper):
LD(DΜ, D; Dc) = 1/Z_Ξ© Ξ£_{pβΞ©} m_p [|DΜ_p - D_p|/D_c,p - Ξ»_c log D_c,p]
Where:
D_c,pis the confidence of depthD_pat pixelp- The depth error is inversely weighted by confidence:
|DΜ_p - D_p|/D_c,p - Higher confidence β lower weight on depth error (model trusts its prediction, less supervision needed)
- Lower confidence β higher weight on depth error (model is uncertain, needs more supervision)
- The
-Ξ»_c log D_c,pterm is a regularization that prevents overconfidence (encourages appropriate confidence calibration)
Uncertainty-aware training mechanism:
This creates a heteroscedastic regression setup where:
- The model learns to predict both depth (mean) and confidence (inverse variance/uncertainty)
- High-confidence regions: Model is certain β small depth errors are acceptable β less gradient signal
- Low-confidence regions: Model is uncertain β depth errors are penalized more β stronger gradient signal
- This allows the model to focus learning on uncertain regions while being more lenient on confident predictions
Example:
- Pixel with
conf = 10: Depth error of0.1mβ weighted error =0.1/10 = 0.01(small contribution to loss) - Pixel with
conf = 1: Depth error of0.1mβ weighted error =0.1/1 = 0.1(large contribution to loss) - The model learns to assign high confidence to regions it can predict well, and low confidence to challenging regions
Ground truth visibility masks (from paper context):
- Multi-view geometry: Pixels visible in multiple views get
v = 1(high confidence target) - Occluded regions: Pixels occluded in other views get
v = 0(low confidence target) - Sparse depth regions: Areas with missing/noisy ground truth depth get lower confidence targets
- Teacher-student alignment: Teacher model's high-quality predictions help identify reliable regions
Training process (inferred from paper Section 4.2):
- Teacher model generates high-quality pseudo-depth for real-world noisy data
- Visibility masks are computed from multi-view geometry (COLMAP, SfM)
- Confidence loss
LC(Δ, v)supervises the network to predict confidence matching visibility - Depth loss uses confidence to weight errors: unreliable regions (low confidence) contribute less to depth loss
- Joint optimization: Confidence and depth are learned together, with confidence acting as an uncertainty estimate
Key insight from paper: Confidence serves dual purpose:
- Training: Acts as a learned weighting for depth loss (uncertainty-aware training)
- Inference: Acts as a reliability score for filtering unreliable predictions
Confidence loss formulation (inferred from paper and DINOv2-style training):
Based on the paper's mention of LC(Δ, v) and similarity to DINOv2's loss patterns, the confidence loss likely follows:
LC(Δ, v) = ||Δ - v||β # L1 loss between predicted confidence and visibility mask
# or
LC(Δ, v) = BCE(Ο(Δ), v) # Binary cross-entropy if v is binary
Where:
v β {0, 1}orv β [0, 1]is the visibility mask (1 = visible/reliable, 0 = occluded/unreliable)- The model learns to predict higher confidence (
Δ) for visible pixels and lower confidence for occluded pixels - Since confidence uses
expp1activation (range[1, +β)), the loss likely normalizes or scales the visibility mask to match
Connection to DINOv2 training paradigm:
Similar to DINOv2's teacher-student training where:
- Teacher provides soft targets (high-quality pseudo-labels)
- Student learns from teacher with confidence weighting
- Confidence indicates how much to trust the teacher's supervision
In DA3:
- Teacher model provides high-quality depth pseudo-labels (Section 4.1-4.2)
- Confidence indicates how much to trust these pseudo-labels
- Visibility masks provide ground truth for confidence learning from multi-view geometry
- Joint optimization: Confidence and depth are learned together, creating uncertainty-aware training
5. DualDPT Architecture (Main Models)
File: src/depth_anything_3/model/dualdpt.py
DA3 uses a dual-head architecture with two confidence outputs:
Main head confidence (
depth_conf):- Primary depth prediction confidence
- Used for filtering in COLMAP export
Auxiliary head confidence (
depth_aux_conf):- Secondary confidence from auxiliary prediction branch
- Used internally for multi-scale consistency
Both use the same expp1 activation: conf = exp(logits) + 1
6. Confidence Usage in Export
File: src/depth_anything_3/utils/export/colmap.py, lines 35-44
# 1. Compute percentile threshold
conf_thresh = np.percentile(prediction.conf, conf_thresh_percentile) # Default: 40th percentile
# 2. Filter points
points, colors = _depths_to_world_points_with_colors(
prediction.depth,
prediction.intrinsics,
prediction.extrinsics,
prediction.processed_images,
prediction.conf, # Confidence used for filtering
conf_thresh, # Threshold
)
Filtering logic (in glb.py):
valid = np.isfinite(depth[i]) & (depth[i] > 0) & (conf[i] >= conf_thresh)
Only pixels with confidence >= threshold are converted to 3D points.
7. Confidence Statistics
Typical confidence values:
- High confidence:
conf > 10-100(well-textured, multi-view consistent regions) - Medium confidence:
conf β 2-10(moderate texture, some consistency) - Low confidence:
conf β 1-2(textureless, occluded, or inconsistent regions)
Percentile filtering:
conf_thresh_percentile=40.0means: keep top 60% most confident pixelsconf_thresh_percentile=50.0means: keep top 50% (median split)conf_thresh_percentile=10.0means: keep top 90% (very permissive)
8. Key Insights
- Learned, not computed: Confidence is a neural network prediction, not a post-hoc error metric
- Unbounded above: No maximum confidence value (can be very large for highly reliable pixels)
- Minimum of 1: The
+1ensures no pixel has zero confidence (all pixels have some baseline reliability) - Multi-view aware: Confidence reflects multi-view geometric consistency (learned during training)
- Texture-dependent: Higher confidence in textured regions, lower in textureless areas
- Occlusion-aware: Lower confidence in occluded or boundary regions
9. Comparison with Other Methods
| Method | Confidence Source | Range | Interpretation |
|---|---|---|---|
| DA3 | Learned by network | [1, +β) |
Higher = more reliable |
| COLMAP MVS | Photo-consistency score | [0, 1] |
Higher = better photo-consistency |
| Stereo matching | Matching cost/confidence | [0, 1] |
Higher = better match quality |
| Uncertainty estimation | Predicted variance | [0, +β) |
Lower = more certain |
10. Limitations
- No explicit error modeling: Confidence doesn't directly predict depth error magnitude
- Training-dependent: Quality depends on training data and supervision
- Relative, not absolute: Confidence values are relative (higher = better), not absolute error bounds
- No calibration: Confidence values aren't calibrated to actual error rates (unlike calibrated uncertainty)
Summary
DA3's confidence is a learned reliability score that:
- Is predicted by the neural network (not computed from errors)
- Uses
expp1activation:conf = exp(logits) + 1(range:[1, +β)) - Reflects multi-view geometric consistency, texture richness, and occlusion
- Is used to filter unreliable depth predictions before point cloud generation
- Default filtering keeps top 60% most confident pixels (
conf_thresh_percentile=40.0)
The confidence map is essentially the model's self-assessment of how reliable each depth prediction is, learned from training on multi-view datasets with ground truth visibility and geometry.
Coordinate System Notes
- Extrinsics format: DA3 uses w2c (world-to-camera) format, which is converted to c2w (camera-to-world) for point cloud generation
- COLMAP format: COLMAP's
rig_from_worldexpects w2c format, so the extrinsics are used directly - Camera model: Pinhole camera model is assumed
Summary
DA3's COLMAP export bypasses the traditional feature-based pipeline entirely. Instead:
- Model predicts β Dense depth maps + camera poses
- Geometric projection β Convert depth pixels to 3D points
- Structure creation β Build COLMAP reconstruction with points, cameras, and observations
This results in a dense, geometrically consistent point cloud that can be directly used in downstream applications like 3D Gaussian Splatting, mesh reconstruction, or further COLMAP processing.