If your last Android project with OpenCV used "OpenCV4Android", OpenCV Manager and a hand-copied sdk/ folder, almost everything has changed. In 2026 the official OpenCV Android SDK is a Maven Central artifact, camera access goes through CameraX, and the deep-learning half of the app often belongs in LiteRT rather than cv::dnn. This guide is the modern workflow.
1. Get OpenCV from Maven Central
Stop vendoring the SDK. The official artifact is org.opencv:opencv on Maven Central:
// app/build.gradle.kts
dependencies {
implementation("org.opencv:opencv:5.0.0")
implementation("androidx.camera:camera-core:1.4.2")
implementation("androidx.camera:camera-camera2:1.4.2")
implementation("androidx.camera:camera-lifecycle:1.4.2")
implementation("androidx.camera:camera-view:1.4.2")
}
Check Maven Central for the newest OpenCV and CameraX versions before copying those numbers. If you prefer the downloadable SDK zip from the GitHub release, note the 5.0.0 release note: the original Android SDK package was built with an old NDK whose bundled C++ standard library is not aligned for 16 KB memory pages, and Google Play now requires 16 KB page-size support. Use the package with the 16kb-page-fix suffix for Play releases, or the Maven artifact.
Initialize OpenCV once, in your Application or first Activity. OpenCVLoader.initLocal() replaced the old initAsync / OpenCV Manager dance years ago:
import org.opencv.android.OpenCVLoader
class App : Application() {
override fun onCreate() {
super.onCreate()
check(OpenCVLoader.initLocal()) { "OpenCV failed to load" }
}
}
2. Watch the OpenCV 5 Java package renames
OpenCV 5 moved several modules, and Java is the one binding where that requires code changes:
// OpenCV 4.x
import org.opencv.calib3d.Calib3d
import org.opencv.features2d.ORB
// OpenCV 5.x
import org.opencv.geometry.Geometry // findHomography, solvePnP
import org.opencv.calib.Calib // calibrateCamera, stereoCalibrate
import org.opencv.features.ORB
Imgproc.convexHull and friends moved to Geometry too. Haar cascades (CascadeClassifier) and HOG now live in opencv_contrib, which the standard Android artifact does not include; use FaceDetectorYN from objdetect instead for faces.
3. Camera frames with CameraX
Do not use the legacy JavaCameraView. CameraX's ImageAnalysis use case hands you YUV_420_888 frames on a background executor, which is exactly what a vision pipeline wants:
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import androidx.camera.lifecycle.ProcessCameraProvider
import org.opencv.core.CvType
import org.opencv.core.Mat
import org.opencv.imgproc.Imgproc
fun ImageProxy.toBgrMat(): Mat {
val y = planes[0].buffer
val u = planes[1].buffer
val v = planes[2].buffer
val ySize = y.remaining(); val uSize = u.remaining(); val vSize = v.remaining()
val nv21 = ByteArray(ySize + uSize + vSize)
y.get(nv21, 0, ySize)
v.get(nv21, ySize, vSize) // NV21 is Y then interleaved VU
u.get(nv21, ySize + vSize, uSize)
val yuv = Mat(height + height / 2, width, CvType.CV_8UC1)
yuv.put(0, 0, nv21)
val bgr = Mat()
Imgproc.cvtColor(yuv, bgr, Imgproc.COLOR_YUV2BGR_NV21)
yuv.release()
return bgr
}
val analysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888)
.build()
.also { ia ->
ia.setAnalyzer(analysisExecutor) { image ->
val bgr = image.toBgrMat()
try { process(bgr, image.imageInfo.rotationDegrees) } finally {
bgr.release(); image.close()
}
}
}
ProcessCameraProvider.getInstance(context).get()
.bindToLifecycle(lifecycleOwner, cameraSelector, preview, analysis)
The interleaved-VU copy above assumes the common pixel-stride-2 layout; check planes[1].pixelStride and fall back to a per-pixel copy (or ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888) on devices that report stride 1. STRATEGY_KEEP_ONLY_LATEST is the Android equivalent of appsink drop=true: never queue frames behind slow processing. Apply rotationDegrees with Core.rotate before you run anything orientation-sensitive.
4. Where the model runs: cv::dnn or LiteRT?
OpenCV 5's new DNN engine is excellent on desktop CPUs, but on a phone you usually want the NPU or GPU, and that is LiteRT's job. LiteRT is the renamed TensorFlow Lite; the Ultralytics LiteRT export produces a .tflite for YOLO26 that runs with the GPU or NNAPI/vendor delegates.
The practical split we use:
- OpenCV for capture conversion, letterboxing, color, undistortion, tracking, ArUco, drawing, and any classical pipeline.
- LiteRT for the neural network when you need hardware acceleration or a model under ~50 MB.
cv::dnnon Android only for small ONNX models where a single dependency matters more than peak speed.
Feed LiteRT from the same Mat you pre-processed with OpenCV and hand the [1, N, 6] end-to-end output straight back into OpenCV for drawing; YOLO26's NMS-free head means there is no post-processing to port.
5. JNI performance pitfalls
The Java bindings are fine for calling OpenCV functions; they are expensive for moving pixels. Rules that hold up in profiling:
- Never loop over pixels in Kotlin/Java.
Mat.get/Mat.putper element crosses JNI each call. Use whole-bufferput(row, col, ByteArray)or move the loop into C++. - Pass native addresses across the boundary, not copies.
Mat.getNativeObjAddr()gives you acv::Mat*to use in your own JNI function:
external fun processFrame(matAddr: Long): Int
processFrame(bgr.nativeObjAddr)
extern "C" JNIEXPORT jint JNICALL
Java_com_example_Vision_processFrame(JNIEnv*, jobject, jlong addr) {
cv::Mat& frame = *reinterpret_cast<cv::Mat*>(addr);
cv::cvtColor(frame, frame, cv::COLOR_BGR2GRAY); // in place, zero copy
return frame.rows;
}
- Release
Mats deterministically. The finalizer runs late; a camera loop that allocates a freshMatper frame and relies on GC will stutter.use {}-style helpers or explicitrelease()infinally. - Keep the analysis executor single-threaded and drop frames; parallel analyzers fight for the same cores the GPU delegate needs.
- Build OpenCV with only the modules you use (
-DBUILD_LIST=core,imgproc,imgcodecs,objdetect,features,geometry) if APK size matters; the full SDK adds tens of megabytes per ABI.
6. Checklist
org.opencv:opencvfrom Maven Central, or the16kb-page-fixSDK package for Play.OpenCVLoader.initLocal()at startup; no OpenCV Manager.- Update
calib3d/features2dimports for OpenCV 5. - CameraX
ImageAnalysiswithKEEP_ONLY_LATEST, YUV to BGR viacvtColor. - LiteRT for the accelerated model, OpenCV around it.
- No per-pixel JNI, native addresses across the boundary, explicit
release().
We build and modernize Android vision apps; get in touch if yours is still on OpenCV4Android.