File size: 3,105 Bytes
ddaad99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# SPDX-License-Identifier: Apache-2.0
"""Images -> the network input ``image [1, 6, 3, 736, 1280]`` float32 with the Autoware node's exact semantics
(research/bevformer/SPEC.md section 3.1):

1. ``cv_bridge`` BGR8; any image that is not 1600x900 goes through ``cv::resize(img, (1600, 900))`` (INTER_LINEAR on
   uint8, ``AW/src/bevformer_node.cpp:690-697``): :func:`node_transport`, the vendored bit-exact emulation
   ``ttaw.image_linear.opencv_resize_linear_u8`` (C14, ttaw 0.16.0; numpy, no OpenCV);
2. normalise FIRST in float (BGR mean, std 1), ``cv::resize`` x0.8 INTER_LINEAR on the float image, zero pad bottom /
   right to /32, CHW: the vendored C14 preset ``ttaw.image.bevformer_preprocess`` (bit-exact against OpenCV and the
   research golden input, ``common/tests/host/test_image_bevformer_host.py``).

The cameras must already be in the network order (``reference.config.CAMERA_ORDER``). numpy only; JPEG / PNG decoding
(:func:`decode_bgr`, and the API's ``ttaw.io``) uses PIL, which gives the same pixels as OpenCV's ``imdecode`` (what
``cv_bridge`` and the public-data converter use) on the shipped sample (``tests/test_host_preprocess.py``).
"""
from __future__ import annotations

from typing import Any, Sequence

import numpy as np

from ..reference import config as C
from ..ttaw.image import bevformer_preprocess
from ..ttaw.image_linear import opencv_resize_linear_u8

__all__ = ["node_transport", "decode_bgr", "rgb_to_bgr", "preprocess_images"]


def node_transport(image_bgr: np.ndarray) -> np.ndarray:
    """The node's ``cv::resize`` to 1600x900 (INTER_LINEAR on uint8) when the size differs; identity otherwise."""
    img = np.asarray(image_bgr)
    if img.ndim != 3 or img.shape[2] != 3 or img.dtype != np.uint8:
        raise ValueError(f"expected a uint8 (H, W, 3) BGR image, got {img.dtype} {img.shape}")
    if img.shape[:2] == C.NODE_HW:
        return img
    return opencv_resize_linear_u8(np.ascontiguousarray(img), C.NODE_HW)


def decode_bgr(data: bytes) -> np.ndarray:
    """JPEG / PNG bytes -> BGR uint8 (what ``cv_bridge`` ``bgr8`` hands the node), decoded with PIL."""
    from ..ttaw.io import decode_image

    return rgb_to_bgr(decode_image(bytes(data)))


def rgb_to_bgr(image_rgb: np.ndarray) -> np.ndarray:
    """``ttaw.io.load_image`` returns RGB; the network wants BGR (``to_rgb`` false)."""
    return np.ascontiguousarray(np.asarray(image_rgb)[:, :, ::-1])


def preprocess_images(images_bgr: Sequence[Any], *, workers: int = 1, out: np.ndarray = None) -> np.ndarray:
    """Six BGR uint8 images in network order -> ``image [1, 6, 3, 736, 1280]`` float32 (``out`` may be a
    preallocated array of that shape)."""
    imgs = [node_transport(im) for im in images_bgr]
    if len(imgs) != C.NUM_CAMS:
        raise ValueError(f"need {C.NUM_CAMS} images in the order {list(C.CAMERA_ORDER)}, got {len(imgs)}")
    target = None if out is None else out.reshape((C.NUM_CAMS, 3) + C.IMG_HW)
    res = bevformer_preprocess(imgs, scale=C.SCALE, pad_divisor=C.PAD_DIVISOR, out=target, workers=workers)
    return res.reshape((1, C.NUM_CAMS, 3) + C.IMG_HW)