工作需要,在android上使用opencv。opencv当前的版本(2.4.8)已经有了opencv4android,但是一方面这个SDK将所有opencv的功能打到了一个so包里,所以so的体积较大,从而造成使用该so的apk也大,上层对此不太满意;另一方面,使用opencv4android必须在手机上安装openv manager,上层对此也感觉有点别扭。所以我尝试用opencv的源码自己编译应用于android平台的opencv动态库以供底层开发用。首先介绍一下相关环境:
opencv: opencv2.4.8 (linux)
系统:ubuntu12.04
开发环境:eclipse juno(with CDT等), android ndk r9c
针对android开发可用的so库需要用ndk build编译,我详细查看了下opencv.org上的文档,上面只有opencv在linux,win等上的编译方法,当然还有opencv4android的使用方法。网上的文档找到一个使用老版本opencv来自己编译的帖子,但版本较老就没采用,所以只能自己分析下opencv的源码写mk文件进行编译。
为了压缩动态库的体积,也根据自己的实际需求,暂时只编译了三个库:core,imgproc,highgui. 幸运的是,新版本的opencv模块化比较好,所以不存在太多源码拆分的问题。所以,将源码中module文件夹下的三个模块源码放到jni/opencv目录下就可以了。之后在opencv文件夹下编写Android.mk文件,如下:
LOCAL_PATH:= $(call my-dir)
ZLIB_PATH:= /home/yxh/eclipse/android-ndk-r9c/platforms/android-14/arch-arm/usr
#opencv_core module
include $(CLEAR_VARS)
LOCAL_MODULE := libopencv_core
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/core/include \
$(ZLIB_PATH)/include
LOCAL_CPPFLAGS := -frtti -fexceptions
LOCAL_LDLIBS += -llog -lz
LOCAL_SRC_FILES := \
core/src/precomp.cpp \
core/src/algorithm.cpp \
core/src/alloc.cpp \
core/src/arithm.cpp \
core/src/array.cpp \
core/src/cmdparser.cpp \
core/src/convert.cpp \
core/src/copy.cpp \
core/src/datastructs.cpp \
core/src/drawing.cpp \
core/src/dxt.cpp \
core/src/gpumat.cpp \
core/src/lapack.cpp \
core/src/mathfuncs.cpp \
core/src/matmul.cpp \
core/src/matop.cpp \
core/src/matrix.cpp \
core/src/opengl_interop_deprecated.cpp \
core/src/opengl_interop.cpp \
core/src/persistence.cpp \
core/src/parallel.cpp \
core/src/rand.cpp \
core/src/stat.cpp \
core/src/system.cpp \
core/src/tables.cpp
include $(BUILD_SHARED_LIBRARY)
#opencv_imgproc module
include $(CLEAR_VARS)
LOCAL_MODULE := libopencv_imgproc
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/core/include \
$(LOCAL_PATH)/imgproc/include \
$(ZLIB_PATH)/include
LOCAL_CPPFLAGS := -frtti -fexceptions
LOCAL_LDLIBS += -llog -lz
LOCAL_SHARED_LIBRARIES := libopencv_core
LOCAL_SRC_FILES := \
imgproc/src/accum.cpp \
imgproc/src/approx.cpp \
imgproc/src/canny.cpp \
imgproc/src/clahe.cpp \
imgproc/src/color.cpp \
imgproc/src/contours.cpp \
imgproc/src/convhull.cpp \
imgproc/src/corner.cpp \
imgproc/src/cornersubpix.cpp \
imgproc/src/deriv.cpp \
imgproc/src/distransform.cpp \
imgproc/src/emd.cpp \
imgproc/src/featureselect.cpp \
imgproc/src/filter.cpp \
imgproc/src/floodfill.cpp \
imgproc/src/gabor.cpp \
imgproc/src/generalized_hough.cpp \
imgproc/src/geometry.cpp \
imgproc/src/grabcut.cpp \
imgproc/src/histogram.cpp \
imgproc/src/hough.cpp \
imgproc/src/imgwarp.cpp \
imgproc/src/linefit.cpp \
imgproc/src/matchcontours.cpp \
imgproc/src/moments.cpp \
imgproc/src/morph.cpp \
imgproc/src/phasecorr.cpp \
imgproc/src/pyramids.cpp \
imgproc/src/rotcalipers.cpp \
imgproc/src/samplers.cpp \
imgproc/src/segmentation.cpp \
imgproc/src/shapedescr.cpp \
imgproc/src/smooth.cpp \
imgproc/src/subdivision2d.cpp \
imgproc/src/sumpixels.cpp \
imgproc/src/tables.cpp \
imgproc/src/templmatch.cpp \
imgproc/src/thresh.cpp \
imgproc/src/undistort.cpp \
imgproc/src/utils.cpp
include $(BUILD_SHARED_LIBRARY)
#opencv_highgui module
include $(CLEAR_VARS)
LOCAL_MODULE := libopencv_highgui
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/core/include \
$(LOCAL_PATH)/imgproc/include \
$(LOCAL_PATH)/highgui/include \
$(ZLIB_PATH)/include \
/usr/include \
/usr/include/x86_64-linux-gnu
LOCAL_CPPFLAGS := -frtti -fexceptions
LOCAL_LDLIBS := -llog -lz
LOCAL_SHARED_LIBRARIES := libopencv_core
LOCAL_SHARED_LIBRARIES += libjpeg libpng
LOCAL_SRC_FILES := \
highgui/src/bitstrm.cpp \
highgui/src/cap.cpp \
highgui/src/cap_ffmpeg.cpp \
highgui/src/cap_images.cpp \
highgui/src/cap_v4l.cpp \
highgui/src/grfmt_base.cpp \
highgui/src/grfmt_bmp.cpp \
highgui/src/grfmt_imageio.cpp \
highgui/src/grfmt_jpeg.cpp \
highgui/src/grfmt_png.cpp \
highgui/src/grfmt_pxm.cpp \
highgui/src/grfmt_sunras.cpp \
highgui/src/loadsave.cpp \
highgui/src/utils.cpp \
highgui/src/window.cpp \
highgui/src/window_gtk.cpp \
highgui/src/grfmt_exr.cpp \
highgui/src/grfmt_tiff.cpp
include $(BUILD_SHARED_LIBRARY)
说几点mk文件中需要注意的地方。
1、opencv依赖于zlib库,该库在NDK中已经提供了,但要在include的时候加入进去,所以定义了ZLIB_PATH变量,将其指向zlib.h的位置,并在之后三个模块的编译中加入到include中。
2、LOCAL_CPPFLAGS := -frtti -fexceptions,是编译opencv必须的编译标志,不加会提示exception等错误。如果是在工作中某个系统里(类ANDROID)中增加opencv的功能,但系统的编译选项中没开-frtti,则会在链接的时候报错。
3、前两个模块比较好弄,因为相对独立,需要注意的是core/include下需要将源码中生成的cvconfig.h文件拷贝过去,这个文件包含很多宏定义来控制opencv编译中哪些功能打开哪些功能关闭。实际上,比较麻烦的是highgui库的编译,以为设计的依赖比较多。首先声明的是,因为功能划分,本人在编译中没有打开gtk+等界面工具,只进行数据的处理。同时,经过查询比较,本人认为现版本Opencv在NDK-BUILD下编译时也不支持ffmpeg的相关功能。我曾经尝试打开ffmpeg相关开关并进行编译调试,调试到后来在源文件中发现某线程功能的条件编译不支持android ndk下的编译(供出源码,摘自cap_ffmpeg_impl.hpp)
#if defined WIN32 || defined _WIN32 || defined WINCE
struct ImplMutex::Impl
{
void init()
{
#if (_WIN32_WINNT >= 0x0600)
::InitializeCriticalSectionEx(&cs, 1000, 0);
#else
::InitializeCriticalSection(&cs);
#endif
refcount = 1;
}
void destroy() { DeleteCriticalSection(&cs); }
void lock() { EnterCriticalSection(&cs); }
bool trylock() { return TryEnterCriticalSection(&cs) != 0; }
void unlock() { LeaveCriticalSection(&cs); }
CRITICAL_SECTION cs;
int refcount;
};
#ifndef __GNUC__
static int _interlockedExchangeAdd(int* addr, int delta)
{
#if defined _MSC_VER && _MSC_VER >= 1500
return (int)_InterlockedExchangeAdd((long volatile*)addr, delta);
#else
return (int)InterlockedExchangeAdd((long volatile*)addr, delta);
#endif
}
#endif // __GNUC__
#elif defined __APPLE__
#include
struct ImplMutex::Impl
{
void init() { sl = OS_SPINLOCK_INIT; refcount = 1; }
void destroy() { }
void lock() { OSSpinLockLock(&sl); }
bool trylock() { return OSSpinLockTry(&sl); }
void unlock() { OSSpinLockUnlock(&sl); }
OSSpinLock sl;
int refcount;
};
#elif defined __linux__ && !defined ANDROID
struct ImplMutex::Impl
{
void init() { pthread_spin_init(&sl, 0); refcount = 1; }
void destroy() { pthread_spin_destroy(&sl); }
void lock() { pthread_spin_lock(&sl); }
bool trylock() { return pthread_spin_trylock(&sl) == 0; }
void unlock() { pthread_spin_unlock(&sl); }
pthread_spinlock_t sl;
int refcount;
};
#else
struct ImplMutex::Impl
{
void init() { pthread_mutex_init(&sl, 0); refcount = 1; }
void destroy() { pthread_mutex_destroy(&sl); }
void lock() { pthread_mutex_lock(&sl); }
bool trylock() { return pthread_mutex_trylock(&sl) == 0; }
void unlock() { pthread_mutex_unlock(&sl); }
pthread_mutex_t sl;
int refcount;
};
#endif
上述条件编译最后在NDK下走到了#else,而pthread_mutex_init出自wp32threads.h,该头文件中第一依赖于windows.h。。。android下玩不转了。。。
另外,分析了下opencv4android的config发现也没打开ffmpeg,同时从论坛里看到某外说道现版本opencv在android上确不支持ffmpeg。所以本人认为现版本opencv从源码上对android下的ffmpeg调用就不支持,这一点如果各位有别的见解,欢迎讨论。
接上文,所以本人在编译highgui模块时也关闭了ffmpeg相关的依赖,但为了能够支持图片的存取操作,打开了jpeg,png的依赖,所以在mk文件中有
LOCAL_C_INCLUDES := \
/usr/include \
/usr/include/x86_64-linux-gnu
LOCAL_SHARED_LIBRARIES += libjpeg libpng
这两个库的编译也是从相关官网上下载源码,编写mk文件,这两个库的mk文件是从网上找来的,粘到jpeg和png源码目录下,ndk-build异常顺利 :-). 下面也贴上两个mk的代码(转载):
jpeg下Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libjpeg
LOCAL_SRC_FILES := \
jaricom.c jcapimin.c jcapistd.c jcarith.c jccoefct.c jccolor.c \
jcdctmgr.c jchuff.c jcinit.c jcmainct.c jcmarker.c jcmaster.c \
jcomapi.c jcparam.c jcprepct.c jcsample.c jctrans.c jdapimin.c \
jdapistd.c jdarith.c jdatadst.c jdatasrc.c jdcoefct.c jdcolor.c \
jddctmgr.c jdhuff.c jdinput.c jdmainct.c jdmarker.c jdmaster.c \
jdmerge.c jdpostct.c jdsample.c jdtrans.c jerror.c jfdctflt.c \
jfdctfst.c jfdctint.c jidctflt.c jidctfst.c jidctint.c jquant1.c \
jquant2.c jutils.c jmemmgr.c jmemnobs.c
include $(BUILD_SHARED_LIBRARY)
png下Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libpng
LOCAL_SRC_FILES := png.c pngerror.c pngget.c pngmem.c pngpread.c pngread.c pngrio.c \
pngrtran.c pngrutil.c pngset.c pngtrans.c pngwio.c pngwrite.c pngwtran.c \
pngwutil.c
LOCAL_LDLIBS := -lz
include $(BUILD_SHARED_LIBRARY)
PS:难怪异常顺利,真真没啥依赖。。。
这样,opencv编译中mk上就没啥可啰嗦的了。但opencv有另外一些重要的依赖本人是在eclipse中设置的,主要是标准C++的支持。在这里提醒大家,编译opencv中一定要使用标准C++,这个在NDK中有带的,具体在sources/cxx-stl/gnu-libstdc++下。如果使用stlport等其他一些C++库,会出现一些问题。本人最初就是使用的stlport,出现了一些问题,为了fix自己改stlport改来改去头大了很麻烦。
下面给出eclipse中的相关设置:(C/C++ general下paths and symbols中添加)
${NDKROOT}/toolchains/arm-linux-androideabi-4.6/prebuilt/linux-x86_64/lib/gcc/arm-linux-androideabi/4.6/include
${NDKROOT}/sources/cxx-stl/gnu-libstdc++/4.6/include
${NDKROOT}/sources/cxx-stl/gnu-libstdc++/4.6/libs/armeabi-v7a/include
${NDKROOT}/platforms/android-9/arch-arm/usr/include
/home/yxh/OpenCV-2.4.8-android-sdk/sdk/native/jni/include
/usr/include
${JAVA_HOME}/include
${NDKROOT}/platforms/android-12/arch-arm/usr/include/
PS:有些可以去掉,NDK几个应该是必须的,请自行根据实际情况实验。
设置完以上的东西,最后给出jni下Application.mk:
APP_ABI := armeabi-v7a
APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
APP_PLATFORM := android-8
上面APP_ABI应该设置成相应处理器的,好像有个$ABI的东西可以自适应
这样,在eclipse下ndk-build就可以生成libopencv_core,libopencv_imgproc,libopencv_highgui三个so库了。这样就可以在自己jni开发中使用Opencv了~