- Added a new proposal for boundary smoothing in the weight cutout process, differentiating between Split edges and Outline smoothing requirements. - Introduced a new capability `cutout-boundary-smoothing` to control smoothing strength and default values. - Updated specifications to define the behavior of boundary-specific smoothing strength and default settings. - Created tasks to verify the implementation of boundary behavior and asset updates. - Developed comprehensive tests for depth symmetry and normal scaling to ensure expected behavior and performance. - Introduced new attributes for normal mapping to facilitate persistent face attributes sharing between depth symmetry and image materials. - Built a new node group for O Image Depth Symmetry, integrating the new smoothing capabilities and ensuring compatibility with existing workflows.
170 lines
5.9 KiB
Python
170 lines
5.9 KiB
Python
"""Calibrate metric reference depth and the Depth Symmetry base plane."""
|
|
|
|
PLANE_VARIANCE_FLOOR_RATIO = 0.025
|
|
DEPTH_REFERENCE_PERCENTILE = 95.0
|
|
ADAPTIVE_DEPTH_TRANSPARENCY_RATIO = 0.05
|
|
DEFAULT_DEPTH_BASELINE = 8.0
|
|
DEFAULT_DEPTH_DIRECTION = (0.0, 0.0, 1.0)
|
|
|
|
|
|
def use_adaptive_depth_calibration(selection_transparent_fraction):
|
|
"""Return whether the Selection input has enough transparent context."""
|
|
return float(selection_transparent_fraction) >= ADAPTIVE_DEPTH_TRANSPARENCY_RATIO
|
|
|
|
|
|
def metric_depth_reference(camera_depth, valid, uniform_scale):
|
|
"""Return the shared upper-percentile scene depth for both Depth shapes."""
|
|
import numpy as np
|
|
|
|
camera_depth = np.asarray(camera_depth, dtype=np.float32)
|
|
valid = np.asarray(valid, dtype=bool)
|
|
if valid.shape != camera_depth.shape:
|
|
raise ValueError("Depth values and validity mask dimensions do not match")
|
|
usable = valid & np.isfinite(camera_depth) & (camera_depth > 0.0)
|
|
if not usable.any():
|
|
raise ValueError("The depth field contains no valid metric depth")
|
|
if not uniform_scale > 0.0:
|
|
raise ValueError("Depth Uniform Scale must be positive")
|
|
reference = np.percentile(
|
|
camera_depth[usable],
|
|
DEPTH_REFERENCE_PERCENTILE,
|
|
)
|
|
return float(reference) * float(uniform_scale)
|
|
|
|
|
|
def _sample_valid_bilinear(values, valid, x, y):
|
|
import numpy as np
|
|
|
|
height, width = values.shape
|
|
x = np.clip(x, 0.0, max(width - 1, 0))
|
|
y = np.clip(y, 0.0, max(height - 1, 0))
|
|
x0 = np.floor(x).astype(np.int64)
|
|
y0 = np.floor(y).astype(np.int64)
|
|
x1 = np.minimum(x0 + 1, width - 1)
|
|
y1 = np.minimum(y0 + 1, height - 1)
|
|
fx = x - x0
|
|
fy = y - y0
|
|
samples = np.stack(
|
|
(
|
|
values[y0, x0],
|
|
values[y0, x1],
|
|
values[y1, x0],
|
|
values[y1, x1],
|
|
),
|
|
axis=1,
|
|
)
|
|
weights = np.stack(
|
|
(
|
|
(1.0 - fx) * (1.0 - fy),
|
|
fx * (1.0 - fy),
|
|
(1.0 - fx) * fy,
|
|
fx * fy,
|
|
),
|
|
axis=1,
|
|
)
|
|
sample_valid = np.stack(
|
|
(
|
|
valid[y0, x0],
|
|
valid[y0, x1],
|
|
valid[y1, x0],
|
|
valid[y1, x1],
|
|
),
|
|
axis=1,
|
|
)
|
|
weights *= sample_valid
|
|
totals = weights.sum(axis=1)
|
|
result = np.zeros(len(x), dtype=np.float32)
|
|
usable = totals > 1e-8
|
|
result[usable] = (samples[usable] * weights[usable]).sum(axis=1) / totals[usable]
|
|
return result, usable
|
|
|
|
|
|
def _weighted_plane(coordinates, values, weights):
|
|
import numpy as np
|
|
|
|
total = float(np.sum(weights))
|
|
if total <= 1e-12:
|
|
return np.zeros(3, dtype=np.float32)
|
|
center = np.sum(coordinates * weights[:, None], axis=0) / total
|
|
value_center = float(np.sum(values * weights) / total)
|
|
centered = coordinates - center
|
|
covariance = (centered * weights[:, None]).T @ centered / total
|
|
cross = centered.T @ (weights * (values - value_center)) / total
|
|
eigenvalues, eigenvectors = np.linalg.eigh(covariance)
|
|
largest = max(float(eigenvalues[-1]), 0.0)
|
|
if largest <= 1e-12:
|
|
slopes = np.zeros(2, dtype=np.float64)
|
|
else:
|
|
supported = np.maximum(eigenvalues, largest * PLANE_VARIANCE_FLOOR_RATIO)
|
|
slopes = eigenvectors @ ((eigenvectors.T @ cross) / supported)
|
|
intercept = value_center - float(center @ slopes)
|
|
return np.asarray((*slopes, intercept), dtype=np.float32)
|
|
|
|
|
|
def _stable_plane(coordinates, values):
|
|
import numpy as np
|
|
|
|
coordinates = np.asarray(coordinates, dtype=np.float64)
|
|
values = np.asarray(values, dtype=np.float64)
|
|
if len(coordinates) < 2:
|
|
return np.asarray((0.0, 0.0, np.median(values)), dtype=np.float32)
|
|
weights = np.ones(len(values), dtype=np.float64)
|
|
plane = np.zeros(3, dtype=np.float32)
|
|
for _iteration in range(4):
|
|
plane = _weighted_plane(coordinates, values, weights)
|
|
residual = values - coordinates @ plane[:2] - plane[2]
|
|
centered = residual - np.median(residual)
|
|
scale = max(float(np.median(np.abs(centered))) * 1.4826, 1e-6)
|
|
weights = np.minimum(
|
|
1.0,
|
|
(2.5 * scale) / np.maximum(np.abs(centered), 1e-12),
|
|
)
|
|
return plane
|
|
|
|
|
|
def depth_symmetry_projection_parameters(
|
|
vertices,
|
|
vertex_uv,
|
|
camera_depth,
|
|
valid,
|
|
uniform_scale,
|
|
):
|
|
"""Fit a metric base plane and return its center depth and direction."""
|
|
import numpy as np
|
|
|
|
vertices = np.asarray(vertices, dtype=np.float32)
|
|
uv = np.asarray(vertex_uv, dtype=np.float32)
|
|
if vertices.ndim != 2 or vertices.shape[1] != 3:
|
|
raise ValueError("Depth Symmetry vertices must have three components")
|
|
if uv.shape != (len(vertices), 2):
|
|
raise ValueError("Depth Symmetry UV coordinates do not match its vertices")
|
|
camera_depth = np.asarray(camera_depth, dtype=np.float32)
|
|
valid = np.asarray(valid, dtype=bool)
|
|
if valid.shape != camera_depth.shape:
|
|
raise ValueError("Depth Symmetry depth and mask dimensions do not match")
|
|
if not uniform_scale > 0.0:
|
|
raise ValueError("Depth Symmetry Uniform Scale must be positive")
|
|
height, width = camera_depth.shape
|
|
sample_x = uv[:, 0] * max(width - 1, 0)
|
|
sample_y = (1.0 - uv[:, 1]) * max(height - 1, 0)
|
|
calibration_valid = valid & np.isfinite(camera_depth) & (camera_depth > 0.0)
|
|
sampled, usable = _sample_valid_bilinear(
|
|
camera_depth,
|
|
calibration_valid,
|
|
sample_x,
|
|
sample_y,
|
|
)
|
|
if not usable.any():
|
|
raise ValueError("MoGe-2 found no valid Depth Symmetry samples")
|
|
local_coordinates = vertices[:, :2]
|
|
metric_depth = sampled[usable] * float(uniform_scale)
|
|
plane = _stable_plane(local_coordinates[usable], metric_depth)
|
|
reference = metric_depth_reference(
|
|
camera_depth,
|
|
calibration_valid,
|
|
uniform_scale,
|
|
)
|
|
direction = np.asarray((plane[0], plane[1], 1.0), dtype=np.float64)
|
|
direction /= np.linalg.norm(direction)
|
|
return reference, tuple(float(value) for value in direction)
|