Image Segmentation
LiteRT
LiteRT
LiteRT
on-device
android
gpu
line-segment-detection
mlsd
wireframe
mobilenetv2
Instructions to use litert-community/M-LSD-tiny-LiteRT with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- LiteRT
How to use litert-community/M-LSD-tiny-LiteRT with LiteRT:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
| license: apache-2.0 | |
| library_name: LiteRT | |
| pipeline_tag: image-segmentation | |
| tags: [litert, tflite, on-device, android, gpu, line-segment-detection, mlsd, wireframe, mobilenetv2] | |
| base_model: navervision/mlsd | |
| # M-LSD-tiny β LiteRT (on-device line segment detection, fully-GPU) | |
| [M-LSD](https://github.com/navervision/mlsd) (NAVER, AAAI 2022) light-weight real-time **line segment | |
| detection**, converted to **LiteRT** and running **fully on the `CompiledModel` GPU** (ML Drift) on Android. | |
| Detects straight line segments β building edges, document borders, wireframes, room layout. The **tiny** | |
| variant (MobileNetV2 backbone, 0.62M params) is **1.4 MB** in fp16. | |
|  | |
| ## On-device (Pixel 8a, Tensor G3 β verified) | |
| | | | | |
| |---|---| | |
| | nodes on GPU | **99 / 99** LITERT_CL (full residency) | | |
| | inference | **~2 ms** (512Γ512) | | |
| | size | **1.4 MB** (fp16) | | |
| | accuracy | device-vs-PyTorch corr **0.997** (127 vs 128 lines decoded) | | |
| ``` | |
| image[1,4,512,512] (RGB + ones channel, scaled to [-1,1]) β[GPU: MobileNetV2 U-Net]β tpMap[1,9,256,256] | |
| ``` | |
| The output is a "TP map": channel 0 = line-center heatmap, channels 1β4 = start/end displacement. The decode | |
| (sigmoid + 3Γ3 NMS over centers, displacement β endpoints, Γ2) runs on the host. | |
| ## How it converts (litert-torch) | |
| Pure CNN encoder-decoder. A single re-authoring: the decoder's `F.interpolate(bilinear, align_corners=True)` | |
| β **`align_corners=False`** (the Mali delegate bans `align_corners=True` + half-pixel). MobileNetV2 has no | |
| max-pool (strided convs β no `PADV2`), and the upsample is `RESIZE_BILINEAR`, not a transposed conv β fully | |
| GPU-clean. Result: banned ops NONE, all tensors β€4D, tflite-vs-torch corr **1.0**, device-vs-torch corr **0.997**. | |
| ## Preprocessing & decode | |
| Resize to 512Γ512, append a 4th channel of ones, scale `(x/127.5) - 1`, NCHW. Decode: sigmoid the center map, | |
| 3Γ3 max NMS, threshold (0.10), displacement β endpoints, filter by length, Γ2 to 512-space. | |
| ## Minimal usage | |
| **Android (Kotlin, CompiledModel GPU)** | |
| ```kotlin | |
| val model = CompiledModel.create(context.assets, "mlsd_fp16.tflite", | |
| CompiledModel.Options(Accelerator.GPU), null) | |
| val inputs = model.createInputBuffers(); val outputs = model.createOutputBuffers() | |
| inputs[0].writeFloat(x) // [1,4,512,512] NCHW: RGB + ones channel, x/127.5 - 1 | |
| model.run(inputs, outputs) | |
| val tpMap = outputs[0].readFloat() // [1,9,256,256]: ch0 center, ch1-4 displacement | |
| // sigmoid + 3x3 NMS + displacement -> segments: port of the Python decode below. | |
| ``` | |
| **Python (desktop verification)** | |
| ```python | |
| import numpy as np | |
| from PIL import Image | |
| from scipy.ndimage import maximum_filter | |
| from ai_edge_litert.interpreter import Interpreter | |
| im = Image.open("photo.jpg").convert("RGB").resize((512, 512)) | |
| a = np.asarray(im, np.float32) | |
| a = np.concatenate([a, np.ones((512, 512, 1), np.float32)], -1) # 4th channel of ones | |
| x = ((a.transpose(2, 0, 1)[None] / 127.5) - 1.0).copy() # [1,4,512,512] | |
| it = Interpreter(model_path="mlsd_fp16.tflite"); it.allocate_tensors() | |
| it.set_tensor(it.get_input_details()[0]["index"], x); it.invoke() | |
| tp = it.get_tensor(it.get_output_details()[0]["index"])[0] # [9,256,256] | |
| center = 1 / (1 + np.exp(-tp[0])); disp = tp[1:5] | |
| peak = (center == maximum_filter(center, 3)) & (center > 0.10) # 3x3 NMS + threshold | |
| ys, xs = np.where(peak) | |
| order = center[ys, xs].argsort()[::-1][:200] # top-200 centers | |
| lines = [] | |
| for y, x0 in zip(ys[order], xs[order]): | |
| dxs, dys, dxe, dye = disp[:, y, x0] | |
| if np.hypot(dxs - dxe, dys - dye) > 20: # min segment length (px) | |
| lines.append([(x0 + dxs) * 2, (y + dys) * 2, (x0 + dxe) * 2, (y + dye) * 2]) | |
| print(f"{len(lines)} line segments (x0,y0,x1,y1 in 512-space)") | |
| ``` | |
| ## License | |
| [Apache-2.0](https://github.com/navervision/mlsd/blob/main/LICENSE). Upstream: | |
| [navervision/mlsd](https://github.com/navervision/mlsd); PyTorch port [lhwcv/mlsd_pytorch](https://github.com/lhwcv/mlsd_pytorch). | |