TwinLiteNet β€” Drivable-area + lane segmentation (LiteRT GPU)

On-device drivable-area and lane-line segmentation running fully on the LiteRT CompiledModel GPU delegate (no CPU fallback). TwinLiteNet (2023) is an ultra-light ESPNet-based network with two segmentation heads β€” the ADAS perception building block "where can I drive" + "where are the lanes". Only 3.1 MB, ~44 ms/frame on a Pixel 8a.

  • Architecture: ESPNet-C encoder + two seg decoders β€” pure CNN.
  • Weights: chequanghuy/TwinLiteNet (BDD100K) Β· MIT.
  • Size: 3.1 MB.

TwinLiteNet drivable area + lanes

Drivable area (green) + lane lines (red) on a dashcam highway frame. Source: Wikimedia Commons (Public Domain).

I/O

  • Input: [1, 3, 360, 640] NCHW, RGB, x/255.
  • Outputs: two [1, 2, 360, 640] logit maps β€” drivable_area and lane_line. Take argmax over the class dim (2) β†’ binary masks.

GPU conversion

TwinLiteNet is a pure CNN. It converts fully GPU-compatible (270/270 nodes on the delegate, 1 partition; device corr 0.99997 / 0.99998 on the two heads, ~44 ms) with one patch: the ConvTranspose2d upsamplers β†’ ZeroStuffConvT2d (nearest-upsample

  • stride zero-stuff mask + flipped conv; the Mali delegate rejects TRANSPOSE_CONV). Exact. CPU-exact vs PyTorch (corr 1.0).

Minimal usage

Kotlin (Android, LiteRT CompiledModel GPU)

val options = CompiledModel.Options(Accelerator.GPU)
val model = CompiledModel.create(context.assets, "twinlite.tflite", options, null)
val inBufs = model.createInputBuffers()
val outBufs = model.createOutputBuffers()   // [0] = drivable area, [1] = lane line

inBufs[0].writeFloat(inputNCHW)             // [1,3,360,640] RGB, x/255
model.run(inBufs, outBufs)
val da = outBufs[0].readFloat()             // [2*360*640]; argmax over the 2 classes -> drivable mask
val ll = outBufs[1].readFloat()             // [2*360*640]; argmax -> lane mask
// per pixel p: class = if (da[p] > da[360*640 + p]) 0 else 1

Python (LiteRT / ai-edge-litert)

import numpy as np
from ai_edge_litert.interpreter import Interpreter

it = Interpreter(model_path="twinlite.tflite"); it.allocate_tensors()
inp, out = it.get_input_details(), it.get_output_details()
it.set_tensor(inp[0]["index"], x)           # [1,3,360,640] float32, RGB, x/255
it.invoke()
outs = sorted(out, key=lambda o: o["index"])
da = it.get_tensor(outs[0]["index"])[0].argmax(0)   # [360,640] drivable-area mask
ll = it.get_tensor(outs[1]["index"])[0].argmax(0)   # [360,640] lane mask

Conversion

Converted with litert-torch (build_twinlite.py): loads the MIT BDD100K weights, swaps ConvTranspose2d β†’ ZeroStuffConvT2d, and exports the two-head graph.

License

MIT (TwinLiteNet / chequanghuy). Trained on BDD100K.

Downloads last month
12
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support