Files
LDETR_V1/utils/curve.py
2026-08-18 18:50:32 +05:30

78 lines
2.9 KiB
Python

"""Letterbox resize + curve sampling utilities.
Camera-agnostic design note: images of any source resolution/aspect ratio are
letterboxed (aspect-preserving resize + pad) into a fixed network input size, and all
lane coordinates are normalized to [0,1] within that canonical frame. `LetterboxTransform`
carries enough state to map predictions back to original-image pixel coordinates.
"""
from __future__ import annotations
from dataclasses import dataclass
import numpy as np
@dataclass
class LetterboxTransform:
scale: float
pad_x: float
pad_y: float
out_w: int
out_h: int
src_w: int
src_h: int
def apply_points(self, points: np.ndarray) -> np.ndarray:
"""points: (N,2) array of (x,y) in source-image pixel coords -> letterboxed pixel coords."""
out = points.copy().astype(np.float32)
out[:, 0] = out[:, 0] * self.scale + self.pad_x
out[:, 1] = out[:, 1] * self.scale + self.pad_y
return out
def invert_points(self, points: np.ndarray) -> np.ndarray:
"""Inverse of apply_points: letterboxed pixel coords -> source-image pixel coords."""
out = points.copy().astype(np.float32)
out[:, 0] = (out[:, 0] - self.pad_x) / self.scale
out[:, 1] = (out[:, 1] - self.pad_y) / self.scale
return out
def compute_letterbox(src_w: int, src_h: int, out_w: int, out_h: int) -> LetterboxTransform:
scale = min(out_w / src_w, out_h / src_h)
new_w, new_h = src_w * scale, src_h * scale
pad_x = (out_w - new_w) / 2.0
pad_y = (out_h - new_h) / 2.0
return LetterboxTransform(scale=scale, pad_x=pad_x, pad_y=pad_y,
out_w=out_w, out_h=out_h, src_w=src_w, src_h=src_h)
def sample_lane_at_ys(lane_points: list[tuple[float, float]], sample_ys: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
"""Linearly interpolate a polyline's x(y) at the given y-values.
Returns (x_values, valid_mask) — valid_mask is False outside the polyline's
observed y-range (no extrapolation).
"""
pts = np.array(sorted(lane_points, key=lambda p: p[1]), dtype=np.float32)
ys_src = pts[:, 1]
xs_src = pts[:, 0]
y_min, y_max = ys_src.min(), ys_src.max()
valid = (sample_ys >= y_min) & (sample_ys <= y_max)
xs_out = np.zeros_like(sample_ys, dtype=np.float32)
if valid.any():
xs_out[valid] = np.interp(sample_ys[valid], ys_src, xs_src)
return xs_out, valid
def polyfit_cubic(lane_points: list[tuple[float, float]]) -> np.ndarray:
"""Least-squares cubic fit x = k*y^3 + m*y^2 + n*y + b (normalized coords expected).
Used only as a reference/visualization utility, not inside the training loop
(the model regresses these coefficients directly).
"""
pts = np.array(lane_points, dtype=np.float64)
ys, xs = pts[:, 1], pts[:, 0]
coeffs = np.polyfit(ys, xs, deg=3) # k, m, n, b
return coeffs.astype(np.float32)