Sunday, February 01, 2015

Building OpenCV for Android from Source

Reference

Introduction

  1. Check Android Version
    1. Most of Example does not run completely on Lollipop because native camera are not supported by OpenCV 2.4.10.  libnative_camera_r5.0.0.so does not exist yet.
  2. Download Android source code and compile it 
    1. For full build including "OpenCV Manager(or Engine)" android platform source tree is needed. Check my previous post (http://jylee-world.blogspot.kr/2013/11/android-platform-nexus-4-rooting.html)
    2. We get opencv_engine.apk(including libOpenCVEngine.so and libOpenCVEngine_jni.so) by setting source tree.
  3. Download android NDK and set ANDROID_NDK
  4. Download android SDK and set ANDROID_SDK
  5. Setup for cmake (explained in detail in the next sections)
    1. Modify cmake_android_arm.sh
    2. Combine cmake_android_service.sh)
  6. Apply minor source modifications (explained in detail in the next sections)
  7. cmake
  8. make
 I use opencv-2.4.10-release, android-ndk-r9d(NDK),  adt-bundle-linux-x86_64-20140702(SDK), android-4.4.2_r1 (Android Source)


Prerequisite

Unfortunately the following list is not complete.
build-essentilal cmake ant ...blah blah

Create platform_source.properties file in Android source tree

When cmake start to compile, opencv-2.4.10/modules/androidcamera/CMakeLists.txt needs this file to parse ${ANDROID_VERSION}. I use 4.4 instead of 4.4.2 because opencv-2.4.10 source code does not support 4.4.2.
cat ./android-4.4.2_r1/development/sdk/platform_source.properties
Pkg.Desc=Android SDK Platform 4.4
Pkg.UserSrc=false
Platform.Version=4.4
Platform.CodeName=KitKat
Pkg.Revision=1
AndroidVersion.ApiLevel=19
AndroidVersion.CodeName=kitkat
Layoutlib.Api=10
Layoutlib.Revision=1
Platform.MinToolsRev=22

Generating Makefines using cmake

There are some scripts for building opencv for android in directory ./platforms/scripts. Documents such as  (http://answers.opencv.org/question/3699/building-android-manager-from-git-repository/ ) recommend to use ./platforms/scripts/cmake_android_arm.sh for arm android machines. But this scripts is not complete. I use the following option for my NEXUS4 machine. (I don't like to follow auto parsing by scripts.)
-DANDROID_ABI="armeabi-v7a with NEON"
-DANDROID_TOOLCHAIN_NAME="arm-linux-androideabi-4.8"
-DANDROID_NATIVE_API_LEVEL=19
 And I add the following options for building OpenCV Manager.
-DBUILD_ANDROID_SERVICE=ON
-DBUILD_ANDROID_PACKAGE=ON
-DANDROID_SOURCE_TREE=<my android platform directory>
-DANDROID_PRODUCT=mako            /* for NEXUX4 */
And options for building camera. This is not required.
-DBUILD_ANDROID_CAMERA_WRAPPER=ON
So my final script is ...
cmake -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DCMAKE_TOOLCHAIN_FILE=../android/android.toolchain.cmake \
       -DANDROID_ABI="armeabi-v7a with NEON" \
       -DANDROID_TOOLCHAIN_NAME="arm-linux-androideabi-4.8" \
       -DANDROID_NATIVE_API_LEVEL=19 \
       -DBUILD_ANDROID_CAMERA_WRAPPER=ON \
       -DBUILD_ANDROID_SERVICE=ON \
       -DBUILD_ANDROID_PACKAGE=ON \
       -DANDROID_SOURCE_TREE=/home/rofox/work/android/android-4.4.2_r1/platform \
       -DANDROID_PRODUCT=mako $@ ../..

Code Change for package setup

Even though you build your own libnative_camera_rX.X.X.so, unless you modify CMakeLists.txt in platforms/android/package  prebuilt libraries in 3rdparty/lib/armeabi-v7a/lib*.so are copied to OpenCV_2.4.10_binary_pack_armv7a_neon-release-unsigned.apk instead of yours. The following code is my own setup.

diff --git a/platforms/android/package/CMakeLists.txt b/platforms/android/package/CMakeLists.txt
index b48a55a..4770fdb 100644
--- a/platforms/android/package/CMakeLists.txt
+++ b/platforms/android/package/CMakeLists.txt
@@ -79,7 +79,9 @@ add_custom_command(
          COMMAND ${CMAKE_COMMAND} -E remove ${android_proj_target_files}
          COMMAND ${CMAKE_COMMAND} -E make_directory "${PACKAGE_DIR}/src"
          COMMAND ${CMAKE_COMMAND} -E make_directory "${PACKAGE_DIR}/libs/${ANDROID_NDK_ABI_NAME}/"
-         ${CAMERA_LIB_COMMANDS}
+         #${CAMERA_LIB_COMMANDS}
+         COMMAND ${CMAKE_COMMAND} -E copy "${OpenCV_BINARY_DIR}/lib/${ANDROID_NDK_ABI_NAME}/libopencv_info.so" "${PACKAGE_DIR}/libs/${ANDROID_NDK_ABI_NAME}/"
+         COMMAND ${CMAKE_COMMAND} -E copy "${OpenCV_BINARY_DIR}/lib/${ANDROID_NDK_ABI_NAME}/libnative_camera_r${ANDROID_VERSION}.so" "${PACKAGE_DIR}/libs/${ANDROID_NDK_ABI_NAME}/"
          COMMAND ${CMAKE_COMMAND} -E copy "${opencv_java_location}" "${PACKAGE_DIR}/libs/${ANDROID_NDK_ABI_NAME}/"
          COMMAND ${ANDROID_EXECUTABLE} --silent update project --path "${PACKAGE_DIR}" --target "${android_proj_sdk_target}" --name "${target_name}"
          COMMAND ${ANT_EXECUTABLE} -q -noinput -k release

Signing OpenCV_2.4.10_binary_pack_armv7a_neon-release-unsigned.apk 

If you try "adb install OpenCV_2.4.10_binary_pack_armv7a_neon-release-unsigned.apk" without any manipulation, you will fail with the following message.
$ adb install OpenCV_2.4.10_binary_pack_armv7a_neon-release-unsigned.apk
2823 KB/s (6458213 bytes in 2.233s)
    pkg: /data/local/tmp/OpenCV_2.4.10_binary_pack_armv7a_neon-release-unsigned.apk
Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES]
Generated Binary pack package is not signed in release mode. I do not know yet How to build DEBUG mode without release mode signing. Anyway it is easy to sign this package. I prefer using command line to GUI tool.
Use keytool  to generate keystore, which is located in Java SDK tools.
 $ keytool -genkey -v -keystore ~/myrelease.keystore -alias android_sign -keyalg RSA -keysize 2048 -validity 10000
Enter keystore password:
Re-enter new password:
What is your first and last name?
  [Unknown]:  <type your_name>
What is the name of your organizational unit?
  [Unknown]:  <type your organization unit>
What is the name of your organization?
  [Unknown]:  <type your organization>
What is the name of your City or Locality?
  [Unknown]:  <type your city>
What is the name of your State or Province?
  [Unknown]:  <type your state>
What is the two-letter country code for this unit?
  [Unknown]:  <type your country code, in my case 82 for korea>
Is CN=<your_name>, OU=<your unit>, O=<your organization>, L=<your city>, ST=<your state>, C=<your country code> correct?
  [no]:  yes
Generating 2,048 bit RSA key pair and self-signed certificate (SHA256withRSA) with a validity of 10,000 days
    for: CN=<your_name>, OU=<your unit>, O=<your organization>, L=<your city>, ST=<your state>, C=<your country code>
Enter key password for <android_sign>
    (RETURN if same as keystore password):
Re-enter new password:
[Storing <home_dir>/myrelease.keystore]
After then sign using the generated myrelease. keystore 
 $ jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ~/myrelease.keystore OpenCV_2.4.10_binary_pack_armv7a_neon-release-unsigned.apk android_sign
Enter Passphrase for keystore:
   adding: META-INF/MANIFEST.MF
   adding: META-INF/ANDROID_.SF
   adding: META-INF/ANDROID_.RSA
  signing: res/drawable/icon.png
  signing: AndroidManifest.xml
  signing: resources.arsc
  signing: classes.dex
  signing: lib/armeabi-v7a/libnative_camera_r4.0.3.so
  signing: lib/armeabi-v7a/libnative_camera_r4.2.0.so
  signing: lib/armeabi-v7a/libnative_camera_r3.0.1.so
  signing: lib/armeabi-v7a/libopencv_java.so
  signing: lib/armeabi-v7a/libnative_camera_r2.3.3.so
  signing: lib/armeabi-v7a/libnative_camera_r2.2.0.so
  signing: lib/armeabi-v7a/libnative_camera_r4.1.1.so
  signing: lib/armeabi-v7a/libnative_camera_r4.3.0.so
  signing: lib/armeabi-v7a/libnative_camera_r4.4.0.so
  signing: lib/armeabi-v7a/libnative_camera_r4.0.0.so
jar signed.
Warning:
No -tsa or -tsacert is provided and this jar is not timestamped. Without a timestamp, users may not be able to validate this jar after the signer certificate's expiration date (2042-06-19) or after any future revocation date.
Verification is ...
$ jarsigner -verify -verbose -certs OpenCV_2.4.10_binary_pack_armv7a_neon-release-unsigned.apk
s       1286 Sun Feb 01 20:43:14 KST 2015 META-INF/MANIFEST.MF
...
...
  [certificate is valid from 2/1/15 8:41 PM to 6/19/42 8:41 PM]
      [CertPath not validated: Path does not chain with any of the trust anchors]
  s = signature was verified
  m = entry is listed in manifest
  k = at least one certificate was found in keystore
  i = at least one certificate was found in identity scope
jar verified.
Warning:
This jar contains entries whose certificate chain is not validated.
This jar contains signatures that does not include a timestamp. Without a timestamp, users may not be able to validate this jar after the signer certificate's expiration date (2042-06-19) or after any future revocation date.
Now you can install using "adb install"

Minor Corrections

I found some errors. These are maybe caused by Android Source Version mismatch. But I try to correct them by myself. Most of them are INCLUDE directory setup and undefined LOG functions. They are easy to resolve.  
diff --git a/modules/androidcamera/camera_wrapper/CMakeLists.txt b/modules/androidcamera/camera_wrapper/CMakeLists.txt
index d08e2c4..b8d9326 100644
--- a/modules/androidcamera/camera_wrapper/CMakeLists.txt
+++ b/modules/androidcamera/camera_wrapper/CMakeLists.txt
@@ -2,7 +2,7 @@ SET (the_target native_camera_r${ANDROID_VERSION})
 
 project(${the_target})
 
-link_directories("${ANDROID_SOURCE_TREE}/out/target/product/generic/system/lib")
+link_directories("${ANDROID_SOURCE_TREE}/out/target/product/${ANDROID_PRODUCT}/system/lib")
 
 if (ANDROID_VERSION VERSION_LESS "4.1")
     INCLUDE_DIRECTORIES(BEFORE
diff --git a/platforms/android/package/CMakeLists.txt b/platforms/android/package/CMakeLists.txt
index b48a55a..4770fdb 100644
--- a/platforms/android/package/CMakeLists.txt
+++ b/platforms/android/package/CMakeLists.txt
@@ -79,7 +79,9 @@ add_custom_command(
          COMMAND ${CMAKE_COMMAND} -E remove ${android_proj_target_files}
          COMMAND ${CMAKE_COMMAND} -E make_directory "${PACKAGE_DIR}/src"
          COMMAND ${CMAKE_COMMAND} -E make_directory "${PACKAGE_DIR}/libs/${ANDROID_NDK_ABI_NAME}/"
-         ${CAMERA_LIB_COMMANDS}
+         #${CAMERA_LIB_COMMANDS}
+         COMMAND ${CMAKE_COMMAND} -E copy "${OpenCV_BINARY_DIR}/lib/${ANDROID_NDK_ABI_NAME}/libopencv_info.so" "${PACKAGE_DIR}/libs/${ANDROID_NDK_ABI_NAME}/"
+         COMMAND ${CMAKE_COMMAND} -E copy "${OpenCV_BINARY_DIR}/lib/${ANDROID_NDK_ABI_NAME}/libnative_camera_r${ANDROID_VERSION}.so" "${PACKAGE_DIR}/libs/${ANDROID_NDK_ABI_NAME}/"
          COMMAND ${CMAKE_COMMAND} -E copy "${opencv_java_location}" "${PACKAGE_DIR}/libs/${ANDROID_NDK_ABI_NAME}/"
          COMMAND ${ANDROID_EXECUTABLE} --silent update project --path "${PACKAGE_DIR}" --target "${android_proj_sdk_target}" --name "${target_name}"
          COMMAND ${ANT_EXECUTABLE} -q -noinput -k release
diff --git a/platforms/android/service/engine/CMakeLists.txt b/platforms/android/service/engine/CMakeLists.txt
index b1cac93..7dd9b64 100644
--- a/platforms/android/service/engine/CMakeLists.txt
+++ b/platforms/android/service/engine/CMakeLists.txt
@@ -71,6 +71,7 @@ include_directories(
   "${ANDROID_SOURCE_TREE}/frameworks/base/include"
   "${ANDROID_SOURCE_TREE}/system/core/include"
   "${ANDROID_SOURCE_TREE}/frameworks/base/core/jni"
+  "${ANDROID_SOURCE_TREE}/frameworks/native/include"
   )
 
 add_library(${engine}_jni SHARED ${engine_jni_files})
diff --git a/platforms/android/service/engine/jni/BinderComponent/BnOpenCVEngine.cpp b/platforms/android/service/engine/jni/BinderComponent/BnOpenCVEngine.cpp
index fccb329..bda5364 100644
--- a/platforms/android/service/engine/jni/BinderComponent/BnOpenCVEngine.cpp
+++ b/platforms/android/service/engine/jni/BinderComponent/BnOpenCVEngine.cpp
@@ -5,6 +5,8 @@
 #include 
 #include 
 
+#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
+
 using namespace android;
 
 BnOpenCVEngine::~BnOpenCVEngine()
diff --git a/platforms/android/service/engine/jni/BinderComponent/HardwareDetector.cpp b/platforms/android/service/engine/jni/BinderComponent/HardwareDetector.cpp
index b842987..5ebfefa 100644
--- a/platforms/android/service/engine/jni/BinderComponent/HardwareDetector.cpp
+++ b/platforms/android/service/engine/jni/BinderComponent/HardwareDetector.cpp
@@ -5,6 +5,7 @@
 #include "StringUtils.h"
 #include 
 
+#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
 using namespace std;
 
 int GetCpuID()
diff --git a/platforms/android/service/engine/jni/BinderComponent/OpenCVEngine.cpp b/platforms/android/service/engine/jni/BinderComponent/OpenCVEngine.cpp
index 6c17e22..aac0f5e 100644
--- a/platforms/android/service/engine/jni/BinderComponent/OpenCVEngine.cpp
+++ b/platforms/android/service/engine/jni/BinderComponent/OpenCVEngine.cpp
@@ -11,6 +11,11 @@
 #include 
 #include 
 
+#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
+#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
+#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
+#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
+
 using namespace android;
 
 const int OpenCVEngine::Platform = DetectKnownPlatforms();
diff --git a/platforms/android/service/engine/jni/JNIWrapper/JavaBasedPackageManager.cpp b/platforms/android/service/engine/jni/JNIWrapper/JavaBasedPackageManager.cpp
index d202d65..4c72267 100644
--- a/platforms/android/service/engine/jni/JNIWrapper/JavaBasedPackageManager.cpp
+++ b/platforms/android/service/engine/jni/JNIWrapper/JavaBasedPackageManager.cpp
@@ -5,6 +5,11 @@
 #undef LOG_TAG
 #define LOG_TAG "JavaBasedPackageManager"
 
+#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
+#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
+#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
+#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
+
 using namespace std;
 
 JavaBasedPackageManager::JavaBasedPackageManager(JavaVM* JavaMashine, jobject MarketConnector):
diff --git a/platforms/android/service/engine/jni/JNIWrapper/OpenCVEngine_jni.cpp b/platforms/android/service/engine/jni/JNIWrapper/OpenCVEngine_jni.cpp
index dac4916..6c0f0d6 100644
--- a/platforms/android/service/engine/jni/JNIWrapper/OpenCVEngine_jni.cpp
+++ b/platforms/android/service/engine/jni/JNIWrapper/OpenCVEngine_jni.cpp
@@ -10,6 +10,11 @@
 #undef LOG_TAG
 #define LOG_TAG "OpenCVEngine/JNI"
 
+#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
+#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
+#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
+#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
+
 using namespace android;
 
 sp OpenCVEngineBinder = NULL;
diff --git a/platforms/android/service/engine/jni/JNIWrapper/OpenCVLibraryInfo.cpp b/platforms/android/service/engine/jni/JNIWrapper/OpenCVLibraryInfo.cpp
index e7dc6d2..36646cb 100644
--- a/platforms/android/service/engine/jni/JNIWrapper/OpenCVLibraryInfo.cpp
+++ b/platforms/android/service/engine/jni/JNIWrapper/OpenCVLibraryInfo.cpp
@@ -3,6 +3,11 @@
 #include 
 #include 
 
+#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
+#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
+#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
+#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
+
 JNIEXPORT jlong JNICALL Java_org_opencv_engine_OpenCVLibraryInfo_open
   (JNIEnv * env, jobject, jstring str)
 {
diff --git a/platforms/android/service/engine/jni/NativeService/CommonPackageManager.cpp b/platforms/android/service/engine/jni/NativeService/CommonPackageManager.cpp
index 5c5022f..ce71be8 100644
--- a/platforms/android/service/engine/jni/NativeService/CommonPackageManager.cpp
+++ b/platforms/android/service/engine/jni/NativeService/CommonPackageManager.cpp
@@ -9,6 +9,11 @@
 #undef LOG_TAG
 #define LOG_TAG "CommonPackageManager"
 
+#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
+#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
+#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
+#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
+
 using namespace std;
 
 vector CommonPackageManager::GetInstalledVersions()
diff --git a/platforms/android/service/engine/jni/NativeService/PackageInfo.cpp b/platforms/android/service/engine/jni/NativeService/PackageInfo.cpp
index ca364b4..a49bb27 100644
--- a/platforms/android/service/engine/jni/NativeService/PackageInfo.cpp
+++ b/platforms/android/service/engine/jni/NativeService/PackageInfo.cpp
@@ -8,6 +8,11 @@
 #include 
 #include 
 
+#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
+#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
+#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
+#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
+
 using namespace std;
 
 map PackageInfo::InitPlatformNameMap()
diff --git a/platforms/android/service/engine/jni/Tests/HardwareDetectionTest.cpp b/platforms/android/service/engine/jni/Tests/HardwareDetectionTest.cpp
index 8e7dfab..f8b5604 100644
--- a/platforms/android/service/engine/jni/Tests/HardwareDetectionTest.cpp
+++ b/platforms/android/service/engine/jni/Tests/HardwareDetectionTest.cpp
@@ -147,10 +147,12 @@ TEST(CpuID, CheckMips)
 }
 #endif
 #else
+#if defined(USE_TEGRA_HW_DETECTOR)
 TEST(TegraDetector, Detect)
 {
     EXPECT_TRUE(DetectTegra() != 0);
 }
+#endif
 
 TEST(CpuID, CheckArmV7)
 {
diff --git a/platforms/android/service/engine/jni/Tests/OpenCVEngineTest.cpp b/platforms/android/service/engine/jni/Tests/OpenCVEngineTest.cpp
index b0bb6d5..6f4c8e8 100644
--- a/platforms/android/service/engine/jni/Tests/OpenCVEngineTest.cpp
+++ b/platforms/android/service/engine/jni/Tests/OpenCVEngineTest.cpp
@@ -13,6 +13,11 @@
 #include 
 #include 
 
+#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
+#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
+#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
+#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
+
 using namespace android;
 
 class ServiceStarter

No comments: