Skip to main content
This tutorial runs an Edge Impulse computer vision model on a live Android camera feed. CameraX captures each frame, the app converts it to the format your model expects, and JNI passes it to the Edge Impulse C++ classifier. The app draws the results over the camera preview. The same code handles image classification, object detection, and visual anomaly detection. To switch between them, deploy a different model.
Android app preview with bounding boxes around detected objects in a live camera feed

Object detection running on Android

If you’re new to the Android examples, start with Static buffer inference. It covers the JNI bridge and inference loop without a camera pipeline.

Prerequisites

  • A trained vision model in Edge Impulse: image classification, object detection, or visual anomaly detection
  • Android Studio with NDK 27.0.12077973 and CMake 3.22.1 installed
  • An Android device with a camera running API 24 or later

1. Clone the repository

2. Download TensorFlow Lite libraries

3. Export your model

  1. In Edge Impulse Studio, go to Deployment.
  2. Select Android (C++ library).
  3. Enable EON Compiler to reduce memory use and inference time.
  4. Click Build and download the .zip file.

4. Integrate the model

Extract the downloaded .zip file and copy its contents, except CMakeLists.txt, into app/src/main/cpp/. The folder then looks like this:

5. Build and run

  1. Open the project in Android Studio.
  2. Select Build -> Make Project.
  3. Connect your Android device.
  4. Run the app and grant the camera permission when prompted.
Point the camera at an object. The inference results appear over the preview.

How it works

CameraX sends frames to an ImageAnalysis use case, which converts them to RGB and passes them to the C++ classifier. A custom overlay view draws the results.

Camera setup

CameraX binds a preview stream and a frame analysis pipeline to the activity lifecycle. The STRATEGY_KEEP_ONLY_LATEST backpressure strategy drops frames that arrive while the classifier is busy, so the preview stays smooth:

Image processing

The app converts each frame to an RGB byte array and runs inference on a background coroutine. It closes the ImageProxy straight after reading it to free the camera buffer:

Native inference

The image crosses the JNI boundary as a byte array, and the native code passes it to the classifier as a signal:

Bounding box overlay

For object detection, a custom View subclass draws bounding boxes and labels directly over the camera preview:

Customization

Adjust the confidence threshold

Limit inference frequency

If the classifier is slower than the camera frame rate, run inference at a fixed interval:

Bounding box alignment

If bounding boxes are offset from the objects they mark, scale them from the model’s input size to the view size:

Troubleshooting

  • Run inference every 200 to 300 ms instead of on every frame.
  • Lower the camera resolution with setTargetResolution().
  • Check that XNNPACK is enabled in CMakeLists.txt.
  • Try a smaller model architecture.
  • If confidence is consistently low, lower the threshold slightly.
  • Check that the lighting matches your training data.
  • Check that the camera resolution is close to the model’s input resolution. A large mismatch reduces accuracy.
  • Check that the TFLite libraries are in app/src/main/cpp/tensorflow-lite/.
  • Check that edge-impulse-sdk/, model-parameters/, and tflite-model/ are all in app/src/main/cpp/.
  • Check that your NDK and CMake versions match the prerequisites.

Next steps

Resources