Articles8
Tags7
Categories2

OpenCV2.4.13.6在Android上的采坑(native扩展nonfree)

最近在做一个小组作业,利用SUFT实现图像拼接。采用的是OpenCV2.4.13.6,而在搭建的时候发现Android pack中并没有nonfree文件夹(包括nonfree/features2d.hpp头文件),而libs/**abi/文件夹中也没有相关的libnonfree.so文件,所以并不能使用SUFT。

所以只好利用NDK自己构建.so文件。参考前辈的使用方法

使用工具:

  • ndk-bundle工具
  • opencv-2.4.13.6-android-sdk
  • opencv-2.4.13.6-vc14(注意版本必须和android-sdk相同)

开始构建libnonfree.so文件

1.首先解压下载的opencv-2.4.13.6-android-sdk.zip文件,注意文件目录不能含有中文(否则后面.mk可能会编码错乱),如:F:\opencv-2.4.13.6-android-sdk

2.安装opencv-2.4.13.6-vc14.exe,得到如下文件夹

3.打开目录sources/modules/nonfree/include/opencv2,将其中的nonfree文件夹复制到F:/opencv-2.4.13.6-android-sdk/OpenCV-android-sdk/sdk/native/jni/include/opencv2文件夹中

4.在任意位置新建一个libnonfree文件夹,并在其中新建jni文件夹,例如我新建在F:/opencv-2.4.13.6-android-sdk/OpenCV-android-sdk/sdk/native/jni/include/libnonfree

再将opencv-2.4.13.6-vc14文件夹sources/modules/nonfree/src位置的nonfree_init.cppprecomp.hppsift.cppsurf.cpp四个文件复制到之前新建的libnonfree/jni文件夹中。

5.在libnonfree/jni文件夹中新建两个文件Android.mkApplication.mk,此时你的文件目录应该是这样

编辑Android.mk文件:

LOCAL_PATH  := $(call my-dir)
OPENCV_PATH := F:\opencv-2.4.13.6-android-sdk\OpenCV-android-sdk\sdk\native\jni

include $(CLEAR_VARS)
OPENCV_INSTALL_MODULES := on
OPENCV_CAMERA_MODULES  := off
include $(OPENCV_PATH)/OpenCV.mk

LOCAL_C_INCLUDES :=             \
    $(LOCAL_PATH)               \
    $(OPENCV_PATH)/include

LOCAL_SRC_FILES :=              \
    nonfree_init.cpp            \
    sift.cpp                    \
    surf.cpp

LOCAL_MODULE := nonfree
LOCAL_CFLAGS := -Werror -O3 -ffast-math
LOCAL_LDLIBS := -llog -ldl

include $(BUILD_SHARED_LIBRARY)

编辑Application.mk文件:

APP_STL := gnustl_shared
APP_CPPFLAGS := -std=c++11 -frtti -fexceptions
APP_ABI := armeabi,armeabi-v7a,mips,x86
APP_PLATFORM := android-21

6.完成上面的步骤后,就可以使用命令行工具进入F:/opencv-2.4.13.6-android-sdk/OpenCV-android-sdk/sdk/native/jni/include/libnonfree文件夹,再使用ndk-build命令,如遇到“‘ndk-build’不是内部或外部命令,也不是可运行的程序或批处理文件”,请检查下载ndk-bundle工具并在环境变量中配置好ndk-build

此时执行,会下列一系类报错,例如

一次性解决方案:
第一步,打开precomp.hpp注释掉第46行#include "cvconfig.h"和第66行# include "opencv2/ocl/private/util.hpp";第二步,打开nonfree_init.cpp文件,注释掉第66到第77行,OPENCV_OCL部分,完成

再次执行‘ndk-build’,通过!

此时你的libnonfree文件夹里多了两个文件夹libsobj,其中libs里面的全部.so文件就是我们需要的文件啦!

开始使用SUFT

1.在任意位置新建一个libnonfree_demo文件夹,并在其中新建jni文件夹,例如我建立在libnonfree文件夹同级目处

以armeabi架构为例,将上面的libnonfree/libs/armeabi中所有的.so文件拷贝到jni文件中

2.在文件夹中新建一个.cpp文件,例如test.cpp

#include <jni.h>
#include <android/log.h>

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/nonfree/features2d.hpp>
#include <opencv2/nonfree/nonfree.hpp>
#include <iostream>

using namespace cv;
using namespace std;

#define  LOG_TAG    "nonfree_demo"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)

int run_demo();

extern "C" {
JNIEXPORT void JNICALL Java_com_zzapp_nonfree_MainActivity_runDemo(JNIEnv * env, jobject obj);
};

JNIEXPORT void JNICALL Java_com_zzapp_nonfree_MainActivity_runDemo(JNIEnv * env, jobject obj)
{
    LOGI( "开始demo\n");
    run_demo();
    LOGI( "结束demo\n");
}

int run_demo()
{
    // 输入输出图片地址
    const char * imgInFile = "/sdcard/nonfree/image.jpg";
    const char * imgOutFile = "/sdcard/nonfree/image_result.jpg";

    Mat image;
    image = imread(imgInFile, CV_LOAD_IMAGE_COLOR);
    if(! image.data )
    {
        LOGI("图片不存在\n");
        return -1;
    }

    vector<KeyPoint> keypoints;
    Mat descriptors;


    // 计算特征描述
    SiftFeatureDetector detector;
    LOGI("检测到%d个特征点\n", (int) keypoints.size());
    detector.detect(image, keypoints);
    LOGI("计算特征描述\n");
    detector.compute(image,keypoints, descriptors);

    // 存储特征描述
    FileStorage fs;
    LOGI("以写入方式打开文件\n");
    fs.open("descriptors.des", FileStorage::WRITE);
    LOGI("写入文件\n");
    fs << "descriptors" << descriptors;
    LOGI("释放文件\n");
    fs.release();

    // 显示图片关键点
    Mat outputImg;
    Scalar keypointColor = Scalar(255, 0, 0);
    LOGI("绘制图片关键点\n");
    drawKeypoints(image, keypoints, outputImg, keypointColor, DrawMatchesFlags::DRAW_RICH_KEYPOINTS);

    LOGI("存储生成图片\n");
    imwrite(imgOutFile, outputImg);

    LOGI("完成\n");
    return 0;
}

注意其中的方法名Java_com_zzapp_nonfree_MainActivity_runDemo,你需要将其中的com_zzapp_nonfree_MainActivity更改为你的包名加上使用方法的Activity名(包名在后面的使用中应用到,应提前想好)

3.在libnonfree_demo/jni文件夹中新建两个文件Android.mkApplication.mk,此时你的文件目录应该是这样

编辑Android.mk文件:

LOCAL_PATH  := $(call my-dir)
OPENCV_PATH := F:\opencv-2.4.13.6-android-sdk\OpenCV-android-sdk\sdk\native\jni

include $(CLEAR_VARS)
LOCAL_MODULE     := nonfree
LOCAL_SRC_FILES  := libnonfree.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
OPENCV_INSTALL_MODULES := on
OPENCV_CAMERA_MODULES  := off
include $(OPENCV_PATH)/OpenCV.mk

LOCAL_C_INCLUDES :=             \
    $(LOCAL_PATH)               \
    $(OPENCV_PATH)/include

LOCAL_SRC_FILES :=              \
    test.cpp

LOCAL_MODULE := nonfree_demo
LOCAL_CFLAGS := -Werror -O3 -ffast-math
LOCAL_LDLIBS := -llog -ldl
LOCAL_SHARED_LIBRARIES += nonfree

include $(BUILD_SHARED_LIBRARY)

编辑Application.mk文件:

APP_STL := gnustl_shared
APP_CPPFLAGS := -std=c++11 -frtti -fexceptions
APP_ABI := armeabi
APP_PLATFORM := android-21

4.同上,使用命令行工具进入F:/opencv-2.4.13.6-android-sdk/OpenCV-android-sdk/sdk/native/jni/include/libnonfree_demo文件夹,再使用ndk-build命令,成功后,在libnonfree_demo/libs/armeabi里生成了几个.so文件,包括一个libnonfree_demo.so文件

此时自己的.so文件已生成完毕,即可使用啦!

Android使用.so文件

1.新建项目,对于我们的上面的.so文件,我们新建一个项目,要和上面test.cpp文件方法Java_com_zzapp_nonfree_MainActivity_runDemo中一致

2.使用.so有两种方法,在build.gradle文件中添加和制作.jar文件添加,这里我更偏向于后者。所以来制作.jar文件。

新建lib文件夹(注意是“lib”,而不是“libs”),在其中新建一个文件夹armeabi,将libnonfree_demo/libs/armeabi中的所有.so文件拷贝到armeabi文件夹中。以zip方式压缩lib文件夹,并重命名,改为.jar后缀名,例如so.jar

3.将so.jar文件拷贝到项目的libs下,右键选择Add As Library...作为库添加到项目。

4.修改MainActivity文件

package com.zzapp.nonfree

import android.support.v7.app.AppCompatActivity
import android.os.Bundle

/**
 * Project Nonfree
 * Date 2018/4/25
 *
 * 主活动
 *
 * @author zzzz
 */
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 使用方法
        runDemo()
    }

    // 库里面的方法
    external fun runDemo()

    companion object {
        init {
            // 加载.so库文件
            System.loadLibrary("nonfree_demo")
        }
    }
}

5.构建前的最后一步,添加权限。因为在test.cpp中,我们有读写外部文件的操作,所以应在AndroidManifest.xml文件中添加两条权限

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

在手机/sdcard/nonfree目录下新建文件image.jpg,打包运行应用。成功!!

Author:zzzz1997
Link:https://zzzz1997.github.io/2019/09/11/OpenCV2-4-13-6/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可