--- license: apache-2.0 library_name: litert pipeline_tag: image-classification tags: - litert - tflite - android - on-device - gpu - fine-grained - plant-identification - plantnet - resnet18 --- # PlantNet-300K ResNet18 — LiteRT (plant species ID, GPU) On-device **fine-grained plant species identification** — 1081 species — running **fully on the LiteRT `CompiledModel` GPU** delegate (no CPU fallback). A [PlantNet-300K](https://github.com/plantnet/PlantNet-300K) (NeurIPS 2021) ResNet18. ~16 ms/frame on a Pixel 8a. - **Architecture:** torchvision ResNet18 (pure CNN). - **Weights:** [cpoisson/plantnet300k-resnet18](https://huggingface.co/cpoisson/plantnet300k-resnet18) · Apache-2.0. - **Classes:** 1081 plant species (Latin names). - **Size:** 47 MB. ![PlantNet-300K plant identification](hero.png) ## I/O - **Input:** `[1, 3, 224, 224]` NCHW, RGB, ImageNet-normalized (mean `[0.485,0.456,0.406]`, std `[0.229,0.224,0.225]`; center-crop then resize 224). - **Output:** `[1, 1081]` species logits — softmax + top-k for the predicted species. **Labels:** class index `i` maps to the `i`-th species when the PlantNet-300K species-id strings are sorted (torchvision `ImageFolder` order); names from `plantnet300K_species_id_2_name.json`. ## GPU conversion Plain torchvision ResNet18 — a pure CNN. It converts to a fully GPU-compatible graph (**37/37 nodes on the delegate, 1 partition**; device corr 0.99999, top-1 match) with **one patch**: the ResNet stem `MaxPool2d(padding=1)` lowers to a PADV2 with `-inf` padding (`PADV2: src has wrong size` on the Mali delegate), replaced by an explicit 0-pad + unpadded maxpool — exact, since the maxpool input is post-ReLU (≥ 0). CPU-exact vs PyTorch (corr 0.99999999999). ## Minimal usage ### Kotlin (Android, LiteRT CompiledModel GPU) ```kotlin val options = CompiledModel.Options(Accelerator.GPU) val model = CompiledModel.create(context.assets, "plantnet.tflite", options, null) val inBufs = model.createInputBuffers() val outBufs = model.createOutputBuffers() inBufs[0].writeFloat(inputNCHW) // [1,3,224,224], RGB, ImageNet-norm model.run(inBufs, outBufs) val logits = outBufs[0].readFloat() // [1081] val top = logits.indices.sortedByDescending { logits[it] }.take(5) // species indices ``` ### Python (LiteRT / ai-edge-litert) ```python from ai_edge_litert.interpreter import Interpreter import numpy as np it = Interpreter(model_path="plantnet.tflite"); it.allocate_tensors() inp, out = it.get_input_details(), it.get_output_details() it.set_tensor(inp[0]["index"], x) # [1,3,224,224] float32, ImageNet-norm it.invoke() logits = it.get_tensor(out[0]["index"])[0] # [1081] top5 = logits.argsort()[::-1][:5] ``` ## Conversion Converted with **litert-torch** (`build_plantnet.py`): loads the Apache-2.0 ResNet18 weights, applies the ZeroPadMaxPool patch, and exports. ## License Apache-2.0 (weights: cpoisson/plantnet300k-resnet18). PlantNet-300K code: BSD-2-Clause (plantnet/PlantNet-300K, NeurIPS 2021).