If your Android app needs the contrib modules, and aruco is usually the reason, the packaged OpenCV SDK will not do, and the walkthroughs you find online are years stale, so first attempts fail for weeks. The dependable route is the build script that ships in the OpenCV source tree: sources/platforms/android/build_sdk.py, run with --extra_modules_path pointed at the opencv_contrib modules directory. This guide covers the tools to install, one command with every flag traced to where it comes from, the check that proves the contrib modules landed in the artifacts, and the four failures that consume the most time.
The decision: one script, one pinned toolchain
Do not hand-type the cmake command. Run the in-tree script, and pin three versions before you touch anything: the NDK, CMake and the JDK. The script exists to emit the Android SDK layout and the AAR packages Android Studio consumes, with the Java bindings built and staged. A hand-rolled cmake -GNinja line reproduces only the compile step of that, and the gap between the two is exactly where the stale tutorials break.
The OpenCV project documents this route in its own build wiki for Ubuntu 20.04 and newer hosts; read it once so you know where each flag comes from. The one cmake variable you need to know by name is OPENCV_EXTRA_MODULES_PATH, and --extra_modules_path is its front door: same setting, fed by the script instead of your keyboard.
What the script assembles for you
build_sdk.py drives cmake for you. The invocation it builds is the one old tutorials hand-copy: -GNinja, -DOPENCV_EXTRA_MODULES_PATH= pointed at your contrib modules, -DBUILD_TESTS=ON, -DINSTALL_TESTS=ON, and the Android toolchain the NDK supplies. On top of the compile it stages each ABI, builds the Java wrappers, and packages the SDK and AAR you hand to Gradle.
That packaging is the part you cannot reproduce by hand without owning every ABI and binding difference. Artifacts from the script drop into Android Studio the way the official ones do; artifacts from a hand-rolled line are yours to debug forever.
Install the tools first
On Ubuntu 20.04 or newer: Android Studio, which installs the SDK and NDK; CMake and the Ninja build tool via sudo apt install ninja-build; and Python. The from-source guide sets the floors at CMake 3.14.1 and Ninja 1.9.0, and anything at or above those floors works.
On Windows, a working set looks like this:
- Git Bash on PATH.
- CMake 3.24.3 on PATH.
- The
jdk1.8.0_202JDK on PATH, withJAVA_HOMEpointing at it. - MSYS, ANT, Ninja and Python.
- A host C compiler: MinGW, or Visual Studio with the
-G "Visual Studio 17 2022"generator.
One builder ran the whole thing through Git Bash and skipped WSL entirely; WSL is the fallback when the native toolchain fights you.
Worked example: OpenCV 4.5.1 with contrib on Ubuntu 20.04
Inputs, each with its origin:
- Host: Ubuntu 20.04 or newer with
ninja-buildinstalled, per the build page prerequisites. - Sources: OpenCV 4.5.1 and the matching opencv_contrib 4.5.1, checked out side by side; 4.5.1 is the pairing this invocation shipped with.
- NDK and SDK: installed from Android Studio's SDK manager, with Show package details ticked so you see exactly which versions you pinned.
- Contrib path:
opencv_contrib/modules, the modules directory, never the repository root.
The paths below are illustrative; the flag names, their order and the two positional arguments are the documented ones:
cd ~/src/opencv
python sources/platforms/android/build_sdk.py \
--ndk_path=$HOME/Android/Sdk/ndk \
--sdk_path=$HOME/Android/Sdk \
--extra_modules_path=$HOME/src/opencv_contrib/modules \
$HOME/build/opencv-android \
$HOME/src/opencv/sources \
--opencl --no_samples_build
Read it in order: the script inside the checkout, then the NDK root and the SDK root, then the contrib modules directory, then the two positional arguments, build directory first ($HOME/build/opencv-android), OpenCV sources directory second ($HOME/src/opencv/sources), then the OpenCL switch and the samples switch.
Confirm success before opening Android Studio. Open the CMake cache in $HOME/build/opencv-android and check that OPENCV_EXTRA_MODULES_PATH carries $HOME/src/opencv_contrib/modules. That entry decides everything: empty means the run produced core-only libraries; set means the SDK and AAR under that directory carry the contrib modules. Decision rule: entry empty, stop, fix the flag and rerun; entry set, ship that AAR.
Put the artifacts into Android Studio
The AAR is a dependency like any other. For a project still on the old Manager-based setup, migrate an OpenCV Android project from Manager to a Gradle SDK setup covers the Gradle side, where most integration errors live. Keep exactly one OpenCV on the classpath: once a stale package and your fresh AAR coexist, you get the crashes described in OpenCV version conflicts that crash vision calibration tools.
The failures, in order of probability
-
A broken or mismatched NDK. The run dies inside the cmake compiler checks, and the recorded answer is blunt: probably a broken NDK, select a different build tool. Fix: install one NDK from Android Studio's SDK manager and point
--ndk_pathat it alone. - Two CMake installs. The warning above covers it; keep one version on PATH, first position. If CMake then cannot find OpenCV in the SDK samples, work through CMake can't find OpenCV in SDK samples: fix the config path, and apply the pinning discipline from Docker build SDK version mismatch: pin one library version everywhere.
-
JDK drift.
JAVA_HOMEpointing at one JDK while PATH resolves another breaks the Java packaging step late in the build. SetJAVA_HOMEexplicitly, exactly as the Windows list above does. - opencv and opencv_contrib at different versions. The build configures, then fails mid-compile on missing headers. Check out the same release tag in both trees. On desktop builds the same mismatch surfaces as the problem solved in missing OpenCV aruco module: fix the CMake calibration build.
The camera that feeds the build
The SDK is the software half; the pixels come from hardware, and choosing it is its own discipline, covered in board level camera selection for embedded vision machines and camera module interface compatibility: what to verify. From the board cameras range, the LS-BCM-0003 is a 3.1 MP USB3 board-level camera on the IMX252 sensor, 1/1.8" global shutter, 120 fps, C-mount.
- Sensor format
- 1/1.8"
- Lens mount
- C-mount
- Interface
- USB3
- Resolution
- 5 MP
The contrib calibration and stereo tools want two views, and a binocular module gives you both in one part. The LS-BCM-0043 is a binocular camera / module with a 1/2" sensor behind an M12 mount. Its resolution is 1.3 MP over USB3, colour or mono, and at the full 1280 x 1024 frame size it delivers 123FPS.
- Sensor format
- 1/2"
- Lens mount
- M12
- Interface
- USB3
- Resolution
- Up to 3 MP
Finish the hardware with one cable and one lens: a 3 m high-flex drag-chain USB3 camera cable, and a low-distortion 12 mm M12 lens in 1/1.8" format rated for 5 MP sensors, from the M12 board lenses and camera cables ranges.
- Sensor format
- 1/1.8"
- Mount
- M12
Once frames arrive through the camera SDK, wrap an SDK frame buffer in an OpenCV cv::Mat without copying keeps the pipeline at full frame rate. To lock camera, lens and controller as one set instead, start at the camera step of the system configurator.
When not to build at all
If every call your app makes resolves against the main library, do not build: the packaged Android SDK carries those modules, and your remaining work is the Gradle integration in the migration guide. If you need even one contrib module, typically aruco, you must build, and the worked example is the shortest path. Community prebuilt bundles install fast, but you inherit the maintainer's module list and version pin, which is fine for a demo and wrong for a machine you ship.
Checklist before the first run
- One NDK, installed from Android Studio's SDK manager, its path written next to the build command.
- One CMake on PATH, version known, and not the SDK-bundled one by accident.
-
JAVA_HOMEset, matching thejavathat PATH resolves. - opencv and opencv_contrib checked out at the same release tag.
-
ninja-buildand Python installed on the Ubuntu host. -
--extra_modules_pathaimed atopencv_contrib/modules, and the two positional arguments in order: build directory, then sources directory. - After the run, the CMake cache shows your contrib path before anything moves into Android Studio.
For the camera, lens and controller half of the system, ask us through the contact page and bring your pinned build with you.
Questions
Do I really need the contrib modules in my Android build?
Only if your code calls something that lives in the opencv_contrib tree, and aruco is the usual one. The test is a single lookup: find which module owns the class or function you want. Main-library owner means the packaged prebuilt Android SDK already carries it and you can skip the build. Contrib owner means you build, and the worked example is the shortest route.
Can I do this build on Windows instead of Linux?
Yes, and the PATH entries are where it goes wrong. Add C:\Program Files\Git\cmd, C:\Program Files\CMake\bin and C:\Program Files\Java\jdk1.8.0_202\bin to PATH, set JAVA_HOME to C:\Program Files\Java\jdk1.8.0_202, and add C:\MinGW\bin after installing mingw32-base-bin, mingw32-gcc-fortran-bin, mingw32-gcc-g++-bin, mingw32-gcc-objc-bin and msys-base-bin. One builder ran the entire build through Git Bash without WSL; WSL is the fallback when the native toolchain fights back.
How do I confirm the contrib modules made it into the AAR?
Open the CMake cache in the build directory you passed as the first positional argument and confirm OPENCV_EXTRA_MODULES_PATH holds your opencv_contrib/modules path. An empty value means core-only libraries, and no amount of refreshing Gradle will add aruco to them. Then run the app-side check: reference one contrib class against the new AAR, and if it resolves at compile time the module is in.
What exactly should --extra_modules_path point at?
At the modules directory inside the opencv_contrib checkout, opencv_contrib/modules, not the repository root and not a single module folder. The flag feeds OPENCV_EXTRA_MODULES_PATH, which expects the folder that contains every contrib module subdirectory. A relative form such as ../../../opencv_contrib/modules/ works when the two checkouts sit side by side. Aim it one level too high and the build configures cleanly with zero contrib modules, which is the silent failure.
What do --opencl and --no_samples_build change?
--opencl adds the OpenCL acceleration path to the native libraries; --no_samples_build skips compiling the sample applications. Keep both as the invocation shows them. Samples add build time and drag in errors from code you will never ship, while the OpenCL path is what you want on devices that expose a GPU-side runtime. Removing the flags changes the artifacts and the build time, not which contrib modules get compiled.



