Instance Segmentation Model Export - LTDETRv2

Open In Colab

This notebook demonstrates how to export an LTDETRv2 instance segmentation model to ONNX and TensorRT.

The notebook covers the following steps:

  1. Install LightlyTrain

  2. Export a trained LTDETRv2 model to ONNX

  3. Run inference with the ONNX model

  4. Export the model to TensorRT

  5. Run inference with the TensorRT engine

Important: When running on Google Colab make sure to select a GPU runtime. You can do this by going to Runtime > Change runtime type and selecting a GPU hardware accelerator.

!pip install "lightly-train[onnx,onnxruntime,onnxslim]"

Export to ONNX

Load the model weights

Load the COCO-trained LTDETRv2 instance segmentation model with LightlyTrain’s load_model function.

import lightly_train

model = lightly_train.load_model("ltdetrv2-seg-s-coco")

Download an example image

Download an example image for inference:

!wget -O image.jpg http://images.cocodataset.org/val2017/000000039769.jpg

Preprocessing

import numpy as np
import torchvision.transforms.v2 as T
from PIL import Image
from torchvision.transforms.functional import pil_to_tensor

image_pil = Image.open("image.jpg").convert("RGB")
image_tensor = pil_to_tensor(image_pil)

w, h = image_pil.size
transforms = T.Compose(
    [
        T.Resize(model.image_size),
        T.ToTensor(),
        T.Normalize(**model.image_normalize) if model.image_normalize else T.Identity(),
    ]
)
image_tensor_transformed = transforms(image_pil)[None]
orig_target_size = np.array([[h, w]], dtype=np.int64)

Get the model predictions for reference

Define a helper that displays each instance’s mask, bounding box, and class label. We will use it for the PyTorch, ONNX, and TensorRT predictions.

import matplotlib.pyplot as plt
import torch
from torchvision.utils import draw_bounding_boxes, draw_segmentation_masks


def visualize_instances(image, boxes, masks, labels, classes):
    image_with_masks = draw_segmentation_masks(image, masks=masks.cpu(), alpha=0.6)
    image_with_instances = draw_bounding_boxes(
        image_with_masks,
        boxes=boxes.cpu(),
        labels=[classes[label.item()] for label in labels.cpu()],
    )
    fig, axs = plt.subplots(1, 2, figsize=(12, 8))
    axs[0].imshow(image.permute(1, 2, 0))
    axs[0].axis("off")
    axs[1].imshow(image_with_instances.permute(1, 2, 0))
    axs[1].axis("off")
    fig.show()
results = model.predict(image_tensor, threshold=0.6)
visualize_instances(
    image_tensor,
    boxes=results["bboxes"],
    masks=results["masks"],
    labels=results["labels"],
    classes=model.classes,
)

Export the model to ONNX

model.export_onnx(
    out="model.onnx",
    # precision="fp16",  # Use FP16 weights for a smaller and faster model.
)

See export_onnx for all available options.

Run inference with the ONNX model

The exported graph takes the preprocessed image and the original image size in (height, width) order. It returns all queries, so apply the same score and mask thresholds as model.predict.

import onnxruntime as ort
import torch.nn.functional as F

sess = ort.InferenceSession("model.onnx")
input_dtype = sess.get_inputs()[0].type
input_dtype_numpy = {
    "tensor(float)": "float32",
    "tensor(float16)": "float16",
}[input_dtype]

labels_onnx, boxes_onnx, masks_onnx, scores_onnx = sess.run(
    output_names=None,
    input_feed={
        "images": image_tensor_transformed.numpy().astype(input_dtype_numpy),
        "orig_target_size": orig_target_size,
    },
)

labels_onnx = labels_onnx[0]
boxes_onnx = boxes_onnx[0]
masks_onnx = masks_onnx[0]
scores_onnx = scores_onnx[0]

keep = scores_onnx > 0.6
labels_onnx = torch.from_numpy(labels_onnx[keep])
boxes_onnx = torch.from_numpy(boxes_onnx[keep])
masks_onnx = torch.from_numpy(masks_onnx[keep])
masks_onnx = (
    F.interpolate(
        masks_onnx.unsqueeze(1),
        size=(h, w),
        mode="bilinear",
        align_corners=False,
    ).squeeze(1)
    > 0.0
)

visualize_instances(
    image_tensor,
    boxes=boxes_onnx,
    masks=masks_onnx,
    labels=labels_onnx,
    classes=model.classes,
)

Export to TensorRT

Requirements

TensorRT is not part of LightlyTrain’s dependencies and must be installed separately. Installation depends on your OS, Python version, GPU, and NVIDIA driver/CUDA setup. See the TensorRT documentation for details.

On CUDA 12.x systems, install the TensorRT version tested with this tutorial. Pinning the version prevents pip from installing TensorRT 11, which is not yet supported by LightlyTrain:

!pip install "tensorrt-cu12==10.13.3.9"
model.export_tensorrt(
    out="model.trt",
    # precision="fp16",  # Use FP16 weights for a smaller and faster engine.
)

See export_tensorrt for all available options.

Run inference with the TensorRT engine

import tensorrt as trt


class TRT:
    def __init__(self, engine_path: str, device: str = "cuda:0", verbose: bool = False):
        self.device = torch.device(device)
        logger = trt.Logger(trt.Logger.VERBOSE if verbose else trt.Logger.INFO)
        trt.init_libnvinfer_plugins(logger, "")
        runtime = trt.Runtime(logger)

        with open(engine_path, "rb") as f:
            self.engine = runtime.deserialize_cuda_engine(f.read())
        self.context = self.engine.create_execution_context()

        io_names = [
            self.engine.get_tensor_name(i) for i in range(self.engine.num_io_tensors)
        ]
        self.in_names = [
            name
            for name in io_names
            if self.engine.get_tensor_mode(name) == trt.TensorIOMode.INPUT
        ]
        self.out_names = [
            name
            for name in io_names
            if self.engine.get_tensor_mode(name) == trt.TensorIOMode.OUTPUT
        ]

        self.buffers = {}
        self.bindings = []
        for name in io_names:
            shape = tuple(self.context.get_tensor_shape(name))
            np_dtype = trt.nptype(self.engine.get_tensor_dtype(name))
            torch_dtype = torch.from_numpy(np.empty((), dtype=np_dtype)).dtype
            buffer = torch.empty(
                shape, device=self.device, dtype=torch_dtype
            ).contiguous()
            self.buffers[name] = buffer
            self.bindings.append(buffer.data_ptr())

    @torch.no_grad()
    def __call__(self, inputs: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]:
        for name in self.in_names:
            self.buffers[name].copy_(inputs[name].to(self.device))
        if not self.context.execute_v2(self.bindings):
            raise RuntimeError("TensorRT execution failed")
        return {name: self.buffers[name] for name in self.out_names}
trt_model = TRT("model.trt")
outputs_trt = trt_model(
    {
        "images": image_tensor_transformed,
        "orig_target_size": torch.from_numpy(orig_target_size),
    }
)

labels_trt = outputs_trt["labels"][0]
boxes_trt = outputs_trt["boxes"][0]
masks_trt = outputs_trt["masks"][0]
scores_trt = outputs_trt["scores"][0]

keep = scores_trt > 0.6
labels_trt = labels_trt[keep]
boxes_trt = boxes_trt[keep]
masks_trt = masks_trt[keep]
masks_trt = (
    F.interpolate(
        masks_trt.unsqueeze(1),
        size=(h, w),
        mode="bilinear",
        align_corners=False,
    ).squeeze(1)
    > 0.0
)

visualize_instances(
    image_tensor,
    boxes=boxes_trt,
    masks=masks_trt,
    labels=labels_trt,
    classes=model.classes,
)