RTMPose-Hand-LiteRT / README.md
mlboydaisuke's picture
Add minimal usage snippets (Kotlin + Python)
97e7be2 verified
|
Raw
History Blame Contribute Delete
3.33 kB
---
license: apache-2.0
library_name: LiteRT
pipeline_tag: keypoint-detection
tags: [litert, tflite, on-device, android, gpu, hand-pose, keypoint-detection, rtmpose, mmpose]
base_model: open-mmlab/mmpose
---
# RTMPose-Hand β€” LiteRT (on-device 21-keypoint hand pose, fully-GPU)
[RTMPose](https://github.com/open-mmlab/mmpose/tree/main/projects/rtmpose) (mmpose, CSPNeXt + RTMCC/SimCC head)
**hand** pose, converted to **LiteRT** and running **fully on the `CompiledModel` GPU** (ML Drift) on Android.
The **21 standard hand keypoints** (wrist + 4 joints Γ— 5 fingers) for a single centered hand.
![RTMPose-Hand β€” input | hand skeleton (on-device LiteRT GPU)](samples/sample.png)
## On-device (Pixel 8a, Tensor G3 β€” verified)
| | |
|---|---|
| nodes on GPU | **333 / 333** LITERT_CL (full residency) |
| inference | **~4 ms** (256Γ—256) |
| size | 28 MB (fp16) |
| accuracy | device-vs-PyTorch SimCC corr **0.999**, 21/21 keypoints |
```
image[1,3,256,256] (ImageNet 0-255) β†’[GPU: CSPNeXt + RTMCC]β†’ simcc_x[1,21,512], simcc_y[1,21,512]
```
## Minimal usage
**Android (Kotlin, CompiledModel GPU)**
```kotlin
val model = CompiledModel.create(context.assets, "rtmhand_fp16.tflite",
CompiledModel.Options(Accelerator.GPU), null)
val inputs = model.createInputBuffers()
val outputs = model.createOutputBuffers()
inputs[0].writeFloat(chw) // [1,3,256,256] mmpose mean/std (0-255 RGB), NCHW
model.run(inputs, outputs)
val simccX = outputs[0].readFloat() // [1,21,512]
val simccY = outputs[1].readFloat() // [1,21,512]; keypoint = argmax / 2
```
**Python (desktop verification)**
```python
MEAN = np.array([123.675, 116.28, 103.53], np.float32)
STD = np.array([58.395, 57.12, 57.375], np.float32)
import numpy as np
from PIL import Image
from ai_edge_litert.interpreter import Interpreter
img = Image.open("hand.jpg").convert("RGB").resize((256, 256)) # centered subject crop
x = ((np.asarray(img, np.float32) - MEAN) / STD).transpose(2, 0, 1)[None]
it = Interpreter(model_path="rtmhand_fp16.tflite"); it.allocate_tensors()
it.set_tensor(it.get_input_details()[0]["index"], x); it.invoke()
od = it.get_output_details() # output 0 = simcc_x, 1 = simcc_y
sx = it.get_tensor(od[0]["index"])[0] # simcc_x [21,512]
sy = it.get_tensor(od[1]["index"])[0] # simcc_y [21,512]
kx, ky = sx.argmax(-1) / 2.0, sy.argmax(-1) / 2.0 # 21 keypoints, px in 256x256
for i, (a, b) in enumerate(zip(kx, ky)):
print(f"kp{i}: ({a:.1f}, {b:.1f})")
```
## How it converts (litert-torch)
Identical RTMPose-family recipe (both numerically exact, no PixelShuffle since there's no neck):
1. **`ScaleNorm` (RMS) β†’ SafeRMSNorm** β€” fp16-overflow all-zero-head fix (scale x down by S=64 before squaring).
2. **GAU `act@act` BMM β†’ broadcast-multiply + reduce-sum**.
Result: banned ops NONE, all tensors ≀4D, tflite-vs-torch corr **1.0**, device-vs-torch corr **0.999**.
## Preprocessing
Center-crop to square, resize to 256Γ—256, ImageNet 0-255 normalize, NCHW. Top-down β€” one centered hand.
SimCC argmax (Γ· split=2) β†’ pixel.
## License
[Apache-2.0](https://github.com/open-mmlab/mmpose/blob/main/LICENSE). Upstream:
[open-mmlab/mmpose](https://github.com/open-mmlab/mmpose) RTMPose-Hand.