xujunwei
Upload BEVFormer Tiny Resnet50 project
1765773
Raw
History Blame Contribute Delete
11.1 kB
from __future__ import annotations
import hashlib
import json
from concurrent.futures import ThreadPoolExecutor
import math
import time
from pathlib import Path
from typing import Any, Iterable
import numpy as np
CAMERA_NAMES = (
"CAM_FRONT",
"CAM_FRONT_RIGHT",
"CAM_FRONT_LEFT",
"CAM_BACK",
"CAM_BACK_LEFT",
"CAM_BACK_RIGHT",
)
IMAGE_MEAN = np.asarray([123.675, 116.28, 103.53], dtype=np.float32)
IMAGE_STD = np.asarray([58.395, 57.12, 57.375], dtype=np.float32)
SHAPES = {
"images": (6, 3, 480, 800),
"images_unpadded": (6, 3, 450, 800),
"img_feat": (6, 256, 15, 25),
"encoder_img_feat": (1, 6, 256, 15, 25),
"can_bus": (1, 18),
"shift": (1, 2),
"lidar2img": (1, 6, 4, 4),
"bev": (1, 2500, 256),
"decoder": (1, 900, 10),
}
EXPECTED_TENSORS = {
"backbone": {
"inputs": {"images": 6912000},
"outputs": {"img_feat": 576000},
},
"encoder_temporal": {
"inputs": {
"can_bus": 18,
"img_feat": 576000,
"lidar2img": 96,
"shift": 2,
"prev_bev": 640000,
},
"outputs": {"bev_embed": 640000},
},
"encoder_scene_start": {
"inputs": {
"can_bus": 18,
"img_feat": 576000,
"lidar2img": 96,
},
"outputs": {"bev_embed": 640000},
},
"decoder": {
"inputs": {"bev_embed": 640000},
"outputs": {"cls_scores": 9000, "bbox_preds": 9000},
},
}
ROTATE_CENTER = (100.0, 100.0)
def sha256_file(path: str | Path) -> str:
digest = hashlib.sha256()
with open(path, "rb") as handle:
for block in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def load_json(path: str | Path) -> dict[str, Any]:
return json.loads(Path(path).read_text(encoding="utf-8"))
def normalize_rc(value: Any) -> int:
return 0 if value is None else int(value)
def resolve_path(path: str | Path, repo_root: str | Path) -> Path:
value = Path(path)
if value.is_absolute():
return value
return Path(repo_root) / value
def load_record(record: dict[str, Any], repo_root: str | Path) -> np.ndarray:
path = resolve_path(record["path"], repo_root)
if not path.is_file():
raise FileNotFoundError(path)
actual_sha = sha256_file(path)
expected_sha = record.get("sha256")
if expected_sha and actual_sha != expected_sha:
raise RuntimeError(f"SHA mismatch for {path}: expected={expected_sha} actual={actual_sha}")
dtype = "<f2" if "float16" in str(record["dtype"]).lower() else "<f4"
shape = tuple(int(value) for value in record["shape"])
array = np.fromfile(path, dtype=dtype)
if array.size != math.prod(shape):
raise RuntimeError(f"Element mismatch for {path}: expected={math.prod(shape)} actual={array.size}")
return np.ascontiguousarray(array.reshape(shape))
def _load_camera_image(path: Path) -> np.ndarray:
try:
import cv2
except ModuleNotFoundError as exc:
raise RuntimeError("OpenCV is required for board-side camera image preprocessing.") from exc
image = cv2.imread(str(path), cv2.IMREAD_UNCHANGED)
if image is None:
raise FileNotFoundError(path)
if image.ndim != 3 or image.shape[2] != 3:
raise ValueError(f"Expected 3-channel camera image: {path} shape={image.shape}")
image = image.astype(np.float32)
image = image[..., ::-1]
image = (image - IMAGE_MEAN) / IMAGE_STD
image = cv2.resize(image, (800, 450), interpolation=cv2.INTER_LINEAR)
return image.transpose(2, 0, 1)
def _preprocess_one_camera(
index: int,
camera_name: str,
item: dict[str, Any],
repo_root: str | Path,
check_sha: bool,
) -> tuple[int, np.ndarray, dict[str, Any]]:
path = resolve_path(item["path"], repo_root)
if not path.is_file():
raise FileNotFoundError(path)
actual_sha = None
expected_sha = item.get("sha256")
if check_sha:
actual_sha = sha256_file(path)
if expected_sha and actual_sha != expected_sha:
raise RuntimeError(f"SHA mismatch for {path}: expected={expected_sha} actual={actual_sha}")
tensor = _load_camera_image(path)
if tensor.shape != SHAPES["images_unpadded"][1:]:
raise ValueError(f"Unexpected preprocessed camera tensor shape: {tensor.shape}")
return index, tensor, {
"name": camera_name,
"path": str(path),
"sha_checked": bool(check_sha),
"sha256": actual_sha or expected_sha,
}
def preprocess_camera_images(
record: dict[str, Any],
repo_root: str | Path,
check_sha: bool = False,
num_workers: int = 6,
) -> tuple[np.ndarray, dict[str, Any]]:
images_by_name = {item["name"]: item for item in record["images"]}
camera_order = tuple(record.get("order", CAMERA_NAMES))
if camera_order != CAMERA_NAMES:
raise RuntimeError(f"Unexpected camera order: {camera_order}")
tasks = [(index, name, images_by_name[name], repo_root, check_sha) for index, name in enumerate(CAMERA_NAMES)]
if int(num_workers) > 1:
with ThreadPoolExecutor(max_workers=int(num_workers)) as executor:
results = list(executor.map(lambda args: _preprocess_one_camera(*args), tasks))
else:
results = [_preprocess_one_camera(*args) for args in tasks]
padded = np.zeros(SHAPES["images"], dtype=np.float32)
checked_images = [None] * len(CAMERA_NAMES)
for index, tensor, image_record in results:
padded[index, :, :450, :] = tensor
checked_images[index] = image_record
return np.ascontiguousarray(padded, dtype=np.float32), {
"source": "camera_images",
"pipeline": record.get(
"pipeline",
"BGR -> RGB -> normalize(mean/std) -> resize(800,450) -> CHW -> pad to 480 rows",
),
"camera_order": list(CAMERA_NAMES),
"sha_checked": bool(check_sha),
"num_workers": int(num_workers),
"unpadded_shape": list(SHAPES["images_unpadded"]),
"padded_shape": list(padded.shape),
"reference_preprocessed_sha256": record.get("reference_preprocessed_sha256"),
"images": checked_images,
}
def load_backbone_images(
assets: dict[str, Any],
repo_root: str | Path,
check_image_sha: bool = False,
preprocess_workers: int = 6,
) -> tuple[np.ndarray, dict[str, Any]]:
if "camera_images" not in assets:
raise KeyError("camera_images is required; this demo starts from raw camera JPG inputs.")
return preprocess_camera_images(
assets["camera_images"],
repo_root,
check_sha=check_image_sha,
num_workers=preprocess_workers,
)
def as_encoder_img_feat(img_feat: np.ndarray) -> np.ndarray:
native = np.ascontiguousarray(img_feat, dtype="<f2")
return np.ascontiguousarray(native.astype(np.float32).reshape(SHAPES["encoder_img_feat"]))
def elapsed_ms(start_ns: int) -> float:
return (time.perf_counter_ns() - start_ns) / 1.0e6
def stats(values: Iterable[float]) -> dict[str, float]:
items = [float(value) for value in values]
if not items:
return {"count": 0}
mean = float(sum(items) / len(items))
variance = float(sum((value - mean) ** 2 for value in items) / len(items))
return {
"count": len(items),
"mean": mean,
"min": float(min(items)),
"max": float(max(items)),
"sum": float(sum(items)),
"var": variance,
}
def _torchvision_inverse_affine_matrix(center: tuple[float, float], angle: float) -> list[float]:
rot = math.radians(angle)
a = math.cos(rot)
b = -math.sin(rot)
c = math.sin(rot)
d = math.cos(rot)
matrix = [d, -b, 0.0, -c, a, 0.0]
cx, cy = center
matrix[2] += matrix[0] * (-cx) + matrix[1] * (-cy)
matrix[5] += matrix[3] * (-cx) + matrix[4] * (-cy)
matrix[2] += cx
matrix[5] += cy
return matrix
def rotate_prev_bev_like_torchvision(
previous_bev: np.ndarray,
angle: float,
rotate_center: tuple[float, float] = ROTATE_CENTER,
) -> np.ndarray:
bev = np.ascontiguousarray(previous_bev, dtype=np.float32).reshape(SHAPES["bev"])
grid = bev[0].reshape(50, 50, 256)
height, width, channels = grid.shape
center_f = (
float(rotate_center[0]) - width * 0.5,
float(rotate_center[1]) - height * 0.5,
)
theta = np.asarray(
_torchvision_inverse_affine_matrix(center_f, -float(angle)),
dtype=np.float32,
).reshape(2, 3)
x_base = np.linspace(-width * 0.5 + 0.5, width * 0.5 - 0.5, width, dtype=np.float32)
y_base = np.linspace(-height * 0.5 + 0.5, height * 0.5 - 0.5, height, dtype=np.float32)
x_grid, y_grid = np.meshgrid(x_base, y_base)
src_x = (x_grid * theta[0, 0] + y_grid * theta[0, 1] + theta[0, 2]) / (0.5 * width)
src_y = (x_grid * theta[1, 0] + y_grid * theta[1, 1] + theta[1, 2]) / (0.5 * height)
ix = np.rint(((src_x + 1.0) * width - 1.0) * 0.5).astype(np.int64)
iy = np.rint(((src_y + 1.0) * height - 1.0) * 0.5).astype(np.int64)
valid = (ix >= 0) & (ix < width) & (iy >= 0) & (iy < height)
out = np.zeros((height, width, channels), dtype=np.float32)
out[valid] = grid[iy[valid], ix[valid]]
return np.ascontiguousarray(out.reshape(SHAPES["bev"]), dtype=np.float32)
def rotate_prev_bev(previous_live_bev: np.ndarray, rotation_can_bus: np.ndarray) -> np.ndarray:
angle = float(rotation_can_bus.reshape(-1)[-1])
previous_native = np.ascontiguousarray(previous_live_bev, dtype="<f2")
previous_semantic = previous_native.astype(np.float32).reshape(SHAPES["bev"])
rotated = rotate_prev_bev_like_torchvision(previous_semantic, angle)
rotated_native = np.ascontiguousarray(rotated, dtype="<f2")
return np.ascontiguousarray(rotated_native.astype(np.float32).reshape(SHAPES["bev"]))
def save_raw_outputs(output_dir: Path, frame_index: int, cls_scores: np.ndarray, bbox_preds: np.ndarray) -> dict[str, Any]:
output_dir.mkdir(parents=True, exist_ok=True)
cls_path = output_dir / f"frame{frame_index:03d}_cls_scores_fp32.raw"
bbox_path = output_dir / f"frame{frame_index:03d}_bbox_preds_fp32.raw"
np.ascontiguousarray(cls_scores, dtype="<f4").tofile(cls_path)
np.ascontiguousarray(bbox_preds, dtype="<f4").tofile(bbox_path)
return {
"cls_scores": {"path": str(cls_path), "sha256": sha256_file(cls_path)},
"bbox_preds": {"path": str(bbox_path), "sha256": sha256_file(bbox_path)},
}
def save_final_coordinates(
output_dir: Path,
frame_index: int,
boxes: np.ndarray,
scores: np.ndarray,
labels: np.ndarray,
) -> dict[str, Any]:
output_dir.mkdir(parents=True, exist_ok=True)
path = output_dir / f"frame{frame_index:03d}_final_coordinates.npz"
np.savez(
path,
boxes=np.ascontiguousarray(boxes, dtype=np.float32),
scores=np.ascontiguousarray(scores, dtype=np.float32),
labels=np.ascontiguousarray(labels, dtype=np.int64),
)
return {
"path": str(path),
"sha256": sha256_file(path),
"box_count": int(len(scores)),
}